diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml
index a8f525880..01c152fc9 100644
--- a/.github/workflows/translate.yml
+++ b/.github/workflows/translate.yml
@@ -1,17 +1,13 @@
-
name: Weekly Translation Workflow
on:
schedule:
- # 每周日 UTC 02:00 执行 (北京时间周日 10:00)
- cron: '0 2 * * 0'
- workflow_dispatch: # 允许手动触发
-
+ workflow_dispatch:
permissions:
contents: write
-
jobs:
translate:
runs-on: ubuntu-latest
@@ -50,148 +46,115 @@ jobs:
return bool(re.search(r'[\u4e00-\u9fff]', text))
def translate_text(text, target_lang):
+ if not text.strip() or not is_chinese(text):
+ return text
try:
- return GoogleTranslator(source='zh-CN', target=target_lang).translate(text)
+ # 过滤掉一些不该翻译的特殊符号
+ clean_text = text.strip()
+ result = GoogleTranslator(source='zh-CN', target=target_lang).translate(clean_text)
+ return result
except Exception as e:
- print(f"\nTranslation error: {e}")
+ print(f"\n[Error] {e}")
return text
-
- def translate_line_preserving_variables(line, target_lang):
+
+ def process_content_with_vars(content, target_lang):
"""
- Translate only Chinese parts in echo/read/send_stats commands, excluding shell variables
+ 核心逻辑:保护 ${var} 和 $var,翻译其中的中文部分
"""
- # Match double or single quoted strings
- def repl(match):
- full_string = match.group(0)
- quote = full_string[0]
- content = full_string[1:-1]
- # Split by variable expressions
- parts = re.split(r'(\$\{?\w+\}?)', content)
- translated_parts = [
- translate_text(p, target_lang) if is_chinese(p) else p
- for p in parts
- ]
- return quote + ''.join(translated_parts) + quote
-
- return re.sub(r'(?:\'[^\']*\'|"[^"]*")', repl, line)
+ # 匹配 ${var} 或 $var (字母数字下划线)
+ parts = re.split(r'(\$\{\w+\}|\$\w+)', content)
+ translated_parts = []
+ for p in parts:
+ if p.startswith('$'): # 变量部分,保持原样
+ translated_parts.append(p)
+ elif is_chinese(p): # 中文部分,翻译
+ translated_parts.append(translate_text(p, target_lang))
+ else: # 其他英文/符号,保持原样
+ translated_parts.append(p)
+ return "".join(translated_parts)
+
+ def universal_translator(line, target_lang):
+ """
+ 通用翻译引擎:识别行内所有引号内容并翻译
+ """
+ # 1. 保护注释行
+ leading_space = re.match(r'^(\s*)', line).group(1)
+ stripped = line.strip()
+ if stripped.startswith('#'):
+ comment_content = stripped[1:].strip()
+ if is_chinese(comment_content):
+ return f"{leading_space}# {translate_text(comment_content, target_lang)}\n"
+ return line
+
+ # 2. 识别所有引号内的内容 (双引号或单引号)
+ # 使用正则匹配引号对,同时处理转义引号 \"
+ def replacer(match):
+ quote_type = match.group(1) # ' 或 "
+ content = match.group(2) # 引号内的文本内容
+ if is_chinese(content):
+ # 翻译内容,但保护里面的变量
+ translated = process_content_with_vars(content, target_lang)
+ return f"{quote_type}{translated}{quote_type}"
+ return match.group(0)
+
+ # 匹配 "content" 或 'content'
+ new_line = re.sub(r'([\'"])(.*?)(? Google翻译语言代码
- languages = {
- 'en': 'en', # 英语
- 'tw': 'zh-TW', # 繁体中文
- 'kr': 'ko', # 韩语
- 'jp': 'ja' # 日语
- }
-
- success_count = 0
-
- for dir_name, lang_code in languages.items():
- output_file = f'{dir_name}/kejilion.sh'
- if translate_file(input_file, output_file, lang_code):
- success_count += 1
- print(f"✓ Successfully translated to {dir_name}")
- else:
- print(f"✗ Failed to translate to {dir_name}")
- print("-" * 50)
-
- print(f"\nTranslation summary: {success_count}/{len(languages)} languages completed")
-
- if success_count == 0:
- sys.exit(1)
+ langs = {'en': 'en', 'tw': 'zh-TW', 'kr': 'ko', 'jp': 'ja'}
+ for dir_name, lang_code in langs.items():
+ translate_file(input_file, f'{dir_name}/kejilion.sh', lang_code)
EOF
- name: Run translation
- run: |
- python translate.py
+ run: python translate.py
- name: Check for changes
id: check_changes
run: |
git add .
- if git diff --staged --quiet; then
- echo "has_changes=false" >> $GITHUB_OUTPUT
- else
- echo "has_changes=true" >> $GITHUB_OUTPUT
- fi
+ git diff --staged --quiet || echo "has_changes=true" >> $GITHUB_OUTPUT
- name: Commit and push changes
if: steps.check_changes.outputs.has_changes == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- git add .
git commit -m "🌐 Weekly translation update - $(date +'%Y-%m-%d %H:%M:%S')"
git push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
+
- name: Create summary
if: always()
run: |
echo "## Translation Summary 📊" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "| Language | Directory | Status |" >> $GITHUB_STEP_SUMMARY
- echo "|----------|-----------|--------|" >> $GITHUB_STEP_SUMMARY
-
+ echo "| Language | Status |" >> $GITHUB_STEP_SUMMARY
+ echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
for dir in en tw kr jp; do
- if [ -f "$dir/kejilion.sh" ]; then
- size=$(stat -c%s "$dir/kejilion.sh")
- echo "| $dir | /$dir/ | ✅ Success (${size} bytes) |" >> $GITHUB_STEP_SUMMARY
- else
- echo "| $dir | /$dir/ | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
- fi
+ [ -f "$dir/kejilion.sh" ] && status="✅ Success" || status="❌ Failed"
+ echo "| $dir | $status |" >> $GITHUB_STEP_SUMMARY
done
-
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "**Execution time:** $(date)" >> $GITHUB_STEP_SUMMARY
-
- if [ "${{ steps.check_changes.outputs.has_changes }}" == "true" ]; then
- echo "**Changes:** New translations committed and pushed" >> $GITHUB_STEP_SUMMARY
- else
- echo "**Changes:** No changes detected" >> $GITHUB_STEP_SUMMARY
- fi
-
-
diff --git a/README.ja.md b/README.ja.md
index 675d9e886..16a0d59de 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -27,7 +27,7 @@ bash <(curl -sL kejilion.sh) jp
***
## 🖼️ 実際のスクリーンショット
-
+
***
diff --git a/README.kr.md b/README.kr.md
index c2fa28aa2..38ff36ce5 100644
--- a/README.kr.md
+++ b/README.kr.md
@@ -28,7 +28,7 @@ bash <(curl -sL kejilion.sh) kr
## 🖼️ 실제 화면 미리보기 (Preview)
-
+
diff --git a/README.md b/README.md
index e33b8f7be..9be65afee 100644
--- a/README.md
+++ b/README.md
@@ -98,8 +98,8 @@ bash <(curl -sL kejilion.sh) en
## 🖼️ 效果图预览 (Preview)
@@ -90,7 +90,7 @@ bash <(curl -sL kejilion.sh) ru
## 🖼️ Предпросмотр интерфейса
-

+

diff --git a/README.tw.md b/README.tw.md
index 8efdbe335..3f5eaad15 100644
--- a/README.tw.md
+++ b/README.tw.md
@@ -27,7 +27,7 @@ bash <(curl -sL kejilion.sh) tw
## 🖼️ 실제 화면 미리보기 (Preview)
-

+

diff --git a/apps/README.md b/apps/README.md
new file mode 100644
index 000000000..e238e5132
--- /dev/null
+++ b/apps/README.md
@@ -0,0 +1,4 @@
+# 🚀 开发者应用入驻指南 (kejilion.sh)
+
+https://github.com/kejilion/apps
+
diff --git a/auto_cert_renewal.sh b/auto_cert_renewal.sh
index 4d574d6dc..796b9b566 100644
--- a/auto_cert_renewal.sh
+++ b/auto_cert_renewal.sh
@@ -23,35 +23,64 @@ for cert_file in $certs_directory*_cert.pem; do
# 计算距离过期还有几天
days_until_expiry=$(( ($expiration_timestamp - $current_timestamp) / 86400 ))
- # 检查是否需要续签(在满足续签条件的情况下)
if [ $days_until_expiry -le $days_before_expiry ]; then
+
echo "证书将在${days_before_expiry}天内过期,正在进行自动续签。"
-
- docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n
- docker stop nginx > /dev/null 2>&1
+ # 1. 检查目录是否存在
+ docker exec nginx [ -d /var/www/letsencrypt ] && DIR_OK=true || DIR_OK=false
+
+ # 2. 检查配置文件是否包含关键字
+ # 假设你的配置文件在容器内的 /etc/nginx/conf.d/ 目录下(这是 Nginx 容器的默认路径)
+ docker exec nginx grep -q "letsencrypt" /etc/nginx/conf.d/$yuming.conf && CONF_OK=true || CONF_OK=false
+
+ # 输出结果
+ echo "--- 自动化环境检测报告 ---"
+ if [ "$DIR_OK" = true ]; then echo "✅ 目录检测:/var/www/letsencrypt 存在"; else echo "❌ 目录检测:/var/www/letsencrypt 不存在"; fi
+ if [ "$CONF_OK" = true ]; then echo "✅ 配置检测:$yuming.conf 已包含续签规则"; else echo "❌ 配置检测:$yuming.conf 未发现 letsencrypt 字样"; fi
+
+ if [ "$DIR_OK" = true ] && [ "$CONF_OK" = true ]; then
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n
+
+ docker run --rm \
+ -v "/etc/letsencrypt:/etc/letsencrypt" \
+ -v "/home/web/letsencrypt:/var/www/letsencrypt" \
+ certbot/certbot certonly \
+ --webroot \
+ -w /var/www/letsencrypt \
+ -d "$yuming" \
+ --email your@email.com \
+ --agree-tos \
+ --no-eff-email \
+ --key-type ecdsa \
+ --force-renewal
+
+ mkdir -p /home/web/certs/
+ cp /etc/letsencrypt/live/$yuming/fullchain.pem /home/web/certs/${yuming}_cert.pem > /dev/null 2>&1
+ cp /etc/letsencrypt/live/$yuming/privkey.pem /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
+
+ openssl rand -out /home/web/certs/ticket12.key 48
+ openssl rand -out /home/web/certs/ticket13.key 80
+
+ docker exec nginx nginx -t && docker exec nginx nginx -s reload
+
+ else
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n
- iptables -P INPUT ACCEPT
- iptables -P FORWARD ACCEPT
- iptables -P OUTPUT ACCEPT
- iptables -F
+ docker stop nginx > /dev/null 2>&1
- ip6tables -P INPUT ACCEPT
- ip6tables -P FORWARD ACCEPT
- ip6tables -P OUTPUT ACCEPT
- ip6tables -F
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d $yuming --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
- docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d $yuming --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ mkdir -p /home/web/certs/
+ cp /etc/letsencrypt/live/$yuming/fullchain.pem /home/web/certs/${yuming}_cert.pem > /dev/null 2>&1
+ cp /etc/letsencrypt/live/$yuming/privkey.pem /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
- mkdir -p /home/web/certs/
- cp /etc/letsencrypt/live/$yuming/fullchain.pem /home/web/certs/${yuming}_cert.pem > /dev/null 2>&1
- cp /etc/letsencrypt/live/$yuming/privkey.pem /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
+ openssl rand -out /home/web/certs/ticket12.key 48
+ openssl rand -out /home/web/certs/ticket13.key 80
- openssl rand -out /home/web/certs/ticket12.key 48
- openssl rand -out /home/web/certs/ticket13.key 80
-
- docker start nginx > /dev/null 2>&1
+ docker start nginx > /dev/null 2>&1
+ fi
echo "证书已成功续签。"
else
diff --git a/client_config b/client_config
deleted file mode 100644
index 9c041b3db..000000000
--- a/client_config
+++ /dev/null
@@ -1,54 +0,0 @@
-#在oci=begin和oci=end之间放入你的API配置信息 支持多个配置文件 机器人切换profile可更换操作配置
-oci=begin
-
-[DEFAULT]
-user=ocid1.user.oc1..aaaaaaaaxxxxgwlg3xuzwgsaazxtzbozqq
-fingerprint=b8:33:6f:xxxx:45:43:33
-tenancy=ocid1.tenancy.oc1..aaaaaaaaxxx7x7h4ya
-region=ap-singapore-1
-key_file=写你的API密钥文件路径
-
-[DEFAULT2]
-user=ocid1.user.oc1..aaaaaaaaxxxxgwlg3xuzwgsaazxtzbozqq
-fingerprint=b8:33:6f:xxxx:45:43:33
-tenancy=ocid1.tenancy.oc1..aaaaaaaaxxx7x7h4ya
-region=ap-singapore-1
-key_file=写你的API密钥文件路径
-
-oci=end
-
-
-
-#用户信息 从 https://t.me/radiance_helper_bot 配置(bot可使用/raninfo命令随机生成)
-#必传
-username=
-#必传
-password=
-
-
-#cloudflare 功能参数 非必传
-#非必传 cloudflare邮箱
-cf_email=
-#非必传 cloudflare key 在我的个人资料->API令牌处->API密钥->Global API Key 获取
-cf_account_key=
-
-
-#非必填 本机ip和端口号 (进阶玩家选项 可填写域名) 不写将自动获取本机ip 并使用默认端口号9527 (小白用户建议不填) 如填写 格式为:https://xxx.xx:9527
-local_address=
-#非必填 url名称(默认为address 可在bot上修改)
-local_url_name=
-
-#非必填 启动模式 填写local为启动本地无公网IP模式(只要能联网即可) 不填或填其他 则启动端口模式
-model=
-
-
-
-#在azure=begin和azure=end之间放入你的azure的API配置信息 支持多个配置文件 机器人切换profile可更换操作配置 上传配置支持使用原格式({"appId":"xxx","password":"xxx"...})上传
-azure=begin
-
-[az001]
-appId=551xxxx7-xxxx-xxxx-xxxx-b9xxxx60cc65
-password=T618Q~.LIy_xxxxx~jm~xxxxxx
-tenant=xxxx3713-xxxx-4cb5-xxxx-3001060xxxxx
-
-azure=end
\ No newline at end of file
diff --git a/cn/kejilion.sh b/cn/kejilion.sh
index 1c2b0d9c1..a055869d9 100644
--- a/cn/kejilion.sh
+++ b/cn/kejilion.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-sh_v="4.0.5"
+sh_v="4.5.2"
gl_hui='\e[37m'
@@ -29,6 +29,8 @@ else
gh_proxy="https://"
fi
+gh_https_url="https://"
+
}
quanju_canshu
@@ -45,6 +47,8 @@ run_command() {
canshu_v6() {
if grep -q '^canshu="V6"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
+ elif grep -q '^canshu="V6"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
fi
}
@@ -52,6 +56,8 @@ canshu_v6() {
CheckFirstRun_true() {
if grep -q '^permission_granted="true"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
+ elif grep -q '^permission_granted="true"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
fi
}
@@ -86,6 +92,8 @@ yinsiyuanquan2() {
if grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
+elif grep -q '^ENABLE_STATS="false"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
fi
}
@@ -102,6 +110,7 @@ sed -i '/^alias k=/d' ~/.profile > /dev/null 2>&1
sed -i '/^alias k=/d' ~/.bash_profile > /dev/null 2>&1
cp -f ./kejilion.sh ~/kejilion.sh > /dev/null 2>&1
cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
@@ -118,7 +127,7 @@ UserLicenseAgreement() {
echo "首次使用脚本,请先阅读并同意用户许可协议。"
echo "用户许可协议: https://blog.kejilion.pro/user-license-agreement/"
echo -e "----------------------"
- read -r -p "是否同意以上条款?(y/n): " user_input
+ read -e -p "是否同意以上条款?(y/n): " user_input
if [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]; then
@@ -154,7 +163,7 @@ public_ip=$(get_public_ip)
isp_info=$(curl -s --max-time 3 http://ipinfo.io/org)
-if echo "$isp_info" | grep -Eiq 'china|mobile|unicom|telecom'; then
+if echo "$isp_info" | grep -Eiq 'CHINANET|mobile|unicom|telecom'; then
ipv4_address=$(get_local_ip)
else
ipv4_address="$public_ip"
@@ -176,7 +185,7 @@ install() {
for package in "$@"; do
if ! command -v "$package" &>/dev/null; then
- echo -e "${gl_huang}正在安装 $package...${gl_bai}"
+ echo -e "${gl_kjlan}正在安装 $package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
dnf install -y epel-release
@@ -213,12 +222,15 @@ install() {
check_disk_space() {
+ local required_gb=$1
+ local path=${2:-/}
- required_gb=$1
- required_space_mb=$((required_gb * 1024))
- available_space_mb=$(df -m / | awk 'NR==2 {print $4}')
+ mkdir -p "$path"
- if [ $available_space_mb -lt $required_space_mb ]; then
+ local required_space_mb=$((required_gb * 1024))
+ local available_space_mb=$(df -m "$path" | awk 'NR==2 {print $4}')
+
+ if [ "$available_space_mb" -lt "$required_space_mb" ]; then
echo -e "${gl_huang}提示: ${gl_bai}磁盘空间不足!"
echo "当前可用空间: $((available_space_mb/1024))G"
echo "最小需求空间: ${required_gb}G"
@@ -230,8 +242,15 @@ check_disk_space() {
}
+
install_dependency() {
+ switch_mirror false false
+ check_port
+ check_swap
+ prefer_ipv4
+ auto_optimize_dns
install wget unzip tar jq grep
+
}
remove() {
@@ -241,7 +260,7 @@ remove() {
fi
for package in "$@"; do
- echo -e "${gl_huang}正在卸载 $package...${gl_bai}"
+ echo -e "${gl_kjlan}正在卸载 $package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf remove -y "$package"
elif command -v yum &>/dev/null; then
@@ -349,22 +368,22 @@ kejilion() {
-check_port() {
- install lsof
+stop_containers_or_kill_process() {
+ local port=$1
+ local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
- stop_containers_or_kill_process() {
- local port=$1
- local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
+ if [ -n "$containers" ]; then
+ docker stop $containers
+ else
+ install lsof
+ for pid in $(lsof -t -i:$port); do
+ kill -9 $pid
+ done
+ fi
+}
- if [ -n "$containers" ]; then
- docker stop $containers
- else
- for pid in $(lsof -t -i:$port); do
- kill -9 $pid
- done
- fi
- }
+check_port() {
stop_containers_or_kill_process 80
stop_containers_or_kill_process 443
}
@@ -377,23 +396,23 @@ if [ "$country" = "CN" ]; then
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
- "https://docker-0.unsee.tech",
- "https://docker.1panel.live",
- "https://registry.dockermirror.com",
- "https://docker.imgdb.de",
- "https://docker.m.daocloud.io",
- "https://hub.firefly.store",
- "https://hub.littlediary.cn",
+ "https://docker.1ms.run",
+ "https://docker.m.ixdev.cn",
"https://hub.rat.dev",
- "https://dhub.kubesre.xyz",
- "https://cjie.eu.org",
- "https://docker.1panelproxy.com",
+ "https://dockerproxy.net",
+ "https://docker-registry.nmqu.com",
+ "https://docker.amingg.com",
"https://docker.hlmirror.com",
- "https://hub.fast360.xyz",
- "https://dockerpull.cn",
- "https://cr.laoyou.ip-ddns.com",
- "https://docker.melikeme.cn",
- "https://docker.kejilion.pro"
+ "https://hub1.nat.tf",
+ "https://hub2.nat.tf",
+ "https://hub3.nat.tf",
+ "https://docker.m.daocloud.io",
+ "https://docker.kejilion.pro",
+ "https://docker.367231.xyz",
+ "https://hub.1panel.dev",
+ "https://dockerproxy.cool",
+ "https://docker.apiba.cn",
+ "https://proxy.vvvv.ee"
]
}
EOF
@@ -407,80 +426,40 @@ restart docker
}
-install_add_docker_guanfang() {
+
+linuxmirrors_install_docker() {
+
local country=$(curl -s ipinfo.io/country)
if [ "$country" = "CN" ]; then
- cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/install && chmod +x install
- sh install --mirror Aliyun
- rm -f install
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source mirrors.huaweicloud.com/docker-ce \
+ --source-registry docker.1ms.run \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
else
- curl -fsSL https://get.docker.com | sh
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source download.docker.com \
+ --source-registry registry.hub.docker.com \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
fi
-install_add_docker_cn
+install_add_docker_cn
}
install_add_docker() {
- echo -e "${gl_huang}正在安装docker环境...${gl_bai}"
- if [ -f /etc/os-release ] && grep -q "Fedora" /etc/os-release; then
- install_add_docker_guanfang
- elif command -v dnf &>/dev/null; then
- dnf update -y
- dnf install -y yum-utils device-mapper-persistent-data lvm2
- rm -f /etc/yum.repos.d/docker*.repo > /dev/null
- country=$(curl -s ipinfo.io/country)
- arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo | tee /etc/yum.repos.d/docker-ce.repo > /dev/null
- else
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo > /dev/null
- fi
- dnf install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
- elif [ -f /etc/os-release ] && grep -q "Kali" /etc/os-release; then
- apt update
- apt upgrade -y
- apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
- rm -f /usr/share/keyrings/docker-archive-keyring.gpg
- local country=$(curl -s ipinfo.io/country)
- local arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- else
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- fi
- apt update
- apt install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
-
- elif command -v apt &>/dev/null || command -v yum &>/dev/null; then
- install_add_docker_guanfang
+ echo -e "${gl_kjlan}正在安装docker环境...${gl_bai}"
+ if command -v apt &>/dev/null || command -v yum &>/dev/null || command -v dnf &>/dev/null; then
+ linuxmirrors_install_docker
else
install docker docker-compose
install_add_docker_cn
@@ -657,7 +636,7 @@ while true; do
send_stats "拉取镜像"
read -e -p "请输入镜像名(多个镜像名请用空格分隔): " imagenames
for name in $imagenames; do
- echo -e "${gl_huang}正在获取镜像: $name${gl_bai}"
+ echo -e "${gl_kjlan}正在获取镜像: $name${gl_bai}"
docker pull $name
done
;;
@@ -665,7 +644,7 @@ while true; do
send_stats "更新镜像"
read -e -p "请输入镜像名(多个镜像名请用空格分隔): " imagenames
for name in $imagenames; do
- echo -e "${gl_huang}正在更新镜像: $name${gl_bai}"
+ echo -e "${gl_kjlan}正在更新镜像: $name${gl_bai}"
docker pull $name
done
;;
@@ -1025,91 +1004,84 @@ disable_ddos_defense() {
# 管理国家IP规则的函数
manage_country_rules() {
local action="$1"
- local country_code="$2"
- local ipset_name="${country_code,,}_block"
- local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
+ shift # 去掉第一个参数,剩下的全是国家代码
install ipset
- case "$action" in
- block)
- # 如果 ipset 不存在则创建
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ for country_code in "$@"; do
+ local ipset_name="${country_code,,}_block"
+ local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
- # 下载 IP 区域文件
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "错误:下载 $country_code 的 IP 区域文件失败"
- exit 1
- fi
+ case "$action" in
+ block)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
- # 将 IP 添加到 ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "错误:下载 $country_code 的 IP 区域文件失败"
+ continue
+ fi
- # 使用 iptables 阻止 IP
- iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- iptables -I OUTPUT -m set --match-set "$ipset_name" dst -j DROP
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- echo "已成功阻止 $country_code 的 IP 地址"
- rm "${country_code,,}.zone"
- ;;
+ iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- allow)
- # 为允许的国家创建 ipset(如果不存在)
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ echo "已成功阻止 $country_code 的 IP 地址"
+ rm "${country_code,,}.zone"
+ ;;
- # 下载 IP 区域文件
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "错误:下载 $country_code 的 IP 区域文件失败"
- exit 1
- fi
+ allow)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
+
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "错误:下载 $country_code 的 IP 区域文件失败"
+ continue
+ fi
- # 删除现有的国家规则
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
- ipset flush "$ipset_name"
+ ipset flush "$ipset_name"
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- # 将 IP 添加到 ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
- # 仅允许指定国家的 IP
- iptables -P INPUT DROP
- iptables -P OUTPUT DROP
- iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- iptables -A OUTPUT -m set --match-set "$ipset_name" dst -j ACCEPT
+ iptables -P INPUT DROP
+ iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- echo "已成功仅允许 $country_code 的 IP 地址"
- rm "${country_code,,}.zone"
- ;;
+ echo "已成功允许 $country_code 的 IP 地址"
+ rm "${country_code,,}.zone"
+ ;;
- unblock)
- # 删除国家的 iptables 规则
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
+ unblock)
+ iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- # 销毁 ipset
- if ipset list "$ipset_name" &> /dev/null; then
- ipset destroy "$ipset_name"
- fi
+ if ipset list "$ipset_name" &> /dev/null; then
+ ipset destroy "$ipset_name"
+ fi
- echo "已成功解除 $country_code 的 IP 地址限制"
- ;;
+ echo "已成功解除 $country_code 的 IP 地址限制"
+ ;;
- *)
- ;;
- esac
+ *)
+ echo "用法: manage_country_rules {block|allow|unblock}
"
+ ;;
+ esac
+ done
}
+
+
+
+
+
+
iptables_panel() {
root_use
install iptables
@@ -1223,18 +1195,18 @@ iptables_panel() {
;;
15)
- read -e -p "请输入阻止的国家代码(如 CN, US, JP): " country_code
+ read -e -p "请输入阻止的国家代码(多个国家代码可用空格隔开如 CN US JP): " country_code
manage_country_rules block $country_code
send_stats "允许国家 $country_code 的IP"
;;
16)
- read -e -p "请输入允许的国家代码(如 CN, US, JP): " country_code
+ read -e -p "请输入允许的国家代码(多个国家代码可用空格隔开如 CN US JP): " country_code
manage_country_rules allow $country_code
send_stats "阻止国家 $country_code 的IP"
;;
17)
- read -e -p "请输入清除的国家代码(如 CN, US, JP): " country_code
+ read -e -p "请输入清除的国家代码(多个国家代码可用空格隔开如 CN US JP): " country_code
manage_country_rules unblock $country_code
send_stats "清除国家 $country_code 的IP"
;;
@@ -1252,8 +1224,6 @@ iptables_panel() {
-
-
add_swap() {
local new_swap=$1 # 获取传入的参数
@@ -1342,11 +1312,9 @@ ldnmp_v() {
install_ldnmp_conf() {
# 创建必要的目录和文件
- cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/redis web/log/nginx && touch web/docker-compose.yml
+ cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/stream.d web/redis web/log/nginx web/letsencrypt && touch web/docker-compose.yml
wget -O /home/web/nginx.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default10.conf
- wget -O /home/web/redis/valkey.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/valkey.conf
-
default_server_ssl
@@ -1362,31 +1330,69 @@ install_ldnmp_conf() {
}
+update_docker_compose_with_db_creds() {
+ cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
+ if ! grep -q "letsencrypt" /home/web/docker-compose.yml; then
+ wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
-install_ldnmp() {
+ dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+
+ sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
+ fi
+
+ if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
+ sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ fi
- check_swap
+}
- cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
- if ! grep -q "network_mode" /home/web/docker-compose.yml; then
- wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
- dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
- fi
- if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
- sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- fi
+auto_optimize_dns() {
+ # 获取国家代码(如 CN、US 等)
+ local country=$(curl -s ipinfo.io/country)
+
+ # 根据国家设置 DNS
+ if [ "$country" = "CN" ]; then
+ local dns1_ipv4="223.5.5.5"
+ local dns2_ipv4="183.60.83.19"
+ local dns1_ipv6="2400:3200::1"
+ local dns2_ipv6="2400:da00::6666"
+ else
+ local dns1_ipv4="1.1.1.1"
+ local dns2_ipv4="8.8.8.8"
+ local dns1_ipv6="2606:4700:4700::1111"
+ local dns2_ipv6="2001:4860:4860::8888"
+ fi
+
+ set_dns
+
+
+}
+
+
+prefer_ipv4() {
+grep -q '^precedence ::ffff:0:0/96 100' /etc/gai.conf 2>/dev/null \
+ || echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
+echo "已切换为 IPv4 优先"
+send_stats "已切换为 IPv4 优先"
+}
+
+
+
+
+install_ldnmp() {
+
+ update_docker_compose_with_db_creds
cd /home/web && docker compose up -d
sleep 1
@@ -1395,9 +1401,17 @@ install_ldnmp() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- restart_ldnmp
+
+ # mysql调优
+ wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
+ docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
+ rm -rf /home/custom_mysql_config.cnf
+
+ restart_ldnmp
+ sleep 2
+
clear
echo "LDNMP环境安装完毕"
echo "------------------------"
@@ -1422,13 +1436,12 @@ install_certbot() {
install_ssltls() {
docker stop nginx > /dev/null 2>&1
- check_port > /dev/null 2>&1
cd ~
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ ! -f "$file_path" ]; then
local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
- local ipv6_pattern='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))))$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
if [[ ($yuming =~ $ipv4_pattern || $yuming =~ $ipv6_pattern) ]]; then
mkdir -p /etc/letsencrypt/live/$yuming/
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -1438,7 +1451,7 @@ install_ssltls() {
openssl req -x509 -key /etc/letsencrypt/live/$yuming/privkey.pem -out /etc/letsencrypt/live/$yuming/fullchain.pem -days 5475 -subj "/C=US/ST=State/L=City/O=Organization/OU=Organizational Unit/CN=Common Name"
fi
else
- docker run -it --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
fi
fi
mkdir -p /home/web/certs/
@@ -1475,7 +1488,7 @@ if [ -z "$yuming" ]; then
fi
install_docker
install_certbot
-docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
install_ssltls_text
@@ -1534,12 +1547,69 @@ certs_status() {
echo -e "4. 防火墙限制 ➠ 检查80/443端口是否开放,确保验证可访问"
echo -e "5. 申请次数超限 ➠ Let's Encrypt有每周限额(5次/域名/周)"
echo -e "6. 国内备案限制 ➠ 中国大陆环境请确认域名是否备案"
- break_end
- clear
- echo "请再次尝试部署 $webname"
- add_yuming
- install_ssltls
- certs_status
+ echo "------------------------"
+ echo "1. 重新申请 2. 导入已有证书 0. 退出"
+ echo "------------------------"
+ read -e -p "请输入你的选择: " sub_choice
+ case $sub_choice in
+ 1)
+ send_stats "重新申请"
+ echo "请再次尝试部署 $webname"
+ add_yuming
+ install_ssltls
+ certs_status
+
+ ;;
+ 2)
+ send_stats "导入已有证书"
+
+ # 定义文件路径
+ local cert_file="/home/web/certs/${yuming}_cert.pem"
+ local key_file="/home/web/certs/${yuming}_key.pem"
+
+ mkdir -p /home/web/certs
+
+ # 1. 输入证书 (ECC 和 RSA 证书开头都是 BEGIN CERTIFICATE)
+ echo "请粘贴 证书 (CRT/PEM) 内容 (按两次回车结束):"
+ local cert_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$cert_content" == *"-----BEGIN"* ]] && break
+ cert_content+="${line}"$'\n'
+ done
+
+ # 2. 输入私钥 (兼容 RSA, ECC, PKCS#8)
+ echo "请粘贴 证书私钥 (Private Key) 内容 (按两次回车结束):"
+ local key_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$key_content" == *"-----BEGIN"* ]] && break
+ key_content+="${line}"$'\n'
+ done
+
+ # 3. 智能校验
+ # 只要包含 "BEGIN CERTIFICATE" 和 "PRIVATE KEY" 即可通过
+ if [[ "$cert_content" == *"-----BEGIN CERTIFICATE-----"* && "$key_content" == *"PRIVATE KEY-----"* ]]; then
+ echo -n "$cert_content" > "$cert_file"
+ echo -n "$key_content" > "$key_file"
+
+ chmod 644 "$cert_file"
+ chmod 600 "$key_file"
+
+ # 识别当前证书类型并显示
+ if [[ "$key_content" == *"EC PRIVATE KEY"* ]]; then
+ echo "检测到 ECC 证书已成功保存。"
+ else
+ echo "检测到 RSA 证书已成功保存。"
+ fi
+ auth_method="ssl_imported"
+ else
+ echo "错误:无效的证书或私钥格式!"
+ certs_status
+ fi
+ ;;
+ *)
+ exit
+ ;;
+ esac
fi
}
@@ -1561,6 +1631,40 @@ add_yuming() {
}
+check_ip_and_get_access_port() {
+ local yuming="$1"
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ read -e -p "请输入访问/监听端口,回车默认使用 80: " access_port
+ access_port=${access_port:-80}
+ fi
+}
+
+
+
+update_nginx_listen_port() {
+ local yuming="$1"
+ local access_port="$2"
+ local conf="/home/web/conf.d/${yuming}.conf"
+
+ # 如果 access_port 为空,则跳过
+ [ -z "$access_port" ] && return 0
+
+ # 删除所有 listen 行
+ sed -i '/^[[:space:]]*listen[[:space:]]\+/d' "$conf"
+
+ # 在 server { 后插入新的 listen
+ sed -i "/server {/a\\
+ listen ${access_port};\\
+ listen [::]:${access_port};
+" "$conf"
+}
+
+
+
add_db() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbname="${dbname}"
@@ -1571,28 +1675,8 @@ add_db() {
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "CREATE DATABASE $dbname; GRANT ALL PRIVILEGES ON $dbname.* TO \"$dbuse\"@\"%\";"
}
-reverse_proxy() {
- ip_address
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
- sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$ipv4_address/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$duankou|g" /home/web/conf.d/$yuming.conf
- nginx_http_on
- docker exec nginx nginx -s reload
-}
-
-
-restart_redis() {
- rm -rf /home/web/redis/*
- docker exec redis redis-cli FLUSHALL > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory 1gb > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory-policy allkeys-lru > /dev/null 2>&1
-}
-
-
restart_ldnmp() {
- restart_redis
docker exec nginx chown -R nginx:nginx /var/www/html > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/proxy > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/fastcgi > /dev/null 2>&1
@@ -1600,7 +1684,8 @@ restart_ldnmp() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi > /dev/null 2>&1
docker exec php chown -R www-data:www-data /var/www/html > /dev/null 2>&1
docker exec php74 chown -R www-data:www-data /var/www/html > /dev/null 2>&1
- cd /home/web && docker compose restart nginx php php74
+ cd /home/web && docker compose restart
+
}
@@ -1635,7 +1720,7 @@ phpmyadmin_upgrade() {
cd /home/web/
docker rm -f $ldnmp_pods > /dev/null 2>&1
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
- curl -sS -O https://raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
docker compose -f docker-compose.phpmyadmin.yml up -d
clear
ip_address
@@ -1694,7 +1779,6 @@ web_cache() {
send_stats "清理站点缓存"
cf_purge_cache
cd /home/web && docker compose restart
- restart_redis
}
@@ -1776,7 +1860,7 @@ check_waf_status() {
check_cf_mode() {
- if [ -f "/path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf" ]; then
+ if [ -f "/etc/fail2ban/action.d/cloudflare-docker.conf" ]; then
CFmessage=" cf模式已开启"
else
CFmessage=""
@@ -1847,6 +1931,43 @@ patch_wp_debug() {
}
+
+
+patch_wp_url() {
+ local HOME_URL="$1"
+ local SITE_URL="$2"
+ local TARGET_DIR="/home/web/html"
+
+ find "$TARGET_DIR" -type f -name "wp-config-sample.php" | while read -r FILE; do
+ # 删除旧定义
+ sed -i "/define(['\"]WP_HOME['\"].*/d" "$FILE"
+ sed -i "/define(['\"]WP_SITEURL['\"].*/d" "$FILE"
+
+ # 生成插入内容
+ INSERT="
+define('WP_HOME', '$HOME_URL');
+define('WP_SITEURL', '$SITE_URL');
+"
+
+ # 插入到 “Happy publishing” 之前
+ awk -v insert="$INSERT" '
+ /Happy publishing/ {
+ print insert
+ }
+ { print }
+ ' "$FILE" > "$FILE.tmp" && mv -f "$FILE.tmp" "$FILE"
+
+ echo "[+] Updated WP_HOME and WP_SITEURL in $FILE"
+ done
+}
+
+
+
+
+
+
+
+
nginx_br() {
local mode=$1
@@ -1990,19 +2111,11 @@ nginx_gzip() {
web_security() {
send_stats "LDNMP环境防御"
while true; do
+ check_f2b_status
check_waf_status
check_cf_mode
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "服务器网站防御程序 ${check_docker}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
+ echo -e "服务器网站防御程序 ${check_f2b_status}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
echo "------------------------"
echo "1. 安装防御程序"
echo "------------------------"
@@ -2024,11 +2137,16 @@ web_security() {
case $sub_choice in
1)
f2b_install_sshd
- cd /path/to/fail2ban/config/fail2ban/filter.d
+ cd /etc/fail2ban/filter.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/fail2ban-nginx-cc.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-418.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-deny.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-unauthorized.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-bad-request.conf
+
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- sed -i "/cloudflare/d" /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ sed -i "/cloudflare/d" /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
;;
5)
@@ -2042,56 +2160,57 @@ web_security() {
local xxx="fail2ban-nginx-cc"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-418"
+ local xxx="nginx-418"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-bad-request"
+ local xxx="nginx-bad-request"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-badbots"
+ local xxx="nginx-badbots"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-botsearch"
+ local xxx="nginx-botsearch"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-deny"
+ local xxx="nginx-deny"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-http-auth"
+ local xxx="nginx-http-auth"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-unauthorized"
+ local xxx="nginx-unauthorized"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-php-url-fopen"
+ local xxx="php-url-fopen"
f2b_status_xxx
echo "------------------------"
;;
7)
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
;;
8)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
+ tail -f /var/log/fail2ban.log
;;
9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
+ remove fail2ban
+ rm -rf /etc/fail2ban
crontab -l | grep -v "CF-Under-Attack.sh" | crontab - 2>/dev/null
echo "Fail2Ban防御程序已卸载"
+ break
;;
11)
install nano
- nano /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ nano /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
break
;;
12)
- docker exec -it fail2ban fail2ban-client unban --all
+ fail2ban-client unban --all
;;
21)
@@ -2104,14 +2223,14 @@ web_security() {
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default11.conf
docker exec nginx nginx -s reload
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- cd /path/to/fail2ban/config/fail2ban/action.d
+ cd /etc/fail2ban/action.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/cloudflare-docker.conf
- sed -i "s/kejilion@outlook.com/$cfuser/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
- sed -i "s/APIKEY00000/$cftoken/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/kejilion@outlook.com/$cfuser/g" /etc/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/APIKEY00000/$cftoken/g" /etc/fail2ban/action.d/cloudflare-docker.conf
f2b_status
echo "已配置cloudflare模式,可在cf后台,站点-安全性-事件中查看拦截记录"
@@ -2176,26 +2295,23 @@ web_security() {
break
;;
esac
- fi
break_end
done
}
-check_nginx_mode() {
+check_ldnmp_mode() {
-CONFIG_FILE="/home/web/nginx.conf"
+ local MYSQL_CONTAINER="mysql"
+ local MYSQL_CONF="/etc/mysql/conf.d/custom_mysql_config.cnf"
-# 获取当前的 worker_processes 设置值
-current_value=$(grep -E '^\s*worker_processes\s+[0-9]+;' "$CONFIG_FILE" | awk '{print $2}' | tr -d ';')
-
-# 根据值设置模式信息
-if [ "$current_value" = "8" ]; then
- mode_info=" 高性能模式"
-else
- mode_info=" 标准模式"
-fi
+ # 检查 MySQL 配置文件中是否包含 4096M
+ if docker exec "$MYSQL_CONTAINER" grep -q "4096M" "$MYSQL_CONF" 2>/dev/null; then
+ mode_info=" 高性能模式"
+ else
+ mode_info=" 标准模式"
+ fi
@@ -2204,7 +2320,7 @@ fi
check_nginx_compression() {
- CONFIG_FILE="/home/web/nginx.conf"
+ local CONFIG_FILE="/home/web/nginx.conf"
# 检查 zstd 是否开启且未被注释(整行以 zstd on; 开头)
if grep -qE '^\s*zstd\s+on;' "$CONFIG_FILE"; then
@@ -2233,7 +2349,7 @@ check_nginx_compression() {
web_optimization() {
while true; do
- check_nginx_mode
+ check_ldnmp_mode
check_nginx_compression
clear
send_stats "优化LDNMP环境"
@@ -2252,9 +2368,11 @@ web_optimization() {
1)
send_stats "站点标准模式"
- # nginx调优
- sed -i 's/worker_connections.*/worker_connections 10240;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 4;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((1024 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
+
# php调优
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2282,7 +2400,6 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_balanced
@@ -2293,8 +2410,10 @@ web_optimization() {
send_stats "站点高性能模式"
# nginx调优
- sed -i 's/worker_connections.*/worker_connections 20480;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 8;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((2048 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
# php调优
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2321,7 +2440,6 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_web_server
echo "LDNMP环境已设置成 高性能模式"
@@ -2372,7 +2490,7 @@ web_optimization() {
check_docker_app() {
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name" ; then
check_docker="${gl_lv}已安装${gl_bai}"
else
check_docker="${gl_hui}未安装${gl_bai}"
@@ -2383,7 +2501,7 @@ check_docker_app() {
# check_docker_app() {
-# if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+# if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
# check_docker="${gl_lv}已安装${gl_bai}"
# else
# check_docker="${gl_hui}未安装${gl_bai}"
@@ -2423,54 +2541,61 @@ done
check_docker_image_update() {
-
local container_name=$1
+ update_status=""
- local country=$(curl -s ipinfo.io/country)
- if [[ "$country" == "CN" ]]; then
- update_status=""
- return
- fi
+ # 1. 区域检查
+ local country=$(curl -s --max-time 2 ipinfo.io/country)
+ [[ "$country" == "CN" ]] && return
- # 获取容器的创建时间和镜像名称
+ # 2. 获取本地镜像信息
local container_info=$(docker inspect --format='{{.Created}},{{.Config.Image}}' "$container_name" 2>/dev/null)
- local container_created=$(echo "$container_info" | cut -d',' -f1)
- local image_name=$(echo "$container_info" | cut -d',' -f2)
+ [[ -z "$container_info" ]] && return
- # 提取镜像仓库和标签
- local image_repo=${image_name%%:*}
- local image_tag=${image_name##*:}
-
- # 默认标签为 latest
- [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
-
- # 添加对官方镜像的支持
- [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
-
- # 从 Docker Hub API 获取镜像发布时间
- local hub_info=$(curl -s "https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag")
- local last_updated=$(echo "$hub_info" | jq -r '.last_updated' 2>/dev/null)
+ local container_created=$(echo "$container_info" | cut -d',' -f1)
+ local full_image_name=$(echo "$container_info" | cut -d',' -f2)
+ local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
+
+ # 3. 智能路由判断
+ if [[ "$full_image_name" == ghcr.io* ]]; then
+ # --- 场景 A: 镜像在 GitHub (ghcr.io) ---
+ # 提取仓库路径,例如 ghcr.io/onexru/oneimg -> onexru/oneimg
+ local repo_path=$(echo "$full_image_name" | sed 's/ghcr.io\///' | cut -d':' -f1)
+ # 注意:ghcr.io 的 API 比较复杂,通常最快的方法是查 GitHub Repo 的 Release
+ local api_url="https://api.github.com/repos/$repo_path/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
+
+ elif [[ "$full_image_name" == *"oneimg"* ]]; then
+ # --- 场景 B: 特殊指定 (即便在 Docker Hub,也想通过 GitHub Release 判断) ---
+ local api_url="https://api.github.com/repos/onexru/oneimg/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
- # 验证获取的时间
- if [[ -n "$last_updated" && "$last_updated" != "null" ]]; then
- local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
- local last_updated_ts=$(date -d "$last_updated" +%s 2>/dev/null)
+ else
+ # --- 场景 C: 标准 Docker Hub ---
+ local image_repo=${full_image_name%%:*}
+ local image_tag=${full_image_name##*:}
+ [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+
+ local api_url="https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag"
+ local remote_date=$(curl -s "$api_url" | jq -r '.last_updated' 2>/dev/null)
+ fi
- # 比较时间戳
- if [[ $container_created_ts -lt $last_updated_ts ]]; then
+ # 4. 时间戳对比
+ if [[ -n "$remote_date" && "$remote_date" != "null" ]]; then
+ local remote_ts=$(date -d "$remote_date" +%s 2>/dev/null)
+ if [[ $container_created_ts -lt $remote_ts ]]; then
update_status="${gl_huang}发现新版本!${gl_bai}"
- else
- update_status=""
fi
- else
- update_status=""
fi
-
}
+
+
+
block_container_port() {
local container_name_or_id=$1
local allowed_ip=$2
@@ -2705,16 +2830,33 @@ clear_host_port_rules() {
setup_docker_dir() {
- mkdir -p /home/docker/ 2>/dev/null
+ mkdir -p /home /home/docker 2>/dev/null
+
if [ -d "/vol1/1000/" ] && [ ! -d "/vol1/1000/docker" ]; then
cp -f /home/docker /home/docker1 2>/dev/null
rm -rf /home/docker 2>/dev/null
mkdir -p /vol1/1000/docker 2>/dev/null
ln -s /vol1/1000/docker /home/docker 2>/dev/null
fi
+
+ if [ -d "/volume1/" ] && [ ! -d "/volume1/docker" ]; then
+ cp -f /home/docker /home/docker1 2>/dev/null
+ rm -rf /home/docker 2>/dev/null
+ mkdir -p /volume1/docker 2>/dev/null
+ ln -s /volume1/docker /home/docker 2>/dev/null
+ fi
+
+
}
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "${app_id}" /home/docker/appno.txt || echo "${app_id}" >> /home/docker/appno.txt
+
+}
+
docker_app() {
@@ -2723,11 +2865,11 @@ send_stats "${docker_name}管理"
while true; do
clear
check_docker_app
- check_docker_image_update $docker_name
+ check_docker_image_update "${docker_app_service:-$docker_name}"
echo -e "$docker_name $check_docker $update_status"
echo "$docker_describe"
echo "$docker_url"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
@@ -2748,18 +2890,27 @@ while true; do
read -e -p "请输入你的选择: " choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+ while true; do
+ read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}错误: ${gl_bai}端口 $app_port 已被占用,请更换一个端口"
+ send_stats "应用端口已被占用"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
install jq
install_docker
docker_rum
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
clear
echo "$docker_name 已经安装完成"
@@ -2773,8 +2924,8 @@ while true; do
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
docker_rum
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
clear
echo "$docker_name 已经安装完成"
@@ -2789,8 +2940,8 @@ while true; do
docker rmi -f "$docker_img"
rm -rf "/home/docker/$docker_name"
rm -f /home/docker/${docker_name}_port.conf
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "应用已卸载"
send_stats "卸载$docker_name"
;;
@@ -2840,9 +2991,10 @@ docker_app_plus() {
echo -e "$app_name $check_docker $update_status"
echo "$app_text"
echo "$app_url"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_check_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
- local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
+ local docker_port=$(docker port "$docker_check_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
fi
@@ -2861,36 +3013,53 @@ docker_app_plus() {
read -e -p "输入你的选择: " choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+
+ while true; do
+ read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}错误: ${gl_bai}端口 $app_port 已被占用,请更换一个端口"
+ send_stats "应用端口已被占用"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
+
install jq
install_docker
docker_app_install
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
+ send_stats "$app_name 安装"
;;
+
2)
docker_app_update
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ add_app_id
+ send_stats "$app_name 更新"
;;
+
3)
docker_app_uninstall
rm -f /home/docker/${docker_name}_port.conf
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "$app_name 卸载"
;;
+
5)
echo "${docker_name}域名访问设置"
send_stats "${docker_name}域名访问设置"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
+
;;
6)
echo "域名格式 example.com 不带https://"
@@ -2902,7 +3071,8 @@ docker_app_plus() {
;;
8)
send_stats "阻止IP访问 ${docker_name}"
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
;;
*)
break
@@ -2940,7 +3110,7 @@ docker network create $NETWORK_NAME
docker run -d \
--name=node-exporter \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
prom/node-exporter
# Run Prometheus container
@@ -2949,7 +3119,7 @@ docker run -d \
-v $PROMETHEUS_DIR/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $PROMETHEUS_DIR/data:/prometheus \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
--user 0:0 \
prom/prometheus:latest
@@ -2959,7 +3129,7 @@ docker run -d \
-p ${docker_port}:3000 \
-v $GRAFANA_DIR:/var/lib/grafana \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
grafana/grafana:latest
}
@@ -3005,55 +3175,44 @@ tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
f2b_status() {
- docker exec -it fail2ban fail2ban-client reload
+ fail2ban-client reload
sleep 3
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
}
f2b_status_xxx() {
- docker exec -it fail2ban fail2ban-client status $xxx
+ fail2ban-client status $xxx
}
-f2b_install_sshd() {
-
- docker run -d \
- --name=fail2ban \
- --net=host \
- --cap-add=NET_ADMIN \
- --cap-add=NET_RAW \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e VERBOSITY=-vv \
- -v /path/to/fail2ban/config:/config \
- -v /var/log:/var/log:ro \
- -v /home/web/log/nginx/:/remotelogs/nginx:ro \
- --restart unless-stopped \
- lscr.io/linuxserver/fail2ban:latest
-
- sleep 3
- if grep -q 'Alpine' /etc/issue; then
- cd /path/to/fail2ban/config/fail2ban/filter.d
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd.conf
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd-ddos.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-ssh.conf
- elif command -v dnf &>/dev/null; then
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+check_f2b_status() {
+ if command -v fail2ban-client >/dev/null 2>&1; then
+ check_f2b_status="${gl_lv}已安装${gl_bai}"
else
- install rsyslog
- systemctl start rsyslog
- systemctl enable rsyslog
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/linux-ssh.conf
- systemctl restart rsyslog
+ check_f2b_status="${gl_hui}未安装${gl_bai}"
fi
-
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf
}
-f2b_sshd() {
+f2b_install_sshd() {
+
+ docker rm -f fail2ban >/dev/null 2>&1
+ install fail2ban
+ start fail2ban
+ enable fail2ban
+
+ if command -v dnf &>/dev/null; then
+ cd /etc/fail2ban/jail.d/
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+ fi
+
+ if command -v apt &>/dev/null; then
+ install rsyslog
+ systemctl start rsyslog
+ systemctl enable rsyslog
+ fi
+
+}
+
+f2b_sshd() {
if grep -q 'Alpine' /etc/issue; then
xxx=alpine-sshd
f2b_status_xxx
@@ -3063,6 +3222,80 @@ f2b_sshd() {
fi
}
+# 基础参数配置:封禁时长(bantime)、时间窗口(findtime)、重试次数(maxretry)
+# 说明:
+# - 优先写入 /etc/fail2ban/jail.d/sshd.local(覆盖默认 jail 配置,升级不易丢)
+# - 若是 Alpine 且 jail 名称不同,依然写 sshd.local;Fail2Ban 会按 jail 名称匹配
+f2b_basic_config() {
+ root_use
+ install nano
+
+ if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo -e "${gl_hui}未检测到 fail2ban-client,请先安装 fail2ban。${gl_bai}"
+ return
+ fi
+
+ local jail_name="sshd"
+ if grep -qi 'Alpine' /etc/issue 2>/dev/null; then
+ # Alpine 默认 jail 通常为 sshd;仅当检测到自定义 alpine-sshd 规则时才切换
+ if [ -f /etc/fail2ban/filter.d/alpine-sshd.conf ] || [ -f /etc/fail2ban/jail.d/alpine-ssh.conf ] || [ -f /etc/fail2ban/jail.d/alpine-sshd.local ]; then
+ jail_name="alpine-sshd"
+ fi
+ fi
+
+ echo "即将配置 SSH jail:$jail_name"
+ read -e -p "封禁时长 bantime (秒/分钟/小时,如 3600 或 1h) [默认 1h]: " bantime
+ read -e -p "时间窗口 findtime (秒/分钟/小时,如 600 或 10m) [默认 10m]: " findtime
+ read -e -p "重试次数 maxretry (整数) [默认 5]: " maxretry
+
+ bantime=${bantime:-1h}
+ findtime=${findtime:-10m}
+ maxretry=${maxretry:-5}
+
+ mkdir -p /etc/fail2ban/jail.d
+ cat > /etc/fail2ban/jail.d/sshd.local <> /etc/fail2ban/jail.d/sshd.local
+ fi
+ fi
+
+ echo -e "${gl_lv}已写入配置${gl_bai}: /etc/fail2ban/jail.d/sshd.local"
+ fail2ban-client reload >/dev/null 2>&1 || true
+ sleep 2
+ fail2ban-client status $jail_name || true
+}
+
+# 直接打开主配置/覆盖配置编辑(nano)
+# 优先编辑 /etc/fail2ban/jail.d/sshd.local(更安全),若不存在则创建
+f2b_edit_config() {
+ root_use
+ install nano
+
+ if [ ! -d /etc/fail2ban ]; then
+ echo -e "${gl_hui}/etc/fail2ban 不存在,请先安装 fail2ban。${gl_bai}"
+ return
+ fi
+
+ mkdir -p /etc/fail2ban/jail.d
+ local cfg="/etc/fail2ban/jail.d/sshd.local"
+ [ -f "$cfg" ] || printf "[sshd]\n# bantime/findtime/maxretry\n" > "$cfg"
+
+ nano "$cfg"
+ echo -e "${gl_lv}已保存${gl_bai},正在 reload fail2ban..."
+ fail2ban-client reload >/dev/null 2>&1 || true
+}
@@ -3133,8 +3366,7 @@ send_stats "安装LDNMP环境"
root_use
clear
echo -e "${gl_huang}LDNMP环境未安装,开始安装LDNMP环境...${gl_bai}"
-check_disk_space 3
-check_port
+check_disk_space 3 /home
install_dependency
install_docker
install_certbot
@@ -3150,8 +3382,6 @@ send_stats "安装nginx环境"
root_use
clear
echo -e "${gl_huang}nginx未安装,开始安装nginx环境...${gl_bai}"
-check_disk_space 1
-check_port
install_dependency
install_docker
install_certbot
@@ -3201,10 +3431,21 @@ ldnmp_web_on() {
}
nginx_web_on() {
- clear
- echo "您的 $webname 搭建好了!"
- echo "https://$yuming"
+ clear
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ echo "您的 $webname 搭建好了!"
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ mv /home/web/conf.d/"$yuming".conf /home/web/conf.d/"${yuming}_${access_port}".conf
+ echo "http://$yuming:$access_port"
+ elif grep -q '^[[:space:]]*#.*if (\$scheme = http)' "/home/web/conf.d/"$yuming".conf"; then
+ echo "http://$yuming"
+ else
+ echo "https://$yuming"
+ fi
}
@@ -3221,40 +3462,40 @@ ldnmp_wp() {
fi
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/wordpress.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
nginx_http_on
+
cd /home/web/html
mkdir $yuming
cd $yuming
wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/refs/heads/main/wp-latest.zip
- # wget -O latest.zip https://cn.wordpress.org/latest-zh_CN.zip
- # wget -O latest.zip https://wordpress.org/latest.zip
unzip latest.zip
rm latest.zip
- echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
+ echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379'); define('WP_REDIS_MAXTTL', 86400); define('WP_CACHE_KEY_SALT', '${yuming}_');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|database_name_here|$dbname|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|username_here|$dbuse|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|password_here|$dbusepasswd|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|localhost|mysql|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
+ patch_wp_url "https://$yuming" "https://$yuming"
cp /home/web/html/$yuming/wordpress/wp-config-sample.php /home/web/html/$yuming/wordpress/wp-config.php
+
restart_ldnmp
nginx_web_on
-# echo "数据库名: $dbname"
-# echo "用户名: $dbuse"
-# echo "密码: $dbusepasswd"
-# echo "数据库地址: mysql"
-# echo "表前缀: wp_"
}
+
ldnmp_Proxy() {
clear
webname="反向代理-IP+端口"
@@ -3267,21 +3508,43 @@ ldnmp_Proxy() {
if [ -z "$yuming" ]; then
add_yuming
fi
+
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy" ]; then
- read -e -p "请输入你的反代IP: " reverseproxy
+ read -e -p "请输入你的反代IP (回车默认本机IP 127.0.0.1): " reverseproxy
+ reverseproxy=${reverseproxy:-127.0.0.1}
fi
if [ -z "$port" ]; then
read -e -p "请输入你的反代端口: " port
fi
nginx_install_status
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
+ wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/backend_$backend/g" /home/web/conf.d/"$yuming".conf
+
+
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$reverseproxy/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$port|g" /home/web/conf.d/$yuming.conf
+
+ reverseproxy_port="$reverseproxy:$port"
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i '/remote_addr/d' /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3292,8 +3555,6 @@ ldnmp_Proxy() {
ldnmp_Proxy_backend() {
clear
webname="反向代理-负载均衡"
- yuming="${1:-}"
- reverseproxy_port="${2:-}"
send_stats "安装$webname"
echo "开始部署 $webname"
@@ -3301,14 +3562,17 @@ ldnmp_Proxy_backend() {
add_yuming
fi
- # 获取用户输入的多个IP:端口(用空格分隔)
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy_port" ]; then
read -e -p "请输入你的多个反代IP+端口用空格隔开(例如 127.0.0.1:3000 127.0.0.1:3002): " reverseproxy_port
fi
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
@@ -3318,15 +3582,16 @@ ldnmp_Proxy_backend() {
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- # 动态生成 upstream 配置
upstream_servers=""
for server in $reverseproxy_port; do
upstream_servers="$upstream_servers server $server;\n"
done
- # 替换模板中的占位符
sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3334,6 +3599,200 @@ ldnmp_Proxy_backend() {
+
+
+
+list_stream_services() {
+
+ STREAM_DIR="/home/web/stream.d"
+ printf "%-25s %-18s %-25s %-20s\n" "服务名" "通信类型" "本机地址" "后端地址"
+
+ if [ -z "$(ls -A "$STREAM_DIR")" ]; then
+ return
+ fi
+
+ for conf in "$STREAM_DIR"/*; do
+ # 服务名取文件名
+ service_name=$(basename "$conf" .conf)
+
+ # 获取 upstream 块中的 server 后端 IP:端口
+ backend=$(grep -Po '(?<=server )[^;]+' "$conf" | head -n1)
+
+ # 获取 listen 端口
+ listen_port=$(grep -Po '(?<=listen )[^;]+' "$conf" | head -n1)
+
+ # 默认本地 IP
+ ip_address
+ local_ip="$ipv4_address"
+
+ # 获取通信类型,优先从文件名后缀或内容判断
+ if grep -qi 'udp;' "$conf"; then
+ proto="udp"
+ else
+ proto="tcp"
+ fi
+
+ # 拼接监听 IP:端口
+ local_addr="$local_ip:$listen_port"
+
+ printf "%-22s %-14s %-21s %-20s\n" "$service_name" "$proto" "$local_addr" "$backend"
+ done
+}
+
+
+
+
+
+
+
+
+
+stream_panel() {
+ send_stats "Stream四层代理"
+ local app_id="104"
+ local docker_name="nginx"
+
+ while true; do
+ clear
+ check_docker_app
+ check_docker_image_update $docker_name
+ echo -e "Stream四层代理转发工具 $check_docker $update_status"
+ echo "NGINX Stream 是 NGINX 的 TCP/UDP 代理模块,用于实现高性能的 传输层流量转发和负载均衡。"
+ echo "------------------------"
+ if [ -d "/home/web/stream.d" ]; then
+ list_stream_services
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 安装 2. 更新 3. 卸载"
+ echo "------------------------"
+ echo "4. 添加转发服务 5. 修改转发服务 6. 删除转发服务"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+ case $choice in
+ 1)
+ nginx_install_status
+ add_app_id
+ send_stats "安装Stream四层代理"
+ ;;
+ 2)
+ update_docker_compose_with_db_creds
+ nginx_upgrade
+ add_app_id
+ send_stats "更新Stream四层代理"
+ ;;
+ 3)
+ read -e -p "确定要删除 nginx 容器吗?这可能会影响网站功能!(y/N): " confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ docker rm -f nginx
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "更新Stream四层代理"
+ echo "nginx 容器已删除。"
+ else
+ echo "操作已取消。"
+ fi
+
+ ;;
+
+ 4)
+ ldnmp_Proxy_backend_stream
+ add_app_id
+ send_stats "添加四层代理"
+ ;;
+ 5)
+ send_stats "编辑转发配置"
+ read -e -p "请输入你要编辑的服务名: " stream_name
+ install nano
+ nano /home/web/stream.d/$stream_name.conf
+ docker restart nginx
+ send_stats "修改四层代理"
+ ;;
+ 6)
+ send_stats "删除转发配置"
+ read -e -p "请输入你要删除的服务名: " stream_name
+ rm /home/web/stream.d/$stream_name.conf > /dev/null 2>&1
+ docker restart nginx
+ send_stats "删除四层代理"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ break_end
+ done
+}
+
+
+
+ldnmp_Proxy_backend_stream() {
+ clear
+ webname="Stream四层代理-负载均衡"
+
+ send_stats "安装$webname"
+ echo "开始部署 $webname"
+
+ # 获取代理名称
+ read -erp "请输入代理转发名称 (如 mysql_proxy): " proxy_name
+ if [ -z "$proxy_name" ]; then
+ echo "名称不能为空"; return 1
+ fi
+
+ # 获取监听端口
+ read -erp "请输入本机监听端口 (如 3306): " listen_port
+ if ! [[ "$listen_port" =~ ^[0-9]+$ ]]; then
+ echo "端口必须是数字"; return 1
+ fi
+
+ echo "请选择协议类型:"
+ echo "1. TCP 2. UDP"
+ read -erp "请输入序号 [1-2]: " proto_choice
+
+ case "$proto_choice" in
+ 1) proto="tcp"; listen_suffix="" ;;
+ 2) proto="udp"; listen_suffix=" udp" ;;
+ *) echo "无效选择"; return 1 ;;
+ esac
+
+ read -e -p "请输入你的一个或者多个后端IP+端口用空格隔开(例如 10.13.0.2:3306 10.13.0.3:3306): " reverseproxy_port
+
+ nginx_install_status
+ cd /home && mkdir -p web/stream.d
+ grep -q '^[[:space:]]*stream[[:space:]]*{' /home/web/nginx.conf || echo -e '\nstream {\n include /etc/nginx/stream.d/*.conf;\n}' | tee -a /home/web/nginx.conf
+ wget -O /home/web/stream.d/$proxy_name.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend-stream.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/${proxy_name}_${backend}/g" /home/web/stream.d/"$proxy_name".conf
+ sed -i "s|listen 80|listen $listen_port $listen_suffix|g" /home/web/stream.d/$proxy_name.conf
+ sed -i "s|listen \[::\]:|listen [::]:${listen_port} ${listen_suffix}|g" "/home/web/stream.d/${proxy_name}.conf"
+
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 动态添加/$upstream_servers/g" /home/web/stream.d/$proxy_name.conf
+
+ docker exec nginx nginx -s reload
+ clear
+ echo "您的 $webname 搭建好了!"
+ echo "------------------------"
+ echo "访问地址:"
+ ip_address
+ if [ -n "$ipv4_address" ]; then
+ echo "$ipv4_address:${listen_port}"
+ fi
+ if [ -n "$ipv6_address" ]; then
+ echo "$ipv6_address:${listen_port}"
+ fi
+ echo ""
+}
+
+
+
+
+
find_container_by_host_port() {
port="$1"
docker_name=$(docker ps --format '{{.ID}} {{.Names}}' | while read id name; do
@@ -3351,11 +3810,11 @@ ldnmp_web_status() {
root_use
while true; do
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
- local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+ local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
- local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+ local db_output="${gl_lv}${db_count}${gl_bai}"
clear
send_stats "LDNMP站点管理"
@@ -3363,8 +3822,7 @@ ldnmp_web_status() {
echo "------------------------"
ldnmp_v
- # ls -t /home/web/conf.d | sed 's/\.[^.]*$//'
- echo -e "${output} 证书到期时间"
+ echo -e "站点: ${output} 证书到期时间"
echo -e "------------------------"
for cert_file in /home/web/certs/*_cert.pem; do
local domain=$(basename "$cert_file" | sed 's/_cert.pem//')
@@ -3375,9 +3833,28 @@ ldnmp_web_status() {
fi
done
+ for conf_file in /home/web/conf.d/*_*.conf; do
+ [ -e "$conf_file" ] || continue
+ basename "$conf_file" .conf
+ done
+
+ for conf_file in /home/web/conf.d/*.conf; do
+ [ -e "$conf_file" ] || continue
+
+ filename=$(basename "$conf_file")
+
+ if [ "$filename" = "map.conf" ] || [ "$filename" = "default.conf" ]; then
+ continue
+ fi
+
+ if ! grep -q "ssl_certificate" "$conf_file"; then
+ basename "$conf_file" .conf
+ fi
+ done
+
echo "------------------------"
echo ""
- echo -e "${db_output}"
+ echo -e "数据库: ${db_output}"
echo -e "------------------------"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys"
@@ -3391,11 +3868,11 @@ ldnmp_web_status() {
echo ""
echo "操作"
echo "------------------------"
- echo "1. 申请/更新域名证书 2. 更换站点域名"
+ echo "1. 申请/更新域名证书 2. 克隆站点域名"
echo "3. 清理站点缓存 4. 创建关联站点"
echo "5. 查看访问日志 6. 查看错误日志"
echo "7. 编辑全局配置 8. 编辑站点配置"
- echo "9. 管理站点数据库 10. 查看站点分析报告"
+ echo "9. 管理站点数据库 10. 查看站点分析报告"
echo "------------------------"
echo "20. 删除指定站点数据"
echo "------------------------"
@@ -3407,30 +3884,26 @@ ldnmp_web_status() {
send_stats "申请域名证书"
read -e -p "请输入你的域名: " yuming
install_certbot
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
;;
2)
- send_stats "更换站点域名"
- echo -e "${gl_hong}强烈建议: ${gl_bai}先备份好全站数据再更换站点域名!"
+ send_stats "克隆站点域名"
read -e -p "请输入旧域名: " oddyuming
read -e -p "请输入新域名: " yuming
install_certbot
install_ssltls
certs_status
- # mysql替换
- add_db
+ add_db
local odd_dbname=$(echo "$oddyuming" | sed -e 's/[^A-Za-z0-9]/_/g')
local odd_dbname="${odd_dbname}"
docker exec mysql mysqldump -u root -p"$dbrootpasswd" $odd_dbname | docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname
- docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE $odd_dbname;"
-
local tables=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -D $dbname -e "SHOW TABLES;" | awk '{ if (NR>1) print $1 }')
for table in $tables; do
@@ -3441,18 +3914,15 @@ ldnmp_web_status() {
done
# 网站目录替换
- mv /home/web/html/$oddyuming /home/web/html/$yuming
+ cp -r /home/web/html/$oddyuming /home/web/html/$yuming
find /home/web/html/$yuming -type f -exec sed -i "s/$odd_dbname/$dbname/g" {} +
find /home/web/html/$yuming -type f -exec sed -i "s/$oddyuming/$yuming/g" {} +
- mv /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
+ cp /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
sed -i "s/$oddyuming/$yuming/g" /home/web/conf.d/$yuming.conf
- rm /home/web/certs/${oddyuming}_key.pem
- rm /home/web/certs/${oddyuming}_cert.pem
-
- docker exec nginx nginx -s reload
+ cd /home/web && docker compose restart
;;
@@ -3513,7 +3983,7 @@ ldnmp_web_status() {
20)
web_del
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
;;
*)
@@ -3558,21 +4028,21 @@ while true; do
install wget
iptables_open
panel_app_install
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
send_stats "${panelname}安装"
;;
2)
panel_app_manage
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
send_stats "${panelname}控制"
;;
3)
panel_app_uninstall
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
send_stats "${panelname}卸载"
;;
*)
@@ -3871,6 +4341,7 @@ frps_main_ports() {
frps_panel() {
send_stats "FRP服务端"
+ local app_id="55"
local docker_name="frps"
local docker_port=8056
while true; do
@@ -3879,7 +4350,7 @@ frps_panel() {
check_docker_image_update $docker_name
echo -e "FRP服务端 $check_frp $update_status"
echo "构建FRP内网穿透服务环境,将无公网IP的设备暴露到互联网"
- echo "官网介绍: https://github.com/fatedier/frp/"
+ echo "官网介绍: ${gh_https_url}github.com/fatedier/frp/"
echo "视频教学: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
if [ -d "/home/frp/" ]; then
check_docker_app_ip
@@ -3901,8 +4372,8 @@ frps_panel() {
install jq grep ss
install_docker
generate_frps_config
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP服务端已经安装完成"
;;
2)
@@ -3911,8 +4382,8 @@ frps_panel() {
docker rm -f frps && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frps.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frps.toml /home/frp/frps.toml
donlond_frp frps
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP服务端已经更新完成"
;;
3)
@@ -3922,8 +4393,8 @@ frps_panel() {
rm -rf /home/frp
close_port 8055 8056
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "应用已卸载"
;;
5)
@@ -3968,6 +4439,7 @@ frps_panel() {
frpc_panel() {
send_stats "FRP客户端"
+ local app_id="56"
local docker_name="frpc"
local docker_port=8055
while true; do
@@ -3976,7 +4448,7 @@ frpc_panel() {
check_docker_image_update $docker_name
echo -e "FRP客户端 $check_frp $update_status"
echo "与服务端对接,对接后可创建内网穿透服务到互联网访问"
- echo "官网介绍: https://github.com/fatedier/frp/"
+ echo "官网介绍: ${gh_https_url}github.com/fatedier/frp/"
echo "视频教学: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
echo "------------------------"
if [ -d "/home/frp/" ]; then
@@ -3997,8 +4469,8 @@ frpc_panel() {
install jq grep ss
install_docker
configure_frpc
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP客户端已经安装完成"
;;
2)
@@ -4007,8 +4479,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
donlond_frp frpc
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP客户端已经更新完成"
;;
@@ -4018,8 +4490,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine
rm -rf /home/frp
close_port 8055
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "应用已卸载"
;;
@@ -4050,6 +4522,7 @@ frpc_panel() {
yt_menu_pro() {
+ local app_id="66"
local VIDEO_DIR="/home/yt-dlp"
local URL_FILE="$VIDEO_DIR/urls.txt"
local ARCHIVE_FILE="$VIDEO_DIR/archive.txt"
@@ -4068,7 +4541,7 @@ yt_menu_pro() {
send_stats "yt-dlp 下载工具"
echo -e "yt-dlp $YTDLP_STATUS"
echo -e "yt-dlp 是一个功能强大的视频下载工具,支持 YouTube、Bilibili、Twitter 等数千站点。"
- echo -e "官网地址:https://github.com/yt-dlp/yt-dlp"
+ echo -e "官网地址:${gh_https_url}github.com/yt-dlp/yt-dlp"
echo "-------------------------"
echo "已下载视频列表:"
ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(暂无)"
@@ -4087,26 +4560,26 @@ yt_menu_pro() {
send_stats "正在安装 yt-dlp..."
echo "正在安装 yt-dlp..."
install ffmpeg
- sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
- sudo chmod a+rx /usr/local/bin/yt-dlp
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ curl -L ${gh_https_url}github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ chmod a+rx /usr/local/bin/yt-dlp
+
+ add_app_id
echo "安装完成。按任意键继续..."
read ;;
2)
send_stats "正在更新 yt-dlp..."
echo "正在更新 yt-dlp..."
- sudo yt-dlp -U
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ yt-dlp -U
+
+ add_app_id
echo "更新完成。按任意键继续..."
read ;;
3)
send_stats "正在卸载 yt-dlp..."
echo "正在卸载 yt-dlp..."
- sudo rm -f /usr/local/bin/yt-dlp
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+ rm -f /usr/local/bin/yt-dlp
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "卸载完成。按任意键继续..."
read ;;
5)
@@ -4203,7 +4676,7 @@ fix_dpkg() {
linux_update() {
- echo -e "${gl_huang}正在系统更新...${gl_bai}"
+ echo -e "${gl_kjlan}正在系统更新...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
elif command -v yum &>/dev/null; then
@@ -4230,7 +4703,7 @@ linux_update() {
linux_clean() {
- echo -e "${gl_huang}正在系统清理...${gl_bai}"
+ echo -e "${gl_kjlan}正在系统清理...${gl_bai}"
if command -v dnf &>/dev/null; then
rpm --rebuilddb
dnf autoremove -y
@@ -4309,10 +4782,17 @@ linux_clean() {
bbr_on() {
-cat > /etc/sysctl.conf << EOF
-net.ipv4.tcp_congestion_control=bbr
-EOF
-sysctl -p
+# 统一写入到 sysctl.d 以防与内核调优模块打架
+local CONF="/etc/sysctl.d/99-kejilion-bbr.conf"
+mkdir -p /etc/sysctl.d
+echo "net.core.default_qdisc=fq" > "$CONF"
+echo "net.ipv4.tcp_congestion_control=bbr" >> "$CONF"
+
+# 清理可能导致冲突的旧版 sysctl.conf 残留
+sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf 2>/dev/null
+
+sysctl -p "$CONF" >/dev/null 2>&1 || sysctl --system >/dev/null 2>&1
}
@@ -4321,8 +4801,8 @@ set_dns() {
ip_address
-rm /etc/resolv.conf
-touch /etc/resolv.conf
+chattr -i /etc/resolv.conf
+> /etc/resolv.conf
if [ -n "$ipv4_address" ]; then
echo "nameserver $dns1_ipv4" >> /etc/resolv.conf
@@ -4334,6 +4814,13 @@ if [ -n "$ipv6_address" ]; then
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
fi
+if [ ! -s /etc/resolv.conf ]; then
+ echo "nameserver 223.5.5.5" >> /etc/resolv.conf
+ echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+fi
+
+chattr +i /etc/resolv.conf
+
}
@@ -4378,7 +4865,9 @@ while true; do
;;
3)
install nano
+ chattr -i /etc/resolv.conf
nano /etc/resolv.conf
+ chattr +i /etc/resolv.conf
send_stats "手动编辑DNS配置"
;;
*)
@@ -4402,39 +4891,32 @@ correct_ssh_config() {
local sshd_config="/etc/ssh/sshd_config"
- # 如果找到 PasswordAuthentication 设置为 yes
- if grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
- # 如果找到 PubkeyAuthentication 设置为 yes
- if grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
+ if grep -Eq "^\s*PasswordAuthentication\s+no" "$sshd_config"; then
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
-e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' "$sshd_config"
+ else
+ sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin yes/' \
+ -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication yes/' \
+ -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' "$sshd_config"
fi
- # 如果 PasswordAuthentication 和 PubkeyAuthentication 都没有匹配,则设置默认值
- if ! grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config" && ! grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
-
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
}
new_ssh_port() {
- # 备份 SSH 配置文件
+ local new_port=$1
+
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
open_port $new_port
@@ -4448,21 +4930,7 @@ new_ssh_port() {
-add_sshkey() {
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f /root/.ssh/sshkey -N ""
- cat ~/.ssh/sshkey.pub >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
-
- ip_address
- echo -e "私钥信息已生成,务必复制保存,可保存成 ${gl_huang}${ipv4_address}_ssh.key${gl_bai} 文件,用于以后的SSH登录"
-
- echo "--------------------------------"
- cat ~/.ssh/sshkey
- echo "--------------------------------"
+sshkey_on() {
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
@@ -4470,61 +4938,335 @@ add_sshkey() {
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
- echo -e "${gl_lv}ROOT私钥登录已开启,已关闭ROOT密码登录,重连将会生效${gl_bai}"
+ echo -e "${gl_lv}用户密钥登录模式已开启,已关闭密码登录模式,重连将会生效${gl_bai}"
}
-import_sshkey() {
- read -e -p "请输入您的SSH公钥内容(通常以 'ssh-rsa' 或 'ssh-ed25519' 开头): " public_key
+add_sshkey() {
+ chmod 700 "${HOME}"
+ mkdir -p "${HOME}/.ssh"
+ chmod 700 "${HOME}/.ssh"
+ touch "${HOME}/.ssh/authorized_keys"
- if [[ -z "$public_key" ]]; then
- echo -e "${gl_hong}错误:未输入公钥内容。${gl_bai}"
- return 1
- fi
+ ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f "${HOME}/.ssh/sshkey" -N ""
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- echo "$public_key" >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
+ cat "${HOME}/.ssh/sshkey.pub" >> "${HOME}/.ssh/authorized_keys"
+ chmod 600 "${HOME}/.ssh/authorized_keys"
- sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
- -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
- -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
- -e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
+ ip_address
+ echo -e "私钥信息已生成,务必复制保存,可保存成 ${gl_huang}${ipv4_address}_ssh.key${gl_bai} 文件,用于以后的SSH登录"
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
- restart_ssh
- echo -e "${gl_lv}公钥已成功导入,ROOT私钥登录已开启,已关闭ROOT密码登录,重连将会生效${gl_bai}"
+ echo "--------------------------------"
+ cat "${HOME}/.ssh/sshkey"
+ echo "--------------------------------"
+ sshkey_on
}
-add_sshpasswd() {
-echo "设置你的ROOT密码"
-passwd
-sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
-sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
-rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
-restart_ssh
-echo -e "${gl_lv}ROOT登录设置完毕!${gl_bai}"
+import_sshkey() {
-}
+ local public_key="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local auth_keys="${ssh_dir}/authorized_keys"
+ if [[ -z "$public_key" ]]; then
+ read -e -p "请输入您的SSH公钥内容(通常以 'ssh-rsa' 或 'ssh-ed25519' 开头): " public_key
+ fi
-root_use() {
-clear
-[ "$EUID" -ne 0 ] && echo -e "${gl_huang}提示: ${gl_bai}该功能需要root用户才能运行!" && break_end && kejilion
+ if [[ -z "$public_key" ]]; then
+ echo -e "${gl_hong}错误:未输入公钥内容。${gl_bai}"
+ return 1
+ fi
+
+ if [[ ! "$public_key" =~ ^ssh-(rsa|ed25519|ecdsa) ]]; then
+ echo -e "${gl_hong}错误:看起来不像合法的 SSH 公钥。${gl_bai}"
+ return 1
+ fi
+
+ if grep -Fxq "$public_key" "$auth_keys" 2>/dev/null; then
+ echo "该公钥已存在,无需重复添加"
+ return 0
+ fi
+
+ mkdir -p "$ssh_dir"
+ chmod 700 "$ssh_dir"
+ touch "$auth_keys"
+ echo "$public_key" >> "$auth_keys"
+ chmod 600 "$auth_keys"
+
+ sshkey_on
+}
+
+
+
+fetch_remote_ssh_keys() {
+
+ local keys_url="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local authorized_keys="${ssh_dir}/authorized_keys"
+ local temp_file
+
+ if [[ -z "${keys_url}" ]]; then
+ read -e -p "请输入您的远端公钥URL: " keys_url
+ fi
+
+ echo "此脚本将从远程 URL 拉取 SSH 公钥,并添加到 ${authorized_keys}"
+ echo ""
+ echo "远程公钥地址:"
+ echo " ${keys_url}"
+ echo ""
+
+ # 创建临时文件
+ temp_file=$(mktemp)
+
+ # 下载公钥
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 10 "${keys_url}" -o "${temp_file}" || {
+ echo "错误:无法从 URL 下载公钥(网络问题或地址无效)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q --timeout=10 -O "${temp_file}" "${keys_url}" || {
+ echo "错误:无法从 URL 下载公钥(网络问题或地址无效)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ else
+ echo "错误:系统中未找到 curl 或 wget,无法下载公钥" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ # 检查内容是否有效
+ if [[ ! -s "${temp_file}" ]]; then
+ echo "错误:下载到的文件为空,URL 可能不包含任何公钥" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ mkdir -p "${ssh_dir}"
+ chmod 700 "${ssh_dir}"
+ touch "${authorized_keys}"
+ chmod 600 "${authorized_keys}"
+
+ # 备份原有 authorized_keys
+ if [[ -f "${authorized_keys}" ]]; then
+ cp "${authorized_keys}" "${authorized_keys}.bak.$(date +%Y%m%d-%H%M%S)"
+ echo "已备份原有 authorized_keys 文件"
+ fi
+
+ # 追加公钥(避免重复)
+ local added=0
+ while IFS= read -r line; do
+ [[ -z "${line}" || "${line}" =~ ^# ]] && continue
+
+ if ! grep -Fxq "${line}" "${authorized_keys}" 2>/dev/null; then
+ echo "${line}" >> "${authorized_keys}"
+ ((added++))
+ fi
+ done < "${temp_file}"
+
+ rm -f "${temp_file}"
+
+ echo ""
+ if (( added > 0 )); then
+ echo "成功添加 ${added} 条新的公钥到 ${authorized_keys}"
+ sshkey_on
+ else
+ echo "没有新的公钥需要添加(可能已全部存在)"
+ fi
+
+ echo ""
+}
+
+
+
+
+fetch_github_ssh_keys() {
+
+ local username="$1"
+ local base_dir="${2:-$HOME}"
+
+ echo "操作前,请确保您已在 GitHub 账户中添加了 SSH 公钥:"
+ echo " 1. 登录 ${gh_https_url}github.com/settings/keys"
+ echo " 2. 点击 New SSH key 或 Add SSH key"
+ echo " 3. Title 可随意填写(例如:Home Laptop 2026)"
+ echo " 4. 将本地公钥内容(通常是 ~/.ssh/id_ed25519.pub 或 id_rsa.pub 的全部内容)粘贴到 Key 字段"
+ echo " 5. 点击 Add SSH key 完成添加"
+ echo ""
+ echo "添加完成后,GitHub 会公开提供您的所有公钥,地址为:"
+ echo " ${gh_https_url}github.com/您的用户名.keys"
+ echo ""
+
+
+ if [[ -z "${username}" ]]; then
+ read -e -p "请输入您的 GitHub 用户名(username,不含 @): " username
+ fi
+
+ if [[ -z "${username}" ]]; then
+ echo "错误:GitHub 用户名不能为空" >&2
+ return 1
+ fi
+
+ keys_url="${gh_https_url}github.com/${username}.keys"
+
+ fetch_remote_ssh_keys "${keys_url}" "${base_dir}"
+
+}
+
+
+sshkey_panel() {
+ root_use
+ send_stats "用户密钥登录"
+ while true; do
+ clear
+ local REAL_STATUS=$(grep -i "^PubkeyAuthentication" /etc/ssh/sshd_config | tr '[:upper:]' '[:lower:]')
+ if [[ "$REAL_STATUS" =~ "yes" ]]; then
+ IS_KEY_ENABLED="${gl_lv}已启用${gl_bai}"
+ else
+ IS_KEY_ENABLED="${gl_hui}未启用${gl_bai}"
+ fi
+ echo -e "用户密钥登录模式 ${IS_KEY_ENABLED}"
+ echo "进阶玩法: https://blog.kejilion.pro/ssh-key"
+ echo "------------------------------------------------"
+ echo "将会生成密钥对,更安全的方式SSH登录"
+ echo "------------------------"
+ echo "1. 生成新密钥对 2. 手动输入已有公钥"
+ echo "3. 从GitHub导入已有公钥 4. 从URL导入已有公钥"
+ echo "5. 编辑公钥文件 6. 查看本机密钥"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "请输入你的选择: " host_dns
+ case $host_dns in
+ 1)
+ send_stats "生成新密钥"
+ add_sshkey
+ break_end
+ ;;
+ 2)
+ send_stats "导入已有公钥"
+ import_sshkey
+ break_end
+ ;;
+ 3)
+ send_stats "导入GitHub远端公钥"
+ fetch_github_ssh_keys
+ break_end
+ ;;
+ 4)
+ send_stats "导入URL远端公钥"
+ read -e -p "请输入您的远端公钥URL: " keys_url
+ fetch_remote_ssh_keys "${keys_url}"
+ break_end
+ ;;
+
+ 5)
+ send_stats "编辑公钥文件"
+ install nano
+ nano ${HOME}/.ssh/authorized_keys
+ break_end
+ ;;
+
+ 6)
+ send_stats "查看本机密钥"
+ echo "------------------------"
+ echo "公钥信息"
+ cat ${HOME}/.ssh/authorized_keys
+ echo "------------------------"
+ echo "私钥信息"
+ cat ${HOME}/.ssh/sshkey
+ echo "------------------------"
+ break_end
+ ;;
+ *)
+ break # 跳出循环,退出菜单
+ ;;
+ esac
+ done
+
+
+}
+
+
+
+
+
+
+add_sshpasswd() {
+
+ root_use
+ send_stats "设置密码登录模式"
+ echo "设置密码登录模式"
+
+ local target_user="$1"
+
+ # 如果没有通过参数传入,则交互输入
+ if [[ -z "$target_user" ]]; then
+ read -e -p "请输入要修改密码的用户名(默认 root): " target_user
+ fi
+
+ # 回车不输入,默认 root
+ target_user=${target_user:-root}
+
+ # 校验用户是否存在
+ if ! id "$target_user" >/dev/null 2>&1; then
+ echo "错误:用户 $target_user 不存在"
+ return 1
+ fi
+
+ passwd "$target_user"
+
+ if [[ "$target_user" == "root" ]]; then
+ sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
+ fi
+
+ sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
+
+ restart_ssh
+
+ echo -e "${gl_lv}密码设置完毕,已更改为密码登录模式!${gl_bai}"
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+root_use() {
+clear
+[ "$EUID" -ne 0 ] && echo -e "${gl_huang}提示: ${gl_bai}该功能需要root用户才能运行!" && break_end && kejilion
}
+
+
+
+
+
+
+
+
+
dd_xitong() {
send_stats "重装系统"
dd_xitong_MollyLau() {
@@ -4538,6 +5280,7 @@ dd_xitong() {
dd_xitong_1() {
echo -e "重装后初始用户名: ${gl_huang}root${gl_bai} 初始密码: ${gl_huang}LeitboGi0ro${gl_bai} 初始端口: ${gl_huang}22${gl_bai}"
+ echo -e "${gl_huang}重装后请及时修改初始密码,防止暴力入侵。命令行输入passwd修改密码${gl_bai}"
echo -e "按任意键继续..."
read -n 1 -s -r -p ""
install wget
@@ -4571,18 +5314,20 @@ dd_xitong() {
echo "重装系统"
echo "--------------------------------"
echo -e "${gl_hong}注意: ${gl_bai}重装有风险失联,不放心者慎用。重装预计花费15分钟,请提前备份数据。"
- echo -e "${gl_hui}感谢MollyLau大佬和bin456789大佬的脚本支持!${gl_bai} "
+ echo -e "${gl_hui}感谢bin456789大佬和leitbogioro大佬的脚本支持!${gl_bai} "
+ echo -e "${gl_hui}bin456789项目地址: ${gh_https_url}github.com/bin456789/reinstall${gl_bai}"
+ echo -e "${gl_hui}leitbogioro项目地址: ${gh_https_url}github.com/leitbogioro/Tools${gl_bai}"
echo "------------------------"
- echo "1. Debian 12 2. Debian 11"
- echo "3. Debian 10 4. Debian 9"
+ echo "1. Debian 13 2. Debian 12"
+ echo "3. Debian 11 4. Debian 10"
echo "------------------------"
- echo "11. Ubuntu 24.04 12. Ubuntu 22.04"
- echo "13. Ubuntu 20.04 14. Ubuntu 18.04"
+ echo "11. Ubuntu 26.04 12. Ubuntu 24.04"
+ echo "13. Ubuntu 22.04 14. Ubuntu 20.04"
echo "------------------------"
echo "21. Rocky Linux 10 22. Rocky Linux 9"
echo "23. Alma Linux 10 24. Alma Linux 9"
echo "25. oracle Linux 10 26. oracle Linux 9"
- echo "27. Fedora Linux 42 28. Fedora Linux 41"
+ echo "27. Fedora Linux 44 28. Fedora Linux 43"
echo "29. CentOS 10 30. CentOS 9"
echo "------------------------"
echo "31. Alpine Linux 32. Arch Linux"
@@ -4590,72 +5335,74 @@ dd_xitong() {
echo "35. openSUSE Tumbleweed 36. fnos飞牛公测版"
echo "------------------------"
echo "41. Windows 11 42. Windows 10"
- echo "43. Windows 7 44. Windows Server 2022"
- echo "45. Windows Server 2019 46. Windows Server 2016"
+ echo "43. Windows 7 44. Windows Server 2025"
+ echo "45. Windows Server 2022 46. Windows Server 2019"
echo "47. Windows 11 ARM"
echo "------------------------"
echo "0. 返回上一级选单"
echo "------------------------"
read -e -p "请选择要重装的系统: " sys_choice
case "$sys_choice" in
+
+
1)
- send_stats "重装debian 12"
- dd_xitong_1
- bash InstallNET.sh -debian 12
+ send_stats "重装debian 13"
+ dd_xitong_3
+ bash reinstall.sh debian 13
reboot
exit
;;
+
2)
- send_stats "重装debian 11"
- dd_xitong_1
- bash InstallNET.sh -debian 11
+ send_stats "重装debian 12"
+ dd_xitong_3
+ bash reinstall.sh debian 12
reboot
exit
;;
3)
- send_stats "重装debian 10"
- dd_xitong_1
- bash InstallNET.sh -debian 10
+ send_stats "重装debian 11"
+ dd_xitong_3
+ bash reinstall.sh debian 11
reboot
exit
;;
4)
- send_stats "重装debian 9"
- dd_xitong_1
- bash InstallNET.sh -debian 9
+ send_stats "重装debian 10"
+ dd_xitong_3
+ bash reinstall.sh debian 10
reboot
exit
;;
11)
- send_stats "重装ubuntu 24.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 24.04
+ send_stats "重装ubuntu 26.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 26.04
reboot
exit
;;
12)
- send_stats "重装ubuntu 22.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 22.04
+ send_stats "重装ubuntu 24.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 24.04
reboot
exit
;;
13)
- send_stats "重装ubuntu 20.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 20.04
+ send_stats "重装ubuntu 22.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 22.04
reboot
exit
;;
14)
- send_stats "重装ubuntu 18.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 18.04
+ send_stats "重装ubuntu 20.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 20.04
reboot
exit
;;
-
21)
send_stats "重装rockylinux10"
dd_xitong_3
@@ -4705,17 +5452,17 @@ dd_xitong() {
;;
27)
- send_stats "重装fedora42"
+ send_stats "重装fedora44"
dd_xitong_3
- bash reinstall.sh fedora
+ bash reinstall.sh fedora 44
reboot
exit
;;
28)
- send_stats "重装fedora41"
+ send_stats "重装fedora43"
dd_xitong_3
- bash reinstall.sh fedora 41
+ bash reinstall.sh fedora 43
reboot
exit
;;
@@ -4784,7 +5531,6 @@ dd_xitong() {
exit
;;
-
41)
send_stats "重装windows11"
dd_xitong_2
@@ -4792,6 +5538,7 @@ dd_xitong() {
reboot
exit
;;
+
42)
dd_xitong_2
send_stats "重装windows10"
@@ -4799,32 +5546,35 @@ dd_xitong() {
reboot
exit
;;
+
43)
send_stats "重装windows7"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://archive.org/download/en_windows_7_professional_with_sp1_x64_dvd_u_676939_201906/en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso" --image-name='windows 7 professional'
reboot
exit
;;
44)
- send_stats "重装windows server 22"
+ send_stats "重装windows server 25"
dd_xitong_2
- bash InstallNET.sh -windows 2022 -lang "cn"
+ bash InstallNET.sh -windows 2025 -lang "cn"
reboot
exit
;;
+
45)
- send_stats "重装windows server 19"
+ send_stats "重装windows server 22"
dd_xitong_2
- bash InstallNET.sh -windows 2019 -lang "cn"
+ bash InstallNET.sh -windows 2022 -lang "cn"
reboot
exit
;;
+
46)
- send_stats "重装windows server 16"
+ send_stats "重装windows server 19"
dd_xitong_2
- bash InstallNET.sh -windows 2016 -lang "cn"
+ bash InstallNET.sh -windows 2019 -lang "cn"
reboot
exit
;;
@@ -4849,6 +5599,140 @@ bbrv3() {
root_use
send_stats "bbrv3管理"
+ xanmod_add_repo() {
+ local keyring="/usr/share/keyrings/xanmod-archive-keyring.gpg"
+ local list_file="/etc/apt/sources.list.d/xanmod-release.list"
+ local key_url="https://dl.xanmod.org/archive.key"
+ local fallback_key_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key"
+ local os_codename=""
+
+ if command -v lsb_release >/dev/null 2>&1; then
+ os_codename=$(lsb_release -sc)
+ elif [ -r /etc/os-release ]; then
+ os_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
+ fi
+
+ # 兼容官方已移除的老系统代号(回退使用 releases 尝试旧包库)
+ if ! echo "bookworm trixie forky sid noble plucky questing resolute faye gigi wilma xia zara zena jammy" | grep -qw "$os_codename"; then
+ os_codename="releases"
+ fi
+
+ if [ -z "$os_codename" ]; then
+ echo "无法获取系统代号,无法配置XanMod源"
+ return 1
+ fi
+
+ install wget gnupg ca-certificates
+ mkdir -p /usr/share/keyrings /etc/apt/sources.list.d
+ if ! wget -qO - "$key_url" | gpg --dearmor -o "$keyring" --yes; then
+ echo "官方密钥下载失败,尝试备用下载源..."
+ wget -qO - "$fallback_key_url" | gpg --dearmor -o "$keyring" --yes || return 1
+ fi
+ chmod 644 "$keyring"
+ echo "deb [signed-by=$keyring] http://deb.xanmod.org $os_codename main" > "$list_file"
+ }
+
+ xanmod_detect_psabi_level() {
+ local psabi_output=""
+ psabi_output=$(awk 'BEGIN {
+ while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
+ if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
+ if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
+ if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
+ if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
+ if (level > 0) { print level; exit }
+ exit 1
+ }' /proc/cpuinfo 2>/dev/null) || return 1
+ printf '%s' "$psabi_output" | tr -dc '0-9' | head -c 1
+ }
+
+ xanmod_package_available() {
+ local package="$1"
+ apt-cache policy "$package" 2>/dev/null | grep -q 'Candidate: [^ ]'
+ }
+
+ xanmod_detect_package() {
+ local psabi_level=""
+ local level=""
+ local package=""
+ local prefix_list="linux-xanmod linux-xanmod-lts"
+
+ psabi_level=$(xanmod_detect_psabi_level) || return 1
+ [ -n "$psabi_level" ] || return 1
+ [ "$psabi_level" -gt 3 ] && psabi_level=3
+
+ apt update -y >/dev/null 2>&1
+
+ for prefix in $prefix_list; do
+ level="$psabi_level"
+ while [ "$level" -ge 1 ]; do
+ package="${prefix}-x64v${level}"
+ if xanmod_package_available "$package"; then
+ if [ "$level" != "$psabi_level" ] || [ "$prefix" = "linux-xanmod-lts" ]; then
+ echo "已自动匹配合适安装包: $package" >&2
+ fi
+ printf '%s\n' "$package"
+ return 0
+ fi
+ level=$((level - 1))
+ done
+ done
+
+ echo "软件源中未找到适配此CPU的XanMod内核包" >&2
+ return 1
+ }
+
+ xanmod_installed() {
+ dpkg-query -W -f='${Package}\n' 'linux-*xanmod*' 2>/dev/null | grep -q '^linux-.*xanmod'
+ }
+
+ xanmod_install_or_update() {
+ local action="$1"
+ local package=""
+
+ check_disk_space 3
+ check_swap
+ xanmod_add_repo || {
+ echo "XanMod官方仓库配置失败,请稍后重试"
+ return 1
+ }
+
+ package=$(xanmod_detect_package) || {
+ echo "无法识别当前CPU或找不到匹配内核包,已取消安装"
+ return 1
+ }
+
+ apt update -y
+ if [ "$action" = "update" ]; then
+ apt install -y --only-upgrade "$package" || apt install -y "$package" || {
+ echo "XanMod内核更新失败,请检查软件源或稍后重试"
+ return 1
+ }
+ else
+ apt install -y "$package" || {
+ echo "XanMod内核安装失败,请检查软件源或稍后重试"
+ return 1
+ }
+ fi
+
+ bbr_on || {
+ echo "BBR3参数写入失败,请检查系统配置"
+ return 1
+ }
+ echo "XanMod BBRv3内核处理完成。重启后生效"
+ server_reboot
+ }
+
+ xanmod_uninstall() {
+ apt purge -y 'linux-*xanmod*'
+ apt autoremove -y
+ update-grub 2>/dev/null || true
+ rm -f /etc/apt/sources.list.d/xanmod-release.list
+ rm -f /usr/share/keyrings/xanmod-archive-keyring.gpg
+ echo "XanMod内核已卸载。重启后生效"
+ server_reboot
+ }
+
local cpu_arch=$(uname -m)
if [ "$cpu_arch" = "aarch64" ]; then
bash <(curl -sL jhb.ovh/jb/bbrv3arm.sh)
@@ -4856,7 +5740,20 @@ bbrv3() {
linux_Settings
fi
- if dpkg -l | grep -q 'linux-xanmod'; then
+ if [ -r /etc/os-release ]; then
+ . /etc/os-release
+ if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
+ echo "当前环境不支持,仅支持Debian和Ubuntu系统"
+ break_end
+ linux_Settings
+ fi
+ else
+ echo "无法确定操作系统类型"
+ break_end
+ linux_Settings
+ fi
+
+ if xanmod_installed; then
while true; do
clear
local kernel_version=$(uname -r)
@@ -4874,38 +5771,14 @@ bbrv3() {
case $sub_choice in
1)
- apt purge -y 'linux-*xanmod1*'
- update-grub
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 步骤3:添加存储库
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- echo "XanMod内核已更新。重启后生效"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
-
- server_reboot
-
- ;;
+ xanmod_install_or_update update
+ ;;
2)
- apt purge -y 'linux-*xanmod1*'
- update-grub
- echo "XanMod内核已卸载。重启后生效"
- server_reboot
- ;;
-
+ xanmod_uninstall
+ ;;
*)
- break # 跳出循环,退出菜单
- ;;
+ break
+ ;;
esac
done
@@ -4917,48 +5790,12 @@ bbrv3() {
echo "------------------------------------------------"
echo "仅支持Debian/Ubuntu"
echo "请备份数据,将为你升级Linux内核开启BBR3"
- echo "VPS是512M内存的,请提前添加1G虚拟内存,防止因内存不足失联!"
echo "------------------------------------------------"
read -e -p "确定继续吗?(Y/N): " choice
case "$choice" in
[Yy])
- check_disk_space 3
- if [ -r /etc/os-release ]; then
- . /etc/os-release
- if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
- echo "当前环境不支持,仅支持Debian和Ubuntu系统"
- break_end
- linux_Settings
- fi
- else
- echo "无法确定操作系统类型"
- break_end
- linux_Settings
- fi
-
- check_swap
- install wget gnupg
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 步骤3:添加存储库
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- bbr_on
-
- echo "XanMod内核安装并BBR3启用成功。重启后生效"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
- server_reboot
-
+ xanmod_install_or_update install
;;
[Nn])
echo "已取消"
@@ -4971,7 +5808,6 @@ bbrv3() {
}
-
elrepo_install() {
# 导入 ELRepo GPG 公钥
echo "导入 ELRepo GPG 公钥..."
@@ -5087,7 +5923,7 @@ elrepo() {
clamav_freshclam() {
- echo -e "${gl_huang}正在更新病毒库...${gl_bai}"
+ echo -e "${gl_kjlan}正在更新病毒库...${gl_bai}"
docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
@@ -5101,7 +5937,7 @@ clamav_scan() {
return
fi
- echo -e "${gl_huang}正在扫描目录$@... ${gl_bai}"
+ echo -e "${gl_kjlan}正在扫描目录$@... ${gl_bai}"
# 构建 mount 参数
local MOUNT_PARAMS=""
@@ -5119,7 +5955,7 @@ clamav_scan() {
> /home/docker/clamav/log/scan.log > /dev/null 2>&1
# 执行 Docker 命令
- docker run -it --rm \
+ docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
$MOUNT_PARAMS \
@@ -5189,171 +6025,348 @@ clamav() {
}
+# ============================================================================
+# Linux 内核调优模块(重构版)
+# 统一核心函数 + 场景差异化参数 + 持久化到配置文件 + 硬件自适应
+# 替换原 optimize_high_performance / optimize_balanced / optimize_web_server / restore_defaults
+# ============================================================================
+# 获取内存大小(MB)
+_get_mem_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
+}
-# 高性能模式优化函数
-optimize_high_performance() {
- echo -e "${gl_lv}切换到${tiaoyou_moshi}...${gl_bai}"
-
- echo -e "${gl_lv}优化文件描述符...${gl_bai}"
- ulimit -n 65535
+# 统一内核调优核心函数
+# 参数: $1 = 模式名称, $2 = 场景 (high/balanced/web/stream/game)
+_kernel_optimize_core() {
+ local mode_name="$1"
+ local scene="${2:-high}"
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
+ local MEM_MB=$(_get_mem_mb)
+
+ echo -e "${gl_lv}切换到${mode_name}...${gl_bai}"
+
+ # ── 根据场景设定参数 ──
+ local SWAPPINESS DIRTY_RATIO DIRTY_BG_RATIO OVERCOMMIT MIN_FREE_KB VFS_PRESSURE
+ local RMEM_MAX WMEM_MAX TCP_RMEM TCP_WMEM
+ local SOMAXCONN BACKLOG SYN_BACKLOG
+ local PORT_RANGE SCHED_AUTOGROUP THP NUMA FIN_TIMEOUT
+ local KEEPALIVE_TIME KEEPALIVE_INTVL KEEPALIVE_PROBES
+
+ case "$scene" in
+ high|stream|game)
+ # 高性能/直播/游戏:激进参数
+ SWAPPINESS=10
+ DIRTY_RATIO=15
+ DIRTY_BG_RATIO=5
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=67108864
+ WMEM_MAX=67108864
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ SOMAXCONN=8192
+ BACKLOG=250000
+ SYN_BACKLOG=8192
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=10
+ KEEPALIVE_TIME=300
+ KEEPALIVE_INTVL=30
+ KEEPALIVE_PROBES=5
+ ;;
+ web)
+ # 网站服务器:高并发优先
+ SWAPPINESS=10
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=33554432
+ WMEM_MAX=33554432
+ TCP_RMEM="4096 131072 33554432"
+ TCP_WMEM="4096 131072 33554432"
+ SOMAXCONN=16384
+ BACKLOG=10000
+ SYN_BACKLOG=16384
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=15
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ balanced)
+ # 均衡模式:适度优化
+ SWAPPINESS=30
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=0
+ VFS_PRESSURE=75
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ SOMAXCONN=4096
+ BACKLOG=5000
+ SYN_BACKLOG=4096
+ PORT_RANGE="1024 49151"
+ SCHED_AUTOGROUP=1
+ THP="always"
+ NUMA=1
+ FIN_TIMEOUT=30
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ esac
- echo -e "${gl_lv}优化虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=15 2>/dev/null
- sysctl -w vm.dirty_background_ratio=5 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # ── 根据内存大小自适应调整 ──
+ if [ "$MEM_MB" -ge 16384 ]; then
+ MIN_FREE_KB=131072
+ [ "$scene" != "balanced" ] && SWAPPINESS=5
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ MIN_FREE_KB=32768
+ # 小内存缩小缓冲区
+ if [ "$scene" != "balanced" ]; then
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ fi
+ else
+ MIN_FREE_KB=16384
+ SWAPPINESS=30
+ OVERCOMMIT=0
+ RMEM_MAX=4194304
+ WMEM_MAX=4194304
+ TCP_RMEM="4096 32768 4194304"
+ TCP_WMEM="4096 32768 4194304"
+ SOMAXCONN=1024
+ BACKLOG=1000
+ fi
- echo -e "${gl_lv}优化网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=250000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # ── 直播场景额外:UDP 缓冲区加大 ──
+ local STREAM_EXTRA=""
+ if [ "$scene" = "stream" ]; then
+ STREAM_EXTRA="
+# 直播推流 UDP 优化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
- echo -e "${gl_lv}优化缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # ── 游戏服场景额外:低延迟优先 ──
+ local GAME_EXTRA=""
+ if [ "$scene" = "game" ]; then
+ GAME_EXTRA="
+# 游戏服低延迟优化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384
+net.ipv4.tcp_slow_start_after_idle = 0"
+ fi
- echo -e "${gl_lv}优化CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # ── 加载 BBR 模块 ──
+ local CC="bbr"
+ local QDISC="fq"
+ local KVER
+ KVER=$(uname -r | grep -oP '^\d+\.\d+')
+ if printf '%s\n%s' "4.9" "$KVER" | sort -V -C; then
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ fi
+ if ! sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
+ else
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
- echo -e "${gl_lv}其他优化...${gl_bai}"
- # 禁用透明大页面,减少延迟
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # ── 备份已有配置 ──
+ [ -f "$CONF" ] && cp "$CONF" "${CONF}.bak.$(date +%s)"
+
+ # ── 写入配置文件(持久化) ──
+ echo -e "${gl_lv}写入优化配置...${gl_bai}"
+ cat > "$CONF" << SYSCTL
+# kejilion 内核调优配置
+# 模式: $mode_name | 场景: $scene
+# 内存: ${MEM_MB}MB | 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
+
+# ── TCP 拥塞控制 ──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $CC
+
+# ── TCP 缓冲区 ──
+net.core.rmem_max = $RMEM_MAX
+net.core.wmem_max = $WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+
+# ── 连接队列 ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $BACKLOG
+net.ipv4.tcp_max_syn_backlog = $SYN_BACKLOG
+
+# ── TCP 连接优化 ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = $FIN_TIMEOUT
+net.ipv4.tcp_keepalive_time = $KEEPALIVE_TIME
+net.ipv4.tcp_keepalive_intvl = $KEEPALIVE_INTVL
+net.ipv4.tcp_keepalive_probes = $KEEPALIVE_PROBES
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── 端口与内存 ──
+net.ipv4.ip_local_port_range = $PORT_RANGE
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── 虚拟内存 ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = $DIRTY_RATIO
+vm.dirty_background_ratio = $DIRTY_BG_RATIO
+vm.overcommit_memory = $OVERCOMMIT
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = $VFS_PRESSURE
+
+# ── CPU/内核调度 ──
+kernel.sched_autogroup_enabled = $SCHED_AUTOGROUP
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = $NUMA" || echo "# numa_balancing 不支持")
+
+# ── 安全防护 ──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── 文件描述符 ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── 连接跟踪 ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+echo "net.netfilter.nf_conntrack_max = $((SOMAXCONN * 32))"
+echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+echo "# conntrack 未启用"
+fi)
+$STREAM_EXTRA
+$GAME_EXTRA
+SYSCTL
+
+ # ── 应用配置(逐行,跳过不支持的参数) ──
+ echo -e "${gl_lv}应用优化参数...${gl_bai}"
+ local applied=0 skipped=0
+ while IFS= read -r line; do
+ # 跳过注释和空行
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
+ [[ -z "${line// /}" ]] && continue
+ if sysctl -w "$line" >/dev/null 2>&1; then
+ applied=$((applied + 1))
+ else
+ skipped=$((skipped + 1))
+ fi
+ done < "$CONF"
+ echo -e "${gl_lv}已应用 ${applied} 项参数${skipped:+,跳过 ${skipped} 项不支持的参数}${gl_bai}"
+ # ── 透明大页面 ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo "$THP" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
+ fi
-}
+ # ── 文件描述符限制 ──
+ if ! grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
-# 均衡模式优化函数
-optimize_balanced() {
- echo -e "${gl_lv}切换到均衡模式...${gl_bai}"
-
- echo -e "${gl_lv}优化文件描述符...${gl_bai}"
- ulimit -n 32768
-
- echo -e "${gl_lv}优化虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=30 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=32768 2>/dev/null
-
- echo -e "${gl_lv}优化网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=8388608 2>/dev/null
- sysctl -w net.core.wmem_max=8388608 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=125000 2>/dev/null
- sysctl -w net.core.somaxconn=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 32768 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 49151' 2>/dev/null
-
- echo -e "${gl_lv}优化缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=75 2>/dev/null
-
- echo -e "${gl_lv}优化CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}其他优化...${gl_bai}"
- # 还原透明大页面
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # 还原 NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# kejilion-optimize
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ fi
+ # ── BBR 持久化 ──
+ if [ "$CC" = "bbr" ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ # 清理旧的 sysctl.conf 里的 bbr 配置(避免冲突)
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+ fi
+ echo -e "${gl_lv}${mode_name} 优化完成!配置已持久化到 ${CONF}${gl_bai}"
+ echo -e "${gl_lv}内存: ${MEM_MB}MB | 拥塞算法: ${CC} | 队列: ${QDISC}${gl_bai}"
}
-# 还原默认设置函数
-restore_defaults() {
- echo -e "${gl_lv}还原到默认设置...${gl_bai}"
-
- echo -e "${gl_lv}还原文件描述符...${gl_bai}"
- ulimit -n 1024
-
- echo -e "${gl_lv}还原虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=60 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=16384 2>/dev/null
-
- echo -e "${gl_lv}还原网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=212992 2>/dev/null
- sysctl -w net.core.wmem_max=212992 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=1000 2>/dev/null
- sysctl -w net.core.somaxconn=128 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=0 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='32768 60999' 2>/dev/null
-
- echo -e "${gl_lv}还原缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=100 2>/dev/null
-
- echo -e "${gl_lv}还原CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}还原其他优化...${gl_bai}"
- # 还原透明大页面
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # 还原 NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# ── 各模式入口函数(保持原有调用接口不变) ──
+optimize_high_performance() {
+ _kernel_optimize_core "${tiaoyou_moshi:-高性能优化模式}" "high"
}
+optimize_balanced() {
+ _kernel_optimize_core "均衡优化模式" "balanced"
+}
-
-# 网站搭建优化函数
optimize_web_server() {
- echo -e "${gl_lv}切换到网站搭建优化模式...${gl_bai}"
+ _kernel_optimize_core "网站搭建优化模式" "web"
+}
+
+# ── 还原默认设置(完全清理) ──
+restore_defaults() {
+ echo -e "${gl_lv}还原到默认设置...${gl_bai}"
- echo -e "${gl_lv}优化文件描述符...${gl_bai}"
- ulimit -n 65535
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
- echo -e "${gl_lv}优化虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # 删除优化配置文件(含外链自动调优配置)
+ rm -f "$CONF"
+ rm -f /etc/sysctl.d/99-network-optimize.conf
- echo -e "${gl_lv}优化网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=5000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # 清理 sysctl.conf 里可能残留的 bbr 配置
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
- echo -e "${gl_lv}优化缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # 重新加载系统默认配置
+ sysctl --system 2>/dev/null | tail -1
- echo -e "${gl_lv}优化CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # 还原透明大页面
+ [ -f /sys/kernel/mm/transparent_hugepage/enabled ] && \
+ echo always > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
- echo -e "${gl_lv}其他优化...${gl_bai}"
- # 禁用透明大页面,减少延迟
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # 清理文件描述符配置
+ if grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# kejilion-optimize/,+4d' /etc/security/limits.conf
+ fi
+ # 清理 BBR 持久化
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+ echo -e "${gl_lv}系统已还原到默认设置${gl_bai}"
}
@@ -5362,18 +6375,26 @@ Kernel_optimize() {
while true; do
clear
send_stats "Linux内核调优管理"
+ local current_mode=$(grep "^# 模式:" /etc/sysctl.d/99-kejilion-optimize.conf 2>/dev/null | sed 's/# 模式: //' | awk -F'|' '{print $1}' | xargs)
+ [ -z "$current_mode" ] && [ -f /etc/sysctl.d/99-network-optimize.conf ] && current_mode="自动调优模式"
echo "Linux系统内核参数优化"
+ if [ -n "$current_mode" ]; then
+ echo -e "当前模式: ${gl_lv}${current_mode}${gl_bai}"
+ else
+ echo -e "当前模式: ${gl_hui}未优化${gl_bai}"
+ fi
echo "视频介绍: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
echo "------------------------------------------------"
echo "提供多种系统参数调优模式,用户可以根据自身使用场景进行选择切换。"
echo -e "${gl_huang}提示: ${gl_bai}生产环境请谨慎使用!"
- echo "--------------------"
- echo "1. 高性能优化模式: 最大化系统性能,优化文件描述符、虚拟内存、网络设置、缓存管理和CPU设置。"
- echo "2. 均衡优化模式: 在性能与资源消耗之间取得平衡,适合日常使用。"
- echo "3. 网站优化模式: 针对网站服务器进行优化,提高并发连接处理能力、响应速度和整体性能。"
- echo "4. 直播优化模式: 针对直播推流的特殊需求进行优化,减少延迟,提高传输性能。"
- echo "5. 游戏服优化模式: 针对游戏服务器进行优化,提高并发处理能力和响应速度。"
- echo "6. 还原默认设置: 将系统设置还原为默认配置。"
+ echo -e "--------------------"
+ echo -e "1. 高性能优化模式: 最大化系统性能,激进的内存和网络参数。"
+ echo -e "2. 均衡优化模式: 在性能与资源消耗之间取得平衡,适合日常使用。"
+ echo -e "3. 网站优化模式: 针对网站服务器优化,超高并发连接队列。"
+ echo -e "4. 直播优化模式: 针对直播推流优化,UDP 缓冲区加大,减少延迟。"
+ echo -e "5. 游戏服优化模式: 针对游戏服务器优化,低延迟优先。"
+ echo -e "6. 还原默认设置: 将系统设置还原为默认配置。"
+ echo -e "7. 自动调优: 根据测试数据自动调优内核参数。${gl_huang}★${gl_bai}"
echo "--------------------"
echo "0. 返回上一级选单"
echo "--------------------"
@@ -5401,23 +6422,30 @@ Kernel_optimize() {
4)
cd ~
clear
- local tiaoyou_moshi="直播优化模式"
- optimize_high_performance
+ _kernel_optimize_core "直播优化模式" "stream"
send_stats "直播推流优化"
;;
5)
cd ~
clear
- local tiaoyou_moshi="游戏服优化模式"
- optimize_high_performance
+ _kernel_optimize_core "游戏服优化模式" "game"
send_stats "游戏服优化"
;;
6)
cd ~
clear
restore_defaults
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh -o /tmp/network-optimize.sh && source /tmp/network-optimize.sh && restore_network_defaults
send_stats "还原默认设置"
;;
+
+ 7)
+ cd ~
+ clear
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ send_stats "内核自动调优"
+ ;;
+
*)
break
;;
@@ -5430,6 +6458,8 @@ Kernel_optimize() {
+
+
update_locale() {
local lang=$1
local locale_file=$2
@@ -5654,7 +6684,10 @@ linux_trash() {
done
}
-
+linux_fav() {
+send_stats "命令收藏夹"
+bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+}
# 创建备份
create_backup() {
@@ -5666,7 +6699,7 @@ create_backup() {
echo " - 备份单个目录: /var/www"
echo " - 备份多个目录: /etc /home /var/log"
echo " - 直接回车将使用默认目录 (/etc /usr /home)"
- read -r -p "请输入要备份的目录(多个目录用空格分隔,直接回车则使用默认目录):" input
+ read -e -p "请输入要备份的目录(多个目录用空格分隔,直接回车则使用默认目录):" input
# 如果用户没有输入目录,则使用默认目录
if [ -z "$input" ]; then
@@ -5800,30 +6833,95 @@ linux_backup() {
-# 显示连接列表
-list_connections() {
- echo "已保存的连接:"
- echo "------------------------"
- cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
- echo "------------------------"
+# SSH 输入标准化函数
+kj_ssh_validate_host() {
+ local host="$1"
+ [[ -n "$host" && ! "$host" =~ [[:space:]] && "$host" =~ ^[A-Za-z0-9._:-]+$ ]]
}
+kj_ssh_validate_port() {
+ local port="$1"
+ [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
+}
-# 添加新连接
-add_connection() {
- send_stats "添加新连接"
- echo "创建新连接示例:"
- echo " - 连接名称: my_server"
- echo " - IP地址: 192.168.1.100"
- echo " - 用户名: root"
- echo " - 端口: 22"
- echo "------------------------"
- read -e -p "请输入连接名称: " name
- read -e -p "请输入IP地址: " ip
- read -e -p "请输入用户名 (默认: root): " user
- local user=${user:-root} # 如果用户未输入,则使用默认值 root
- read -e -p "请输入端口号 (默认: 22): " port
- local port=${port:-22} # 如果用户未输入,则使用默认值 22
+kj_ssh_validate_user() {
+ local user="$1"
+ [[ -n "$user" && "$user" =~ ^[A-Za-z_][A-Za-z0-9._-]*$ ]]
+}
+
+kj_ssh_read_host_port() {
+ local host_prompt="$1"
+ local port_prompt="$2"
+ local default_port="${3:-22}"
+
+ while true; do
+ read -e -p "$host_prompt" KJ_SSH_HOST
+ if kj_ssh_validate_host "$KJ_SSH_HOST"; then
+ break
+ fi
+ echo "错误: 请输入有效的服务器地址。"
+ done
+
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ break
+ fi
+ echo "错误: 端口必须是 1-65535 之间的数字。"
+ done
+}
+
+kj_ssh_read_host_user_port() {
+ local host_prompt="$1"
+ local user_prompt="$2"
+ local port_prompt="$3"
+ local default_user="${4:-root}"
+ local default_port="${5:-22}"
+
+ kj_ssh_read_host_port "$host_prompt" "$port_prompt" "$default_port"
+
+ while true; do
+ read -e -p "$user_prompt" KJ_SSH_USER
+ KJ_SSH_USER=${KJ_SSH_USER:-$default_user}
+ if kj_ssh_validate_user "$KJ_SSH_USER"; then
+ break
+ fi
+ echo "错误: 用户名格式不正确。"
+ done
+}
+
+kj_ssh_parse_remote() {
+ local remote_raw="$1"
+ local default_user="${2:-root}"
+ local remote_user remote_host
+
+ if [[ "$remote_raw" == *@* ]]; then
+ remote_user="${remote_raw%@*}"
+ remote_host="${remote_raw#*@}"
+ else
+ remote_user="$default_user"
+ remote_host="$remote_raw"
+ fi
+
+ if ! kj_ssh_validate_user "$remote_user"; then
+ echo "错误: SSH 用户名格式不正确。"
+ return 1
+ fi
+
+ if ! kj_ssh_validate_host "$remote_host"; then
+ echo "错误: SSH 主机地址格式不正确。"
+ return 1
+ fi
+
+ KJ_SSH_USER="$remote_user"
+ KJ_SSH_HOST="$remote_host"
+ KJ_SSH_REMOTE="$remote_user@$remote_host"
+}
+
+kj_ssh_read_auth() {
+ local key_file="$1"
+ local password_or_key=""
echo "请选择身份验证方式:"
echo "1. 密码"
@@ -5833,37 +6931,92 @@ add_connection() {
case $auth_choice in
1)
read -s -p "请输入密码: " password_or_key
- echo # 换行
+ echo
+ if [ -z "$password_or_key" ]; then
+ echo "错误: 密码不能为空。"
+ return 1
+ fi
+ KJ_SSH_AUTH_METHOD="password"
+ KJ_SSH_AUTH_SECRET="$password_or_key"
;;
2)
echo "请粘贴密钥内容 (粘贴完成后按两次回车):"
- local password_or_key=""
while IFS= read -r line; do
- # 如果输入为空行且密钥内容已经包含了开头,则结束输入
if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
break
fi
- # 如果是第一行或已经开始输入密钥内容,则继续添加
if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- local password_or_key+="${line}"$'\n'
+ password_or_key+="${line}"$'\n'
fi
done
- # 检查是否是密钥内容
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/$name.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- local password_or_key="$key_file"
+ if [[ "$password_or_key" != *"-----BEGIN"* || "$password_or_key" != *"PRIVATE KEY-----"* ]]; then
+ echo "无效的密钥内容!"
+ return 1
fi
+
+ mkdir -p "$(dirname "$key_file")"
+ echo -n "$password_or_key" > "$key_file"
+ chmod 600 "$key_file"
+ KJ_SSH_AUTH_METHOD="key"
+ KJ_SSH_AUTH_SECRET="$key_file"
;;
*)
echo "无效的选择!"
- return
+ return 1
;;
esac
+}
+
+kj_ssh_read_password() {
+ local prompt="${1:-请输入密码: }"
+ while true; do
+ read -e -s -p "$prompt" KJ_SSH_PASSWORD
+ echo
+ [ -n "$KJ_SSH_PASSWORD" ] && break
+ echo "错误: 密码不能为空。"
+ done
+}
+
+kj_ssh_read_port() {
+ local port_prompt="$1"
+ local default_port="${2:-22}"
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ return 0
+ fi
+ echo "错误: 端口必须是 1-65535 之间的数字。"
+ done
+}
+
+# 显示连接列表
+list_connections() {
+ echo "已保存的连接:"
+ echo "------------------------"
+ cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
+ echo "------------------------"
+}
- echo "$name|$ip|$user|$port|$password_or_key" >> "$CONFIG_FILE"
+
+# 添加新连接
+add_connection() {
+ send_stats "添加新连接"
+ echo "创建新连接示例:"
+ echo " - 连接名称: my_server"
+ echo " - IP地址: 192.168.1.100"
+ echo " - 用户名: root"
+ echo " - 端口: 22"
+ echo "------------------------"
+ read -e -p "请输入连接名称: " name
+
+ kj_ssh_read_host_user_port "请输入IP地址: " "请输入用户名 (默认: root): " "请输入端口号 (默认: 22): " "root" "22"
+ if ! kj_ssh_read_auth "$KEY_DIR/$name.key"; then
+ return
+ fi
+
+ echo "$name|$KJ_SSH_HOST|$KJ_SSH_USER|$KJ_SSH_PORT|$KJ_SSH_AUTH_SECRET" >> "$CONFIG_FILE"
echo "连接已保存!"
}
@@ -5988,38 +7141,66 @@ list_partitions() {
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT | grep -v "sr\|loop"
}
-# 挂载分区
+
+# 持久化挂载分区
mount_partition() {
send_stats "挂载分区"
read -e -p "请输入要挂载的分区名称(例如 sda1): " PARTITION
+ DEVICE="/dev/$PARTITION"
+ MOUNT_POINT="/mnt/$PARTITION"
+
# 检查分区是否存在
- if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
+ if ! lsblk -no NAME | grep -qw "$PARTITION"; then
echo "分区不存在!"
- return
+ return 1
fi
- # 检查分区是否已经挂载
- if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
+ # 检查是否已挂载
+ if mount | grep -qw "$DEVICE"; then
echo "分区已经挂载!"
- return
+ return 1
+ fi
+
+ # 获取 UUID
+ UUID=$(blkid -s UUID -o value "$DEVICE")
+ if [ -z "$UUID" ]; then
+ echo "无法获取 UUID!"
+ return 1
+ fi
+
+ # 获取文件系统类型
+ FSTYPE=$(blkid -s TYPE -o value "$DEVICE")
+ if [ -z "$FSTYPE" ]; then
+ echo "无法获取文件系统类型!"
+ return 1
fi
# 创建挂载点
- MOUNT_POINT="/mnt/$PARTITION"
mkdir -p "$MOUNT_POINT"
- # 挂载分区
- mount "/dev/$PARTITION" "$MOUNT_POINT"
-
- if [ $? -eq 0 ]; then
- echo "分区挂载成功: $MOUNT_POINT"
- else
+ # 挂载
+ if ! mount "$DEVICE" "$MOUNT_POINT"; then
echo "分区挂载失败!"
rmdir "$MOUNT_POINT"
+ return 1
fi
+
+ echo "分区已成功挂载到 $MOUNT_POINT"
+
+ # 检查 /etc/fstab 是否已经存在 UUID 或挂载点
+ if grep -qE "UUID=$UUID|[[:space:]]$MOUNT_POINT[[:space:]]" /etc/fstab; then
+ echo "/etc/fstab 中已存在该分区记录,跳过写入"
+ return 0
+ fi
+
+ # 写入 /etc/fstab
+ echo "UUID=$UUID $MOUNT_POINT $FSTYPE defaults,nofail 0 2" >> /etc/fstab
+
+ echo "已写入 /etc/fstab,实现持久化挂载"
}
+
# 卸载分区
unmount_partition() {
send_stats "卸载分区"
@@ -6168,52 +7349,23 @@ add_task() {
read -e -p "请输入任务名称: " name
read -e -p "请输入本地目录: " local_path
read -e -p "请输入远程目录: " remote_path
- read -e -p "请输入远程用户@IP: " remote
- read -e -p "请输入 SSH 端口 (默认 22): " port
- port=${port:-22}
- echo "请选择身份验证方式:"
- echo "1. 密码"
- echo "2. 密钥"
- read -e -p "请选择 (1/2): " auth_choice
+ while true; do
+ read -e -p "请输入远程用户@IP: " remote
+ if kj_ssh_parse_remote "$remote" "root"; then
+ remote="$KJ_SSH_REMOTE"
+ break
+ fi
+ done
- case $auth_choice in
- 1)
- read -s -p "请输入密码: " password_or_key
- echo # 换行
- auth_method="password"
- ;;
- 2)
- echo "请粘贴密钥内容 (粘贴完成后按两次回车):"
- local password_or_key=""
- while IFS= read -r line; do
- # 如果输入为空行且密钥内容已经包含了开头,则结束输入
- if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
- break
- fi
- # 如果是第一行或已经开始输入密钥内容,则继续添加
- if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- password_or_key+="${line}"$'\n'
- fi
- done
+ kj_ssh_read_port "请输入 SSH 端口 (默认 22): " "22"
+ port="$KJ_SSH_PORT"
- # 检查是否是密钥内容
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/${name}_sync.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- password_or_key="$key_file"
- auth_method="key"
- else
- echo "无效的密钥内容!"
- return
- fi
- ;;
- *)
- echo "无效的选择!"
- return
- ;;
- esac
+ if ! kj_ssh_read_auth "$KEY_DIR/${name}_sync.key"; then
+ return
+ fi
+ auth_method="$KJ_SSH_AUTH_METHOD"
+ password_or_key="$KJ_SSH_AUTH_SECRET"
echo "请选择同步模式:"
echo "1. 标准模式 (-avz)"
@@ -6232,6 +7384,7 @@ add_task() {
echo "任务已保存!"
}
+
# 删除任务
delete_task() {
send_stats "删除同步任务"
@@ -6439,9 +7592,12 @@ rsync_manager() {
-linux_ps() {
+linux_info() {
+
+
clear
+ echo -e "${gl_kjlan}正在查询系统信息……${gl_bai}"
send_stats "系统信息查询"
ip_address
@@ -6490,43 +7646,46 @@ linux_ps() {
local timezone=$(current_timezone)
+ local tcp_count=$(ss -t | wc -l)
+ local udp_count=$(ss -u | wc -l)
- echo ""
+ clear
echo -e "系统信息查询"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}主机名: ${gl_bai}$hostname"
- echo -e "${gl_kjlan}系统版本: ${gl_bai}$os_info"
- echo -e "${gl_kjlan}Linux版本: ${gl_bai}$kernel_version"
+ echo -e "${gl_kjlan}主机名: ${gl_bai}$hostname"
+ echo -e "${gl_kjlan}系统版本: ${gl_bai}$os_info"
+ echo -e "${gl_kjlan}Linux版本: ${gl_bai}$kernel_version"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU架构: ${gl_bai}$cpu_arch"
- echo -e "${gl_kjlan}CPU型号: ${gl_bai}$cpu_info"
- echo -e "${gl_kjlan}CPU核心数: ${gl_bai}$cpu_cores"
- echo -e "${gl_kjlan}CPU频率: ${gl_bai}$cpu_freq"
+ echo -e "${gl_kjlan}CPU架构: ${gl_bai}$cpu_arch"
+ echo -e "${gl_kjlan}CPU型号: ${gl_bai}$cpu_info"
+ echo -e "${gl_kjlan}CPU核心数: ${gl_bai}$cpu_cores"
+ echo -e "${gl_kjlan}CPU频率: ${gl_bai}$cpu_freq"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU占用: ${gl_bai}$cpu_usage_percent%"
- echo -e "${gl_kjlan}系统负载: ${gl_bai}$load"
- echo -e "${gl_kjlan}物理内存: ${gl_bai}$mem_info"
- echo -e "${gl_kjlan}虚拟内存: ${gl_bai}$swap_info"
- echo -e "${gl_kjlan}硬盘占用: ${gl_bai}$disk_info"
+ echo -e "${gl_kjlan}CPU占用: ${gl_bai}$cpu_usage_percent%"
+ echo -e "${gl_kjlan}系统负载: ${gl_bai}$load"
+ echo -e "${gl_kjlan}TCP|UDP连接数: ${gl_bai}$tcp_count|$udp_count"
+ echo -e "${gl_kjlan}物理内存: ${gl_bai}$mem_info"
+ echo -e "${gl_kjlan}虚拟内存: ${gl_bai}$swap_info"
+ echo -e "${gl_kjlan}硬盘占用: ${gl_bai}$disk_info"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}总接收: ${gl_bai}$rx"
- echo -e "${gl_kjlan}总发送: ${gl_bai}$tx"
+ echo -e "${gl_kjlan}总接收: ${gl_bai}$rx"
+ echo -e "${gl_kjlan}总发送: ${gl_bai}$tx"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}网络算法: ${gl_bai}$congestion_algorithm $queue_algorithm"
+ echo -e "${gl_kjlan}网络算法: ${gl_bai}$congestion_algorithm $queue_algorithm"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}运营商: ${gl_bai}$isp_info"
+ echo -e "${gl_kjlan}运营商: ${gl_bai}$isp_info"
if [ -n "$ipv4_address" ]; then
- echo -e "${gl_kjlan}IPv4地址: ${gl_bai}$ipv4_address"
+ echo -e "${gl_kjlan}IPv4地址: ${gl_bai}$ipv4_address"
fi
if [ -n "$ipv6_address" ]; then
- echo -e "${gl_kjlan}IPv6地址: ${gl_bai}$ipv6_address"
+ echo -e "${gl_kjlan}IPv6地址: ${gl_bai}$ipv6_address"
fi
- echo -e "${gl_kjlan}DNS地址: ${gl_bai}$dns_addresses"
- echo -e "${gl_kjlan}地理位置: ${gl_bai}$country $city"
- echo -e "${gl_kjlan}系统时间: ${gl_bai}$timezone $current_time"
+ echo -e "${gl_kjlan}DNS地址: ${gl_bai}$dns_addresses"
+ echo -e "${gl_kjlan}地理位置: ${gl_bai}$country $city"
+ echo -e "${gl_kjlan}系统时间: ${gl_bai}$timezone $current_time"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}运行时长: ${gl_bai}$runtime"
+ echo -e "${gl_kjlan}运行时长: ${gl_bai}$runtime"
echo
@@ -6541,6 +7700,58 @@ linux_tools() {
clear
# send_stats "基础工具"
echo -e "基础工具"
+
+ tools=(
+ curl wget sudo socat htop iftop unzip tar tmux ffmpeg
+ btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders
+ vim nano git
+ )
+
+ if command -v apt >/dev/null 2>&1; then
+ PM="apt"
+ elif command -v dnf >/dev/null 2>&1; then
+ PM="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PM="yum"
+ elif command -v pacman >/dev/null 2>&1; then
+ PM="pacman"
+ elif command -v apk >/dev/null 2>&1; then
+ PM="apk"
+ elif command -v zypper >/dev/null 2>&1; then
+ PM="zypper"
+ elif command -v opkg >/dev/null 2>&1; then
+ PM="opkg"
+ elif command -v pkg >/dev/null 2>&1; then
+ PM="pkg"
+ else
+ echo "❌ 未识别的包管理器"
+ exit 1
+ fi
+
+ echo "📦 使用包管理器: $PM"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+
+ for ((i=0; i<${#tools[@]}; i+=2)); do
+ # 左列
+ if command -v "${tools[i]}" >/dev/null 2>&1; then
+ left=$(printf "✅ %-12s 已安装" "${tools[i]}")
+ else
+ left=$(printf "❌ %-12s 未安装" "${tools[i]}")
+ fi
+
+ # 右列(防止数组越界)
+ if [[ -n "${tools[i+1]}" ]]; then
+ if command -v "${tools[i+1]}" >/dev/null 2>&1; then
+ right=$(printf "✅ %-12s 已安装" "${tools[i+1]}")
+ else
+ right=$(printf "❌ %-12s 未安装" "${tools[i+1]}")
+ fi
+ printf "%-42s %s\n" "$left" "$right"
+ else
+ printf "%s\n" "$left"
+ fi
+ done
+
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}curl 下载工具 ${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}wget 下载工具 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}3. ${gl_bai}sudo 超级管理权限工具 ${gl_kjlan}4. ${gl_bai}socat 通信连接工具"
@@ -6551,7 +7762,7 @@ linux_tools() {
echo -e "${gl_kjlan}11. ${gl_bai}btop 现代化监控工具 ${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}ranger 文件管理工具"
echo -e "${gl_kjlan}13. ${gl_bai}ncdu 磁盘占用查看工具 ${gl_kjlan}14. ${gl_bai}fzf 全局搜索工具"
echo -e "${gl_kjlan}15. ${gl_bai}vim 文本编辑器 ${gl_kjlan}16. ${gl_bai}nano 文本编辑器 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}17. ${gl_bai}git 版本控制系统"
+ echo -e "${gl_kjlan}17. ${gl_bai}git 版本控制系统 ${gl_kjlan}18. ${gl_bai}opencode AI编程助手 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}黑客帝国屏保 ${gl_kjlan}22. ${gl_bai}跑火车屏保"
echo -e "${gl_kjlan}26. ${gl_bai}俄罗斯方块小游戏 ${gl_kjlan}27. ${gl_bai}贪吃蛇小游戏"
@@ -6710,6 +7921,17 @@ linux_tools() {
send_stats "安装git"
;;
+ 18)
+ clear
+ cd ~
+ curl -fsSL https://opencode.ai/install | bash
+ source ~/.bashrc
+ source ~/.profile
+ opencode
+ send_stats "安装opencode"
+ ;;
+
+
21)
clear
install cmatrix
@@ -6738,6 +7960,7 @@ linux_tools() {
nsnake
send_stats "安装nsnake"
;;
+
28)
clear
install ninvaders
@@ -6763,6 +7986,8 @@ linux_tools() {
clear
send_stats "全部卸载"
remove htop iftop tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
+ opencode uninstall
+ rm -rf ~/.opencode
;;
41)
@@ -6820,7 +8045,7 @@ linux_bbr() {
send_stats "alpine开启bbr3"
;;
2)
- sed -i '/net.ipv4.tcp_congestion_control=bbr/d' /etc/sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control=/d' /etc/sysctl.conf
sysctl -p
server_reboot
;;
@@ -6844,16 +8069,337 @@ linux_bbr() {
-linux_docker() {
+docker_ssh_migration() {
- while true; do
- clear
- # send_stats "docker管理"
- echo -e "Docker管理"
- docker_tato
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}安装更新Docker环境 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
+ is_compose_container() {
+ local container=$1
+ docker inspect "$container" | jq -e '.[0].Config.Labels["com.docker.compose.project"]' >/dev/null 2>&1
+ }
+
+ list_backups() {
+ local BACKUP_ROOT="/tmp"
+ echo -e "${gl_kjlan}当前备份列表:${gl_bai}"
+ ls -1dt ${BACKUP_ROOT}/docker_backup_* 2>/dev/null || echo "无备份"
+ }
+
+
+
+ # ----------------------------
+ # 备份
+ # ----------------------------
+ backup_docker() {
+ send_stats "Docker备份"
+
+ echo -e "${gl_kjlan}正在备份 Docker 容器...${gl_bai}"
+ docker ps --format '{{.Names}}'
+ read -e -p "请输入要备份的容器名(多个空格分隔,回车备份全部运行中容器): " containers
+
+ install tar jq gzip
+ install_docker
+
+ local BACKUP_ROOT="/tmp"
+ local DATE_STR=$(date +%Y%m%d_%H%M%S)
+ local TARGET_CONTAINERS=()
+ if [ -z "$containers" ]; then
+ mapfile -t TARGET_CONTAINERS < <(docker ps --format '{{.Names}}')
+ else
+ read -ra TARGET_CONTAINERS <<< "$containers"
+ fi
+ [[ ${#TARGET_CONTAINERS[@]} -eq 0 ]] && { echo -e "${gl_hong}没有找到容器${gl_bai}"; return; }
+
+ local BACKUP_DIR="${BACKUP_ROOT}/docker_backup_${DATE_STR}"
+ mkdir -p "$BACKUP_DIR"
+
+ local RESTORE_SCRIPT="${BACKUP_DIR}/docker_restore.sh"
+ echo "#!/bin/bash" > "$RESTORE_SCRIPT"
+ echo "set -e" >> "$RESTORE_SCRIPT"
+ echo "# 自动生成的还原脚本" >> "$RESTORE_SCRIPT"
+
+ # 记录已打包过的 Compose 项目路径,避免重复打包
+ declare -A PACKED_COMPOSE_PATHS=()
+
+ for c in "${TARGET_CONTAINERS[@]}"; do
+ echo -e "${gl_lv}备份容器: $c${gl_bai}"
+ local inspect_file="${BACKUP_DIR}/${c}_inspect.json"
+ docker inspect "$c" > "$inspect_file"
+
+ if is_compose_container "$c"; then
+ echo -e "${gl_kjlan}检测到 $c 是 docker-compose 容器${gl_bai}"
+ local project_dir=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty')
+ local project_name=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty')
+
+ if [ -z "$project_dir" ]; then
+ read -e -p "未检测到 compose 目录,请手动输入路径: " project_dir
+ fi
+
+ # 如果该 Compose 项目已经打包过,跳过
+ if [[ -n "${PACKED_COMPOSE_PATHS[$project_dir]}" ]]; then
+ echo -e "${gl_huang}Compose 项目 [$project_name] 已备份过,跳过重复打包...${gl_bai}"
+ continue
+ fi
+
+ if [ -f "$project_dir/docker-compose.yml" ]; then
+ echo "compose" > "${BACKUP_DIR}/backup_type_${project_name}"
+ echo "$project_dir" > "${BACKUP_DIR}/compose_path_${project_name}.txt"
+ tar -czf "${BACKUP_DIR}/compose_project_${project_name}.tar.gz" -C "$project_dir" .
+ echo "# docker-compose 恢复: $project_name" >> "$RESTORE_SCRIPT"
+ echo "cd \"$project_dir\" && docker compose up -d" >> "$RESTORE_SCRIPT"
+ PACKED_COMPOSE_PATHS["$project_dir"]=1
+ echo -e "${gl_lv}Compose 项目 [$project_name] 已打包: ${project_dir}${gl_bai}"
+ else
+ echo -e "${gl_hong}未找到 docker-compose.yml,跳过此容器...${gl_bai}"
+ fi
+ else
+ # 普通容器备份卷
+ local VOL_PATHS
+ VOL_PATHS=$(docker inspect "$c" --format '{{range .Mounts}}{{.Source}} {{end}}')
+ for path in $VOL_PATHS; do
+ echo "打包卷: $path"
+ tar -czpf "${BACKUP_DIR}/${c}_$(basename $path).tar.gz" -C / "$(echo $path | sed 's/^\///')"
+ done
+
+ # 端口
+ local PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[] | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$inspect_file" 2>/dev/null)
+ for p in "${PORTS[@]}"; do PORT_ARGS+="-p $p "; done
+
+ # 环境变量
+ local ENV_VARS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[] | @sh' "$inspect_file")
+ for e in "${ENVS[@]}"; do ENV_VARS+="-e $e "; done
+
+ # 卷映射
+ local VOL_ARGS=""
+ for path in $VOL_PATHS; do VOL_ARGS+="-v $path:$path "; done
+
+ # 镜像
+ local IMAGE
+ IMAGE=$(jq -r '.[0].Config.Image' "$inspect_file")
+
+ echo -e "\n# 还原容器: $c" >> "$RESTORE_SCRIPT"
+ echo "docker run -d --name $c $PORT_ARGS $VOL_ARGS $ENV_VARS $IMAGE" >> "$RESTORE_SCRIPT"
+ fi
+ done
+
+
+ # 备份 /home/docker 下的所有文件(不含子目录)
+ if [ -d "/home/docker" ]; then
+ echo -e "${gl_kjlan}备份 /home/docker 下的文件...${gl_bai}"
+ find /home/docker -maxdepth 1 -type f | tar -czf "${BACKUP_DIR}/home_docker_files.tar.gz" -T -
+ echo -e "${gl_lv}/home/docker 下的文件已打包到: ${BACKUP_DIR}/home_docker_files.tar.gz${gl_bai}"
+ fi
+
+ chmod +x "$RESTORE_SCRIPT"
+ echo -e "${gl_lv}备份完成: ${BACKUP_DIR}${gl_bai}"
+ echo -e "${gl_lv}可用还原脚本: ${RESTORE_SCRIPT}${gl_bai}"
+
+
+ }
+
+ # ----------------------------
+ # 还原
+ # ----------------------------
+ restore_docker() {
+
+ send_stats "Docker还原"
+ read -e -p "请输入要还原的备份目录: " BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}备份目录不存在${gl_bai}"; return; }
+
+ echo -e "${gl_kjlan}开始执行还原操作...${gl_bai}"
+
+ install tar jq gzip
+ install_docker
+
+ # --------- 优先还原 Compose 项目 ---------
+ for f in "$BACKUP_DIR"/backup_type_*; do
+ [[ ! -f "$f" ]] && continue
+ if grep -q "compose" "$f"; then
+ project_name=$(basename "$f" | sed 's/backup_type_//')
+ path_file="$BACKUP_DIR/compose_path_${project_name}.txt"
+ [[ -f "$path_file" ]] && original_path=$(cat "$path_file") || original_path=""
+ [[ -z "$original_path" ]] && read -e -p "未找到原始路径,请输入还原目录路径: " original_path
+
+ # 检查该 compose 项目的容器是否已经在运行
+ running_count=$(docker ps --filter "label=com.docker.compose.project=$project_name" --format '{{.Names}}' | wc -l)
+ if [[ "$running_count" -gt 0 ]]; then
+ echo -e "${gl_huang}Compose 项目 [$project_name] 已有容器在运行,跳过还原...${gl_bai}"
+ continue
+ fi
+
+ read -e -p "确认还原 Compose 项目 [$project_name] 到路径 [$original_path] ? (y/n): " confirm
+ [[ "$confirm" != "y" ]] && read -e -p "请输入新的还原路径: " original_path
+
+ mkdir -p "$original_path"
+ tar -xzf "$BACKUP_DIR/compose_project_${project_name}.tar.gz" -C "$original_path"
+ echo -e "${gl_lv}Compose 项目 [$project_name] 已解压到: $original_path${gl_bai}"
+
+ cd "$original_path" || return
+ docker compose down || true
+ docker compose up -d
+ echo -e "${gl_lv}Compose 项目 [$project_name] 还原完成!${gl_bai}"
+ fi
+ done
+
+ # --------- 继续还原普通容器 ---------
+ echo -e "${gl_kjlan}检查并还原普通 Docker 容器...${gl_bai}"
+ local has_container=false
+ for json in "$BACKUP_DIR"/*_inspect.json; do
+ [[ ! -f "$json" ]] && continue
+ has_container=true
+ container=$(basename "$json" | sed 's/_inspect.json//')
+ echo -e "${gl_lv}处理容器: $container${gl_bai}"
+
+ # 检查容器是否已经存在且正在运行
+ if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] 已在运行,跳过还原...${gl_bai}"
+ continue
+ fi
+
+ IMAGE=$(jq -r '.[0].Config.Image' "$json")
+ [[ -z "$IMAGE" || "$IMAGE" == "null" ]] && { echo -e "${gl_hong}未找到镜像信息,跳过: $container${gl_bai}"; continue; }
+
+ # 端口映射
+ PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$json")
+ for p in "${PORTS[@]}"; do
+ [[ -n "$p" ]] && PORT_ARGS="$PORT_ARGS -p $p"
+ done
+
+ # 环境变量
+ ENV_ARGS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[]' "$json")
+ for e in "${ENVS[@]}"; do
+ ENV_ARGS="$ENV_ARGS -e \"$e\""
+ done
+
+ # 卷映射 + 卷数据恢复
+ VOL_ARGS=""
+ mapfile -t VOLS < <(jq -r '.[0].Mounts[] | "\(.Source):\(.Destination)"' "$json")
+ for v in "${VOLS[@]}"; do
+ VOL_SRC=$(echo "$v" | cut -d':' -f1)
+ VOL_DST=$(echo "$v" | cut -d':' -f2)
+ mkdir -p "$VOL_SRC"
+ VOL_ARGS="$VOL_ARGS -v $VOL_SRC:$VOL_DST"
+
+ VOL_FILE="$BACKUP_DIR/${container}_$(basename $VOL_SRC).tar.gz"
+ if [[ -f "$VOL_FILE" ]]; then
+ echo "恢复卷数据: $VOL_SRC"
+ tar -xzf "$VOL_FILE" -C /
+ fi
+ done
+
+ # 删除已存在但未运行的容器
+ if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] 存在但未运行,删除旧容器...${gl_bai}"
+ docker rm -f "$container"
+ fi
+
+ # 启动容器
+ echo "执行还原命令: docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ eval "docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ done
+
+ [[ "$has_container" == false ]] && echo -e "${gl_huang}未找到普通容器的备份信息${gl_bai}"
+
+ # 还原 /home/docker 下的文件
+ if [ -f "$BACKUP_DIR/home_docker_files.tar.gz" ]; then
+ echo -e "${gl_kjlan}正在还原 /home/docker 下的文件...${gl_bai}"
+ mkdir -p /home/docker
+ tar -xzf "$BACKUP_DIR/home_docker_files.tar.gz" -C /
+ echo -e "${gl_lv}/home/docker 下的文件已还原完成${gl_bai}"
+ else
+ echo -e "${gl_huang}未找到 /home/docker 下文件的备份,跳过...${gl_bai}"
+ fi
+
+
+ }
+
+
+ # ----------------------------
+ # 迁移
+ # ----------------------------
+ migrate_docker() {
+ send_stats "Docker迁移"
+ install jq
+ read -e -p "请输入要迁移的备份目录: " BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}备份目录不存在${gl_bai}"; return; }
+
+ kj_ssh_read_host_user_port "目标服务器IP: " "目标服务器SSH用户名 [默认root]: " "目标服务器SSH端口 [默认22]: " "root" "22"
+ local TARGET_IP="$KJ_SSH_HOST"
+ local TARGET_USER="$KJ_SSH_USER"
+ local TARGET_PORT="$KJ_SSH_PORT"
+
+ local LATEST_TAR="$BACKUP_DIR"
+
+ echo -e "${gl_huang}传输备份中...${gl_bai}"
+ if [[ -z "$TARGET_PASS" ]]; then
+ # 使用密钥登录
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no -r "$LATEST_TAR" "$TARGET_USER@$TARGET_IP:/tmp/"
+ fi
+
+ }
+
+ # ----------------------------
+ # 删除备份
+ # ----------------------------
+ delete_backup() {
+ send_stats "Docker备份文件删除"
+ read -e -p "请输入要删除的备份目录: " BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}备份目录不存在${gl_bai}"; return; }
+ rm -rf "$BACKUP_DIR"
+ echo -e "${gl_lv}已删除备份: ${BACKUP_DIR}${gl_bai}"
+ }
+
+ # ----------------------------
+ # 主菜单
+ # ----------------------------
+ main_menu() {
+ send_stats "Docker备份迁移还原"
+ while true; do
+ clear
+ echo "------------------------"
+ echo -e "Docker备份/迁移/还原工具"
+ echo "------------------------"
+ list_backups
+ echo -e ""
+ echo "------------------------"
+ echo -e "1. 备份docker项目"
+ echo -e "2. 迁移docker项目"
+ echo -e "3. 还原docker项目"
+ echo -e "4. 删除docker项目的备份文件"
+ echo "------------------------"
+ echo -e "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "请选择: " choice
+ case $choice in
+ 1) backup_docker ;;
+ 2) migrate_docker ;;
+ 3) restore_docker ;;
+ 4) delete_backup ;;
+ 0) return ;;
+ *) echo -e "${gl_hong}无效选项${gl_bai}" ;;
+ esac
+ break_end
+ done
+ }
+
+ main_menu
+}
+
+
+
+
+
+linux_docker() {
+
+ while true; do
+ clear
+ # send_stats "docker管理"
+ echo -e "Docker管理"
+ docker_tato
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}安装更新Docker环境 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}2. ${gl_bai}查看Docker全局状态 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}3. ${gl_bai}Docker容器管理 ${gl_huang}★${gl_bai}"
@@ -6869,6 +8415,7 @@ linux_docker() {
echo -e "${gl_kjlan}11. ${gl_bai}开启Docker-ipv6访问"
echo -e "${gl_kjlan}12. ${gl_bai}关闭Docker-ipv6访问"
echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}19. ${gl_bai}备份/迁移/还原Docker环境"
echo -e "${gl_kjlan}20. ${gl_bai}卸载Docker环境"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
@@ -7076,6 +8623,9 @@ linux_docker() {
restart docker
;;
+
+
+
11)
clear
send_stats "Docker v6 开"
@@ -7088,6 +8638,11 @@ linux_docker() {
docker_ipv6_off
;;
+ 19)
+ docker_ssh_migration
+ ;;
+
+
20)
clear
send_stats "Docker卸载"
@@ -7147,6 +8702,7 @@ linux_test() {
echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 三网线路测试"
echo -e "${gl_kjlan}17. ${gl_bai}i-abc 多功能测速脚本"
echo -e "${gl_kjlan}18. ${gl_bai}NetQuality 网络质量体检脚本 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}19. ${gl_bai}TcpQuality TCP重传探测脚本 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}硬件性能测试"
@@ -7157,6 +8713,7 @@ linux_test() {
echo -e "${gl_kjlan}综合性测试"
echo -e "${gl_kjlan}31. ${gl_bai}bench 性能测试"
echo -e "${gl_kjlan}32. ${gl_bai}spiritysdx 融合怪测评 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}nodequality 融合怪测评 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
echo -e "${gl_kjlan}------------------------${gl_bai}"
@@ -7253,6 +8810,12 @@ linux_test() {
bash <(curl -sL Net.Check.Place)
;;
+ 19)
+ clear
+ send_stats "TcpQuality TCP重传探测脚本"
+ bash <(curl -sL https://raw.githubusercontent.com/ibsgss/TcpQuality/main/runTcpQuality.sh)
+ ;;
+
21)
clear
send_stats "yabs性能测试"
@@ -7274,9 +8837,17 @@ linux_test() {
32)
send_stats "spiritysdx融合怪测评"
clear
- curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ curl -L ${gh_proxy}github.com/spiritLHLS/ecs/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ ;;
+
+ 33)
+ send_stats "nodequality融合怪测评"
+ clear
+ bash <(curl -sL https://run.NodeQuality.com)
;;
+
+
0)
kejilion
@@ -7343,7 +8914,7 @@ linux_Oracle() {
local speedtest_interval=${speedtest_interval:-$DEFAULT_SPEEDTEST_INTERVAL}
# 运行Docker容器
- docker run -itd --name=lookbusy --restart=always \
+ docker run -d --name=lookbusy --restart=always \
-e TZ=Asia/Shanghai \
-e CPU_UTIL="$cpu_util" \
-e CPU_CORE="$cpu_core" \
@@ -7411,12 +8982,12 @@ linux_Oracle() {
4)
clear
- echo "该功能处于开发阶段,敬请期待!"
+ send_stats "R探长开机脚本"
+ bash <(wget -qO- ${gh_proxy}github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh)
;;
5)
clear
add_sshpasswd
-
;;
6)
clear
@@ -7441,6 +9012,9 @@ linux_Oracle() {
}
+
+
+
docker_tato() {
local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
@@ -7458,20 +9032,20 @@ docker_tato() {
ldnmp_tato() {
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
-local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml 2>/dev/null | tr -d '[:space:]')
if [ -n "$dbrootpasswd" ]; then
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
fi
-local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+local db_output="${gl_lv}${db_count}${gl_bai}"
if command -v docker &>/dev/null; then
if docker ps --filter "name=nginx" --filter "status=running" | grep -q nginx; then
echo -e "${gl_huang}------------------------"
- echo -e "${gl_lv}环境已安装${gl_bai} $output $db_output"
+ echo -e "${gl_lv}环境已安装${gl_bai} 站点: $output 数据库: $db_output"
fi
fi
@@ -7513,7 +9087,7 @@ linux_ldnmp() {
echo -e "${gl_huang}23. ${gl_bai}站点反向代理-IP+端口 ${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}站点反向代理-域名"
echo -e "${gl_huang}25. ${gl_bai}安装Bitwarden密码管理平台 ${gl_huang}26. ${gl_bai}安装Halo博客网站"
echo -e "${gl_huang}27. ${gl_bai}安装AI绘画提示词生成器 ${gl_huang}28. ${gl_bai}站点反向代理-负载均衡"
- echo -e "${gl_huang}30. ${gl_bai}自定义静态站点"
+ echo -e "${gl_huang}29. ${gl_bai}Stream四层代理转发 ${gl_huang}30. ${gl_bai}自定义静态站点"
echo -e "${gl_huang}------------------------"
echo -e "${gl_huang}31. ${gl_bai}站点数据管理 ${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}备份全站数据"
echo -e "${gl_huang}33. ${gl_bai}定时远程备份 ${gl_huang}34. ${gl_bai}还原全站数据"
@@ -7544,19 +9118,27 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/discuz.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/main/Discuz_X3.5_SC_UTF8_20240520.zip
- unzip latest.zip
+ LATEST_URL=$(curl -s https://api.gitee.com/api/v5/repos/Discuz/DiscuzX/releases/latest | grep -o 'https://[^"]*Discuz_X[^"]*SC_UTF8[^"]*\.zip' | head -n 1)
+ wget -O latest.zip ${LATEST_URL}
+ unzip -q latest.zip
+ mv upload/* .
+ rm -rf upload readme readme.html utility.html LICENSE qqqun.png
rm latest.zip
restart_ldnmp
@@ -7581,18 +9163,22 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/kdy.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/1.50.02.zip
+ LATEST_VERSION=$(curl -s https://api.github.com/repos/kalcaddle/kodbox/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/${LATEST_VERSION}.zip
unzip -o latest.zip
rm latest.zip
mv /home/web/html/$yuming/kodbox* /home/web/html/$yuming/kodbox
@@ -7616,12 +9202,17 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/maccms.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7659,12 +9250,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/dujiaoka.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7707,12 +9304,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/flarum.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec php rm -f /usr/local/etc/php/conf.d/optimized_php.ini
@@ -7733,9 +9336,13 @@ linux_ldnmp() {
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/sitemap"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/oauth"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/best-answer:*"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/upload"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/gamification"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/byobu:*"
docker exec php sh -c "cd /var/www/html/$yuming && composer require v17development/flarum-seo"
docker exec php sh -c "cd /var/www/html/$yuming && composer require clarkwinkelmann/flarum-ext-emojionearea"
+
restart_ldnmp
@@ -7758,12 +9365,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/typecho.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7796,13 +9409,17 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/refs/heads/main/index_php.conf
sed -i "s|/var/www/html/yuming.com/|/var/www/html/yuming.com/linkstack|g" /home/web/conf.d/$yuming.conf
sed -i "s|yuming.com|$yuming|g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7832,12 +9449,15 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/index_php.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7966,12 +9586,17 @@ linux_ldnmp() {
add_yuming
read -e -p "请输入跳转域名: " reverseproxy
nginx_install_status
+
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/rewrite.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s/baidu.com/$reverseproxy/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7989,6 +9614,7 @@ linux_ldnmp() {
echo "已阻止IP+端口访问该服务"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
@@ -8003,12 +9629,16 @@ linux_ldnmp() {
echo -e "域名格式: ${gl_huang}google.com${gl_bai}"
read -e -p "请输入你的反代域名: " fandai_yuming
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-domain.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s|fandaicom|$fandai_yuming|g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -8024,20 +9654,17 @@ linux_ldnmp() {
send_stats "安装$webname"
echo "开始部署 $webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
docker run -d \
--name bitwarden \
- --restart always \
+ --restart=always \
-p 3280:80 \
-v /home/web/html/$yuming/bitwarden/data:/data \
vaultwarden/server
+
duankou=3280
- reverse_proxy
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
- nginx_web_on
;;
@@ -8047,15 +9674,11 @@ linux_ldnmp() {
send_stats "安装$webname"
echo "开始部署 $webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
- docker run -d --name halo --restart always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- duankou=8010
- reverse_proxy
+ docker run -d --name halo --restart=always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- nginx_web_on
+ duankou=8010
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
;;
@@ -8066,11 +9689,14 @@ linux_ldnmp() {
echo "开始部署 $webname"
add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8093,6 +9719,10 @@ linux_ldnmp() {
;;
+ 29)
+ stream_panel
+ ;;
+
30)
clear
webname="静态站点"
@@ -8101,11 +9731,14 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8160,7 +9793,7 @@ linux_ldnmp() {
send_stats "LDNMP环境备份"
local backup_filename="web_$(date +"%Y%m%d%H%M%S").tar.gz"
- echo -e "${gl_huang}正在备份 $backup_filename ...${gl_bai}"
+ echo -e "${gl_kjlan}正在备份 $backup_filename ...${gl_bai}"
cd /home/ && tar czvf "$backup_filename" web
while true; do
@@ -8169,16 +9802,14 @@ linux_ldnmp() {
read -e -p "要传送备份数据到远程服务器吗?(Y/N): " choice
case "$choice" in
[Yy])
- read -e -p "请输入远端服务器IP: " remote_ip
- if [ -z "$remote_ip" ]; then
- echo "错误: 请输入远端服务器IP。"
- continue
- fi
+ kj_ssh_read_host_port "请输入远端服务器IP: " "目标服务器SSH端口 [默认22]: " "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
local latest_tar=$(ls -t /home/*.tar.gz | head -1)
if [ -n "$latest_tar" ]; then
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 添加等待时间
- scp -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
echo "文件已传送至远程服务器home目录。"
else
echo "未找到要传送的文件。"
@@ -8256,10 +9887,9 @@ linux_ldnmp() {
docker compose down > /dev/null 2>&1
rm -rf /home/web > /dev/null 2>&1
- echo -e "${gl_huang}正在解压 $filename ...${gl_bai}"
+ echo -e "${gl_kjlan}正在解压 $filename ...${gl_bai}"
cd /home/ && tar -xzf "$filename"
- check_port
install_dependency
install_docker
install_certbot
@@ -8383,7 +10013,6 @@ linux_ldnmp() {
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods > /dev/null 2>&1
- restart_redis
send_stats "更新$ldnmp_pods"
echo "更新${ldnmp_pods}完成"
@@ -8396,7 +10025,6 @@ linux_ldnmp() {
cd /home/web/
docker compose down --rmi all
- check_port
install_dependency
install_docker
install_certbot
@@ -8452,2386 +10080,9380 @@ linux_ldnmp() {
-linux_panel() {
+moltbot_menu() {
+ local app_id="114"
+ send_stats "clawdbot/moltbot管理"
- while true; do
- clear
- echo -e "应用市场"
- echo -e "${gl_kjlan}------------------------"
+ check_openclaw_update() {
+ if ! command -v npm >/dev/null 2>&1; then
+ return 1
+ fi
- local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+ # 加上 --no-update-notifier,并确保错误重定向位置正确
+ local_version=$(npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null | grep openclaw | awk '{print $NF}' | sed 's/^.*@//')
- # 用循环设置颜色
- for i in {1..100}; do
- if echo "$app_numbers" | grep -q "^$i$"; then
- declare "color$i=${gl_lv}"
- else
- declare "color$i=${gl_bai}"
- fi
- done
+ if [ -z "$local_version" ]; then
+ return 1
+ fi
- echo -e "${gl_kjlan}1. ${color1}宝塔面板官方版 ${gl_kjlan}2. ${color2}aaPanel宝塔国际版"
- echo -e "${gl_kjlan}3. ${color3}1Panel新一代管理面板 ${gl_kjlan}4. ${color4}NginxProxyManager可视化面板"
- echo -e "${gl_kjlan}5. ${color5}OpenList多存储文件列表程序 ${gl_kjlan}6. ${color6}Ubuntu远程桌面网页版"
- echo -e "${gl_kjlan}7. ${color7}哪吒探针VPS监控面板 ${gl_kjlan}8. ${color8}QB离线BT磁力下载面板"
- echo -e "${gl_kjlan}9. ${color9}Poste.io邮件服务器程序 ${gl_kjlan}10. ${color10}RocketChat多人在线聊天系统"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${color11}禅道项目管理软件 ${gl_kjlan}12. ${color12}青龙面板定时任务管理平台"
- echo -e "${gl_kjlan}13. ${color13}Cloudreve网盘 ${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}简单图床图片管理程序"
- echo -e "${gl_kjlan}15. ${color15}emby多媒体管理系统 ${gl_kjlan}16. ${color16}Speedtest测速面板"
- echo -e "${gl_kjlan}17. ${color17}AdGuardHome去广告软件 ${gl_kjlan}18. ${color18}onlyoffice在线办公OFFICE"
- echo -e "${gl_kjlan}19. ${color19}雷池WAF防火墙面板 ${gl_kjlan}20. ${color20}portainer容器管理面板"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${color21}VScode网页版 ${gl_kjlan}22. ${color22}UptimeKuma监控工具"
- echo -e "${gl_kjlan}23. ${color23}Memos网页备忘录 ${gl_kjlan}24. ${color24}Webtop远程桌面网页版 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}25. ${color25}Nextcloud网盘 ${gl_kjlan}26. ${color26}QD-Today定时任务管理框架"
- echo -e "${gl_kjlan}27. ${color27}Dockge容器堆栈管理面板 ${gl_kjlan}28. ${color28}LibreSpeed测速工具"
- echo -e "${gl_kjlan}29. ${color29}searxng聚合搜索站 ${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism私有相册系统"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${color31}StirlingPDF工具大全 ${gl_kjlan}32. ${color32}drawio免费的在线图表软件 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${color33}Sun-Panel导航面板 ${gl_kjlan}34. ${color34}Pingvin-Share文件分享平台"
- echo -e "${gl_kjlan}35. ${color35}极简朋友圈 ${gl_kjlan}36. ${color36}LobeChatAI聊天聚合网站"
- echo -e "${gl_kjlan}37. ${color37}MyIP工具箱 ${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}小雅alist全家桶"
- echo -e "${gl_kjlan}39. ${color39}Bililive直播录制工具 ${gl_kjlan}40. ${color40}webssh网页版SSH连接工具"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${color41}耗子管理面板 ${gl_kjlan}42. ${color42}Nexterm远程连接工具"
- echo -e "${gl_kjlan}43. ${color43}RustDesk远程桌面(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk远程桌面(中继端) ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}45. ${color45}Docker加速站 ${gl_kjlan}46. ${color46}GitHub加速站 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}47. ${color47}普罗米修斯监控 ${gl_kjlan}48. ${color48}普罗米修斯(主机监控)"
- echo -e "${gl_kjlan}49. ${color49}普罗米修斯(容器监控) ${gl_kjlan}50. ${color50}补货监控工具"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}51. ${color51}PVE开小鸡面板 ${gl_kjlan}52. ${color52}DPanel容器管理面板"
- echo -e "${gl_kjlan}53. ${color53}llama3聊天AI大模型 ${gl_kjlan}54. ${color54}AMH主机建站管理面板"
- echo -e "${gl_kjlan}55. ${color55}FRP内网穿透(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP内网穿透(客户端) ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}57. ${color57}Deepseek聊天AI大模型 ${gl_kjlan}58. ${color58}Dify大模型知识库 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}59. ${color59}NewAPI大模型资产管理 ${gl_kjlan}60. ${color60}JumpServer开源堡垒机"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}61. ${color61}在线翻译服务器 ${gl_kjlan}62. ${color62}RAGFlow大模型知识库"
- echo -e "${gl_kjlan}63. ${color63}OpenWebUI自托管AI平台 ${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools工具箱"
- echo -e "${gl_kjlan}65. ${color65}n8n自动化工作流平台 ${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp视频下载工具"
- echo -e "${gl_kjlan}67. ${color67}ddns-go动态DNS管理工具 ${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL证书管理平台"
- echo -e "${gl_kjlan}69. ${color69}SFTPGo文件传输工具 ${gl_kjlan}70. ${color70}AstrBot聊天机器人框架"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}71. ${color71}Navidrome私有音乐服务器 ${gl_kjlan}72. ${color72}bitwarden密码管理器 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}73. ${color73}LibreTV私有影视 ${gl_kjlan}74. ${color74}MoonTV私有影视"
- echo -e "${gl_kjlan}75. ${color75}Melody音乐精灵 ${gl_kjlan}76. ${color76}在线DOS老游戏"
- echo -e "${gl_kjlan}77. ${color77}迅雷离线下载工具 ${gl_kjlan}78. ${color78}PandaWiki智能文档管理系统"
- echo -e "${gl_kjlan}79. ${color79}Beszel服务器监控 ${gl_kjlan}80. ${color80}linkwarden书签管理"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}81. ${color81}JitsiMeet视频会议"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "请输入你的选择: " sub_choice
+ remote_version=$(npm view openclaw version --no-update-notifier 2>/dev/null)
- case $sub_choice in
- 1)
+ if [ -z "$remote_version" ]; then
+ return 1
+ fi
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="宝塔面板"
- local panelurl="https://www.bt.cn/new/index.html"
+ if [ "$local_version" != "$remote_version" ]; then
+ echo "${gl_huang}检测到新版本:$remote_version${gl_bai}"
+ else
+ echo "${gl_lv}当前版本已是最新:$local_version${gl_bai}"
+ fi
+ }
- panel_app_install() {
- if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
- }
- panel_app_manage() {
- bt
- }
+ get_install_status() {
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "${gl_lv}已安装${gl_bai}"
+ else
+ echo "${gl_hui}未安装${gl_bai}"
+ fi
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ get_running_status() {
+ if pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
+ echo "${gl_lv}运行中${gl_bai}"
+ else
+ echo "${gl_hui}未运行${gl_bai}"
+ fi
+ }
- install_panel
+ show_menu() {
- ;;
- 2)
+ clear
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="aapanel"
- local panelurl="https://www.aapanel.com/new/index.html"
+ local install_status=$(get_install_status)
+ local running_status=$(get_running_status)
+ local update_message=$(check_openclaw_update)
+
+ echo "======================================="
+ echo -e "🦞 OPENCLAW 管理工具 by KEJILION 🦞"
+ echo -e "💡 终端执行 \033[1;33mk claw\033[0m 快速进入菜单"
+ echo -e "$install_status $running_status $update_message"
+ echo "======================================="
+ echo "1. 安装"
+ echo "2. 启动"
+ echo "3. 停止"
+ echo "--------------------"
+ echo "4. 状态日志查看"
+ echo "5. 换模型"
+ echo "6. API管理"
+ echo "7. 机器人连接对接"
+ echo "8. 插件管理(安装/删除)"
+ echo "9. 技能管理(安装/删除)"
+ echo "10. 编辑主配置文件"
+ echo "11. 配置向导"
+ echo "12. 健康检测与修复"
+ echo "13. WebUI访问与设置"
+ echo "14. TUI命令行对话窗口"
+ echo "15. 记忆/Memory"
+ echo "16. 权限管理"
+ echo "17. 多智能体管理"
+ echo "--------------------"
+ echo "18. 备份与还原"
+ echo "19. 更新"
+ echo "20. 卸载"
+ echo "--------------------"
+ echo "0. 返回上一级选单"
+ echo "--------------------"
+ printf "请输入选项并回车: "
+ }
- panel_app_install() {
- URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
- }
- panel_app_manage() {
- bt
- }
+ start_gateway() {
+ openclaw gateway stop
+ openclaw gateway start
+ sleep 3
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
- install_panel
+ install_node_and_tools() {
+ if command -v dnf &>/dev/null; then
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
+ dnf update -y
+ dnf group install -y "Development Tools" "Development Libraries"
+ dnf install -y cmake libatomic nodejs
+ fi
- ;;
- 3)
+ if command -v apt &>/dev/null; then
+ curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
+ apt update -y
+ apt install build-essential python3 libatomic1 nodejs -y
+ fi
+ }
- local lujing="command -v 1pctl"
- local panelname="1Panel"
- local panelurl="https://1panel.cn/"
+ sync_openclaw_api_models() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ [ ! -f "$config_file" ] && return 0
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$ENABLE_STATS" "$sh_v" <<'PY'
+import copy
+import json
+import os
+import platform
+import sys
+import time
+import urllib.request
+from datetime import datetime, timezone
+
+path = sys.argv[1]
+stats_enabled = (sys.argv[2].lower() == "true") if len(sys.argv) > 2 else True
+script_version = sys.argv[3] if len(sys.argv) > 3 else ""
+
+def send_stat(action):
+ if not stats_enabled:
+ return
+ payload = {
+ "action": action,
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
+ "country": "",
+ "os_info": platform.platform(),
+ "cpu_arch": platform.machine(),
+ "version": script_version,
+ }
+ try:
+ req = urllib.request.Request(
+ "https://api.kejilion.pro/api/log",
+ data=json.dumps(payload).encode("utf-8"),
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=3):
+ pass
+ except Exception:
+ pass
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('ℹ️ 未检测到 API providers,跳过模型同步')
+ raise SystemExit(0)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+changed = False
+fatal_errors = []
+summary = []
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+def prompt_delete_provider(name):
+ prompt = f"⚠️ {name} /models 探测连续失败 3 次。是否删除该 API 供应商及其全部相关模型?[y/N]: "
+ try:
+ ans = input(prompt).strip().lower()
+ except EOFError:
+ return False
+ return ans in ('y', 'yes')
+
+
+def rebind_defaults_before_delete(name):
+ global changed
+
+ replacement = None
+
+ def get_replacement():
+ nonlocal replacement
+ if replacement is None:
+ candidates = collect_available_refs(exclude_provider=name)
+ replacement = candidates[0] if candidates else None
+ return replacement
+
+ primary_ref = get_primary_ref(defaults)
+ if ref_provider(primary_ref) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: 默认主模型指向该 provider,但无可用替代模型,已中止删除')
+ return False
+ set_primary_ref(defaults, repl)
+ changed = True
+ summary.append(f'🔁 删除前已切换默认主模型: {primary_ref} -> {repl}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: {fk} 指向该 provider,但无可用替代模型,已中止删除')
+ return False
+ defaults[fk] = repl
+ changed = True
+ summary.append(f'🔁 删除前已切换 {fk}: {val} -> {repl}')
+
+ return True
+
+
+def delete_provider_and_refs(name):
+ global changed
+
+ if not rebind_defaults_before_delete(name):
+ return False
+
+ removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+ for r in removed_refs:
+ defaults_models.pop(r, None)
+ if removed_refs:
+ changed = True
+
+ if name in providers:
+ providers.pop(name, None)
+ changed = True
+
+ summary.append(f'🗑️ 已删除 provider {name},并移除 defaults.models 下 {len(removed_refs)} 个模型引用')
+ return True
+
+
+def fetch_remote_models_with_retry(name, base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ data = json.loads(payload)
+ return data, None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+for name, provider in list(providers.items()):
+ if not isinstance(provider, dict):
+ summary.append(f'ℹ️ 跳过 {name}: provider 结构非法')
+ continue
+
+ api = provider.get('api', '')
+ base_url = provider.get('baseUrl')
+ api_key = provider.get('apiKey')
+ model_list = provider.get('models', [])
+
+ if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ summary.append(f'ℹ️ 跳过 {name}: 无 baseUrl/apiKey/models')
+ continue
+
+ if api not in SUPPORTED_APIS:
+ summary.append(f'🔁 {name}: 发现非法协议 {api or "(unset)"},将重新探测')
+ provider['api'] = ''
+ api = ''
+ changed = True
+
+ data, err, attempts = fetch_remote_models_with_retry(name, base_url, api_key, retries=3)
+ if err is not None:
+ summary.append(f'⚠️ {name}: /models 探测失败,已重试 {attempts} 次 ({type(err).__name__}: {err})')
+ send_stat('OpenClaw API确认介入')
+ if prompt_delete_provider(name):
+ deleted = delete_provider_and_refs(name)
+ if deleted:
+ send_stat('OpenClaw API删失败Provider-确认')
+ summary.append(f'✅ {name}: 用户已确认删除该 provider 及全部相关模型引用')
+ else:
+ send_stat('OpenClaw API删失败Provider-拒绝')
+ summary.append(f'ℹ️ {name}: 用户未确认删除,保留现有 provider 配置')
+ continue
+
+ if attempts > 1:
+ summary.append(f'🔁 {name}: /models 第 {attempts} 次重试后成功')
+
+ if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ summary.append(f'⚠️ 跳过 {name}: /models 返回结构不可识别')
+ continue
+
+ remote_ids = []
+ for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+ remote_set = set(remote_ids)
+
+ if not remote_set:
+ fatal_errors.append(f'❌ {name} 上游 /models 为空,无法为该 provider 提供兜底模型')
+ continue
+
+ local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+ local_ids = [str(m['id']) for m in local_models]
+ local_set = set(local_ids)
+
+ template = None
+ for m in local_models:
+ template = copy.deepcopy(m)
+ break
+ if template is None:
+ summary.append(f'⚠️ 跳过 {name}: 本地 models 无有效模板模型')
+ continue
+
+ removed_ids = [mid for mid in local_ids if mid not in remote_set]
+ added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+ kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+ new_models = kept_models[:]
+
+ for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{name} / {mid}'
+ new_models.append(nm)
+
+ if not new_models:
+ fatal_errors.append(f'❌ {name} 同步后无可用模型,无法保障默认模型/回退模型兜底')
+ continue
+
+ expected_refs = {model_ref(name, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+ local_refs = {model_ref(name, mid) for mid in local_ids}
+
+ first_ref = model_ref(name, str(new_models[0]['id']))
+
+ primary_ref = get_primary_ref(defaults)
+ if isinstance(primary_ref, str) and primary_ref in (local_refs - expected_refs):
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ summary.append(f'🔁 默认模型已兜底替换: {primary_ref} -> {first_ref}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in (local_refs - expected_refs):
+ defaults[fk] = first_ref
+ changed = True
+ summary.append(f'🔁 {fk} 已兜底替换: {val} -> {first_ref}')
+
+ stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/') and r not in expected_refs]
+ for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+ for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+ if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+ summary.append(f'✅ {name}: 新增 {len(added_ids)} 个,删除 {len(removed_ids)} 个,当前 {len(new_models)} 个')
+
+ if added_ids:
+ summary.append(f'➕ 新增模型({len(added_ids)}):')
+ for mid in added_ids:
+ summary.append(f' + {mid}')
+ if removed_ids:
+ summary.append(f'➖ 删除模型({len(removed_ids)}):')
+ for mid in removed_ids:
+ summary.append(f' - {mid}')
+
+
+if fatal_errors:
+ for line in summary:
+ print(line)
+ for err in fatal_errors:
+ print(err)
+ print('❌ 模型同步失败:存在 provider 同步后无可用模型,已中止写入')
+ raise SystemExit(2)
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+ for line in summary:
+ print(line)
+ print('✅ OpenClaw API 模型一致性同步完成并已写入配置')
+else:
+ for line in summary:
+ print(line)
+ print('ℹ️ 无需同步:配置已与上游 /models 保持一致')
+PY
+ }
- panel_app_install() {
- install bash
- bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
- }
- panel_app_manage() {
- 1pctl user-info
- 1pctl update password
- }
- panel_app_uninstall() {
- 1pctl uninstall
- }
+ install_moltbot() {
+ echo "开始安装 OpenClaw..."
+ send_stats "开始安装 OpenClaw..."
+ install git jq
- install_panel
+ install_node_and_tools
- ;;
- 4)
+ country=$(curl -s ipinfo.io/country)
+ if [[ "$country" == "CN" || "$country" == "HK" ]]; then
+ npm config set registry https://registry.npmmirror.com
+ fi
- local docker_name="npm"
- local docker_img="jc21/nginx-proxy-manager:latest"
- local docker_port=81
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
- docker_rum() {
+ npm install -g openclaw@latest
+ openclaw onboard --install-daemon
+ start_gateway
+ add_app_id
+ break_end
- docker run -d \
- --name=$docker_name \
- -p ${docker_port}:81 \
- -p 80:80 \
- -p 443:443 \
- -v /home/docker/npm/data:/data \
- -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
- --restart=always \
- $docker_img
+ }
- }
+ start_bot() {
+ echo "启动 OpenClaw..."
+ send_stats "启动 OpenClaw..."
+ start_gateway
+ break_end
+ }
- local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
- local docker_url="官网介绍: https://nginxproxymanager.com/"
- local docker_use="echo \"初始用户名: admin@example.com\""
- local docker_passwd="echo \"初始密码: changeme\""
- local app_size="1"
+ stop_bot() {
+ echo "停止 OpenClaw..."
+ send_stats "停止 OpenClaw..."
+ tmux kill-session -t gateway > /dev/null 2>&1
+ openclaw gateway stop
+ break_end
+ }
- docker_app
+ view_logs() {
+ echo "查看 OpenClaw 状态日志"
+ send_stats "查看 OpenClaw 日志"
+ openclaw status
+ openclaw gateway status
+ openclaw logs
+ break_end
+ }
- ;;
- 5)
- local docker_name="openlist"
- local docker_img="openlistteam/openlist:latest-aria2"
- local docker_port=5244
- docker_rum() {
- docker run -d \
- --restart=always \
- -v /home/docker/openlist:/opt/openlist/data \
- -p ${docker_port}:5244 \
- -e PUID=0 \
- -e PGID=0 \
- -e UMASK=022 \
- --name="openlist" \
- openlistteam/openlist:latest-aria2
+ # OpenClaw API 协议探测逻辑已移除:不再自动探测/判定 API 类型。
+ # 说明:API 类型由用户显式配置(models.providers..api),脚本不再尝试调用 /responses 做推断。
+
+ # 构造模型配置 JSON
+ build-openclaw-provider-models-json() {
+ local provider_name="$1"
+ local model_ids="$2"
+ local models_array="["
+ local first=true
+
+ while read -r model_id; do
+ [ -z "$model_id" ] && continue
+ [[ $first == false ]] && models_array+=","
+ first=false
+
+ local context_window=1048576
+ local max_tokens=128000
+ local input_cost=0.15
+ local output_cost=0.60
+
+ case "$model_id" in
+ *opus*|*pro*|*preview*|*thinking*|*sonnet*)
+ input_cost=2.00
+ output_cost=12.00
+ ;;
+ *gpt-5*|*codex*)
+ input_cost=1.25
+ output_cost=10.00
+ ;;
+ *flash*|*lite*|*haiku*|*mini*|*nano*)
+ input_cost=0.10
+ output_cost=0.40
+ ;;
+ esac
+
+ models_array+=$(cat < 0 then
+ .["\($prov)/\($mid)"] //= {}
+ else
+ .
+ end
+ )
+ )
+ ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
+ }
+
+ # 核心函数:获取并添加所有模型
+ add-all-models-from-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+
+ echo "🔍 正在获取 $provider_name 的所有可用模型..."
+
+ local models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -z "$models_json" ]]; then
+ echo "❌ 无法获取模型列表"
+ return 1
+ fi
+
+ local model_ids=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+')
+
+ if [[ -z "$model_ids" ]]; then
+ echo "❌ 未找到任何模型"
+ return 1
+ fi
+
+ local model_count=$(echo "$model_ids" | wc -l)
+ echo "✅ 发现 $model_count 个模型"
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$model_ids")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 成功添加 $model_count 个模型到 $provider_name"
+ echo "📦 模型引用格式: $provider_name/"
+ return 0
+ else
+ echo "❌ 配置注入失败"
+ return 1
+ fi
+ }
+
+ # 仅添加默认模型并保留 provider
+ add-default-model-only-to-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+ local default_model="$4"
+
+ if [[ -z "$default_model" ]]; then
+ echo "❌ 默认模型不能为空"
+ return 1
+ fi
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$default_model")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 已添加 provider:$provider_name"
+ echo "✅ 仅写入默认模型:$default_model"
+ return 0
+ else
+ echo "❌ 配置注入失败"
+ return 1
+ fi
+ }
+
+ add-openclaw-provider-interactive() {
+ send_stats "OpenClaw API添加"
+ echo "=== 交互式添加 OpenClaw Provider (全量模型) ==="
+
+ # 1. Provider 名称
+ read -erp "请输入 Provider 名称 (如: deepseek): " provider_name
+ while [[ -z "$provider_name" ]]; do
+ echo "❌ Provider 名称不能为空"
+ read -erp "请输入 Provider 名称: " provider_name
+ done
+
+ # 2. Base URL
+ read -erp "请输入 Base URL (如: https://api.xxx.com/v1): " base_url
+ while [[ -z "$base_url" ]]; do
+ echo "❌ Base URL 不能为空"
+ read -erp "请输入 Base URL: " base_url
+ done
+ base_url="${base_url%/}"
+
+ # 3. API Key
+ read -rsp "请输入 API Key (输入不显示): " api_key
+ echo
+ while [[ -z "$api_key" ]]; do
+ echo "❌ API Key 不能为空"
+ read -rsp "请输入 API Key: " api_key
+ echo
+ done
+
+ # 4. 不再探测/判断 API 类型;协议由用户自行选择与维护
+
+ # 5. 获取模型列表
+ echo "🔍 正在获取可用模型列表..."
+ models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -n "$models_json" ]]; then
+ available_models=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+' | sort)
+
+ if [[ -n "$available_models" ]]; then
+ model_count=$(echo "$available_models" | wc -l)
+ echo "✅ 发现 $model_count 个可用模型:"
+ echo "--------------------------------"
+ # 全部显示,带序号
+ i=1
+ model_list=()
+ while read -r model; do
+ echo "[$i] $model"
+ model_list+=("$model")
+ ((i++))
+ done <<< "$available_models"
+ echo "--------------------------------"
+ fi
+ fi
+
+ # 5. 选择默认模型
+ echo
+ read -erp "请输入默认 Model ID (或序号,留空则使用第一个): " input_model
+
+ if [[ -z "$input_model" && -n "$available_models" ]]; then
+ default_model=$(echo "$available_models" | head -1)
+ echo "🎯 使用第一个模型: $default_model"
+ elif [[ "$input_model" =~ ^[0-9]+$ ]] && [ "${#model_list[@]}" -gt 0 ] && [ "$input_model" -ge 1 ] && [ "$input_model" -le "${#model_list[@]}" ]; then
+ default_model="${model_list[$((input_model-1))]}"
+ echo "🎯 已选择模型: $default_model"
+ else
+ default_model="$input_model"
+ fi
+
+ # 6. 确认信息
+ echo
+ echo "====== 确认信息 ======"
+ echo "Provider : $provider_name"
+ echo "Base URL : $base_url"
+ echo "API Key : ${api_key:0:8}****"
+ echo "默认模型 : $default_model"
+ echo "模型总数 : $model_count"
+ echo "======================"
+
+ read -erp "是否同时添加其他所有可用模型?(y/N): " confirm
+
+ install jq
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ add-all-models-from-provider "$provider_name" "$base_url" "$api_key"
+ add_result=$?
+ finish_msg="✅ 完成!所有 $model_count 个模型已加载"
+ else
+ add-default-model-only-to-provider "$provider_name" "$base_url" "$api_key" "$default_model"
+ add_result=$?
+ finish_msg="✅ 完成!已保留 provider,并仅加载默认模型:$default_model"
+ fi
+
+ if [[ $add_result -eq 0 ]]; then
+ echo
+ echo "🔄 设置默认模型并重启网关..."
+ openclaw models set "$provider_name/$default_model"
+ openclaw_sync_sessions_model "$provider_name/$default_model"
+ start_gateway
+ echo "$finish_msg"
+ echo "✅ 当前 API 协议类型: $DETECTED_API"
+ fi
+
+ break_end
+ }
+
+
+
+openclaw_api_manage_list() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API列表"
+
+ while IFS=$'\t' read -r rec_type idx name base_url model_count api_type latency_txt latency_level; do
+ case "$rec_type" in
+ MSG)
+ echo "$idx"
+ ;;
+ ROW)
+ local latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
+
+ printf '%b\n' "[$idx] ${name} | API: ${base_url} | 协议: ${api_type} | 模型数量: ${gl_huang}${model_count}${gl_bai} | 延迟/状态: ${latency_color}${latency_txt}${gl_bai}"
+ ;;
+ esac
+ done < <(python3 - "$config_file" <<-'PY'
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+
+def ping_models(base_url, api_key):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'OpenClaw-API-Manage/1.0',
+ },
+ )
+ start = time.perf_counter()
+ with urllib.request.urlopen(req, timeout=4) as resp:
+ resp.read(2048)
+ return int((time.perf_counter() - start) * 1000)
+
+
+def classify_latency(latency):
+ if latency == '不可用':
+ return '不可用', 'unavailable'
+ if latency == '未检测':
+ return '未检测', 'unchecked'
+ if isinstance(latency, int):
+ if latency <= 800:
+ level = 'low'
+ elif latency <= 2000:
+ level = 'medium'
+ else:
+ level = 'high'
+ return f'{latency}ms', level
+ return str(latency), 'unchecked'
+
+
+try:
+ with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+except FileNotFoundError:
+ print('MSG\tℹ️ 未找到 openclaw.json,请先完成安装/初始化。')
+ raise SystemExit(0)
+except Exception as e:
+ print(f'MSG\t❌ 读取配置失败: {type(e).__name__}: {e}')
+ raise SystemExit(0)
+
+providers = ((obj.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or not providers:
+ print('MSG\tℹ️ 当前未配置任何 API provider。')
+ raise SystemExit(0)
+
+print('MSG\t--- 已配置 API 列表 ---')
+
+for idx, name in enumerate(sorted(providers.keys()), start=1):
+ provider = providers.get(name)
+ if not isinstance(provider, dict):
+ base_url = '-'
+ model_count = 0
+ latency_raw = '不可用'
+ else:
+ base_url = provider.get('baseUrl') or provider.get('url') or provider.get('endpoint') or '-'
+ models = provider.get('models') if isinstance(provider.get('models'), list) else []
+ model_count = sum(1 for m in models if isinstance(m, dict) and m.get('id'))
+ api = provider.get('api', '')
+ api_key = provider.get('apiKey')
+
+ latency_raw = '未检测'
+ if api in SUPPORTED_APIS:
+ if isinstance(base_url, str) and base_url != '-' and isinstance(api_key, str) and api_key:
+ try:
+ latency_raw = ping_models(base_url, api_key)
+ except Exception:
+ latency_raw = '不可用'
+ else:
+ latency_raw = '不可用'
+
+ latency_text, latency_level = classify_latency(latency_raw)
+ api_label = api if api in SUPPORTED_APIS else '-'
+ print(
+ 'ROW\t' + '\t'.join([
+ str(idx),
+ str(name),
+ str(base_url),
+ str(model_count),
+ str(api_label),
+ str(latency_text),
+ str(latency_level),
+ ])
+ )
+PY
+)
+}
+sync-openclaw-provider-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API按Provider同步"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到配置文件: $config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "请输入要同步的 API 名称(provider),直接回车同步全部: " provider_name
+ if [ -z "$provider_name" ]; then
+ if sync_openclaw_api_models; then
+ start_gateway
+ else
+ echo "❌ API 模型同步失败,已中止重启网关。请检查 provider /models 返回后重试。"
+ return 1
+ fi
+ break_end
+ return 0
+ fi
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" <<'PY2'
+import copy
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+target = sys.argv[2]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('❌ 未检测到 API providers,无法同步')
+ raise SystemExit(2)
+
+provider = providers.get(target)
+if not isinstance(provider, dict):
+ print(f'❌ 未找到 provider: {target}')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def fetch_remote_models_with_retry(base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ return json.loads(payload), None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+api = provider.get('api', '')
+base_url = provider.get('baseUrl')
+api_key = provider.get('apiKey')
+model_list = provider.get('models', [])
+
+if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ print(f'❌ provider {target} 缺少 baseUrl/apiKey/models,无法执行同步')
+ raise SystemExit(3)
+
+if api not in SUPPORTED_APIS:
+ print(f'ℹ️ provider {target} 当前 api={api},但脚本已不再探测/纠正协议;请手动设置为 openai-completions 或 openai-responses')
+
+protocol_msg = None
+
+data, err, attempts = fetch_remote_models_with_retry(base_url, api_key, retries=3)
+if err is not None:
+ print(f'❌ {target}: /models 探测失败,已重试 {attempts} 次 ({type(err).__name__}: {err})')
+ raise SystemExit(4)
+
+if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ print(f'❌ {target}: /models 返回结构不可识别')
+ raise SystemExit(4)
+
+remote_ids = []
+for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+remote_set = set(remote_ids)
+if not remote_set:
+ print(f'❌ {target}: 上游 /models 为空,已中止同步')
+ raise SystemExit(5)
+
+local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+local_ids = [str(m['id']) for m in local_models]
+local_set = set(local_ids)
+
+template = copy.deepcopy(local_models[0]) if local_models else None
+if template is None:
+ print(f'❌ {target}: 本地 models 无有效模板模型,无法补全新增模型')
+ raise SystemExit(3)
+
+removed_ids = [mid for mid in local_ids if mid not in remote_set]
+added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+new_models = kept_models[:]
+for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{target} / {mid}'
+ new_models.append(nm)
+
+if not new_models:
+ print(f'❌ {target}: 同步后无可用模型,已中止写入')
+ raise SystemExit(5)
+
+expected_refs = {model_ref(target, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+local_refs = {model_ref(target, mid) for mid in local_ids}
+removed_refs = local_refs - expected_refs
+first_ref = model_ref(target, str(new_models[0]['id']))
+
+changed = False
+primary_ref = get_primary_ref(defaults)
+if isinstance(primary_ref, str) and primary_ref in removed_refs:
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ print(f'🔁 默认模型已兜底替换: {primary_ref} -> {first_ref}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in removed_refs:
+ defaults[fk] = first_ref
+ changed = True
+ print(f'🔁 {fk} 已兜底替换: {val} -> {first_ref}')
+
+stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(target + '/') and r not in expected_refs]
+for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ {target}: 新增 {len(added_ids)} 个,删除 {len(removed_ids)} 个,当前 {len(new_models)} 个')
+
+if added_ids:
+ print(f'➕ 新增模型({len(added_ids)}):')
+ for mid in added_ids:
+ print(f' + {mid}')
+if removed_ids:
+ print(f'➖ 删除模型({len(removed_ids)}):')
+ for mid in removed_ids:
+ print(f' - {mid}')
+
+if changed:
+ print('✅ 指定 provider 模型一致性同步完成并已写入配置')
+else:
+ print('ℹ️ 无需同步:该 provider 配置已与上游 /models 保持一致')
+PY2
+ local rc=$?
+ case "$rc" in
+ 0)
+ echo "✅ 同步执行完成"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 同步失败:provider 不存在或未配置"
+ ;;
+ 3)
+ echo "❌ 同步失败:provider 配置不完整或类型不支持"
+ ;;
+ 4)
+ echo "❌ 同步失败:上游 /models 请求失败"
+ ;;
+ 5)
+ echo "❌ 同步失败:上游模型为空或同步后无可用模型"
+ ;;
+ *)
+ echo "❌ 同步失败:请检查配置文件结构或日志输出"
+ ;;
+ esac
+
+ break_end
+}
+
+openclaw_detect_api_protocol_by_provider() {
+ # 协议探测逻辑已移除:脚本不再自动探测/判定 API 类型。
+ # 保留函数以兼容菜单调用,但不做任何改写。
+ echo "ℹ️ 已关闭协议探测:请手动在 ${HOME}/.openclaw/openclaw.json 中设置 provider.api 为 openai-completions 或 openai-responses"
+ return 0
+}
+
+fix-openclaw-provider-protocol-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API协议切换"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到配置文件: $config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "请输入要切换协议的 API 名称(provider): " provider_name
+ if [ -z "$provider_name" ]; then
+ echo "❌ provider 名称不能为空"
+ break_end
+ return 1
+ fi
+
+ echo "请选择要设置的 API 类型:"
+ echo "1. openai-completions"
+ echo "2. openai-responses"
+ read -erp "请输入你的选择 (1/2): " proto_choice
+
+ local new_api=""
+ case "$proto_choice" in
+ 1) new_api="openai-completions" ;;
+ 2) new_api="openai-responses" ;;
+ *)
+ echo "❌ 无效选择"
+ break_end
+ return 1
+ ;;
+ esac
+
+ install python3 >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" "$new_api" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+new_api = sys.argv[3]
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+if new_api not in SUPPORTED_APIS:
+ print('❌ 非法协议值')
+ raise SystemExit(3)
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+providers = ((work.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or name not in providers or not isinstance(providers.get(name), dict):
+ print(f'❌ 未找到 provider: {name}')
+ raise SystemExit(2)
+
+providers[name]['api'] = new_api
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ 已更新 provider {name} 协议为: {new_api}')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 切换失败:provider 不存在或未配置"
+ ;;
+ 3)
+ echo "❌ 切换失败:协议值非法"
+ ;;
+ *)
+ echo "❌ 切换失败:请检查配置文件结构或日志输出"
+ ;;
+ esac
+
+ break_end
+}
+
+ delete-openclaw-provider-interactive() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ send_stats "OpenClaw API删除入口"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到配置文件: $config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "请输入要删除的 API 名称(provider): " provider_name
+ if [ -z "$provider_name" ]; then
+ send_stats "OpenClaw API删除取消"
+ echo "❌ provider 名称不能为空"
+ break_end
+ return 1
+ fi
+
+ python3 - "$config_file" "$provider_name" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or name not in providers:
+ print(f'❌ 未找到 provider: {name}')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+replacement_candidates = collect_available_refs(exclude_provider=name)
+replacement = replacement_candidates[0] if replacement_candidates else None
+
+primary_ref = get_primary_ref(defaults)
+if ref_provider(primary_ref) == name:
+ if not replacement:
+ print('❌ 删除中止:默认主模型指向该 provider,且无可用替代模型')
+ raise SystemExit(3)
+ set_primary_ref(defaults, replacement)
+ print(f'🔁 默认主模型切换: {primary_ref} -> {replacement}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ if not replacement:
+ print(f'❌ 删除中止:{fk} 指向该 provider,且无可用替代模型')
+ raise SystemExit(3)
+ defaults[fk] = replacement
+ print(f'🔁 {fk} 切换: {val} -> {replacement}')
+
+removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+for r in removed_refs:
+ defaults_models.pop(r, None)
+
+providers.pop(name, None)
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'🗑️ 已删除 provider: {name}')
+print(f'🧹 已清理 defaults.models 中 {len(removed_refs)} 个关联模型引用')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ send_stats "OpenClaw API删除确认"
+ echo "✅ 删除完成"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 删除失败:provider 不存在"
+ ;;
+ 3)
+ send_stats "OpenClaw API删除取消"
+ echo "❌ 删除失败:无可用替代模型,已保持原配置"
+ ;;
+ *)
+ echo "❌ 删除失败:请检查配置文件结构或日志输出"
+ ;;
+ esac
+
+ break_end
+ }
+
+ openclaw_api_providers_showcase() {
+ send_stats "OpenClaw API厂商推荐"
+
+ clear
+ echo ""
+ echo -e "${gl_kjlan}╔════════════════════════════════════════════════════════════╗${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_huang}🌟 API 厂商推荐列表${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_zi}部分入口含 AFF${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}╚════════════════════════════════════════════════════════════╝${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● DeepSeek${gl_bai}"
+ echo -e " ${gl_kjlan}https://api-docs.deepseek.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● OpenRouter${gl_bai}"
+ echo -e " ${gl_kjlan}https://openrouter.ai/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Kimi${gl_bai}"
+ echo -e " ${gl_kjlan}https://platform.moonshot.cn/docs/guide/start-using-kimi-api${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 超算互联网${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.scnet.cn/${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 优云智算${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://passport.compshare.cn/register?referral_code=4mscFZXfutfFi8swMVsPuf${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 硅基流动${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://cloud.siliconflow.cn/i/irWVdPic${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 智谱 GLM${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.bigmodel.cn/glm-coding?ic=HYOTDOAJMR${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● PackyAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.packyapi.com/register?aff=wHri${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 云雾 API${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://yunwu.ai/register?aff=ZuyK${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 柏拉图AI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://api.bltcy.ai/register?aff=TBzb114019${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● MiniMax${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.minimaxi.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● NVIDIA${gl_bai}"
+ echo -e " ${gl_kjlan}https://build.nvidia.com/settings/api-keys${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Ollama${gl_bai}"
+ echo -e " ${gl_kjlan}https://ollama.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 白山云${gl_bai}"
+ echo -e " ${gl_kjlan}https://ai.baishan.com/${gl_bai}"
+ echo ""
+ echo -e "${gl_kjlan}────────────────────────────────────────────────────────────${gl_bai}"
+ echo -e " ${gl_zi}图例:${gl_lv}● 官方入口${gl_bai} ${gl_huang}● AFF 推荐入口${gl_bai}"
+ echo ""
+ echo -e "${gl_huang}提示:复制链接到浏览器打开即可访问${gl_bai}"
+ echo ""
+ read -erp "按回车键返回..." dummy
+ }
+
+ openclaw_api_manage_menu() {
+ send_stats "OpenClaw API入口"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw API 管理"
+ echo "======================================="
+ openclaw_api_manage_list
+ echo "---------------------------------------"
+ echo "1. 添加API"
+ echo "2. 同步API供应商模型列表"
+ echo "3. 切换 API 类型(completions / responses)"
+ echo "4. 删除API"
+ echo "5. API 厂商推荐"
+ echo "0. 退出"
+ echo "---------------------------------------"
+ read -erp "请输入你的选择: " api_choice
+
+ case "$api_choice" in
+ 1)
+ add-openclaw-provider-interactive
+ ;;
+ 2)
+ sync-openclaw-provider-interactive
+ ;;
+ 3)
+ fix-openclaw-provider-protocol-interactive
+ ;;
+ 4)
+ delete-openclaw-provider-interactive
+ ;;
+ 5)
+ openclaw_api_providers_showcase
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update && apt install -y gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then
+ dnf install -y gum
+ else
+ yum install -y gum
+ fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive refresh
+ zypper --non-interactive install gum
+ fi
+ }
+
+
+
+ change_model() {
+ send_stats "换模型"
+
+ local orange="#FF8C00"
+
+ openclaw_probe_status_line() {
+ local status_text="$1"
+ local status_color_ok='[32m'
+ local status_color_fail='[31m'
+ local status_color_reset='[0m'
+ if [ "$status_text" = "可用" ]; then
+ printf "%b最小检测结果:%s%b
+" "$status_color_ok" "$status_text" "$status_color_reset"
+ else
+ printf "%b最小检测结果:%s%b
+" "$status_color_fail" "$status_text" "$status_color_reset"
+ fi
+ }
+
+ openclaw_model_probe() {
+ local target_model="$1"
+ local probe_timeout=25
+ local tmp_payload tmp_response probe_result probe_status reply_preview reply_trimmed
+ local oc_config provider_name base_url api_key request_model
+ local first_endpoint second_endpoint
+ local first_exit first_http first_latency second_exit second_http second_latency
+ local first_reply second_reply
+
+ oc_config=$(openclaw_get_config_file)
+ [ ! -f "$oc_config" ] && {
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="未找到 openclaw 配置文件"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ }
+
+ provider_name="${target_model%%/*}"
+ request_model="${target_model#*/}"
+ base_url=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].baseUrl // empty' "$oc_config" 2>/dev/null)
+ api_key=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].apiKey // empty' "$oc_config" 2>/dev/null)
+ if [ -z "$provider_name" ] || [ -z "$base_url" ] || [ -z "$api_key" ]; then
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="未读取到 provider/baseUrl/apiKey"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ fi
+
+ base_url="${base_url%/}"
+ first_endpoint="/responses"
+ second_endpoint="/chat/completions"
+
+ openclaw_extract_probe_reply() {
+ python3 - "$1" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+path = Path(sys.argv[1])
+raw = path.read_text(encoding='utf-8', errors='replace').strip()
+reply = ''
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get('choices') or []
+ if choices and isinstance(choices[0], dict):
+ message = choices[0].get('message') or {}
+ if isinstance(message, dict):
+ reply = message.get('content') or ''
+ if not reply:
+ output = data.get('output') or []
+ if isinstance(output, list):
+ texts = []
+ for item in output:
+ if not isinstance(item, dict):
+ continue
+ for content in item.get('content') or []:
+ if not isinstance(content, dict):
+ continue
+ text = content.get('text')
+ if isinstance(text, str) and text.strip():
+ texts.append(text.strip())
+ if texts:
+ break
+ if texts:
+ reply = ' '.join(texts)
+ if not reply:
+ for key in ('error', 'message', 'detail'):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ reply = value.strip()
+ break
+ if isinstance(value, dict):
+ nested = value.get('message')
+ if isinstance(nested, str) and nested.strip():
+ reply = nested.strip()
+ break
+ except Exception:
+ reply = raw
+reply = ' '.join(str(reply).split())
+print(reply)
+PYTHON_EOF
+ }
+
+ openclaw_run_probe() {
+ local endpoint="$1"
+ tmp_payload=$(mktemp)
+ tmp_response=$(mktemp)
+ if [ "$endpoint" = "/responses" ]; then
+ printf '{"model":"%s","input":"hi","temperature":0,"max_output_tokens":16}' "$request_model" > "$tmp_payload"
+ else
+ printf '{"model":"%s","messages":[{"role":"user","content":"hi"}],"temperature":0,"max_tokens":16}' "$request_model" > "$tmp_payload"
+ fi
+
+ probe_result=$(python3 - "$base_url" "$api_key" "$tmp_payload" "$tmp_response" "$probe_timeout" "$endpoint" <<'PYTHON_EOF'
+import sys
+import time
+import urllib.error
+import urllib.request
+
+base_url, api_key, payload_path, response_path, timeout, endpoint = sys.argv[1:7]
+timeout = int(timeout)
+url = base_url + endpoint
+payload = open(payload_path, 'rb').read()
+req = urllib.request.Request(
+ url,
+ data=payload,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'Bearer {api_key}',
+ },
+ method='POST',
+)
+start = time.time()
+body = b''
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, 'status', 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, 'code', 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode('utf-8', errors='replace')
+ exit_code = 1
+elapsed = int((time.time() - start) * 1000)
+with open(response_path, 'wb') as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYTHON_EOF
+)
+ probe_status=$?
+ reply_preview=$(openclaw_extract_probe_reply "$tmp_response")
+ rm -f "$tmp_payload" "$tmp_response"
+ return $probe_status
+ }
+
+ openclaw_run_probe "$first_endpoint"
+ first_exit=${probe_result%%|*}
+ first_http=${probe_result#*|}
+ first_http=${first_http%%|*}
+ first_latency=${probe_result##*|}
+ first_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ if [ "$first_exit" = "0" ] && [ "$first_http" -ge 200 ] && [ "$first_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http}"
+ OPENCLAW_PROBE_LATENCY="${first_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ openclaw_run_probe "$second_endpoint"
+ second_exit=${probe_result%%|*}
+ second_http=${probe_result#*|}
+ second_http=${second_http%%|*}
+ second_latency=${probe_result##*|}
+ second_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ if [ "$second_exit" = "0" ] && [ "$second_http" -ge 200 ] && [ "$second_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0},切换 ${second_endpoint} -> HTTP ${second_http}"
+ OPENCLAW_PROBE_LATENCY="${second_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ OPENCLAW_PROBE_STATUS="FAIL"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0} / exit ${first_exit:-1};${second_endpoint} -> HTTP ${second_http:-0} / exit ${second_exit:-1}"
+ OPENCLAW_PROBE_LATENCY="${first_latency:-?}ms -> ${second_latency:-?}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 1
+ }
+
+ clear
+
+ while true; do
+ local models_raw models_list default_model model_count selected_model confirm_switch
+
+ # 从配置文件读取模型键(不调用 openclaw models list)
+ local oc_config
+ oc_config=$(openclaw_get_config_file)
+
+ models_raw=$(jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d')
+ if [ -z "$models_raw" ]; then
+ echo "获取模型列表失败:配置文件中未找到 agents.defaults.models。"
+ break_end
+ return 1
+ fi
+
+ # 为每个模型加编号,便于快速定位(例如:"(10) or-api/...:free")
+ models_list=$(echo "$models_raw" | awk '{print "(" NR ") " $0}')
+ model_count=$(echo "$models_list" | sed '/^\s*$/d' | wc -l | tr -d ' ')
+
+ # 从配置文件读取默认模型(更快);失败再回退到 openclaw 命令
+ default_model=$(jq -r '.agents.defaults.model.primary // empty' "$oc_config" 2>/dev/null)
+ [ -z "$default_model" ] && default_model="(unknown)"
+
+ clear
+
+ install_gum
+ install gum
+
+ # 若 gum 不存在,降级为原始手动输入流程
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "--- 模型管理 ---"
+ echo "当前可用模型:"
+ jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d'
+ echo "----------------"
+ read -e -p "请输入要设置的模型名称 (例如 openrouter/openai/gpt-4o)(输入 0 退出): " selected_model
+
+ if [ "$selected_model" = "0" ]; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+
+ if [ -z "$selected_model" ]; then
+ echo "错误:模型名称不能为空。请重试。"
+ echo ""
+ continue
+ fi
+
+ echo "正在切换模型为: $selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切换失败:openclaw models set 返回错误。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ return 0
+ else
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "gum 不可用,返回旧版输入模式。"
+ sleep 1
+ continue
+ fi
+ gum style --foreground "$orange" --bold "模型管理"
+ gum style --foreground "$orange" "可用模型(Auth=yes):${model_count}"
+ gum style --foreground "$orange" "当前默认:${default_model}"
+ echo ""
+ gum style --faint "↑↓ 选择 / Enter 测试 / Esc 退出"
+ echo ""
+
+ selected_model=$(echo "$models_list" | gum filter --placeholder "搜索模型(如 cli-api/gpt-5.2)" --prompt "选择模型 > " --indicator "➜ " --prompt.foreground "$orange" --indicator.foreground "$orange" --cursor-text.foreground "$orange" --match.foreground "$orange" --header "" --height 35)
+
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+ fi
+
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
+
+ echo ""
+ echo "正在检测模型: $selected_model"
+ if openclaw_model_probe "$selected_model"; then
+ openclaw_probe_status_line "可用"
+ else
+ openclaw_probe_status_line "不可用"
+ fi
+ echo "状态:$OPENCLAW_PROBE_MESSAGE"
+ echo "延迟:$OPENCLAW_PROBE_LATENCY"
+ echo "摘要:$OPENCLAW_PROBE_REPLY"
+ echo ""
+
+ printf "是否切换到该模型?[y/N,Esc 返回列表]: "
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ [nN]|"") confirm_switch="no" ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
+
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "已返回模型选择列表。"
+ sleep 1
+ continue
+ fi
+
+ echo "正在切换模型为: $selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切换失败:openclaw models set 返回错误。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ done
+ }
+
+
+ openclaw_get_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_get_agents_dir() {
+ local user_agents="${HOME}/.openclaw/agents"
+ local root_agents="/root/.openclaw/agents"
+ if [ -d "$user_agents" ]; then
+ echo "$user_agents"
+ elif [ "$HOME" = "/root" ] && [ -d "$root_agents" ]; then
+ echo "$root_agents"
+ else
+ echo "$user_agents"
+ fi
+ }
+
+ openclaw_sync_sessions_model() {
+ local model_ref="$1"
+ [ -z "$model_ref" ] && return 1
+
+ local agents_dir
+ agents_dir=$(openclaw_get_agents_dir)
+ [ ! -d "$agents_dir" ] && return 0
+
+ local provider="${model_ref%%/*}"
+ local model="${model_ref#*/}"
+ [ "$provider" = "$model_ref" ] && { provider=""; model="$model_ref"; }
+
+ local count=0
+ local agent_dir sessions_file backup_file
+
+ for agent_dir in "$agents_dir"/*/; do
+ [ ! -d "$agent_dir" ] && continue
+ sessions_file="$agent_dir/sessions/sessions.json"
+ [ ! -f "$sessions_file" ] && continue
+
+ backup_file="${sessions_file}.bak"
+ cp "$sessions_file" "$backup_file" 2>/dev/null || continue
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if [ -n "$provider" ]; then
+ jq --arg model "$model" --arg provider "$provider" \
+ 'to_entries | map(.value.modelOverride = $model | .value.providerOverride = $provider) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ else
+ jq --arg model "$model" \
+ 'to_entries | map(.value.modelOverride = $model | del(.value.providerOverride)) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ fi
+ fi
+ done
+
+ [ "$count" -gt 0 ] && echo "✅ 已同步 $count 个 agent 的会话模型为 $model_ref"
+ return 0
+ }
+
+ resolve_openclaw_plugin_id() {
+ local raw_input="$1"
+ local plugin_id="$raw_input"
+
+ plugin_id="${plugin_id#@openclaw/}"
+ if [[ "$plugin_id" == @*/* ]]; then
+ plugin_id="${plugin_id##*/}"
+ fi
+ plugin_id="${plugin_id%%@*}"
+ echo "$plugin_id"
+ }
+
+ sync_openclaw_plugin_allowlist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | if (.plugins.allow | index($pid)) == null then .plugins.allow += [$pid] else . end
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 已同步 plugins.allow 白名单: $plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+if plugin_id not in a:
+ a.append(plugin_id)
+
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 已同步 plugins.allow 白名单: $plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ 已安装插件,但同步 plugins.allow 失败,请手动检查: $config_file"
+ return 1
+ }
+
+ sync_openclaw_plugin_denylist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | .plugins.allow = (.plugins.allow | map(select(. != $pid)))
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 已从 plugins.allow 移除: $plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+a = [x for x in a if x != plugin_id]
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 已从 plugins.allow 移除: $plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ plugins.allow 移除失败,请手动检查: $config_file"
+ return 1
+ }
+
+
+
+
+
+
+ install_plugin() {
+ send_stats "插件管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo " 插件管理 (安装/删除) "
+ echo "========================================"
+ echo "当前插件列表:"
+ openclaw plugins list
+ echo "--------------------------------------------------------"
+ echo "推荐的常用插件 ID (直接复制括号内的 ID 即可):"
+ echo "--------------------------------------------------------"
+ echo "📱 通讯渠道:"
+ echo " - [feishu] # 飞书/Lark 集成"
+ echo " - [telegram] # Telegram 机器人"
+ echo " - [slack] # Slack 企业通讯"
+ echo " - [msteams] # Microsoft Teams"
+ echo " - [discord] # Discord 社区管理"
+ echo " - [whatsapp] # WhatsApp 自动化"
+ echo ""
+ echo "🧠 记忆与 AI:"
+ echo " - [memory-core] # 基础记忆 (文件检索)"
+ echo " - [memory-lancedb] # 增强记忆 (向量数据库)"
+ echo " - [copilot-proxy] # Copilot 接口转发"
+ echo ""
+ echo "⚙️ 功能扩展:"
+ echo " - [lobster] # 审批流 (带人工确认)"
+ echo " - [voice-call] # 语音通话能力"
+ echo " - [nostr] # 加密隐私聊天"
+ echo "--------------------------------------------------------"
+
+ echo "1) 安装/启用插件"
+ echo "2) 删除/禁用插件"
+ echo "0) 返回"
+ read -e -p "请选择操作:" plugin_action
+
+ [ "$plugin_action" = "0" ] && break
+ [ -z "$plugin_action" ] && continue
+
+ read -e -p "请输入插件 ID(空格分隔,输入 0 退出): " raw_input
+ [ "$raw_input" = "0" ] && break
+ [ -z "$raw_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ for token in $raw_input; do
+ local plugin_id
+ local plugin_full
+ plugin_id=$(resolve_openclaw_plugin_id "$token")
+ plugin_full="$token"
+ [ -z "$plugin_id" ] && continue
+
+ if [ "$plugin_action" = "1" ]; then
+ echo "🔍 正在检查插件状态: $plugin_id"
+ local plugin_list
+ plugin_list=$(openclaw plugins list 2>/dev/null)
+
+ if echo "$plugin_list" | grep -qw "$plugin_id" && echo "$plugin_list" | grep "$plugin_id" | grep -q "disabled"; then
+ echo "💡 插件 [$plugin_id] 已预装,正在激活..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ if [ -d "/usr/lib/node_modules/openclaw/extensions/$plugin_id" ]; then
+ echo "💡 发现系统内置目录存在该插件,尝试直接启用..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ echo "📥 本地未发现,尝试下载安装: $plugin_full"
+ rm -rf "${HOME}/.openclaw/extensions/$plugin_id"
+ [ "$HOME" != "/root" ] && rm -rf "/root/.openclaw/extensions/$plugin_id"
+ if openclaw plugins install "$plugin_full"; then
+ echo "✅ 下载成功,正在启用..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "❌ 安装失败:$plugin_full"
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "🗑️ 正在删除/禁用插件: $plugin_id"
+ openclaw plugins disable "$plugin_id" >/dev/null 2>&1
+ if openclaw plugins uninstall "$plugin_id"; then
+ echo "✅ 已卸载: $plugin_id"
+ else
+ echo "⚠️ 卸载失败,可能为预装插件,仅禁用: $plugin_id"
+ fi
+ sync_openclaw_plugin_denylist "$plugin_id" >/dev/null 2>&1
+ success_list="$success_list $plugin_id"
+ changed=true
+ fi
+ done
+
+ echo ""
+ echo "====== 操作汇总 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失败:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 跳过:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 正在重启 OpenClaw 服务以加载变更..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+
+ install_skill() {
+ send_stats "技能管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo " 技能管理 (安装/删除) "
+ echo "========================================"
+ echo "当前已安装技能:"
+ openclaw skills list
+ echo "----------------------------------------"
+
+ # 输出推荐的实用技能列表
+ echo "推荐的实用技能(可直接复制名称输入):"
+ echo "github # 管理 GitHub Issues/PR/CI (gh CLI)"
+ echo "notion # 操作 Notion 页面、数据库和块"
+ echo "apple-notes # macOS 原生笔记管理 (创建/编辑/搜索)"
+ echo "apple-reminders # macOS 提醒事项管理 (待办清单)"
+ echo "1password # 自动化读取和注入 1Password 密钥"
+ echo "gog # Google Workspace (Gmail/云盘/文档) 全能助手"
+ echo "things-mac # 深度整合 Things 3 任务管理"
+ echo "bluebubbles # 通过 BlueBubbles 完美收发 iMessage"
+ echo "himalaya # 终端邮件管理 (IMAP/SMTP 强力工具)"
+ echo "summarize # 网页/播客/YouTube 视频内容一键总结"
+ echo "openhue # 控制 Philips Hue 智能灯光场景"
+ echo "video-frames # 视频抽帧与短片剪辑 (ffmpeg 驱动)"
+ echo "openai-whisper # 本地音频转文字 (离线隐私保护)"
+ echo "coding-agent # 自动运行 Claude Code/Codex 等编程助手"
+ echo "----------------------------------------"
+
+ echo "1) 安装技能"
+ echo "2) 删除技能"
+ echo "0) 返回"
+ read -e -p "请选择操作:" skill_action
+
+ [ "$skill_action" = "0" ] && break
+ [ -z "$skill_action" ] && continue
+
+ read -e -p "请输入技能名称(空格分隔,输入 0 退出): " skill_input
+ [ "$skill_input" = "0" ] && break
+ [ -z "$skill_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ if [ "$skill_action" = "2" ]; then
+ read -e -p "二次确认:删除仅影响用户目录 ~/.openclaw/workspace/skills,确认继续?(y/N): " confirm_del
+ if [[ ! "$confirm_del" =~ ^[Yy]$ ]]; then
+ echo "已取消删除。"
+ break_end
+ continue
+ fi
+ fi
+
+ for token in $skill_input; do
+ local skill_name
+ skill_name="$token"
+ [ -z "$skill_name" ] && continue
+
+ if [ "$skill_action" = "1" ]; then
+ local skill_found=false
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ echo "💡 技能 [$skill_name] 已在用户目录安装。"
+ skill_found=true
+ elif [ -d "/usr/lib/node_modules/openclaw/skills/${skill_name}" ]; then
+ echo "💡 技能 [$skill_name] 已在系统目录安装。"
+ skill_found=true
+ fi
+
+ if [ "$skill_found" = true ]; then
+ read -e -p "技能 [$skill_name] 已安装,是否重新安装?(y/N): " reinstall
+ if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
+ skipped_list="$skipped_list $skill_name"
+ continue
+ fi
+ fi
+
+ echo "正在安装技能:$skill_name ..."
+ if npx clawhub install "$skill_name" --yes --no-input 2>/dev/null || npx clawhub install "$skill_name"; then
+ echo "✅ 技能 $skill_name 安装成功。"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "❌ 安装失败:$skill_name"
+ failed_list="$failed_list $skill_name"
+ fi
+ else
+ echo "🗑️ 正在删除技能: $skill_name"
+ npx clawhub uninstall "$skill_name" --yes --no-input 2>/dev/null || npx clawhub uninstall "$skill_name" >/dev/null 2>&1
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ rm -rf "${HOME}/.openclaw/workspace/skills/${skill_name}"
+ echo "✅ 已删除用户技能目录: $skill_name"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "⏭️ 未发现用户技能目录: $skill_name"
+ skipped_list="$skipped_list $skill_name"
+ fi
+ fi
+ done
+
+ echo ""
+ echo "====== 操作汇总 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失败:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 跳过:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 正在重启 OpenClaw 服务以加载变更..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+openclaw_json_get_bool() {
+ local expr="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r "$expr" "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_channel_has_cfg() {
+ local channel="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r --arg c "$channel" '
+ (.channels[$c] // null) as $v
+ | if ($v | type) != "object" then
+ false
+ else
+ ([ $v
+ | to_entries[]
+ | select((.key == "enabled" or .key == "dmPolicy" or .key == "groupPolicy" or .key == "streaming") | not)
+ | .value
+ | select(. != null and . != "" and . != false)
+ ] | length) > 0
+ end
+ ' "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_dir_has_files() {
+ local dir="$1"
+ [ -d "$dir" ] && find "$dir" -type f -print -quit 2>/dev/null | grep -q .
+ }
+
+ openclaw_plugin_local_installed() {
+ local plugin="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -s "$config_file" ] && jq -e --arg p "$plugin" '.plugins.installs[$p]' "$config_file" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ # 兼容两种常见目录命名:
+ # - ~/.openclaw/extensions/qqbot
+ # - ~/.openclaw/extensions/openclaw-qqbot
+ # 避免无脑 substring,优先精确匹配与 openclaw- 前缀匹配。
+ [ -d "${HOME}/.openclaw/extensions/${plugin}" ] \
+ || [ -d "${HOME}/.openclaw/extensions/openclaw-${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/openclaw-${plugin}" ]
+ }
+
+ openclaw_bot_status_text() {
+ local enabled="$1"
+ local configured="$2"
+ local connected="$3"
+ local abnormal="$4"
+ if [ "$abnormal" = "true" ]; then
+ echo "异常"
+ elif [ "$enabled" != "true" ]; then
+ echo "未启用"
+ elif [ "$connected" = "true" ]; then
+ echo "已连接"
+ elif [ "$configured" = "true" ]; then
+ echo "已配置"
+ else
+ echo "未配置"
+ fi
+ }
+
+ openclaw_colorize_bot_status() {
+ local status="$1"
+ case "$status" in
+ 已连接) echo -e "${gl_lv}${status}${gl_bai}" ;;
+ 已配置) echo -e "${gl_huang}${status}${gl_bai}" ;;
+ 异常) echo -e "${gl_hong}${status}${gl_bai}" ;;
+ *) echo "$status" ;;
+ esac
+ }
+
+ openclaw_print_bot_status_line() {
+ local label="$1"
+ local status="$2"
+ echo -e "- ${label}: $(openclaw_colorize_bot_status "$status")"
+ }
+
+ openclaw_show_bot_local_status_block() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local json_ok="false"
+ if [ -s "$config_file" ] && jq empty "$config_file" >/dev/null 2>&1; then
+ json_ok="true"
+ fi
+
+ local tg_enabled tg_cfg tg_connected tg_abnormal tg_status
+ tg_enabled=$(openclaw_json_get_bool '.channels.telegram.enabled // .plugins.entries.telegram.enabled // false')
+ tg_cfg=$(openclaw_channel_has_cfg "telegram")
+ tg_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/telegram"; then
+ tg_connected="true"
+ fi
+ tg_abnormal="false"
+ if [ "$tg_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ tg_abnormal="true"
+ fi
+ tg_status=$(openclaw_bot_status_text "$tg_enabled" "$tg_cfg" "$tg_connected" "$tg_abnormal")
+
+ local feishu_enabled feishu_cfg feishu_connected feishu_abnormal feishu_status
+ feishu_enabled=$(openclaw_json_get_bool '.plugins.entries.feishu.enabled // .plugins.entries["openclaw-lark"].enabled // .channels.feishu.enabled // .channels.lark.enabled // false')
+ feishu_cfg=$(openclaw_channel_has_cfg "feishu")
+ if [ "$feishu_cfg" != "true" ]; then
+ feishu_cfg=$(openclaw_channel_has_cfg "lark")
+ fi
+ feishu_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/feishu" || openclaw_dir_has_files "${HOME}/.openclaw/lark" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-lark"; then
+ feishu_connected="true"
+ fi
+ feishu_abnormal="false"
+ if [ "$feishu_enabled" = "true" ] && ! openclaw_plugin_local_installed "feishu" && ! openclaw_plugin_local_installed "lark" && ! openclaw_plugin_local_installed "openclaw-lark"; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_connected" != "true" ] && [ "$feishu_enabled" = "true" ] && [ "$feishu_cfg" = "true" ] && { openclaw_plugin_local_installed "feishu" || openclaw_plugin_local_installed "lark" || openclaw_plugin_local_installed "openclaw-lark"; }; then
+ feishu_connected="true"
+ fi
+ feishu_status=$(openclaw_bot_status_text "$feishu_enabled" "$feishu_cfg" "$feishu_connected" "$feishu_abnormal")
+
+ local wa_enabled wa_cfg wa_connected wa_abnormal wa_status
+ wa_enabled=$(openclaw_json_get_bool '.plugins.entries.whatsapp.enabled // .channels.whatsapp.enabled // false')
+ wa_cfg=$(openclaw_channel_has_cfg "whatsapp")
+ wa_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/whatsapp"; then
+ wa_connected="true"
+ fi
+ wa_abnormal="false"
+ if [ "$wa_enabled" = "true" ] && ! openclaw_plugin_local_installed "whatsapp"; then
+ wa_abnormal="true"
+ fi
+ if [ "$wa_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wa_abnormal="true"
+ fi
+ wa_status=$(openclaw_bot_status_text "$wa_enabled" "$wa_cfg" "$wa_connected" "$wa_abnormal")
+
+ local dc_enabled dc_cfg dc_connected dc_abnormal dc_status
+ dc_enabled=$(openclaw_json_get_bool '.channels.discord.enabled // .plugins.entries.discord.enabled // false')
+ dc_cfg=$(openclaw_channel_has_cfg "discord")
+ dc_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/discord"; then
+ dc_connected="true"
+ fi
+ dc_abnormal="false"
+ if [ "$dc_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ dc_abnormal="true"
+ fi
+ dc_status=$(openclaw_bot_status_text "$dc_enabled" "$dc_cfg" "$dc_connected" "$dc_abnormal")
+
+ local slack_enabled slack_cfg slack_connected slack_abnormal slack_status
+ slack_enabled=$(openclaw_json_get_bool '.plugins.entries.slack.enabled // .channels.slack.enabled // false')
+ slack_cfg=$(openclaw_channel_has_cfg "slack")
+ slack_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/slack"; then
+ slack_connected="true"
+ fi
+ slack_abnormal="false"
+ if [ "$slack_enabled" = "true" ] && ! openclaw_plugin_local_installed "slack"; then
+ slack_abnormal="true"
+ fi
+ if [ "$slack_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ slack_abnormal="true"
+ fi
+ slack_status=$(openclaw_bot_status_text "$slack_enabled" "$slack_cfg" "$slack_connected" "$slack_abnormal")
+
+ local qq_enabled qq_cfg qq_connected qq_abnormal qq_status
+ qq_enabled=$(openclaw_json_get_bool '.plugins.entries.qqbot.enabled // .channels.qqbot.enabled // false')
+ qq_cfg=$(openclaw_channel_has_cfg "qqbot")
+ qq_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/qqbot/sessions" || openclaw_dir_has_files "${HOME}/.openclaw/qqbot/data"; then
+ qq_connected="true"
+ fi
+ qq_abnormal="false"
+ if [ "$qq_enabled" = "true" ] && ! openclaw_plugin_local_installed "qqbot"; then
+ qq_abnormal="true"
+ fi
+ if [ "$qq_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ qq_abnormal="true"
+ fi
+ qq_status=$(openclaw_bot_status_text "$qq_enabled" "$qq_cfg" "$qq_connected" "$qq_abnormal")
+
+ local wx_enabled wx_cfg wx_connected wx_abnormal wx_status
+ wx_enabled=$(openclaw_json_get_bool '.plugins.entries.weixin.enabled // .plugins.entries["openclaw-weixin"].enabled // .channels.weixin.enabled // .channels["openclaw-weixin"].enabled // false')
+ wx_cfg=$(openclaw_channel_has_cfg "weixin")
+ if [ "$wx_cfg" != "true" ]; then
+ wx_cfg=$(openclaw_channel_has_cfg "openclaw-weixin")
+ fi
+ wx_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/weixin" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-weixin"; then
+ wx_connected="true"
+ fi
+ wx_abnormal="false"
+ if [ "$wx_enabled" = "true" ] && ! openclaw_plugin_local_installed "weixin" && ! openclaw_plugin_local_installed "openclaw-weixin"; then
+ wx_abnormal="true"
+ fi
+ if [ "$wx_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wx_abnormal="true"
+ fi
+ wx_status=$(openclaw_bot_status_text "$wx_enabled" "$wx_cfg" "$wx_connected" "$wx_abnormal")
+
+ echo "本地状态(仅本机配置/缓存,不做网络探测):"
+ openclaw_print_bot_status_line "Telegram" "$tg_status"
+ openclaw_print_bot_status_line "飞书(Lark)" "$feishu_status"
+ openclaw_print_bot_status_line "WhatsApp" "$wa_status"
+ openclaw_print_bot_status_line "Discord" "$dc_status"
+ openclaw_print_bot_status_line "Slack" "$slack_status"
+ openclaw_print_bot_status_line "QQ Bot" "$qq_status"
+ openclaw_print_bot_status_line "微信 (Weixin)" "$wx_status"
+ }
+
+ change_tg_bot_code() {
+ send_stats "机器人对接"
+ while true; do
+ clear
+ echo "========================================"
+ echo " 机器人连接对接 "
+ echo "========================================"
+ openclaw_show_bot_local_status_block
+ echo "----------------------------------------"
+ echo "1. Telegram 机器人对接"
+ echo "2. 飞书 (Lark) 机器人对接"
+ echo "3. WhatsApp 机器人对接"
+ echo "4. QQ 机器人对接"
+ echo "5. 微信机器人对接"
+ echo "----------------------------------------"
+ echo "0. 返回上一级选单"
+ echo "----------------------------------------"
+ read -e -p "请输入你的选择: " bot_choice
+
+ case $bot_choice in
+ 1)
+ read -e -p "请输入TG机器人收到的连接码 (例如 NYA99R2F)(输入 0 退出): " code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "错误:连接码不能为空。"; sleep 1; continue; fi
+ openclaw pairing approve telegram "$code"
+ break_end
+ ;;
+ 2)
+ npx -y @larksuite/openclaw-lark install
+ openclaw config set channels.feishu.streaming true
+ openclaw config set channels.feishu.requireMention true --json
+ break_end
+ ;;
+ 3)
+ read -e -p "请输入WhatsApp收到的连接码 (例如 NYA99R2F)(输入 0 退出): " code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "错误:连接码不能为空。"; sleep 1; continue; fi
+ openclaw pairing approve whatsapp "$code"
+ break_end
+ ;;
+ 4)
+ echo "QQ 官方对接地址:"
+ echo "https://q.qq.com/qqbot/openclaw/login.html"
+ break_end
+ ;;
+ 5)
+ npx -y @tencent-weixin/openclaw-weixin-cli@latest install
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ openclaw_backup_root() {
+ echo "${HOME}/.openclaw/backups"
+ }
+
+ openclaw_is_interactive_terminal() {
+ [ -t 0 ] && [ -t 1 ]
+ }
+
+ openclaw_has_command() {
+ command -v "$1" >/dev/null 2>&1
+ }
+
+
+ openclaw_is_safe_relpath() {
+ local rel="$1"
+ [ -z "$rel" ] && return 1
+ [[ "$rel" = /* ]] && return 1
+ [[ "$rel" == *"//"* ]] && return 1
+ [[ "$rel" == *$'\n'* ]] && return 1
+ [[ "$rel" == *$'\r'* ]] && return 1
+ case "$rel" in
+ ../*|*/../*|*/..|..)
+ return 1
+ ;;
+ esac
+ return 0
+ }
+
+ openclaw_restore_path_allowed() {
+ local mode="$1"
+ local rel="$2"
+ case "$mode" in
+ memory)
+ case "$rel" in
+ MEMORY.md|AGENTS.md|USER.md|SOUL.md|TOOLS.md|memory/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ project)
+ case "$rel" in
+ openclaw.json|workspace/*|extensions/*|skills/*|prompts/*|tools/*|telegram/*|feishu/*|whatsapp/*|discord/*|slack/*|qqbot/*|logs/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ }
+
+ openclaw_pack_backup_archive() {
+ local backup_type="$1"
+ local export_mode="$2"
+ local payload_dir="$3"
+ local output_file="$4"
+
+ local tmp_root
+ tmp_root=$(mktemp -d) || return 1
+ local pack_dir="$tmp_root/package"
+ mkdir -p "$pack_dir"
+
+ cp -a "$payload_dir" "$pack_dir/payload"
+
+ (
+ cd "$pack_dir/payload" || exit 1
+ find . -type f | sed 's|^\./||' | sort > "$pack_dir/manifest.files"
+ : > "$pack_dir/manifest.sha256"
+ while IFS= read -r f; do
+ [ -z "$f" ] && continue
+ sha256sum "$f" >> "$pack_dir/manifest.sha256"
+ done < "$pack_dir/manifest.files"
+ ) || { rm -rf "$tmp_root"; return 1; }
+
+ cat > "$pack_dir/backup.meta" </dev/null
+ ) || { echo "❌ sha256 校验失败,拒绝还原"; return 1; }
+
+ echo "$pkg_dir"
+ return 0
+ }
+
+ openclaw_get_all_agent_workspaces() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json, sys, os
+try:
+ with open(sys.argv[1]) as f: data = json.load(f)
+ agents = data.get("agents", {}).get("list", [])
+ results = [{"id": "main", "ws": os.path.expanduser("~/.openclaw/workspace")}]
+ for a in agents:
+ aid = a.get("id"); ws = a.get("workspace")
+ if aid and ws and aid != "main": results.append({"id": aid, "ws": os.path.expanduser(ws)})
+ print(json.dumps(results))
+except: print("[]")
+PY
+ else
+ echo '[{"id": "main", "ws": "'"${HOME}"'/.openclaw/workspace"}]'
+ fi
+ }
+
+ openclaw_memory_backup_export() {
+ send_stats "OpenClaw记忆全量备份"
+ local backup_root=$(openclaw_backup_root)
+ local ts=$(date +%Y%m%d-%H%M%S)
+ local out_file="$backup_root/openclaw-memory-full-${ts}.tar.gz"
+ mkdir -p "$backup_root"
+ local tmp_payload=$(mktemp -d) || return 1
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c "import json, sys, os, shutil;
+workspaces = json.loads(sys.argv[1]); tmp_payload = sys.argv[2]
+for item in workspaces:
+ aid = item['id']; ws = item['ws']
+ if not os.path.isdir(ws): continue
+ target_dir = os.path.join(tmp_payload, 'agents', aid)
+ os.makedirs(target_dir, exist_ok=True)
+ for f in ['MEMORY.md', 'memory']:
+ src = os.path.join(ws, f)
+ if os.path.exists(src):
+ if os.path.isfile(src): shutil.copy2(src, target_dir)
+ else: shutil.copytree(src, os.path.join(target_dir, f), dirs_exist_ok=True)
+" "$workspaces_json" "$tmp_payload"
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 未找到可备份的记忆文件"; rm -rf "$tmp_payload"; break_end; return 1
+ fi
+ if openclaw_pack_backup_archive "memory-full" "multi-agent" "$tmp_payload" "$out_file"; then
+ echo "✅ 记忆全量备份完成 (含多智能体): $out_file"; openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ 记忆全量备份失败"
+ fi
+ rm -rf "$tmp_payload"; break_end
+ }
+
+ openclaw_memory_backup_import() {
+ send_stats "OpenClaw记忆全量还原"
+ local archive_path=$(openclaw_read_import_path "还原记忆全量 (支持多智能体)")
+ [ -z "$archive_path" ] && { echo "❌ 未输入路径"; break_end; return 1; }
+ local tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir=$(openclaw_prepare_import_archive "memory-full" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c 'import json, sys, os, shutil;
+workspaces = {item["id"]: item["ws"] for item in json.loads(sys.argv[1])};
+payload_dir = sys.argv[2]; agents_root = os.path.join(payload_dir, "agents")
+if os.path.isdir(agents_root):
+ for aid in os.listdir(agents_root):
+ if aid in workspaces:
+ src_agent_dir = os.path.join(agents_root, aid); dest_ws = workspaces[aid]
+ os.makedirs(dest_ws, exist_ok=True)
+ for f in os.listdir(src_agent_dir):
+ src = os.path.join(src_agent_dir, f); dest = os.path.join(dest_ws, f)
+ if os.path.isfile(src): shutil.copy2(src, dest)
+ else: shutil.copytree(src, dest, dirs_exist_ok=True)
+ print(f"✅ 已还原智能体记忆: {aid}")' "$workspaces_json" "$pkg_dir/payload"
+ rm -rf "$tmp_unpack"; echo "✅ 记忆全量还原完成"; break_end
+ }
+
+
+ openclaw_project_backup_export() {
+ send_stats "OpenClaw项目备份"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ if [ ! -d "$openclaw_root" ]; then
+ echo "❌ 未找到 OpenClaw 根目录: $openclaw_root"
+ break_end
+ return 1
+ fi
+
+ echo "备份模式:"
+ echo "1. 安全模式(默认,推荐):workspace + openclaw.json + extensions/skills/prompts/tools(如存在)"
+ echo "2. 完整模式(含更多状态,敏感风险更高)"
+ read -e -p "请选择备份模式(默认 1): " export_mode
+ [ -z "$export_mode" ] && export_mode="1"
+
+ local mode_label="safe"
+ local tmp_payload
+ tmp_payload=$(mktemp -d) || return 1
+
+ if [ "$export_mode" = "2" ]; then
+ mode_label="full"
+ for d in workspace extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in telegram feishu whatsapp discord slack qqbot logs; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ else
+ [ -d "$openclaw_root/workspace" ] && cp -a "$openclaw_root/workspace" "$tmp_payload/"
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ fi
+
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 未找到可备份的 OpenClaw 项目内容"
+ rm -rf "$tmp_payload"
+ break_end
+ return 1
+ fi
+
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ local out_file="$backup_root/openclaw-project-${mode_label}-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ if openclaw_pack_backup_archive "openclaw-project" "$mode_label" "$tmp_payload" "$out_file"; then
+ echo "✅ OpenClaw 项目备份完成 (${mode_label}): $out_file"
+ openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ OpenClaw 项目备份失败"
+ fi
+
+ rm -rf "$tmp_payload"
+ break_end
+ }
+
+ openclaw_project_backup_import() {
+ send_stats "OpenClaw项目还原"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ mkdir -p "$openclaw_root"
+
+ echo "⚠️ 高风险操作:项目还原会覆盖 OpenClaw 配置与工作区内容。"
+ echo "⚠️ 还原前将执行 manifest/sha256 校验、白名单恢复、gateway 停启与健康检查。"
+ read -e -p "请输入确认词【我已知晓高风险并继续还原】后继续: " confirm_text
+ if [ "$confirm_text" != "我已知晓高风险并继续还原" ]; then
+ echo "❌ 确认词不匹配,已取消还原"
+ break_end
+ return 1
+ fi
+
+ local archive_path
+ archive_path=$(openclaw_read_import_path "请输入 OpenClaw 项目备份包路径")
+ [ -z "$archive_path" ] && { echo "❌ 未输入备份路径"; break_end; return 1; }
+
+ local tmp_unpack
+ tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir
+ pkg_dir=$(openclaw_prepare_import_archive "openclaw-project" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+
+ local invalid=0
+ local valid_list
+ valid_list=$(mktemp)
+ while IFS= read -r rel; do
+ [ -z "$rel" ] && continue
+ if ! openclaw_is_safe_relpath "$rel" || ! openclaw_restore_path_allowed project "$rel"; then
+ echo "❌ 检测到非法或越权路径: $rel"
+ invalid=1
+ break
+ fi
+ echo "$rel" >> "$valid_list"
+ done < "$pkg_dir/manifest.files"
+
+ if [ "$invalid" -ne 0 ]; then
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "❌ 还原中止:存在不安全路径"
+ break_end
+ return 1
+ fi
+
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "⏸️ 还原前停止 OpenClaw gateway..."
+ openclaw gateway stop >/dev/null 2>&1
+ fi
+
+ while IFS= read -r rel; do
+ mkdir -p "$openclaw_root/$(dirname "$rel")"
+ cp -a "$pkg_dir/payload/$rel" "$openclaw_root/$rel"
+ done < "$valid_list"
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "▶️ 还原后启动 OpenClaw gateway..."
+ openclaw gateway start >/dev/null 2>&1
+ sleep 2
+ echo "🩺 gateway 健康检查:"
+ openclaw gateway status || true
+ fi
+
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "✅ OpenClaw 项目还原完成"
+ break_end
+ }
+
+ openclaw_backup_detect_type() {
+ local file_name="$1"
+ if [[ "$file_name" == openclaw-memory-full-*.tar.gz ]]; then
+ echo "记忆备份文件"
+ elif [[ "$file_name" == openclaw-project-*.tar.gz ]]; then
+ echo "项目备份文件"
+ else
+ echo "其他备份文件"
+ fi
+ }
+
+ openclaw_backup_collect_files() {
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ mapfile -t OPENCLAW_BACKUP_FILES < <(find "$backup_root" -maxdepth 1 -type f -name '*.tar.gz' -printf '%f\n' | sort -r)
+ }
+
+
+ openclaw_backup_render_file_list() {
+ local backup_root i file_name file_path file_type file_size file_time
+ local has_memory=0 has_project=0 has_other=0
+ backup_root=$(openclaw_backup_root)
+ openclaw_backup_collect_files
+
+ echo "备份目录: $backup_root"
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ echo "暂无备份文件"
+ return 0
+ fi
+
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_type=$(openclaw_backup_detect_type "${OPENCLAW_BACKUP_FILES[$i]}")
+ case "$file_type" in
+ "记忆备份文件") has_memory=1 ;;
+ "项目备份文件") has_project=1 ;;
+ "其他备份文件") has_other=1 ;;
+ esac
+ done
+
+ if [ "$has_memory" -eq 1 ]; then
+ echo "记忆备份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "记忆备份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_project" -eq 1 ]; then
+ echo "项目备份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "项目备份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_other" -eq 1 ]; then
+ echo "其他备份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "其他备份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+ }
+
+ openclaw_backup_file_exists_in_list() {
+ local target_file="$1"
+ local item
+ for item in "${OPENCLAW_BACKUP_FILES[@]}"; do
+ [ "$item" = "$target_file" ] && return 0
+ done
+ return 1
+ }
+
+ openclaw_backup_delete_file() {
+ send_stats "OpenClaw删除备份文件"
+ local backup_root backup_root_real user_input target_file target_path target_type
+ backup_root=$(openclaw_backup_root)
+
+ openclaw_backup_render_file_list
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ break_end
+ return 0
+ fi
+
+ read -e -p "请输入要删除的文件名或完整路径(0 取消): " user_input
+ if [ "$user_input" = "0" ]; then
+ echo "已取消删除。"
+ break_end
+ return 0
+ fi
+ if [ -z "$user_input" ]; then
+ echo "❌ 输入不能为空。"
+ break_end
+ return 1
+ fi
+
+ backup_root_real=$(realpath -m "$backup_root")
+ if [[ "$user_input" == /* ]]; then
+ target_path=$(realpath -m "$user_input")
+ case "$target_path" in
+ "$backup_root_real"/*) ;;
+ *)
+ echo "❌ 路径越界:仅允许删除备份根目录内的文件。"
+ break_end
+ return 1
+ ;;
+ esac
+ target_file=$(basename "$target_path")
+ else
+ target_file=$(basename -- "$user_input")
+ target_path="$backup_root/$target_file"
+ fi
+
+ if [ ! -f "$target_path" ]; then
+ echo "❌ 目标文件不存在: $target_path"
+ break_end
+ return 1
+ fi
+
+ if ! openclaw_backup_file_exists_in_list "$target_file"; then
+ echo "❌ 目标文件不在当前备份列表中。"
+ break_end
+ return 1
+ fi
+
+ target_type=$(openclaw_backup_detect_type "$target_file")
+
+ echo "即将删除: [$target_type] $target_path"
+ read -e -p "第一次确认:输入 yes 确认继续: " confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "已取消删除。"
+ break_end
+ return 0
+ fi
+ read -e -p "二次确认:输入 DELETE 执行删除: " confirm_step2
+ if [ "$confirm_step2" != "DELETE" ]; then
+ echo "已取消删除。"
+ break_end
+ return 0
+ fi
+
+ if rm -f -- "$target_path"; then
+ echo "✅ 删除成功: $target_file"
+ else
+ echo "❌ 删除失败: $target_file"
+ fi
+ break_end
+ }
+
+ openclaw_backup_list_files() {
+ openclaw_backup_render_file_list
+ break_end
+ }
+
+ openclaw_memory_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_memory_config_get() {
+ local key="$1"
+ local default_value="${2:-}"
+ local value
+ value=$(openclaw config get "$key" 2>/dev/null | head -n 1 | sed -e 's/^"//' -e 's/"$//')
+ if [ -z "$value" ] || [ "$value" = "null" ] || [ "$value" = "undefined" ]; then
+ echo "$default_value"
+ return 0
+ fi
+ echo "$value"
+ }
+
+ openclaw_memory_config_set() {
+ local key="$1"
+ shift
+ openclaw config set "$key" "$@" >/dev/null 2>&1
+ }
+
+ openclaw_memory_config_unset() {
+ local key="$1"
+ openclaw config unset "$key" >/dev/null 2>&1
+ }
+
+ openclaw_memory_cleanup_legacy_keys() {
+ openclaw_memory_config_unset "memory.local"
+ }
+
+ openclaw_memory_list_agents() {
+ if command -v openclaw >/dev/null 2>&1; then
+ local agents_json
+ agents_json=$(openclaw agents list --json 2>/dev/null || true)
+ if [ -n "$agents_json" ]; then
+ python3 - "$agents_json" <<'PY'
+import json, os, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ data = None
+seen = set()
+results = []
+if isinstance(data, list):
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ if not aid or aid in seen:
+ continue
+ ws = item.get('workspace') or ("~/.openclaw/workspace" if aid == 'main' else f"~/.openclaw/workspace-{aid}")
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+if results:
+ for aid, ws in results:
+ print(f"{aid}\t{ws}")
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+ [ $? -eq 0 ] && return 0
+ fi
+ fi
+ local config_path
+ config_path=$(openclaw_memory_config_file)
+ python3 - "$config_path" <<'PY'
+import json, os, sys
+config_path = sys.argv[1]
+results = [("main", os.path.expanduser("~/.openclaw/workspace"))]
+seen = {"main"}
+try:
+ if os.path.exists(config_path):
+ with open(config_path, encoding='utf-8') as f:
+ data = json.load(f)
+ agents = data.get('agents', {}).get('list', [])
+ if isinstance(agents, list):
+ for item in agents:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ ws = item.get('workspace')
+ if not aid or aid in seen:
+ continue
+ if not ws:
+ ws = f"~/.openclaw/workspace-{aid}"
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+except Exception:
+ pass
+for aid, ws in results:
+ print(f"{aid}\t{ws}")
+PY
+ }
+
+ openclaw_memory_status_value() {
+ local key="$1"
+ local agent_id="${2:-}"
+ if [ -n "$agent_id" ]; then
+ openclaw memory status --agent "$agent_id" 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ else
+ openclaw memory status 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ fi
+ }
+
+ openclaw_memory_expand_path() {
+ local raw_path="$1"
+ if [ -z "$raw_path" ]; then
+ echo ""
+ return 0
+ fi
+ raw_path=$(echo "$raw_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [[ "$raw_path" == ~* ]]; then
+ echo "${raw_path/#\~/$HOME}"
+ else
+ echo "$raw_path"
+ fi
+ }
+
+ openclaw_memory_rebuild_index_single() {
+ local agent_id="${1:-main}"
+ local store_raw store_file ts backup_file
+ store_raw=$(openclaw_memory_status_value "Store" "$agent_id")
+ store_file=$(openclaw_memory_expand_path "$store_raw")
+ if [ -z "$store_file" ] || [ ! -f "$store_file" ]; then
+ echo "⚠️ [$agent_id] 未找到索引库文件,可能为空或不存在。"
+ echo " Store 原始值: ${store_raw:-<空>}"
+ echo " 仍将执行重建索引。"
+ else
+ ts=$(date +%Y%m%d_%H%M%S)
+ backup_file="${store_file}.bak.${ts}"
+ if mv "$store_file" "$backup_file"; then
+ echo "✅ [$agent_id] 已备份索引: $backup_file"
+ else
+ echo "⚠️ [$agent_id] 索引备份失败,继续重建。"
+ fi
+ fi
+ openclaw memory index --agent "$agent_id" --force
+ }
+
+ openclaw_memory_rebuild_index_safe() {
+ local agent_id="${1:-main}"
+ openclaw_memory_rebuild_index_single "$agent_id"
+ openclaw gateway restart
+ echo "✅ 索引已重建并自动重启网关"
+ echo ""
+ openclaw_memory_render_status
+ }
+
+ openclaw_memory_rebuild_index_all() {
+ local count=0
+ local agent_lines agent_id workspace
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id workspace; do
+ [ -z "$agent_id" ] && continue
+ openclaw_memory_rebuild_index_single "$agent_id"
+ count=$((count+1))
+ done </dev/null)
+ if [ -z "$json_output" ]; then
+ echo "获取记忆状态失败(openclaw memory status --json 无输出)"
+ return 1
+ fi
+ python3 - "$json_output" <<'PY'
+import json, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ print("获取记忆状态失败(JSON 解析错误)")
+ raise SystemExit(1)
+if not isinstance(data, list) or len(data) == 0:
+ print("未检测到任何智能体记忆状态。")
+ raise SystemExit(0)
+first = True
+for entry in data:
+ if not isinstance(entry, dict):
+ continue
+ agent_id = entry.get("agentId", "?")
+ s = entry.get("status", {})
+ if not isinstance(s, dict):
+ s = {}
+ if not first:
+ print("")
+ first = False
+ print("Agent: %s" % agent_id)
+ backend = s.get("backend") or s.get("provider") or "-"
+ print(" 底层方案: %s" % backend)
+ files = s.get("files", 0)
+ chunks = s.get("chunks", 0)
+ print(" 已收录: %s 文件 / %s 块" % (files, chunks))
+ dirty = s.get("dirty")
+ dirty_str = "是" if dirty else "否"
+ print(" 待刷新: %s" % dirty_str)
+ vec = s.get("vector", {})
+ if isinstance(vec, dict) and vec.get("enabled"):
+ vec_str = "就绪" if vec.get("available") else "已启用(不可用)"
+ else:
+ vec_str = "未启用"
+ print(" 向量库: %s" % vec_str)
+ ws = s.get("workspaceDir") or "-"
+ print(" 工作区: %s" % ws)
+ db = s.get("dbPath") or "-"
+ print(" 索引库: %s" % db)
+ scan = entry.get("scan", {})
+ if isinstance(scan, dict):
+ issues = scan.get("issues", [])
+ if issues:
+ for issue in issues[:3]:
+ print(" ⚠️ %s" % issue)
+PY
+ }
+
+ openclaw_memory_get_backend() {
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "local" ]; then
+ echo "builtin"
+ else
+ echo "$backend"
+ fi
+ }
+
+ openclaw_memory_get_local_model_path() {
+ openclaw_memory_config_get "agents.defaults.memorySearch.local.modelPath"
+ }
+
+ openclaw_memory_local_model_status() {
+ local model_path="$1"
+ if [ -z "$model_path" ]; then
+ echo "missing"
+ return
+ fi
+ if [[ "$model_path" == hf:* ]]; then
+ echo "hf"
+ return
+ fi
+ if [ -f "$model_path" ]; then
+ echo "ok"
+ else
+ echo "missing"
+ fi
+ }
+
+ openclaw_memory_qmd_available() {
+ if command -v qmd >/dev/null 2>&1; then
+ echo "true"
+ return
+ fi
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "qmd" ]; then
+ echo "true"
+ return
+ fi
+ echo "false"
+ }
+
+ openclaw_memory_probe_url() {
+ local url="$1"
+ if ! command -v curl >/dev/null 2>&1; then
+ echo "unknown"
+ return
+ fi
+ if [ -z "$url" ]; then
+ echo "unknown"
+ return
+ fi
+ if curl -I -m 2 -s "$url" >/dev/null 2>&1; then
+ echo "ok"
+ else
+ echo "fail"
+ fi
+ }
+
+ openclaw_memory_recommend() {
+ local qmd_ok model_path model_status hf_ok mirror_ok
+ qmd_ok=$(openclaw_memory_qmd_available)
+ model_path=$(openclaw_memory_get_local_model_path)
+ model_status=$(openclaw_memory_local_model_status "$model_path")
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+
+ OPENCLAW_MEMORY_RECOMMEND_REASON=()
+ if [ "$qmd_ok" = "true" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD 可用")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("未检测到 QMD")
+ fi
+ if [ -n "$model_path" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型路径: $model_path")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("未配置本地模型路径")
+ fi
+ case "$model_status" in
+ ok) OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型文件存在") ;;
+ hf) OPENCLAW_MEMORY_RECOMMEND_REASON+=("模型来自 HF 下载源(国内可能慢/失败)") ;;
+ *) OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型文件不存在或不可用") ;;
+ esac
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co 可访问")
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("hf-mirror.com 可访问")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co / hf-mirror.com 可能不可达(疑似国内/受限网络)")
+ fi
+
+ if [ "$qmd_ok" = "true" ]; then
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && { [ "$hf_ok" = "ok" ] || [ "$mirror_ok" = "ok" ]; }; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && [ "$hf_ok" = "fail" ] && [ "$mirror_ok" = "fail" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ else
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ fi
+ }
+
+
+ openclaw_memory_detect_region() {
+ OPENCLAW_MEMORY_COUNTRY="unknown"
+ OPENCLAW_MEMORY_USE_MIRROR="false"
+ if command -v curl >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_COUNTRY=$(curl -s -m 2 ipinfo.io/country | tr -d '
+' | tr -d '
+')
+ fi
+ case "$OPENCLAW_MEMORY_COUNTRY" in
+ CN|HK)
+ OPENCLAW_MEMORY_USE_MIRROR="true"
+ ;;
+ esac
+ }
+
+ openclaw_memory_select_sources() {
+ local hf_ok mirror_ok
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+ OPENCLAW_MEMORY_HF_OK="$hf_ok"
+ OPENCLAW_MEMORY_MIRROR_OK="$mirror_ok"
+ if [ "$OPENCLAW_MEMORY_USE_MIRROR" = "true" ]; then
+ if [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ elif [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://gh.kejilion.pro/"
+ else
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://"
+ fi
+ }
+
+ openclaw_memory_download_file() {
+ local url="$1"
+ local dest="$2"
+ mkdir -p "$(dirname "$dest")"
+ if command -v curl >/dev/null 2>&1; then
+ curl -L --fail --retry 2 -o "$dest" "$url"
+ return $?
+ fi
+ if command -v wget >/dev/null 2>&1; then
+ wget -O "$dest" "$url"
+ return $?
+ fi
+ echo "❌ 未检测到 curl 或 wget,无法下载。"
+ return 1
+ }
+
+ openclaw_memory_check_sqlite() {
+ if ! command -v sqlite3 >/dev/null 2>&1; then
+ echo "⚠️ 未检测到 sqlite3,QMD 可能无法正常运行。"
+ return 1
+ fi
+ local ver
+ ver=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
+ echo "✅ sqlite3 可用: ${ver:-unknown}"
+ echo "ℹ️ sqlite 扩展支持无法可靠检测,将继续。"
+ return 0
+ }
+
+ openclaw_memory_ensure_bun() {
+ if [ -x "$HOME/.bun/bin/bun" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun 已存在"
+ return 0
+ fi
+ echo "⬇️ 安装 bun..."
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL https://bun.sh/install | bash
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO- https://bun.sh/install | bash
+ else
+ echo "❌ 未检测到 curl 或 wget,无法安装 bun。"
+ return 1
+ fi
+ if [ -d "$HOME/.bun/bin" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun 安装完成"
+ return 0
+ fi
+ echo "❌ bun 安装失败"
+ return 1
+ }
+
+ openclaw_memory_ensure_qmd() {
+ local qmd_path
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -n "$qmd_path" ]; then
+ if qmd --version >/dev/null 2>&1; then
+ echo "✅ qmd 已存在且可用: $qmd_path"
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ return 0
+ else
+ echo "⚠️ qmd 命令存在但模块损坏,重新安装..."
+ fi
+ fi
+ echo "⬇️ 通过 npm 安装 qmd: @tobilu/qmd"
+ npm install -g @tobilu/qmd
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -z "$qmd_path" ]; then
+ echo "❌ qmd 安装失败"
+ return 1
+ fi
+ if ! qmd --version >/dev/null 2>&1; then
+ echo "❌ qmd 安装后仍无法运行"
+ return 1
+ fi
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ echo "✅ qmd 安装完成: $qmd_path"
+ return 0
+ }
+
+ openclaw_memory_render_auto_summary() {
+ echo "---------------------------------------"
+ echo "✅ 环境就绪"
+ echo "方案: ${OPENCLAW_MEMORY_AUTO_SCHEME:-unknown}"
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "模式: 仅写配置(未安装/未下载)"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "索引: 已执行"
+ else
+ echo "索引: 已跳过"
+ fi
+ if [ "$OPENCLAW_MEMORY_RESTARTED" = "true" ]; then
+ echo "重启: 已执行"
+ else
+ echo "重启: 已跳过"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ echo "qmd: $OPENCLAW_MEMORY_QMD_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_MODEL_PATH" ]; then
+ echo "模型: $OPENCLAW_MEMORY_MODEL_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_COUNTRY" ]; then
+ echo "地区: $OPENCLAW_MEMORY_COUNTRY"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_HF_BASE" ]; then
+ echo "下载源: $OPENCLAW_MEMORY_HF_BASE"
+ fi
+ echo "最终状态:"
+ openclaw_memory_render_status
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_auto_confirm() {
+ local scheme_label="$1"
+ OPENCLAW_MEMORY_PREHEAT="true"
+ OPENCLAW_MEMORY_RESTARTED="false"
+ OPENCLAW_MEMORY_CONFIG_ONLY="false"
+ echo "即将执行自动部署(详细模式)"
+ echo "目标方案: $scheme_label"
+ echo "地区: ${OPENCLAW_MEMORY_COUNTRY:-unknown}"
+ echo "镜像源探测: huggingface.co=${OPENCLAW_MEMORY_HF_OK:-unknown} hf-mirror.com=${OPENCLAW_MEMORY_MIRROR_OK:-unknown}"
+ echo "下载源: ${OPENCLAW_MEMORY_HF_BASE:-unknown}"
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_PATH" ]; then
+ echo "预计下载路径: $OPENCLAW_MEMORY_EXPECT_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_SIZE" ]; then
+ echo "可能流量/磁盘占用: $OPENCLAW_MEMORY_EXPECT_SIZE"
+ else
+ echo "可能流量/磁盘占用: 视实际情况而定"
+ fi
+ echo "确认后将自动安装/下载、写入配置、构建索引并重启网关"
+ echo "高级选项: 输入 config 仅写配置(不安装不下载、不索引、不重启)"
+ read -e -p "输入 yes 确认继续(默认 N): " confirm_step
+ case "$confirm_step" in
+ yes|YES)
+ OPENCLAW_MEMORY_PREHEAT="true"
+ ;;
+ config|CONFIG)
+ OPENCLAW_MEMORY_CONFIG_ONLY="true"
+ OPENCLAW_MEMORY_PREHEAT="false"
+ ;;
+ *)
+ echo "已取消自动部署。"
+ return 1
+ ;;
+ esac
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "⚠️ 已选择仅写配置,不安装不下载"
+ else
+ echo "✅ 将自动构建索引并重启网关"
+ fi
+ return 0
+ }
+
+ openclaw_memory_auto_setup_qmd() {
+ echo "🔍 检测 QMD 环境"
+ openclaw_memory_cleanup_legacy_keys
+ openclaw_memory_check_sqlite || true
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ if command -v qmd >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_QMD_PATH=$(command -v qmd)
+ else
+ OPENCLAW_MEMORY_QMD_PATH="qmd"
+ fi
+ else
+ openclaw_memory_ensure_qmd || return 1
+ fi
+ local backend
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ]; then
+ echo "✅ memory.backend 已是 qmd"
+ else
+ openclaw_memory_config_set "memory.backend" "qmd"
+ echo "✅ 已设置 memory.backend=qmd"
+ fi
+ local qmd_cmd
+ qmd_cmd=$(openclaw_memory_config_get "memory.qmd.command")
+ if [ -z "$qmd_cmd" ] || [[ "$qmd_cmd" != /* ]] || [ "$qmd_cmd" != "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ openclaw_memory_config_set "memory.qmd.command" "$OPENCLAW_MEMORY_QMD_PATH"
+ echo "✅ 已写入 memory.qmd.command: $OPENCLAW_MEMORY_QMD_PATH"
+ else
+ echo "✅ memory.qmd.command 已正确"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "🔥 预热索引(可能下载模型)"
+ openclaw_memory_prepare_workspace_all
+ local preh_agent_lines preh_agent_id preh_workspace
+ preh_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r preh_agent_id preh_workspace; do
+ [ -z "$preh_agent_id" ] && continue
+ openclaw memory index --agent "$preh_agent_id" --force
+ done </dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ OPENCLAW_MEMORY_RESTARTED="true"
+ openclaw_memory_render_auto_summary
+ return 0
+ }
+
+ openclaw_memory_auto_setup_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "记忆方案自动部署"
+ echo "======================================="
+ echo "1. QMD"
+ echo "2. Local"
+ echo "3. Auto(自动选择)"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " auto_choice
+ case "$auto_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_apply_scheme() {
+ local scheme="$1"
+ openclaw_memory_cleanup_legacy_keys
+ case "$scheme" in
+ qmd)
+ openclaw_memory_config_set "memory.backend" "qmd"
+ if [ $? -ne 0 ]; then
+ echo "❌ 写入配置失败"
+ return 1
+ fi
+ openclaw_memory_config_set "memory.qmd.command" "qmd" >/dev/null 2>&1
+ ;;
+ local)
+ openclaw_memory_config_set "memory.backend" "builtin"
+ if [ $? -ne 0 ]; then
+ echo "❌ 写入配置失败"
+ return 1
+ fi
+ openclaw_memory_config_set "agents.defaults.memorySearch.provider" "local" >/dev/null 2>&1
+ ;;
+ *)
+ echo "❌ 未知方案: $scheme"
+ return 1
+ esac
+ echo "✅ 已更新记忆方案配置"
+ return 0
+ }
+
+ openclaw_memory_offer_restart() {
+ echo "配置已写入,需要重启 OpenClaw 网关后生效。"
+ read -e -p "是否立即重启 OpenClaw 网关?(Y/n): " restart_choice
+ if [[ "$restart_choice" =~ ^[Nn]$ ]]; then
+ echo "已跳过重启,可稍后执行: openclaw gateway restart"
+ return 0
+ fi
+ if declare -F start_gateway >/dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ }
+
+ openclaw_memory_fix_index() {
+ local backend include_dm
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ] && ! command -v qmd >/dev/null 2>&1; then
+ echo "⚠️ 检测到当前方案为 QMD,但未安装 qmd 命令。"
+ echo " 可切换 Local,或安装 bun + qmd 后再试。"
+ fi
+ include_dm=$(openclaw config get memory.qmd.includeDefaultMemory 2>/dev/null)
+ echo "======================================="
+ echo "索引修复诊断"
+ echo "======================================="
+ echo "当前 includeDefaultMemory: ${include_dm:-未设置}"
+ echo ""
+ if [ "$include_dm" = "false" ]; then
+ echo "⚠️ 检测到 includeDefaultMemory=false"
+ echo " 这会导致默认记忆文件(MEMORY.md + memory/*.md)不被索引"
+ echo " 所以 Indexed 会一直显示 0/N"
+ echo ""
+ read -e -p "是否恢复为 true 并重建索引?(Y/n): " fix_choice
+ if [[ ! "$fix_choice" =~ ^[Nn]$ ]]; then
+ openclaw_memory_config_set "memory.qmd.includeDefaultMemory" true
+ if [ $? -ne 0 ]; then
+ echo "❌ 写入配置失败"
+ break_end
+ return 1
+ fi
+ echo "✅ 已恢复 includeDefaultMemory=true"
+ openclaw_memory_rebuild_index_all
+ else
+ echo "已取消。"
+ fi
+ else
+ echo "includeDefaultMemory 配置正常。"
+ echo "将执行:清理旧索引 → 全量重建所有智能体索引"
+ echo ""
+ read -e -p "确认执行?(Y/n): " confirm_fix
+ if [[ ! "$confirm_fix" =~ ^[Nn]$ ]]; then
+ openclaw_memory_rebuild_index_all
+ else
+ echo "已取消。"
+ fi
+ fi
+ break_end
+ }
+
+ openclaw_memory_scheme_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 记忆方案"
+ echo "======================================="
+ local backend current_label
+ backend=$(openclaw_memory_get_backend)
+ case "$backend" in
+ qmd) current_label="QMD" ;;
+ builtin|local) current_label="Local" ;;
+ *) current_label="未配置" ;;
+ esac
+ echo "当前方案: $current_label"
+ echo ""
+ echo "QMD : 轻量索引,依赖 qmd 命令(适合网络受限)"
+ echo "Local: 本地向量检索,依赖 embedding 模型文件"
+ echo "Auto : 自动推荐(基于可用性 + 网络探测)"
+ echo "---------------------------------------"
+ echo "1. 切换 QMD(自动部署/已装则跳过)"
+ echo "2. 切换 Local(自动部署/已装则跳过)"
+ echo "3. Auto(自动推荐并自动部署)"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " scheme_choice
+ case "$scheme_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_file_collect() {
+ OPENCLAW_MEMORY_FILES=()
+ OPENCLAW_MEMORY_FILE_LABELS=()
+ local agent_lines agent_id base_dir memory_dir memory_file rel
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id base_dir; do
+ [ -z "$agent_id" ] && continue
+ memory_dir="$base_dir/memory"
+ memory_file="$base_dir/MEMORY.md"
+ if [ -f "$memory_file" ]; then
+ OPENCLAW_MEMORY_FILES+=("$memory_file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/MEMORY.md")
+ fi
+ if [ -d "$memory_dir" ]; then
+ while IFS= read -r file; do
+ [ -f "$file" ] || continue
+ rel="${file#$base_dir/}"
+ OPENCLAW_MEMORY_FILES+=("$file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/$rel")
+ done < <(find "$memory_dir" -type f -name '*.md' | sort)
+ fi
+ done </dev/null || stat -c %y "$file" | awk '{print $1" "$2}')
+ printf "%s | %s | %s | %s\\n" "$((i+1))" "$rel" "$size" "$mtime"
+ done
+ }
+
+ openclaw_memory_view_file() {
+ local file="$1"
+ [ -f "$file" ] || {
+ echo "❌ 文件不存在: $file"
+ return 1
+ }
+ local total_lines
+ total_lines=$(wc -l < "$file" 2>/dev/null || echo 0)
+ local default_lines=120
+ local start_line count
+ echo "文件: $file"
+ echo "总行数: $total_lines"
+ read -e -p "请输入起始行(回车默认末尾 $default_lines 行): " start_line
+ read -e -p "请输入显示行数(回车默认 $default_lines): " count
+ [ -z "$count" ] && count=$default_lines
+ if [ -z "$start_line" ]; then
+ if [ "$total_lines" -le "$count" ]; then
+ start_line=1
+ else
+ start_line=$((total_lines - count + 1))
+ fi
+ fi
+ if ! [[ "$start_line" =~ ^[0-9]+$ ]] || ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ echo "❌ 请输入有效的数字。"
+ return 1
+ fi
+ if [ "$start_line" -lt 1 ]; then
+ start_line=1
+ fi
+ if [ "$count" -le 0 ]; then
+ echo "❌ 行数必须大于 0。"
+ return 1
+ fi
+ local end_line=$((start_line + count - 1))
+ if [ "$end_line" -gt "$total_lines" ]; then
+ end_line=$total_lines
+ fi
+ if [ "$total_lines" -eq 0 ]; then
+ echo "(空文件)"
+ return 0
+ fi
+ echo "---------------------------------------"
+ sed -n "${start_line},${end_line}p" "$file"
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_files_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 记忆文件"
+ echo "======================================="
+ openclaw_memory_file_render_list
+ echo "---------------------------------------"
+ read -e -p "请输入文件编号查看(0 返回): " file_choice
+ if [ "$file_choice" = "0" ]; then
+ return 0
+ fi
+ if ! [[ "$file_choice" =~ ^[0-9]+$ ]]; then
+ echo "无效的选择,请重试。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_file_collect
+ if [ ${#OPENCLAW_MEMORY_FILES[@]} -eq 0 ]; then
+ read -p "未找到记忆文件,按回车返回..."
+ return 0
+ fi
+ local idx=$((file_choice-1))
+ if [ "$idx" -lt 0 ] || [ "$idx" -ge ${#OPENCLAW_MEMORY_FILES[@]} ]; then
+ echo "无效的编号,请重试。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_view_file "${OPENCLAW_MEMORY_FILES[$idx]}"
+ read -p "按回车返回列表..."
+ done
+ }
+
+
+ openclaw_memory_search_test() {
+ read -e -p "输入搜索关键词: " query
+ if [ -z "$query" ]; then
+ echo "关键词不能为空。"
+ return 1
+ fi
+ echo "正在搜索记忆..."
+ openclaw memory search "$query" --max-results 5
+ }
+
+ openclaw_memory_deep_status() {
+ echo "正在探测嵌入模型就绪状态..."
+ openclaw memory status --deep
+ }
+
+ openclaw_memory_menu() {
+ send_stats "OpenClaw记忆管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 记忆管理"
+ echo "======================================="
+ openclaw_memory_render_status
+ echo "1. 更新记忆索引"
+ echo "2. 查看记忆文件"
+ echo "3. 索引修复(Indexed 异常)"
+ echo "4. 记忆方案(QMD/Local/Auto)"
+ echo "5. 搜索测试(验证索引是否工作)"
+ echo "6. 深度状态探测(检查嵌入模型)"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " memory_choice
+ case "$memory_choice" in
+ 1)
+ echo "即将更新记忆索引。"
+ read -e -p "第一次确认:输入 yes 继续: " confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "已取消。"
+ break_end
+ continue
+ fi
+ openclaw_memory_prepare_workspace_all
+ read -e -p "二次确认:输入 force 使用全量(留空为增量): " confirm_step2
+ if [ "$confirm_step2" = "force" ]; then
+ echo "⚠️ 全量重建更彻底,但耗时更长。"
+ echo "推荐:输入 rebuild 进行安全重建(先备份索引库)。"
+ read -e -p "第三次确认:输入 rebuild 执行安全重建;直接回车继续普通 force: " confirm_step3
+ if [ "$confirm_step3" = "rebuild" ]; then
+ openclaw_memory_rebuild_index_all
+ else
+ local fl_agent_lines fl_agent_id fl_workspace
+ fl_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r fl_agent_id fl_workspace; do
+ [ -z "$fl_agent_id" ] && continue
+ openclaw memory index --agent "$fl_agent_id" --force
+ done </dev/null 2>&1 || {
+ echo "⚠️ 权限备份失败:$backup_file"
+ return 1
+ }
+ echo "✅ 已备份当前权限配置: $backup_file"
+ return 0
+ }
+
+ openclaw_permission_restore_backup() {
+ local config_file backup_file
+ config_file=$(openclaw_permission_config_file)
+ backup_file=$(openclaw_permission_backup_file)
+ if [ ! -s "$backup_file" ]; then
+ echo "❌ 未找到可恢复的权限备份文件。"
+ return 1
+ fi
+ cp -f "$backup_file" "$config_file" >/dev/null 2>&1 || {
+ echo "❌ 权限恢复失败:$backup_file"
+ return 1
+ }
+ echo "✅ 已恢复切换前权限配置"
+ openclaw_permission_restart_gateway || true
+ return 0
+ }
+
+ openclaw_permission_restart_gateway() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ 未检测到 openclaw,无法重启 OpenClaw Gateway。"
+ return 1
+ fi
+ echo "正在重启 OpenClaw Gateway..."
+ openclaw gateway restart >/dev/null 2>&1 || {
+ openclaw gateway stop >/dev/null 2>&1
+ openclaw gateway start >/dev/null 2>&1
+ }
+ }
+
+ openclaw_permission_get_value() {
+ local path="$1"
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+
+ if openclaw_has_command openclaw; then
+ local value
+ value=$(openclaw config get "$path" 2>&1 | head -n 1)
+ if [ -n "$value" ]; then
+ if echo "$value" | grep -qi "config path not found"; then
+ echo "(unset)"
+ return 0
+ fi
+ if [ "$value" = "null" ]; then
+ echo "(unset)"
+ else
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ fi
+ return 0
+ fi
+ fi
+
+ [ -f "$config_file" ] || { echo "(unset)"; return 0; }
+
+ if openclaw_has_command jq; then
+ local jq_value
+ jq_value=$(jq -r --arg p "$path" 'getpath($p|split(".")) // "(unset)"' "$config_file" 2>/dev/null) || jq_value="(unset)"
+ [ "$jq_value" = "null" ] && jq_value="(unset)"
+ echo "$jq_value"
+ return 0
+ fi
+
+ if openclaw_has_command python3; then
+ python3 - "$config_file" "$path" <<'PY'
+import json, sys
+path = sys.argv[2]
+with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+cur = obj
+for part in path.split('.'):
+ if isinstance(cur, dict) and part in cur:
+ cur = cur[part]
+ else:
+ print('(unset)')
+ raise SystemExit(0)
+if isinstance(cur, bool):
+ print('true' if cur else 'false')
+elif cur is None:
+ print('(unset)')
+else:
+ print(json.dumps(cur, ensure_ascii=False) if isinstance(cur, (dict, list)) else str(cur))
+PY
+ return 0
+ fi
+
+ echo "(unset)"
+ return 0
+ }
+
+ openclaw_permission_unset_optional() {
+ local key="$1"
+ local probe
+ if ! openclaw_has_command openclaw; then
+ return 1
+ fi
+ if openclaw config unset "$key" >/dev/null 2>&1; then
+ return 0
+ fi
+ probe=$(openclaw config get "$key" 2>&1 | head -n 1)
+ if [ -z "$probe" ] || [ "$probe" = "null" ] || [ "$probe" = "(unset)" ] || echo "$probe" | grep -qi "config path not found"; then
+ return 0
+ fi
+ return 1
+ }
+
+ openclaw_permission_detect_mode() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ [ ! -f "$config_file" ] && { echo "未知模式"; return; }
+
+ python3 - "$config_file" <<'PY'
+import json, sys
+
+def get_v(o, p):
+ for k in p.split('.'):
+ if isinstance(o, dict) and k in o:
+ o = o[k]
+ else:
+ return "(unset)"
+ return str(o).lower()
+
+try:
+ with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ d = json.load(f)
+ p = get_v(d, "tools.profile")
+ s = get_v(d, "tools.exec.security")
+ a = get_v(d, "tools.exec.ask")
+ e = get_v(d, "tools.elevated.enabled")
+ b = get_v(d, "commands.bash")
+ ap = get_v(d, "tools.exec.applyPatch.enabled")
+ w = get_v(d, "tools.exec.applyPatch.workspaceOnly")
+
+ if p == "coding" and s == "allowlist" and a == "on-miss" and e == "false" and b == "false" and ap == "false":
+ print("标准安全模式")
+ elif p == "coding" and s == "allowlist" and a == "on-miss" and e == "true" and b == "true" and ap == "true" and w == "true":
+ print("开发增强模式")
+ elif (p == "full" or p == "(unset)") and s == "full" and a == "off" and e == "true" and b == "true" and ap == "true":
+ print("完全开放模式")
+ else:
+ print("自定义模式")
+except Exception:
+ print("自定义模式")
+PY
+ }
+
+ openclaw_permission_update_exec_approvals() {
+ local sec="$1"
+ local ask="$2"
+ local fallback="$3"
+ local approvals_file="$HOME/.openclaw/exec-approvals.json"
+
+ mkdir -p "$HOME/.openclaw"
+
+ # 生成 JSON 并通过 openclaw approvals set --stdin 写入(优先)
+ # 若 CLI 不支持则回退直接写文件
+ local json_payload
+ json_payload=$(python3 -c '
+import json, sys, os
+path = sys.argv[1]
+try:
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ data = json.load(f)
+ else:
+ data = {"version": 1, "defaults": {}}
+except Exception:
+ data = {"version": 1, "defaults": {}}
+if "defaults" not in data:
+ data["defaults"] = {}
+data["defaults"]["security"] = sys.argv[2]
+data["defaults"]["ask"] = sys.argv[3]
+data["defaults"]["askFallback"] = sys.argv[4]
+data["defaults"]["autoAllowSkills"] = True
+print(json.dumps(data, indent=2))
+' "$approvals_file" "$sec" "$ask" "$fallback")
+
+ if openclaw_has_command openclaw && echo "$json_payload" | openclaw approvals set --stdin >/dev/null 2>&1; then
+ return 0
+ fi
+ # 回退:直接写文件
+ echo "$json_payload" > "$approvals_file"
+ }
+
+ openclaw_permission_render_status() {
+ echo "应用层配置: ~/.openclaw/openclaw.json"
+ echo "宿主机审批: ~/.openclaw/exec-approvals.json"
+ echo "---------------------------------------"
+ local current_profile current_sec current_ask current_elevated
+ current_profile=$(openclaw config get tools.profile 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_sec=$(openclaw config get tools.exec.security 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_ask=$(openclaw config get tools.exec.ask 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_elevated=$(openclaw config get tools.elevated.enabled 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ # 清理空值
+ [ -z "$current_profile" ] || echo "$current_profile" | grep -qi "config path not found" && current_profile=""
+ [ -z "$current_sec" ] || echo "$current_sec" | grep -qi "config path not found" && current_sec=""
+ [ -z "$current_ask" ] || echo "$current_ask" | grep -qi "config path not found" && current_ask=""
+ [ -z "$current_elevated" ] || echo "$current_elevated" | grep -qi "config path not found" && current_elevated=""
+
+ local current_mode="未知 / 自定义"
+ if [ "$current_profile" = "full" ] && [ "$current_sec" = "full" ] && [ "$current_ask" = "off" ]; then
+ current_mode="\033[1;31m完全开放模式\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" = "true" ]; then
+ current_mode="\033[1;33m开发增强模式\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" != "true" ]; then
+ current_mode="\033[1;32m标准安全模式\033[0m"
+ elif [ -z "$current_profile" ] && [ -z "$current_sec" ]; then
+ current_mode="\033[1;36m官方沙盒兜底\033[0m"
+ fi
+ echo -e " 当前综合安全等级: ${current_mode}"
+ echo "---------------------------------------"
+ echo -e "${gl_huang}[应用层 Tool Policy 状态]${gl_bai}"
+ echo " Profile (预设): ${current_profile:-(unset)}"
+ echo " Exec 限制: ${current_sec:-(unset)}"
+ echo " 审批提示: ${current_ask:-(unset)}"
+ echo " 提权开关: ${current_elevated:-(unset)}"
+
+ echo -e "\n${gl_huang}[底层 Exec Approvals 状态]${gl_bai}"
+ if openclaw_has_command openclaw; then
+ local approvals_json
+ approvals_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$approvals_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ defaults = d.get("file", {}).get("defaults", {})
+ if not defaults:
+ defaults = d.get("defaults", {})
+ sec = defaults.get("security", "(unset)")
+ ask = defaults.get("ask", "(unset)")
+ fb = defaults.get("askFallback", "(unset)")
+ auto = defaults.get("autoAllowSkills", False)
+ print(" 拦截策略 (Security): " + str(sec))
+ print(" 提示策略 (Ask): " + str(ask))
+ print(" 无UI兜底 (AskFallback): " + str(fb))
+ print(" 自动放行技能 (autoAllowSkills): " + ("on" if auto else "off"))
+ exists = d.get("exists", True)
+ if not exists:
+ print(" (审批文件不存在,使用系统内置安全兜底)")
+except Exception as e:
+ print(" (解析失败: " + str(e) + ")")
+' "$approvals_json"
+ else
+ echo " (openclaw approvals get --json 无输出)"
+ fi
+ elif [ -f "$HOME/.openclaw/exec-approvals.json" ]; then
+ python3 -c '
+import json, os
+path = os.path.expanduser("~/.openclaw/exec-approvals.json")
+try:
+ with open(path) as f:
+ d = json.load(f).get("defaults", {})
+ print(" 拦截策略 (Security): " + str(d.get("security", "(unset)")))
+ print(" 提示策略 (Ask): " + str(d.get("ask", "(unset)")))
+ print(" 无UI兜底 (AskFallback): " + str(d.get("askFallback", "(unset)")))
+except Exception:
+ print(" (配置文件解析失败)")
+'
+ else
+ echo " (未配置,强制使用系统内置安全兜底策略)"
+ fi
+ }
+
+ openclaw_permission_apply_standard() {
+ send_stats "OpenClaw权限-标准安全模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在配置应用层策略..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled false >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval true >/dev/null 2>&1 # 拦截危险的内联代码
+ openclaw config unset commands.bash >/dev/null 2>&1 # 废弃旧版参数
+
+ echo "正在配置宿主机审批拦截..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切换为标准安全模式 (所有危险命令将通过UI/TG请求你的审批)${gl_bai}"
+ }
+
+ openclaw_permission_apply_developer() {
+ send_stats "OpenClaw权限-开发增强模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在配置应用层策略..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1 # 允许智能体申请提权
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "正在配置宿主机审批拦截..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切换为开发增强模式 (允许提权,但常规危险命令依然需要审批)${gl_bai}"
+ }
+
+ openclaw_permission_apply_full() {
+ send_stats "OpenClaw权限-完全开放模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在配置应用层策略..."
+ openclaw config set tools.profile full >/dev/null 2>&1
+ openclaw config set tools.exec.security full >/dev/null 2>&1
+ openclaw config set tools.exec.ask off >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "正在瓦解宿主机拦截防御..."
+ # 这里的 full 和 off 将彻底绕过底层宿主机的 exec 审批系统
+ openclaw_permission_update_exec_approvals "full" "off" "full"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切换为完全开放模式 (警告:所有宿主机命令拦截已失效,智能体具有最高权限)${gl_bai}"
+ }
+
+ openclaw_permission_restore_official_defaults() {
+ send_stats "OpenClaw权限-恢复官方默认"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "清理应用层强制覆盖..."
+ openclaw config unset tools.profile >/dev/null 2>&1
+ openclaw config unset tools.exec.security >/dev/null 2>&1
+ openclaw config unset tools.exec.ask >/dev/null 2>&1
+ openclaw config unset tools.elevated.enabled >/dev/null 2>&1
+ openclaw config unset tools.exec.strictInlineEval >/dev/null 2>&1
+
+ echo "清理宿主机拦截配置..."
+ # 优先通过 CLI 清空审批配置,回退直接删文件
+ if echo '{"version":1,"defaults":{}}' | openclaw approvals set --stdin >/dev/null 2>&1; then
+ true
+ else
+ rm -f "$HOME/.openclaw/exec-approvals.json"
+ fi
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已恢复到 OpenClaw 官方安全沙盒防御机制${gl_bai}"
+ }
+
+ openclaw_permission_run_audit() {
+ echo "======================================="
+ echo "运行 OpenClaw 官方安全审计与体检..."
+ echo "======================================="
+ openclaw security audit
+ echo "---------------------------------------"
+ read -e -p "是否尝试自动修复发现的安全隐患?(y/n): " fix_choice
+ if [[ "$fix_choice" == "y" || "$fix_choice" == "Y" || "$fix_choice" == "yes" ]]; then
+ openclaw security audit --fix
+ echo -e "${gl_lv}✅ 自动修复完成。${gl_bai}"
+ fi
+ echo "按任意键返回..."
+ read -n 1 -s
+ }
+
+
+ openclaw_permission_manage_allowlist() {
+ while true; do
+ clear
+ echo "======================================="
+ echo " Exec 命令白名单管理"
+ echo "======================================="
+ echo "当前白名单:"
+ local allowlist_json
+ allowlist_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$allowlist_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ f = d.get("file", {})
+ agents = f.get("agents", {})
+ found = False
+ for agent_id, agent_data in agents.items():
+ al = agent_data.get("allowlist", [])
+ if al:
+ found = True
+ print(" 智能体 [%s]:" % agent_id)
+ for item in al:
+ print(" - %s" % item)
+ if not found:
+ print(" (空,未配置任何白名单规则)")
+except Exception as e:
+ print(" (解析失败: " + str(e) + ")")
+' "$allowlist_json"
+ else
+ echo " (无法获取)"
+ fi
+ echo "---------------------------------------"
+ echo "1. 添加白名单规则"
+ echo "2. 移除白名单规则"
+ echo "0. 返回"
+ echo "---------------------------------------"
+ read -e -p "请选择: " al_choice
+ case "$al_choice" in
+ 1)
+ read -e -p "输入要放行的命令路径 (支持 glob,如 /usr/bin/git): " pattern
+ [ -z "$pattern" ] && { echo "不能为空"; break_end; continue; }
+ read -e -p "指定智能体ID (留空=所有智能体 *): " agent_id
+ agent_id="${agent_id:-*}"
+ openclaw approvals allowlist add --agent "$agent_id" "$pattern"
+ break_end
+ ;;
+ 2)
+ read -e -p "输入要移除的命令路径: " pattern
+ [ -z "$pattern" ] && { echo "不能为空"; break_end; continue; }
+ openclaw approvals allowlist remove "$pattern"
+ break_end
+ ;;
+ 0) return 0 ;;
+ *) echo "无效选择"; sleep 1 ;;
+ esac
+ done
+ }
+
+ openclaw_permission_menu() {
+ send_stats "OpenClaw权限管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo " OpenClaw 权限管理 (双层架构深度适配)"
+ echo "======================================="
+ openclaw_permission_render_status
+ echo "---------------------------------------"
+ echo -e "${gl_kjlan}1.${gl_bai} 切换为标准安全模式(日常推荐,弹卡片审批)"
+ echo -e "${gl_kjlan}2.${gl_bai} 切换为开发增强模式(允许智能体申请提权)"
+ echo -e "${gl_kjlan}3.${gl_bai} 切换为完全开放模式(${gl_hong}高风险!彻底解除所有宿主机拦截${gl_bai})"
+ echo -e "${gl_kjlan}4.${gl_bai} 恢复官方默认沙盒防御策略"
+ echo -e "${gl_kjlan}5.${gl_bai} 运行底层安全审计与自动修复"
+ echo -e "${gl_kjlan}6.${gl_bai} 管理 Exec 命令白名单"
+ echo -e "${gl_kjlan}0.${gl_bai} 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " perm_choice
+ case "$perm_choice" in
+ 1)
+ echo "准备应用:标准安全模式"
+ read -e -p "输入 yes 确认: " confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_standard; else echo "已取消"; fi
+ break_end
+ ;;
+ 2)
+ echo "准备应用:开发增强模式"
+ read -e -p "输入 yes 确认: " confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_developer; else echo "已取消"; fi
+ break_end
+ ;;
+ 3)
+ echo -e "${gl_hong}⚠️ 完全开放模式会彻底瓦解 exec 审批并自动放行高危代码。${gl_bai}"
+ read -e -p "输入 FULL 确认继续: " confirm
+ if [ "$confirm" = "FULL" ]; then openclaw_permission_apply_full; else echo "已取消"; fi
+ break_end
+ ;;
+ 4)
+ echo "将清除所有定制覆盖,恢复 OpenClaw 刚安装时的严格沙盒状态。"
+ read -e -p "输入 yes 确认: " confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_restore_official_defaults; else echo "已取消"; fi
+ break_end
+ ;;
+ 5)
+ openclaw_permission_run_audit
+ ;;
+ 6)
+ openclaw_permission_manage_allowlist
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_multiagent_config_file() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ echo "$config_file"
+ return 0
+ fi
+ openclaw config file 2>/dev/null | tail -n 1
+ }
+
+ openclaw_multiagent_default_agent() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+value="(unset)"
+try:
+ with open(path) as f:
+ data=json.load(f)
+ defaults=data.get("agents",{}).get("defaults",{}) if isinstance(data,dict) else {}
+ value=defaults.get("agent") or None
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and (item.get("isDefault") or item.get("default")):
+ value=item.get("id")
+ break
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and item.get("id"):
+ value=item.get("id")
+ break
+except Exception:
+ value="(unset)"
+print(value or "(unset)")
+PY
+ return 0
+ fi
+ local value
+ value=$(openclaw config get agents.defaults.agent 2>&1 | head -n 1)
+ if [ -z "$value" ] || echo "$value" | grep -qi "config path not found"; then
+ value=$(openclaw agents list --json 2>/dev/null | python3 -c 'import json,sys
+try:
+ data=json.load(sys.stdin)
+ print(next((x.get("id","(unset)") for x in data if x.get("isDefault")), "(unset)"))
+except Exception:
+ print("(unset)")' 2>/dev/null)
+ fi
+ [ -z "$value" ] && value="(unset)"
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ }
+
+ openclaw_multiagent_require_openclaw() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ 未检测到 openclaw 命令,请先安装或初始化 OpenClaw。"
+ return 1
+ fi
+ return 0
+ }
+
+ openclaw_multiagent_agents_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents list --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:从配置文件读取
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ agents=data.get("agents",{}).get("list",[])
+ if not isinstance(agents,list):
+ agents=[]
+ print(json.dumps(agents, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_bindings_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents bindings --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:从配置文件读取
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ bindings=data.get("agents",{}).get("bindings",[])
+ if not isinstance(bindings,list):
+ bindings=[]
+ results=[]
+ for item in bindings:
+ if not isinstance(item,dict):
+ continue
+ results.append({"agentId": item.get("agentId") or item.get("agent") or "?", "description": item.get("description") or "-"})
+ print(json.dumps(results, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_sessions_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw sessions --json 2>/dev/null | grep -v '^\[')
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:从文件系统读取
+ python3 <<'PY'
+import json,os
+base=os.path.expanduser("~/.openclaw/agents")
+sessions=[]
+try:
+ agent_dirs=[d for d in os.listdir(base) if os.path.isdir(os.path.join(base,d))]
+except Exception:
+ agent_dirs=[]
+for agent_id in agent_dirs:
+ path=os.path.join(base,agent_id,"sessions","sessions.json")
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path) as f:
+ data=json.load(f)
+ except Exception:
+ continue
+ if isinstance(data,dict):
+ items=data.items()
+ elif isinstance(data,list):
+ items=[(item.get("key") or "?", item) for item in data if isinstance(item,dict)]
+ else:
+ continue
+ for key,item in items:
+ if not isinstance(item,dict):
+ continue
+ model=item.get("model") or "-"
+ sessions.append({"agentId": agent_id, "key": key, "model": model})
+print(json.dumps({"path":"(filesystem)","count":len(sessions),"sessions":sessions}, ensure_ascii=False))
+PY
+ }
+
+ openclaw_multiagent_render_status() {
+ local config_file default_agent
+ config_file=$(openclaw_multiagent_config_file)
+ default_agent=$(openclaw_multiagent_default_agent)
+ echo "配置文件: ${config_file:-$(openclaw_permission_config_file)}"
+ echo "默认智能体: $default_agent"
+ python3 -c '
+import json,sys
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+sess_obj=json.loads(sys.argv[3] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+print("已配置智能体数: %s" % len(agents))
+print("路由绑定数: %s" % len(bindings))
+print("会话总数: %s" % len(sessions))
+print("---------------------------------------")
+if not agents:
+ print("当前未配置任何多智能体。")
+else:
+ for item in agents[:8]:
+ aid = item.get("id","?")
+ identity = item.get("identityName") or item.get("name") or "-"
+ emoji = item.get("identityEmoji") or ""
+ ws = item.get("workspace") or "-"
+ model = item.get("model") or "-"
+ is_default = item.get("isDefault", False)
+ bcount = item.get("bindings", 0)
+ default_tag = " [默认]" if is_default else ""
+ print("- 智能体ID: \033[1;36m%s\033[0m%s" % (aid, default_tag))
+ print(" 身份名称: %s %s" % (identity, emoji))
+ print(" 模型: %s" % model)
+ print(" 工作目录: %s" % ws)
+ print(" 绑定数: %s" % bcount)
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)" "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_list_agents() {
+ send_stats "OpenClaw多智能体-列出Agent"
+ python3 -c 'import json,sys; agents=json.loads(sys.argv[1] or "[]");
+if not agents: print("暂无已配置 Agent。"); raise SystemExit(0)
+for idx,item in enumerate(agents,1):
+ print("%s. %s" % (idx, item.get("id","?"))); print(" workspace : %s" % item.get("workspace","-")); ident=(item.get("identityName") or "-") + ((" " + item.get("identityEmoji")) if item.get("identityEmoji") else ""); print(" identity : %s" % ident.strip()); print(" model : %s" % (item.get("model") or "-")); print(" bindings : %s" % item.get("bindings",0)); print(" default : %s" % ("yes" if item.get("isDefault") else "no"))' "$(openclaw_multiagent_agents_json)"
+ }
+
+ openclaw_multiagent_add_agent() {
+ send_stats "OpenClaw多智能体-新增Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id workspace confirm
+ read -e -p "请输入新的 Agent ID: " agent_id
+ [ -z "$agent_id" ] && echo "已取消:Agent ID 不能为空。" && return 1
+ read -e -p "请输入 workspace 路径(默认 ~/.openclaw/workspace-${agent_id}): " workspace
+ [ -z "$workspace" ] && workspace="~/.openclaw/workspace-${agent_id}"
+ echo "将创建智能体: $agent_id"
+ echo "工作目录: $workspace"
+ read -e -p "输入 yes 确认继续: " confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents add "$agent_id" --workspace "$workspace"; then
+ echo "✅ 智能体创建成功: $agent_id"
+ local name theme
+ read -e -p "请输入智能体身份名称 (如: 代码专家): " name
+ [ -z "$name" ] && name="$agent_id"
+ read -e -p "请输入智能体性格主题 (如: 严谨、高效): " theme
+ [ -z "$theme" ] && theme="助手"
+ echo "正在配置智能体身份..."
+ openclaw agents set-identity --agent "$agent_id" --name "$name" --theme "$theme"
+ else
+ echo "❌ 智能体创建失败"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_delete_agent() {
+ send_stats "OpenClaw多智能体-删除Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id confirm
+ read -e -p "请输入要删除的 Agent ID: " agent_id
+ [ -z "$agent_id" ] && echo "已取消:Agent ID 不能为空。" && return 1
+ echo "⚠️ 删除智能体可能影响其工作目录、路由绑定与会话路由。"
+ read -e -p "输入 DELETE 确认删除 ${agent_id}: " confirm
+ [ "$confirm" = "DELETE" ] || { echo "已取消"; return 1; }
+ if openclaw agents delete "$agent_id"; then
+ echo "✅ 智能体删除成功: $agent_id"
+ else
+ echo "❌ 智能体删除失败"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_list_bindings() {
+ send_stats "OpenClaw多智能体-查看路由绑定"
+ python3 -c '
+import json,sys
+bindings=json.loads(sys.argv[1] or "[]")
+if not bindings:
+ print("暂无路由绑定。")
+ raise SystemExit(0)
+for idx,item in enumerate(bindings,1):
+ desc = item.get("description") or "-"
+ print("%s. agent=%s | %s" % (idx, item.get("agentId","?"), desc))
+' "$(openclaw_multiagent_bindings_json)"
+ }
+
+ openclaw_multiagent_add_binding() {
+ send_stats "OpenClaw多智能体-新增路由绑定"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "请输入智能体 ID: " agent_id
+ read -e -p "请输入路由绑定值(如 telegram:ops / discord:guild-a): " bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "已取消:参数不能为空。" && return 1
+ echo "将绑定智能体 [$agent_id] -> [$bind_value]"
+ read -e -p "输入 yes 确认继续: " confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents bind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 路由绑定添加成功"
+ else
+ echo "❌ 路由绑定添加失败"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_remove_binding() {
+ send_stats "OpenClaw多智能体-移除路由绑定"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "请输入智能体 ID: " agent_id
+ read -e -p "请输入要移除的路由绑定值: " bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "已取消:参数不能为空。" && return 1
+ echo "将移除智能体 [$agent_id] 的路由绑定 [$bind_value]"
+ read -e -p "输入 yes 确认继续: " confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents unbind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 路由绑定移除成功"
+ else
+ echo "❌ 路由绑定移除失败"
+ return 1
+ fi
+ }
+
+
+ openclaw_multiagent_show_sessions() {
+ send_stats "OpenClaw多智能体-会话概况"
+ python3 -c '
+import json,sys
+sess_obj=json.loads(sys.argv[1] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+if not sessions:
+ print("暂无 session 数据。")
+ raise SystemExit(0)
+by_agent={}
+for item in sessions:
+ aid = item.get("agentId","?")
+ by_agent[aid] = by_agent.get(aid, 0) + 1
+print("会话汇总:")
+for agent_id,count in sorted(by_agent.items()):
+ print("- %s: %s" % (agent_id, count))
+print("---------------------------------------")
+for item in sessions[:10]:
+ key = item.get("key","-")
+ model = item.get("model") or "-"
+ aid = item.get("agentId","?")
+ tokens = ""
+ it = item.get("inputTokens")
+ ot = item.get("outputTokens")
+ if it is not None:
+ tokens = " | in=%s out=%s" % (it, ot or 0)
+ print("%s | %s | %s%s" % (aid, key, model, tokens))
+' "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_health_check() {
+ send_stats "OpenClaw多智能体-健康检查"
+ openclaw_multiagent_require_openclaw || return 1
+ local config_file
+ config_file=$(openclaw_multiagent_config_file)
+ echo "检查配置文件: ${config_file:-$(openclaw_permission_config_file)}"
+ openclaw config validate || echo "⚠️ 配置校验未通过,请检查上方输出。"
+ python3 -c '
+import json,sys,os
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+print("---------------------------------------")
+if not agents:
+ print("⚠️ 未发现已配置智能体。")
+else:
+ for item in agents:
+ ws = item.get("workspace") or ""
+ aid = item.get("id","?")
+ if ws and os.path.isdir(os.path.expanduser(ws)):
+ state = "OK"
+ elif aid == "main":
+ state = "OK"
+ else:
+ state = "MISSING"
+ model = item.get("model") or "-"
+ bcount = item.get("bindings", 0)
+ print("agent=%s workspace=%s model=%s bindings=%s [%s]" % (aid, ws or "-", model, bcount, state))
+print("路由绑定数=%s" % len(bindings))
+print("✅ 多智能体健康检查完成")
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)"
+ echo ""
+ echo "运行安全审计..."
+ openclaw security audit 2>/dev/null || echo "⚠️ 安全审计命令不可用"
+ }
+
+
+ openclaw_multiagent_set_identity() {
+ openclaw_multiagent_require_openclaw || return 1
+ openclaw_multiagent_list_agents
+ read -e -p "输入要修改身份的智能体ID: " agent_id
+ [ -z "$agent_id" ] && { echo "ID 不能为空"; return 1; }
+ echo "修改选项(留空跳过):"
+ read -e -p " 新名称: " new_name
+ read -e -p " 新 Emoji: " new_emoji
+ local cmd="openclaw agents set-identity --agent $agent_id"
+ [ -n "$new_name" ] && cmd="$cmd --name $new_name"
+ [ -n "$new_emoji" ] && cmd="$cmd --emoji $new_emoji"
+ echo "也可以从 IDENTITY.md 自动读取身份信息。"
+ read -e -p "是否从 IDENTITY.md 读取?(y/n): " from_id
+ if [ "$from_id" = "y" ]; then
+ cmd="openclaw agents set-identity --agent $agent_id --from-identity"
+ fi
+ eval "$cmd"
+ }
+
+ openclaw_multiagent_cleanup_sessions() {
+ openclaw_multiagent_require_openclaw || return 1
+ echo "即将清理过期/冗余会话数据..."
+ read -e -p "输入 yes 确认: " confirm
+ [ "$confirm" != "yes" ] && { echo "已取消"; return 0; }
+ openclaw sessions cleanup
+ }
+
+ openclaw_multiagent_menu() {
+ send_stats "OpenClaw多智能体管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 多智能体管理"
+ echo "======================================="
+ openclaw_multiagent_render_status
+ echo "---------------------------------------"
+ echo "1. 新增智能体"
+ echo "2. 删除智能体"
+ echo "3. 查看路由绑定"
+ echo "4. 新增路由绑定"
+ echo "5. 移除路由绑定"
+ echo "6. 查看会话概况"
+ echo "7. 运行多智能体健康检查"
+ echo "8. 修改智能体身份(名称/Emoji)"
+ echo "9. 清理过期会话"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " multi_choice
+ case "$multi_choice" in
+ 1) openclaw_multiagent_add_agent; break_end ;;
+ 2) openclaw_multiagent_delete_agent; break_end ;;
+ 3) openclaw_multiagent_list_bindings; break_end ;;
+ 4) openclaw_multiagent_add_binding; break_end ;;
+ 5) openclaw_multiagent_remove_binding; break_end ;;
+ 6) openclaw_multiagent_show_sessions; break_end ;;
+ 7) openclaw_multiagent_health_check; break_end ;;
+ 8) openclaw_multiagent_set_identity; break_end ;;
+ 9) openclaw_multiagent_cleanup_sessions; break_end ;;
+ 0) return 0 ;;
+ *) echo "无效的选择,请重试。"; sleep 1 ;;
+ esac
+ done
+ }
+
+
+openclaw_backup_restore_menu() {
+
+ send_stats "OpenClaw备份与还原"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 备份与还原"
+ echo "======================================="
+ openclaw_backup_render_file_list
+ echo "---------------------------------------"
+ echo "1. 备份记忆全量"
+ echo "2. 还原记忆全量"
+ echo "3. 备份 OpenClaw 项目(默认安全模式)"
+ echo "4. 还原 OpenClaw 项目(高级/高风险)"
+ echo "5. 删除备份文件"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " backup_choice
+
+ case "$backup_choice" in
+ 1) openclaw_memory_backup_export ;;
+ 2) openclaw_memory_backup_import ;;
+ 3) openclaw_project_backup_export ;;
+ 4) openclaw_project_backup_import ;;
+ 5) openclaw_backup_delete_file ;;
+ 0) return 0 ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ update_moltbot() {
+ echo "更新 OpenClaw..."
+ send_stats "更新 OpenClaw..."
+ install_node_and_tools
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+ npm install -g openclaw@latest
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ start_gateway
+ hash -r
+ add_app_id
+ echo "更新完成"
+ break_end
+ }
+
+
+ uninstall_moltbot() {
+ echo "卸载 OpenClaw..."
+ send_stats "卸载 OpenClaw..."
+ openclaw uninstall
+ npm uninstall -g openclaw
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ rm -rf "$HOME/.openclaw"
+ [ "$HOME" != "/root" ] && [ -d /root/.openclaw ] && echo "⚠️ 检测到 root 目录下仍存在 /root/.openclaw,如需清理请手动处理"
+ hash -r
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "卸载完成"
+ break_end
+ }
+
+ nano_openclaw_json() {
+ send_stats "编辑 OpenClaw 配置文件"
+ install nano
+ nano "$(openclaw_get_config_file)"
+ start_gateway
+ }
+
+
+
+
+
+
+ openclaw_find_webui_domain() {
+ local conf domain_list
+
+ domain_list=$(
+ grep -R "18789" /home/web/conf.d/*.conf 2>/dev/null \
+ | awk -F: '{print $1}' \
+ | sort -u \
+ | while read conf; do
+ basename "$conf" .conf
+ done
+ )
+
+ if [ -n "$domain_list" ]; then
+ echo "$domain_list"
+ fi
+ }
+
+
+
+ openclaw_show_webui_addr() {
+ local local_ip token domains
+
+ echo "=================================="
+ echo "OpenClaw WebUI 访问地址"
+ local_ip="127.0.0.1"
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+ echo
+ echo "本机地址:"
+ echo "http://${local_ip}:18789/#token=${token}"
+
+ domains=$(openclaw_find_webui_domain)
+ if [ -n "$domains" ]; then
+ echo "域名地址:"
+ echo "$domains" | while read d; do
+ echo "https://${d}/#token=${token}"
+ done
+ fi
+
+ echo "=================================="
+ }
+
+
+
+ # 添加域名(调用你给的函数)
+ openclaw_domain_webui() {
+ add_yuming
+ ldnmp_Proxy ${yuming} 127.0.0.1 18789
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+
+ clear
+ echo "访问地址:"
+ echo "https://${yuming}/#token=$token"
+ echo "先访问URL触发设备ID,然后回车下一步进行配对。"
+ read
+ echo -e "${gl_kjlan}正在加载设备列表……${gl_bai}"
+ # 自动添加域名到 allowedOrigins
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ new_origin="https://${yuming}"
+ # 使用 jq 安全修改 JSON,确保结构存在且不重复添加域名
+ if command -v jq >/dev/null 2>&1; then
+ tmp_json=$(mktemp)
+ jq 'if .gateway.controlUi == null then .gateway.controlUi = {"allowedOrigins": ["http://127.0.0.1"]} else . end | if (.gateway.controlUi.allowedOrigins | contains([$origin]) | not) then .gateway.controlUi.allowedOrigins += [$origin] else . end' --arg origin "$new_origin" "$config_file" > "$tmp_json" && mv "$tmp_json" "$config_file"
+ echo -e "${gl_kjlan}已将域名 ${yuming} 加入 allowedOrigins 配置${gl_bai}"
+ openclaw gateway restart >/dev/null 2>&1
+ fi
+ fi
+
+ openclaw devices list
+
+ read -e -p "请输入 Request_Key: " Request_Key
+
+ [ -z "$Request_Key" ] && {
+ echo "Request_Key 不能为空"
+ return 1
+ }
+
+ openclaw devices approve "$Request_Key"
+
+ }
+
+ # 删除域名
+ openclaw_remove_domain() {
+ echo "域名格式 example.com 不带https://"
+ web_del
+ }
+
+ # 主菜单
+ openclaw_webui_menu() {
+
+ send_stats "WebUI访问与设置"
+ while true; do
+ clear
+ openclaw_show_webui_addr
+ echo
+ echo "1. 添加域名访问"
+ echo "2. 删除域名访问"
+ echo "0. 退出"
+ echo
+ read -e -p "请选择: " choice
+
+ case "$choice" in
+ 1)
+ openclaw_domain_webui
+ echo
+ read -p "按回车返回菜单..."
+ ;;
+ 2)
+ openclaw_remove_domain
+ read -p "按回车返回菜单..."
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "无效选项"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ # 主循环
+ while true; do
+ show_menu
+ read choice
+ case $choice in
+ 1) install_moltbot ;;
+ 2) start_bot ;;
+ 3) stop_bot ;;
+ 4) view_logs ;;
+ 5) change_model ;;
+ 6) openclaw_api_manage_menu ;;
+ 7) change_tg_bot_code ;;
+ 8) install_plugin ;;
+ 9) install_skill ;;
+ 10) nano_openclaw_json ;;
+ 11) send_stats "初始化配置向导"
+ openclaw onboard --install-daemon
+ break_end
+ ;;
+ 12) send_stats "健康检测与修复"
+ openclaw doctor --fix
+ break_end
+ ;;
+ 13) openclaw_webui_menu ;;
+ 14) send_stats "TUI命令行对话"
+ openclaw tui
+ break_end
+ ;;
+ 15) openclaw_memory_menu ;;
+ 16) openclaw_permission_menu ;;
+ 17) openclaw_multiagent_menu ;;
+ 18) openclaw_backup_restore_menu ;;
+ 19) update_moltbot ;;
+ 20) uninstall_moltbot ;;
+ *) break ;;
+ esac
+ done
+
+}
+
+
+
+
+linux_panel() {
+
+local sub_choice="$1"
+
+clear
+cd ~
+install git
+echo -e "${gl_kjlan}正在更新应用列表请稍等……${gl_bai}"
+if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+fi
+
+while true; do
+
+ if [ -z "$sub_choice" ]; then
+ clear
+ echo -e "应用市场"
+ echo -e "${gl_kjlan}-------------------------"
+
+ local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+
+ # 用循环设置颜色
+ for i in {1..150}; do
+ if echo "$app_numbers" | grep -q "^$i$"; then
+ declare "color$i=${gl_lv}"
+ else
+ declare "color$i=${gl_bai}"
+ fi
+ done
+
+ echo -e "${gl_kjlan}1. ${color1}宝塔面板官方版 ${gl_kjlan}2. ${color2}aaPanel宝塔国际版"
+ echo -e "${gl_kjlan}3. ${color3}1Panel新一代管理面板 ${gl_kjlan}4. ${color4}NginxProxyManager可视化面板"
+ echo -e "${gl_kjlan}5. ${color5}OpenList多存储文件列表程序 ${gl_kjlan}6. ${color6}Ubuntu远程桌面网页版"
+ echo -e "${gl_kjlan}7. ${color7}哪吒探针VPS监控面板 ${gl_kjlan}8. ${color8}QB离线BT磁力下载面板"
+ echo -e "${gl_kjlan}9. ${color9}Poste.io邮件服务器程序 ${gl_kjlan}10. ${color10}RocketChat多人在线聊天系统"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}11. ${color11}禅道项目管理软件 ${gl_kjlan}12. ${color12}青龙面板定时任务管理平台"
+ echo -e "${gl_kjlan}13. ${color13}Cloudreve网盘 ${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}简单图床图片管理程序"
+ echo -e "${gl_kjlan}15. ${color15}emby多媒体管理系统 ${gl_kjlan}16. ${color16}Speedtest测速面板"
+ echo -e "${gl_kjlan}17. ${color17}AdGuardHome去广告软件 ${gl_kjlan}18. ${color18}onlyoffice在线办公OFFICE"
+ echo -e "${gl_kjlan}19. ${color19}雷池WAF防火墙面板 ${gl_kjlan}20. ${color20}portainer容器管理面板"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}21. ${color21}VScode网页版 ${gl_kjlan}22. ${color22}UptimeKuma监控工具"
+ echo -e "${gl_kjlan}23. ${color23}Memos网页备忘录 ${gl_kjlan}24. ${color24}Webtop远程桌面网页版 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}25. ${color25}Nextcloud网盘 ${gl_kjlan}26. ${color26}QD-Today定时任务管理框架"
+ echo -e "${gl_kjlan}27. ${color27}Dockge容器堆栈管理面板 ${gl_kjlan}28. ${color28}LibreSpeed测速工具"
+ echo -e "${gl_kjlan}29. ${color29}searxng聚合搜索站 ${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism私有相册系统"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}31. ${color31}StirlingPDF工具大全 ${gl_kjlan}32. ${color32}drawio免费的在线图表软件 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${color33}Sun-Panel导航面板 ${gl_kjlan}34. ${color34}Pingvin-Share文件分享平台"
+ echo -e "${gl_kjlan}35. ${color35}极简朋友圈 ${gl_kjlan}36. ${color36}LobeChatAI聊天聚合网站"
+ echo -e "${gl_kjlan}37. ${color37}MyIP工具箱 ${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}小雅alist全家桶"
+ echo -e "${gl_kjlan}39. ${color39}Bililive直播录制工具 ${gl_kjlan}40. ${color40}webssh网页版SSH连接工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}41. ${color41}耗子管理面板 ${gl_kjlan}42. ${color42}Nexterm远程连接工具"
+ echo -e "${gl_kjlan}43. ${color43}RustDesk远程桌面(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk远程桌面(中继端) ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}45. ${color45}Docker加速站 ${gl_kjlan}46. ${color46}GitHub加速站 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}47. ${color47}普罗米修斯监控 ${gl_kjlan}48. ${color48}普罗米修斯(主机监控)"
+ echo -e "${gl_kjlan}49. ${color49}普罗米修斯(容器监控) ${gl_kjlan}50. ${color50}补货监控工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}51. ${color51}PVE开小鸡面板 ${gl_kjlan}52. ${color52}DPanel容器管理面板"
+ echo -e "${gl_kjlan}53. ${color53}llama3聊天AI大模型 ${gl_kjlan}54. ${color54}AMH主机建站管理面板"
+ echo -e "${gl_kjlan}55. ${color55}FRP内网穿透(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP内网穿透(客户端) ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}57. ${color57}Deepseek聊天AI大模型 ${gl_kjlan}58. ${color58}Dify大模型知识库 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}59. ${color59}NewAPI大模型资产管理 ${gl_kjlan}60. ${color60}JumpServer开源堡垒机"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}61. ${color61}在线翻译服务器 ${gl_kjlan}62. ${color62}RAGFlow大模型知识库"
+ echo -e "${gl_kjlan}63. ${color63}OpenWebUI自托管AI平台 ${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools工具箱"
+ echo -e "${gl_kjlan}65. ${color65}n8n自动化工作流平台 ${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp视频下载工具"
+ echo -e "${gl_kjlan}67. ${color67}ddns-go动态DNS管理工具 ${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL证书管理平台"
+ echo -e "${gl_kjlan}69. ${color69}SFTPGo文件传输工具 ${gl_kjlan}70. ${color70}AstrBot聊天机器人框架"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}71. ${color71}Navidrome私有音乐服务器 ${gl_kjlan}72. ${color72}bitwarden密码管理器 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}73. ${color73}LibreTV私有影视 ${gl_kjlan}74. ${color74}MoonTV私有影视"
+ echo -e "${gl_kjlan}75. ${color75}Melody音乐精灵 ${gl_kjlan}76. ${color76}在线DOS老游戏"
+ echo -e "${gl_kjlan}77. ${color77}迅雷离线下载工具 ${gl_kjlan}78. ${color78}PandaWiki智能文档管理系统"
+ echo -e "${gl_kjlan}79. ${color79}Beszel服务器监控 ${gl_kjlan}80. ${color80}linkwarden书签管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}81. ${color81}JitsiMeet视频会议 ${gl_kjlan}82. ${color82}gpt-load高性能AI透明代理"
+ echo -e "${gl_kjlan}83. ${color83}komari服务器监控工具 ${gl_kjlan}84. ${color84}Wallos个人财务管理工具"
+ echo -e "${gl_kjlan}85. ${color85}immich图片视频管理器 ${gl_kjlan}86. ${color86}jellyfin媒体管理系统"
+ echo -e "${gl_kjlan}87. ${color87}SyncTV一起看片神器 ${gl_kjlan}88. ${color88}Owncast自托管直播平台"
+ echo -e "${gl_kjlan}89. ${color89}FileCodeBox文件快递 ${gl_kjlan}90. ${color90}matrix去中心化聊天协议"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}91. ${color91}gitea私有代码仓库 ${gl_kjlan}92. ${color92}FileBrowser文件管理器"
+ echo -e "${gl_kjlan}93. ${color93}Dufs极简静态文件服务器 ${gl_kjlan}94. ${color94}Gopeed高速下载工具"
+ echo -e "${gl_kjlan}95. ${color95}paperless文档管理平台 ${gl_kjlan}96. ${color96}2FAuth自托管二步验证器"
+ echo -e "${gl_kjlan}97. ${color97}WireGuard组网(服务端) ${gl_kjlan}98. ${color98}WireGuard组网(客户端)"
+ echo -e "${gl_kjlan}99. ${color99}DSM群晖虚拟机 ${gl_kjlan}100. ${color100}Syncthing点对点文件同步工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}101. ${color101}AI视频生成工具 ${gl_kjlan}102. ${color102}VoceChat多人在线聊天系统"
+ echo -e "${gl_kjlan}103. ${color103}Umami网站统计工具 ${gl_kjlan}104. ${color104}Stream四层代理转发工具"
+ echo -e "${gl_kjlan}105. ${color105}思源笔记 ${gl_kjlan}106. ${color106}Drawnix开源白板工具"
+ echo -e "${gl_kjlan}107. ${color107}PanSou网盘搜索 ${gl_kjlan}108. ${color108}LangBot聊天机器人"
+ echo -e "${gl_kjlan}109. ${color109}ZFile在线网盘 ${gl_kjlan}110. ${color110}Karakeep书签管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}111. ${color111}多格式文件转换工具 ${gl_kjlan}112. ${color112}Lucky大内网穿透工具"
+ echo -e "${gl_kjlan}113. ${color113}Firefox浏览器 ${gl_kjlan}114. ${color114}OpenClaw机器人管理工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}115. ${color115}Hermes机器人管理工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}第三方应用列表"
+ echo -e "${gl_kjlan}想要让你的应用出现在这里?查看开发者指南: ${gl_huang}https://dev.kejilion.sh/${gl_bai}"
+
+ for f in "$HOME"/apps/*.conf; do
+ [ -e "$f" ] || continue
+ local base_name=$(basename "$f" .conf)
+ # 获取应用描述
+ local app_text=$(grep "app_text=" "$f" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
+
+ # 检查安装状态 (匹配 appno.txt 中的 ID)
+ # 这里假设 appno.txt 中记录的是 base_name (即文件名)
+ if echo "$app_numbers" | grep -q "^$base_name$"; then
+ # 如果已安装:显示 base_name - 描述 [已安装] (绿色)
+ echo -e "${gl_kjlan}$base_name${gl_bai} - ${gl_lv}$app_text [已安装]${gl_bai}"
+ else
+ # 如果未安装:正常显示
+ echo -e "${gl_kjlan}$base_name${gl_bai} - $app_text"
+ fi
+ done
+
+
+
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}b. ${gl_bai}备份全部应用数据 ${gl_kjlan}r. ${gl_bai}还原全部应用数据"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "请输入你的选择: " sub_choice
+ fi
+
+ case $sub_choice in
+ 1|bt|baota)
+ local app_id="1"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="宝塔面板"
+ local panelurl="https://www.bt.cn/new/index.html"
+
+ panel_app_install() {
+ if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+
+
+ ;;
+ 2|aapanel)
+
+
+ local app_id="2"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="aapanel"
+ local panelurl="https://www.aapanel.com/new/index.html"
+
+ panel_app_install() {
+ URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+ ;;
+ 3|1p|1panel)
+
+ local app_id="3"
+ local lujing="command -v 1pctl"
+ local panelname="1Panel"
+ local panelurl="https://1panel.cn/"
+
+ panel_app_install() {
+ install bash
+ bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
+ }
+
+ panel_app_manage() {
+ 1pctl user-info
+ 1pctl update password
+ }
+
+ panel_app_uninstall() {
+ 1pctl uninstall
+ }
+
+ install_panel
+
+ ;;
+ 4|npm)
+
+ local app_id="4"
+ local docker_name="npm"
+ local docker_img="jc21/nginx-proxy-manager:latest"
+ local docker_port=81
+
+ docker_rum() {
+
+ docker run -d \
+ --name=$docker_name \
+ -p ${docker_port}:81 \
+ -p 80:80 \
+ -p 443:443 \
+ -v /home/docker/npm/data:/data \
+ -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
+ --restart=always \
+ $docker_img
+
+
+ }
+
+ local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
+ local docker_url="官网介绍: https://nginxproxymanager.com/"
+ local docker_use="echo \"初始用户名: admin@example.com\""
+ local docker_passwd="echo \"初始密码: changeme\""
+ local app_size="1"
+
+ docker_app
+
+ ;;
+
+ 5|openlist)
+
+ local app_id="5"
+ local docker_name="openlist"
+ local docker_img="openlistteam/openlist:latest-aria2"
+ local docker_port=5244
+
+ docker_rum() {
+
+ mkdir -p /home/docker/openlist
+ chmod -R 777 /home/docker/openlist
+
+ docker run -d \
+ --restart=always \
+ -v /home/docker/openlist:/opt/openlist/data \
+ -p ${docker_port}:5244 \
+ -e PUID=0 \
+ -e PGID=0 \
+ -e UMASK=022 \
+ --name="openlist" \
+ openlistteam/openlist:latest-aria2
+
+ }
+
+
+ local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
+ local docker_url="官网介绍: ${gh_https_url}github.com/OpenListTeam/OpenList"
+ local docker_use="docker exec openlist ./openlist admin random"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 6|webtop-ubuntu)
+
+ local app_id="6"
+ local docker_name="webtop-ubuntu"
+ local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
+ local docker_port=3006
+
+ docker_rum() {
+
+ read -e -p "设置登录用户名: " admin
+ read -e -p "设置登录用户密码: " admin_password
+ docker run -d \
+ --name=webtop-ubuntu \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:ubuntu-kde
+
+
+ }
+
+
+ local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
+ local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+ 7|nezha)
+ clear
+ send_stats "搭建哪吒"
+
+ local app_id="7"
+ local docker_name="nezha-dashboard"
+ local docker_port=8008
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+ clear
+ echo -e "哪吒监控 $check_docker $update_status"
+ echo "开源、轻量、易用的服务器监控与运维工具"
+ echo "官网搭建文档: https://nezha.wiki/guide/dashboard.html"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 使用"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+
+ case $choice in
+ 1)
+ check_disk_space 1
+ install unzip jq
+ install_docker
+ curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+ ;;
+
+ 8|qb|QB)
+
+ local app_id="8"
+ local docker_name="qbittorrent"
+ local docker_img="lscr.io/linuxserver/qbittorrent:latest"
+ local docker_port=8081
+
+ docker_rum() {
+
+ docker run -d \
+ --name=qbittorrent \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e WEBUI_PORT=${docker_port} \
+ -e TORRENTING_PORT=56881 \
+ -p ${docker_port}:${docker_port} \
+ -p 56881:56881 \
+ -p 56881:56881/udp \
+ -v /home/docker/qbittorrent/config:/config \
+ -v /home/docker/qbittorrent/downloads:/downloads \
+ --restart=always \
+ lscr.io/linuxserver/qbittorrent:latest
+
+ }
+
+ local docker_describe="qbittorrent离线BT磁力下载服务"
+ local docker_url="官网介绍: https://hub.docker.com/r/linuxserver/qbittorrent"
+ local docker_use="sleep 3"
+ local docker_passwd="docker logs qbittorrent"
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 9|mail)
+ send_stats "搭建邮局"
+ clear
+ install telnet
+ local app_id="9"
+ local docker_name=“mailserver”
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+
+ clear
+ echo -e "邮局服务 $check_docker $update_status"
+ echo "poste.io 是一个开源的邮件服务器解决方案,"
+ echo "视频介绍: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+
+ echo ""
+ echo "端口检测"
+ port=25
+ timeout=3
+ if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
+ echo -e "${gl_lv}端口 $port 当前可用${gl_bai}"
+ else
+ echo -e "${gl_hong}端口 $port 当前不可用${gl_bai}"
+ fi
+ echo ""
+
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ yuming=$(cat /home/docker/mail.txt)
+ echo "访问地址: "
+ echo "https://$yuming"
+ fi
+
+ echo "------------------------"
+ echo "1. 安装 2. 更新 3. 卸载"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+
+ case $choice in
+ 1)
+ setup_docker_dir
+ check_disk_space 2 /home/docker
+ read -e -p "请设置邮箱域名 例如 mail.yuming.com : " yuming
+ mkdir -p /home/docker
+ echo "$yuming" > /home/docker/mail.txt
+ echo "------------------------"
+ ip_address
+ echo "先解析这些DNS记录"
+ echo "A mail $ipv4_address"
+ echo "CNAME imap $yuming"
+ echo "CNAME pop $yuming"
+ echo "CNAME smtp $yuming"
+ echo "MX @ $yuming"
+ echo "TXT @ v=spf1 mx ~all"
+ echo "TXT ? ?"
+ echo ""
+ echo "------------------------"
+ echo "按任意键继续..."
+ read -n 1 -s -r -p ""
+
+ install jq
+ install_docker
+
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.io
+
+
+ add_app_id
+
+ clear
+ echo "poste.io已经安装完成"
+ echo "------------------------"
+ echo "您可以使用以下地址访问poste.io:"
+ echo "https://$yuming"
+ echo ""
+
+ ;;
+
+ 2)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.i
+ yuming=$(cat /home/docker/mail.txt)
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.i
+
+
+ add_app_id
+
+ clear
+ echo "poste.io已经安装完成"
+ echo "------------------------"
+ echo "您可以使用以下地址访问poste.io:"
+ echo "https://$yuming"
+ echo ""
+ ;;
+ 3)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.io
+ rm /home/docker/mail.txt
+ rm -rf /home/docker/mail
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "应用已卸载"
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 10|rocketchat)
+
+ local app_id="10"
+ local app_name="Rocket.Chat聊天系统"
+ local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
+ local app_url="官方介绍: https://www.rocket.chat/"
+ local docker_name="rocketchat"
+ local docker_port="3897"
+ local app_size="2"
+
+ docker_app_install() {
+ docker run --name db -d --restart=always \
+ -v /home/docker/mongo/dump:/dump \
+ mongo:latest --replSet rs5 --oplogSize 256
+ sleep 1
+ docker exec db mongosh --eval "printjson(rs.initiate())"
+ sleep 5
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+
+ clear
+ ip_address
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat:latest
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ clear
+ ip_address
+ echo "rocket.chat已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_uninstall() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat
+ docker rm -f db
+ docker rmi -f mongo:latest
+ rm -rf /home/docker/mongo
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 11|zentao)
+ local app_id="11"
+ local docker_name="zentao-server"
+ local docker_img="idoop/zentao:latest"
+ local docker_port=82
+
+
+ docker_rum() {
+
+
+ docker run -d -p ${docker_port}:80 \
+ -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
+ -e BIND_ADDRESS="false" \
+ -v /home/docker/zentao-server/:/opt/zbox/ \
+ --add-host smtp.exmail.qq.com:163.177.90.125 \
+ --name zentao-server \
+ --restart=always \
+ idoop/zentao:latest
+
+
+ }
+
+ local docker_describe="禅道是通用的项目管理软件"
+ local docker_url="官网介绍: https://www.zentao.net/"
+ local docker_use="echo \"初始用户名: admin\""
+ local docker_passwd="echo \"初始密码: 123456\""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 12|qinglong)
+ local app_id="12"
+ local docker_name="qinglong"
+ local docker_img="whyour/qinglong:latest"
+ local docker_port=5700
+
+ docker_rum() {
+
+
+ docker run -d \
+ -v /home/docker/qinglong/data:/ql/data \
+ -p ${docker_port}:5700 \
+ --name qinglong \
+ --hostname qinglong \
+ --restart=always \
+ whyour/qinglong:latest
+
+
+ }
+
+ local docker_describe="青龙面板是一个定时任务管理平台"
+ local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 13|cloudreve)
+
+ local app_id="13"
+ local app_name="cloudreve网盘"
+ local app_text="cloudreve是一个支持多家云存储的网盘系统"
+ local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
+ local docker_name="cloudreve"
+ local docker_port="5212"
+ local app_size="2"
+
+ docker_app_install() {
+ cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
+ curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
+ sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
+ cd /home/docker/cloud/
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ cd /home/docker/cloud/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ rm -rf /home/docker/cloud
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+ 14|easyimage)
+ local app_id="14"
+ local docker_name="easyimage"
+ local docker_img="ddsderek/easyimage:latest"
+ local docker_port=8014
+ docker_rum() {
+
+ docker run -d \
+ --name easyimage \
+ -p ${docker_port}:80 \
+ -e TZ=Asia/Shanghai \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -v /home/docker/easyimage/config:/app/web/config \
+ -v /home/docker/easyimage/i:/app/web/i \
+ --restart=always \
+ ddsderek/easyimage:latest
+
+ }
+
+ local docker_describe="简单图床是一个简单的图床程序"
+ local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 15|emby)
+ local app_id="15"
+ local docker_name="emby"
+ local docker_img="linuxserver/emby:latest"
+ local docker_port=8015
+
+ docker_rum() {
+
+ docker run -d --name=emby --restart=always \
+ -v /home/docker/emby/config:/config \
+ -v /home/docker/emby/share1:/mnt/share1 \
+ -v /home/docker/emby/share2:/mnt/share2 \
+ -v /mnt/notify:/mnt/notify \
+ -p ${docker_port}:8096 \
+ -e UID=1000 -e GID=100 -e GIDLIST=100 \
+ linuxserver/emby:latest
+
+ }
+
+
+ local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
+ local docker_url="官网介绍: https://emby.media/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 16|looking)
+ local app_id="16"
+ local docker_name="looking-glass"
+ local docker_img="wikihostinc/looking-glass-server"
+ local docker_port=8016
+
+
+ docker_rum() {
+
+ docker run -d --name looking-glass --restart=always -p ${docker_port}:80 wikihostinc/looking-glass-server
+
+ }
+
+ local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
+ local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 17|adguardhome)
+
+ local app_id="17"
+ local docker_name="adguardhome"
+ local docker_img="adguard/adguardhome"
+ local docker_port=8017
+
+ docker_rum() {
+
+ docker run -d \
+ --name adguardhome \
+ -v /home/docker/adguardhome/work:/opt/adguardhome/work \
+ -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
+ -p 53:53/tcp \
+ -p 53:53/udp \
+ -p ${docker_port}:3000/tcp \
+ --restart=always \
+ adguard/adguardhome
+
+
+ }
+
+
+ local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
+ local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 18|onlyoffice)
+
+ local app_id="18"
+ local docker_name="onlyoffice"
+ local docker_img="onlyoffice/documentserver"
+ local docker_port=8018
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:80 \
+ --restart=always \
+ --name onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
+ onlyoffice/documentserver
+
+
+ }
+
+ local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
+ local docker_url="官网介绍: https://www.onlyoffice.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 19|safeline)
+ send_stats "搭建雷池"
+
+ local app_id="19"
+ local docker_name=safeline-mgt
+ local docker_port=9443
+ while true; do
+ check_docker_app
+ clear
+ echo -e "雷池服务 $check_docker"
+ echo "雷池是长亭科技开发的WAF站点防火墙程序面板,可以反代站点进行自动化防御"
+ echo "视频介绍: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ check_docker_app_ip
+ fi
+ echo ""
+
+ echo "------------------------"
+ echo "1. 安装 2. 更新 3. 重置密码 4. 卸载"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+
+ case $choice in
+ 1)
+ install_docker
+ check_disk_space 5
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
+
+ add_app_id
+ clear
+ echo "雷池WAF面板已经安装完成"
+ check_docker_app_ip
+ docker exec safeline-mgt resetadmin
+
+ ;;
+
+ 2)
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
+ docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
+ echo ""
+
+ add_app_id
+ clear
+ echo "雷池WAF面板已经更新完成"
+ check_docker_app_ip
+ ;;
+ 3)
+ docker exec safeline-mgt resetadmin
+ ;;
+ 4)
+ cd /data/safeline
+ docker compose down --rmi all
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "如果你是默认安装目录那现在项目已经卸载。如果你是自定义安装目录你需要到安装目录下自行执行:"
+ echo "docker compose down && docker compose down --rmi all"
+ ;;
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 20|portainer)
+ local app_id="20"
+ local docker_name="portainer"
+ local docker_img="portainer/portainer"
+ local docker_port=8020
+
+ docker_rum() {
+
+ docker run -d \
+ --name portainer \
+ -p ${docker_port}:9000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/portainer:/data \
+ --restart=always \
+ portainer/portainer
+
+ }
+
+
+ local docker_describe="portainer是一个轻量级的docker容器管理面板"
+ local docker_url="官网介绍: https://www.portainer.io/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 21|vscode)
+ local app_id="21"
+ local docker_name="vscode-web"
+ local docker_img="codercom/code-server"
+ local docker_port=8021
+
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart=always codercom/code-server
+
+ }
+
+
+ local docker_describe="VScode是一款强大的在线代码编写工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
+ local docker_use="sleep 3"
+ local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 22|uptime-kuma)
+ local app_id="22"
+ local docker_name="uptime-kuma"
+ local docker_img="louislam/uptime-kuma:latest"
+ local docker_port=8022
+
+
+ docker_rum() {
+
+ docker run -d \
+ --name=uptime-kuma \
+ -p ${docker_port}:3001 \
+ -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
+ --restart=always \
+ louislam/uptime-kuma:latest
+
+ }
+
+
+ local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 23|memos)
+ local app_id="23"
+ local docker_name="memos"
+ local docker_img="neosmemo/memos:stable"
+ local docker_port=8023
+
+ docker_rum() {
+
+ docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart=always neosmemo/memos:stable
+
+ }
+
+ local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
+ local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 24|webtop)
+ local app_id="24"
+ local docker_name="webtop"
+ local docker_img="lscr.io/linuxserver/webtop:latest"
+ local docker_port=8024
+
+ docker_rum() {
+
+ read -e -p "设置登录用户名: " admin
+ read -e -p "设置登录用户密码: " admin_password
+ docker run -d \
+ --name=webtop \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -e LC_ALL=zh_CN.UTF-8 \
+ -e DOCKER_MODS=linuxserver/mods:universal-package-install \
+ -e INSTALL_PACKAGES=font-noto-cjk \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:latest
+
+ }
+
+
+ local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
+ local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 25|nextcloud)
+ local app_id="25"
+ local docker_name="nextcloud"
+ local docker_img="nextcloud:latest"
+ local docker_port=8025
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+
+ }
+
+ local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
+ local docker_url="官网介绍: https://nextcloud.com/"
+ local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 26|qd)
+ local app_id="26"
+ local docker_name="qd"
+ local docker_img="qdtoday/qd:latest"
+ local docker_port=8026
+
+ docker_rum() {
+
+ docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+
+ }
+
+ local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
+ local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 27|dockge)
+ local app_id="27"
+ local docker_name="dockge"
+ local docker_img="louislam/dockge:latest"
+ local docker_port=8027
+
+ docker_rum() {
+
+ docker run -d --name dockge --restart=always -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+
+ }
+
+ local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
+ local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 28|speedtest)
+ local app_id="28"
+ local docker_name="speedtest"
+ local docker_img="ghcr.io/librespeed/speedtest"
+ local docker_port=8028
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 --name speedtest --restart=always ghcr.io/librespeed/speedtest
+
+ }
+
+ local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
+ local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 29|searxng)
+ local app_id="29"
+ local docker_name="searxng"
+ local docker_img="searxng/searxng"
+ local docker_port=8029
+
+ docker_rum() {
+
+ docker run -d \
+ --name searxng \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v "/home/docker/searxng:/etc/searxng" \
+ searxng/searxng
+
+ }
+
+ local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
+ local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 30|photoprism)
+ local app_id="30"
+ local docker_name="photoprism"
+ local docker_img="photoprism/photoprism:latest"
+ local docker_port=8030
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d \
+ --name photoprism \
+ --restart=always \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ -p ${docker_port}:2342 \
+ -e PHOTOPRISM_UPLOAD_NSFW="true" \
+ -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
+ -v /home/docker/photoprism/storage:/photoprism/storage \
+ -v /home/docker/photoprism/Pictures:/photoprism/originals \
+ photoprism/photoprism
+
+ }
+
+
+ local docker_describe="photoprism非常强大的私有相册系统"
+ local docker_url="官网介绍: https://www.photoprism.app/"
+ local docker_use="echo \"账号: admin 密码: $rootpasswd\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 31|s-pdf)
+ local app_id="31"
+ local docker_name="s-pdf"
+ local docker_img="frooodle/s-pdf:latest"
+ local docker_port=8031
+
+ docker_rum() {
+
+ docker run -d \
+ --name s-pdf \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
+ -v /home/docker/s-pdf/extraConfigs:/configs \
+ -v /home/docker/s-pdf/logs:/logs \
+ -e DOCKER_ENABLE_SECURITY=false \
+ frooodle/s-pdf:latest
+ }
+
+ local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
+ local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 32|drawio)
+ local app_id="32"
+ local docker_name="drawio"
+ local docker_img="jgraph/drawio"
+ local docker_port=8032
+
+ docker_rum() {
+
+ docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+
+ }
+
+
+ local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
+ local docker_url="官网介绍: https://www.drawio.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 33|sun-panel)
+ local app_id="33"
+ local docker_name="sun-panel"
+ local docker_img="hslr/sun-panel"
+ local docker_port=8033
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:3002 \
+ -v /home/docker/sun-panel/conf:/app/conf \
+ -v /home/docker/sun-panel/uploads:/app/uploads \
+ -v /home/docker/sun-panel/database:/app/database \
+ --name sun-panel \
+ hslr/sun-panel
+
+ }
+
+ local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
+ local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
+ local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 34|pingvin-share)
+ local app_id="34"
+ local docker_name="pingvin-share"
+ local docker_img="stonith404/pingvin-share"
+ local docker_port=8034
+
+ docker_rum() {
+
+ docker run -d \
+ --name pingvin-share \
+ --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/pingvin-share/data:/opt/app/backend/data \
+ stonith404/pingvin-share
+ }
+
+ local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
+ local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 35|moments)
+ local app_id="35"
+ local docker_name="moments"
+ local docker_img="kingwrcy/moments:latest"
+ local docker_port=8035
+
+ docker_rum() {
+
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/moments/data:/app/data \
+ -v /etc/localtime:/etc/localtime:ro \
+ -v /etc/timezone:/etc/timezone:ro \
+ --name moments \
+ kingwrcy/moments:latest
+ }
+
+
+ local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
+ local docker_url="官网介绍: ${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
+ local docker_use="echo \"账号: admin 密码: a123456\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+
+ 36|lobe-chat)
+ local app_id="36"
+ local docker_name="lobe-chat"
+ local docker_img="lobehub/lobe-chat:latest"
+ local docker_port=8036
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:3210 \
+ --name lobe-chat \
+ --restart=always \
+ lobehub/lobe-chat
+ }
+
+ local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
+ local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 37|myip)
+ local app_id="37"
+ local docker_name="myip"
+ local docker_img="jason5ng32/myip:latest"
+ local docker_port=8037
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+
+ }
+
+
+ local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
+ local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 38|xiaoya)
+ send_stats "小雅全家桶"
+ clear
+ install_docker
+ check_disk_space 1
+ bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
+ ;;
+
+ 39|bililive)
+
+ if [ ! -d /home/docker/bililive-go/ ]; then
+ mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
+ wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
+ fi
+
+ local app_id="39"
+ local docker_name="bililive-go"
+ local docker_img="chigusa/bililive-go"
+ local docker_port=8039
+
+ docker_rum() {
+
+ docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+
+ }
+
+ local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 40|webssh)
+ local app_id="40"
+ local docker_name="webssh"
+ local docker_img="jrohy/webssh"
+ local docker_port=8040
+ docker_rum() {
+ docker run -d -p ${docker_port}:5032 --restart=always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
+ }
+
+ local docker_describe="简易在线ssh连接工具和sftp工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 41|haozi|acepanel)
+
+ local app_id="41"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AcePanel 原耗子面板"
+ local panelurl="官方地址: ${gh_proxy}github.com/acepanel/panel"
+
+ panel_app_install() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+ }
+
+ panel_app_manage() {
+ acepanel help
+ }
+
+ panel_app_uninstall() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+
+ }
+
+ install_panel
+
+ ;;
+
+
+ 42|nexterm)
+ local app_id="42"
+ local docker_name="nexterm"
+ local docker_img="germannewsmaker/nexterm:latest"
+ local docker_port=8042
+
+ docker_rum() {
+
+ ENCRYPTION_KEY=$(openssl rand -hex 32)
+ docker run -d \
+ --name nexterm \
+ -e ENCRYPTION_KEY=${ENCRYPTION_KEY} \
+ -p ${docker_port}:6989 \
+ -v /home/docker/nexterm:/app/data \
+ --restart=always \
+ germannewsmaker/nexterm:latest
+
+ }
+
+ local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
+ local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 43|hbbs)
+ local app_id="43"
+ local docker_name="hbbs"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbs
+
+ }
+
+
+ local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
+ local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
+ local docker_use="docker logs hbbs"
+ local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 44|hbbr)
+ local app_id="44"
+ local docker_name="hbbr"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbr
+
+ }
+
+ local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
+ local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
+ local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 45|registry)
+ local app_id="45"
+ local docker_name="registry"
+ local docker_img="registry:2"
+ local docker_port=8045
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name registry \
+ -v /home/docker/registry:/var/lib/registry \
+ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
+ --restart=always \
+ registry:2
+
+ }
+
+ local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
+ local docker_url="官网介绍: https://hub.docker.com/_/registry"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 46|ghproxy)
+ local app_id="46"
+ local docker_name="ghproxy"
+ local docker_img="wjqserver/ghproxy:latest"
+ local docker_port=8046
+
+ docker_rum() {
+
+ docker run -d --name ghproxy --restart=always -p ${docker_port}:8080 -v /home/docker/ghproxy/config:/data/ghproxy/config wjqserver/ghproxy:latest
+
+ }
+
+ local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/WJQSERVER-STUDIO/ghproxy"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 47|prometheus|grafana)
+
+ local app_id="47"
+ local app_name="普罗米修斯监控"
+ local app_text="Prometheus+Grafana企业级监控系统"
+ local app_url="官网介绍: https://prometheus.io"
+ local docker_name="grafana"
+ local docker_port="8047"
+ local app_size="2"
+
+ docker_app_install() {
+ prometheus_install
+ clear
+ ip_address
+ echo "已经安装完成"
+ check_docker_app_ip
+ echo "初始用户名密码均为: admin"
+ }
+
+ docker_app_update() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+
+ rm -rf /home/docker/monitoring
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+ 48|node-exporter)
+ local app_id="48"
+ local docker_name="node-exporter"
+ local docker_img="prom/node-exporter"
+ local docker_port=8048
+
+ docker_rum() {
+
+ docker run -d \
+ --name=node-exporter \
+ -p ${docker_port}:9100 \
+ --restart=always \
+ prom/node-exporter
+
+
+ }
+
+ local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/prometheus/node_exporter"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 49|cadvisor)
+ local app_id="49"
+ local docker_name="cadvisor"
+ local docker_img="gcr.io/cadvisor/cadvisor:latest"
+ local docker_port=8049
+
+ docker_rum() {
+
+ docker run -d \
+ --name=cadvisor \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ --volume=/:/rootfs:ro \
+ --volume=/var/run:/var/run:rw \
+ --volume=/sys:/sys:ro \
+ --volume=/var/lib/docker/:/var/lib/docker:ro \
+ gcr.io/cadvisor/cadvisor:latest \
+ -housekeeping_interval=10s \
+ -docker_only=true
+
+ }
+
+ local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/google/cadvisor"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 50|changedetection)
+ local app_id="50"
+ local docker_name="changedetection"
+ local docker_img="dgtlmoon/changedetection.io:latest"
+ local docker_port=8050
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:5000 \
+ -v /home/docker/datastore:/datastore \
+ --name changedetection dgtlmoon/changedetection.io:latest
+
+ }
+
+ local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/dgtlmoon/changedetection.io"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 51|pve)
+ clear
+ send_stats "PVE开小鸡"
+ check_disk_space 1
+ curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
+ ;;
+
+
+ 52|dpanel)
+ local app_id="52"
+ local docker_name="dpanel"
+ local docker_img="dpanel/dpanel:lite"
+ local docker_port=8052
+
+ docker_rum() {
+
+ docker run -d --name dpanel --restart=always \
+ -p ${docker_port}:8080 -e APP_NAME=dpanel \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/dpanel:/dpanel \
+ dpanel/dpanel:lite
+
+ }
+
+ local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/donknap/dpanel"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 53|llama3)
+ local app_id="53"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
+ local docker_url="官网介绍: ${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run llama3.2:1b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+ 54|amh)
+
+ local app_id="54"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AMH面板"
+ local panelurl="官方地址: https://amh.sh/index.htm?amh"
+
+ panel_app_install() {
+ cd ~
+ wget https://dl.amh.sh/amh.sh && bash amh.sh
+ }
+
+ panel_app_manage() {
+ panel_app_install
+ }
+
+ panel_app_uninstall() {
+ panel_app_install
+ }
+
+ install_panel
+ ;;
+
+
+ 55|frps)
+ frps_panel
+ ;;
+
+ 56|frpc)
+ frpc_panel
+ ;;
+
+ 57|deepseek)
+ local app_id="57"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
+ local docker_url="官网介绍: ${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+ 58|dify)
+ local app_id="58"
+ local app_name="Dify知识库"
+ local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
+ local app_url="官方网站: https://docs.dify.ai/zh-hans"
+ local docker_name="docker-nginx-1"
+ local docker_port="8058"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
+ sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+
+ docker compose up -d
+
+ chown -R 1001:1001 /home/docker/dify/docker/volumes/app/storage
+ chmod -R 755 /home/docker/dify/docker/volumes/app/storage
+ docker compose down
+ docker compose up -d
+
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ cd /home/docker/dify/
+ git pull ${gh_proxy}github.com/langgenius/dify.git main > /dev/null 2>&1
+ sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
+ cd /home/docker/dify/docker/ && docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/dify
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+
+ ;;
+
+ 59|new-api)
+ local app_id="59"
+ local app_name="NewAPI"
+ local app_text="新一代大模型网关与AI资产管理系统"
+ local app_url="官方网站: ${gh_https_url}github.com/Calcium-Ion/new-api"
+ local docker_name="new-api"
+ local docker_port="8059"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ cd /home/docker/new-api/
+
+ git pull ${gh_proxy}github.com/Calcium-Ion/new-api.git main > /dev/null 2>&1
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ rm -rf /home/docker/new-api
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 60|jms)
+
+ local app_id="60"
+ local app_name="JumpServer开源堡垒机"
+ local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
+ local app_url="官方介绍: ${gh_https_url}github.com/jumpserver/jumpserver"
+ local docker_name="jms_web"
+ local docker_port="80"
+ local app_size="2"
+
+ docker_app_install() {
+ curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ echo "初始用户名: admin"
+ echo "初始密码: ChangeMe"
+ }
+
+
+ docker_app_update() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh upgrade
+ echo "应用已更新"
+ }
+
+
+ docker_app_uninstall() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh uninstall
+ cd /opt
+ rm -rf jumpserver-installer*/
+ rm -rf jumpserver
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+ 61|libretranslate)
+ local app_id="61"
+ local docker_name="libretranslate"
+ local docker_img="libretranslate/libretranslate:latest"
+ local docker_port=8061
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name libretranslate \
+ libretranslate/libretranslate \
+ --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+
+ }
+
+ local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/LibreTranslate/LibreTranslate"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+
+ 62|ragflow)
+ local app_id="62"
+ local app_name="RAGFlow知识库"
+ local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
+ local app_url="官方网站: ${gh_https_url}github.com/infiniflow/ragflow"
+ local docker_name="ragflow-server"
+ local docker_port="8062"
+ local app_size="8"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ cd /home/docker/ragflow/
+ git pull ${gh_proxy}github.com/infiniflow/ragflow.git main > /dev/null 2>&1
+ cd /home/docker/ragflow/docker/
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/ragflow
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 63|open-webui)
+ local app_id="63"
+ local docker_name="open-webui"
+ local docker_img="ghcr.io/open-webui/open-webui:main"
+ local docker_port=8063
+ docker_rum() {
- local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
- local docker_url="官网介绍: https://github.com/OpenListTeam/OpenList"
- local docker_use="docker exec -it openlist ./openlist admin random"
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart=always ghcr.io/open-webui/open-webui:main
- ;;
+ }
- 6)
+ local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
+ local docker_url="官网介绍: ${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
- local docker_name="webtop-ubuntu"
- local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
- local docker_port=3006
+ 64|it-tools)
+ local app_id="64"
+ local docker_name="it-tools"
+ local docker_img="corentinth/it-tools:latest"
+ local docker_port=8064
- docker_rum() {
+ docker_rum() {
+ docker run -d --name it-tools --restart=always -p ${docker_port}:80 corentinth/it-tools:latest
+ }
+ local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/CorentinTh/it-tools"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker run -d \
- --name=webtop-ubuntu \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=ubuntu-abc \
- -e PASSWORD=ubuntuABC123 \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:ubuntu-kde
+ 65|n8n)
+ local app_id="65"
+ local docker_name="n8n"
+ local docker_img="docker.n8n.io/n8nio/n8n"
+ local docker_port=8065
- }
+ docker_rum() {
+ add_yuming
+ mkdir -p /home/docker/n8n
+ chmod -R 777 /home/docker/n8n
+
+ docker run -d --name n8n \
+ --restart=always \
+ -p ${docker_port}:5678 \
+ -v /home/docker/n8n:/home/node/.n8n \
+ -e N8N_HOST=${yuming} \
+ -e N8N_PORT=5678 \
+ -e N8N_PROTOCOL=https \
+ -e WEBHOOK_URL=https://${yuming}/ \
+ docker.n8n.io/n8nio/n8n
- local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: ubuntu-abc\""
- local docker_passwd="echo \"密码: ubuntuABC123\""
- local app_size="2"
- docker_app
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
+ }
- ;;
- 7)
- clear
- send_stats "搭建哪吒"
- local docker_name="nezha-dashboard"
- local docker_port=8008
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
- clear
- echo -e "哪吒监控 $check_docker $update_status"
- echo "开源、轻量、易用的服务器监控与运维工具"
- echo "官网搭建文档: https://nezha.wiki/guide/dashboard.html"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. 使用"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "输入你的选择: " choice
+ local docker_describe="是一款功能强大的自动化工作流平台"
+ local docker_url="官网介绍: ${gh_https_url}github.com/n8n-io/n8n"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- case $choice in
- 1)
- check_disk_space 1
- install unzip jq
- install_docker
- curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- ;;
+ 66|yt)
+ yt_menu_pro
+ ;;
- *)
- break
- ;;
- esac
- break_end
- done
- ;;
+ 67|ddns)
+ local app_id="67"
+ local docker_name="ddns-go"
+ local docker_img="jeessy/ddns-go"
+ local docker_port=8067
- 8)
+ docker_rum() {
+ docker run -d \
+ --name ddns-go \
+ --restart=always \
+ -p ${docker_port}:9876 \
+ -v /home/docker/ddns-go:/root \
+ jeessy/ddns-go
- local docker_name="qbittorrent"
- local docker_img="lscr.io/linuxserver/qbittorrent:latest"
- local docker_port=8081
-
- docker_rum() {
-
- docker run -d \
- --name=qbittorrent \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e WEBUI_PORT=${docker_port} \
- -e TORRENTING_PORT=56881 \
- -p ${docker_port}:${docker_port} \
- -p 56881:56881 \
- -p 56881:56881/udp \
- -v /home/docker/qbittorrent/config:/config \
- -v /home/docker/qbittorrent/downloads:/downloads \
- --restart unless-stopped \
- lscr.io/linuxserver/qbittorrent:latest
+ }
- }
+ local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/jeessy2/ddns-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="qbittorrent离线BT磁力下载服务"
- local docker_url="官网介绍: https://hub.docker.com/r/linuxserver/qbittorrent"
- local docker_use="sleep 3"
- local docker_passwd="docker logs qbittorrent"
- local app_size="1"
- docker_app
+ 68|allinssl)
+ local app_id="68"
+ local docker_name="allinssl"
+ local docker_img="allinssl/allinssl:latest"
+ local docker_port=8068
- ;;
+ docker_rum() {
+ docker run -d --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
+ }
- 9)
- send_stats "搭建邮局"
- clear
- install telnet
- local docker_name=“mailserver”
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
+ local docker_describe="开源免费的 SSL 证书自动化管理平台"
+ local docker_url="官网介绍: https://allinssl.com"
+ local docker_use="echo \"安全入口: /allinssl\""
+ local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
+ local app_size="1"
+ docker_app
+ ;;
- clear
- echo -e "邮局服务 $check_docker $update_status"
- echo "poste.io 是一个开源的邮件服务器解决方案,"
- echo "视频介绍: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
- echo ""
- echo "端口检测"
- port=25
- timeout=3
- if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
- echo -e "${gl_lv}端口 $port 当前可用${gl_bai}"
- else
- echo -e "${gl_hong}端口 $port 当前不可用${gl_bai}"
- fi
- echo ""
+ 69|sftpgo)
+ local app_id="69"
+ local docker_name="sftpgo"
+ local docker_img="drakkan/sftpgo:latest"
+ local docker_port=8069
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
- yuming=$(cat /home/docker/mail.txt)
- echo "访问地址: "
- echo "https://$yuming"
- fi
+ docker_rum() {
- echo "------------------------"
- echo "1. 安装 2. 更新 3. 卸载"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "输入你的选择: " choice
+ mkdir -p /home/docker/sftpgo/data
+ mkdir -p /home/docker/sftpgo/config
+ chown -R 1000:1000 /home/docker/sftpgo
- case $choice in
- 1)
- check_disk_space 2
- read -e -p "请设置邮箱域名 例如 mail.yuming.com : " yuming
- mkdir -p /home/docker
- echo "$yuming" > /home/docker/mail.txt
- echo "------------------------"
- ip_address
- echo "先解析这些DNS记录"
- echo "A mail $ipv4_address"
- echo "CNAME imap $yuming"
- echo "CNAME pop $yuming"
- echo "CNAME smtp $yuming"
- echo "MX @ $yuming"
- echo "TXT @ v=spf1 mx ~all"
- echo "TXT ? ?"
- echo ""
- echo "------------------------"
- echo "按任意键继续..."
- read -n 1 -s -r -p ""
+ docker run -d \
+ --name sftpgo \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -p 22022:2022 \
+ --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
+ --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
+ drakkan/sftpgo:latest
- install jq
- install_docker
+ }
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.io
+ local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
+ local docker_url="官网介绍: https://sftpgo.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
- clear
- echo "poste.io已经安装完成"
- echo "------------------------"
- echo "您可以使用以下地址访问poste.io:"
- echo "https://$yuming"
- echo ""
+ 70|astrbot)
+ local app_id="70"
+ local docker_name="astrbot"
+ local docker_img="soulter/astrbot:latest"
+ local docker_port=8070
- ;;
+ docker_rum() {
- 2)
- docker rm -f mailserver
- docker rmi -f analogic/poste.i
- yuming=$(cat /home/docker/mail.txt)
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.i
-
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ mkdir -p /home/docker/astrbot/data
- clear
- echo "poste.io已经安装完成"
- echo "------------------------"
- echo "您可以使用以下地址访问poste.io:"
- echo "https://$yuming"
- echo ""
- ;;
- 3)
- docker rm -f mailserver
- docker rmi -f analogic/poste.io
- rm /home/docker/mail.txt
- rm -rf /home/docker/mail
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
- echo "应用已卸载"
- ;;
+ docker run -d \
+ -p ${docker_port}:6185 \
+ -p 6195:6195 \
+ -p 6196:6196 \
+ -p 6199:6199 \
+ -p 11451:11451 \
+ -v /home/docker/astrbot/data:/AstrBot/data \
+ --restart=always \
+ --name astrbot \
+ soulter/astrbot:latest
- *)
- break
- ;;
+ }
- esac
- break_end
- done
+ local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
+ local docker_url="官网介绍: https://astrbot.app/"
+ local docker_use="echo \"用户名: astrbot 密码: astrbot\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- ;;
- 10)
+ 71|navidrome)
+ local app_id="71"
+ local docker_name="navidrome"
+ local docker_img="deluan/navidrome:latest"
+ local docker_port=8071
- local app_name="Rocket.Chat聊天系统"
- local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
- local app_url="官方介绍: https://www.rocket.chat/"
- local docker_name="rocketchat"
- local docker_port="3897"
- local app_size="2"
-
- docker_app_install() {
- docker run --name db -d --restart=always \
- -v /home/docker/mongo/dump:/dump \
- mongo:latest --replSet rs5 --oplogSize 256
- sleep 1
- docker exec -it db mongosh --eval "printjson(rs.initiate())"
- sleep 5
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ docker_rum() {
- clear
- ip_address
- echo "已经安装完成"
- check_docker_app_ip
- }
+ docker run -d \
+ --name navidrome \
+ --restart=always \
+ --user $(id -u):$(id -g) \
+ -v /home/docker/navidrome/music:/music \
+ -v /home/docker/navidrome/data:/data \
+ -p ${docker_port}:4533 \
+ -e ND_LOGLEVEL=info \
+ deluan/navidrome:latest
- docker_app_update() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat:latest
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "rocket.chat已经安装完成"
- check_docker_app_ip
- }
+ }
- docker_app_uninstall() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat
- docker rm -f db
- docker rmi -f mongo:latest
- rm -rf /home/docker/mongo
- echo "应用已卸载"
- }
+ local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
+ local docker_url="官网介绍: https://www.navidrome.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_app_plus
- ;;
+ 72|bitwarden)
+ local app_id="72"
+ local docker_name="bitwarden"
+ local docker_img="vaultwarden/server"
+ local docker_port=8072
- 11)
- local docker_name="zentao-server"
- local docker_img="idoop/zentao:latest"
- local docker_port=82
+ docker_rum() {
+ docker run -d \
+ --name bitwarden \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/bitwarden/data:/data \
+ vaultwarden/server
- docker_rum() {
+ }
+ local docker_describe="一个你可以控制数据的密码管理器"
+ local docker_url="官网介绍: https://bitwarden.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:80 \
- -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
- -e BIND_ADDRESS="false" \
- -v /home/docker/zentao-server/:/opt/zbox/ \
- --add-host smtp.exmail.qq.com:163.177.90.125 \
- --name zentao-server \
- --restart=always \
- idoop/zentao:latest
+ ;;
- }
- local docker_describe="禅道是通用的项目管理软件"
- local docker_url="官网介绍: https://www.zentao.net/"
- local docker_use="echo \"初始用户名: admin\""
- local docker_passwd="echo \"初始密码: 123456\""
- local app_size="2"
- docker_app
- ;;
+ 73|libretv)
- 12)
- local docker_name="qinglong"
- local docker_img="whyour/qinglong:latest"
- local docker_port=5700
+ local app_id="73"
+ local docker_name="libretv"
+ local docker_img="bestzwei/libretv:latest"
+ local docker_port=8073
- docker_rum() {
+ docker_rum() {
+ read -e -p "设置LibreTV的登录密码: " app_passwd
- docker run -d \
- -v /home/docker/qinglong/data:/ql/data \
- -p ${docker_port}:5700 \
- --name qinglong \
- --hostname qinglong \
- --restart unless-stopped \
- whyour/qinglong:latest
+ docker run -d \
+ --name libretv \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -e PASSWORD=${app_passwd} \
+ bestzwei/libretv:latest
+ }
- }
+ local docker_describe="免费在线视频搜索与观看平台"
+ local docker_url="官网介绍: ${gh_https_url}github.com/LibreSpark/LibreTV"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="青龙面板是一个定时任务管理平台"
- local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ ;;
- ;;
- 13)
- local app_name="cloudreve网盘"
- local app_text="cloudreve是一个支持多家云存储的网盘系统"
- local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
- local docker_name="cloudreve"
- local docker_port="5212"
- local app_size="2"
-
- docker_app_install() {
- cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
- curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
- sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
- cd /home/docker/cloud/
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
+ 74|moontv)
- docker_app_update() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- cd /home/docker/cloud/ && docker compose up -d
- }
+ local app_id="74"
+ local app_name="moontv私有影视"
+ local app_text="免费在线视频搜索与观看平台"
+ local app_url="视频介绍: ${gh_https_url}github.com/MoonTechLab/LunaTV"
+ local docker_name="moontv-core"
+ local docker_port="8074"
+ local app_size="2"
- docker_app_uninstall() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- rm -rf /home/docker/cloud
- echo "应用已卸载"
- }
+ docker_app_install() {
+ read -e -p "设置登录用户名: " admin
+ read -e -p "设置登录用户密码: " admin_password
+ read -e -p "输入授权码: " shouquanma
- docker_app_plus
- ;;
- 14)
- local docker_name="easyimage"
- local docker_img="ddsderek/easyimage:latest"
- local docker_port=85
- docker_rum() {
-
- docker run -d \
- --name easyimage \
- -p ${docker_port}:80 \
- -e TZ=Asia/Shanghai \
- -e PUID=1000 \
- -e PGID=1000 \
- -v /home/docker/easyimage/config:/app/web/config \
- -v /home/docker/easyimage/i:/app/web/i \
- --restart unless-stopped \
- ddsderek/easyimage:latest
+ mkdir -p /home/docker/moontv
+ mkdir -p /home/docker/moontv/config
+ mkdir -p /home/docker/moontv/data
+ cd /home/docker/moontv
- }
+ curl -o /home/docker/moontv/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/moontv-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin_password|${admin_password}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin|${admin}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|shouquanma|${shouquanma}|g" /home/docker/moontv/docker-compose.yml
+ cd /home/docker/moontv/
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- local docker_describe="简单图床是一个简单的图床程序"
- local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 15)
- local docker_name="emby"
- local docker_img="linuxserver/emby:latest"
- local docker_port=8096
+ docker_app_update() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ cd /home/docker/moontv/ && docker compose up -d
+ }
- docker_rum() {
- docker run -d --name=emby --restart=always \
- -v /home/docker/emby/config:/config \
- -v /home/docker/emby/share1:/mnt/share1 \
- -v /home/docker/emby/share2:/mnt/share2 \
- -v /mnt/notify:/mnt/notify \
- -p ${docker_port}:8096 \
- -e UID=1000 -e GID=100 -e GIDLIST=100 \
- linuxserver/emby:latest
+ docker_app_uninstall() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ rm -rf /home/docker/moontv
+ echo "应用已卸载"
+ }
- }
+ docker_app_plus
+ ;;
- local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
- local docker_url="官网介绍: https://emby.media/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 16)
- local docker_name="looking-glass"
- local docker_img="wikihostinc/looking-glass-server"
- local docker_port=89
+ 75|melody)
+ local app_id="75"
+ local docker_name="melody"
+ local docker_img="foamzou/melody:latest"
+ local docker_port=8075
- docker_rum() {
+ docker_rum() {
- docker run -d --name looking-glass --restart always -p ${docker_port}:80 wikihostinc/looking-glass-server
+ docker run -d \
+ --name melody \
+ --restart=always \
+ -p ${docker_port}:5566 \
+ -v /home/docker/melody/.profile:/app/backend/.profile \
+ foamzou/melody:latest
- }
- local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
- local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ }
- ;;
- 17)
+ local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/foamzou/melody"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="adguardhome"
- local docker_img="adguard/adguardhome"
- local docker_port=3000
- docker_rum() {
+ ;;
- docker run -d \
- --name adguardhome \
- -v /home/docker/adguardhome/work:/opt/adguardhome/work \
- -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
- -p 53:53/tcp \
- -p 53:53/udp \
- -p ${docker_port}:3000/tcp \
- --restart always \
- adguard/adguardhome
+ 76|dosgame)
- }
+ local app_id="76"
+ local docker_name="dosgame"
+ local docker_img="oldiy/dosgame-web-docker:latest"
+ local docker_port=8076
+ docker_rum() {
+ docker run -d \
+ --name dosgame \
+ --restart=always \
+ -p ${docker_port}:262 \
+ oldiy/dosgame-web-docker:latest
- local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
- local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ }
- ;;
+ local docker_describe="是一个中文DOS游戏合集网站"
+ local docker_url="官网介绍: ${gh_https_url}github.com/rwv/chinese-dos-games"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- 18)
+ ;;
- local docker_name="onlyoffice"
- local docker_img="onlyoffice/documentserver"
- local docker_port=8082
+ 77|xunlei)
- docker_rum() {
+ local app_id="77"
+ local docker_name="xunlei"
+ local docker_img="cnk3x/xunlei"
+ local docker_port=8077
- docker run -d -p ${docker_port}:80 \
- --restart=always \
- --name onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
- onlyoffice/documentserver
+ docker_rum() {
+ read -e -p "设置登录用户名: " app_use
+ read -e -p "设置登录密码: " app_passwd
- }
+ docker run -d \
+ --name xunlei \
+ --restart=always \
+ --privileged \
+ -e XL_DASHBOARD_USERNAME=${app_use} \
+ -e XL_DASHBOARD_PASSWORD=${app_passwd} \
+ -v /home/docker/xunlei/data:/xunlei/data \
+ -v /home/docker/xunlei/downloads:/xunlei/downloads \
+ -p ${docker_port}:2345 \
+ cnk3x/xunlei
- local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
- local docker_url="官网介绍: https://www.onlyoffice.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ }
- ;;
+ local docker_describe="迅雷你的离线高速BT磁力下载工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/cnk3x/xunlei"
+ local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 19)
- send_stats "搭建雷池"
+ ;;
- local docker_name=safeline-mgt
- local docker_port=9443
- while true; do
- check_docker_app
- clear
- echo -e "雷池服务 $check_docker"
- echo "雷池是长亭科技开发的WAF站点防火墙程序面板,可以反代站点进行自动化防御"
- echo "视频介绍: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. 安装 2. 更新 3. 重置密码 4. 卸载"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "输入你的选择: " choice
- case $choice in
- 1)
- install_docker
- check_disk_space 5
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
- clear
- echo "雷池WAF面板已经安装完成"
- check_docker_app_ip
- docker exec safeline-mgt resetadmin
+ 78|PandaWiki)
- ;;
+ local app_id="78"
+ local app_name="PandaWiki"
+ local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
+ local app_url="官方介绍: ${gh_https_url}github.com/chaitin/PandaWiki"
+ local docker_name="panda-wiki-nginx"
+ local docker_port="2443"
+ local app_size="2"
- 2)
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
- docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
- echo ""
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
- clear
- echo "雷池WAF面板已经更新完成"
- check_docker_app_ip
- ;;
- 3)
- docker exec safeline-mgt resetadmin
- ;;
- 4)
- cd /data/safeline
- docker compose down --rmi all
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
- echo "如果你是默认安装目录那现在项目已经卸载。如果你是自定义安装目录你需要到安装目录下自行执行:"
- echo "docker compose down && docker compose down --rmi all"
- ;;
- *)
- break
- ;;
+ docker_app_install() {
+ bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
+ }
- esac
- break_end
- done
+ docker_app_update() {
+ docker_app_install
+ }
- ;;
- 20)
- local docker_name="portainer"
- local docker_img="portainer/portainer"
- local docker_port=9050
+ docker_app_uninstall() {
+ docker_app_install
+ }
- docker_rum() {
+ docker_app_plus
+ ;;
- docker run -d \
- --name portainer \
- -p ${docker_port}:9000 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/portainer:/data \
- --restart always \
- portainer/portainer
- }
+ 79|beszel)
- local docker_describe="portainer是一个轻量级的docker容器管理面板"
- local docker_url="官网介绍: https://www.portainer.io/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="79"
+ local docker_name="beszel"
+ local docker_img="henrygd/beszel"
+ local docker_port=8079
- ;;
+ docker_rum() {
- 21)
- local docker_name="vscode-web"
- local docker_img="codercom/code-server"
- local docker_port=8180
+ mkdir -p /home/docker/beszel && \
+ docker run -d \
+ --name beszel \
+ --restart=always \
+ -v /home/docker/beszel:/beszel_data \
+ -p ${docker_port}:8090 \
+ henrygd/beszel
+ }
- docker_rum() {
+ local docker_describe="Beszel轻量易用的服务器监控"
+ local docker_url="官网介绍: https://beszel.dev/zh/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart always codercom/code-server
+ ;;
- }
+ 80|linkwarden)
- local docker_describe="VScode是一款强大的在线代码编写工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
- local docker_use="sleep 3"
- local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
- local app_size="1"
- docker_app
- ;;
- 22)
- local docker_name="uptime-kuma"
- local docker_img="louislam/uptime-kuma:latest"
- local docker_port=3003
+ local app_id="80"
+ local app_name="linkwarden书签管理"
+ local app_text="一个开源的自托管书签管理平台,支持标签、搜索和团队协作。"
+ local app_url="官方网站: https://linkwarden.app/"
+ local docker_name="linkwarden-linkwarden-1"
+ local docker_port="8080"
+ local app_size="3"
+ docker_app_install() {
+ install git openssl
+ mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
- docker_rum() {
+ # 下载官方 docker-compose 和 env 文件
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
- docker run -d \
- --name=uptime-kuma \
- -p ${docker_port}:3001 \
- -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
- --restart=always \
- louislam/uptime-kuma:latest
+ # 生成随机密钥与密码
+ local ADMIN_EMAIL="admin@example.com"
+ local ADMIN_PASSWORD=$(openssl rand -hex 8)
- }
+ sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
+ sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
+ sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
+ sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
+ # 追加管理员账号信息
+ echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
+ echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
- local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
- 23)
- local docker_name="memos"
- local docker_img="ghcr.io/usememos/memos:latest"
- local docker_port=5230
+ # 启动容器
+ docker compose up -d
- docker_rum() {
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
+
+ # 保留原本的变量
+ source .env
+ mv .env.new .env
+ echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
+ echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
+ echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
+ echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
+ echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
+ echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ rm -rf /home/docker/linkwarden
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
- docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart always ghcr.io/usememos/memos:latest
+ ;;
- }
- local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
- local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 24)
- local docker_name="webtop"
- local docker_img="lscr.io/linuxserver/webtop:latest"
- local docker_port=3083
-
- docker_rum() {
-
-
- docker run -d \
- --name=webtop \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=webtop-abc \
- -e PASSWORD=webtopABC123 \
- -e LC_ALL=zh_CN.UTF-8 \
- -e DOCKER_MODS=linuxserver/mods:universal-package-install \
- -e INSTALL_PACKAGES=font-noto-cjk \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:latest
+ 81|jitsi)
+ local app_id="81"
+ local app_name="JitsiMeet视频会议"
+ local app_text="一个开源的安全视频会议解决方案,支持多人在线会议、屏幕共享与加密通信。"
+ local app_url="官方网站: https://jitsi.org/"
+ local docker_name="jitsi"
+ local docker_app_service="jitsi-web-1"
+ local docker_port="8081"
+ local app_size="3"
+ docker_app_install() {
- }
+ add_yuming
+ mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
+ rm -rf docker-jitsi-meet-* jitsi.zip
+ local jitsi_zip_url=$(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep -m1 '"zipball_url"' | cut -d\" -f4)
+ if [ -z "$jitsi_zip_url" ]; then
+ echo "Failed to get Jitsi Meet latest download URL"
+ return 1
+ fi
+ wget -O jitsi.zip "$jitsi_zip_url"
+ unzip -q jitsi.zip
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ cp env.example .env
+ ./gen-passwords.sh
+ mkdir -p /home/docker/jitsi/config/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
+ sed -i "s|^CONFIG=.*|CONFIG=/home/docker/jitsi/config|" .env
+ grep -q '^COMPOSE_PROJECT_NAME=' .env && sed -i "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=jitsi|" .env || echo "COMPOSE_PROJECT_NAME=jitsi" >> .env
+ sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
+ sed -i "s|^#\?PUBLIC_URL=.*|PUBLIC_URL=https://$yuming|" .env
+ grep -q '^PUBLIC_URL=' .env || echo "PUBLIC_URL=https://$yuming" >> .env
+ grep -q '^DISABLE_HTTPS=' .env && sed -i "s|^DISABLE_HTTPS=.*|DISABLE_HTTPS=1|" .env || echo "DISABLE_HTTPS=1" >> .env
+ grep -q '^ENABLE_HTTP_REDIRECT=' .env && sed -i "s|^ENABLE_HTTP_REDIRECT=.*|ENABLE_HTTP_REDIRECT=0|" .env || echo "ENABLE_HTTP_REDIRECT=0" >> .env
+ sed -i "/'\${HTTPS_PORT}:443'/d" docker-compose.yml
+ docker compose up -d
+ local jitsi_web_container=$(docker compose ps -q web)
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ [ -n "$jitsi_web_container" ] && block_container_port "$jitsi_web_container" "$ipv4_address"
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose pull
+ docker compose up -d
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose down --rmi all
+ rm -rf /home/docker/jitsi
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
- local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: webtop-abc\""
- local docker_passwd="echo \"密码: webtopABC123\""
- local app_size="2"
- docker_app
- ;;
- 25)
- local docker_name="nextcloud"
- local docker_img="nextcloud:latest"
- local docker_port=8989
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
- docker_rum() {
+ 82|gpt-load)
- docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+ local app_id="82"
+ local docker_name="gpt-load"
+ local docker_img="tbphp/gpt-load:latest"
+ local docker_port=8082
- }
+ docker_rum() {
- local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
- local docker_url="官网介绍: https://nextcloud.com/"
- local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ read -e -p "设置${docker_name}的登录密钥(sk-开头字母和数字组合)如: sk-159kejilionyyds163: " app_passwd
- 26)
- local docker_name="qd"
- local docker_img="qdtoday/qd:latest"
- local docker_port=8923
+ mkdir -p /home/docker/gpt-load && \
+ docker run -d --name gpt-load \
+ -p ${docker_port}:3001 \
+ -e AUTH_KEY=${app_passwd} \
+ -v "/home/docker/gpt-load/data":/app/data \
+ tbphp/gpt-load:latest
- docker_rum() {
+ }
- docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+ local docker_describe="高性能AI接口透明代理服务"
+ local docker_url="官网介绍: https://www.gpt-load.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
- local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 27)
- local docker_name="dockge"
- local docker_img="louislam/dockge:latest"
- local docker_port=5003
- docker_rum() {
- docker run -d --name dockge --restart unless-stopped -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+ 83|komari)
- }
+ local app_id="83"
+ local docker_name="komari"
+ local docker_img="ghcr.io/komari-monitor/komari:latest"
+ local docker_port=8083
- local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 28)
- local docker_name="speedtest"
- local docker_img="ghcr.io/librespeed/speedtest"
- local docker_port=8028
+ mkdir -p /home/docker/komari && \
+ docker run -d \
+ --name komari \
+ -p ${docker_port}:25774 \
+ -v /home/docker/komari:/app/data \
+ -e ADMIN_USERNAME=admin \
+ -e ADMIN_PASSWORD=1212156 \
+ -e TZ=Asia/Shanghai \
+ --restart=always \
+ ghcr.io/komari-monitor/komari:latest
- docker_rum() {
+ }
- docker run -d -p ${docker_port}:8080 --name speedtest --restart always ghcr.io/librespeed/speedtest
+ local docker_describe="轻量级的自托管服务器监控工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/komari-monitor/komari/tree/main"
+ local docker_use="echo \"默认账号: admin 默认密码: 1212156\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
- local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 29)
- local docker_name="searxng"
- local docker_img="searxng/searxng"
- local docker_port=8029
- docker_rum() {
+ 84|wallos)
- docker run -d \
- --name searxng \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -v "/home/docker/searxng:/etc/searxng" \
- searxng/searxng
+ local app_id="84"
+ local docker_name="wallos"
+ local docker_img="bellamy/wallos:latest"
+ local docker_port=8084
- }
+ docker_rum() {
- local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
- local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/wallos && \
+ docker run -d --name wallos \
+ -v /home/docker/wallos/db:/var/www/html/db \
+ -v /home/docker/wallos/logos:/var/www/html/images/uploads/logos \
+ -e TZ=UTC \
+ -p ${docker_port}:80 \
+ --restart=always \
+ bellamy/wallos:latest
- 30)
- local docker_name="photoprism"
- local docker_img="photoprism/photoprism:latest"
- local docker_port=2342
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
-
- docker_rum() {
-
- docker run -d \
- --name photoprism \
- --restart always \
- --security-opt seccomp=unconfined \
- --security-opt apparmor=unconfined \
- -p ${docker_port}:2342 \
- -e PHOTOPRISM_UPLOAD_NSFW="true" \
- -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
- -v /home/docker/photoprism/storage:/photoprism/storage \
- -v /home/docker/photoprism/Pictures:/photoprism/originals \
- photoprism/photoprism
+ }
- }
+ local docker_describe="开源个人订阅追踪器,可用于财务管理"
+ local docker_url="官网介绍: ${gh_https_url}github.com/ellite/Wallos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="photoprism非常强大的私有相册系统"
- local docker_url="官网介绍: https://www.photoprism.app/"
- local docker_use="echo \"账号: admin 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 85|immich)
+ local app_id="85"
+ local app_name="immich图片视频管理器"
+ local app_text="高性能自托管照片和视频管理解决方案。"
+ local app_url="官网介绍: ${gh_https_url}github.com/immich-app/immich"
+ local docker_name="immich_server"
+ local docker_port="8085"
+ local app_size="3"
- 31)
- local docker_name="s-pdf"
- local docker_img="frooodle/s-pdf:latest"
- local docker_port=8020
-
- docker_rum() {
-
- docker run -d \
- --name s-pdf \
- --restart=always \
- -p ${docker_port}:8080 \
- -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
- -v /home/docker/s-pdf/extraConfigs:/configs \
- -v /home/docker/s-pdf/logs:/logs \
- -e DOCKER_ENABLE_SECURITY=false \
- frooodle/s-pdf:latest
- }
+ docker_app_install() {
+ install git openssl wget
+ mkdir -p /home/docker/${docker_name} && cd /home/docker/${docker_name}
- local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
- local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ wget -O docker-compose.yml ${gh_proxy}github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+ wget -O .env ${gh_proxy}github.com/immich-app/immich/releases/latest/download/example.env
+ sed -i "s/2283:2283/${docker_port}:2283/g" /home/docker/${docker_name}/docker-compose.yml
- 32)
- local docker_name="drawio"
- local docker_img="jgraph/drawio"
- local docker_port=7080
+ docker compose up -d
- docker_rum() {
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
- docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+ }
- }
+ docker_app_update() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ docker_app_install
+ }
+ docker_app_uninstall() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ rm -rf /home/docker/${docker_name}
+ echo "应用已卸载"
+ }
- local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
- local docker_url="官网介绍: https://www.drawio.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_plus
- 33)
- local docker_name="sun-panel"
- local docker_img="hslr/sun-panel"
- local docker_port=3009
- docker_rum() {
+ ;;
- docker run -d --restart=always -p ${docker_port}:3002 \
- -v /home/docker/sun-panel/conf:/app/conf \
- -v /home/docker/sun-panel/uploads:/app/uploads \
- -v /home/docker/sun-panel/database:/app/database \
- --name sun-panel \
- hslr/sun-panel
- }
+ 86|jellyfin)
- local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
- local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
- local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="86"
+ local docker_name="jellyfin"
+ local docker_img="jellyfin/jellyfin"
+ local docker_port=8086
- 34)
- local docker_name="pingvin-share"
- local docker_img="stonith404/pingvin-share"
- local docker_port=3060
-
- docker_rum() {
-
- docker run -d \
- --name pingvin-share \
- --restart always \
- -p ${docker_port}:3000 \
- -v /home/docker/pingvin-share/data:/opt/app/backend/data \
- stonith404/pingvin-share
- }
+ docker_rum() {
- local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
- local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/jellyfin/media
+ chmod -R 777 /home/docker/jellyfin
+ docker run -d \
+ --name jellyfin \
+ --user root \
+ --volume /home/docker/jellyfin/config:/config \
+ --volume /home/docker/jellyfin/cache:/cache \
+ --mount type=bind,source=/home/docker/jellyfin/media,target=/media \
+ -p ${docker_port}:8096 \
+ -p 7359:7359/udp \
+ --restart=always \
+ jellyfin/jellyfin
- 35)
- local docker_name="moments"
- local docker_img="kingwrcy/moments:latest"
- local docker_port=8035
-
- docker_rum() {
-
- docker run -d --restart unless-stopped \
- -p ${docker_port}:3000 \
- -v /home/docker/moments/data:/app/data \
- -v /etc/localtime:/etc/localtime:ro \
- -v /etc/timezone:/etc/timezone:ro \
- --name moments \
- kingwrcy/moments:latest
- }
+ }
- local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
- local docker_url="官网介绍: ${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
- local docker_use="echo \"账号: admin 密码: a123456\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="是一款开源媒体服务器软件"
+ local docker_url="官网介绍: https://jellyfin.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 36)
- local docker_name="lobe-chat"
- local docker_img="lobehub/lobe-chat:latest"
- local docker_port=8036
+ 87|synctv)
- docker_rum() {
+ local app_id="87"
+ local docker_name="synctv"
+ local docker_img="synctvorg/synctv"
+ local docker_port=8087
- docker run -d -p ${docker_port}:3210 \
- --name lobe-chat \
- --restart=always \
- lobehub/lobe-chat
- }
+ docker_rum() {
- local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
- local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker run -d \
+ --name synctv \
+ -v /home/docker/synctv:/root/.synctv \
+ -p ${docker_port}:8080 \
+ --restart=always \
+ synctvorg/synctv
- 37)
- local docker_name="myip"
- local docker_img="jason5ng32/myip:latest"
- local docker_port=8037
+ }
- docker_rum() {
+ local docker_describe="远程一起观看电影和直播的程序。它提供了同步观影、直播、聊天等功能"
+ local docker_url="官网介绍: ${gh_https_url}github.com/synctv-org/synctv"
+ local docker_use="echo \"初始账号和密码: root 登陆后请及时修改登录密码\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+ ;;
- }
+ 88|owncast)
- local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
- local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="88"
+ local docker_name="owncast"
+ local docker_img="owncast/owncast:latest"
+ local docker_port=8088
- 38)
- send_stats "小雅全家桶"
- clear
- install_docker
- check_disk_space 1
- bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
- ;;
+ docker_rum() {
- 39)
+ docker run -d \
+ --name owncast \
+ -p ${docker_port}:8080 \
+ -p 1935:1935 \
+ -v /home/docker/owncast/data:/app/data \
+ --restart=always \
+ owncast/owncast:latest
- if [ ! -d /home/docker/bililive-go/ ]; then
- mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
- wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
- fi
- local docker_name="bililive-go"
- local docker_img="chigusa/bililive-go"
- local docker_port=8039
+ }
- docker_rum() {
+ local docker_describe="开源、免费的自建直播平台"
+ local docker_url="官网介绍: https://owncast.online"
+ local docker_use="echo \"访问地址后面带 /admin 访问管理员页面\""
+ local docker_passwd="echo \"初始账号: admin 初始密码: abc123 登陆后请及时修改登录密码\""
+ local app_size="1"
+ docker_app
- docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+ ;;
- }
- local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 40)
- local docker_name="webssh"
- local docker_img="jrohy/webssh"
- local docker_port=8040
- docker_rum() {
- docker run -d -p ${docker_port}:5032 --restart always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
- }
+ 89|file-code-box)
- local docker_describe="简易在线ssh连接工具和sftp工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="89"
+ local docker_name="file-code-box"
+ local docker_img="lanol/filecodebox:latest"
+ local docker_port=8089
- 41)
+ docker_rum() {
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="耗子面板"
- local panelurl="官方地址: ${gh_proxy}github.com/TheTNB/panel"
+ docker run -d \
+ --name file-code-box \
+ -p ${docker_port}:12345 \
+ -v /home/docker/file-code-box/data:/app/data \
+ --restart=always \
+ lanol/filecodebox:latest
- panel_app_install() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
- cd ~
- }
+ }
- panel_app_manage() {
- panel-cli
- }
+ local docker_describe="匿名口令分享文本和文件,像拿快递一样取文件"
+ local docker_url="官网介绍: ${gh_https_url}github.com/vastsa/FileCodeBox"
+ local docker_use="echo \"访问地址后面带 /#/admin 访问管理员页面\""
+ local docker_passwd="echo \"管理员密码: FileCodeBox2023\""
+ local app_size="1"
+ docker_app
- panel_app_uninstall() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
- cd ~
- }
+ ;;
- install_panel
- ;;
- 42)
- local docker_name="nexterm"
- local docker_img="germannewsmaker/nexterm:latest"
- local docker_port=8042
+ 90|matrix)
- docker_rum() {
+ local app_id="90"
+ local docker_name="matrix"
+ local docker_img="matrixdotorg/synapse:latest"
+ local docker_port=8090
- docker run -d \
- --name nexterm \
- -p ${docker_port}:6989 \
- -v /home/docker/nexterm:/app/data \
- --restart unless-stopped \
- germannewsmaker/nexterm:latest
+ docker_rum() {
- }
+ add_yuming
- local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
- local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ if [ ! -d /home/docker/matrix/data ]; then
+ docker run --rm \
+ -v /home/docker/matrix/data:/data \
+ -e SYNAPSE_SERVER_NAME=${yuming} \
+ -e SYNAPSE_REPORT_STATS=yes \
+ --name matrix \
+ matrixdotorg/synapse:latest generate
+ fi
- 43)
- local docker_name="hbbs"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker run -d \
+ --name matrix \
+ -v /home/docker/matrix/data:/data \
+ -p ${docker_port}:8008 \
+ --restart=always \
+ matrixdotorg/synapse:latest
- docker_rum() {
+ echo "创建初始用户或管理员。请设置以下内容用户名和密码以及是否为管理员。"
+ docker exec -it matrix register_new_matrix_user \
+ http://localhost:8008 \
+ -c /data/homeserver.yaml
- docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbs
+ sed -i '/^enable_registration:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration: true' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^enable_registration_without_verification:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration_without_verification: true' /home/docker/matrix/data/homeserver.yaml
- }
+ docker restart matrix
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="docker logs hbbs"
- local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
- local app_size="1"
- docker_app
- ;;
+ }
- 44)
- local docker_name="hbbr"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ local docker_describe="Matrix是一个去中心化的聊天协议"
+ local docker_url="官网介绍: https://matrix.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbr
- }
- local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 91|gitea)
- 45)
- local docker_name="registry"
- local docker_img="registry:2"
- local docker_port=8045
+ local app_id="91"
- docker_rum() {
+ local app_name="gitea私有代码仓库"
+ local app_text="免费新一代的代码托管平台,提供接近 GitHub 的使用体验。"
+ local app_url="视频介绍: ${gh_https_url}github.com/go-gitea/gitea"
+ local docker_name="gitea"
+ local docker_port="8091"
+ local app_size="2"
- docker run -d \
- -p ${docker_port}:5000 \
- --name registry \
- -v /home/docker/registry:/var/lib/registry \
- -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
- --restart always \
- registry:2
+ docker_app_install() {
- }
+ mkdir -p /home/docker/gitea
+ mkdir -p /home/docker/gitea/gitea
+ mkdir -p /home/docker/gitea/data
+ mkdir -p /home/docker/gitea/postgres
+ cd /home/docker/gitea
- local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
- local docker_url="官网介绍: https://hub.docker.com/_/registry"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ curl -o /home/docker/gitea/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/gitea-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/gitea/docker-compose.yml
+ cd /home/docker/gitea/
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- 46)
- local docker_name="ghproxy"
- local docker_img="wjqserver/ghproxy:latest"
- local docker_port=8046
- docker_rum() {
+ docker_app_update() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ cd /home/docker/gitea/ && docker compose up -d
+ }
- docker run -d --name ghproxy --restart always -p ${docker_port}:8080 wjqserver/ghproxy:latest
- }
+ docker_app_uninstall() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ rm -rf /home/docker/gitea
+ echo "应用已卸载"
+ }
- local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
- local docker_url="官网介绍: https://github.com/WJQSERVER-STUDIO/ghproxy"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_plus
- 47)
+ ;;
- local app_name="普罗米修斯监控"
- local app_text="Prometheus+Grafana企业级监控系统"
- local app_url="官网介绍: https://prometheus.io"
- local docker_name="grafana"
- local docker_port="8047"
- local app_size="2"
- docker_app_install() {
- prometheus_install
- clear
- ip_address
- echo "已经安装完成"
- check_docker_app_ip
- echo "初始用户名密码均为: admin"
- }
+ 92|filebrowser)
- docker_app_update() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
- docker_app_install
- }
+ local app_id="92"
+ local docker_name="filebrowser"
+ local docker_img="hurlenko/filebrowser"
+ local docker_port=8092
- docker_app_uninstall() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
+ docker_rum() {
- rm -rf /home/docker/monitoring
- echo "应用已卸载"
- }
+ docker run -d \
+ --name filebrowser \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/filebrowser/data:/data \
+ -v /home/docker/filebrowser/config:/config \
+ -e FB_BASEURL=/filebrowser \
+ hurlenko/filebrowser
- docker_app_plus
- ;;
+ }
- 48)
- local docker_name="node-exporter"
- local docker_img="prom/node-exporter"
- local docker_port=8048
+ local docker_describe="是一个基于Web的文件管理器"
+ local docker_url="官网介绍: https://filebrowser.org/"
+ local docker_use="docker logs filebrowser"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run -d \
- --name=node-exporter \
- -p ${docker_port}:9100 \
- --restart unless-stopped \
- prom/node-exporter
+ 93|dufs)
+ local app_id="93"
+ local docker_name="dufs"
+ local docker_img="sigoden/dufs"
+ local docker_port=8093
- }
+ docker_rum() {
- local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/prometheus/node_exporter"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
-
- 49)
- local docker_name="cadvisor"
- local docker_img="gcr.io/cadvisor/cadvisor:latest"
- local docker_port=8049
-
- docker_rum() {
-
- docker run -d \
- --name=cadvisor \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- --volume=/:/rootfs:ro \
- --volume=/var/run:/var/run:rw \
- --volume=/sys:/sys:ro \
- --volume=/var/lib/docker/:/var/lib/docker:ro \
- gcr.io/cadvisor/cadvisor:latest \
- -housekeeping_interval=10s \
- -docker_only=true
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}:/data \
+ -p ${docker_port}:5000 \
+ ${docker_img} /data -A
- }
+ }
- local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/google/cadvisor"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="极简静态文件服务器,支持上传下载"
+ local docker_url="官网介绍: ${gh_https_url}github.com/sigoden/dufs"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 50)
- local docker_name="changedetection"
- local docker_img="dgtlmoon/changedetection.io:latest"
- local docker_port=8050
+ 94|gopeed)
- docker_rum() {
+ local app_id="94"
+ local docker_name="gopeed"
+ local docker_img="liwei2633/gopeed"
+ local docker_port=8094
- docker run -d --restart always -p ${docker_port}:5000 \
- -v /home/docker/datastore:/datastore \
- --name changedetection dgtlmoon/changedetection.io:latest
+ docker_rum() {
- }
+ read -e -p "设置登录用户名: " app_use
+ read -e -p "设置登录密码: " app_passwd
- local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
- local docker_url="官网介绍: https://github.com/dgtlmoon/changedetection.io"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}/downloads:/app/Downloads \
+ -v /home/docker/${docker_name}/storage:/app/storage \
+ -p ${docker_port}:9999 \
+ ${docker_img} -u ${app_use} -p ${app_passwd}
+ }
- 51)
- clear
- send_stats "PVE开小鸡"
- check_disk_space 1
- curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
- ;;
+ local docker_describe="分布式高速下载工具,支持多种协议"
+ local docker_url="官网介绍: ${gh_https_url}github.com/GopeedLab/gopeed"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 52)
- local docker_name="dpanel"
- local docker_img="dpanel/dpanel:lite"
- local docker_port=8052
- docker_rum() {
- docker run -it -d --name dpanel --restart=always \
- -p ${docker_port}:8080 -e APP_NAME=dpanel \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/dpanel:/dpanel \
- dpanel/dpanel:lite
+ 95|paperless)
- }
+ local app_id="95"
- local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
- local docker_url="官网介绍: https://github.com/donknap/dpanel"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_name="paperless文档管理平台"
+ local app_text="开源的电子文档管理系统,它的主要用途是把你的纸质文件数字化并管理起来。"
+ local app_url="视频介绍: https://docs.paperless-ngx.com/"
+ local docker_name="paperless-webserver-1"
+ local docker_port="8095"
+ local app_size="2"
- 53)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ docker_app_install() {
- docker_rum() {
+ mkdir -p /home/docker/paperless
+ mkdir -p /home/docker/paperless/export
+ mkdir -p /home/docker/paperless/consume
+ cd /home/docker/paperless
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ curl -o /home/docker/paperless/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/docker-compose.postgres-tika.yml
+ curl -o /home/docker/paperless/docker-compose.env ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/.env
- }
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/paperless/docker-compose.yml
+ cd /home/docker/paperless
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run llama3.2:1b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
- 54)
+ docker_app_update() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ docker_app_install
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="AMH面板"
- local panelurl="官方地址: https://amh.sh/index.htm?amh"
- panel_app_install() {
- cd ~
- wget https://dl.amh.sh/amh.sh && bash amh.sh
- }
+ docker_app_uninstall() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ rm -rf /home/docker/paperless
+ echo "应用已卸载"
+ }
- panel_app_manage() {
- panel_app_install
- }
+ docker_app_plus
- panel_app_uninstall() {
- panel_app_install
- }
+ ;;
- install_panel
- ;;
- 55)
- frps_panel
- ;;
+ 96|2fauth)
- 56)
- frpc_panel
- ;;
+ local app_id="96"
- 57)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ local app_name="2FAuth自托管二步验证器"
+ local app_text="自托管的双重身份验证 (2FA) 账户管理和验证码生成工具。"
+ local app_url="官网: ${gh_https_url}github.com/Bubka/2FAuth"
+ local docker_name="2fauth"
+ local docker_port="8096"
+ local app_size="1"
- docker_rum() {
+ docker_app_install() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ add_yuming
- }
+ mkdir -p /home/docker/2fauth
+ mkdir -p /home/docker/2fauth/data
+ chmod -R 777 /home/docker/2fauth/
+ cd /home/docker/2fauth
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ curl -o /home/docker/2fauth/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/2fauth-docker-compose.yml
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/2fauth/docker-compose.yml
+ sed -i "s/yuming.com/${yuming}/g" /home/docker/2fauth/docker-compose.yml
+ cd /home/docker/2fauth
+ docker compose up -d
- 58)
- local app_name="Dify知识库"
- local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
- local app_url="官方网站: https://docs.dify.ai/zh-hans"
- local docker_name="docker-nginx-1"
- local docker_port="8058"
- local app_size="3"
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
- # sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
- docker_app_update() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- cd /home/docker/dify/
- git pull origin main
- sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- cd /home/docker/dify/docker/ && docker compose up -d
- }
+ docker_app_update() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ docker_app_install
+ }
- docker_app_uninstall() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- rm -rf /home/docker/dify
- echo "应用已卸载"
- }
- docker_app_plus
+ docker_app_uninstall() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ rm -rf /home/docker/2fauth
+ echo "应用已卸载"
+ }
- ;;
+ docker_app_plus
- 59)
- local app_name="New API"
- local app_text="新一代大模型网关与AI资产管理系统"
- local app_url="官方网站: https://github.com/Calcium-Ion/new-api"
- local docker_name="new-api"
- local docker_port="8059"
- local app_size="3"
+ ;;
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ 97|wgs)
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
+ local app_id="97"
+ local docker_name="wireguard"
+ local docker_img="lscr.io/linuxserver/wireguard:latest"
+ local docker_port=8097
- docker_app_update() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- cd /home/docker/new-api/
- git pull origin main
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ docker_rum() {
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
+ read -e -p "请输入组网的客户端数量 (默认 5): " COUNT
+ COUNT=${COUNT:-5}
+ read -e -p "请输入 WireGuard 网段 (默认 10.13.13.0): " NETWORK
+ NETWORK=${NETWORK:-10.13.13.0}
- }
+ PEERS=$(seq -f "wg%02g" 1 "$COUNT" | paste -sd,)
- docker_app_uninstall() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- rm -rf /home/docker/new-api
- echo "应用已卸载"
- }
+ ip link delete wg0 &>/dev/null
- docker_app_plus
+ ip_address
+ docker run -d \
+ --name=wireguard \
+ --network host \
+ --cap-add=NET_ADMIN \
+ --cap-add=SYS_MODULE \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SERVERURL=${ipv4_address} \
+ -e SERVERPORT=51820 \
+ -e PEERS=${PEERS} \
+ -e INTERNAL_SUBNET=${NETWORK} \
+ -e ALLOWEDIPS=${NETWORK}/24 \
+ -e PERSISTENTKEEPALIVE_PEERS=all \
+ -e LOG_CONFS=true \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules \
+ --restart=always \
+ lscr.io/linuxserver/wireguard:latest
- ;;
+ sleep 3
- 60)
+ docker exec wireguard sh -c "
+ f='/config/wg_confs/wg0.conf'
+ sed -i 's/51820/${docker_port}/g' \$f
+ "
- local app_name="JumpServer开源堡垒机"
- local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
- local app_url="官方介绍: https://github.com/jumpserver/jumpserver"
- local docker_name="jms_web"
- local docker_port="80"
- local app_size="2"
+ docker exec wireguard sh -c "
+ for d in /config/peer_*; do
+ sed -i 's/51820/${docker_port}/g' \$d/*.conf
+ done
+ "
- docker_app_install() {
- curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
- clear
- echo "已经安装完成"
- check_docker_app_ip
- echo "初始用户名: admin"
- echo "初始密码: ChangeMe"
- }
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ sed -i "/^DNS/d" "$d"/*.conf
+ done
+ '
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ for f in "$d"/*.conf; do
+ grep -q "^PersistentKeepalive" "$f" || \
+ sed -i "/^AllowedIPs/ a PersistentKeepalive = 25" "$f"
+ done
+ done
+ '
+
+ docker exec wireguard bash -c '
+ for d in /config/peer_*; do
+ cd "$d" || continue
+ conf_file=$(ls *.conf)
+ base_name="${conf_file%.conf}"
+ qrencode -o "$base_name.png" < "$conf_file"
+ done
+ '
- docker_app_update() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh upgrade
- echo "应用已更新"
- }
+ docker restart wireguard
+ sleep 2
+ echo
+ echo -e "${gl_huang}所有客户端二维码配置: ${gl_bai}"
+ docker exec wireguard bash -c 'for i in $(ls /config | grep peer_ | sed "s/peer_//"); do echo "--- $i ---"; /app/show-peer $i; done'
+ sleep 2
+ echo
+ echo -e "${gl_huang}所有客户端配置代码: ${gl_bai}"
+ docker exec wireguard sh -c 'for d in /config/peer_*; do echo "# $(basename $d) "; cat $d/*.conf; echo; done'
+ sleep 2
+ echo -e "${gl_lv}${COUNT}个客户端配置全部输出,使用方法如下:${gl_bai}"
+ echo -e "${gl_lv}1. 手机下载wg的APP,扫描上方二维码,可以快速连接网络${gl_bai}"
+ echo -e "${gl_lv}2. Windows下载客户端,复制配置代码连接网络。${gl_bai}"
+ echo -e "${gl_lv}3. Linux用脚本部署WG客户端,复制配置代码连接网络。${gl_bai}"
+ echo -e "${gl_lv}官方客户端下载方式: https://www.wireguard.com/install/${gl_bai}"
+ break_end
- docker_app_uninstall() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh uninstall
- cd /opt
- rm -rf jumpserver-installer*/
- rm -rf jumpserver
- echo "应用已卸载"
- }
+ }
- docker_app_plus
- ;;
+ local docker_describe="现代化、高性能的虚拟专用网络工具"
+ local docker_url="官网介绍: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 61)
- local docker_name="libretranslate"
- local docker_img="libretranslate/libretranslate:latest"
- local docker_port=8061
+ ;;
- docker_rum() {
- docker run -d \
- -p ${docker_port}:5000 \
- --name libretranslate \
- libretranslate/libretranslate \
- --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+ 98|wgc)
- }
+ local app_id="98"
+ local docker_name="wireguardc"
+ local docker_img="kjlion/wireguard:alpine"
+ local docker_port=51820
- local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
- local docker_url="官网介绍: https://github.com/LibreTranslate/LibreTranslate"
- local docker_use=""
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_rum() {
+ mkdir -p /home/docker/wireguard/config/
+ local CONFIG_FILE="/home/docker/wireguard/config/wg0.conf"
- 62)
- local app_name="RAGFlow知识库"
- local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
- local app_url="官方网站: https://github.com/infiniflow/ragflow"
- local docker_name="ragflow-server"
- local docker_port="8062"
- local app_size="8"
+ # 创建目录(如果不存在)
+ mkdir -p "$(dirname "$CONFIG_FILE")"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
+ echo "请粘贴你的客户端配置,连续按两次回车保存:"
- docker_app_update() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- cd /home/docker/ragflow/
- git pull origin main
- cd /home/docker/ragflow/docker/
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- }
+ # 初始化变量
+ input=""
+ empty_line_count=0
- docker_app_uninstall() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- rm -rf /home/docker/ragflow
- echo "应用已卸载"
- }
+ # 逐行读取用户输入
+ while IFS= read -r line; do
+ if [[ -z "$line" ]]; then
+ ((empty_line_count++))
+ if [[ $empty_line_count -ge 2 ]]; then
+ break
+ fi
+ else
+ empty_line_count=0
+ input+="$line"$'\n'
+ fi
+ done
- docker_app_plus
+ # 写入配置文件
+ echo "$input" > "$CONFIG_FILE"
- ;;
+ echo "客户端配置已保存到 $CONFIG_FILE"
+ ip link delete wg0 &>/dev/null
- 63)
- local docker_name="open-webui"
- local docker_img="ghcr.io/open-webui/open-webui:main"
- local docker_port=8063
+ docker run -d \
+ --name wireguardc \
+ --network host \
+ --cap-add NET_ADMIN \
+ --cap-add SYS_MODULE \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules:ro \
+ --restart=always \
+ kjlion/wireguard:alpine
- docker_rum() {
+ sleep 3
- docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+ docker logs wireguardc
- }
+ break_end
- local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use=""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ }
- 64)
- local docker_name="it-tools"
- local docker_img="corentinth/it-tools:latest"
- local docker_port=8064
+ local docker_describe="现代化、高性能的虚拟专用网络工具"
+ local docker_url="官网介绍: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
- docker run -d --name it-tools --restart unless-stopped -p ${docker_port}:80 corentinth/it-tools:latest
- }
+ ;;
- local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
- local docker_url="官网介绍: https://github.com/CorentinTh/it-tools"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 99|dsm)
- 65)
- local docker_name="n8n"
- local docker_img="docker.n8n.io/n8nio/n8n"
- local docker_port=8065
+ local app_id="99"
- docker_rum() {
+ local app_name="dsm群晖虚拟机"
+ local app_text="Docker容器中的虚拟DSM"
+ local app_url="官网: ${gh_https_url}github.com/vdsm/virtual-dsm"
+ local docker_name="dsm"
+ local docker_port="8099"
+ local app_size="16"
- add_yuming
- mkdir -p /home/docker/n8n
- chmod -R 777 /home/docker/n8n
-
- docker run -d --name n8n \
- --restart always \
- -p ${docker_port}:5678 \
- -v /home/docker/n8n:/home/node/.n8n \
- -e N8N_HOST=${yuming} \
- -e N8N_PORT=5678 \
- -e N8N_PROTOCOL=https \
- -e N8N_WEBHOOK_URL=https://${yuming}/ \
- docker.n8n.io/n8nio/n8n
+ docker_app_install() {
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ read -e -p "设置 CPU 核数 (默认 2): " CPU_CORES
+ local CPU_CORES=${CPU_CORES:-2}
- }
+ read -e -p "设置内存大小 (默认 4G): " RAM_SIZE
+ local RAM_SIZE=${RAM_SIZE:-4}
- local docker_describe="是一款功能强大的自动化工作流平台"
- local docker_url="官网介绍: https://github.com/n8n-io/n8n"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/dsm
+ mkdir -p /home/docker/dsm/dev
+ chmod -R 777 /home/docker/dsm/
+ cd /home/docker/dsm
- 66)
- yt_menu_pro
- ;;
+ curl -o /home/docker/dsm/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/dsm-docker-compose.yml
+ sed -i "s/5000:5000/${docker_port}:5000/g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|CPU_CORES: "2"|CPU_CORES: "${CPU_CORES}"|g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|RAM_SIZE: "2G"|RAM_SIZE: "${RAM_SIZE}G"|g" /home/docker/dsm/docker-compose.yml
+ cd /home/docker/dsm
+ docker compose up -d
- 67)
- local docker_name="ddns-go"
- local docker_img="jeessy/ddns-go"
- local docker_port=8067
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- docker_rum() {
- docker run -d \
- --name ddns-go \
- --restart=always \
- -p ${docker_port}:9876 \
- -v /home/docker/ddns-go:/root \
- jeessy/ddns-go
- }
+ docker_app_update() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ docker_app_install
+ }
- local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
- local docker_url="官网介绍: https://github.com/jeessy2/ddns-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 68)
- local docker_name="allinssl"
- local docker_img="allinssl/allinssl:latest"
- local docker_port=8068
+ docker_app_uninstall() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ rm -rf /home/docker/dsm
+ echo "应用已卸载"
+ }
- docker_rum() {
- docker run -itd --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
- }
+ docker_app_plus
- local docker_describe="开源免费的 SSL 证书自动化管理平台"
- local docker_url="官网介绍: https://allinssl.com"
- local docker_use="echo \"安全入口: /allinssl\""
- local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 69)
- local docker_name="sftpgo"
- local docker_img="drakkan/sftpgo:latest"
- local docker_port=8069
- docker_rum() {
+ 100|syncthing)
- mkdir -p /home/docker/sftpgo/data
- mkdir -p /home/docker/sftpgo/config
- chown -R 1000:1000 /home/docker/sftpgo
+ local app_id="100"
+ local docker_name="syncthing"
+ local docker_img="syncthing/syncthing:latest"
+ local docker_port=8100
- docker run -d \
- --name sftpgo \
- --restart=always \
- -p ${docker_port}:8080 \
- -p 22022:2022 \
- --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
- --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
- drakkan/sftpgo:latest
+ docker_rum() {
+ docker run -d \
+ --name=syncthing \
+ --hostname=my-syncthing \
+ --restart=always \
+ -p ${docker_port}:8384 \
+ -p 22000:22000/tcp \
+ -p 22000:22000/udp \
+ -p 21027:21027/udp \
+ -v /home/docker/syncthing:/var/syncthing \
+ syncthing/syncthing:latest
+ }
- }
+ local docker_describe="开源的点对点文件同步工具,类似于 Dropbox、Resilio Sync,但完全去中心化。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/syncthing/syncthing"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
- local docker_url="官网介绍: https://sftpgo.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 70)
- local docker_name="astrbot"
- local docker_img="soulter/astrbot:latest"
- local docker_port=8070
+ 101|moneyprinterturbo)
+ local app_id="101"
+ local app_name="AI视频生成工具"
+ local app_text="MoneyPrinterTurbo是一款使用AI大模型合成高清短视频的工具"
+ local app_url="官方网站: ${gh_https_url}github.com/harry0703/MoneyPrinterTurbo"
+ local docker_name="moneyprinterturbo"
+ local docker_port="8101"
+ local app_size="3"
- docker_rum() {
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git && cd MoneyPrinterTurbo/
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
- mkdir -p /home/docker/astrbot/data
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- sudo docker run -d \
- -p ${docker_port}:6185 \
- -p 6195:6195 \
- -p 6196:6196 \
- -p 6199:6199 \
- -p 11451:11451 \
- -v /home/docker/astrbot/data:/AstrBot/data \
- --restart unless-stopped \
- --name astrbot \
- soulter/astrbot:latest
+ docker_app_update() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ cd /home/docker/MoneyPrinterTurbo/
- }
+ git pull ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose up -d
+ }
- local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
- local docker_url="官网介绍: https://astrbot.app/"
- local docker_use="echo \"用户名: astrbot 密码: astrbot\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_uninstall() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ rm -rf /home/docker/MoneyPrinterTurbo
+ echo "应用已卸载"
+ }
+ docker_app_plus
- 71)
- local docker_name="navidrome"
- local docker_img="deluan/navidrome:latest"
- local docker_port=8071
+ ;;
- docker_rum() {
- docker run -d \
- --name navidrome \
- --restart=unless-stopped \
- --user $(id -u):$(id -g) \
- -v /home/docker/navidrome/music:/music \
- -v /home/docker/navidrome/data:/data \
- -p ${docker_port}:4533 \
- -e ND_LOGLEVEL=info \
- deluan/navidrome:latest
- }
+ 102|vocechat)
- local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
- local docker_url="官网介绍: https://www.navidrome.org/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="102"
+ local docker_name="vocechat-server"
+ local docker_img="privoce/vocechat-server:latest"
+ local docker_port=8102
+ docker_rum() {
- 72)
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ --name vocechat-server \
+ -v /home/docker/vocechat/data:/home/vocechat-server/data \
+ privoce/vocechat-server:latest
- local docker_name="bitwarden"
- local docker_img="vaultwarden/server"
- local docker_port=8072
+ }
- docker_rum() {
+ local docker_describe="是一款支持独立部署的个人云社交媒体聊天服务"
+ local docker_url="官网介绍: ${gh_https_url}github.com/Privoce/vocechat-web"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d \
- --name bitwarden \
- --restart always \
- -p ${docker_port}:80 \
- -v /home/docker/bitwarden/data:/data \
- vaultwarden/server
+ ;;
- }
- local docker_describe="一个你可以控制数据的密码管理器"
- local docker_url="官网介绍: https://bitwarden.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ 103|umami)
+ local app_id="103"
+ local app_name="Umami网站统计工具"
+ local app_text="开源、轻量、隐私友好的网站分析工具,类似于GoogleAnalytics。"
+ local app_url="官方网站: ${gh_https_url}github.com/umami-software/umami"
+ local docker_name="umami-umami-1"
+ local docker_port="8103"
+ local app_size="1"
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/umami-software/umami.git && cd umami
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/umami/docker-compose.yml
- ;;
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ echo "初始用户名: admin"
+ echo "初始密码: umami"
+ }
+ docker_app_update() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ cd /home/docker/umami/
+ git pull ${gh_proxy}github.com/umami-software/umami.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/umami/docker-compose.yml
+ cd /home/docker/umami/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ rm -rf /home/docker/umami
+ echo "应用已卸载"
+ }
- 73)
+ docker_app_plus
- local docker_name="libretv"
- local docker_img="bestzwei/libretv:latest"
- local docker_port=8073
+ ;;
- docker_rum() {
+ 104|nginx-stream)
+ stream_panel
+ ;;
- read -e -p "设置LibreTV的登录密码: " app_passwd
- docker run -d \
- --name libretv \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -e PASSWORD=${app_passwd} \
- bestzwei/libretv:latest
+ 105|siyuan)
- }
+ local app_id="105"
+ local docker_name="siyuan"
+ local docker_img="b3log/siyuan"
+ local docker_port=8105
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/LibreSpark/LibreTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ read -e -p "设置登录密码: " app_passwd
+ docker run -d \
+ --name siyuan \
+ --restart=always \
+ -v /home/docker/siyuan/workspace:/siyuan/workspace \
+ -p ${docker_port}:6806 \
+ -e PUID=1001 \
+ -e PGID=1002 \
+ b3log/siyuan \
+ --workspace=/siyuan/workspace/ \
+ --accessAuthCode="${app_passwd}"
- 74)
+ }
- local docker_name="moontv"
- local docker_img="ghcr.io/senshinya/moontv:latest"
- local docker_port=8074
+ local docker_describe="思源笔记是一款隐私优先的知识管理系统"
+ local docker_url="官网介绍: ${gh_https_url}github.com/siyuan-note/siyuan"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- read -e -p "设置MoonTV的登录密码: " app_passwd
- docker run -d \
- --name moontv \
- --restart unless-stopped \
- -p ${docker_port}:3000 \
- -e PASSWORD=${app_passwd} \
- ghcr.io/senshinya/moontv:latest
+ 106|drawnix)
- }
+ local app_id="106"
+ local docker_name="drawnix"
+ local docker_img="pubuzhixing/drawnix"
+ local docker_port=8106
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/senshinya/MoonTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ docker run -d \
+ --restart=always \
+ --name drawnix \
+ -p ${docker_port}:80 \
+ pubuzhixing/drawnix
+ }
- 75)
+ local docker_describe="是一款强大的开源白板工具,集成思维导图、流程图等。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/plait-board/drawnix"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="melody"
- local docker_img="foamzou/melody:latest"
- local docker_port=8075
+ ;;
- docker_rum() {
- docker run -d \
- --name melody \
- --restart unless-stopped \
- -p ${docker_port}:5566 \
- -v /home/docker/melody/.profile:/app/backend/.profile \
- foamzou/melody:latest
+ 107|pansou)
+ local app_id="107"
+ local docker_name="pansou"
+ local docker_img="ghcr.io/fish2018/pansou-web"
+ local docker_port=8107
- }
+ docker_rum() {
- local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
- local docker_url="官网介绍: https://github.com/foamzou/melody"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d \
+ --name pansou \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/pansou/data:/app/data \
+ -v /home/docker/pansou/logs:/app/logs \
+ -e ENABLED_PLUGINS="hunhepan,jikepan,panwiki,pansearch,panta,qupansou,
+susu,thepiratebay,wanou,xuexizhinan,panyq,zhizhen,labi,muou,ouge,shandian,
+duoduo,huban,cyg,erxiao,miaoso,fox4k,pianku,clmao,wuji,cldi,xiaozhang,
+libvio,leijing,xb6v,xys,ddys,hdmoli,yuhuage,u3c3,javdb,clxiong,jutoushe,
+sdso,xiaoji,xdyh,haisou,bixin,djgou,nyaa,xinjuc,aikanzy,qupanshe,xdpan,
+discourse,yunsou,ahhhhfs,nsgame,gying" \
+ ghcr.io/fish2018/pansou-web
+ }
- ;;
+ local docker_describe="PanSou是一个高性能的网盘资源搜索API服务。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/fish2018/pansou"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 76)
- local docker_name="dosgame"
- local docker_img="oldiy/dosgame-web-docker:latest"
- local docker_port=8076
- docker_rum() {
- docker run -d \
- --name dosgame \
- --restart unless-stopped \
- -p ${docker_port}:262 \
- oldiy/dosgame-web-docker:latest
- }
+ 108|langbot)
+ local app_id="108"
+ local app_name="LangBot聊天机器人"
+ local app_text="是一个开源的大语言模型原生即时通信机器人开发平台"
+ local app_url="官方网站: ${gh_https_url}github.com/langbot-app/LangBot"
+ local docker_name="langbot_plugin_runtime"
+ local docker_port="8108"
+ local app_size="1"
- local docker_describe="是一个中文DOS游戏合集网站"
- local docker_url="官网介绍: https://github.com/rwv/chinese-dos-games"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langbot-app/LangBot && cd LangBot/docker
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- ;;
+ docker_app_update() {
+ cd /home/docker/LangBot/docker && docker compose down --rmi all
+ cd /home/docker/LangBot/
+ git pull ${gh_proxy}github.com/langbot-app/LangBot main > /dev/null 2>&1
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ cd /home/docker/LangBot/docker/ && docker compose up -d
+ }
- 77)
+ docker_app_uninstall() {
+ cd /home/docker/LangBot/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/LangBot
+ echo "应用已卸载"
+ }
- local docker_name="xunlei"
- local docker_img="cnk3x/xunlei"
- local docker_port=8077
+ docker_app_plus
- docker_rum() {
+ ;;
- read -e -p "设置${docker_name}的登录用户名: " app_use
- read -e -p "设置${docker_name}的登录密码: " app_passwd
- docker run -d \
- --name xunlei \
- --restart unless-stopped \
- --privileged \
- -e XL_DASHBOARD_USERNAME=${app_use} \
- -e XL_DASHBOARD_PASSWORD=${app_passwd} \
- -v /home/docker/xunlei/data:/xunlei/data \
- -v /home/docker/xunlei/downloads:/xunlei/downloads \
- -p ${docker_port}:2345 \
- cnk3x/xunlei
+ 109|zfile)
- }
+ local app_id="109"
+ local docker_name="zfile"
+ local docker_img="zhaojun1998/zfile:latest"
+ local docker_port=8109
- local docker_describe="迅雷你的离线高速BT磁力下载工具"
- local docker_url="官网介绍: https://github.com/cnk3x/xunlei"
- local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ docker run -d --name=zfile --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/zfile/db:/root/.zfile-v4/db \
+ -v /home/docker/zfile/logs:/root/.zfile-v4/logs \
+ -v /home/docker/zfile/file:/data/file \
+ -v /home/docker/zfile/application.properties:/root/.zfile-v4/application.properties \
+ zhaojun1998/zfile:latest
- 78)
+ }
- local app_name="PandaWiki"
- local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
- local app_url="官方介绍: https://github.com/chaitin/PandaWiki"
- local docker_name="panda-wiki-nginx"
- local docker_port="2443"
- local app_size="2"
+ local docker_describe="是一个适用于个人或小团队的在线网盘程序。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/zfile-dev/zfile"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_app_install() {
- bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
- }
+ ;;
- docker_app_update() {
- docker_app_install
- }
+ 110|karakeep)
+ local app_id="110"
+ local app_name="karakeep书签管理"
+ local app_text="是一款可自行托管的书签应用,带有人工智能功能,专为数据囤积者而设计。"
+ local app_url="官方网站: ${gh_https_url}github.com/karakeep-app/karakeep"
+ local docker_name="docker-web-1"
+ local docker_port="8110"
+ local app_size="1"
- docker_app_uninstall() {
- docker_app_install
- }
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/karakeep-app/karakeep.git && cd karakeep/docker && cp .env.sample .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
- docker_app_plus
- ;;
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+ docker_app_update() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ cd /home/docker/karakeep/
+ git pull ${gh_proxy}github.com/karakeep-app/karakeep.git main > /dev/null 2>&1
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ cd /home/docker/karakeep/docker/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/karakeep
+ echo "应用已卸载"
+ }
- 79)
+ docker_app_plus
- local docker_name="beszel"
- local docker_img="henrygd/beszel"
- local docker_port=8079
+ ;;
- docker_rum() {
- mkdir -p /home/docker/beszel && \
- docker run -d \
- --name beszel \
- --restart=unless-stopped \
- -v /home/docker/beszel:/beszel_data \
- -p ${docker_port}:8090 \
- henrygd/beszel
- }
+ 111|convertx)
- local docker_describe="Beszel轻量易用的服务器监控"
- local docker_url="官网介绍: https://beszel.dev/zh/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="111"
+ local docker_name="convertx"
+ local docker_img="ghcr.io/c4illin/convertx:latest"
+ local docker_port=8111
- ;;
+ docker_rum() {
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/convertx:/app/data \
+ ${docker_img}
- 80)
- local app_name="linkwarden书签管理"
- local app_text="一个开源的自托管书签管理平台,支持标签、搜索和团队协作。"
- local app_url="官方网站: https://linkwarden.app/"
- local docker_name="linkwarden-linkwarden-1"
- local docker_port="8080"
- local app_size="3"
+ }
- docker_app_install() {
- install git openssl
- mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
+ local docker_describe="是一个功能强大的多格式文件转换工具(支持文档、图像、音频视频等)强烈建议添加域名访问"
+ local docker_url="项目地址: ${gh_https_url}github.com/c4illin/ConvertX"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- # 下载官方 docker-compose 和 env 文件
- curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
- curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
+ ;;
- # 生成随机密钥与密码
- local ADMIN_EMAIL="admin@example.com"
- local ADMIN_PASSWORD=$(openssl rand -hex 8)
- sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
- sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
- sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
- sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
+ 112|lucky)
- # 追加管理员账号信息
- echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
- echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
+ local app_id="112"
+ local docker_name="lucky"
+ local docker_img="gdy666/lucky:v2"
+ # 由于 Lucky 使用 host 网络模式,这里的端口仅作记录/说明参考,实际由应用自身控制(默认16601)
+ local docker_port=8112
- sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+ docker_rum() {
- # 启动容器
- docker compose up -d
+ docker run -d --name=${docker_name} --restart=always \
+ --network host \
+ -v /home/docker/lucky/conf:/app/conf \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ ${docker_img}
- clear
- echo "已经安装完成"
- check_docker_app_ip
+ echo "正在等待 Lucky 初始化..."
+ sleep 10
+ docker exec lucky /app/lucky -rSetHttpAdminPort ${docker_port}
- }
+ }
- docker_app_update() {
- cd /home/docker/linkwarden && docker compose down --rmi all
- curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
- curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
-
- # 保留原本的变量
- source .env
- mv .env.new .env
- echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
- echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
- echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
- echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
- echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
- echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
- sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
-
- docker compose up -d
- }
+ local docker_describe="Lucky 是一个大内网穿透及端口转发管理工具,支持 DDNS、反向代理、WOL 等功能。"
+ local docker_url="项目地址: ${gh_https_url}github.com/gdy666/lucky"
+ local docker_use="echo \"默认账号密码: 666\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_app_uninstall() {
- cd /home/docker/linkwarden && docker compose down --rmi all
- rm -rf /home/docker/linkwarden
- echo "应用已卸载"
- }
+ ;;
- docker_app_plus
- ;;
+ 113|firefox)
+ local app_id="113"
+ local docker_name="firefox"
+ local docker_img="jlesage/firefox:latest"
+ local docker_port=8113
+ docker_rum() {
- 81)
- local app_name="JitsiMeet视频会议"
- local app_text="一个开源的安全视频会议解决方案,支持多人在线会议、屏幕共享与加密通信。"
- local app_url="官方网站: https://jitsi.org/"
- local docker_name="jitsi"
- local docker_port="8081"
- local app_size="3"
+ read -e -p "设置登录密码: " admin_password
- docker_app_install() {
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:5800 \
+ -v /home/docker/firefox:/config:rw \
+ -e ENABLE_CJK_FONT=1 \
+ -e WEB_AUDIO=1 \
+ -e VNC_PASSWORD="${admin_password}" \
+ ${docker_img}
+ }
- add_yuming
- mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
- wget $(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep zip | cut -d\" -f4)
- unzip "$(ls -t | head -n 1)"
- cd "$(ls -dt */ | head -n 1)"
- cp env.example .env
- ./gen-passwords.sh
- mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
- sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
- sed -i "s|^#PUBLIC_URL=https://meet.example.com:\${HTTPS_PORT}|PUBLIC_URL=https://$yuming:443|" .env
- docker compose up -d
+ local docker_describe="是一个运行在 Docker 中的 Firefox 浏览器,支持通过网页直接访问桌面版浏览器界面。"
+ local docker_url="项目地址: ${gh_https_url}github.com/jlesage/docker-firefox"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ ;;
- }
+ 114|Moltbot|ClawdBot|moltbot|clawdbot|openclaw|OpenClaw)
+ moltbot_menu
+ ;;
- docker_app_update() {
- cd /home/docker/jitsi
- cd "$(ls -dt */ | head -n 1)"
- docker compose down --rmi all
- docker compose up -d
+ 115|hermes)
+ bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/hermes_manager.sh)
+ ;;
- }
+ b)
+ clear
+ send_stats "全部应用备份"
- docker_app_uninstall() {
- cd /home/docker/jitsi
- cd "$(ls -dt */ | head -n 1)"
- docker compose down --rmi all
- rm -rf /home/docker/jitsi
- echo "应用已卸载"
- }
+ local backup_filename="app_$(date +"%Y%m%d%H%M%S").tar.gz"
+ echo -e "${gl_kjlan}正在备份 $backup_filename ...${gl_bai}"
+ cd / && tar czvf "$backup_filename" home
- docker_app_plus
+ while true; do
+ clear
+ echo "备份文件已创建: /$backup_filename"
+ read -e -p "要传送备份数据到远程服务器吗?(Y/N): " choice
+ case "$choice" in
+ [Yy])
+ kj_ssh_read_host_port "请输入远端服务器IP: " "目标服务器SSH端口 [默认22]: " "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
+ local latest_tar=$(ls -t /app*.tar.gz | head -1)
+ if [ -n "$latest_tar" ]; then
+ ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
+ sleep 2 # 添加等待时间
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/"
+ echo "文件已传送至远程服务器/根目录。"
+ else
+ echo "未找到要传送的文件。"
+ fi
+ break
+ ;;
+ *)
+ echo "注意: 目前备份仅包含docker项目,不包含宝塔,1panel等建站面板的数据备份。"
+ break
+ ;;
+ esac
+ done
- ;;
+ ;;
+ r)
+ root_use
+ send_stats "全部应用还原"
+ echo "可用的应用备份"
+ echo "-------------------------"
+ ls -lt /app*.gz | awk '{print $NF}'
+ echo ""
+ read -e -p "回车键还原最新的备份,输入备份文件名还原指定的备份,输入0退出:" filename
+ if [ "$filename" == "0" ]; then
+ break_end
+ linux_panel
+ fi
+
+ # 如果用户没有输入文件名,使用最新的压缩包
+ if [ -z "$filename" ]; then
+ local filename=$(ls -t /app*.tar.gz | head -1)
+ fi
+
+ if [ -n "$filename" ]; then
+ echo -e "${gl_kjlan}正在解压 $filename ...${gl_bai}"
+ cd / && tar -xzf "$filename"
+ echo "应用数据已还原,目前请手动进入指定应用菜单,更新应用,即可还原应用。"
+ else
+ echo "没有找到压缩包。"
+ fi
+ ;;
- 0)
- kejilion
- ;;
- *)
- echo "无效的输入!"
- ;;
- esac
- break_end
+ 0)
+ kejilion
+ ;;
+ *)
+ cd ~
+ install git
+ if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+ else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+ fi
+ local custom_app="$HOME/apps/${sub_choice}.conf"
+ if [ -f "$custom_app" ]; then
+ . "$custom_app"
+ else
+ echo -e "${gl_hong}错误: 未找到编号为 ${sub_choice} 的应用配置${gl_bai}"
+ fi
+ ;;
+ esac
+ break_end
+ sub_choice=""
- done
+done
}
+
linux_work() {
while true; do
@@ -10977,37 +19599,527 @@ linux_work() {
done
;;
- 22)
- read -e -p "请输入你创建或进入的工作区名称,如1001 kj001 work1: " SESSION_NAME
- tmux_run
- send_stats "自定义工作区"
- ;;
+ 22)
+ read -e -p "请输入你创建或进入的工作区名称,如1001 kj001 work1: " SESSION_NAME
+ tmux_run
+ send_stats "自定义工作区"
+ ;;
+
+
+ 23)
+ read -e -p "请输入你要后台执行的命令,如:curl -fsSL https://get.docker.com | sh: " tmuxd
+ tmux_run_d
+ send_stats "注入命令到后台工作区"
+ ;;
+
+ 24)
+ read -e -p "请输入要删除的工作区名称: " gongzuoqu_name
+ tmux kill-window -t $gongzuoqu_name
+ send_stats "删除工作区"
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ echo "无效的输入!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+# 智能切换镜像源函数
+switch_mirror() {
+ # 可选参数,默认为 false
+ local upgrade_software=${1:-false}
+ local clean_cache=${2:-false}
+
+ # 获取用户国家
+ local country
+ country=$(curl -s ipinfo.io/country)
+
+ echo "检测到国家:$country"
+
+ if [ "$country" = "CN" ]; then
+ echo "使用国内镜像源..."
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.huaweicloud.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ echo "使用海外镜像源..."
+ if [ -f /etc/os-release ] && grep -qi "oracle" /etc/os-release; then
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.xtom.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --use-official-source true \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ fi
+ fi
+}
+
+
+fail2ban_panel() {
+ root_use
+ send_stats "ssh防御"
+ while true; do
+
+ check_f2b_status
+ echo -e "SSH防御程序 $check_f2b_status"
+ echo "fail2ban是一个SSH防止暴力破解工具"
+ echo "官网介绍: ${gh_proxy}github.com/fail2ban/fail2ban"
+ echo "------------------------"
+ echo "1. 安装防御程序"
+ echo "------------------------"
+ echo "2. 查看SSH拦截记录"
+ echo "3. 日志实时监控"
+ echo "------------------------"
+ echo "4. 基础参数配置(封禁时长/时间窗口/重试次数)"
+ echo "5. 编辑配置文件(nano)"
+ echo "------------------------"
+ echo "9. 卸载防御程序"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "请输入你的选择: " sub_choice
+ case $sub_choice in
+ 1)
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ break_end
+ ;;
+ 2)
+ echo "------------------------"
+ f2b_sshd
+ echo "------------------------"
+ break_end
+ ;;
+ 3)
+ tail -f /var/log/fail2ban.log
+ break
+ ;;
+ 4)
+ send_stats "SSH防御基础参数配置"
+ f2b_basic_config
+ break_end
+ ;;
+ 5)
+ send_stats "SSH防御编辑配置文件"
+ f2b_edit_config
+ break_end
+ ;;
+ 9)
+ remove fail2ban
+ rm -rf /etc/fail2ban
+ echo "Fail2Ban防御程序已卸载"
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+}
+
+
+
+
+
+net_menu() {
+
+ send_stats "网卡管理工具"
+ show_nics() {
+ echo "================ 当前网卡信息 ================"
+ printf "%-18s %-12s %-20s %-26s\n" "网卡名" "状态" "IP地址" "MAC地址"
+ echo "------------------------------------------------"
+ for nic in $(ls /sys/class/net); do
+ state=$(cat /sys/class/net/$nic/operstate 2>/dev/null)
+ ipaddr=$(ip -4 addr show $nic | awk '/inet /{print $2}' | head -n1)
+ mac=$(cat /sys/class/net/$nic/address 2>/dev/null)
+ printf "%-15s %-10s %-18s %-20s\n" "$nic" "$state" "${ipaddr:-无}" "$mac"
+ done
+ echo "================================================"
+ }
+
+ while true; do
+ clear
+ show_nics
+ echo
+ echo "=========== 网卡管理菜单 ==========="
+ echo "1. 启用网卡"
+ echo "2. 禁用网卡"
+ echo "3. 查看网卡详细信息"
+ echo "4. 刷新网卡信息"
+ echo "0. 返回上一级选单"
+ echo "===================================="
+ read -erp "请选择操作: " choice
+
+ case $choice in
+ 1)
+ send_stats "启用网卡"
+ read -erp "请输入要启用的网卡名: " nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" up && echo "✔ 网卡 $nic 已启用"
+ else
+ echo "✘ 网卡不存在"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 2)
+ send_stats "禁用网卡"
+ read -erp "请输入要禁用的网卡名: " nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" down && echo "✔ 网卡 $nic 已禁用"
+ else
+ echo "✘ 网卡不存在"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 3)
+ send_stats "查看网卡详情"
+ read -erp "请输入要查看的网卡名: " nic
+ if ip link show "$nic" &>/dev/null; then
+ echo "========== $nic 详细信息 =========="
+ ip addr show "$nic"
+ ethtool "$nic" 2>/dev/null | head -n 10
+ else
+ echo "✘ 网卡不存在"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 4)
+ send_stats "刷新网卡信息"
+ continue
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+log_menu() {
+ send_stats "系统日志管理工具"
+
+ show_log_overview() {
+ echo "============= 系统日志概览 ============="
+ echo "主机名: $(hostname)"
+ echo "系统时间: $(date)"
+ echo
+ echo "[ /var/log 目录占用 ]"
+ du -sh /var/log 2>/dev/null
+ echo
+ echo "[ journal 日志占用 ]"
+ journalctl --disk-usage 2>/dev/null
+ echo "========================================"
+ }
+
+ while true; do
+ clear
+ show_log_overview
+ echo
+ echo "=========== 系统日志管理菜单 ==========="
+ echo "1. 查看最近系统日志(journal)"
+ echo "2. 查看指定服务日志"
+ echo "3. 查看登录/安全日志"
+ echo "4. 实时跟踪日志"
+ echo "5. 清理旧 journal 日志"
+ echo "0. 返回上一级选单"
+ echo "======================================="
+ read -erp "请选择操作: " choice
+
+ case $choice in
+ 1)
+ send_stats "查看最近日志"
+ read -erp "查看最近多少行日志?[默认 100]: " lines
+ lines=${lines:-100}
+ journalctl -n "$lines" --no-pager
+ read -erp "按回车继续..."
+ ;;
+ 2)
+ send_stats "查看指定服务日志"
+ read -erp "请输入服务名(如 sshd、nginx): " svc
+ if systemctl list-unit-files | grep -q "^$svc"; then
+ journalctl -u "$svc" -n 100 --no-pager
+ else
+ echo "✘ 服务不存在或无日志"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 3)
+ send_stats "查看登录/安全日志"
+ echo "====== 最近登录日志 ======"
+ last -n 10
+ echo
+ echo "====== 认证日志 ======"
+ if [ -f /var/log/secure ]; then
+ tail -n 20 /var/log/secure
+ elif [ -f /var/log/auth.log ]; then
+ tail -n 20 /var/log/auth.log
+ else
+ echo "未找到安全日志文件"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 4)
+ send_stats "实时跟踪日志"
+ echo "1) 系统日志"
+ echo "2) 指定服务日志"
+ read -erp "选择跟踪类型: " t
+ if [ "$t" = "1" ]; then
+ journalctl -f
+ elif [ "$t" = "2" ]; then
+ read -erp "输入服务名: " svc
+ journalctl -u "$svc" -f
+ else
+ echo "无效选择"
+ fi
+ ;;
+ 5)
+ send_stats "清理旧 journal 日志"
+ echo "⚠️ 清理 journal 日志(安全方式)"
+ echo "1) 保留最近 7 天"
+ echo "2) 保留最近 3 天"
+ echo "3) 限制日志最大 500M"
+ read -erp "请选择清理方式: " c
+ case $c in
+ 1) journalctl --vacuum-time=7d ;;
+ 2) journalctl --vacuum-time=3d ;;
+ 3) journalctl --vacuum-size=500M ;;
+ *) echo "无效选项" ;;
+ esac
+ echo "✔ journal 日志清理完成"
+ sleep 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+env_menu() {
+
+ BASHRC="$HOME/.bashrc"
+ PROFILE="$HOME/.profile"
+
+ send_stats "系统变量管理工具"
+
+ show_env_vars() {
+ clear
+ send_stats "当前已生效环境变量"
+ echo "========== 当前已生效环境变量(节选) =========="
+ printf "%-20s %s\n" "变量名" "值"
+ echo "-----------------------------------------------"
+ for v in USER HOME SHELL LANG PWD; do
+ printf "%-20s %s\n" "$v" "${!v}"
+ done
+
+ echo
+ echo "PATH:"
+ echo "$PATH" | tr ':' '\n' | nl -ba
+
+ echo
+ echo "========== 配置文件中定义的变量(解析) =========="
+
+ parse_file_vars() {
+ local file="$1"
+ [ -f "$file" ] || return
+
+ echo
+ echo ">>> 来源文件:$file"
+ echo "-----------------------------------------------"
+
+ # 提取 export VAR=xxx 或 VAR=xxx
+ grep -Ev '^\s*#|^\s*$' "$file" \
+ | grep -E '^(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' \
+ | while read -r line; do
+ var=$(echo "$line" | sed -E 's/^(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*).*/\2/')
+ val=$(echo "$line" | sed -E 's/^[^=]+=//')
+ printf "%-20s %s\n" "$var" "$val"
+ done
+ }
+
+ parse_file_vars "$HOME/.bashrc"
+ parse_file_vars "$HOME/.profile"
+
+ echo
+ echo "==============================================="
+ read -erp "按回车继续..."
+ }
+
+
+ view_file() {
+ local file="$1"
+ send_stats "查看变量文件 $file"
+ clear
+ if [ -f "$file" ]; then
+ echo "========== 查看文件:$file =========="
+ cat -n "$file"
+ echo "===================================="
+ else
+ echo "文件不存在:$file"
+ fi
+ read -erp "按回车继续..."
+ }
+
+ edit_file() {
+ local file="$1"
+ send_stats "编辑变量文件 $file"
+ install nano
+ nano "$file"
+ }
+
+ source_files() {
+ echo "正在重新加载环境变量..."
+ send_stats "正在重新加载环境变量"
+ source "$BASHRC"
+ source "$PROFILE"
+ echo "✔ 环境变量已重新加载"
+ read -erp "按回车继续..."
+ }
+
+ while true; do
+ clear
+ echo "=========== 系统环境变量管理 =========="
+ echo "当前用户:$USER"
+ echo "--------------------------------------"
+ echo "1. 查看当前常用环境变量"
+ echo "2. 查看 ~/.bashrc"
+ echo "3. 查看 ~/.profile"
+ echo "4. 编辑 ~/.bashrc"
+ echo "5. 编辑 ~/.profile"
+ echo "6. 重新加载环境变量(source)"
+ echo "--------------------------------------"
+ echo "0. 返回上一级选单"
+ echo "--------------------------------------"
+ read -erp "请选择操作: " choice
+
+ case "$choice" in
+ 1)
+ show_env_vars
+ ;;
+ 2)
+ view_file "$BASHRC"
+ ;;
+ 3)
+ view_file "$PROFILE"
+ ;;
+ 4)
+ edit_file "$BASHRC"
+ ;;
+ 5)
+ edit_file "$PROFILE"
+ ;;
+ 6)
+ source_files
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "无效选项"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
+create_user_with_sshkey() {
+ local new_username="$1"
+ local is_sudo="${2:-false}"
+ local sshkey_vl
- 23)
- read -e -p "请输入你要后台执行的命令,如:curl -fsSL https://get.docker.com | sh: " tmuxd
- tmux_run_d
- send_stats "注入命令到后台工作区"
- ;;
+ if [[ -z "$new_username" ]]; then
+ echo "用法:create_user_with_sshkey <用户名>"
+ return 1
+ fi
- 24)
- read -e -p "请输入要删除的工作区名称: " gongzuoqu_name
- tmux kill-window -t $gongzuoqu_name
- send_stats "删除工作区"
- ;;
+ # 创建用户
+ useradd -m -s /bin/bash "$new_username" || return 1
- 0)
- kejilion
- ;;
- *)
- echo "无效的输入!"
- ;;
- esac
- break_end
+ echo "导入公钥范例:"
+ echo " - URL: ${gh_https_url}github.com/torvalds.keys"
+ echo " - 直接粘贴: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+ read -e -p "请导入 ${new_username} 的公钥: " sshkey_vl
- done
+ case "$sshkey_vl" in
+ http://*|https://*)
+ send_stats "从 URL 导入 SSH 公钥"
+ fetch_remote_ssh_keys "$sshkey_vl" "/home/$new_username"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa*)
+ send_stats "公钥直接导入"
+ import_sshkey "$sshkey_vl" "/home/$new_username"
+ ;;
+ *)
+ echo "错误:未知参数 '$sshkey_vl'"
+ return 1
+ ;;
+ esac
+ # 修正权限
+ chown -R "$new_username:$new_username" "/home/$new_username/.ssh"
+
+ install sudo
+
+ # sudo 免密
+ if [[ "$is_sudo" == "true" ]]; then
+ cat >"/etc/sudoers.d/$new_username" <> /etc/ssh/sshd_config
+ passwd -l "$new_username" &>/dev/null
+ restart_ssh
+
+ echo "用户 $new_username 创建完成"
}
@@ -11021,6 +20133,9 @@ linux_work() {
+
+
+
linux_Settings() {
while true; do
@@ -11029,7 +20144,7 @@ linux_Settings() {
echo -e "系统工具"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}设置脚本启动快捷键 ${gl_kjlan}2. ${gl_bai}修改登录密码"
- echo -e "${gl_kjlan}3. ${gl_bai}ROOT密码登录模式 ${gl_kjlan}4. ${gl_bai}安装Python指定版本"
+ echo -e "${gl_kjlan}3. ${gl_bai}用户密码登录模式 ${gl_kjlan}4. ${gl_bai}安装Python指定版本"
echo -e "${gl_kjlan}5. ${gl_bai}开放所有端口 ${gl_kjlan}6. ${gl_bai}修改SSH连接端口"
echo -e "${gl_kjlan}7. ${gl_bai}优化DNS地址 ${gl_kjlan}8. ${gl_bai}一键重装系统 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}9. ${gl_bai}禁用ROOT账户创建新账户 ${gl_kjlan}10. ${gl_bai}切换优先ipv4/ipv6"
@@ -11041,7 +20156,7 @@ linux_Settings() {
echo -e "${gl_kjlan}19. ${gl_bai}切换系统更新源 ${gl_kjlan}20. ${gl_bai}定时任务管理"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}本机host解析 ${gl_kjlan}22. ${gl_bai}SSH防御程序"
- echo -e "${gl_kjlan}23. ${gl_bai}限流自动关机 ${gl_kjlan}24. ${gl_bai}ROOT私钥登录模式"
+ echo -e "${gl_kjlan}23. ${gl_bai}限流自动关机 ${gl_kjlan}24. ${gl_bai}用户密钥登录模式"
echo -e "${gl_kjlan}25. ${gl_bai}TG-bot系统监控预警 ${gl_kjlan}26. ${gl_bai}修复OpenSSH高危漏洞"
echo -e "${gl_kjlan}27. ${gl_bai}红帽系Linux内核升级 ${gl_kjlan}28. ${gl_bai}Linux系统内核参数优化 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}29. ${gl_bai}病毒扫描工具 ${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}文件管理器"
@@ -11050,9 +20165,11 @@ linux_Settings() {
echo -e "${gl_kjlan}33. ${gl_bai}设置系统回收站 ${gl_kjlan}34. ${gl_bai}系统备份与恢复"
echo -e "${gl_kjlan}35. ${gl_bai}ssh远程连接工具 ${gl_kjlan}36. ${gl_bai}硬盘分区管理工具"
echo -e "${gl_kjlan}37. ${gl_bai}命令行历史记录 ${gl_kjlan}38. ${gl_bai}rsync远程同步工具"
- echo -e "${gl_kjlan}39. ${gl_bai}命令收藏夹 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}39. ${gl_bai}命令收藏夹 ${gl_huang}★${gl_bai} ${gl_kjlan}40. ${gl_bai}网卡管理工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}留言板 ${gl_kjlan}66. ${gl_bai}一条龙系统调优 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}41. ${gl_bai}系统日志管理工具 ${gl_huang}★${gl_bai} ${gl_kjlan}42. ${gl_bai}系统变量管理工具"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}61. ${gl_bai}留言板 ${gl_kjlan}66. ${gl_bai}一条龙系统调优 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}99. ${gl_bai}重启服务器 ${gl_kjlan}100. ${gl_bai}隐私与安全"
echo -e "${gl_kjlan}101. ${gl_bai}k命令高级用法 ${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}卸载科技lion脚本"
echo -e "${gl_kjlan}------------------------"
@@ -11070,7 +20187,10 @@ linux_Settings() {
linux_Settings
fi
find /usr/local/bin/ -type l -exec bash -c 'test "$(readlink -f {})" = "/usr/local/bin/k" && rm -f {}' \;
- ln -s /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ if [ "$kuaijiejian" != "k" ]; then
+ ln -sf /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ fi
+ ln -sf /usr/local/bin/k /usr/bin/$kuaijiejian > /dev/null 2>&1
echo "快捷键已设置"
send_stats "脚本快捷键已设置"
break_end
@@ -11085,8 +20205,7 @@ linux_Settings() {
passwd
;;
3)
- root_use
- send_stats "root密码模式"
+ clear
add_sshpasswd
;;
@@ -11188,7 +20307,7 @@ EOF
while true; do
clear
- sed -i 's/#Port/Port/' /etc/ssh/sshd_config
+ sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
# 读取当前的 SSH 端口号
local current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
@@ -11206,7 +20325,7 @@ EOF
if [[ $new_port =~ ^[0-9]+$ ]]; then # 检查输入是否为数字
if [[ $new_port -ge 1 && $new_port -le 65535 ]]; then
send_stats "SSH端口已修改"
- new_ssh_port
+ new_ssh_port $new_port
elif [[ $new_port -eq 0 ]]; then
send_stats "退出SSH端口修改"
break
@@ -11243,14 +20362,13 @@ EOF
linux_Settings
fi
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
+ create_user_with_sshkey $new_username true
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- passwd -l root
+ ssh-keygen -l -f /home/$new_username/.ssh/authorized_keys &>/dev/null && {
+ passwd -l root &>/dev/null
+ sed -i 's/^[[:space:]]*#\?[[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
+ }
- echo "操作已完成。"
;;
@@ -11261,13 +20379,14 @@ EOF
clear
echo "设置v4/v6优先级"
echo "------------------------"
- local ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
- if [ "$ipv6_disabled" -eq 1 ]; then
+
+ if grep -Eq '^\s*precedence\s+::ffff:0:0/96\s+100\s*$' /etc/gai.conf 2>/dev/null; then
echo -e "当前网络优先级设置: ${gl_huang}IPv4${gl_bai} 优先"
else
echo -e "当前网络优先级设置: ${gl_huang}IPv6${gl_bai} 优先"
fi
+
echo ""
echo "------------------------"
echo "1. IPv4 优先 2. IPv6 优先 3. IPv6 修复工具"
@@ -11278,12 +20397,10 @@ EOF
case $choice in
1)
- sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
- echo "已切换为 IPv4 优先"
- send_stats "已切换为 IPv4 优先"
+ prefer_ipv4
;;
2)
- sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
+ rm -f /etc/gai.conf
echo "已切换为 IPv6 优先"
send_stats "已切换为 IPv6 优先"
;;
@@ -11365,7 +20482,12 @@ EOF
printf "%-24s %-34s %-20s %-10s\n" "用户名" "用户权限" "用户组" "sudo权限"
while IFS=: read -r username _ userid groupid _ _ homedir shell; do
local groups=$(groups "$username" | cut -d : -f 2)
- local sudo_status=$(sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL : ALL)' && echo "Yes" || echo "No")
+ local sudo_status
+ if sudo -n -lU "$username" 2>/dev/null | grep -q "(ALL) \(NOPASSWD: \)\?ALL"; then
+ sudo_status="Yes"
+ else
+ sudo_status="No"
+ fi
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$groups" "$sudo_status"
done < /etc/passwd
@@ -11373,7 +20495,7 @@ EOF
echo ""
echo "账户操作"
echo "------------------------"
- echo "1. 创建普通账户 2. 创建高级账户"
+ echo "1. 创建普通用户 2. 创建高级用户"
echo "------------------------"
echo "3. 赋予最高权限 4. 取消最高权限"
echo "------------------------"
@@ -11387,42 +20509,34 @@ EOF
1)
# 提示用户输入新用户名
read -e -p "请输入新用户名: " new_username
+ create_user_with_sshkey $new_username false
- # 创建新用户并设置密码
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "操作已完成。"
;;
2)
# 提示用户输入新用户名
read -e -p "请输入新用户名: " new_username
-
- # 创建新用户并设置密码
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- # 赋予新用户sudo权限
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- echo "操作已完成。"
+ create_user_with_sshkey $new_username true
;;
3)
read -e -p "请输入用户名: " username
- # 赋予新用户sudo权限
- echo "$username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ install sudo
+ cat >"/etc/sudoers.d/$username" </dev/null | xargs rm -f
+ fi
+ sed -i "/^$username\s*ALL=(ALL)/d" /etc/sudoers
;;
5)
read -e -p "请输入要删除的用户名: " username
- # 删除用户及其主目录
userdel -r "$username"
;;
@@ -11430,6 +20544,7 @@ EOF
break # 跳出循环,退出菜单
;;
esac
+
done
;;
@@ -11617,7 +20732,7 @@ EOF
echo "选择更新源区域"
echo "接入LinuxMirrors切换系统更新源"
echo "------------------------"
- echo "1. 中国大陆【默认】 2. 中国大陆【教育网】 3. 海外地区"
+ echo "1. 中国大陆【默认】 2. 中国大陆【教育网】 3. 海外地区 4. 智能切换更新源"
echo "------------------------"
echo "0. 返回上一级选单"
echo "------------------------"
@@ -11636,6 +20751,11 @@ EOF
send_stats "海外源"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --abroad
;;
+ 4)
+ send_stats "智能切换更新源"
+ switch_mirror false false
+ ;;
+
*)
echo "已取消"
;;
@@ -11747,63 +20867,7 @@ EOF
;;
22)
- root_use
- send_stats "ssh防御"
- while true; do
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
- clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "SSH防御程序 $check_docker"
- echo "fail2ban是一个SSH防止暴力破解工具"
- echo "官网介绍: ${gh_proxy}github.com/fail2ban/fail2ban"
- echo "------------------------"
- echo "1. 安装防御程序"
- echo "------------------------"
- echo "2. 查看SSH拦截记录"
- echo "3. 日志实时监控"
- echo "------------------------"
- echo "9. 卸载防御程序"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "请输入你的选择: " sub_choice
- case $sub_choice in
- 1)
- install_docker
- f2b_install_sshd
-
- cd ~
- f2b_status
- break_end
- ;;
- 2)
- echo "------------------------"
- f2b_sshd
- echo "------------------------"
- break_end
- ;;
- 3)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
- break
- ;;
- 9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
- echo "Fail2Ban防御程序已卸载"
- ;;
- *)
- break
- ;;
- esac
- fi
- done
+ fail2ban_panel
;;
@@ -11881,53 +20945,7 @@ EOF
24)
-
- root_use
- send_stats "私钥登录"
- while true; do
- clear
- echo "ROOT私钥登录模式"
- echo "视频介绍: https://www.bilibili.com/video/BV1Q4421X78n?t=209.4"
- echo "------------------------------------------------"
- echo "将会生成密钥对,更安全的方式SSH登录"
- echo "------------------------"
- echo "1. 生成新密钥 2. 导入已有密钥 3. 查看本机密钥"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "请输入你的选择: " host_dns
-
- case $host_dns in
- 1)
- send_stats "生成新密钥"
- add_sshkey
- break_end
-
- ;;
- 2)
- send_stats "导入已有公钥"
- import_sshkey
- break_end
-
- ;;
- 3)
- send_stats "查看本机密钥"
- echo "------------------------"
- echo "公钥信息"
- cat ~/.ssh/authorized_keys
- echo "------------------------"
- echo "私钥信息"
- cat ~/.ssh/sshkey
- echo "------------------------"
- break_end
-
- ;;
- *)
- break # 跳出循环,退出菜单
- ;;
- esac
- done
-
+ sshkey_panel
;;
25)
@@ -12052,15 +21070,31 @@ EOF
39)
clear
- send_stats "命令行历史记录"
- bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+ linux_fav
+ ;;
+
+ 40)
+ clear
+ net_menu
;;
41)
+ clear
+ log_menu
+ ;;
+
+ 42)
+ clear
+ env_menu
+ ;;
+
+
+ 61)
clear
send_stats "留言板"
- echo "科技lion留言板已迁移至官方社区!请在官方社区进行留言噢!"
- echo "https://bbs.kejilion.pro/"
+ echo "访问科技lion官方留言板,您对脚本有任何想法欢迎留言交流!"
+ echo "https://board.kejilion.pro"
+ echo "公共密码: kejilion.sh"
;;
66)
@@ -12070,16 +21104,18 @@ EOF
echo "一条龙系统调优"
echo "------------------------------------------------"
echo "将对以下内容进行操作与优化"
- echo "1. 更新系统到最新"
+ echo "1. 优化系统更新源,更新系统到最新"
echo "2. 清理系统垃圾文件"
echo -e "3. 设置虚拟内存${gl_huang}1G${gl_bai}"
echo -e "4. 设置SSH端口号为${gl_huang}5522${gl_bai}"
- echo -e "5. 开放所有端口"
- echo -e "6. 开启${gl_huang}BBR${gl_bai}加速"
- echo -e "7. 设置时区到${gl_huang}上海${gl_bai}"
- echo -e "8. 自动优化DNS地址${gl_huang}海外: 1.1.1.1 8.8.8.8 国内: 223.5.5.5 ${gl_bai}"
- echo -e "9. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
- echo -e "10. Linux系统内核参数优化切换到${gl_huang}均衡优化模式${gl_bai}"
+ echo -e "5. 启动fail2ban防御SSH暴力破解"
+ echo -e "6. 开放所有端口"
+ echo -e "7. 开启${gl_huang}BBR${gl_bai}加速"
+ echo -e "8. 设置时区到${gl_huang}上海${gl_bai}"
+ echo -e "9. 自动优化DNS地址${gl_huang}海外: 1.1.1.1 8.8.8.8 国内: 223.5.5.5 ${gl_bai}"
+ echo -e "10. 设置网络为${gl_huang}ipv4优先${gl_bai}"
+ echo -e "11. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "12. Linux系统内核参数优化${gl_huang}自动根据网络环境调优${gl_bai}"
echo "------------------------------------------------"
read -e -p "确定一键保养吗?(Y/N): " choice
@@ -12088,58 +21124,53 @@ EOF
clear
send_stats "一条龙调优启动"
echo "------------------------------------------------"
+ switch_mirror false false
linux_update
- echo -e "[${gl_lv}OK${gl_bai}] 1/10. 更新系统到最新"
+ echo -e "[${gl_lv}OK${gl_bai}] 1/12. 更新系统到最新"
echo "------------------------------------------------"
linux_clean
- echo -e "[${gl_lv}OK${gl_bai}] 2/10. 清理系统垃圾文件"
+ echo -e "[${gl_lv}OK${gl_bai}] 2/12. 清理系统垃圾文件"
echo "------------------------------------------------"
add_swap 1024
- echo -e "[${gl_lv}OK${gl_bai}] 3/10. 设置虚拟内存${gl_huang}1G${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 3/12. 设置虚拟内存${gl_huang}1G${gl_bai}"
echo "------------------------------------------------"
- local new_port=5522
- new_ssh_port
- echo -e "[${gl_lv}OK${gl_bai}] 4/10. 设置SSH端口号为${gl_huang}5522${gl_bai}"
+ new_ssh_port 5522
+ echo -e "[${gl_lv}OK${gl_bai}] 4/12. 设置SSH端口号为${gl_huang}5522${gl_bai}"
+ echo "------------------------------------------------"
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ echo -e "[${gl_lv}OK${gl_bai}] 5/12. 启动fail2ban防御SSH暴力破解"
+
echo "------------------------------------------------"
- echo -e "[${gl_lv}OK${gl_bai}] 5/10. 开放所有端口"
+ echo -e "[${gl_lv}OK${gl_bai}] 6/12. 开放所有端口"
echo "------------------------------------------------"
bbr_on
- echo -e "[${gl_lv}OK${gl_bai}] 6/10. 开启${gl_huang}BBR${gl_bai}加速"
+ echo -e "[${gl_lv}OK${gl_bai}] 7/12. 开启${gl_huang}BBR${gl_bai}加速"
echo "------------------------------------------------"
set_timedate Asia/Shanghai
- echo -e "[${gl_lv}OK${gl_bai}] 7/10. 设置时区到${gl_huang}上海${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 8/12. 设置时区到${gl_huang}上海${gl_bai}"
echo "------------------------------------------------"
- local country=$(curl -s ipinfo.io/country)
- if [ "$country" = "CN" ]; then
- local dns1_ipv4="223.5.5.5"
- local dns2_ipv4="183.60.83.19"
- local dns1_ipv6="2400:3200::1"
- local dns2_ipv6="2400:da00::6666"
- else
- local dns1_ipv4="1.1.1.1"
- local dns2_ipv4="8.8.8.8"
- local dns1_ipv6="2606:4700:4700::1111"
- local dns2_ipv6="2001:4860:4860::8888"
- fi
-
- set_dns
- echo -e "[${gl_lv}OK${gl_bai}] 8/10. 自动优化DNS地址${gl_huang}${gl_bai}"
+ auto_optimize_dns
+ echo -e "[${gl_lv}OK${gl_bai}] 9/12. 自动优化DNS地址${gl_huang}${gl_bai}"
+ echo "------------------------------------------------"
+ prefer_ipv4
+ echo -e "[${gl_lv}OK${gl_bai}] 10/12. 设置网络为${gl_huang}ipv4优先${gl_bai}}"
echo "------------------------------------------------"
install_docker
install wget sudo tar unzip socat btop nano vim
- echo -e "[${gl_lv}OK${gl_bai}] 9/10. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 11/12. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
echo "------------------------------------------------"
- echo "------------------------------------------------"
- optimize_balanced
- echo -e "[${gl_lv}OK${gl_bai}] 10/10. Linux系统内核参数优化"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ echo -e "[${gl_lv}OK${gl_bai}] 12/12. Linux系统内核参数优化"
echo -e "${gl_lv}一条龙系统调优已完成${gl_bai}"
;;
@@ -12406,26 +21437,13 @@ linux_file() {
continue
fi
- read -e -p "请输入远端服务器IP: " remote_ip
- if [ -z "$remote_ip" ]; then
- echo "错误: 请输入远端服务器IP。"
- send_stats "传送文件失败: 未输入远端服务器IP"
- continue
- fi
-
- read -e -p "请输入远端服务器用户名 (默认root): " remote_user
- remote_user=${remote_user:-root}
-
- read -e -p "请输入远端服务器密码: " -s remote_password
- echo
- if [ -z "$remote_password" ]; then
- echo "错误: 请输入远端服务器密码。"
- send_stats "传送文件失败: 未输入远端服务器密码"
- continue
- fi
+ kj_ssh_read_host_user_port "请输入远端服务器IP: " "请输入远端服务器用户名 (默认root): " "请输入登录端口 (默认22): " "root" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local remote_user="$KJ_SSH_USER"
+ local remote_port="$KJ_SSH_PORT"
- read -e -p "请输入登录端口 (默认22): " remote_port
- remote_port=${remote_port:-22}
+ kj_ssh_read_password "请输入远端服务器密码: "
+ local remote_password="$KJ_SSH_PASSWORD"
# 清除已知主机的旧条目
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
@@ -12659,7 +21677,7 @@ echo -e "${gl_kjlan}B站: ${gl_bai}https://b23.tv/2mqnQyh ${gl_kjla
echo -e "${gl_kjlan}官网: ${gl_bai}https://kejilion.pro/ ${gl_kjlan}导航: ${gl_bai}https://dh.kejilion.pro/${gl_bai}"
echo -e "${gl_kjlan}博客: ${gl_bai}https://blog.kejilion.pro/ ${gl_kjlan}软件中心: ${gl_bai}https://app.kejilion.pro/${gl_bai}"
echo "------------------------"
-echo -e "${gl_kjlan}脚本官网: ${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub地址: ${gl_bai}https://github.com/kejilion/sh${gl_bai}"
+echo -e "${gl_kjlan}脚本官网: ${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub地址: ${gl_bai}${gh_https_url}github.com/kejilion/sh${gl_bai}"
echo "------------------------"
echo ""
}
@@ -12667,6 +21685,64 @@ echo ""
+games_server_tools() {
+
+ while true; do
+ clear
+ echo -e "游戏开服脚本合集"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}幻兽帕鲁开服脚本"
+ echo -e "${gl_kjlan}2. ${gl_bai}我的世界开服脚本"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "请输入你的选择: " sub_choice
+
+ case $sub_choice in
+
+ 1) send_stats "幻兽帕鲁开服脚本" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
+ exit
+ ;;
+ 2) send_stats "我的世界开服脚本" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/mc.sh ; chmod +x mc.sh ; ./mc.sh
+ exit
+ ;;
+
+ 0)
+ kejilion
+ ;;
+
+ *)
+ echo "无效的输入!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
kejilion_update() {
@@ -12679,10 +21755,13 @@ while true; do
echo "全部日志: ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt"
echo "------------------------"
- curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
- local sh_v_new=$(curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | cut -d '"' -f 2)
+ curl -s --max-time 15 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
+ # 只下载前5行获取版本号,避免下载整个脚本
+ local sh_v_new=$(curl -s --max-time 15 -r 0-200 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | head -1 | cut -d '"' -f 2)
- if [ "$sh_v" = "$sh_v_new" ]; then
+ if [ -z "$sh_v_new" ]; then
+ echo -e "${gl_hong}无法获取最新版本信息,请检查网络连接${gl_bai}"
+ elif [ "$sh_v" = "$sh_v_new" ]; then
echo -e "${gl_lv}你已经是最新版本!${gl_huang}v$sh_v${gl_bai}"
send_stats "脚本已经最新了,无需更新"
else
@@ -12708,37 +21787,76 @@ while true; do
case "$choice" in
1)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
+ local download_url
if [ "$country" = "CN" ]; then
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh && chmod +x kejilion.sh
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh"
+ else
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh"
+ fi
+
+ # 备份当前脚本
+ cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null
+
+ # 下载到临时文件,校验后再替换
+ local tmp_file=$(mktemp ~/kejilion_tmp.XXXXXX)
+ if curl -sS --max-time 60 --fail -o "$tmp_file" "$download_url" && \
+ [ -s "$tmp_file" ] && \
+ head -1 "$tmp_file" | grep -q '^#!/bin/bash'; then
+ chmod +x "$tmp_file"
+ mv -f "$tmp_file" ~/kejilion.sh
+ canshu_v6
+ CheckFirstRun_true
+ yinsiyuanquan2
+ cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
+ echo -e "${gl_lv}脚本已更新到最新版本!${gl_huang}v$sh_v_new${gl_bai}"
+ send_stats "脚本已经最新$sh_v_new"
else
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh
+ rm -f "$tmp_file"
+ # 恢复备份
+ if [ -f ~/kejilion.sh.bak ]; then
+ mv -f ~/kejilion.sh.bak ~/kejilion.sh
+ fi
+ echo -e "${gl_hong}更新失败!下载出错或文件校验不通过,已恢复原版本${gl_bai}"
+ send_stats "脚本更新失败"
fi
- canshu_v6
- CheckFirstRun_true
- yinsiyuanquan2
- cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
- echo -e "${gl_lv}脚本已更新到最新版本!${gl_huang}v$sh_v_new${gl_bai}"
- send_stats "脚本已经最新$sh_v_new"
break_end
~/kejilion.sh
exit
;;
2)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
local ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
+ local cron_proxy cron_sed_cmd
if [ "$country" = "CN" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ~/kejilion.sh"
elif [ -n "$ipv6_address" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ~/kejilion.sh"
else
- SH_Update_task="curl -sS -O https://raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh"
+ cron_proxy="https://"
+ cron_sed_cmd=""
+ fi
+
+ # 构建健壮的自动更新命令:下载到临时文件 → 校验 → 备份 → 替换 → 恢复本地设置 → 部署
+ SH_Update_task="cd ~ && tmp=\$(mktemp ~/kejilion_tmp.XXXXXX) && curl -sS --max-time 60 --fail -o \"\$tmp\" ${cron_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && [ -s \"\$tmp\" ] && head -1 \"\$tmp\" | grep -q '^#!/bin/bash' && cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null && chmod +x \"\$tmp\" && mv -f \"\$tmp\" ~/kejilion.sh"
+ # 追加设置恢复
+ if [ -n "$cron_sed_cmd" ]; then
+ SH_Update_task="$SH_Update_task && $cron_sed_cmd"
fi
+ # 从旧脚本恢复 permission_granted 和 ENABLE_STATS 设置
+ SH_Update_task="$SH_Update_task && grep -q 'permission_granted=\"true\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/permission_granted=\"false\"/permission_granted=\"true\"/' ~/kejilion.sh; grep -q 'ENABLE_STATS=\"false\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/ENABLE_STATS=\"true\"/ENABLE_STATS=\"false\"/' ~/kejilion.sh"
+ # 部署到 /usr/local/bin/k 和 /usr/bin/k
+ SH_Update_task="$SH_Update_task; cp -f ~/kejilion.sh /usr/local/bin/k 2>/dev/null; ln -sf /usr/local/bin/k /usr/bin/k 2>/dev/null"
+ # 下载失败时清理临时文件
+ SH_Update_task="$SH_Update_task || rm -f \"\$tmp\" 2>/dev/null"
+
check_crontab_installed
(crontab -l | grep -v "kejilion.sh") | crontab -
- # (crontab -l 2>/dev/null; echo "0 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c \"$SH_Update_task\"") | crontab -
+ (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c '$SH_Update_task'") | crontab -
echo -e "${gl_lv}自动更新已开启,每天凌晨2点脚本会自动更新!${gl_bai}"
send_stats "开启脚本自动更新"
break_end
@@ -12787,8 +21905,7 @@ echo -e "${gl_kjlan}12. ${gl_bai}后台工作区"
echo -e "${gl_kjlan}13. ${gl_bai}系统工具"
echo -e "${gl_kjlan}14. ${gl_bai}服务器集群控制"
echo -e "${gl_kjlan}15. ${gl_bai}广告专栏"
-echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}p. ${gl_bai}幻兽帕鲁开服脚本"
+echo -e "${gl_kjlan}16. ${gl_bai}游戏开服脚本合集"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}00. ${gl_bai}脚本更新"
echo -e "${gl_kjlan}------------------------${gl_bai}"
@@ -12797,7 +21914,7 @@ echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "请输入你的选择: " choice
case $choice in
- 1) linux_ps ;;
+ 1) linux_info ;;
2) clear ; send_stats "系统更新" ; linux_update ;;
3) clear ; send_stats "系统清理" ; linux_clean ;;
4) linux_tools ;;
@@ -12814,10 +21931,7 @@ case $choice in
13) linux_Settings ;;
14) linux_cluster ;;
15) kejilion_Affiliates ;;
- p) send_stats "幻兽帕鲁开服脚本" ; cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
- exit
- ;;
+ 16) games_server_tools ;;
00) kejilion_update ;;
0) clear ; exit ;;
*) echo "无效的输入!" ;;
@@ -12856,6 +21970,7 @@ echo "软件状态查看 k status sshd | k 状态 sshd "
echo "软件开机启动 k enable docker | k autostart docke | k 开机启动 docker "
echo "域名证书申请 k ssl"
echo "域名证书到期查询 k ssl ps"
+echo "docker管理平面 k docker"
echo "docker环境安装 k docker install |k docker 安装"
echo "docker容器管理 k docker ps |k docker 容器"
echo "docker镜像管理 k docker img |k docker 镜像"
@@ -12864,12 +21979,20 @@ echo "LDNMP缓存清理 k web cache"
echo "安装WordPress k wp |k wordpress |k wp xxx.com"
echo "安装反向代理 k fd |k rp |k 反代 |k fd xxx.com"
echo "安装负载均衡 k loadbalance |k 负载均衡"
+echo "安装L4负载均衡 k stream |k L4负载均衡"
echo "防火墙面板 k fhq |k 防火墙"
echo "开放端口 k dkdk 8080 |k 打开端口 8080"
echo "关闭端口 k gbdk 7800 |k 关闭端口 7800"
echo "放行IP k fxip 127.0.0.0/8 |k 放行IP 127.0.0.0/8"
echo "阻止IP k zzip 177.5.25.36 |k 阻止IP 177.5.25.36"
-
+echo "命令收藏夹 k fav | k 命令收藏夹"
+echo "应用市场管理 k app"
+echo "应用编号快捷管理 k app 26 | k app 1panel | k app npm"
+echo "fail2ban管理 k fail2ban | k f2b"
+echo "显示系统信息 k info"
+echo "ROOT密钥管理 k sshkey"
+echo "SSH公钥导入(URL) k sshkey "
+echo "SSH公钥导入(GitHub) k sshkey github "
}
@@ -12944,6 +22067,7 @@ else
echo "已阻止IP+端口访问该服务"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
;;
@@ -12952,6 +22076,11 @@ else
ldnmp_Proxy_backend
;;
+
+ stream|L4负载均衡)
+ ldnmp_Proxy_backend_stream
+ ;;
+
swap)
shift
send_stats "快速设置虚拟内存"
@@ -13002,6 +22131,10 @@ else
iptables_panel
;;
+ 命令收藏夹|fav)
+ linux_fav
+ ;;
+
status|状态)
shift
send_stats "软件状态查看"
@@ -13061,7 +22194,7 @@ else
docker_image
;;
*)
- k_info
+ linux_docker
;;
esac
;;
@@ -13081,6 +22214,59 @@ else
fi
;;
+
+ app)
+ shift
+ send_stats "应用$@"
+ linux_panel "$@"
+ ;;
+
+ claw|oc|OpenClaw)
+ moltbot_menu
+ ;;
+
+ info)
+ linux_info
+ ;;
+
+ fail2ban|f2b)
+ fail2ban_panel
+ ;;
+
+
+ sshkey)
+
+ shift
+ case "$1" in
+ "" )
+ # sshkey → 交互菜单
+ send_stats "SSHKey 交互菜单"
+ sshkey_panel
+ ;;
+ github )
+ shift
+ send_stats "从 GitHub 导入 SSH 公钥"
+ fetch_github_ssh_keys "$1"
+ ;;
+ http://*|https://* )
+ send_stats "从 URL 导入 SSH 公钥"
+ fetch_remote_ssh_keys "$1"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa* )
+ send_stats "公钥直接导入"
+ import_sshkey "$1"
+ ;;
+ * )
+ echo "错误:未知参数 '$1'"
+ echo "用法:"
+ echo " k sshkey 进入交互菜单"
+ echo " k sshkey \"\" 直接导入 SSH 公钥"
+ echo " k sshkey 从 URL 导入 SSH 公钥"
+ echo " k sshkey github 从 GitHub 导入 SSH 公钥"
+ ;;
+ esac
+
+ ;;
*)
k_info
;;
diff --git a/cn/tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh b/cn/tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh
new file mode 120000
index 000000000..1158d6222
--- /dev/null
+++ b/cn/tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh
@@ -0,0 +1 @@
+../../../tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh
\ No newline at end of file
diff --git a/cn/tests/openclaw/tests_openclaw_memory_menu_smoke.sh b/cn/tests/openclaw/tests_openclaw_memory_menu_smoke.sh
new file mode 100755
index 000000000..3a555d93c
--- /dev/null
+++ b/cn/tests/openclaw/tests_openclaw_memory_menu_smoke.sh
@@ -0,0 +1,214 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-memory-menu-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw/workspace/memory"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+# 抽取 OpenClaw 记忆菜单实现
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+EOF_INNER
+
+awk 'BEGIN{p=0} /openclaw_memory_config_file\(\) \{/{p=1} /openclaw_memory_menu\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+awk 'BEGIN{p=0} /openclaw_memory_menu\(\) \{/{p=1} /openclaw_backup_restore_menu\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+# stub: openclaw
+cat > "$WORKDIR/bin/openclaw" <<'EOF_INNER'
+#!/usr/bin/env bash
+cmd="$*"
+if [[ "$cmd" == "config get"* ]]; then
+ key="$3"
+ case "$key" in
+ memory.backend) echo "qmd" ;;
+ memory.qmd.includeDefaultMemory) echo "true" ;;
+ memory.qmd.command) echo "qmd" ;;
+ agents.defaults.memorySearch.local.modelPath) echo "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf" ;;
+ agents.defaults.memorySearch.provider) echo "local" ;;
+ *) echo "" ;;
+ esac
+ exit 0
+fi
+if [[ "$cmd" == "config set"* ]]; then
+ key="$3"
+ val="$4"
+ if [[ "$key" == "memory.qmd.includeDefaultMemory" ]]; then
+ echo "$val" > "${HOME}/.openclaw/includeDefaultMemory"
+ fi
+ if [[ "$key" == "memory.backend" ]]; then
+ echo "$val" > "${HOME}/.openclaw/backend"
+ fi
+ if [[ "$key" == "memory.qmd.command" ]]; then
+ echo "$val" > "${HOME}/.openclaw/qmd_command"
+ fi
+ if [[ "$key" == "agents.defaults.memorySearch.provider" ]]; then
+ echo "$val" > "${HOME}/.openclaw/provider"
+ fi
+ exit 0
+fi
+if [[ "$cmd" == "memory status" ]]; then
+ cat <> "${HOME}/.openclaw/index_calls"
+ echo "index ok"
+ exit 0
+fi
+if [[ "$cmd" == "gateway restart" ]]; then
+ echo "$cmd" >> "${HOME}/.openclaw/gateway_calls"
+ echo "gateway restarted"
+ exit 0
+fi
+if [[ "$cmd" == "gateway stop" ]]; then
+ echo "gateway stopped"
+ exit 0
+fi
+if [[ "$cmd" == "gateway start" ]]; then
+ echo "gateway started"
+ exit 0
+fi
+echo "mock openclaw $*"
+EOF_INNER
+chmod +x "$WORKDIR/bin/openclaw"
+
+# stub: qmd
+cat > "$WORKDIR/bin/qmd" <<'EOF_INNER'
+#!/usr/bin/env bash
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/qmd"
+
+# stub: curl (用于网络探测)
+cat > "$WORKDIR/bin/curl" <<'EOF_INNER'
+#!/usr/bin/env bash
+if [[ "$*" == *"huggingface.co"* ]]; then
+ exit 1
+fi
+if [[ "$*" == *"hf-mirror.com"* ]]; then
+ exit 0
+fi
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/curl"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+
+# minimal openclaw.json
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{
+ "memory": {
+ "backend": "qmd",
+ "qmd": {"command": "qmd", "includeDefaultMemory": true}
+ },
+ "agents": {
+ "defaults": {
+ "memorySearch": {
+ "provider": "local",
+ "local": {
+ "modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
+ }
+ }
+ }
+ }
+}
+JSON
+
+# mock index sqlite
+mkdir -p "$HOME/.openclaw/workspace/memory"
+touch "$HOME/.openclaw/workspace/memory/index.sqlite"
+
+# mock memory files
+cat > "$HOME/.openclaw/workspace/MEMORY.md" <<'TXT'
+# MEMORY
+line1
+line2
+line3
+TXT
+cat > "$HOME/.openclaw/workspace/memory/2026-03-11.md" <<'TXT'
+# 2026-03-11
+foo
+bar
+TXT
+
+source "$WORKDIR/harness.sh"
+
+run_menu() {
+ local input="$1"
+ local title="$2"
+ local outfile="$3"
+ echo "[TEST] $title"
+ printf "%b" "$input" | openclaw_memory_menu >"$outfile"
+}
+
+# 1) 状态展示(直接返回)
+run_menu "0\n" "status" "$WORKDIR/out_status.txt"
+# 2) 更新索引(增量)
+run_menu "1\nyes\n\n\n0\n" "index" "$WORKDIR/out_index.txt"
+# 3) 查看记忆文件(列表+查看)
+run_menu "2\n1\n\n\n\n\n0\n0\n" "files" "$WORKDIR/out_files.txt"
+# 4) 索引修复(执行修复 + 不立即重建)
+run_menu "3\ny\n\n\n0\n" "fix" "$WORKDIR/out_fix.txt"
+# 5) 记忆方案(自动推荐并取消)
+run_menu "4\n1\n\n0\n0\n" "scheme" "$WORKDIR/out_scheme.txt"
+
+# 6) Store 缺失时仍执行 rebuild
+: > "$HOME/.openclaw/index_calls"
+OPENCLAW_STORE="~/.openclaw/workspace/memory/missing.sqlite" \
+ openclaw_memory_rebuild_index_safe >"$WORKDIR/out_missing.txt"
+
+export WORKDIR
+python3 - <<'PY'
+import glob
+import os
+
+flag_path = os.path.expanduser('~/.openclaw/includeDefaultMemory')
+if not os.path.exists(flag_path):
+ raise SystemExit('includeDefaultMemory not updated')
+flag = open(flag_path, 'r', encoding='utf-8').read().strip()
+if flag != 'false':
+ raise SystemExit('includeDefaultMemory not updated')
+# ensure backup created
+baks = glob.glob(os.path.expanduser('~/.openclaw/workspace/memory/index.sqlite.bak.*'))
+if not baks:
+ raise SystemExit('index sqlite backup not created')
+# ensure missing store still triggers force index
+calls_path = os.path.expanduser('~/.openclaw/index_calls')
+if not os.path.exists(calls_path):
+ raise SystemExit('index calls not recorded')
+with open(calls_path, 'r', encoding='utf-8') as fh:
+ calls = fh.read()
+if 'memory index --force' not in calls:
+ raise SystemExit('force index not called for missing store')
+# ensure gateway restart called after rebuild
+gw_calls_path = os.path.expanduser('~/.openclaw/gateway_calls')
+if not os.path.exists(gw_calls_path):
+ raise SystemExit('gateway restart not recorded')
+with open(gw_calls_path, 'r', encoding='utf-8') as fh:
+ gw_calls = fh.read()
+if 'gateway restart' not in gw_calls:
+ raise SystemExit('gateway restart not called after rebuild')
+# ensure warning emitted
+with open(os.path.join(os.environ['WORKDIR'], 'out_missing.txt'), 'r', encoding='utf-8') as fh:
+ out = fh.read()
+if 'Store 原始值' not in out:
+ raise SystemExit('missing store warning not found')
+if '索引已重建并自动重启网关' not in out:
+ raise SystemExit('auto restart message not found')
+print('SMOKE_OK')
+PY
diff --git a/custom_mysql_config-1.cnf b/custom_mysql_config-1.cnf
index d3f5a12fa..5261b859f 100644
--- a/custom_mysql_config-1.cnf
+++ b/custom_mysql_config-1.cnf
@@ -1 +1,44 @@
+[mysqld]
+
+# 连接和线程管理
+max_connections = 200 # 小机型避免连接风暴
+thread_cache_size = 64
+interactive_timeout = 20
+wait_timeout = 20
+
+# InnoDB设置
+innodb_buffer_pool_size = 512M # 2G RAM 中最重要的分配,留足系统空间
+innodb_buffer_pool_instances = 1 # 小内存不需要分片
+innodb_log_buffer_size = 8M
+innodb_redo_log_capacity = 64M
+innodb_lock_wait_timeout = 30
+innodb_file_per_table = 1
+innodb_flush_log_at_trx_commit = 1
+innodb_io_capacity = 400 # 普通SSD预期值
+innodb_io_capacity_max = 800
+
+# 缓存和表限制
+table_open_cache = 512
+open_files_limit = 20000
+tmp_table_size = 32M
+max_heap_table_size = 32M
+max_allowed_packet = 32M
+
+# 缓冲区大小
+sort_buffer_size = 2M
+read_buffer_size = 512K
+join_buffer_size = 1M
+
+# 日志管理
+log_error_verbosity = 3
+slow_query_log = 1
+slow_query_log_file = /var/log/mysql/slow.log
+long_query_time = 1
+log_queries_not_using_indexes = 1
+
+# 其他
+sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
+performance_schema=ON
+disable-log-bin
+
diff --git a/custom_mysql_config.cnf b/custom_mysql_config.cnf
index a4240a45c..00aedc27a 100644
--- a/custom_mysql_config.cnf
+++ b/custom_mysql_config.cnf
@@ -42,3 +42,4 @@ log_queries_not_using_indexes = 1 # ⭐记录无索引扫描
# 其他
sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
performance_schema=ON # ⭐生产环境推荐打开,监控分析好用
+disable-log-bin
diff --git a/en/kejilion.sh b/en/kejilion.sh
index 4648ca502..aee9be2ec 100644
--- a/en/kejilion.sh
+++ b/en/kejilion.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-sh_v="4.0.3"
+sh_v="4.5.2"
gl_hui='\e[37m'
@@ -29,12 +29,14 @@ else
gh_proxy="https://"
fi
+gh_https_url="https://"
+
}
quanju_canshu
-# Define a function to execute commands
+# Define a function to execute the command
run_command() {
if [ "$zhushi" -eq 0 ]; then
"$@"
@@ -45,6 +47,8 @@ run_command() {
canshu_v6() {
if grep -q '^canshu="V6"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
+ elif grep -q '^canshu="V6"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
fi
}
@@ -52,14 +56,16 @@ canshu_v6() {
CheckFirstRun_true() {
if grep -q '^permission_granted="true"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
+ elif grep -q '^permission_granted="true"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
fi
}
-# Functions that collect function buried point information, record the current script version number, usage time, system version, CPU architecture, the country of the machine and the function name used by the user. They absolutely do not involve any sensitive information, please rest assured! Please believe me!
-# Why do we need to design this function? The purpose is to better understand the functions that users like to use, and further optimize the functions to launch more functions that meet user needs.
-# For the full text, you can search for the send_stats function call location, transparent and open source, and you can refuse to use it if you have any concerns.
+# This function collects function buried information and records the current script version number, usage time, system version, CPU architecture, machine country and function name used by the user. It does not involve any sensitive information, so don’t worry! Please believe me!
+# Why is this function designed? The purpose is to better understand the functions that users like to use, and to further optimize the functions and launch more functions that meet user needs.
+# The full text can be searched for the send_stats function call location. It is transparent and open source. If you have any concerns, you can refuse to use it.
@@ -86,6 +92,8 @@ yinsiyuanquan2() {
if grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
+elif grep -q '^ENABLE_STATS="false"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
fi
}
@@ -102,6 +110,7 @@ sed -i '/^alias k=/d' ~/.profile > /dev/null 2>&1
sed -i '/^alias k=/d' ~/.bash_profile > /dev/null 2>&1
cp -f ./kejilion.sh ~/kejilion.sh > /dev/null 2>&1
cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
@@ -111,22 +120,22 @@ CheckFirstRun_false() {
fi
}
-# Prompt the user to agree to the terms
+# Prompt user to agree to terms
UserLicenseAgreement() {
clear
- echo -e "${gl_kjlan}Welcome to the Tech lion script toolbox${gl_bai}"
- echo "For the first time using the script, please read and agree to the user license agreement."
+ echo -e "${gl_kjlan}Welcome to the technology lion script toolbox${gl_bai}"
+ echo "When using the script for the first time, please read and agree to the User License Agreement."
echo "User License Agreement: https://blog.kejilion.pro/user-license-agreement/"
echo -e "----------------------"
- read -r -p "Do you agree to the above terms? (y/n):" user_input
+ read -e -p "Do you agree to the above terms? (y/n):" user_input
if [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]; then
- send_stats "License consent"
+ send_stats "License agreement"
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
sed -i 's/^permission_granted="false"/permission_granted="true"/' /usr/local/bin/k
else
- send_stats "Rejection of permission"
+ send_stats "permission denied"
clear
exit
fi
@@ -154,7 +163,7 @@ public_ip=$(get_public_ip)
isp_info=$(curl -s --max-time 3 http://ipinfo.io/org)
-if echo "$isp_info" | grep -Eiq 'china|mobile|unicom|telecom'; then
+if echo "$isp_info" | grep -Eiq 'CHINANET|mobile|unicom|telecom'; then
ipv4_address=$(get_local_ip)
else
ipv4_address="$public_ip"
@@ -170,13 +179,13 @@ ipv6_address=$(curl -s --max-time 1 https://v6.ipinfo.io/ip && echo)
install() {
if [ $# -eq 0 ]; then
- echo "Package parameters are not provided!"
+ echo "No package parameters provided!"
return 1
fi
for package in "$@"; do
if ! command -v "$package" &>/dev/null; then
- echo -e "${gl_huang}Installing$package...${gl_bai}"
+ echo -e "${gl_kjlan}Installing$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
dnf install -y epel-release
@@ -213,35 +222,45 @@ install() {
check_disk_space() {
+ local required_gb=$1
+ local path=${2:-/}
- required_gb=$1
- required_space_mb=$((required_gb * 1024))
- available_space_mb=$(df -m / | awk 'NR==2 {print $4}')
+ mkdir -p "$path"
- if [ $available_space_mb -lt $required_space_mb ]; then
- echo -e "${gl_huang}hint:${gl_bai}Insufficient disk space!"
+ local required_space_mb=$((required_gb * 1024))
+ local available_space_mb=$(df -m "$path" | awk 'NR==2 {print $4}')
+
+ if [ "$available_space_mb" -lt "$required_space_mb" ]; then
+ echo -e "${gl_huang}hint:${gl_bai}Not enough disk space!"
echo "Current available space: $((available_space_mb/1024))G"
- echo "Minimum demand space:${required_gb}G"
- echo "The installation cannot be continued. Please clean the disk space and try again."
- send_stats "Insufficient disk space"
+ echo "Minimum required space:${required_gb}G"
+ echo "The installation cannot continue. Please clear the disk space and try again."
+ send_stats "Not enough disk space"
break_end
kejilion
fi
}
+
install_dependency() {
+ switch_mirror false false
+ check_port
+ check_swap
+ prefer_ipv4
+ auto_optimize_dns
install wget unzip tar jq grep
+
}
remove() {
if [ $# -eq 0 ]; then
- echo "Package parameters are not provided!"
+ echo "No package parameters provided!"
return 1
fi
for package in "$@"; do
- echo -e "${gl_huang}Uninstalling$package...${gl_bai}"
+ echo -e "${gl_kjlan}Uninstalling$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf remove -y "$package"
elif command -v yum &>/dev/null; then
@@ -279,7 +298,7 @@ systemctl() {
}
-# Restart the service
+# Restart service
restart() {
systemctl restart "$1"
if [ $? -eq 0 ]; then
@@ -289,13 +308,13 @@ restart() {
fi
}
-# Start the service
+# Start service
start() {
systemctl start "$1"
if [ $? -eq 0 ]; then
- echo "$1The service has been started."
+ echo "$1The service has started."
else
- echo "Error: Start$1Service failed."
+ echo "Error: start$1Service failed."
fi
}
@@ -303,9 +322,9 @@ start() {
stop() {
systemctl stop "$1"
if [ $? -eq 0 ]; then
- echo "$1Service has been stopped."
+ echo "$1Service has stopped."
else
- echo "Error: Stop$1Service failed."
+ echo "Error: stop$1Service failed."
fi
}
@@ -313,9 +332,9 @@ stop() {
status() {
systemctl status "$1"
if [ $? -eq 0 ]; then
- echo "$1The service status is displayed."
+ echo "$1Service status is shown."
else
- echo "Error: Unable to display$1Service status."
+ echo "Error: cannot be displayed$1Service status."
fi
}
@@ -328,7 +347,7 @@ enable() {
/bin/systemctl enable "$SERVICE_NAME"
fi
- echo "$SERVICE_NAMESet to power on."
+ echo "$SERVICE_NAMEIt has been set to start automatically at boot."
}
@@ -349,22 +368,22 @@ kejilion() {
-check_port() {
- install lsof
+stop_containers_or_kill_process() {
+ local port=$1
+ local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
- stop_containers_or_kill_process() {
- local port=$1
- local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
+ if [ -n "$containers" ]; then
+ docker stop $containers
+ else
+ install lsof
+ for pid in $(lsof -t -i:$port); do
+ kill -9 $pid
+ done
+ fi
+}
- if [ -n "$containers" ]; then
- docker stop $containers
- else
- for pid in $(lsof -t -i:$port); do
- kill -9 $pid
- done
- fi
- }
+check_port() {
stop_containers_or_kill_process 80
stop_containers_or_kill_process 443
}
@@ -377,23 +396,23 @@ if [ "$country" = "CN" ]; then
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
- "https://docker-0.unsee.tech",
- "https://docker.1panel.live",
- "https://registry.dockermirror.com",
- "https://docker.imgdb.de",
- "https://docker.m.daocloud.io",
- "https://hub.firefly.store",
- "https://hub.littlediary.cn",
+ "https://docker.1ms.run",
+ "https://docker.m.ixdev.cn",
"https://hub.rat.dev",
- "https://dhub.kubesre.xyz",
- "https://cjie.eu.org",
- "https://docker.1panelproxy.com",
+ "https://dockerproxy.net",
+ "https://docker-registry.nmqu.com",
+ "https://docker.amingg.com",
"https://docker.hlmirror.com",
- "https://hub.fast360.xyz",
- "https://dockerpull.cn",
- "https://cr.laoyou.ip-ddns.com",
- "https://docker.melikeme.cn",
- "https://docker.kejilion.pro"
+ "https://hub1.nat.tf",
+ "https://hub2.nat.tf",
+ "https://hub3.nat.tf",
+ "https://docker.m.daocloud.io",
+ "https://docker.kejilion.pro",
+ "https://docker.367231.xyz",
+ "https://hub.1panel.dev",
+ "https://dockerproxy.cool",
+ "https://docker.apiba.cn",
+ "https://proxy.vvvv.ee"
]
}
EOF
@@ -407,80 +426,40 @@ restart docker
}
-install_add_docker_guanfang() {
+
+linuxmirrors_install_docker() {
+
local country=$(curl -s ipinfo.io/country)
if [ "$country" = "CN" ]; then
- cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/install && chmod +x install
- sh install --mirror Aliyun
- rm -f install
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source mirrors.huaweicloud.com/docker-ce \
+ --source-registry docker.1ms.run \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
else
- curl -fsSL https://get.docker.com | sh
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source download.docker.com \
+ --source-registry registry.hub.docker.com \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
fi
-install_add_docker_cn
+install_add_docker_cn
}
install_add_docker() {
- echo -e "${gl_huang}Installing docker environment...${gl_bai}"
- if [ -f /etc/os-release ] && grep -q "Fedora" /etc/os-release; then
- install_add_docker_guanfang
- elif command -v dnf &>/dev/null; then
- dnf update -y
- dnf install -y yum-utils device-mapper-persistent-data lvm2
- rm -f /etc/yum.repos.d/docker*.repo > /dev/null
- country=$(curl -s ipinfo.io/country)
- arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo | tee /etc/yum.repos.d/docker-ce.repo > /dev/null
- else
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo > /dev/null
- fi
- dnf install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
- elif [ -f /etc/os-release ] && grep -q "Kali" /etc/os-release; then
- apt update
- apt upgrade -y
- apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
- rm -f /usr/share/keyrings/docker-archive-keyring.gpg
- local country=$(curl -s ipinfo.io/country)
- local arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- else
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- fi
- apt update
- apt install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
-
- elif command -v apt &>/dev/null || command -v yum &>/dev/null; then
- install_add_docker_guanfang
+ echo -e "${gl_kjlan}Installing docker environment...${gl_bai}"
+ if command -v apt &>/dev/null || command -v yum &>/dev/null || command -v dnf &>/dev/null; then
+ linuxmirrors_install_docker
else
install docker docker-compose
install_add_docker_cn
@@ -504,7 +483,7 @@ while true; do
echo "Docker container list"
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
- echo "Container operation"
+ echo "Container operations"
echo "------------------------"
echo "1. Create a new container"
echo "------------------------"
@@ -514,37 +493,37 @@ while true; do
echo "5. Restart the specified container 9. Restart all containers"
echo "------------------------"
echo "11. Enter the specified container 12. View the container log"
- echo "13. View container network 14. View container occupancy"
+ echo "13. Check the container network 14. Check the container occupancy"
echo "------------------------"
- echo "15. Turn on container port access 16. Turn off container port access"
+ echo "15. Enable container port access 16. Close container port access"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
send_stats "Create a new container"
- read -e -p "Please enter the creation command:" dockername
+ read -e -p "Please enter the create command:" dockername
$dockername
;;
2)
send_stats "Start the specified container"
- read -e -p "Please enter the container name (multiple container names separated by spaces):" dockername
+ read -e -p "Please enter the container name (please separate multiple container names with spaces):" dockername
docker start $dockername
;;
3)
- send_stats "Stop the specified container"
- read -e -p "Please enter the container name (multiple container names separated by spaces):" dockername
+ send_stats "Stop specified container"
+ read -e -p "Please enter the container name (please separate multiple container names with spaces):" dockername
docker stop $dockername
;;
4)
send_stats "Delete the specified container"
- read -e -p "Please enter the container name (multiple container names separated by spaces):" dockername
+ read -e -p "Please enter the container name (please separate multiple container names with spaces):" dockername
docker rm -f $dockername
;;
5)
send_stats "Restart the specified container"
- read -e -p "Please enter the container name (multiple container names separated by spaces):" dockername
+ read -e -p "Please enter the container name (please separate multiple container names with spaces):" dockername
docker restart $dockername
;;
6)
@@ -580,7 +559,7 @@ while true; do
break_end
;;
12)
- send_stats "View container log"
+ send_stats "View container logs"
read -e -p "Please enter the container name:" dockername
docker logs $dockername
break_end
@@ -590,7 +569,7 @@ while true; do
echo ""
container_ids=$(docker ps -q)
echo "------------------------------------------------------------"
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "Container name" "network name" "IP address"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
local container_name=$(echo "$container_info" | awk '{print $1}')
@@ -651,27 +630,27 @@ while true; do
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- send_stats "Pull the mirror"
- read -e -p "Please enter the mirror name (please separate multiple mirror names with spaces):" imagenames
+ send_stats "Pull image"
+ read -e -p "Please enter the image name (please separate multiple image names with spaces):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}Getting the image:$name${gl_bai}"
+ echo -e "${gl_kjlan}Obtaining image:$name${gl_bai}"
docker pull $name
done
;;
2)
- send_stats "Update the image"
- read -e -p "Please enter the mirror name (please separate multiple mirror names with spaces):" imagenames
+ send_stats "Update image"
+ read -e -p "Please enter the image name (please separate multiple image names with spaces):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}Updated image:$name${gl_bai}"
+ echo -e "${gl_kjlan}Updating image:$name${gl_bai}"
docker pull $name
done
;;
3)
- send_stats "Delete the mirror"
- read -e -p "Please enter the mirror name (please separate multiple mirror names with spaces):" imagenames
+ send_stats "Delete image"
+ read -e -p "Please enter the image name (please separate multiple image names with spaces):" imagenames
for name in $imagenames; do
docker rmi -f $name
done
@@ -759,11 +738,11 @@ install_crontab() {
;;
esac
else
- echo "The operating system cannot be determined."
+ echo "Unable to determine operating system."
return
fi
- echo -e "${gl_lv}crontab is installed and the cron service is running.${gl_bai}"
+ echo -e "${gl_lv}crontab is installed and cron service is running.${gl_bai}"
}
@@ -775,15 +754,15 @@ docker_ipv6_on() {
local CONFIG_FILE="/etc/docker/daemon.json"
local REQUIRED_IPV6_CONFIG='{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64"}'
- # Check if the configuration file exists, create the file and write the default settings if it does not exist
+ # Check if the configuration file exists, if not create the file and write the default settings
if [ ! -f "$CONFIG_FILE" ]; then
echo "$REQUIRED_IPV6_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
else
- # Use jq to handle updates of configuration files
+ # Use jq to handle configuration file updates
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # Check whether the current configuration already has ipv6 settings
+ # Check if the current configuration already has ipv6 settings
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq '.ipv6 // false')
# Update configuration and enable IPv6
@@ -793,7 +772,7 @@ docker_ipv6_on() {
UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq '. + {"fixed-cidr-v6": "2001:db8:1::/64"}')
fi
- # Comparing original configuration with new configuration
+ # Compare original configuration to new configuration
if [[ "$ORIGINAL_CONFIG" == "$UPDATED_CONFIG" ]]; then
echo -e "${gl_huang}IPv6 access is currently enabled${gl_bai}"
else
@@ -812,20 +791,20 @@ docker_ipv6_off() {
# Check if the configuration file exists
if [ ! -f "$CONFIG_FILE" ]; then
- echo -e "${gl_hong}The configuration file does not exist${gl_bai}"
+ echo -e "${gl_hong}Configuration file does not exist${gl_bai}"
return
fi
- # Read the current configuration
+ # Read current configuration
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # Use jq to handle updates of configuration files
+ # Use jq to handle configuration file updates
local UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq 'del(.["fixed-cidr-v6"]) | .ipv6 = false')
- # Check the current ipv6 status
+ # Check current ipv6 status
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq -r '.ipv6 // false')
- # Comparing original configuration with new configuration
+ # Compare original configuration to new configuration
if [[ "$CURRENT_IPV6" == "false" ]]; then
echo -e "${gl_huang}IPv6 access is currently closed${gl_bai}"
else
@@ -877,23 +856,23 @@ open_port() {
install iptables
for port in "${ports[@]}"; do
- # Delete existing closing rules
+ # Delete existing shutdown rules
iptables -D INPUT -p tcp --dport $port -j DROP 2>/dev/null
iptables -D INPUT -p udp --dport $port -j DROP 2>/dev/null
- # Add Open Rules
+ # Add open rule
if ! iptables -C INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j ACCEPT
fi
if ! iptables -C INPUT -p udp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j ACCEPT
- echo "The port has been opened$port"
+ echo "Port opened$port"
fi
done
save_iptables_rules
- send_stats "The port has been opened"
+ send_stats "Port opened"
}
@@ -911,7 +890,7 @@ close_port() {
iptables -D INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp --dport $port -j ACCEPT 2>/dev/null
- # Add a close rule
+ # Add a shutdown rule
if ! iptables -C INPUT -p tcp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j DROP
fi
@@ -926,7 +905,7 @@ close_port() {
iptables -D INPUT -i lo -j ACCEPT 2>/dev/null
iptables -D FORWARD -i lo -j ACCEPT 2>/dev/null
- # Insert new rules to first
+ # Insert new rule into the first one
iptables -I INPUT 1 -i lo -j ACCEPT
iptables -I FORWARD 1 -i lo -j ACCEPT
@@ -948,7 +927,7 @@ allow_ip() {
# Delete existing blocking rules
iptables -D INPUT -s $ip -j DROP 2>/dev/null
- # Add allow rules
+ # Add allow rule
if ! iptables -C INPUT -s $ip -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j ACCEPT
echo "Released IP$ip"
@@ -972,7 +951,7 @@ block_ip() {
# Delete existing allow rules
iptables -D INPUT -s $ip -j ACCEPT 2>/dev/null
- # Add blocking rules
+ # Add blocking rule
if ! iptables -C INPUT -s $ip -j DROP 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j DROP
echo "IP blocked$ip"
@@ -990,7 +969,7 @@ block_ip() {
enable_ddos_defense() {
- # Turn on defense DDoS
+ # Turn on DDoS protection
iptables -A DOCKER-USER -p tcp --syn -m limit --limit 500/s --limit-burst 100 -j ACCEPT
iptables -A DOCKER-USER -p tcp --syn -j DROP
iptables -A DOCKER-USER -p udp -m limit --limit 3000/s -j ACCEPT
@@ -1003,9 +982,9 @@ enable_ddos_defense() {
send_stats "Turn on DDoS defense"
}
-# Turn off DDoS Defense
+# Turn off DDoS defense
disable_ddos_defense() {
- # Turn off defense DDoS
+ # Turn off DDoS protection
iptables -D DOCKER-USER -p tcp --syn -m limit --limit 500/s --limit-burst 100 -j ACCEPT 2>/dev/null
iptables -D DOCKER-USER -p tcp --syn -j DROP 2>/dev/null
iptables -D DOCKER-USER -p udp -m limit --limit 3000/s -j ACCEPT 2>/dev/null
@@ -1015,115 +994,108 @@ disable_ddos_defense() {
iptables -D INPUT -p udp -m limit --limit 3000/s -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp -j DROP 2>/dev/null
- send_stats "Turn off DDoS Defense"
+ send_stats "Turn off DDoS defense"
}
-# Functions that manage national IP rules
+# Functions to manage national IP rules
manage_country_rules() {
local action="$1"
- local country_code="$2"
- local ipset_name="${country_code,,}_block"
- local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
+ shift # 去掉第一个参数,剩下的全是国家代码
install ipset
- case "$action" in
- block)
- # Create if ipset does not exist
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ for country_code in "$@"; do
+ local ipset_name="${country_code,,}_block"
+ local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
- # Download IP area file
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "Error: Download$country_codeIP zone file failed"
- exit 1
- fi
+ case "$action" in
+ block)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
- # Add IP to ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "Error: Download$country_codeIP zone file failed"
+ continue
+ fi
- # Block IP with iptables
- iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- iptables -I OUTPUT -m set --match-set "$ipset_name" dst -j DROP
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- echo "Blocked successfully$country_codeIP address"
- rm "${country_code,,}.zone"
- ;;
+ iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- allow)
- # Create an ipset for allowed countries (if not exist)
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ echo "Blocked successfully$country_codeIP address"
+ rm "${country_code,,}.zone"
+ ;;
- # Download IP area file
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "Error: Download$country_codeIP zone file failed"
- exit 1
- fi
+ allow)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
+
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "Error: Download$country_codeIP zone file failed"
+ continue
+ fi
- # Delete existing national rules
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
- ipset flush "$ipset_name"
+ ipset flush "$ipset_name"
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- # Add IP to ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
- # Only IPs in designated countries are allowed
- iptables -P INPUT DROP
- iptables -P OUTPUT DROP
- iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- iptables -A OUTPUT -m set --match-set "$ipset_name" dst -j ACCEPT
+ iptables -P INPUT DROP
+ iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- echo "Successfully only allowed$country_codeIP address"
- rm "${country_code,,}.zone"
- ;;
+ echo "Successfully allowed$country_codeIP address"
+ rm "${country_code,,}.zone"
+ ;;
- unblock)
- # Delete the iptables rules for the country
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
+ unblock)
+ iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- # Destroy ipset
- if ipset list "$ipset_name" &> /dev/null; then
- ipset destroy "$ipset_name"
- fi
+ if ipset list "$ipset_name" &> /dev/null; then
+ ipset destroy "$ipset_name"
+ fi
- echo "Successfully lifted$country_codeIP address restrictions"
- ;;
+ echo "Removed successfully$country_codeIP address restrictions"
+ ;;
- *)
- ;;
- esac
+ *)
+ echo "Usage: manage_country_rules {block|allow|unblock} "
+ ;;
+ esac
+ done
}
+
+
+
+
+
+
iptables_panel() {
root_use
install iptables
save_iptables_rules
while true; do
clear
- echo "Advanced Firewall Management"
- send_stats "Advanced Firewall Management"
+ echo "Advanced firewall management"
+ send_stats "Advanced firewall management"
echo "------------------------"
iptables -L INPUT
echo ""
- echo "Firewall Management"
+ echo "Firewall management"
echo "------------------------"
- echo "1. Open the specified port 2. Close the specified port"
+ echo "1. Open the designated port 2. Close the designated port"
echo "3. Open all ports 4. Close all ports"
echo "------------------------"
echo "5. IP whitelist 6. IP blacklist"
@@ -1131,24 +1103,24 @@ iptables_panel() {
echo "------------------------"
echo "11. Allow PING 12. Disable PING"
echo "------------------------"
- echo "13. Start DDOS Defense 14. Turn off DDOS Defense"
+ echo "13. Start DDOS defense 14. Turn off DDOS defense"
echo "------------------------"
- echo "15. Block specified country IP 16. Only specified country IPs are allowed"
- echo "17. Release IP restrictions in designated countries"
+ echo "15. Block specified country IPs 16. Allow only specified country IPs"
+ echo "17. Lift IP restrictions in designated countries"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
read -e -p "Please enter the open port number:" o_port
open_port $o_port
- send_stats "Open a specified port"
+ send_stats "Open specified port"
;;
2)
read -e -p "Please enter the closed port number:" c_port
close_port $c_port
- send_stats "Close the specified port"
+ send_stats "Close specified port"
;;
3)
# Open all ports
@@ -1185,21 +1157,21 @@ iptables_panel() {
5)
# IP whitelist
- read -e -p "Please enter the IP or IP segment to release:" o_ip
+ read -e -p "Please enter the allowed IP or IP segment:" o_ip
allow_ip $o_ip
;;
6)
# IP blacklist
- read -e -p "Please enter the blocked IP or IP segment:" c_ip
+ read -e -p "Please enter the blocked IP or IP range:" c_ip
block_ip $c_ip
;;
7)
- # Clear the specified IP
+ # Clear specified IP
read -e -p "Please enter the cleared IP:" d_ip
iptables -D INPUT -s $d_ip -j ACCEPT 2>/dev/null
iptables -D INPUT -s $d_ip -j DROP 2>/dev/null
iptables-save > /etc/iptables/rules.v4
- send_stats "Clear the specified IP"
+ send_stats "Clear specified IP"
;;
11)
# Allow PING
@@ -1223,20 +1195,20 @@ iptables_panel() {
;;
15)
- read -e -p "Please enter the blocked country code (such as CN, US, JP):" country_code
+ read -e -p "Please enter the blocked country code (multiple country codes can be separated by spaces, such as CN US JP):" country_code
manage_country_rules block $country_code
- send_stats "Allowed countries$country_codeIP"
+ send_stats "allow countries$country_codeIP"
;;
16)
- read -e -p "Please enter the allowed country code (such as CN, US, JP):" country_code
+ read -e -p "Please enter the allowed country codes (multiple country codes can be separated by spaces, such as CN US JP):" country_code
manage_country_rules allow $country_code
- send_stats "Block the country$country_codeIP"
+ send_stats "block country$country_codeIP"
;;
17)
- read -e -p "Please enter the cleared country code (such as CN, US, JP):" country_code
+ read -e -p "Please enter the cleared country code (multiple country codes can be separated by spaces, such as CN US JP):" country_code
manage_country_rules unblock $country_code
- send_stats "Clear the country$country_codeIP"
+ send_stats "clear country$country_codeIP"
;;
*)
@@ -1252,25 +1224,23 @@ iptables_panel() {
-
-
add_swap() {
local new_swap=$1 # 获取传入的参数
# Get all swap partitions in the current system
local swap_partitions=$(grep -E '^/dev/' /proc/swaps | awk '{print $1}')
- # Iterate over and delete all swap partitions
+ # Traverse and delete all swap partitions
for partition in $swap_partitions; do
swapoff "$partition"
wipefs -a "$partition"
mkswap -f "$partition"
done
- # Make sure /swapfile is no longer used
+ # Make sure /swapfile is no longer in use
swapoff /swapfile
- # Delete the old /swapfile
+ # Delete old /swapfile
rm -f /swapfile
# Create a new swap partition
@@ -1288,7 +1258,7 @@ add_swap() {
rc-update add local
fi
- echo -e "The virtual memory size has been resized to${gl_huang}${new_swap}${gl_bai}M"
+ echo -e "The virtual memory size has been adjusted to${gl_huang}${new_swap}${gl_bai}M"
}
@@ -1319,16 +1289,16 @@ ldnmp_v() {
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e "nginx : ${gl_huang}v$nginx_version${gl_bai}"
- # Get the mysql version
+ # Get mysql version
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local mysql_version=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SELECT VERSION();" 2>/dev/null | tail -n 1)
echo -n -e " mysql : ${gl_huang}v$mysql_version${gl_bai}"
- # Get the php version
+ # Get php version
local php_version=$(docker exec php php -v 2>/dev/null | grep -oP "PHP \K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e " php : ${gl_huang}v$php_version${gl_bai}"
- # Get the redis version
+ # Get redis version
local redis_version=$(docker exec redis redis-server -v 2>&1 | grep -oP "v=+\K[0-9]+\.[0-9]+")
echo -e " redis : ${gl_huang}v$redis_version${gl_bai}"
@@ -1342,11 +1312,9 @@ ldnmp_v() {
install_ldnmp_conf() {
# Create necessary directories and files
- cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/redis web/log/nginx && touch web/docker-compose.yml
+ cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/stream.d web/redis web/log/nginx web/letsencrypt && touch web/docker-compose.yml
wget -O /home/web/nginx.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default10.conf
- wget -O /home/web/redis/valkey.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/valkey.conf
-
default_server_ssl
@@ -1362,31 +1330,69 @@ install_ldnmp_conf() {
}
+update_docker_compose_with_db_creds() {
+ cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
+ if ! grep -q "letsencrypt" /home/web/docker-compose.yml; then
+ wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
-install_ldnmp() {
+ dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+
+ sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
+ fi
+
+ if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
+ sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ fi
- check_swap
+}
- cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
- if ! grep -q "network_mode" /home/web/docker-compose.yml; then
- wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
- dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
- fi
- if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
- sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- fi
+auto_optimize_dns() {
+ # Get the country code (such as CN, US, etc.)
+ local country=$(curl -s ipinfo.io/country)
+
+ # Set DNS based on country
+ if [ "$country" = "CN" ]; then
+ local dns1_ipv4="223.5.5.5"
+ local dns2_ipv4="183.60.83.19"
+ local dns1_ipv6="2400:3200::1"
+ local dns2_ipv6="2400:da00::6666"
+ else
+ local dns1_ipv4="1.1.1.1"
+ local dns2_ipv4="8.8.8.8"
+ local dns1_ipv6="2606:4700:4700::1111"
+ local dns2_ipv6="2001:4860:4860::8888"
+ fi
+
+ set_dns
+
+
+}
+
+
+prefer_ipv4() {
+grep -q '^precedence ::ffff:0:0/96 100' /etc/gai.conf 2>/dev/null \
+ || echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
+echo "Switched to IPv4 priority"
+send_stats "Switched to IPv4 priority"
+}
+
+
+
+
+install_ldnmp() {
+
+ update_docker_compose_with_db_creds
cd /home/web && docker compose up -d
sleep 1
@@ -1395,11 +1401,19 @@ install_ldnmp() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- restart_ldnmp
+
+ # mysql tuning
+ wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
+ docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
+ rm -rf /home/custom_mysql_config.cnf
+
+ restart_ldnmp
+ sleep 2
+
clear
- echo "LDNMP environment has been installed"
+ echo "The LDNMP environment is installed"
echo "------------------------"
ldnmp_v
@@ -1416,19 +1430,18 @@ install_certbot() {
local cron_job="0 0 * * * ~/auto_cert_renewal.sh"
crontab -l 2>/dev/null | grep -vF "$cron_job" | crontab -
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "Renewal task has been updated"
+ echo "The renewal task has been updated"
}
install_ssltls() {
docker stop nginx > /dev/null 2>&1
- check_port > /dev/null 2>&1
cd ~
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ ! -f "$file_path" ]; then
local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
- local ipv6_pattern='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))))$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
if [[ ($yuming =~ $ipv4_pattern || $yuming =~ $ipv6_pattern) ]]; then
mkdir -p /etc/letsencrypt/live/$yuming/
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -1438,7 +1451,7 @@ install_ssltls() {
openssl req -x509 -key /etc/letsencrypt/live/$yuming/privkey.pem -out /etc/letsencrypt/live/$yuming/fullchain.pem -days 5475 -subj "/C=US/ST=State/L=City/O=Organization/OU=Organizational Unit/CN=Common Name"
fi
else
- docker run -it --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
fi
fi
mkdir -p /home/web/certs/
@@ -1468,14 +1481,14 @@ install_ssltls_text() {
add_ssl() {
-echo -e "${gl_huang}Quickly apply for an SSL certificate, automatically renew your signature before expiration${gl_bai}"
+echo -e "${gl_huang}Quickly apply for an SSL certificate and automatically renew it before expiration${gl_bai}"
yuming="${1:-}"
if [ -z "$yuming" ]; then
add_yuming
fi
install_docker
install_certbot
-docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
install_ssltls_text
@@ -1484,7 +1497,7 @@ ssl_ps
ssl_ps() {
- echo -e "${gl_huang}The expiration of the applied certificate${gl_bai}"
+ echo -e "${gl_huang}Expiration status of applied certificates${gl_bai}"
echo "Site information Certificate expiration time"
echo "------------------------"
for cert_dir in /etc/letsencrypt/live/*; do
@@ -1524,22 +1537,79 @@ certs_status() {
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ -f "$file_path" ]; then
- send_stats "Successful application for domain name certificate"
+ send_stats "Domain name certificate application successful"
else
- send_stats "Application for domain name certificate failed"
- echo -e "${gl_hong}Notice:${gl_bai}The certificate application failed. Please check the following possible reasons and try again:"
- echo -e "1. Domain name spelling error ➠ Please check whether the domain name is entered correctly"
- echo -e "2. DNS resolution problem ➠ Confirm that the domain name has been correctly resolved to this server IP"
- echo -e "3. Network configuration issues ➠ If you use Cloudflare Warp and other virtual networks, please temporarily shut down"
- echo -e "4. Firewall restrictions ➠ Check whether port 80/443 is open to ensure verification is accessible"
+ send_stats "Domain name certificate application failed"
+ echo -e "${gl_hong}Notice:${gl_bai}Certificate application failed, please check the following possible reasons and try again:"
+ echo -e "1. Domain name is spelled incorrectly ➠ Please check whether the domain name is entered correctly"
+ echo -e "2. DNS resolution problem ➠ Confirm that the domain name has been correctly resolved to the server IP"
+ echo -e "3. Network configuration issues ➠ If you use virtual networks such as Cloudflare Warp, please temporarily shut down"
+ echo -e "4. Firewall restrictions ➠ Check whether port 80/443 is open and ensure that it is accessible"
echo -e "5. The number of applications exceeds the limit ➠ Let's Encrypt has a weekly limit (5 times/domain name/week)"
- echo -e "6. Domestic registration restrictions ➠ Please confirm whether the domain name is registered in mainland China"
- break_end
- clear
- echo "Please try deploying again$webname"
- add_yuming
- install_ssltls
- certs_status
+ echo -e "6. Domestic registration restrictions ➠ For mainland China environment, please confirm whether the domain name is registered"
+ echo "------------------------"
+ echo "1. Reapply 2. Import existing certificate 0. Exit"
+ echo "------------------------"
+ read -e -p "Please enter your choice:" sub_choice
+ case $sub_choice in
+ 1)
+ send_stats "Reapply"
+ echo "Please try deploying again$webname"
+ add_yuming
+ install_ssltls
+ certs_status
+
+ ;;
+ 2)
+ send_stats "Import existing certificate"
+
+ # Define file path
+ local cert_file="/home/web/certs/${yuming}_cert.pem"
+ local key_file="/home/web/certs/${yuming}_key.pem"
+
+ mkdir -p /home/web/certs
+
+ # 1. Enter the certificate (both ECC and RSA certificates start with BEGIN CERTIFICATE)
+ echo "Please paste the certificate (CRT/PEM) content (press Enter twice to end):"
+ local cert_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$cert_content" == *"-----BEGIN"* ]] && break
+ cert_content+="${line}"$'\n'
+ done
+
+ # 2. Enter the private key (compatible with RSA, ECC, PKCS#8)
+ echo "Please paste the certificate private key (Private Key) content (press Enter twice to end):"
+ local key_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$key_content" == *"-----BEGIN"* ]] && break
+ key_content+="${line}"$'\n'
+ done
+
+ # 3. Intelligent verification
+ # Just include "BEGIN CERTIFICATE" and "PRIVATE KEY" to pass
+ if [[ "$cert_content" == *"-----BEGIN CERTIFICATE-----"* && "$key_content" == *"PRIVATE KEY-----"* ]]; then
+ echo -n "$cert_content" > "$cert_file"
+ echo -n "$key_content" > "$key_file"
+
+ chmod 644 "$cert_file"
+ chmod 600 "$key_file"
+
+ # Identify the current certificate type and display it
+ if [[ "$key_content" == *"EC PRIVATE KEY"* ]]; then
+ echo "Detected that the ECC certificate was saved successfully."
+ else
+ echo "Detected that the RSA certificate was saved successfully."
+ fi
+ auth_method="ssl_imported"
+ else
+ echo "Error: Invalid certificate or private key format!"
+ certs_status
+ fi
+ ;;
+ *)
+ exit
+ ;;
+ esac
fi
}
@@ -1557,10 +1627,44 @@ fi
add_yuming() {
ip_address
echo -e "First resolve the domain name to the local IP:${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
- read -e -p "Please enter your IP or the resolved domain name:" yuming
+ read -e -p "Please enter your IP or resolved domain name:" yuming
+}
+
+
+check_ip_and_get_access_port() {
+ local yuming="$1"
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ read -e -p "Please enter the access/listening port, and press Enter to use 80 by default:" access_port
+ access_port=${access_port:-80}
+ fi
+}
+
+
+
+update_nginx_listen_port() {
+ local yuming="$1"
+ local access_port="$2"
+ local conf="/home/web/conf.d/${yuming}.conf"
+
+ # Skip if access_port is empty
+ [ -z "$access_port" ] && return 0
+
+ # Remove all listen lines
+ sed -i '/^[[:space:]]*listen[[:space:]]\+/d' "$conf"
+
+ # Insert new listen after server {
+ sed -i "/server {/a\\
+ listen ${access_port};\\
+ listen [::]:${access_port};
+" "$conf"
}
+
add_db() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbname="${dbname}"
@@ -1571,28 +1675,8 @@ add_db() {
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "CREATE DATABASE $dbname; GRANT ALL PRIVILEGES ON $dbname.* TO \"$dbuse\"@\"%\";"
}
-reverse_proxy() {
- ip_address
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
- sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$ipv4_address/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$duankou|g" /home/web/conf.d/$yuming.conf
- nginx_http_on
- docker exec nginx nginx -s reload
-}
-
-
-restart_redis() {
- rm -rf /home/web/redis/*
- docker exec redis redis-cli FLUSHALL > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory 1gb > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory-policy allkeys-lru > /dev/null 2>&1
-}
-
-
restart_ldnmp() {
- restart_redis
docker exec nginx chown -R nginx:nginx /var/www/html > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/proxy > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/fastcgi > /dev/null 2>&1
@@ -1600,7 +1684,8 @@ restart_ldnmp() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi > /dev/null 2>&1
docker exec php chown -R www-data:www-data /var/www/html > /dev/null 2>&1
docker exec php74 chown -R www-data:www-data /var/www/html > /dev/null 2>&1
- cd /home/web && docker compose restart nginx php php74
+ cd /home/web && docker compose restart
+
}
@@ -1635,7 +1720,7 @@ phpmyadmin_upgrade() {
cd /home/web/
docker rm -f $ldnmp_pods > /dev/null 2>&1
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
- curl -sS -O https://raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
docker compose -f docker-compose.phpmyadmin.yml up -d
clear
ip_address
@@ -1657,18 +1742,18 @@ cf_purge_cache() {
# Check if the configuration file exists
if [ -f "$CONFIG_FILE" ]; then
- # Read API_TOKEN and zone_id from configuration files
+ # Read API_TOKEN and zone_id from configuration file
read API_TOKEN EMAIL ZONE_IDS < "$CONFIG_FILE"
- # Convert ZONE_IDS to an array
+ # Convert ZONE_IDS to array
ZONE_IDS=($ZONE_IDS)
else
- # Prompt the user whether to clean the cache
- read -e -p "Need to clean Cloudflare's cache? (y/n):" answer
+ # Prompt user whether to clear cache
+ read -e -p "Need to clear Cloudflare's cache? (y/n):" answer
if [[ "$answer" == "y" ]]; then
- echo "CF information is saved in$CONFIG_FILE, you can modify CF information later"
+ echo "CF information is stored in$CONFIG_FILE, you can modify the CF information later"
read -e -p "Please enter your API_TOKEN:" API_TOKEN
read -e -p "Please enter your CF username:" EMAIL
- read -e -p "Please enter zone_id (multiple separated by spaces):" -a ZONE_IDS
+ read -e -p "Please enter zone_id (separate multiple with spaces):" -a ZONE_IDS
mkdir -p /home/web/config/
echo "$API_TOKEN $EMAIL ${ZONE_IDS[*]}" > "$CONFIG_FILE"
@@ -1685,16 +1770,15 @@ cf_purge_cache() {
--data '{"purge_everything":true}'
done
- echo "The cache clear request has been sent."
+ echo "Cache clearing request has been sent."
}
web_cache() {
- send_stats "Clean up site cache"
+ send_stats "Clear site cache"
cf_purge_cache
cd /home/web && docker compose restart
- restart_redis
}
@@ -1704,14 +1788,14 @@ web_del() {
send_stats "Delete site data"
yuming_list="${1:-}"
if [ -z "$yuming_list" ]; then
- read -e -p "To delete site data, please enter your domain name (multiple domain names are separated by spaces):" yuming_list
+ read -e -p "To delete site data, please enter your domain name (separate multiple domain names with spaces):" yuming_list
if [[ -z "$yuming_list" ]]; then
return
fi
fi
for yuming in $yuming_list; do
- echo "Deleting the domain name:$yuming"
+ echo "Domain name is being deleted:$yuming"
rm -r /home/web/html/$yuming > /dev/null 2>&1
rm /home/web/conf.d/$yuming.conf > /dev/null 2>&1
rm /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
@@ -1721,8 +1805,8 @@ web_del() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
- # Check whether the database exists before deleting it to avoid errors
- echo "Deleting the database:$dbname"
+ # Check whether the database exists before deleting it to avoid errors.
+ echo "Deleting database:$dbname"
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE ${dbname};" > /dev/null 2>&1
done
@@ -1738,23 +1822,23 @@ nginx_waf() {
wget -O /home/web/nginx.conf "${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf"
fi
- # Decide to turn on or off WAF according to the mode parameter
+ # Determine whether to turn on or off WAF according to the mode parameter
if [ "$mode" == "on" ]; then
- # Turn on WAF: Remove comments
+ # Turn on WAF: remove comments
sed -i 's|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)# modsecurity on;|\1modsecurity on;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)# modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|\1modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|' /home/web/nginx.conf > /dev/null 2>&1
elif [ "$mode" == "off" ]; then
- # Close WAF: Add Comments
+ # Turn off WAF: add comments
sed -i 's|^load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)modsecurity on;|\1# modsecurity on;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|\1# modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|' /home/web/nginx.conf > /dev/null 2>&1
else
- echo "Invalid parameter: Use 'on' or 'off'"
+ echo "Invalid argument: use 'on' or 'off'"
return 1
fi
- # Check nginx images and handle them according to the situation
+ # Check the nginx image and handle it accordingly
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1768,7 +1852,7 @@ check_waf_status() {
if grep -q "^\s*#\s*modsecurity on;" /home/web/nginx.conf; then
waf_status=""
elif grep -q "modsecurity on;" /home/web/nginx.conf; then
- waf_status=" WAF已开启"
+ waf_status="WAF is turned on"
else
waf_status=""
fi
@@ -1776,8 +1860,8 @@ check_waf_status() {
check_cf_mode() {
- if [ -f "/path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf" ]; then
- CFmessage=" cf模式已开启"
+ if [ -f "/etc/fail2ban/action.d/cloudflare-docker.conf" ]; then
+ CFmessage="cf mode is on"
else
CFmessage=""
fi
@@ -1805,7 +1889,7 @@ patch_wp_memory_limit() {
sed -i "/define(['\"]WP_MEMORY_LIMIT['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_MAX_MEMORY_LIMIT['\"].*/d" "$FILE"
- # Insert a new definition before the line with "Happy publishing"
+ # Insert the new definition before the line containing "Happy publishing"
awk -v insert="define('WP_MEMORY_LIMIT', '$MEMORY_LIMIT');\ndefine('WP_MAX_MEMORY_LIMIT', '$MAX_MEMORY_LIMIT');" \
'
/Happy publishing/ {
@@ -1833,7 +1917,7 @@ patch_wp_debug() {
sed -i "/define(['\"]WP_DEBUG_DISPLAY['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_DEBUG_LOG['\"].*/d" "$FILE"
- # Insert a new definition before the line with "Happy publishing"
+ # Insert the new definition before the line containing "Happy publishing"
awk -v insert="define('WP_DEBUG_DISPLAY', $DEBUG_DISPLAY);\ndefine('WP_DEBUG_LOG', $DEBUG_LOG);" \
'
/Happy publishing/ {
@@ -1847,6 +1931,43 @@ patch_wp_debug() {
}
+
+
+patch_wp_url() {
+ local HOME_URL="$1"
+ local SITE_URL="$2"
+ local TARGET_DIR="/home/web/html"
+
+ find "$TARGET_DIR" -type f -name "wp-config-sample.php" | while read -r FILE; do
+ # Delete old definition
+ sed -i "/define(['\"]WP_HOME['\"].*/d" "$FILE"
+ sed -i "/define(['\"]WP_SITEURL['\"].*/d" "$FILE"
+
+ # Generate insert content
+ INSERT="
+define('WP_HOME', '$HOME_URL');
+define('WP_SITEURL', '$SITE_URL');
+"
+
+ # Insert before “Happy publishing”
+ awk -v insert="$INSERT" '
+ /Happy publishing/ {
+ print insert
+ }
+ { print }
+ ' "$FILE" > "$FILE.tmp" && mv -f "$FILE.tmp" "$FILE"
+
+ echo "[+] Updated WP_HOME and WP_SITEURL in $FILE"
+ done
+}
+
+
+
+
+
+
+
+
nginx_br() {
local mode=$1
@@ -1856,7 +1977,7 @@ nginx_br() {
fi
if [ "$mode" == "on" ]; then
- # Turn on Brotli: Remove comments
+ # Turn on Brotli: remove comments
sed -i 's|# load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|# load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1870,7 +1991,7 @@ nginx_br() {
sed -i '/brotli_types/,+6 s/^\(\s*\)#\s*/\1/' /home/web/nginx.conf
elif [ "$mode" == "off" ]; then
- # Close Brotli: Add comments
+ # Close Brotli: add comments
sed -i 's|^load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|# load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|# load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1886,11 +2007,11 @@ nginx_br() {
}' /home/web/nginx.conf
else
- echo "Invalid parameter: Use 'on' or 'off'"
+ echo "Invalid argument: use 'on' or 'off'"
return 1
fi
- # Check nginx images and handle them according to the situation
+ # Check the nginx image and handle it accordingly
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1912,7 +2033,7 @@ nginx_zstd() {
fi
if [ "$mode" == "on" ]; then
- # Turn on Zstd: Remove comments
+ # Turn on Zstd: remove comments
sed -i 's|# load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|# load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1927,7 +2048,7 @@ nginx_zstd() {
elif [ "$mode" == "off" ]; then
- # Close Zstd: Add comments
+ # Close Zstd: add comments
sed -i 's|^load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|# load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|# load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1943,11 +2064,11 @@ nginx_zstd() {
else
- echo "Invalid parameter: Use 'on' or 'off'"
+ echo "Invalid argument: use 'on' or 'off'"
return 1
fi
- # Check nginx images and handle them according to the situation
+ # Check the nginx image and handle it accordingly
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1974,7 +2095,7 @@ nginx_gzip() {
elif [ "$mode" == "off" ]; then
sed -i 's|^\(\s*\)gzip on;|\1# gzip on;|' /home/web/nginx.conf > /dev/null 2>&1
else
- echo "Invalid parameter: Use 'on' or 'off'"
+ echo "Invalid argument: use 'on' or 'off'"
return 1
fi
@@ -1990,45 +2111,42 @@ nginx_gzip() {
web_security() {
send_stats "LDNMP environment defense"
while true; do
+ check_f2b_status
check_waf_status
check_cf_mode
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "Server website defense program${check_docker}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
+ echo -e "Server website defense program${check_f2b_status}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
echo "------------------------"
- echo "1. Install the defense program"
+ echo "1. Install a defense program"
echo "------------------------"
- echo "5. View SSH interception record 6. View website interception record"
- echo "7. View the list of defense rules 8. View real-time monitoring of logs"
+ echo "5. View SSH interception records 6. View website interception records"
+ echo "7. View the list of defense rules 8. View logs for real-time monitoring"
echo "------------------------"
- echo "11. Configure intercept parameters 12. Clear all blocked IPs"
+ echo "11. Configure interception parameters 12. Clear all blocked IPs"
echo "------------------------"
- echo "21. cloudflare mode 22. High load on 5 seconds shield"
+ echo "21. cloudflare mode 22. Enable 5 seconds shield under high load"
echo "------------------------"
echo "31. Turn on WAF 32. Turn off WAF"
- echo "33. Turn on DDOS Defense 34. Turn off DDOS Defense"
+ echo "33. Turn on DDOS defense 34. Turn off DDOS defense"
echo "------------------------"
echo "9. Uninstall the defense program"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
f2b_install_sshd
- cd /path/to/fail2ban/config/fail2ban/filter.d
+ cd /etc/fail2ban/filter.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/fail2ban-nginx-cc.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-418.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-deny.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-unauthorized.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-bad-request.conf
+
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- sed -i "/cloudflare/d" /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ sed -i "/cloudflare/d" /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
;;
5)
@@ -2042,93 +2160,94 @@ web_security() {
local xxx="fail2ban-nginx-cc"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-418"
+ local xxx="nginx-418"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-bad-request"
+ local xxx="nginx-bad-request"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-badbots"
+ local xxx="nginx-badbots"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-botsearch"
+ local xxx="nginx-botsearch"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-deny"
+ local xxx="nginx-deny"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-http-auth"
+ local xxx="nginx-http-auth"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-unauthorized"
+ local xxx="nginx-unauthorized"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-php-url-fopen"
+ local xxx="php-url-fopen"
f2b_status_xxx
echo "------------------------"
;;
7)
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
;;
8)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
+ tail -f /var/log/fail2ban.log
;;
9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
+ remove fail2ban
+ rm -rf /etc/fail2ban
crontab -l | grep -v "CF-Under-Attack.sh" | crontab - 2>/dev/null
echo "Fail2Ban defense program has been uninstalled"
+ break
;;
11)
install nano
- nano /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ nano /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
break
;;
12)
- docker exec -it fail2ban fail2ban-client unban --all
+ fail2ban-client unban --all
;;
21)
send_stats "cloudflare mode"
- echo "Go to the upper right corner of the cf background, select the API token on the left, and obtain the Global API Key"
+ echo "Go to my profile in the upper right corner of the cf backend, select the API token on the left, and get the Global API Key"
echo "https://dash.cloudflare.com/login"
- read -e -p "Enter CF account number:" cfuser
- read -e -p "Enter the Global API Key for CF:" cftoken
+ read -e -p "Enter CF’s account number:" cfuser
+ read -e -p "Enter CF’s Global API Key:" cftoken
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default11.conf
docker exec nginx nginx -s reload
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- cd /path/to/fail2ban/config/fail2ban/action.d
+ cd /etc/fail2ban/action.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/cloudflare-docker.conf
- sed -i "s/kejilion@outlook.com/$cfuser/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
- sed -i "s/APIKEY00000/$cftoken/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/kejilion@outlook.com/$cfuser/g" /etc/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/APIKEY00000/$cftoken/g" /etc/fail2ban/action.d/cloudflare-docker.conf
f2b_status
- echo "Cloudflare mode is configured to view intercept records in the cf background, site-security-events"
+ echo "Cloudflare mode has been configured, and the interception record can be viewed in the cf background, site-security-events"
;;
22)
- send_stats "High load on 5 seconds shield"
- echo -e "${gl_huang}The website automatically detects every 5 minutes. When high load is detected, the shield will be automatically turned on, and low load will be automatically turned off for 5 seconds.${gl_bai}"
+ send_stats "High load enables 5 seconds shield"
+ echo -e "${gl_huang}The website automatically detects every 5 minutes. When it detects high load, it will automatically open the shield, and when it detects low load, it will automatically close the shield for 5 seconds.${gl_bai}"
echo "--------------"
echo "Get CF parameters:"
- echo -e "Go to the upper right corner of the cf background, select the API token on the left, and obtain it${gl_huang}Global API Key${gl_bai}"
- echo -e "Go to the bottom right of the cf background domain name summary page to get${gl_huang}Region ID${gl_bai}"
+ echo -e "Go to my profile in the upper right corner of the cf backend, select the API token on the left, and get${gl_huang}Global API Key${gl_bai}"
+ echo -e "Go to the bottom right of the cf backend domain name summary page to get it${gl_huang}Area ID${gl_bai}"
echo "https://dash.cloudflare.com/login"
echo "--------------"
- read -e -p "Enter CF account number:" cfuser
- read -e -p "Enter the Global API Key for CF:" cftoken
- read -e -p "Enter the region ID of the domain name in CF:" cfzonID
+ read -e -p "Enter CF’s account number:" cfuser
+ read -e -p "Enter CF’s Global API Key:" cftoken
+ read -e -p "Enter the zone ID of the domain name in CF:" cfzonID
cd ~
install jq bc
@@ -2147,7 +2266,7 @@ web_security() {
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
echo "High load automatic shield opening script has been added"
else
- echo "Automatic shield script already exists, no need to add it"
+ echo "The automatic shield opening script already exists, no need to add it"
fi
;;
@@ -2160,8 +2279,8 @@ web_security() {
32)
nginx_waf off
- echo "Site WAF has been closed"
- send_stats "Site WAF has been closed"
+ echo "Site WAF is down"
+ send_stats "Site WAF is down"
;;
33)
@@ -2176,26 +2295,23 @@ web_security() {
break
;;
esac
- fi
break_end
done
}
-check_nginx_mode() {
-
-CONFIG_FILE="/home/web/nginx.conf"
+check_ldnmp_mode() {
-# Get the current worker_processes setting value
-current_value=$(grep -E '^\s*worker_processes\s+[0-9]+;' "$CONFIG_FILE" | awk '{print $2}' | tr -d ';')
+ local MYSQL_CONTAINER="mysql"
+ local MYSQL_CONF="/etc/mysql/conf.d/custom_mysql_config.cnf"
-# Set mode information according to value
-if [ "$current_value" = "8" ]; then
- mode_info=" 高性能模式"
-else
- mode_info=" 标准模式"
-fi
+ # Check if MySQL configuration file contains 4096M
+ if docker exec "$MYSQL_CONTAINER" grep -q "4096M" "$MYSQL_CONF" 2>/dev/null; then
+ mode_info="High performance mode"
+ else
+ mode_info="Standard mode"
+ fi
@@ -2204,25 +2320,25 @@ fi
check_nginx_compression() {
- CONFIG_FILE="/home/web/nginx.conf"
+ local CONFIG_FILE="/home/web/nginx.conf"
- # Check whether zstd is enabled and not commented (the whole line starts with zstd on;)
+ # Check whether zstd is on and uncommented (the whole line starts with zstd on;)
if grep -qE '^\s*zstd\s+on;' "$CONFIG_FILE"; then
- zstd_status=" zstd压缩已开启"
+ zstd_status="zstd compression is on"
else
zstd_status=""
fi
- # Check if brotli is enabled and not commented
+ # Check if brotli is enabled and uncommented
if grep -qE '^\s*brotli\s+on;' "$CONFIG_FILE"; then
- br_status=" br压缩已开启"
+ br_status="brCompression is on"
else
br_status=""
fi
- # Check if gzip is enabled and not commented
+ # Check if gzip is enabled and uncommented
if grep -qE '^\s*gzip\s+on;' "$CONFIG_FILE"; then
- gzip_status=" gzip压缩已开启"
+ gzip_status="gzip compression is on"
else
gzip_status=""
fi
@@ -2233,13 +2349,13 @@ check_nginx_compression() {
web_optimization() {
while true; do
- check_nginx_mode
+ check_ldnmp_mode
check_nginx_compression
clear
send_stats "Optimize LDNMP environment"
echo -e "Optimize LDNMP environment${gl_lv}${mode_info}${gzip_status}${br_status}${zstd_status}${gl_bai}"
echo "------------------------"
- echo "1. Standard mode 2. High performance mode (recommended 2H4G or above)"
+ echo "1. Standard mode 2. High performance mode (2H4G or above recommended)"
echo "------------------------"
echo "3. Turn on gzip compression 4. Turn off gzip compression"
echo "5. Turn on br compression 6. Turn off br compression"
@@ -2247,14 +2363,16 @@ web_optimization() {
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- send_stats "Site standard mode"
+ send_stats "site standards mode"
+
+ local cpu_cores=$(nproc)
+ local connections=$((1024 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
- # nginx tuning
- sed -i 's/worker_connections.*/worker_connections 10240;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 4;/' /home/web/nginx.conf
# php tuning
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2282,19 +2400,20 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_balanced
- echo "LDNMP environment has been set to standard mode"
+ echo "The LDNMP environment has been set to standard mode"
;;
2)
send_stats "Site high performance mode"
# nginx tuning
- sed -i 's/worker_connections.*/worker_connections 20480;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 8;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((2048 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
# php tuning
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2321,10 +2440,9 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_web_server
- echo "LDNMP environment has been set to high performance mode"
+ echo "The LDNMP environment has been set to high performance mode"
;;
3)
@@ -2371,23 +2489,30 @@ web_optimization() {
+check_docker_app() {
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name" ; then
+ check_docker="${gl_lv}Installed${gl_bai}"
+ else
+ check_docker="${gl_hui}Not installed${gl_bai}"
+ fi
+}
-check_docker_app() {
+# check_docker_app() {
-if docker inspect "$docker_name" &>/dev/null; then
- check_docker="${gl_lv}已安装${gl_bai}"
-else
- check_docker="${gl_hui}未安装${gl_bai}"
-fi
+# if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+# check_docker="${gl_lv} has installed ${gl_bai}"
+# else
+# check_docker="${gl_hui} is not installed ${gl_bai}"
+# fi
-}
+# }
check_docker_app_ip() {
echo "------------------------"
-echo "Access address:"
+echo "Visit address:"
ip_address
@@ -2416,54 +2541,61 @@ done
check_docker_image_update() {
-
local container_name=$1
+ update_status=""
- local country=$(curl -s ipinfo.io/country)
- if [[ "$country" == "CN" ]]; then
- update_status=""
- return
- fi
+ # 1. Regional inspection
+ local country=$(curl -s --max-time 2 ipinfo.io/country)
+ [[ "$country" == "CN" ]] && return
- # Get the container creation time and image name
+ # 2. Get local mirror information
local container_info=$(docker inspect --format='{{.Created}},{{.Config.Image}}' "$container_name" 2>/dev/null)
- local container_created=$(echo "$container_info" | cut -d',' -f1)
- local image_name=$(echo "$container_info" | cut -d',' -f2)
-
- # Extract mirror warehouses and tags
- local image_repo=${image_name%%:*}
- local image_tag=${image_name##*:}
+ [[ -z "$container_info" ]] && return
- # The default label is latest
- [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
-
- # Add support for official images
- [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
-
- # Get image publishing time from Docker Hub API
- local hub_info=$(curl -s "https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag")
- local last_updated=$(echo "$hub_info" | jq -r '.last_updated' 2>/dev/null)
-
- # Verify the time of acquisition
- if [[ -n "$last_updated" && "$last_updated" != "null" ]]; then
- local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
- local last_updated_ts=$(date -d "$last_updated" +%s 2>/dev/null)
+ local container_created=$(echo "$container_info" | cut -d',' -f1)
+ local full_image_name=$(echo "$container_info" | cut -d',' -f2)
+ local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
+
+ # 3. Intelligent routing judgment
+ if [[ "$full_image_name" == ghcr.io* ]]; then
+ # --- Scenario A: Mirror on GitHub (ghcr.io) ---
+ # Extract the warehouse path, for example ghcr.io/onexru/oneimg -> onexru/oneimg
+ local repo_path=$(echo "$full_image_name" | sed 's/ghcr.io\///' | cut -d':' -f1)
+ # Note: The API of ghcr.io is relatively complex. Usually the fastest way is to check the Release of GitHub Repo
+ local api_url="https://api.github.com/repos/$repo_path/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
+
+ elif [[ "$full_image_name" == *"oneimg"* ]]; then
+ # --- Scenario B: Special designation (even in Docker Hub, I want to judge by GitHub Release) ---
+ local api_url="https://api.github.com/repos/onexru/oneimg/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
- # Compare timestamps
- if [[ $container_created_ts -lt $last_updated_ts ]]; then
- update_status="${gl_huang}发现新版本!${gl_bai}"
- else
- update_status=""
- fi
else
- update_status=""
+ # --- Scenario C: Standard Docker Hub ---
+ local image_repo=${full_image_name%%:*}
+ local image_tag=${full_image_name##*:}
+ [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+
+ local api_url="https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag"
+ local remote_date=$(curl -s "$api_url" | jq -r '.last_updated' 2>/dev/null)
fi
+ # 4. Timestamp comparison
+ if [[ -n "$remote_date" && "$remote_date" != "null" ]]; then
+ local remote_ts=$(date -d "$remote_date" +%s 2>/dev/null)
+ if [[ $container_created_ts -lt $remote_ts ]]; then
+ update_status="${gl_huang}New version found!${gl_bai}"
+ fi
+ fi
}
+
+
+
block_container_port() {
local container_name_or_id=$1
local allowed_ip=$2
@@ -2488,7 +2620,7 @@ block_container_port() {
iptables -I DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # Check and release the local network 127.0.0.0/8
+ # Check and allow local network 127.0.0.0/8
if ! iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2505,7 +2637,7 @@ block_container_port() {
iptables -I DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # Check and release the local network 127.0.0.0/8
+ # Check and allow local network 127.0.0.0/8
if ! iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2515,7 +2647,7 @@ block_container_port() {
fi
- echo "IP+ ports have been blocked from accessing the service"
+ echo "IP+port has been blocked from accessing the service"
save_iptables_rules
}
@@ -2541,12 +2673,12 @@ clear_container_rules() {
iptables -D DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
- # Clear the rules for releasing the specified IP
+ # Clear the rules that allow specified IPs
if iptables -C DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # Clear the rules for release local network 127.0.0.0/8
+ # Clear the rules that allow local network 127.0.0.0/8
if iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2560,12 +2692,12 @@ clear_container_rules() {
iptables -D DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
- # Clear the rules for releasing the specified IP
+ # Clear the rules that allow specified IPs
if iptables -C DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # Clear the rules for release local network 127.0.0.0/8
+ # Clear the rules that allow local network 127.0.0.0/8
if iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2576,7 +2708,7 @@ clear_container_rules() {
fi
- echo "IP+ports have been allowed to access the service"
+ echo "IP+port has been allowed to access the service"
save_iptables_rules
}
@@ -2590,20 +2722,20 @@ block_host_port() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "Error: Please provide the port number and the IP that is allowed to access."
- echo "Usage: block_host_port "
+ echo "Error: Please provide port number and IP to allow access."
+ echo "Usage: block_host_port "
return 1
fi
install iptables
- # Denied all other IP access
+ # Deny access from all other IPs
if ! iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -j DROP
fi
- # Allow specified IP access
+ # Allow access to specified IP
if ! iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
@@ -2617,12 +2749,12 @@ block_host_port() {
- # Denied all other IP access
+ # Deny access from all other IPs
if ! iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -j DROP
fi
- # Allow specified IP access
+ # Allow access to specified IP
if ! iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
@@ -2637,7 +2769,7 @@ block_host_port() {
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
fi
- echo "IP+ ports have been blocked from accessing the service"
+ echo "IP+port has been blocked from accessing the service"
save_iptables_rules
}
@@ -2649,47 +2781,47 @@ clear_host_port_rules() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "Error: Please provide the port number and the IP that is allowed to access."
- echo "Usage: clear_host_port_rules "
+ echo "Error: Please provide port number and IP to allow access."
+ echo "Usage: clear_host_port_rules "
return 1
fi
install iptables
- # Clear rules that block all other IP access
+ # Clear the rule that blocks access from all other IPs
if iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -j DROP
fi
- # Clear rules that allow native access
+ # Clear rules that allow local access
if iptables -C INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # Clear rules that allow specified IP access
+ # Clear rules that allow access from specified IPs
if iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # Clear rules that block all other IP access
+ # Clear the rule that blocks access from all other IPs
if iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -j DROP
fi
- # Clear rules that allow native access
+ # Clear rules that allow local access
if iptables -C INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # Clear rules that allow specified IP access
+ # Clear rules that allow access from specified IPs
if iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- echo "IP+ports have been allowed to access the service"
+ echo "IP+port has been allowed to access the service"
save_iptables_rules
}
@@ -2698,16 +2830,33 @@ clear_host_port_rules() {
setup_docker_dir() {
- mkdir -p /home/docker/ 2>/dev/null
+ mkdir -p /home /home/docker 2>/dev/null
+
if [ -d "/vol1/1000/" ] && [ ! -d "/vol1/1000/docker" ]; then
cp -f /home/docker /home/docker1 2>/dev/null
rm -rf /home/docker 2>/dev/null
mkdir -p /vol1/1000/docker 2>/dev/null
ln -s /vol1/1000/docker /home/docker 2>/dev/null
fi
+
+ if [ -d "/volume1/" ] && [ ! -d "/volume1/docker" ]; then
+ cp -f /home/docker /home/docker1 2>/dev/null
+ rm -rf /home/docker 2>/dev/null
+ mkdir -p /volume1/docker 2>/dev/null
+ ln -s /volume1/docker /home/docker 2>/dev/null
+ fi
+
+
}
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "${app_id}" /home/docker/appno.txt || echo "${app_id}" >> /home/docker/appno.txt
+
+}
+
docker_app() {
@@ -2716,11 +2865,11 @@ send_stats "${docker_name}manage"
while true; do
clear
check_docker_app
- check_docker_image_update $docker_name
+ check_docker_image_update "${docker_app_service:-$docker_name}"
echo -e "$docker_name $check_docker $update_status"
echo "$docker_describe"
echo "$docker_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
@@ -2734,26 +2883,37 @@ while true; do
echo "1. Install 2. Update 3. Uninstall"
echo "------------------------"
echo "5. Add domain name access 6. Delete domain name access"
- echo "7. Allow IP+ port access 8. Block IP+ port access"
+ echo "7. Allow IP+port access 8. Block IP+port access"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "Enter the application external service port, and enter the default${docker_port}port:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+ while true; do
+ read -e -p "Enter the application external service port and press Enter to use it by default.${docker_port}port:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}mistake:${gl_bai}port$app_portAlready occupied, please change a port"
+ send_stats "Application port is occupied"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
install jq
install_docker
docker_rum
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+ add_app_id
+
clear
- echo "$docker_nameInstalled"
+ echo "$docker_nameInstallation completed"
check_docker_app_ip
echo ""
$docker_use
@@ -2764,8 +2924,11 @@ while true; do
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
docker_rum
+
+ add_app_id
+
clear
- echo "$docker_nameInstalled"
+ echo "$docker_nameInstallation completed"
check_docker_app_ip
echo ""
$docker_use
@@ -2777,20 +2940,22 @@ while true; do
docker rmi -f "$docker_img"
rm -rf "/home/docker/$docker_name"
rm -f /home/docker/${docker_name}_port.conf
- echo "The app has been uninstalled"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "App has been uninstalled"
send_stats "uninstall$docker_name"
;;
5)
- echo "${docker_name}Domain access settings"
- send_stats "${docker_name}Domain access settings"
+ echo "${docker_name}Domain name access settings"
+ send_stats "${docker_name}Domain name access settings"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
block_container_port "$docker_name" "$ipv4_address"
;;
6)
- echo "Domain name format example.com does not come with https://"
+ echo "Domain name format example.com without https://"
web_del
;;
@@ -2817,7 +2982,6 @@ done
-
docker_app_plus() {
send_stats "$app_name"
while true; do
@@ -2827,9 +2991,10 @@ docker_app_plus() {
echo -e "$app_name $check_docker $update_status"
echo "$app_text"
echo "$app_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_check_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
- local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
+ local docker_port=$(docker port "$docker_check_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
fi
@@ -2841,39 +3006,63 @@ docker_app_plus() {
echo "1. Install 2. Update 3. Uninstall"
echo "------------------------"
echo "5. Add domain name access 6. Delete domain name access"
- echo "7. Allow IP+ port access 8. Block IP+ port access"
+ echo "7. Allow IP+port access 8. Block IP+port access"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "Enter the application external service port, and enter the default${docker_port}port:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+
+ while true; do
+ read -e -p "Enter the application external service port and press Enter to use it by default.${docker_port}port:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}mistake:${gl_bai}port$app_portAlready occupied, please change a port"
+ send_stats "Application port is occupied"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
+
install jq
install_docker
docker_app_install
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+
+ add_app_id
+ send_stats "$app_nameInstall"
;;
+
2)
docker_app_update
+ add_app_id
+ send_stats "$app_namerenew"
;;
+
3)
docker_app_uninstall
rm -f /home/docker/${docker_name}_port.conf
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "$app_nameuninstall"
;;
+
5)
- echo "${docker_name}Domain access settings"
- send_stats "${docker_name}Domain access settings"
+ echo "${docker_name}Domain name access settings"
+ send_stats "${docker_name}Domain name access settings"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
+
;;
6)
- echo "Domain name format example.com does not come with https://"
+ echo "Domain name format example.com without https://"
web_del
;;
7)
@@ -2882,7 +3071,8 @@ docker_app_plus() {
;;
8)
send_stats "Block IP access${docker_name}"
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
;;
*)
break
@@ -2920,7 +3110,7 @@ docker network create $NETWORK_NAME
docker run -d \
--name=node-exporter \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
prom/node-exporter
# Run Prometheus container
@@ -2929,7 +3119,7 @@ docker run -d \
-v $PROMETHEUS_DIR/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $PROMETHEUS_DIR/data:/prometheus \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
--user 0:0 \
prom/prometheus:latest
@@ -2939,7 +3129,7 @@ docker run -d \
-p ${docker_port}:3000 \
-v $GRAFANA_DIR:/var/lib/grafana \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
grafana/grafana:latest
}
@@ -2966,12 +3156,12 @@ tmux_run_d() {
local base_name="tmuxd"
local tmuxd_ID=1
-# Functions that check whether the session exists
+# Function to check if session exists
session_exists() {
tmux has-session -t $1 2>/dev/null
}
-# Loop until a non-existent session name is found
+# Loop until a non-existing session name is found
while session_exists "$base_name-$tmuxd_ID"; do
local tmuxd_ID=$((tmuxd_ID + 1))
done
@@ -2985,52 +3175,41 @@ tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
f2b_status() {
- docker exec -it fail2ban fail2ban-client reload
+ fail2ban-client reload
sleep 3
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
}
f2b_status_xxx() {
- docker exec -it fail2ban fail2ban-client status $xxx
+ fail2ban-client status $xxx
}
-f2b_install_sshd() {
+check_f2b_status() {
+ if command -v fail2ban-client >/dev/null 2>&1; then
+ check_f2b_status="${gl_lv}Installed${gl_bai}"
+ else
+ check_f2b_status="${gl_hui}Not installed${gl_bai}"
+ fi
+}
- docker run -d \
- --name=fail2ban \
- --net=host \
- --cap-add=NET_ADMIN \
- --cap-add=NET_RAW \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e VERBOSITY=-vv \
- -v /path/to/fail2ban/config:/config \
- -v /var/log:/var/log:ro \
- -v /home/web/log/nginx/:/remotelogs/nginx:ro \
- --restart unless-stopped \
- lscr.io/linuxserver/fail2ban:latest
-
- sleep 3
- if grep -q 'Alpine' /etc/issue; then
- cd /path/to/fail2ban/config/fail2ban/filter.d
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd.conf
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd-ddos.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-ssh.conf
- elif command -v dnf &>/dev/null; then
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+f2b_install_sshd() {
+
+ docker rm -f fail2ban >/dev/null 2>&1
+ install fail2ban
+ start fail2ban
+ enable fail2ban
+
+ if command -v dnf &>/dev/null; then
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
- else
+ fi
+
+ if command -v apt &>/dev/null; then
install rsyslog
systemctl start rsyslog
systemctl enable rsyslog
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/linux-ssh.conf
- systemctl restart rsyslog
fi
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf
}
f2b_sshd() {
@@ -3043,6 +3222,80 @@ f2b_sshd() {
fi
}
+# Basic parameter configuration: ban duration (bantime), time window (findtime), number of retries (maxretry)
+# illustrate:
+# - Prioritize writing to /etc/fail2ban/jail.d/sshd.local (overrides the default jail configuration and is not easy to lose when upgrading)
+# - If it is Alpine and the jail names are different, still write sshd.local; Fail2Ban will match according to the jail name.
+f2b_basic_config() {
+ root_use
+ install nano
+
+ if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo -e "${gl_hui}fail2ban-client is not detected, please install fail2ban first.${gl_bai}"
+ return
+ fi
+
+ local jail_name="sshd"
+ if grep -qi 'Alpine' /etc/issue 2>/dev/null; then
+ # Alpine default jail is usually sshd; only switches if custom alpine-sshd rules are detected
+ if [ -f /etc/fail2ban/filter.d/alpine-sshd.conf ] || [ -f /etc/fail2ban/jail.d/alpine-ssh.conf ] || [ -f /etc/fail2ban/jail.d/alpine-sshd.local ]; then
+ jail_name="alpine-sshd"
+ fi
+ fi
+
+ echo "About to configure the SSH jail:$jail_name"
+ read -e -p "Bantime bantime (seconds/minutes/hours, such as 3600 or 1h) [default 1h]:" bantime
+ read -e -p "Time window findtime (seconds/minutes/hours, e.g. 600 or 10m) [default 10m]:" findtime
+ read -e -p "Number of retries maxretry (integer) [default 5]:" maxretry
+
+ bantime=${bantime:-1h}
+ findtime=${findtime:-10m}
+ maxretry=${maxretry:-5}
+
+ mkdir -p /etc/fail2ban/jail.d
+ cat > /etc/fail2ban/jail.d/sshd.local <> /etc/fail2ban/jail.d/sshd.local
+ fi
+ fi
+
+ echo -e "${gl_lv}Configuration has been written${gl_bai}: /etc/fail2ban/jail.d/sshd.local"
+ fail2ban-client reload >/dev/null 2>&1 || true
+ sleep 2
+ fail2ban-client status $jail_name || true
+}
+
+# Directly open the main configuration/overlay configuration editor (nano)
+# Edit /etc/fail2ban/jail.d/sshd.local first (more secure), create it if it does not exist
+f2b_edit_config() {
+ root_use
+ install nano
+
+ if [ ! -d /etc/fail2ban ]; then
+ echo -e "${gl_hui}/etc/fail2ban does not exist, please install fail2ban first.${gl_bai}"
+ return
+ fi
+
+ mkdir -p /etc/fail2ban/jail.d
+ local cfg="/etc/fail2ban/jail.d/sshd.local"
+ [ -f "$cfg" ] || printf "[sshd]\n# bantime/findtime/maxretry\n" > "$cfg"
+
+ nano "$cfg"
+ echo -e "${gl_lv}saved${gl_bai}, reloading fail2ban..."
+ fail2ban-client reload >/dev/null 2>&1 || true
+}
@@ -3099,7 +3352,7 @@ ldnmp_install_status_one() {
if docker inspect "php" &>/dev/null; then
clear
send_stats "Unable to install LDNMP environment again"
- echo -e "${gl_huang}hint:${gl_bai}The website construction environment is installed. No need to install again!"
+ echo -e "${gl_huang}hint:${gl_bai}The website building environment has been installed. No need to install again!"
break_end
linux_ldnmp
fi
@@ -3112,9 +3365,8 @@ cd ~
send_stats "Install LDNMP environment"
root_use
clear
-echo -e "${gl_huang}The LDNMP environment is not installed, start installing the LDNMP environment...${gl_bai}"
-check_disk_space 3
-check_port
+echo -e "${gl_huang}The LDNMP environment is not installed. Start installing the LDNMP environment...${gl_bai}"
+check_disk_space 3 /home
install_dependency
install_docker
install_certbot
@@ -3130,8 +3382,6 @@ send_stats "Install nginx environment"
root_use
clear
echo -e "${gl_huang}nginx is not installed, start installing nginx environment...${gl_bai}"
-check_disk_space 1
-check_port
install_dependency
install_docker
install_certbot
@@ -3173,7 +3423,7 @@ nginx_install_status() {
ldnmp_web_on() {
clear
- echo "Yours$webnameBuilt!"
+ echo "your$webnameIt's built!"
echo "https://$yuming"
echo "------------------------"
echo "$webnameThe installation information is as follows:"
@@ -3181,10 +3431,21 @@ ldnmp_web_on() {
}
nginx_web_on() {
- clear
- echo "Yours$webnameBuilt!"
- echo "https://$yuming"
+ clear
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+ echo "your$webnameIt's built!"
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ mv /home/web/conf.d/"$yuming".conf /home/web/conf.d/"${yuming}_${access_port}".conf
+ echo "http://$yuming:$access_port"
+ elif grep -q '^[[:space:]]*#.*if (\$scheme = http)' "/home/web/conf.d/"$yuming".conf"; then
+ echo "http://$yuming"
+ else
+ echo "https://$yuming"
+ fi
}
@@ -3201,43 +3462,43 @@ ldnmp_wp() {
fi
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/wordpress.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
nginx_http_on
+
cd /home/web/html
mkdir $yuming
cd $yuming
wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/refs/heads/main/wp-latest.zip
- # wget -O latest.zip https://cn.wordpress.org/latest-zh_CN.zip
- # wget -O latest.zip https://wordpress.org/latest.zip
unzip latest.zip
rm latest.zip
- echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
+ echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379'); define('WP_REDIS_MAXTTL', 86400); define('WP_CACHE_KEY_SALT', '${yuming}_');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|database_name_here|$dbname|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|username_here|$dbuse|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|password_here|$dbusepasswd|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|localhost|mysql|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
+ patch_wp_url "https://$yuming" "https://$yuming"
cp /home/web/html/$yuming/wordpress/wp-config-sample.php /home/web/html/$yuming/wordpress/wp-config.php
+
restart_ldnmp
nginx_web_on
-# echo "Database name: $dbname"
-# echo "Username: $dbuse"
-# echo "Password: $dbusepasswd"
-# echo "Database address: mysql"
-# echo "Table prefix: wp_"
}
+
ldnmp_Proxy() {
clear
- webname="反向代理-IP+端口"
+ webname="Reverse proxy-IP+port"
yuming="${1:-}"
reverseproxy="${2:-}"
port="${3:-}"
@@ -3247,21 +3508,43 @@ ldnmp_Proxy() {
if [ -z "$yuming" ]; then
add_yuming
fi
+
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy" ]; then
- read -e -p "Please enter your anti-generation IP:" reverseproxy
+ read -e -p "Please enter your anti-generation IP (press Enter to default to the local IP 127.0.0.1):" reverseproxy
+ reverseproxy=${reverseproxy:-127.0.0.1}
fi
if [ -z "$port" ]; then
read -e -p "Please enter your anti-generation port:" port
fi
nginx_install_status
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
+ wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/backend_$backend/g" /home/web/conf.d/"$yuming".conf
+
+
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$reverseproxy/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$port|g" /home/web/conf.d/$yuming.conf
+
+ reverseproxy_port="$reverseproxy:$port"
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# dynamically add/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i '/remote_addr/d' /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3271,9 +3554,7 @@ ldnmp_Proxy() {
ldnmp_Proxy_backend() {
clear
- webname="反向代理-负载均衡"
- yuming="${1:-}"
- reverseproxy_port="${2:-}"
+ webname="Reverse proxy-load balancing"
send_stats "Install$webname"
echo "Start deployment$webname"
@@ -3281,14 +3562,17 @@ ldnmp_Proxy_backend() {
add_yuming
fi
- # Get multiple IPs entered by the user: ports (separated by spaces)
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy_port" ]; then
- read -e -p "Please enter your multiple anti-generation IP+ ports separated by spaces (for example, 127.0.0.1:3000 127.0.0.1:3002):" reverseproxy_port
+ read -e -p "Please enter your multiple anti-generation IP+ports separated by spaces (for example, 127.0.0.1:3000 127.0.0.1:3002):" reverseproxy_port
fi
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
@@ -3298,14 +3582,15 @@ ldnmp_Proxy_backend() {
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- # Dynamically generate upstream configuration
upstream_servers=""
for server in $reverseproxy_port; do
upstream_servers="$upstream_servers server $server;\n"
done
- # Replace placeholders in templates
- sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i "s/# dynamically add/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+
+
+ update_nginx_listen_port "$yuming" "$access_port"
nginx_http_on
docker exec nginx nginx -s reload
@@ -3314,6 +3599,200 @@ ldnmp_Proxy_backend() {
+
+
+
+list_stream_services() {
+
+ STREAM_DIR="/home/web/stream.d"
+ printf "%-25s %-18s %-25s %-20s\n" "Service name" "Communication type" "Local address" "Backend address"
+
+ if [ -z "$(ls -A "$STREAM_DIR")" ]; then
+ return
+ fi
+
+ for conf in "$STREAM_DIR"/*; do
+ # Service name takes file name
+ service_name=$(basename "$conf" .conf)
+
+ # Get the server backend IP:port in the upstream block
+ backend=$(grep -Po '(?<=server )[^;]+' "$conf" | head -n1)
+
+ # Get listen port
+ listen_port=$(grep -Po '(?<=listen )[^;]+' "$conf" | head -n1)
+
+ # Default local IP
+ ip_address
+ local_ip="$ipv4_address"
+
+ # Get the communication type, first judging from the file name suffix or content
+ if grep -qi 'udp;' "$conf"; then
+ proto="udp"
+ else
+ proto="tcp"
+ fi
+
+ # Splice listening IP:port
+ local_addr="$local_ip:$listen_port"
+
+ printf "%-22s %-14s %-21s %-20s\n" "$service_name" "$proto" "$local_addr" "$backend"
+ done
+}
+
+
+
+
+
+
+
+
+
+stream_panel() {
+ send_stats "Stream four-layer proxy"
+ local app_id="104"
+ local docker_name="nginx"
+
+ while true; do
+ clear
+ check_docker_app
+ check_docker_image_update $docker_name
+ echo -e "Stream four-layer proxy forwarding tool$check_docker $update_status"
+ echo "NGINX Stream is the TCP/UDP proxy module of NGINX, which is used to achieve high-performance transport layer traffic forwarding and load balancing."
+ echo "------------------------"
+ if [ -d "/home/web/stream.d" ]; then
+ list_stream_services
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. Install 2. Update 3. Uninstall"
+ echo "------------------------"
+ echo "4. Add forwarding service 5. Modify forwarding service 6. Delete forwarding service"
+ echo "------------------------"
+ echo "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Enter your selection:" choice
+ case $choice in
+ 1)
+ nginx_install_status
+ add_app_id
+ send_stats "Install Stream four-layer agent"
+ ;;
+ 2)
+ update_docker_compose_with_db_creds
+ nginx_upgrade
+ add_app_id
+ send_stats "Update Stream four-layer proxy"
+ ;;
+ 3)
+ read -e -p "Are you sure you want to delete the nginx container? This may affect website functionality! (y/N):" confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ docker rm -f nginx
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "Update Stream four-layer proxy"
+ echo "nginx container has been deleted."
+ else
+ echo "操作已取消。"
+ fi
+
+ ;;
+
+ 4)
+ ldnmp_Proxy_backend_stream
+ add_app_id
+ send_stats "Add layer 4 proxy"
+ ;;
+ 5)
+ send_stats "Edit forwarding configuration"
+ read -e -p "Please enter the service name you want to edit:" stream_name
+ install nano
+ nano /home/web/stream.d/$stream_name.conf
+ docker restart nginx
+ send_stats "Modify layer 4 proxy"
+ ;;
+ 6)
+ send_stats "Delete forwarding configuration"
+ read -e -p "Please enter the service name you want to delete:" stream_name
+ rm /home/web/stream.d/$stream_name.conf > /dev/null 2>&1
+ docker restart nginx
+ send_stats "Delete layer 4 proxy"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ break_end
+ done
+}
+
+
+
+ldnmp_Proxy_backend_stream() {
+ clear
+ webname="Stream four-layer proxy-load balancing"
+
+ send_stats "Install$webname"
+ echo "Start deployment$webname"
+
+ # Get agent name
+ read -erp "Please enter a proxy forwarding name (e.g. mysql_proxy):" proxy_name
+ if [ -z "$proxy_name" ]; then
+ echo "Name cannot be empty"; return 1
+ fi
+
+ # Get listening port
+ read -erp "Please enter the local listening port (such as 3306):" listen_port
+ if ! [[ "$listen_port" =~ ^[0-9]+$ ]]; then
+ echo "Port must be numeric"; return 1
+ fi
+
+ echo "Please select agreement type:"
+ echo "1. TCP 2. UDP"
+ read -erp "Please enter the serial number [1-2]:" proto_choice
+
+ case "$proto_choice" in
+ 1) proto="tcp"; listen_suffix="" ;;
+ 2) proto="udp"; listen_suffix=" udp" ;;
+ *) echo "Invalid selection"; return 1 ;;
+ esac
+
+ read -e -p "Please enter one or more of your backend IP+ports separated by spaces (for example, 10.13.0.2:3306 10.13.0.3:3306):" reverseproxy_port
+
+ nginx_install_status
+ cd /home && mkdir -p web/stream.d
+ grep -q '^[[:space:]]*stream[[:space:]]*{' /home/web/nginx.conf || echo -e '\nstream {\n include /etc/nginx/stream.d/*.conf;\n}' | tee -a /home/web/nginx.conf
+ wget -O /home/web/stream.d/$proxy_name.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend-stream.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/${proxy_name}_${backend}/g" /home/web/stream.d/"$proxy_name".conf
+ sed -i "s|listen 80|listen $listen_port $listen_suffix|g" /home/web/stream.d/$proxy_name.conf
+ sed -i "s|listen \[::\]:|listen [::]:${listen_port} ${listen_suffix}|g" "/home/web/stream.d/${proxy_name}.conf"
+
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# dynamically add/$upstream_servers/g" /home/web/stream.d/$proxy_name.conf
+
+ docker exec nginx nginx -s reload
+ clear
+ echo "your$webnameIt's built!"
+ echo "------------------------"
+ echo "Visit address:"
+ ip_address
+ if [ -n "$ipv4_address" ]; then
+ echo "$ipv4_address:${listen_port}"
+ fi
+ if [ -n "$ipv6_address" ]; then
+ echo "$ipv6_address:${listen_port}"
+ fi
+ echo ""
+}
+
+
+
+
+
find_container_by_host_port() {
port="$1"
docker_name=$(docker ps --format '{{.ID}} {{.Names}}' | while read id name; do
@@ -3331,11 +3810,11 @@ ldnmp_web_status() {
root_use
while true; do
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
- local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+ local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
- local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+ local db_output="${gl_lv}${db_count}${gl_bai}"
clear
send_stats "LDNMP site management"
@@ -3343,8 +3822,7 @@ ldnmp_web_status() {
echo "------------------------"
ldnmp_v
- # ls -t /home/web/conf.d | sed 's/\.[^.]*$//'
- echo -e "${output}Certificate expiration time"
+ echo -e "Site:${output}Certificate expiration time"
echo -e "------------------------"
for cert_file in /home/web/certs/*_cert.pem; do
local domain=$(basename "$cert_file" | sed 's/_cert.pem//')
@@ -3355,62 +3833,77 @@ ldnmp_web_status() {
fi
done
+ for conf_file in /home/web/conf.d/*_*.conf; do
+ [ -e "$conf_file" ] || continue
+ basename "$conf_file" .conf
+ done
+
+ for conf_file in /home/web/conf.d/*.conf; do
+ [ -e "$conf_file" ] || continue
+
+ filename=$(basename "$conf_file")
+
+ if [ "$filename" = "map.conf" ] || [ "$filename" = "default.conf" ]; then
+ continue
+ fi
+
+ if ! grep -q "ssl_certificate" "$conf_file"; then
+ basename "$conf_file" .conf
+ fi
+ done
+
echo "------------------------"
echo ""
- echo -e "${db_output}"
+ echo -e "database:${db_output}"
echo -e "------------------------"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys"
echo "------------------------"
echo ""
- echo "Site Directory"
+ echo "site directory"
echo "------------------------"
echo -e "data${gl_hui}/home/web/html${gl_bai}Certificate${gl_hui}/home/web/certs${gl_bai}Configuration${gl_hui}/home/web/conf.d${gl_bai}"
echo "------------------------"
echo ""
echo "operate"
echo "------------------------"
- echo "1. Apply for/update the domain name certificate 2. Change the site domain name"
- echo "3. Clean up the site cache 4. Create an associated site"
- echo "5. View access log 6. View error log"
+ echo "1. Apply/update domain name certificate 2. Clone site domain name"
+ echo "3. Clear site cache 4. Create associated site"
+ echo "5. View the access log 6. View the error log"
echo "7. Edit global configuration 8. Edit site configuration"
- echo "9. Manage site database 10. View site analysis report"
+ echo "9. Manage site database 10. View site analysis reports"
echo "------------------------"
- echo "20. Delete the specified site data"
+ echo "20. Delete specified site data"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
send_stats "Apply for a domain name certificate"
read -e -p "Please enter your domain name:" yuming
install_certbot
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
;;
2)
- send_stats "Change the site domain name"
- echo -e "${gl_hong}Highly recommended:${gl_bai}First back up the entire site data and then change the site domain name!"
+ send_stats "Clone site domain name"
read -e -p "Please enter the old domain name:" oddyuming
- read -e -p "Please enter the new domain name:" yuming
+ read -e -p "Please enter new domain name:" yuming
install_certbot
install_ssltls
certs_status
- # mysql replacement
- add_db
+ add_db
local odd_dbname=$(echo "$oddyuming" | sed -e 's/[^A-Za-z0-9]/_/g')
local odd_dbname="${odd_dbname}"
docker exec mysql mysqldump -u root -p"$dbrootpasswd" $odd_dbname | docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname
- docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE $odd_dbname;"
-
local tables=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -D $dbname -e "SHOW TABLES;" | awk '{ if (NR>1) print $1 }')
for table in $tables; do
@@ -3421,18 +3914,15 @@ ldnmp_web_status() {
done
# Website directory replacement
- mv /home/web/html/$oddyuming /home/web/html/$yuming
+ cp -r /home/web/html/$oddyuming /home/web/html/$yuming
find /home/web/html/$yuming -type f -exec sed -i "s/$odd_dbname/$dbname/g" {} +
find /home/web/html/$yuming -type f -exec sed -i "s/$oddyuming/$yuming/g" {} +
- mv /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
+ cp /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
sed -i "s/$oddyuming/$yuming/g" /home/web/conf.d/$yuming.conf
- rm /home/web/certs/${oddyuming}_key.pem
- rm /home/web/certs/${oddyuming}_cert.pem
-
- docker exec nginx nginx -s reload
+ cd /home/web && docker compose restart
;;
@@ -3441,10 +3931,10 @@ ldnmp_web_status() {
web_cache
;;
4)
- send_stats "Create an associated site"
- echo -e "Associate a new domain name for the existing site for access"
- read -e -p "Please enter the existing domain name:" oddyuming
- read -e -p "Please enter the new domain name:" yuming
+ send_stats "Create associated sites"
+ echo -e "Associate a new domain name with the existing site for access"
+ read -e -p "Please enter an existing domain name:" oddyuming
+ read -e -p "Please enter new domain name:" yuming
install_certbot
install_ssltls
certs_status
@@ -3476,7 +3966,7 @@ ldnmp_web_status() {
8)
send_stats "Edit site configuration"
- read -e -p "To edit the site configuration, please enter the domain name you want to edit:" yuming
+ read -e -p "To edit site configuration, please enter the domain name you want to edit:" yuming
install nano
nano /home/web/conf.d/$yuming.conf
docker exec nginx nginx -s reload
@@ -3493,7 +3983,7 @@ ldnmp_web_status() {
20)
web_del
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
;;
*)
@@ -3508,7 +3998,7 @@ ldnmp_web_status() {
check_panel_app() {
if $lujing > /dev/null 2>&1; then
- check_panel="${gl_lv}已安装${gl_bai}"
+ check_panel="${gl_lv}Installed${gl_bai}"
else
check_panel=""
fi
@@ -3522,31 +4012,37 @@ while true; do
clear
check_panel_app
echo -e "$panelname $check_panel"
- echo "${panelname}It is a popular and powerful operation and maintenance management panel nowadays."
+ echo "${panelname}It is a popular and powerful operation and maintenance management panel."
echo "Official website introduction:$panelurl "
echo ""
echo "------------------------"
- echo "1. Install 2. Management 3. Uninstall"
+ echo "1. Install 2. Manage 3. Uninstall"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case $choice in
1)
check_disk_space 1
install wget
iptables_open
panel_app_install
+
+ add_app_id
send_stats "${panelname}Install"
;;
2)
panel_app_manage
+
+ add_app_id
send_stats "${panelname}control"
;;
3)
panel_app_uninstall
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
send_stats "${panelname}uninstall"
;;
*)
@@ -3563,9 +4059,9 @@ done
check_frp_app() {
if [ -d "/home/frp/" ]; then
- check_frp="${gl_lv}已安装${gl_bai}"
+ check_frp="${gl_lv}Installed${gl_bai}"
else
- check_frp="${gl_hui}未安装${gl_bai}"
+ check_frp="${gl_hui}Not installed${gl_bai}"
fi
}
@@ -3591,7 +4087,7 @@ donlond_frp() {
generate_frps_config() {
- send_stats "Install the frp server"
+ send_stats "Install frp server"
# Generate random ports and credentials
local bind_port=8055
local dashboard_port=8056
@@ -3613,7 +4109,7 @@ EOF
donlond_frp frps
- # Output generated information
+ # Output the generated information
ip_address
echo "------------------------"
echo "Parameters required for client deployment"
@@ -3633,7 +4129,7 @@ EOF
configure_frpc() {
- send_stats "Install the frp client"
+ send_stats "Install frp client"
read -e -p "Please enter the external network docking IP:" server_addr
read -e -p "Please enter the external network docking token:" token
echo
@@ -3655,12 +4151,12 @@ EOF
}
add_forwarding_service() {
- send_stats "Add FRP intranet service"
- # Prompt the user to enter the service name and forwarding information
- read -e -p "Please enter the service name:" service_name
- read -e -p "Please enter the forwarding type (tcp/udp) [Enter default tcp]:" service_type
+ send_stats "Add frp intranet service"
+ # Prompts user for service name and forwarding information
+ read -e -p "Please enter service name:" service_name
+ read -e -p "Please enter the forwarding type (tcp/udp) [Enter to default to tcp]:" service_type
local service_type=${service_type:-tcp}
- read -e -p "Please enter the intranet IP [Enter default 127.0.0.1]:" local_ip
+ read -e -p "Please enter the intranet IP [default is 127.0.0.1 when pressing Enter]:" local_ip
local local_ip=${local_ip:-127.0.0.1}
read -e -p "Please enter the intranet port:" local_port
read -e -p "Please enter the external network port:" remote_port
@@ -3675,8 +4171,8 @@ remote_port = ${remote_port}
EOF
- # Output generated information
- echo "Serve$service_nameAdded successfully to frpc.toml"
+ # Output the generated information
+ echo "Serve$service_nameSuccessfully added to frpc.toml"
docker restart frpc
@@ -3687,12 +4183,12 @@ EOF
delete_forwarding_service() {
- send_stats "Delete the frp intranet service"
- # Prompt the user to enter the service name that needs to be deleted
- read -e -p "Please enter the service name that needs to be deleted:" service_name
- # Use sed to delete the service and its related configurations
+ send_stats "Delete frp intranet service"
+ # Prompt the user to enter the name of the service that needs to be deleted
+ read -e -p "Please enter the service name to be deleted:" service_name
+ # Use sed to delete the service and its related configuration
sed -i "/\[$service_name\]/,/^$/d" /home/frp/frpc.toml
- echo "Serve$service_nameDeleted successfully from frpc.toml"
+ echo "Serve$service_nameSuccessfully removed from frpc.toml"
docker restart frpc
@@ -3702,8 +4198,8 @@ delete_forwarding_service() {
list_forwarding_services() {
local config_file="$1"
- # Print the header
- printf "%-20s %-25s %-30s %-10s\n" "服务名称" "内网地址" "外网地址" "协议"
+ # Print header
+ printf "%-20s %-25s %-30s %-10s\n" "Service name" "Intranet address" "External network address" "protocol"
awk '
BEGIN {
@@ -3723,7 +4219,7 @@ list_forwarding_services() {
}
/^\[.*\]/ {
- # If there is service information, print the current service before processing the new service
+ # If service information already exists, print the current service before processing the new service
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3732,11 +4228,11 @@ list_forwarding_services() {
type
}
- # Update the current service name
+ # Update current service name
if ($1 != "[common]") {
gsub(/[\[\]]/, "", $1)
current_service=$1
- # Clear the previous value
+ # Clear previous value
local_ip=""
local_port=""
remote_port=""
@@ -3765,7 +4261,7 @@ list_forwarding_services() {
}
END {
- # Print the information for the last service
+ # Print information about the last service
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3778,17 +4274,17 @@ list_forwarding_services() {
-# Get the FRP server port
+# Get FRP server port
get_frp_ports() {
mapfile -t ports < <(ss -tulnape | grep frps | awk '{print $5}' | awk -F':' '{print $NF}' | sort -u)
}
# Generate access address
generate_access_urls() {
- # Get all ports first
+ # First get all ports
get_frp_ports
- # Check if there are ports other than 8055/8056
+ # Check if there is a port other than 8055/8056
local has_valid_ports=false
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3797,18 +4293,18 @@ generate_access_urls() {
fi
done
- # Show title and content only when there is a valid port
+ # Show title and content only if there is a valid port
if [ "$has_valid_ports" = true ]; then
echo "FRP service external access address:"
- # Process IPv4 address
+ # Handling IPv4 addresses
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
echo "http://${ipv4_address}:${port}"
fi
done
- # Process IPv6 addresses (if present)
+ # Handle IPv6 address if present
if [ -n "$ipv6_address" ]; then
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3817,7 +4313,7 @@ generate_access_urls() {
done
fi
- # Handling HTTPS configuration
+ # Handle HTTPS configuration
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
local frps_search_pattern="${ipv4_address}:${port}"
@@ -3845,6 +4341,7 @@ frps_main_ports() {
frps_panel() {
send_stats "FRP server"
+ local app_id="55"
local docker_name="frps"
local docker_port=8056
while true; do
@@ -3852,9 +4349,9 @@ frps_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRP server$check_frp $update_status"
- echo "Build an FRP intranet penetration service environment to expose devices without public IP to the Internet"
- echo "Official website introduction: https://github.com/fatedier/frp/"
- echo "Video teaching: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
+ echo "Build an FRP intranet penetration service environment and expose devices without public IP to the Internet"
+ echo "Official website introduction:${gh_https_url}github.com/fatedier/frp/"
+ echo "Video tutorial: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
if [ -d "/home/frp/" ]; then
check_docker_app_ip
frps_main_ports
@@ -3863,18 +4360,20 @@ frps_panel() {
echo "------------------------"
echo "1. Install 2. Update 3. Uninstall"
echo "------------------------"
- echo "5. Domain name access for intranet service 6. Delete domain name access"
+ echo "5. Intranet service domain name access 6. Delete domain name access"
echo "------------------------"
- echo "7. Allow IP+ port access 8. Block IP+ port access"
+ echo "7. Allow IP+port access 8. Block IP+port access"
echo "------------------------"
echo "00. Refresh service status 0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
install jq grep ss
install_docker
generate_frps_config
+
+ add_app_id
echo "The FRP server has been installed"
;;
2)
@@ -3883,6 +4382,8 @@ frps_panel() {
docker rm -f frps && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frps.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frps.toml /home/frp/frps.toml
donlond_frp frps
+
+ add_app_id
echo "The FRP server has been updated"
;;
3)
@@ -3893,36 +4394,37 @@ frps_panel() {
close_port 8055 8056
- echo "The app has been uninstalled"
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "App has been uninstalled"
;;
5)
echo "Reverse intranet penetration service into domain name access"
- send_stats "FRP access to external domain names"
+ send_stats "FRP external domain name access"
add_yuming
read -e -p "Please enter your intranet penetration service port:" frps_port
ldnmp_Proxy ${yuming} 127.0.0.1 ${frps_port}
block_host_port "$frps_port" "$ipv4_address"
;;
6)
- echo "Domain name format example.com does not come with https://"
+ echo "Domain name format example.com without https://"
web_del
;;
7)
send_stats "Allow IP access"
- read -e -p "Please enter the port to be released:" frps_port
+ read -e -p "Please enter the port that needs to be released:" frps_port
clear_host_port_rules "$frps_port" "$ipv4_address"
;;
8)
send_stats "Block IP access"
- echo "If you have accessed the anti-generation domain name, you can use this function to block IP+ port access, which is more secure."
- read -e -p "Please enter the port you need to block:" frps_port
+ echo "If you have reversed domain name access, you can use this function to block IP+port access, which is safer."
+ read -e -p "Please enter the port to be blocked:" frps_port
block_host_port "$frps_port" "$ipv4_address"
;;
00)
- send_stats "Refresh the FRP service status"
+ send_stats "Refresh FRP service status"
echo "FRP service status has been refreshed"
;;
@@ -3936,17 +4438,18 @@ frps_panel() {
frpc_panel() {
- send_stats "FRP Client"
+ send_stats "FRP client"
+ local app_id="56"
local docker_name="frpc"
local docker_port=8055
while true; do
clear
check_frp_app
check_docker_image_update $docker_name
- echo -e "FRP Client$check_frp $update_status"
- echo "Docking with the server, after docking, you can create intranet penetration service to the Internet access"
- echo "Official website introduction: https://github.com/fatedier/frp/"
- echo "Video teaching: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
+ echo -e "FRP client$check_frp $update_status"
+ echo "Connect with the server. After the connection, you can create an intranet penetration service to access the Internet."
+ echo "Official website introduction:${gh_https_url}github.com/fatedier/frp/"
+ echo "Video tutorial: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
echo "------------------------"
if [ -d "/home/frp/" ]; then
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
@@ -3956,16 +4459,18 @@ frpc_panel() {
echo "------------------------"
echo "1. Install 2. Update 3. Uninstall"
echo "------------------------"
- echo "4. Add external services 5. Delete external services 6. Configure services manually"
+ echo "4. Add external services 5. Delete external services 6. Manually configure services"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
install jq grep ss
install_docker
configure_frpc
+
+ add_app_id
echo "The FRP client has been installed"
;;
2)
@@ -3974,6 +4479,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
donlond_frp frpc
+
+ add_app_id
echo "The FRP client has been updated"
;;
@@ -3983,7 +4490,9 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine
rm -rf /home/frp
close_port 8055
- echo "The app has been uninstalled"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "App has been uninstalled"
;;
4)
@@ -4013,6 +4522,7 @@ frpc_panel() {
yt_menu_pro() {
+ local app_id="66"
local VIDEO_DIR="/home/yt-dlp"
local URL_FILE="$VIDEO_DIR/urls.txt"
local ARCHIVE_FILE="$VIDEO_DIR/archive.txt"
@@ -4022,16 +4532,16 @@ yt_menu_pro() {
while true; do
if [ -x "/usr/local/bin/yt-dlp" ]; then
- local YTDLP_STATUS="${gl_lv}已安装${gl_bai}"
+ local YTDLP_STATUS="${gl_lv}Installed${gl_bai}"
else
- local YTDLP_STATUS="${gl_hui}未安装${gl_bai}"
+ local YTDLP_STATUS="${gl_hui}Not installed${gl_bai}"
fi
clear
send_stats "yt-dlp download tool"
echo -e "yt-dlp $YTDLP_STATUS"
- echo -e "yt-dlp is a powerful video download tool that supports thousands of sites including YouTube, Bilibili, Twitter, etc."
- echo -e "Official website address: https://github.com/yt-dlp/yt-dlp"
+ echo -e "yt-dlp is a powerful video download tool that supports thousands of sites such as YouTube, Bilibili, Twitter, etc."
+ echo -e "Official website address:${gh_https_url}github.com/yt-dlp/yt-dlp"
echo "-------------------------"
echo "Downloaded video list:"
ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(None yet)"
@@ -4039,43 +4549,49 @@ yt_menu_pro() {
echo "1. Install 2. Update 3. Uninstall"
echo "-------------------------"
echo "5. Single video download 6. Batch video download 7. Custom parameter download"
- echo "8. Download as MP3 audio 9. Delete the video directory 10. Cookie management (under development)"
+ echo "8. Download as MP3 audio 9. Delete video directory 10. Cookie management (under development)"
echo "-------------------------"
echo "0. Return to the previous menu"
echo "-------------------------"
- read -e -p "Please enter the option number:" choice
+ read -e -p "Please enter option number:" choice
case $choice in
1)
send_stats "Installing yt-dlp..."
echo "Installing yt-dlp..."
install ffmpeg
- sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
- sudo chmod a+rx /usr/local/bin/yt-dlp
+ curl -L ${gh_https_url}github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ chmod a+rx /usr/local/bin/yt-dlp
+
+ add_app_id
echo "The installation is complete. Press any key to continue..."
read ;;
2)
- send_stats "Update yt-dlp..."
- echo "Update yt-dlp..."
- sudo yt-dlp -U
+ send_stats "Updating yt-dlp..."
+ echo "Updating yt-dlp..."
+ yt-dlp -U
+
+ add_app_id
echo "Update completed. Press any key to continue..."
read ;;
3)
send_stats "Uninstalling yt-dlp..."
echo "Uninstalling yt-dlp..."
- sudo rm -f /usr/local/bin/yt-dlp
- echo "Uninstall is complete. Press any key to continue..."
+ rm -f /usr/local/bin/yt-dlp
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "Uninstallation completed. Press any key to continue..."
read ;;
5)
send_stats "Single video download"
- read -e -p "Please enter the video link:" url
+ read -e -p "Please enter video link:" url
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "After the download is complete, press any key to continue..." ;;
+ read -e -p "Download completed, press any key to continue..." ;;
6)
send_stats "Batch video download"
install nano
@@ -4083,7 +4599,7 @@ yt_menu_pro() {
echo -e "# Enter multiple video link addresses\n# https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.episode.0" > "$URL_FILE"
fi
nano $URL_FILE
- echo "Now start batch download..."
+ echo "Start batch download now..."
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
@@ -4091,31 +4607,31 @@ yt_menu_pro() {
-a "$URL_FILE" \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "The batch download is completed, press any key to continue..." ;;
+ read -e -p "Batch download completed, press any key to continue..." ;;
7)
send_stats "Custom video download"
- read -e -p "Please enter the full yt-dlp parameter (excluding yt-dlp):" custom
+ read -e -p "Please enter the complete yt-dlp parameters (excluding yt-dlp):" custom
yt-dlp -P "$VIDEO_DIR" $custom \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "After the execution is completed, press any key to continue..." ;;
+ read -e -p "Execution completed, press any key to continue..." ;;
8)
send_stats "MP3 download"
- read -e -p "Please enter the video link:" url
+ read -e -p "Please enter video link:" url
yt-dlp -P "$VIDEO_DIR" -x --audio-format mp3 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "The audio download is completed, press any key to continue..." ;;
+ read -e -p "Audio download complete, press any key to continue..." ;;
9)
send_stats "Delete video"
- read -e -p "Please enter the name of the delete video:" rmdir
+ read -e -p "Please enter the name of the deleted video:" rmdir
rm -rf "$VIDEO_DIR/$rmdir"
;;
*)
@@ -4151,7 +4667,7 @@ set_timedate() {
-# Fix dpkg interrupt problem
+# Fix dpkg interruption problem
fix_dpkg() {
pkill -9 -f 'apt|dpkg'
rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock
@@ -4160,7 +4676,7 @@ fix_dpkg() {
linux_update() {
- echo -e "${gl_huang}System update...${gl_bai}"
+ echo -e "${gl_kjlan}System update in progress...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
elif command -v yum &>/dev/null; then
@@ -4187,7 +4703,7 @@ linux_update() {
linux_clean() {
- echo -e "${gl_huang}Cleaning up the system...${gl_bai}"
+ echo -e "${gl_kjlan}System cleaning in progress...${gl_bai}"
if command -v dnf &>/dev/null; then
rpm --rebuilddb
dnf autoremove -y
@@ -4216,9 +4732,9 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v apk &>/dev/null; then
- echo "Clean the package manager cache..."
+ echo "Clean package manager cache..."
apk cache clean
- echo "Delete the system log..."
+ echo "Delete system log..."
rm -rf /var/log/*
echo "Delete APK cache..."
rm -rf /var/cache/apk/*
@@ -4240,7 +4756,7 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v opkg &>/dev/null; then
- echo "Delete the system log..."
+ echo "Delete system log..."
rm -rf /var/log/*
echo "Delete temporary files..."
rm -rf /tmp/*
@@ -4248,9 +4764,9 @@ linux_clean() {
elif command -v pkg &>/dev/null; then
echo "Clean up unused dependencies..."
pkg autoremove -y
- echo "Clean the package manager cache..."
+ echo "Clean package manager cache..."
pkg clean -y
- echo "Delete the system log..."
+ echo "Delete system log..."
rm -rf /var/log/*
echo "Delete temporary files..."
rm -rf /tmp/*
@@ -4266,10 +4782,17 @@ linux_clean() {
bbr_on() {
-cat > /etc/sysctl.conf << EOF
-net.ipv4.tcp_congestion_control=bbr
-EOF
-sysctl -p
+# Unified writing to sysctl.d to prevent conflicts with the kernel tuning module
+local CONF="/etc/sysctl.d/99-kejilion-bbr.conf"
+mkdir -p /etc/sysctl.d
+echo "net.core.default_qdisc=fq" > "$CONF"
+echo "net.ipv4.tcp_congestion_control=bbr" >> "$CONF"
+
+# Clean up remnants of old sysctl.conf that may cause conflicts
+sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf 2>/dev/null
+
+sysctl -p "$CONF" >/dev/null 2>&1 || sysctl --system >/dev/null 2>&1
}
@@ -4278,8 +4801,8 @@ set_dns() {
ip_address
-rm /etc/resolv.conf
-touch /etc/resolv.conf
+chattr -i /etc/resolv.conf
+> /etc/resolv.conf
if [ -n "$ipv4_address" ]; then
echo "nameserver $dns1_ipv4" >> /etc/resolv.conf
@@ -4291,6 +4814,13 @@ if [ -n "$ipv6_address" ]; then
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
fi
+if [ ! -s /etc/resolv.conf ]; then
+ echo "nameserver 223.5.5.5" >> /etc/resolv.conf
+ echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+fi
+
+chattr +i /etc/resolv.conf
+
}
@@ -4315,7 +4845,7 @@ while true; do
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" Limiting
+ read -e -p "Please enter your choice:" Limiting
case "$Limiting" in
1)
local dns1_ipv4="1.1.1.1"
@@ -4335,7 +4865,9 @@ while true; do
;;
3)
install nano
+ chattr -i /etc/resolv.conf
nano /etc/resolv.conf
+ chattr +i /etc/resolv.conf
send_stats "Manually edit DNS configuration"
;;
*)
@@ -4359,39 +4891,32 @@ correct_ssh_config() {
local sshd_config="/etc/ssh/sshd_config"
- # If PasswordAuthentication is found, set to yes
- if grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
- # If found PubkeyAuthentication is set to yes
- if grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
+ if grep -Eq "^\s*PasswordAuthentication\s+no" "$sshd_config"; then
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
-e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' "$sshd_config"
+ else
+ sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin yes/' \
+ -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication yes/' \
+ -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' "$sshd_config"
fi
- # If neither PasswordAuthentication nor PubkeyAuthentication matches, set the default value
- if ! grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config" && ! grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
-
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
}
new_ssh_port() {
- # Backup SSH configuration files
+ local new_port=$1
+
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
open_port $new_port
@@ -4405,21 +4930,7 @@ new_ssh_port() {
-add_sshkey() {
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f /root/.ssh/sshkey -N ""
- cat ~/.ssh/sshkey.pub >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
-
- ip_address
- echo -e "The private key information has been generated. Be sure to copy and save it.${gl_huang}${ipv4_address}_ssh.key${gl_bai}File for future SSH login"
-
- echo "--------------------------------"
- cat ~/.ssh/sshkey
- echo "--------------------------------"
+sshkey_on() {
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
@@ -4427,67 +4938,341 @@ add_sshkey() {
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
- echo -e "${gl_lv}ROOT private key login is enabled, ROOT password login has been closed, reconnection will take effect${gl_bai}"
+ echo -e "${gl_lv}The user key login mode has been turned on and the password login mode has been turned off. Reconnection will take effect.${gl_bai}"
}
-import_sshkey() {
- read -e -p "Please enter your SSH public key contents (usually starting with 'ssh-rsa' or 'ssh-ed25519'):" public_key
+add_sshkey() {
+ chmod 700 "${HOME}"
+ mkdir -p "${HOME}/.ssh"
+ chmod 700 "${HOME}/.ssh"
+ touch "${HOME}/.ssh/authorized_keys"
- if [[ -z "$public_key" ]]; then
- echo -e "${gl_hong}Error: The public key content was not entered.${gl_bai}"
- return 1
- fi
+ ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f "${HOME}/.ssh/sshkey" -N ""
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- echo "$public_key" >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
+ cat "${HOME}/.ssh/sshkey.pub" >> "${HOME}/.ssh/authorized_keys"
+ chmod 600 "${HOME}/.ssh/authorized_keys"
- sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
- -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
- -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
- -e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
+ ip_address
+ echo -e "The private key information has been generated. Be sure to copy and save it. It can be saved as${gl_huang}${ipv4_address}_ssh.key${gl_bai}file for future SSH logins"
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
- restart_ssh
- echo -e "${gl_lv}The public key has been successfully imported, the ROOT private key login has been enabled, the ROOT password login has been closed, and the reconnection will take effect${gl_bai}"
+ echo "--------------------------------"
+ cat "${HOME}/.ssh/sshkey"
+ echo "--------------------------------"
+ sshkey_on
}
-add_sshpasswd() {
-echo "Set your ROOT password"
-passwd
-sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
-sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
-rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
-restart_ssh
-echo -e "${gl_lv}ROOT login is set up!${gl_bai}"
+import_sshkey() {
+
+ local public_key="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local auth_keys="${ssh_dir}/authorized_keys"
+
+ if [[ -z "$public_key" ]]; then
+ read -e -p "Please enter the contents of your SSH public key (usually starts with 'ssh-rsa' or 'ssh-ed25519'):" public_key
+ fi
+
+ if [[ -z "$public_key" ]]; then
+ echo -e "${gl_hong}Error: Public key content not entered.${gl_bai}"
+ return 1
+ fi
+
+ if [[ ! "$public_key" =~ ^ssh-(rsa|ed25519|ecdsa) ]]; then
+ echo -e "${gl_hong}Error: Does not look like a legitimate SSH public key.${gl_bai}"
+ return 1
+ fi
+
+ if grep -Fxq "$public_key" "$auth_keys" 2>/dev/null; then
+ echo "The public key already exists, no need to add it again"
+ return 0
+ fi
+
+ mkdir -p "$ssh_dir"
+ chmod 700 "$ssh_dir"
+ touch "$auth_keys"
+ echo "$public_key" >> "$auth_keys"
+ chmod 600 "$auth_keys"
+
+ sshkey_on
+}
+
+
+
+fetch_remote_ssh_keys() {
+
+ local keys_url="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local authorized_keys="${ssh_dir}/authorized_keys"
+ local temp_file
+
+ if [[ -z "${keys_url}" ]]; then
+ read -e -p "Please enter your remote public key URL:" keys_url
+ fi
+
+ echo "This script will pull the SSH public key from the remote URL and add it to${authorized_keys}"
+ echo ""
+ echo "Remote public key address:"
+ echo " ${keys_url}"
+ echo ""
+
+ # Create temporary files
+ temp_file=$(mktemp)
+
+ # Download public key
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 10 "${keys_url}" -o "${temp_file}" || {
+ echo "Error: Unable to download public key from URL (network issue or invalid address)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q --timeout=10 -O "${temp_file}" "${keys_url}" || {
+ echo "Error: Unable to download public key from URL (network issue or invalid address)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ else
+ echo "Error: curl or wget not found in system, unable to download public key" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ # Check if content is valid
+ if [[ ! -s "${temp_file}" ]]; then
+ echo "Error: The downloaded file is empty and the URL may not contain any public key" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ mkdir -p "${ssh_dir}"
+ chmod 700 "${ssh_dir}"
+ touch "${authorized_keys}"
+ chmod 600 "${authorized_keys}"
+
+ # Back up original authorized_keys
+ if [[ -f "${authorized_keys}" ]]; then
+ cp "${authorized_keys}" "${authorized_keys}.bak.$(date +%Y%m%d-%H%M%S)"
+ echo "The original authorized_keys file has been backed up"
+ fi
+
+ # Append public key (avoid duplication)
+ local added=0
+ while IFS= read -r line; do
+ [[ -z "${line}" || "${line}" =~ ^# ]] && continue
+
+ if ! grep -Fxq "${line}" "${authorized_keys}" 2>/dev/null; then
+ echo "${line}" >> "${authorized_keys}"
+ ((added++))
+ fi
+ done < "${temp_file}"
+
+ rm -f "${temp_file}"
+
+ echo ""
+ if (( added > 0 )); then
+ echo "successfully added${added}A new public key arrives${authorized_keys}"
+ sshkey_on
+ else
+ echo "No new public keys need to be added (all may already exist)"
+ fi
+
+ echo ""
+}
+
+
+
+
+fetch_github_ssh_keys() {
+
+ local username="$1"
+ local base_dir="${2:-$HOME}"
+
+ echo "Before proceeding, make sure you have added your SSH public key to your GitHub account:"
+ echo "1. Login${gh_https_url}github.com/settings/keys"
+ echo "2. Click New SSH key or Add SSH key"
+ echo "3. Title can be filled in as desired (for example: Home Laptop 2026)"
+ echo "4. Paste the contents of the local public key (usually the entire contents of ~/.ssh/id_ed25519.pub or id_rsa.pub) into the Key field"
+ echo "5. Click Add SSH key to complete the addition."
+ echo ""
+ echo "Once added, all your public keys will be publicly available on GitHub at:"
+ echo " ${gh_https_url}github.com/yourusername.keys"
+ echo ""
+
+
+ if [[ -z "${username}" ]]; then
+ read -e -p "Please enter your GitHub username (username without @):" username
+ fi
+
+ if [[ -z "${username}" ]]; then
+ echo "Error: GitHub username cannot be empty" >&2
+ return 1
+ fi
+
+ keys_url="${gh_https_url}github.com/${username}.keys"
+
+ fetch_remote_ssh_keys "${keys_url}" "${base_dir}"
+
+}
+
+
+sshkey_panel() {
+ root_use
+ send_stats "User key login"
+ while true; do
+ clear
+ local REAL_STATUS=$(grep -i "^PubkeyAuthentication" /etc/ssh/sshd_config | tr '[:upper:]' '[:lower:]')
+ if [[ "$REAL_STATUS" =~ "yes" ]]; then
+ IS_KEY_ENABLED="${gl_lv}Enabled${gl_bai}"
+ else
+ IS_KEY_ENABLED="${gl_hui}Not enabled${gl_bai}"
+ fi
+ echo -e "User key login mode${IS_KEY_ENABLED}"
+ echo "Advanced gameplay: https://blog.kejilion.pro/ssh-key"
+ echo "------------------------------------------------"
+ echo "A key pair will be generated, a more secure way to log in via SSH"
+ echo "------------------------"
+ echo "1. Generate a new key pair 2. Manually enter an existing public key"
+ echo "3. Import the existing public key from GitHub 4. Import the existing public key from the URL"
+ echo "5. Edit the public key file 6. View the local key"
+ echo "------------------------"
+ echo "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Please enter your choice:" host_dns
+ case $host_dns in
+ 1)
+ send_stats "Generate new key"
+ add_sshkey
+ break_end
+ ;;
+ 2)
+ send_stats "Import existing public key"
+ import_sshkey
+ break_end
+ ;;
+ 3)
+ send_stats "Import GitHub remote public key"
+ fetch_github_ssh_keys
+ break_end
+ ;;
+ 4)
+ send_stats "Import URL remote public key"
+ read -e -p "Please enter your remote public key URL:" keys_url
+ fetch_remote_ssh_keys "${keys_url}"
+ break_end
+ ;;
+
+ 5)
+ send_stats "Edit public key file"
+ install nano
+ nano ${HOME}/.ssh/authorized_keys
+ break_end
+ ;;
+
+ 6)
+ send_stats "View local key"
+ echo "------------------------"
+ echo "Public key information"
+ cat ${HOME}/.ssh/authorized_keys
+ echo "------------------------"
+ echo "Private key information"
+ cat ${HOME}/.ssh/sshkey
+ echo "------------------------"
+ break_end
+ ;;
+ *)
+ break # 跳出循环,退出菜单
+ ;;
+ esac
+ done
+
+
+}
+
+
+
+
+
+
+add_sshpasswd() {
+
+ root_use
+ send_stats "Set password login mode"
+ echo "Set password login mode"
+
+ local target_user="$1"
+
+ # If no parameters are passed in, enter interactively
+ if [[ -z "$target_user" ]]; then
+ read -e -p "Please enter the user name whose password you want to change (default root):" target_user
+ fi
+
+ # Press Enter and do not enter, the default is root
+ target_user=${target_user:-root}
+
+ # Verify that the user exists
+ if ! id "$target_user" >/dev/null 2>&1; then
+ echo "Error: user$target_userdoes not exist"
+ return 1
+ fi
+
+ passwd "$target_user"
+
+ if [[ "$target_user" == "root" ]]; then
+ sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
+ fi
+
+ sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
+
+ restart_ssh
+
+ echo -e "${gl_lv}The password is set and has been changed to password login mode!${gl_bai}"
+}
+
-}
-root_use() {
-clear
-[ "$EUID" -ne 0 ] && echo -e "${gl_huang}hint:${gl_bai}This feature requires root user to run!" && break_end && kejilion
-}
-dd_xitong() {
- send_stats "Reinstall the system"
- dd_xitong_MollyLau() {
- wget --no-check-certificate -qO InstallNET.sh "${gh_proxy}raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh" && chmod a+x InstallNET.sh
- }
+
+
+
+
+
+
+root_use() {
+clear
+[ "$EUID" -ne 0 ] && echo -e "${gl_huang}hint:${gl_bai}This function requires root user to run!" && break_end && kejilion
+}
+
+
+
+
+
+
+
+
+
+
+
+
+dd_xitong() {
+ send_stats "Reinstall the system"
+ dd_xitong_MollyLau() {
+ wget --no-check-certificate -qO InstallNET.sh "${gh_proxy}raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh" && chmod a+x InstallNET.sh
+
+ }
dd_xitong_bin456789() {
curl -O ${gh_proxy}raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh
@@ -4495,6 +5280,7 @@ dd_xitong() {
dd_xitong_1() {
echo -e "Initial username after reinstallation:${gl_huang}root${gl_bai}Initial password:${gl_huang}LeitboGi0ro${gl_bai}Initial port:${gl_huang}22${gl_bai}"
+ echo -e "${gl_huang}After reinstallation, please change the initial password in time to prevent violent intrusion. Enter passwd on the command line to change the password.${gl_bai}"
echo -e "Press any key to continue..."
read -n 1 -s -r -p ""
install wget
@@ -4527,19 +5313,21 @@ dd_xitong() {
root_use
echo "Reinstall the system"
echo "--------------------------------"
- echo -e "${gl_hong}Notice:${gl_bai}Reinstallation is risky to lose contact, and those who are worried should use it with caution. Reinstallation is expected to take 15 minutes, please back up the data in advance."
- echo -e "${gl_hui}Thanks to MollyLau and bin456789 for the script support!${gl_bai} "
+ echo -e "${gl_hong}Notice:${gl_bai}Reinstalling may cause loss of connection, so use with caution if you are worried. Reinstallation is expected to take 15 minutes, please back up your data in advance."
+ echo -e "${gl_hui}Thanks to boss bin456789 and boss leitbogioro for their script support!${gl_bai} "
+ echo -e "${gl_hui}bin456789 project address:${gh_https_url}github.com/bin456789/reinstall${gl_bai}"
+ echo -e "${gl_hui}leitbogioro project address:${gh_https_url}github.com/leitbogioro/Tools${gl_bai}"
echo "------------------------"
- echo "1. Debian 12 2. Debian 11"
- echo "3. Debian 10 4. Debian 9"
+ echo "1. Debian 13 2. Debian 12"
+ echo "3. Debian 11 4. Debian 10"
echo "------------------------"
- echo "11. Ubuntu 24.04 12. Ubuntu 22.04"
- echo "13. Ubuntu 20.04 14. Ubuntu 18.04"
+ echo "11. Ubuntu 26.04 12. Ubuntu 24.04"
+ echo "13. Ubuntu 22.04 14. Ubuntu 20.04"
echo "------------------------"
echo "21. Rocky Linux 10 22. Rocky Linux 9"
echo "23. Alma Linux 10 24. Alma Linux 9"
echo "25. oracle Linux 10 26. oracle Linux 9"
- echo "27. Fedora Linux 42 28. Fedora Linux 41"
+ echo "27. Fedora Linux 44 28. Fedora Linux 43"
echo "29. CentOS 10 30. CentOS 9"
echo "------------------------"
echo "31. Alpine Linux 32. Arch Linux"
@@ -4547,72 +5335,74 @@ dd_xitong() {
echo "35. openSUSE Tumbleweed 36. fnos Feiniu public beta version"
echo "------------------------"
echo "41. Windows 11 42. Windows 10"
- echo "43. Windows 7 44. Windows Server 2022"
- echo "45. Windows Server 2019 46. Windows Server 2016"
+ echo "43. Windows 7 44. Windows Server 2025"
+ echo "45. Windows Server 2022 46. Windows Server 2019"
echo "47. Windows 11 ARM"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please select the system to reinstall:" sys_choice
+ read -e -p "Please select the system you want to reinstall:" sys_choice
case "$sys_choice" in
+
+
1)
- send_stats "Reinstall debian 12"
- dd_xitong_1
- bash InstallNET.sh -debian 12
+ send_stats "Reinstall debian 13"
+ dd_xitong_3
+ bash reinstall.sh debian 13
reboot
exit
;;
+
2)
- send_stats "Reinstall debian 11"
- dd_xitong_1
- bash InstallNET.sh -debian 11
+ send_stats "Reinstall debian 12"
+ dd_xitong_3
+ bash reinstall.sh debian 12
reboot
exit
;;
3)
- send_stats "Reinstall debian 10"
- dd_xitong_1
- bash InstallNET.sh -debian 10
+ send_stats "Reinstall debian 11"
+ dd_xitong_3
+ bash reinstall.sh debian 11
reboot
exit
;;
4)
- send_stats "Reinstall debian 9"
- dd_xitong_1
- bash InstallNET.sh -debian 9
+ send_stats "Reinstall debian 10"
+ dd_xitong_3
+ bash reinstall.sh debian 10
reboot
exit
;;
11)
- send_stats "Reinstall ubuntu 24.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 24.04
+ send_stats "Reinstall ubuntu 26.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 26.04
reboot
exit
;;
12)
- send_stats "Reinstall ubuntu 22.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 22.04
+ send_stats "Reinstall ubuntu 24.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 24.04
reboot
exit
;;
13)
- send_stats "Reinstall ubuntu 20.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 20.04
+ send_stats "Reinstall ubuntu 22.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 22.04
reboot
exit
;;
14)
- send_stats "Reinstall ubuntu 18.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 18.04
+ send_stats "Reinstall ubuntu 20.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 20.04
reboot
exit
;;
-
21)
send_stats "Reinstall rockylinux10"
dd_xitong_3
@@ -4662,17 +5452,17 @@ dd_xitong() {
;;
27)
- send_stats "Reinstall fedora42"
+ send_stats "Reinstall fedora44"
dd_xitong_3
- bash reinstall.sh fedora
+ bash reinstall.sh fedora 44
reboot
exit
;;
28)
- send_stats "Reinstall fedora41"
+ send_stats "Reinstall fedora43"
dd_xitong_3
- bash reinstall.sh fedora 41
+ bash reinstall.sh fedora 43
reboot
exit
;;
@@ -4734,21 +5524,21 @@ dd_xitong() {
;;
36)
- send_stats "Reload flying cow"
+ send_stats "Reinstall Feiniu"
dd_xitong_3
bash reinstall.sh fnos
reboot
exit
;;
-
41)
- send_stats "Reinstall windows11"
+ send_stats "Reinstall Windows 11"
dd_xitong_2
bash InstallNET.sh -windows 11 -lang "cn"
reboot
exit
;;
+
42)
dd_xitong_2
send_stats "Reinstall Windows 10"
@@ -4756,32 +5546,35 @@ dd_xitong() {
reboot
exit
;;
+
43)
- send_stats "Reinstall Windows 7"
+ send_stats "Reinstall windows7"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://archive.org/download/en_windows_7_professional_with_sp1_x64_dvd_u_676939_201906/en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso" --image-name='windows 7 professional'
reboot
exit
;;
44)
- send_stats "Reinstall windows server 22"
+ send_stats "Reinstall windows server 25"
dd_xitong_2
- bash InstallNET.sh -windows 2022 -lang "cn"
+ bash InstallNET.sh -windows 2025 -lang "cn"
reboot
exit
;;
+
45)
- send_stats "Reinstall windows server 19"
+ send_stats "Reinstall windows server 22"
dd_xitong_2
- bash InstallNET.sh -windows 2019 -lang "cn"
+ bash InstallNET.sh -windows 2022 -lang "cn"
reboot
exit
;;
+
46)
- send_stats "Reinstall windows server 16"
+ send_stats "Reinstall windows server 19"
dd_xitong_2
- bash InstallNET.sh -windows 2016 -lang "cn"
+ bash InstallNET.sh -windows 2019 -lang "cn"
reboot
exit
;;
@@ -4806,6 +5599,146 @@ bbrv3() {
root_use
send_stats "bbrv3 management"
+ xanmod_add_repo() {
+ local keyring="/usr/share/keyrings/xanmod-archive-keyring.gpg"
+ local list_file="/etc/apt/sources.list.d/xanmod-release.list"
+ local key_url="https://dl.xanmod.org/archive.key"
+ local fallback_key_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key"
+ local os_codename=""
+
+ if command -v lsb_release >/dev/null 2>&1; then
+ os_codename=$(lsb_release -sc)
+ elif [ -r /etc/os-release ]; then
+ os_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
+ fi
+
+ # Compatible with the old system codenames that have been officially removed (fall back to using releases to try the old package library)
+ if ! echo "bookworm trixie forky sid noble plucky questing resolute faye gigi wilma xia zara zena" | grep -qw "$os_codename"; then
+ os_codename="releases"
+ fi
+
+ # Officially, apt support for jammy, focal, bullseye and other old systems has been completely removed.
+ if echo "jammy focal bullseye buster" | grep -qw "$os_codename" || [ "$os_codename" = "releases" ]; then
+ echo -e "${gl_hong}XanMod has officially stopped supporting the current system ($os_codename), please upgrade to Debian12/Ubuntu24 or higher.${gl_bai}"
+ return 1
+ fi
+
+ if [ -z "$os_codename" ]; then
+ echo "Unable to obtain system codename, unable to configure XanMod source"
+ return 1
+ fi
+
+ install wget gnupg ca-certificates
+ mkdir -p /usr/share/keyrings /etc/apt/sources.list.d
+ if ! wget -qO - "$key_url" | gpg --dearmor -o "$keyring" --yes; then
+ echo "Official key download failed, try alternate download source..."
+ wget -qO - "$fallback_key_url" | gpg --dearmor -o "$keyring" --yes || return 1
+ fi
+ chmod 644 "$keyring"
+ echo "deb [signed-by=$keyring] http://deb.xanmod.org $os_codename main" > "$list_file"
+ }
+
+ xanmod_detect_psabi_level() {
+ local psabi_output=""
+ psabi_output=$(awk 'BEGIN {
+ while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
+ if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
+ if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
+ if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
+ if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
+ if (level > 0) { print level; exit }
+ exit 1
+ }' /proc/cpuinfo 2>/dev/null) || return 1
+ printf '%s' "$psabi_output" | tr -dc '0-9' | head -c 1
+ }
+
+ xanmod_package_available() {
+ local package="$1"
+ apt-cache policy "$package" 2>/dev/null | grep -q 'Candidate: [^ ]'
+ }
+
+ xanmod_detect_package() {
+ local psabi_level=""
+ local level=""
+ local package=""
+ local prefix_list="linux-xanmod linux-xanmod-lts"
+
+ psabi_level=$(xanmod_detect_psabi_level) || return 1
+ [ -n "$psabi_level" ] || return 1
+ [ "$psabi_level" -gt 3 ] && psabi_level=3
+
+ apt update -y >/dev/null 2>&1
+
+ for prefix in $prefix_list; do
+ level="$psabi_level"
+ while [ "$level" -ge 1 ]; do
+ package="${prefix}-x64v${level}"
+ if xanmod_package_available "$package"; then
+ if [ "$level" != "$psabi_level" ] || [ "$prefix" = "linux-xanmod-lts" ]; then
+ echo "The appropriate installation package has been automatically matched:$package" >&2
+ fi
+ printf '%s\n' "$package"
+ return 0
+ fi
+ level=$((level - 1))
+ done
+ done
+
+ echo "The XanMod kernel package adapted to this CPU was not found in the software source." >&2
+ return 1
+ }
+
+ xanmod_installed() {
+ dpkg-query -W -f='${Package}\n' 'linux-*xanmod*' 2>/dev/null | grep -q '^linux-.*xanmod'
+ }
+
+ xanmod_install_or_update() {
+ local action="$1"
+ local package=""
+
+ check_disk_space 3
+ check_swap
+ xanmod_add_repo || {
+ echo "XanMod official warehouse configuration failed, please try again later."
+ return 1
+ }
+
+ package=$(xanmod_detect_package) || {
+ echo "The current CPU cannot be recognized or a matching kernel package cannot be found. The installation has been cancelled."
+ return 1
+ }
+
+ apt update -y
+ if [ "$action" = "update" ]; then
+ apt install -y --only-upgrade "$package" || apt install -y "$package" || {
+ echo "XanMod kernel update failed, please check the software source or try again later"
+ return 1
+ }
+ else
+ apt install -y "$package" || {
+ echo "XanMod kernel installation failed, please check the software source or try again later"
+ return 1
+ }
+ fi
+
+ bbr_on || {
+ echo "BBR3 parameter writing failed, please check the system configuration"
+ return 1
+ }
+ echo "XanMod BBRv3 kernel processing completed. Take effect after restart"
+ server_reboot
+ }
+
+ xanmod_uninstall() {
+ apt purge -y 'linux-*xanmod*'
+ apt autoremove -y
+ update-grub 2>/dev/null || true
+ rm -f /etc/apt/sources.list.d/xanmod-release.list
+ rm -f /usr/share/keyrings/xanmod-archive-keyring.gpg
+ echo "The XanMod kernel has been uninstalled. Take effect after restart"
+ server_reboot
+ }
+
local cpu_arch=$(uname -m)
if [ "$cpu_arch" = "aarch64" ]; then
bash <(curl -sL jhb.ovh/jb/bbrv3arm.sh)
@@ -4813,56 +5746,45 @@ bbrv3() {
linux_Settings
fi
- if dpkg -l | grep -q 'linux-xanmod'; then
+ if [ -r /etc/os-release ]; then
+ . /etc/os-release
+ if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
+ echo "The current environment does not support it. Only Debian and Ubuntu systems are supported."
+ break_end
+ linux_Settings
+ fi
+ else
+ echo "Unable to determine operating system type"
+ break_end
+ linux_Settings
+ fi
+
+ if xanmod_installed; then
while true; do
clear
local kernel_version=$(uname -r)
- echo "You have installed xanmod's BBRv3 kernel"
+ echo "You have xanmod's BBRv3 kernel installed"
echo "Current kernel version:$kernel_version"
echo ""
- echo "Kernel Management"
+ echo "Kernel management"
echo "------------------------"
- echo "1. Update the BBRv3 kernel 2. Uninstall the BBRv3 kernel"
+ echo "1. Update BBRv3 kernel 2. Uninstall BBRv3 kernel"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- apt purge -y 'linux-*xanmod1*'
- update-grub
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # Step 3: Add a repository
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- echo "The XanMod kernel has been updated. Take effect after restart"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
-
- server_reboot
-
- ;;
+ xanmod_install_or_update update
+ ;;
2)
- apt purge -y 'linux-*xanmod1*'
- update-grub
- echo "The XanMod kernel is uninstalled. Take effect after restart"
- server_reboot
- ;;
-
+ xanmod_uninstall
+ ;;
*)
- break # 跳出循环,退出菜单
- ;;
+ break
+ ;;
esac
done
@@ -4872,50 +5794,14 @@ bbrv3() {
echo "Set up BBR3 acceleration"
echo "Video introduction: https://www.bilibili.com/video/BV14K421x7BS?t=0.1"
echo "------------------------------------------------"
- echo "Only support Debian/Ubuntu"
- echo "Please back up the data and will enable BBR3 for you to upgrade the Linux kernel."
- echo "VPS has 512M memory, please add 1G virtual memory in advance to prevent missing contact due to insufficient memory!"
+ echo "Only supports Debian/Ubuntu"
+ echo "Please back up your data and we will upgrade your Linux kernel and enable BBR3."
echo "------------------------------------------------"
- read -e -p "Are you sure to continue? (Y/N):" choice
+ read -e -p "Are you sure you want to continue? (Y/N):" choice
case "$choice" in
[Yy])
- check_disk_space 3
- if [ -r /etc/os-release ]; then
- . /etc/os-release
- if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
- echo "The current environment does not support it, only supports Debian and Ubuntu systems"
- break_end
- linux_Settings
- fi
- else
- echo "Unable to determine the operating system type"
- break_end
- linux_Settings
- fi
-
- check_swap
- install wget gnupg
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # Step 3: Add a repository
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- bbr_on
-
- echo "XanMod kernel is installed and BBR3 is enabled successfully. Take effect after restart"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
- server_reboot
-
+ xanmod_install_or_update install
;;
[Nn])
echo "Canceled"
@@ -4928,31 +5814,30 @@ bbrv3() {
}
-
elrepo_install() {
- # Import ELRepo GPG public key
+ # Import the ELRepo GPG public key
echo "Import the ELRepo GPG public key..."
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
- # Detect system version
+ # Check system version
local os_version=$(rpm -q --qf "%{VERSION}" $(rpm -qf /etc/os-release) 2>/dev/null | awk -F '.' '{print $1}')
local os_name=$(awk -F= '/^NAME/{print $2}' /etc/os-release)
- # Make sure we run on a supported operating system
+ # Make sure we're running on a supported operating system
if [[ "$os_name" != *"Red Hat"* && "$os_name" != *"AlmaLinux"* && "$os_name" != *"Rocky"* && "$os_name" != *"Oracle"* && "$os_name" != *"CentOS"* ]]; then
echo "Unsupported operating systems:$os_name"
break_end
linux_Settings
fi
# Print detected operating system information
- echo "Operating system detected:$os_name $os_version"
- # 根据系统版本安装对应的 ELRepo 仓库配置
+ echo "Detected operating systems:$os_name $os_version"
+ # Install the corresponding ELRepo warehouse configuration according to the system version
if [[ "$os_version" == 8 ]]; then
- echo "Install ELRepo repository configuration (version 8)..."
+ echo "Installing the ELRepo repository configuration (version 8)..."
yum -y install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm
elif [[ "$os_version" == 9 ]]; then
- echo "Install ELRepo repository configuration (version 9)..."
+ echo "Installing the ELRepo repository configuration (version 9)..."
yum -y install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm
elif [[ "$os_version" == 10 ]]; then
- echo "Install ELRepo repository configuration (version 10)..."
+ echo "Installing the ELRepo repository configuration (version 10)..."
yum -y install https://www.elrepo.org/elrepo-release-10.el10.elrepo.noarch.rpm
else
echo "Unsupported system versions:$os_version"
@@ -4963,7 +5848,7 @@ elrepo_install() {
echo "Enable the ELRepo kernel repository and install the latest mainline kernel..."
# yum -y --enablerepo=elrepo-kernel install kernel-ml
yum --nogpgcheck -y --enablerepo=elrepo-kernel install kernel-ml
- echo "The ELRepo repository configuration is installed and updated to the latest mainline kernel."
+ echo "Installed ELRepo repository configuration and updated to latest mainline kernel."
server_reboot
}
@@ -4976,32 +5861,32 @@ elrepo() {
while true; do
clear
kernel_version=$(uname -r)
- echo "You have installed the elrepo kernel"
+ echo "You have installed elrepo kernel"
echo "Current kernel version:$kernel_version"
echo ""
- echo "Kernel Management"
+ echo "Kernel management"
echo "------------------------"
- echo "1. Update the elrepo kernel 2. Uninstall the elrepo kernel"
+ echo "1. Update elrepo kernel 2. Uninstall elrepo kernel"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
elrepo_install
- send_stats "Update the Red Hat kernel"
+ send_stats "Update Red Hat Kernel"
server_reboot
;;
2)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
- echo "The elrepo kernel is uninstalled. Take effect after restart"
- send_stats "Uninstall the Red Hat kernel"
+ echo "The elrepo kernel has been uninstalled. Take effect after restart"
+ send_stats "Uninstall Red Hat Kernel"
server_reboot
;;
@@ -5014,19 +5899,19 @@ elrepo() {
else
clear
- echo "Please back up the data and will upgrade the Linux kernel for you"
+ echo "Please back up your data and we will upgrade the Linux kernel for you."
echo "Video introduction: https://www.bilibili.com/video/BV1mH4y1w7qA?t=529.2"
echo "------------------------------------------------"
- echo "Only support Red Hat series distributions CentOS/RedHat/Alma/Rocky/oracle"
- echo "Upgrading the Linux kernel can improve system performance and security. It is recommended to try it if conditions permit and upgrade the production environment with caution!"
+ echo "Only supports Red Hat series distributions CentOS/RedHat/Alma/Rocky/oracle"
+ echo "Upgrading the Linux kernel can improve system performance and security. It is recommended to try it if possible, and upgrade the production environment with caution!"
echo "------------------------------------------------"
- read -e -p "Are you sure to continue? (Y/N):" choice
+ read -e -p "Are you sure you want to continue? (Y/N):" choice
case "$choice" in
[Yy])
check_swap
elrepo_install
- send_stats "Upgrade the Red Hat kernel"
+ send_stats "Upgrade Red Hat kernel"
server_reboot
;;
[Nn])
@@ -5044,7 +5929,7 @@ elrepo() {
clamav_freshclam() {
- echo -e "${gl_huang}Update the virus database...${gl_bai}"
+ echo -e "${gl_kjlan}Updating virus database...${gl_bai}"
docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
@@ -5054,11 +5939,11 @@ clamav_freshclam() {
clamav_scan() {
if [ $# -eq 0 ]; then
- echo "Please specify the directory to scan."
+ echo "Please specify the directories to scan."
return
fi
- echo -e "${gl_huang}Scanning directory $@...${gl_bai}"
+ echo -e "${gl_kjlan}Scanning directory $@...${gl_bai}"
# Build mount parameters
local MOUNT_PARAMS=""
@@ -5075,8 +5960,8 @@ clamav_scan() {
mkdir -p /home/docker/clamav/log/ > /dev/null 2>&1
> /home/docker/clamav/log/scan.log > /dev/null 2>&1
- # Execute Docker commands
- docker run -it --rm \
+ # Execute Docker command
+ docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
$MOUNT_PARAMS \
@@ -5084,8 +5969,8 @@ clamav_scan() {
clamav/clamav-debian:latest \
clamscan -r --log=/var/log/clamav/scan.log $SCAN_PARAMS
- echo -e "${gl_lv}$@ Scan is completed, virus report is stored${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
- echo -e "${gl_lv}If there is a virus, please${gl_huang}scan.log${gl_lv}Search for FOUND keyword in the file to confirm the location of the virus${gl_bai}"
+ echo -e "${gl_lv}$@ 扫描完成,病毒报告存放在${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
+ echo -e "${gl_lv}If there is a virus please${gl_huang}scan.log${gl_lv}Search the file for the FOUND keyword to confirm the location of the virus${gl_bai}"
}
@@ -5097,23 +5982,23 @@ clamav_scan() {
clamav() {
root_use
- send_stats "Virus Scan Management"
+ send_stats "Virus scan management"
while true; do
clear
echo "clamav virus scanning tool"
echo "Video introduction: https://www.bilibili.com/video/BV1TqvZe4EQm?t=0.1"
echo "------------------------"
- echo "It is an open source antivirus software tool, mainly used to detect and remove various types of malware."
- echo "Including viruses, Trojan horses, spyware, malicious scripts and other harmful software."
+ echo "It is an open source antivirus software tool mainly used to detect and remove various types of malware."
+ echo "Includes viruses, Trojan horses, spyware, malicious scripts and other harmful software."
echo "------------------------"
- echo -e "${gl_lv}1. Full disk scan${gl_bai} ${gl_huang}2. Scan the important directory${gl_bai} ${gl_kjlan}3. Custom directory scanning${gl_bai}"
+ echo -e "${gl_lv}1. Full scan${gl_bai} ${gl_huang}2. Scan important directories${gl_bai} ${gl_kjlan}3. Custom directory scanning${gl_bai}"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- send_stats "Full disk scan"
+ send_stats "Full scan"
install_docker
docker volume create clam_db > /dev/null 2>&1
clamav_freshclam
@@ -5130,8 +6015,8 @@ clamav() {
break_end
;;
3)
- send_stats "Custom directory scanning"
- read -e -p "Please enter the directory to scan, separated by spaces (for example: /etc /var /usr /home /root):" directories
+ send_stats "Custom directory scan"
+ read -e -p "Please enter the directories to scan, separated by spaces (for example: /etc /var /usr /home /root):" directories
install_docker
clamav_freshclam
clamav_scan $directories
@@ -5146,171 +6031,348 @@ clamav() {
}
+# ============================================================================
+# Linux kernel tuning module (refactored version)
+# Unified core functions + scene differentiation parameters + persistence to configuration files + hardware adaptation
+# Replace the original optimize_high_performance / optimize_balanced / optimize_web_server / restore_defaults
+# ============================================================================
-
-# High-performance mode optimization function
-optimize_high_performance() {
- echo -e "${gl_lv}Switch to${tiaoyou_moshi}...${gl_bai}"
-
- echo -e "${gl_lv}Optimize file descriptors...${gl_bai}"
- ulimit -n 65535
-
- echo -e "${gl_lv}Optimize virtual memory...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=15 2>/dev/null
- sysctl -w vm.dirty_background_ratio=5 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
-
- echo -e "${gl_lv}Optimize network settings...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=250000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
-
- echo -e "${gl_lv}Optimize cache management...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
-
- echo -e "${gl_lv}Optimize CPU settings...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
-
- echo -e "${gl_lv}Other optimizations...${gl_bai}"
- # Disable large transparent pages to reduce latency
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # Disable NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
-
-
+# Get memory size (MB)
+_get_mem_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
}
-# Equalization mode optimization function
-optimize_balanced() {
- echo -e "${gl_lv}Switch to equalization mode...${gl_bai}"
-
- echo -e "${gl_lv}Optimize file descriptors...${gl_bai}"
- ulimit -n 32768
-
- echo -e "${gl_lv}Optimize virtual memory...${gl_bai}"
- sysctl -w vm.swappiness=30 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=32768 2>/dev/null
-
- echo -e "${gl_lv}Optimize network settings...${gl_bai}"
- sysctl -w net.core.rmem_max=8388608 2>/dev/null
- sysctl -w net.core.wmem_max=8388608 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=125000 2>/dev/null
- sysctl -w net.core.somaxconn=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 32768 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 49151' 2>/dev/null
-
- echo -e "${gl_lv}Optimize cache management...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=75 2>/dev/null
+# Unified kernel tuning core functions
+# Parameters: $1 = mode name, $2 = scene (high/balanced/web/stream/game)
+_kernel_optimize_core() {
+ local mode_name="$1"
+ local scene="${2:-high}"
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
+ local MEM_MB=$(_get_mem_mb)
+
+ echo -e "${gl_lv}switch to${mode_name}...${gl_bai}"
+
+ # ──Set parameters according to the scene──
+ local SWAPPINESS DIRTY_RATIO DIRTY_BG_RATIO OVERCOMMIT MIN_FREE_KB VFS_PRESSURE
+ local RMEM_MAX WMEM_MAX TCP_RMEM TCP_WMEM
+ local SOMAXCONN BACKLOG SYN_BACKLOG
+ local PORT_RANGE SCHED_AUTOGROUP THP NUMA FIN_TIMEOUT
+ local KEEPALIVE_TIME KEEPALIVE_INTVL KEEPALIVE_PROBES
+
+ case "$scene" in
+ high|stream|game)
+ # High Performance/Live Broadcasting/Gaming: Radical Parameters
+ SWAPPINESS=10
+ DIRTY_RATIO=15
+ DIRTY_BG_RATIO=5
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=67108864
+ WMEM_MAX=67108864
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ SOMAXCONN=8192
+ BACKLOG=250000
+ SYN_BACKLOG=8192
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=10
+ KEEPALIVE_TIME=300
+ KEEPALIVE_INTVL=30
+ KEEPALIVE_PROBES=5
+ ;;
+ web)
+ # Website server: high concurrency priority
+ SWAPPINESS=10
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=33554432
+ WMEM_MAX=33554432
+ TCP_RMEM="4096 131072 33554432"
+ TCP_WMEM="4096 131072 33554432"
+ SOMAXCONN=16384
+ BACKLOG=10000
+ SYN_BACKLOG=16384
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=15
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ balanced)
+ # Balanced Mode: Moderate Optimization
+ SWAPPINESS=30
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=0
+ VFS_PRESSURE=75
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ SOMAXCONN=4096
+ BACKLOG=5000
+ SYN_BACKLOG=4096
+ PORT_RANGE="1024 49151"
+ SCHED_AUTOGROUP=1
+ THP="always"
+ NUMA=1
+ FIN_TIMEOUT=30
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ esac
- echo -e "${gl_lv}Optimize CPU settings...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
+ # ── Adaptively adjust according to memory size ──
+ if [ "$MEM_MB" -ge 16384 ]; then
+ MIN_FREE_KB=131072
+ [ "$scene" != "balanced" ] && SWAPPINESS=5
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ MIN_FREE_KB=32768
+ # Small memory shrink buffer
+ if [ "$scene" != "balanced" ]; then
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ fi
+ else
+ MIN_FREE_KB=16384
+ SWAPPINESS=30
+ OVERCOMMIT=0
+ RMEM_MAX=4194304
+ WMEM_MAX=4194304
+ TCP_RMEM="4096 32768 4194304"
+ TCP_WMEM="4096 32768 4194304"
+ SOMAXCONN=1024
+ BACKLOG=1000
+ fi
- echo -e "${gl_lv}Other optimizations...${gl_bai}"
- # Restore transparent page
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # Restore NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+ # ── Additional live broadcast scenarios: UDP buffer enlarged ──
+ local STREAM_EXTRA=""
+ if [ "$scene" = "stream" ]; then
+ STREAM_EXTRA="
+# Live streaming UDP optimization
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
+ # ── Game server scene extra: low latency priority ──
+ local GAME_EXTRA=""
+ if [ "$scene" = "game" ]; then
+ GAME_EXTRA="
+# Game server low latency optimization
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384
+net.ipv4.tcp_slow_start_after_idle = 0"
+ fi
-}
+ # ── Load BBR module ──
+ local CC="bbr"
+ local QDISC="fq"
+ local KVER
+ KVER=$(uname -r | grep -oP '^\d+\.\d+')
+ if printf '%s\n%s' "4.9" "$KVER" | sort -V -C; then
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ fi
+ if ! sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
+ else
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
-# Restore the default settings function
-restore_defaults() {
- echo -e "${gl_lv}Restore to default settings...${gl_bai}"
+ # ── Back up existing configuration ──
+ [ -f "$CONF" ] && cp "$CONF" "${CONF}.bak.$(date +%s)"
+
+ # ── Write configuration file (persistence) ──
+ echo -e "${gl_lv}Write optimization configuration...${gl_bai}"
+ cat > "$CONF" << SYSCTL
+# kejilion kernel tuning configuration
+# Mode: $mode_name | Scene: $scene
+# Memory: ${MEM_MB}MB | Generation time: $(date '+%Y-%m-%d %H:%M:%S')
+
+# ──TCP Congestion Control──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $CC
+
+# ── TCP buffer ──
+net.core.rmem_max = $RMEM_MAX
+net.core.wmem_max = $WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+
+# ── Connection queue ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $BACKLOG
+net.ipv4.tcp_max_syn_backlog = $SYN_BACKLOG
+
+# ── TCP connection optimization ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = $FIN_TIMEOUT
+net.ipv4.tcp_keepalive_time = $KEEPALIVE_TIME
+net.ipv4.tcp_keepalive_intvl = $KEEPALIVE_INTVL
+net.ipv4.tcp_keepalive_probes = $KEEPALIVE_PROBES
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── Ports and Memory ──
+net.ipv4.ip_local_port_range = $PORT_RANGE
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── Virtual memory ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = $DIRTY_RATIO
+vm.dirty_background_ratio = $DIRTY_BG_RATIO
+vm.overcommit_memory = $OVERCOMMIT
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = $VFS_PRESSURE
+
+# ──CPU/kernel scheduling──
+kernel.sched_autogroup_enabled = $SCHED_AUTOGROUP
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = $NUMA" || echo "# numa_balancing not supported")
+
+# ──Safety protection──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── File descriptor ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── Connection tracking ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+echo "net.netfilter.nf_conntrack_max = $((SOMAXCONN * 32))"
+echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+echo "# conntrack is not enabled"
+fi)
+$STREAM_EXTRA
+$GAME_EXTRA
+SYSCTL
+
+ # ── Apply configuration (line by line, skip unsupported parameters) ──
+ echo -e "${gl_lv}Apply optimization parameters...${gl_bai}"
+ local applied=0 skipped=0
+ while IFS= read -r line; do
+ # Skip comments and blank lines
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
+ [[ -z "${line// /}" ]] && continue
+ if sysctl -w "$line" >/dev/null 2>&1; then
+ applied=$((applied + 1))
+ else
+ skipped=$((skipped + 1))
+ fi
+ done < "$CONF"
+ echo -e "${gl_lv}Applied${applied}Item parameter ${skipped:+, skip${skipped}Item unsupported parameter}${gl_bai}"
- echo -e "${gl_lv}Restore file descriptor...${gl_bai}"
- ulimit -n 1024
+ # ── Transparent large page ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo "$THP" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
+ fi
- echo -e "${gl_lv}Restore virtual memory...${gl_bai}"
- sysctl -w vm.swappiness=60 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=16384 2>/dev/null
+ # ── File descriptor restrictions ──
+ if ! grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
- echo -e "${gl_lv}Restore network settings...${gl_bai}"
- sysctl -w net.core.rmem_max=212992 2>/dev/null
- sysctl -w net.core.wmem_max=212992 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=1000 2>/dev/null
- sysctl -w net.core.somaxconn=128 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=0 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='32768 60999' 2>/dev/null
+# kejilion-optimize
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ fi
- echo -e "${gl_lv}Restore cache management...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=100 2>/dev/null
+ # ── BBR persistence ──
+ if [ "$CC" = "bbr" ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ # Clean up the bbr configuration in the old sysctl.conf (to avoid conflicts)
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+ fi
- echo -e "${gl_lv}Restore CPU settings...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
+ echo -e "${gl_lv}${mode_name}Optimization completed! The configuration has been persisted to${CONF}${gl_bai}"
+ echo -e "${gl_lv}Memory:${MEM_MB}MB | Congestion algorithm:${CC}| Queue:${QDISC}${gl_bai}"
+}
- echo -e "${gl_lv}Restore other optimizations...${gl_bai}"
- # Restore transparent page
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # Restore NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# ── Entry functions of each mode (keep the original calling interface unchanged) ──
+optimize_high_performance() {
+ _kernel_optimize_core "${tiaoyou_moshi:-高性能优化模式}" "high"
}
+optimize_balanced() {
+ _kernel_optimize_core "Balanced optimization mode" "balanced"
+}
-
-# Website building optimization function
optimize_web_server() {
- echo -e "${gl_lv}Switch to the website building optimization mode...${gl_bai}"
+ _kernel_optimize_core "Website construction optimization mode" "web"
+}
+
+# ── Restore default settings (complete clean) ──
+restore_defaults() {
+ echo -e "${gl_lv}Revert to default settings...${gl_bai}"
- echo -e "${gl_lv}Optimize file descriptors...${gl_bai}"
- ulimit -n 65535
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
- echo -e "${gl_lv}Optimize virtual memory...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # Delete the optimization configuration file (including external link automatic tuning configuration)
+ rm -f "$CONF"
+ rm -f /etc/sysctl.d/99-network-optimize.conf
- echo -e "${gl_lv}Optimize network settings...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=5000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # Clean up possible remaining bbr configuration in sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
- echo -e "${gl_lv}Optimize cache management...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # Reload system default configuration
+ sysctl --system 2>/dev/null | tail -1
- echo -e "${gl_lv}Optimize CPU settings...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # Restore transparent huge pages
+ [ -f /sys/kernel/mm/transparent_hugepage/enabled ] && \
+ echo always > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
- echo -e "${gl_lv}Other optimizations...${gl_bai}"
- # Disable large transparent pages to reduce latency
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # Disable NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # Clean file descriptor configuration
+ if grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# kejilion-optimize/,+4d' /etc/security/limits.conf
+ fi
+ # Clean up BBR persistence
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+ echo -e "${gl_lv}System has been restored to default settings${gl_bai}"
}
@@ -5319,27 +6381,35 @@ Kernel_optimize() {
while true; do
clear
send_stats "Linux kernel tuning management"
- echo "Optimization of kernel parameters in Linux system"
+ local current_mode=$(grep "^# Mode:" /etc/sysctl.d/99-kejilion-optimize.conf 2>/dev/null | sed 's/# mode: //' | awk -F'|' '{print $1}' | xargs)
+ [ -z "$current_mode" ] && [ -f /etc/sysctl.d/99-network-optimize.conf ] && current_mode="Automatic tuning mode"
+ echo "Linux system kernel parameter optimization"
+ if [ -n "$current_mode" ]; then
+ echo -e "Current mode:${gl_lv}${current_mode}${gl_bai}"
+ else
+ echo -e "Current mode:${gl_hui}Not optimized${gl_bai}"
+ fi
echo "Video introduction: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
echo "------------------------------------------------"
- echo "A variety of system parameter tuning modes are provided, and users can choose and switch according to their own usage scenarios."
- echo -e "${gl_huang}hint:${gl_bai}Please use it with caution in the production environment!"
- echo "--------------------"
- echo "1. High-performance optimization mode: Maximize system performance and optimize file descriptors, virtual memory, network settings, cache management and CPU settings."
- echo "2. Balanced optimization mode: Balance between performance and resource consumption, suitable for daily use."
- echo "3. Website optimization mode: Optimize for the website server to improve concurrent connection processing capabilities, response speed and overall performance."
- echo "4. Live broadcast optimization mode: Optimize the special needs of live broadcast streaming to reduce latency and improve transmission performance."
- echo "5. Game server optimization mode: Optimize for game servers to improve concurrent processing capabilities and response speed."
- echo "6. Restore the default settings: Restore the system settings to the default configuration."
+ echo "Provides a variety of system parameter tuning modes, and users can choose to switch according to their own usage scenarios."
+ echo -e "${gl_huang}hint:${gl_bai}Please use it with caution in production environment!"
+ echo -e "--------------------"
+ echo -e "1. High-performance optimization mode: Maximize system performance, aggressive memory and network parameters."
+ echo -e "2. Balanced optimization mode: strikes a balance between performance and resource consumption, suitable for daily use."
+ echo -e "3. Website optimization mode: Optimized for website servers, ultra-high concurrent connection queue."
+ echo -e "4. Live broadcast optimization mode: For live streaming optimization, the UDP buffer is enlarged to reduce delays."
+ echo -e "5. Game server optimization mode: Optimized for game servers, giving priority to low latency."
+ echo -e "6. Restore default settings: Restore system settings to default configuration."
+ echo -e "7. Automatic tuning: Automatically tune kernel parameters based on test data.${gl_huang}★${gl_bai}"
echo "--------------------"
echo "0. Return to the previous menu"
echo "--------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
cd ~
clear
- local tiaoyou_moshi="高性能优化模式"
+ local tiaoyou_moshi="High performance optimization mode"
optimize_high_performance
send_stats "High performance mode optimization"
;;
@@ -5353,28 +6423,35 @@ Kernel_optimize() {
cd ~
clear
optimize_web_server
- send_stats "Website optimization model"
+ send_stats "Website optimization mode"
;;
4)
cd ~
clear
- local tiaoyou_moshi="直播优化模式"
- optimize_high_performance
+ _kernel_optimize_core "Live broadcast optimization mode" "stream"
send_stats "Live streaming optimization"
;;
5)
cd ~
clear
- local tiaoyou_moshi="游戏服优化模式"
- optimize_high_performance
+ _kernel_optimize_core "Game server optimization mode" "game"
send_stats "Game server optimization"
;;
6)
cd ~
clear
restore_defaults
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh -o /tmp/network-optimize.sh && source /tmp/network-optimize.sh && restore_network_defaults
send_stats "Restore default settings"
;;
+
+ 7)
+ cd ~
+ clear
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ send_stats "Kernel automatic tuning"
+ ;;
+
*)
break
;;
@@ -5387,6 +6464,8 @@ Kernel_optimize() {
+
+
update_locale() {
local lang=$1
local locale_file=$2
@@ -5400,7 +6479,7 @@ update_locale() {
locale-gen
echo "LANG=${lang}" > /etc/default/locale
export LANG=${lang}
- echo -e "${gl_lv}The system language has been modified to:$langReconnecting SSH takes effect.${gl_bai}"
+ echo -e "${gl_lv}The system language has been modified to:$langReconnect to SSH to take effect.${gl_bai}"
hash -r
break_end
@@ -5409,7 +6488,7 @@ update_locale() {
install glibc-langpack-zh
localectl set-locale LANG=${lang}
echo "LANG=${lang}" | tee /etc/locale.conf
- echo -e "${gl_lv}The system language has been modified to:$langReconnecting SSH takes effect.${gl_bai}"
+ echo -e "${gl_lv}The system language has been modified to:$langReconnect to SSH to take effect.${gl_bai}"
hash -r
break_end
;;
@@ -5419,7 +6498,7 @@ update_locale() {
;;
esac
else
- echo "Unsupported systems, system type cannot be recognized."
+ echo "Unsupported system, system type cannot be identified."
break_end
fi
}
@@ -5438,12 +6517,12 @@ while true; do
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
update_locale "en_US.UTF-8" "en_US.UTF-8"
- send_stats "Switch to English"
+ send_stats "switch to english"
;;
2)
update_locale "zh_CN.UTF-8" "zh_CN.UTF-8"
@@ -5473,7 +6552,7 @@ else
echo "${bianse}" >> ~/.profile
# source ~/.profile
fi
-echo -e "${gl_lv}Change is completed. Reconnect SSH to view changes!${gl_bai}"
+echo -e "${gl_lv}Change completed. Reconnect to SSH to see the changes!${gl_bai}"
hash -r
break_end
@@ -5499,7 +6578,7 @@ shell_bianse() {
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
@@ -5544,7 +6623,7 @@ shell_bianse() {
linux_trash() {
root_use
- send_stats "System Recycling Station"
+ send_stats "System Recycle Bin"
local bashrc_profile="/root/.bashrc"
local TRASH_DIR="$HOME/.local/share/Trash/files"
@@ -5553,23 +6632,23 @@ linux_trash() {
local trash_status
if ! grep -q "trash-put" "$bashrc_profile"; then
- trash_status="${gl_hui}未启用${gl_bai}"
+ trash_status="${gl_hui}Not enabled${gl_bai}"
else
- trash_status="${gl_lv}已启用${gl_bai}"
+ trash_status="${gl_lv}Enabled${gl_bai}"
fi
clear
- echo -e "Current recycling bin${trash_status}"
- echo -e "After enabling, the files deleted by rm will first enter the recycling bin to prevent the mistaken deletion of important files!"
+ echo -e "Current recycle bin${trash_status}"
+ echo -e "After enabling it, files deleted by rm will be put into the recycle bin first to prevent accidental deletion of important files!"
echo "------------------------------------------------"
- ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "The recycling bin is empty"
+ ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "Recycle bin is empty"
echo "------------------------"
- echo "1. Enable the Recycle Bin 2. Close the Recycle Bin"
- echo "3. Restore content 4. Clear the recycling bin"
+ echo "1. Enable Recycle Bin 2. Close Recycle Bin"
+ echo "3. Restore content 4. Empty Recycle Bin"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
@@ -5577,7 +6656,7 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='trash-put'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "The Recycle Bin is enabled and deleted files will be moved to the Recycle Bin."
+ echo "Recycle Bin is enabled, deleted files will be moved to Recycle Bin."
sleep 2
;;
2)
@@ -5585,23 +6664,23 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='rm -i'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "The recycling bin is closed and the file will be deleted directly."
+ echo "The recycle bin is closed and the files will be deleted directly."
sleep 2
;;
3)
- read -e -p "Enter the file name to restore:" file_to_restore
+ read -e -p "Enter the file name to be restored:" file_to_restore
if [ -e "$TRASH_DIR/$file_to_restore" ]; then
mv "$TRASH_DIR/$file_to_restore" "$HOME/"
- echo "$file_to_restoreRestored to the home directory."
+ echo "$file_to_restoreRestored to home directory."
else
- echo "The file does not exist."
+ echo "File does not exist."
fi
;;
4)
- read -e -p "Confirm to clear the recycling bin? [y/n]:" confirm
+ read -e -p "Are you sure you want to empty the Recycle Bin? [y/n]:" confirm
if [[ "$confirm" == "y" ]]; then
trash-empty
- echo "The recycling bin has been cleared."
+ echo "Recycle Bin has been emptied."
fi
;;
*)
@@ -5611,21 +6690,24 @@ linux_trash() {
done
}
+linux_fav() {
+send_stats "Command Favorites"
+bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+}
-
-# Create a backup
+# Create backup
create_backup() {
- send_stats "Create a backup"
+ send_stats "Create backup"
local TIMESTAMP=$(date +"%Y%m%d%H%M%S")
- # Prompt the user to enter the backup directory
- echo "Create a backup example:"
- echo "- Backup a single directory: /var/www"
- echo "- Backup multiple directories: /etc /home /var/log"
- echo "- Direct Enter will use the default directory (/etc /usr /home)"
- read -r -p "Please enter the directory to back up (multiple directories are separated by spaces, and if you enter directly, use the default directory):" input
+ # Prompt user for backup directory
+ echo "Example of creating a backup:"
+ echo "- Back up a single directory: /var/www"
+ echo "- Back up multiple directories: /etc /home /var/log"
+ echo "- Press Enter to use the default directory (/etc /usr /home)"
+ read -e -p "Please enter the directory to be backed up (separate multiple directories with spaces, and press Enter to use the default directory):" input
- # If the user does not enter a directory, use the default directory
+ # If the user does not enter a directory, the default directory is used
if [ -z "$input" ]; then
BACKUP_PATHS=(
"/etc" # 配置文件和软件包配置
@@ -5633,7 +6715,7 @@ create_backup() {
"/home" # 用户数据
)
else
- # Separate the directory entered by the user into an array by spaces
+ # Separate the directories entered by the user into an array by spaces
IFS=' ' read -r -a BACKUP_PATHS <<< "$input"
fi
@@ -5651,20 +6733,20 @@ create_backup() {
# Generate backup file name
local BACKUP_NAME="${PREFIX}_$TIMESTAMP.tar.gz"
- # Print the directory selected by the user
+ # Print directory selected by user
echo "The backup directory you selected is:"
for path in "${BACKUP_PATHS[@]}"; do
echo "- $path"
done
- # Create a backup
- echo "Creating a backup$BACKUP_NAME..."
+ # Create backup
+ echo "Creating backup$BACKUP_NAME..."
install tar
tar -czvf "$BACKUP_DIR/$BACKUP_NAME" "${BACKUP_PATHS[@]}"
- # Check if the command is successful
+ # Check if the command was successful
if [ $? -eq 0 ]; then
- echo "The backup was created successfully:$BACKUP_DIR/$BACKUP_NAME"
+ echo "Backup created successfully:$BACKUP_DIR/$BACKUP_NAME"
else
echo "Backup creation failed!"
exit 1
@@ -5674,8 +6756,8 @@ create_backup() {
# Restore backup
restore_backup() {
send_stats "Restore backup"
- # Select the backup you want to restore
- read -e -p "Please enter the backup file name to restore:" BACKUP_NAME
+ # Select the backup to restore
+ read -e -p "Please enter the backup file name to be restored:" BACKUP_NAME
# Check if the backup file exists
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
@@ -5683,13 +6765,13 @@ restore_backup() {
exit 1
fi
- echo "Recovering backup$BACKUP_NAME..."
+ echo "Restoring backup$BACKUP_NAME..."
tar -xzvf "$BACKUP_DIR/$BACKUP_NAME" -C /
if [ $? -eq 0 ]; then
- echo "Backup and restore successfully!"
+ echo "Backup and restore successful!"
else
- echo "Backup recovery failed!"
+ echo "Backup restore failed!"
exit 1
fi
}
@@ -5704,7 +6786,7 @@ list_backups() {
delete_backup() {
send_stats "Delete backup"
- read -e -p "Please enter the backup file name to delete:" BACKUP_NAME
+ read -e -p "Please enter the backup file name to be deleted:" BACKUP_NAME
# Check if the backup file exists
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
@@ -5716,7 +6798,7 @@ delete_backup() {
rm -f "$BACKUP_DIR/$BACKUP_NAME"
if [ $? -eq 0 ]; then
- echo "The backup was deleted successfully!"
+ echo "Backup deleted successfully!"
else
echo "Backup deletion failed!"
exit 1
@@ -5734,11 +6816,11 @@ linux_backup() {
echo "------------------------"
list_backups
echo "------------------------"
- echo "1. Create a backup 2. Restore a backup 3. Delete the backup"
+ echo "1. Create backup 2. Restore backup 3. Delete backup"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case $choice in
1) create_backup ;;
2) restore_backup ;;
@@ -5757,83 +6839,203 @@ linux_backup() {
-# Show connection list
-list_connections() {
- echo "Saved connection:"
- echo "------------------------"
- cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
- echo "------------------------"
+# SSH input normalization function
+kj_ssh_validate_host() {
+ local host="$1"
+ [[ -n "$host" && ! "$host" =~ [[:space:]] && "$host" =~ ^[A-Za-z0-9._:-]+$ ]]
}
+kj_ssh_validate_port() {
+ local port="$1"
+ [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
+}
-# Add a new connection
-add_connection() {
- send_stats "Add a new connection"
- echo "Example to create a new connection:"
- echo "- Connection name: my_server"
- echo "- IP address: 192.168.1.100"
- echo "- Username: root"
- echo "- Port: 22"
- echo "------------------------"
- read -e -p "Please enter the connection name:" name
- read -e -p "Please enter your IP address:" ip
- read -e -p "Please enter the username (default: root):" user
- local user=${user:-root} # 如果用户未输入,则使用默认值 root
- read -e -p "Please enter the port number (default: 22):" port
- local port=${port:-22} # 如果用户未输入,则使用默认值 22
-
- echo "Please select the authentication method:"
+kj_ssh_validate_user() {
+ local user="$1"
+ [[ -n "$user" && "$user" =~ ^[A-Za-z_][A-Za-z0-9._-]*$ ]]
+}
+
+kj_ssh_read_host_port() {
+ local host_prompt="$1"
+ local port_prompt="$2"
+ local default_port="${3:-22}"
+
+ while true; do
+ read -e -p "$host_prompt" KJ_SSH_HOST
+ if kj_ssh_validate_host "$KJ_SSH_HOST"; then
+ break
+ fi
+ echo "Error: Please enter a valid server address."
+ done
+
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ break
+ fi
+ echo "Error: Port must be a number between 1-65535."
+ done
+}
+
+kj_ssh_read_host_user_port() {
+ local host_prompt="$1"
+ local user_prompt="$2"
+ local port_prompt="$3"
+ local default_user="${4:-root}"
+ local default_port="${5:-22}"
+
+ kj_ssh_read_host_port "$host_prompt" "$port_prompt" "$default_port"
+
+ while true; do
+ read -e -p "$user_prompt" KJ_SSH_USER
+ KJ_SSH_USER=${KJ_SSH_USER:-$default_user}
+ if kj_ssh_validate_user "$KJ_SSH_USER"; then
+ break
+ fi
+ echo "Error: Username format is incorrect."
+ done
+}
+
+kj_ssh_parse_remote() {
+ local remote_raw="$1"
+ local default_user="${2:-root}"
+ local remote_user remote_host
+
+ if [[ "$remote_raw" == *@* ]]; then
+ remote_user="${remote_raw%@*}"
+ remote_host="${remote_raw#*@}"
+ else
+ remote_user="$default_user"
+ remote_host="$remote_raw"
+ fi
+
+ if ! kj_ssh_validate_user "$remote_user"; then
+ echo "Error: SSH username format is incorrect."
+ return 1
+ fi
+
+ if ! kj_ssh_validate_host "$remote_host"; then
+ echo "Error: SSH host address format is incorrect."
+ return 1
+ fi
+
+ KJ_SSH_USER="$remote_user"
+ KJ_SSH_HOST="$remote_host"
+ KJ_SSH_REMOTE="$remote_user@$remote_host"
+}
+
+kj_ssh_read_auth() {
+ local key_file="$1"
+ local password_or_key=""
+
+ echo "Please select an authentication method:"
echo "1. Password"
echo "2. Key"
- read -e -p "Please enter the selection (1/2):" auth_choice
+ read -e -p "Please enter your choice (1/2):" auth_choice
case $auth_choice in
1)
- read -s -p "Please enter your password:" password_or_key
- echo # 换行
+ read -s -p "Please enter password:" password_or_key
+ echo
+ if [ -z "$password_or_key" ]; then
+ echo "Error: Password cannot be empty."
+ return 1
+ fi
+ KJ_SSH_AUTH_METHOD="password"
+ KJ_SSH_AUTH_SECRET="$password_or_key"
;;
2)
- echo "Please paste the key content (press press Enter twice after pasting):"
- local password_or_key=""
+ echo "Please paste the key content (press Enter twice after pasting):"
while IFS= read -r line; do
- # If the input is empty and the key content already contains the beginning, the input ends
if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
break
fi
- # If it is the first line or the key content has been entered, continue to add
if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- local password_or_key+="${line}"$'\n'
+ password_or_key+="${line}"$'\n'
fi
done
- # Check if it is the key content
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/$name.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- local password_or_key="$key_file"
+ if [[ "$password_or_key" != *"-----BEGIN"* || "$password_or_key" != *"PRIVATE KEY-----"* ]]; then
+ echo "Invalid key content!"
+ return 1
fi
+
+ mkdir -p "$(dirname "$key_file")"
+ echo -n "$password_or_key" > "$key_file"
+ chmod 600 "$key_file"
+ KJ_SSH_AUTH_METHOD="key"
+ KJ_SSH_AUTH_SECRET="$key_file"
;;
*)
echo "Invalid choice!"
- return
+ return 1
;;
esac
+}
+
+kj_ssh_read_password() {
+ local prompt="${1:-请输入密码: }"
+ while true; do
+ read -e -s -p "$prompt" KJ_SSH_PASSWORD
+ echo
+ [ -n "$KJ_SSH_PASSWORD" ] && break
+ echo "Error: Password cannot be empty."
+ done
+}
+
+kj_ssh_read_port() {
+ local port_prompt="$1"
+ local default_port="${2:-22}"
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ return 0
+ fi
+ echo "Error: Port must be a number between 1-65535."
+ done
+}
+
+# Show connection list
+list_connections() {
+ echo "Saved connections:"
+ echo "------------------------"
+ cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
+ echo "------------------------"
+}
+
- echo "$name|$ip|$user|$port|$password_or_key" >> "$CONFIG_FILE"
- echo "The connection is saved!"
+# Add new connection
+add_connection() {
+ send_stats "Add new connection"
+ echo "Example of creating a new connection:"
+ echo "- Connection name: my_server"
+ echo "- IP address: 192.168.1.100"
+ echo "- Username: root"
+ echo "- Port: 22"
+ echo "------------------------"
+ read -e -p "Please enter a connection name:" name
+
+ kj_ssh_read_host_user_port "Please enter IP address:" "Please enter username (default: root):" "Please enter the port number (default: 22):" "root" "22"
+ if ! kj_ssh_read_auth "$KEY_DIR/$name.key"; then
+ return
+ fi
+
+ echo "$name|$KJ_SSH_HOST|$KJ_SSH_USER|$KJ_SSH_PORT|$KJ_SSH_AUTH_SECRET" >> "$CONFIG_FILE"
+ echo "Connection saved!"
}
-# Delete a connection
+# Delete connection
delete_connection() {
- send_stats "Delete a connection"
- read -e -p "Please enter the connection number to delete:" num
+ send_stats "Delete connection"
+ read -e -p "Please enter the connection number to be deleted:" num
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "Error: The corresponding connection was not found."
+ echo "Error: Corresponding connection not found."
return
fi
@@ -5845,7 +7047,7 @@ delete_connection() {
fi
sed -i "${num}d" "$CONFIG_FILE"
- echo "The connection has been deleted!"
+ echo "Connection deleted!"
}
# Use connection
@@ -5855,7 +7057,7 @@ use_connection() {
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "Error: The corresponding connection was not found."
+ echo "Error: Corresponding connection not found."
return
fi
@@ -5863,16 +7065,16 @@ use_connection() {
echo "Connecting to$name ($ip)..."
if [[ -f "$password_or_key" ]]; then
- # Connect with a key
+ # Connect using a key
ssh -o StrictHostKeyChecking=no -i "$password_or_key" -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
echo "Connection failed! Please check the following:"
echo "1. Is the key file path correct?$password_or_key"
- echo "2. Whether the key file permissions are correct (should be 600)."
- echo "3. Whether the target server allows login using the key."
+ echo "2. Are the key file permissions correct (should be 600)."
+ echo "3. Whether the target server allows login using a key."
fi
else
- # Connect with a password
+ # Connect using password
if ! command -v sshpass &> /dev/null; then
echo "Error: sshpass is not installed, please install sshpass first."
echo "Installation method:"
@@ -5883,7 +7085,7 @@ use_connection() {
sshpass -p "$password_or_key" ssh -o StrictHostKeyChecking=no -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
echo "Connection failed! Please check the following:"
- echo "1. Whether the username and password are correct."
+ echo "1. Are the username and password correct?"
echo "2. Whether the target server allows password login."
echo "3. Whether the SSH service of the target server is running normally."
fi
@@ -5897,7 +7099,7 @@ ssh_manager() {
CONFIG_FILE="$HOME/.ssh_connections"
KEY_DIR="$HOME/.ssh/ssh_manager_keys"
- # Check if the configuration file and key directory exist, and if it does not exist, create it
+ # Check if the configuration file and key directory exist, create them if they do not exist
if [[ ! -f "$CONFIG_FILE" ]]; then
touch "$CONFIG_FILE"
fi
@@ -5909,15 +7111,15 @@ ssh_manager() {
while true; do
clear
- echo "SSH Remote Connection Tool"
- echo "Can be connected to other Linux systems via SSH"
+ echo "SSH remote connection tool"
+ echo "Can connect to other Linux systems via SSH"
echo "------------------------"
list_connections
- echo "1. Create a new connection 2. Use a connection 3. Delete a connection"
+ echo "1. Create a new connection 2. Use the connection 3. Delete the connection"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case $choice in
1) add_connection ;;
2) use_connection ;;
@@ -5941,62 +7143,90 @@ ssh_manager() {
# List available hard disk partitions
list_partitions() {
- echo "Available hard disk partitions:"
+ echo "Available hard drive partitions:"
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT | grep -v "sr\|loop"
}
-# Mount the partition
+
+# Persistently mounted partition
mount_partition() {
- send_stats "Mount the partition"
- read -e -p "Please enter the partition name to be mounted (for example, sda1):" PARTITION
+ send_stats "Mount partition"
+ read -e -p "Please enter the name of the partition to be mounted (e.g. sda1):" PARTITION
+
+ DEVICE="/dev/$PARTITION"
+ MOUNT_POINT="/mnt/$PARTITION"
# Check if the partition exists
- if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
+ if ! lsblk -no NAME | grep -qw "$PARTITION"; then
echo "The partition does not exist!"
- return
+ return 1
fi
- # Check if the partition is already mounted
- if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "The partition is already mounted!"
- return
+ # Check if it is mounted
+ if mount | grep -qw "$DEVICE"; then
+ echo "The partition has been mounted!"
+ return 1
fi
- # Create a mount point
- MOUNT_POINT="/mnt/$PARTITION"
- mkdir -p "$MOUNT_POINT"
+ # Get UUID
+ UUID=$(blkid -s UUID -o value "$DEVICE")
+ if [ -z "$UUID" ]; then
+ echo "Unable to get UUID!"
+ return 1
+ fi
+
+ # Get file system type
+ FSTYPE=$(blkid -s TYPE -o value "$DEVICE")
+ if [ -z "$FSTYPE" ]; then
+ echo "Unable to get file system type!"
+ return 1
+ fi
- # Mount the partition
- mount "/dev/$PARTITION" "$MOUNT_POINT"
+ # Create mount point
+ mkdir -p "$MOUNT_POINT"
- if [ $? -eq 0 ]; then
- echo "Partition mount successfully:$MOUNT_POINT"
- else
+ # mount
+ if ! mount "$DEVICE" "$MOUNT_POINT"; then
echo "Partition mount failed!"
rmdir "$MOUNT_POINT"
+ return 1
+ fi
+
+ echo "Partition successfully mounted to$MOUNT_POINT"
+
+ # Check /etc/fstab to see if the UUID or mount point already exists
+ if grep -qE "UUID=$UUID|[[:space:]]$MOUNT_POINT[[:space:]]" /etc/fstab; then
+ echo "The partition record already exists in /etc/fstab, skip writing"
+ return 0
fi
+
+ # Write to /etc/fstab
+ echo "UUID=$UUID $MOUNT_POINT $FSTYPE defaults,nofail 0 2" >> /etc/fstab
+
+ echo "Written to /etc/fstab to achieve persistent mounting"
}
-# Uninstall the partition
+
+# Unmount partition
unmount_partition() {
- send_stats "Uninstall the partition"
- read -e -p "Please enter the partition name (for example, sda1):" PARTITION
+ send_stats "Unmount partition"
+ read -e -p "Please enter the name of the partition to be unmounted (e.g. sda1):" PARTITION
- # Check if the partition is already mounted
+ # Check whether the partition is mounted
MOUNT_POINT=$(lsblk -o MOUNTPOINT | grep -w "$PARTITION")
if [ -z "$MOUNT_POINT" ]; then
echo "The partition is not mounted!"
return
fi
- # Uninstall the partition
+ # Unmount partition
umount "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "Partition uninstallation successfully:$MOUNT_POINT"
+ echo "Partition uninstalled successfully:$MOUNT_POINT"
rmdir "$MOUNT_POINT"
else
- echo "Partition uninstallation failed!"
+ echo "Partition uninstall failed!"
fi
}
@@ -6009,7 +7239,7 @@ list_mounted_partitions() {
# Format partition
format_partition() {
send_stats "Format partition"
- read -e -p "Please enter the partition name to format (for example, sda1):" PARTITION
+ read -e -p "Please enter the name of the partition to be formatted (e.g. sda1):" PARTITION
# Check if the partition exists
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
@@ -6017,19 +7247,19 @@ format_partition() {
return
fi
- # Check if the partition is already mounted
+ # Check whether the partition is mounted
if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "The partition has been mounted, please uninstall it first!"
+ echo "The partition has been mounted, please unmount it first!"
return
fi
- # Select a file system type
- echo "Please select the file system type:"
+ # Select file system type
+ echo "Please select a file system type:"
echo "1. ext4"
echo "2. xfs"
echo "3. ntfs"
echo "4. vfat"
- read -e -p "Please enter your selection:" FS_CHOICE
+ read -e -p "Please enter your choice:" FS_CHOICE
case $FS_CHOICE in
1) FS_TYPE="ext4" ;;
@@ -6040,7 +7270,7 @@ format_partition() {
esac
# Confirm formatting
- read -e -p "Confirm formatting partition /dev/$PARTITIONfor$FS_TYPEIs it? (y/n):" CONFIRM
+ read -e -p "Confirm formatted partition /dev/$PARTITIONfor$FS_TYPE? (y/n):" CONFIRM
if [ "$CONFIRM" != "y" ]; then
echo "The operation has been cancelled."
return
@@ -6051,7 +7281,7 @@ format_partition() {
mkfs.$FS_TYPE "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "The partition format was successful!"
+ echo "Partition formatted successfully!"
else
echo "Partition formatting failed!"
fi
@@ -6069,26 +7299,26 @@ check_partition() {
fi
# Check partition status
- echo "Check partition /dev/$PARTITIONStatus:"
+ echo "Check partition /dev/$PARTITIONstatus:"
fsck "/dev/$PARTITION"
}
-# Main Menu
+# Main menu
disk_manager() {
send_stats "Hard disk management function"
while true; do
clear
- echo "Hard disk partition management"
- echo -e "${gl_huang}This function is internally tested during the test period, please do not use it in the production environment.${gl_bai}"
+ echo "Hard drive partition management"
+ echo -e "${gl_huang}This feature is under internal testing and should not be used in a production environment.${gl_bai}"
echo "------------------------"
list_partitions
echo "------------------------"
- echo "1. Mount the partition 2. Uninstall the partition 3. View mounted partition"
- echo "4. Format the partition 5. Check the partition status"
+ echo "1. Mount the partition 2. Unmount the partition 3. View the mounted partition"
+ echo "4. Format partition 5. Check partition status"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case $choice in
1) mount_partition ;;
2) unmount_partition ;;
@@ -6106,73 +7336,44 @@ disk_manager() {
# Show task list
list_tasks() {
- echo "Saved synchronization tasks:"
+ echo "Saved sync tasks:"
echo "---------------------------------"
awk -F'|' '{print NR " - " $1 " ( " $2 " -> " $3":"$4 " )"}' "$CONFIG_FILE"
echo "---------------------------------"
}
-# Add a new task
+# Add new task
add_task() {
- send_stats "Add a new synchronization task"
- echo "Create a new synchronization task example:"
+ send_stats "Add new sync task"
+ echo "Example of creating a new sync task:"
echo "- Task name: backup_www"
- echo "- Local Directory: /var/www"
+ echo "- Local directory: /var/www"
echo "- Remote address: user@192.168.1.100"
- echo "- Remote Directory: /backup/www"
+ echo "- Remote directory: /backup/www"
echo "- Port number (default 22)"
echo "---------------------------------"
read -e -p "Please enter the task name:" name
read -e -p "Please enter the local directory:" local_path
read -e -p "Please enter the remote directory:" remote_path
- read -e -p "Please enter the remote user @IP:" remote
- read -e -p "Please enter the SSH port (default 22):" port
- port=${port:-22}
- echo "Please select the authentication method:"
- echo "1. Password"
- echo "2. Key"
- read -e -p "Please select (1/2):" auth_choice
+ while true; do
+ read -e -p "Please enter remote user@IP:" remote
+ if kj_ssh_parse_remote "$remote" "root"; then
+ remote="$KJ_SSH_REMOTE"
+ break
+ fi
+ done
- case $auth_choice in
- 1)
- read -s -p "Please enter your password:" password_or_key
- echo # 换行
- auth_method="password"
- ;;
- 2)
- echo "Please paste the key content (press press Enter twice after pasting):"
- local password_or_key=""
- while IFS= read -r line; do
- # If the input is empty and the key content already contains the beginning, the input ends
- if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
- break
- fi
- # If it is the first line or the key content has been entered, continue to add
- if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- password_or_key+="${line}"$'\n'
- fi
- done
+ kj_ssh_read_port "Please enter the SSH port (default 22):" "22"
+ port="$KJ_SSH_PORT"
- # Check if it is the key content
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/${name}_sync.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- password_or_key="$key_file"
- auth_method="key"
- else
- echo "Invalid key content!"
- return
- fi
- ;;
- *)
- echo "Invalid choice!"
- return
- ;;
- esac
+ if ! kj_ssh_read_auth "$KEY_DIR/${name}_sync.key"; then
+ return
+ fi
+ auth_method="$KJ_SSH_AUTH_METHOD"
+ password_or_key="$KJ_SSH_AUTH_SECRET"
- echo "Please select the synchronization mode:"
+ echo "Please select synchronization mode:"
echo "1. Standard mode (-avz)"
echo "2. Delete the target file (-avz --delete)"
read -e -p "Please select (1/2):" mode
@@ -6186,13 +7387,14 @@ add_task() {
install rsync rsync
- echo "Task saved!"
+ echo "Mission saved!"
}
-# Delete a task
+
+# Delete task
delete_task() {
- send_stats "Delete synchronization tasks"
- read -e -p "Please enter the task number to delete:" num
+ send_stats "Delete sync task"
+ read -e -p "Please enter the task number to be deleted:" num
local task=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$task" ]]; then
@@ -6208,7 +7410,7 @@ delete_task() {
fi
sed -i "${num}d" "$CONFIG_FILE"
- echo "Task deleted!"
+ echo "Task has been deleted!"
}
@@ -6218,7 +7420,7 @@ run_task() {
CONFIG_FILE="$HOME/.rsync_tasks"
CRON_FILE="$HOME/.rsync_cron"
- # Analyze parameters
+ # Parse parameters
local direction="push" # 默认是推送到远端
local num
@@ -6229,7 +7431,7 @@ run_task() {
num="$1"
fi
- # If there is no incoming task number, prompt the user to enter
+ # If no task number is passed in, the user is prompted to enter
if [[ -z "$num" ]]; then
read -e -p "Please enter the task number to be executed:" num
fi
@@ -6242,13 +7444,13 @@ run_task() {
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # Adjust source and target path according to synchronization direction
+ # Adjust source and destination paths based on synchronization direction
if [[ "$direction" == "pull" ]]; then
- echo "Pulling synchronization to local:$remote:$local_path -> $remote_path"
+ echo "Pulling and synchronizing to local:$remote:$local_path -> $remote_path"
source="$remote:$local_path"
destination="$remote_path"
else
- echo "Push synchronization to the remote end:$local_path -> $remote:$remote_path"
+ echo "Pushing and synchronizing to the remote end:$local_path -> $remote:$remote_path"
source="$local_path"
destination="$remote:$remote_path"
fi
@@ -6268,12 +7470,12 @@ run_task() {
else
# Check whether the key file exists and whether the permissions are correct
if [[ ! -f "$password_or_key" ]]; then
- echo "Error: The key file does not exist:$password_or_key"
+ echo "Error: Key file does not exist:$password_or_key"
return
fi
if [[ "$(stat -c %a "$password_or_key")" != "600" ]]; then
- echo "Warning: The key file permissions are incorrect, and are being repaired..."
+ echo "Warning: Incorrect key file permissions, fixing..."
chmod 600 "$password_or_key"
fi
@@ -6281,20 +7483,20 @@ run_task() {
fi
if [[ $? -eq 0 ]]; then
- echo "Synchronization is complete!"
+ echo "Synchronization completed!"
else
- echo "Synchronization failed! Please check the following:"
+ echo "Sync failed! Please check the following:"
echo "1. Is the network connection normal?"
- echo "2. Is the remote host accessible?"
+ echo "2. Whether the remote host is accessible"
echo "3. Is the authentication information correct?"
- echo "4. Do local and remote directories have correct access permissions"
+ echo "4. Do the local and remote directories have correct access permissions?"
fi
}
-# Create a timed task
+# Create a scheduled task
schedule_task() {
- send_stats "Add synchronization timing tasks"
+ send_stats "Add synchronization scheduled tasks"
read -e -p "Please enter the task number to be synchronized regularly:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
@@ -6302,9 +7504,9 @@ schedule_task() {
return
fi
- echo "Please select the timed execution interval:"
- echo "1) Execute once an hour"
- echo "2) Perform once a day"
+ echo "Please select the scheduled execution interval:"
+ echo "1) Execute once every hour"
+ echo "2) Execute once a day"
echo "3) Execute once a week"
read -e -p "Please enter options (1/2/3):" interval
@@ -6314,7 +7516,7 @@ schedule_task() {
1) cron_time="$random_minute * * * *" ;; # 每小时,随机分钟执行
2) cron_time="$random_minute 0 * * *" ;; # 每天,随机分钟执行
3) cron_time="$random_minute 0 * * 1" ;; # 每周,随机分钟执行
- *) echo "Error: Please enter a valid option!" ; return ;;
+ *) echo "Error: Please enter valid options!" ; return ;;
esac
local cron_job="$cron_time k rsync_run $num"
@@ -6322,38 +7524,38 @@ schedule_task() {
# Check if the same task already exists
if crontab -l | grep -q "k rsync_run $num"; then
- echo "Error: The timing synchronization of this task already exists!"
+ echo "Error: The scheduled synchronization for this task already exists!"
return
fi
- # Create a crontab to the user
+ # Create to user's crontab
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "The timing task has been created:$cron_job"
+ echo "Scheduled task has been created:$cron_job"
}
# View scheduled tasks
view_tasks() {
- echo "Current timing tasks:"
+ echo "Current scheduled tasks:"
echo "---------------------------------"
crontab -l | grep "k rsync_run"
echo "---------------------------------"
}
-# Delete timing tasks
+# Delete scheduled tasks
delete_task_schedule() {
- send_stats "Delete synchronization timing tasks"
- read -e -p "Please enter the task number to delete:" num
+ send_stats "Delete synchronization scheduled tasks"
+ read -e -p "Please enter the task number to be deleted:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "Error: Please enter a valid task number!"
return
fi
crontab -l | grep -v "k rsync_run $num" | crontab -
- echo "Deleted task number$numTiming tasks"
+ echo "Task number deleted$numscheduled tasks"
}
-# Task Management Main Menu
+# Task management main menu
rsync_manager() {
CONFIG_FILE="$HOME/.rsync_tasks"
CRON_FILE="$HOME/.rsync_cron"
@@ -6361,19 +7563,19 @@ rsync_manager() {
while true; do
clear
echo "Rsync remote synchronization tool"
- echo "Synchronization between remote directories supports incremental synchronization, efficient and stable."
+ echo "Synchronization between remote directories supports incremental synchronization, which is efficient and stable."
echo "---------------------------------"
list_tasks
echo
view_tasks
echo
echo "1. Create a new task 2. Delete a task"
- echo "3. Perform local synchronization to the remote end 4. Perform remote synchronization to the local end"
- echo "5. Create a timing task 6. Delete a timing task"
+ echo "3. Perform local synchronization to the remote site 4. Perform remote synchronization to the local site"
+ echo "5. Create a scheduled task 6. Delete a scheduled task"
echo "---------------------------------"
echo "0. Return to the previous menu"
echo "---------------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case $choice in
1) add_task ;;
2) delete_task ;;
@@ -6396,9 +7598,12 @@ rsync_manager() {
-linux_ps() {
+linux_info() {
+
+
clear
+ echo -e "${gl_kjlan}Querying system information...${gl_bai}"
send_stats "System information query"
ip_address
@@ -6443,15 +7648,17 @@ linux_ps() {
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1 % 3600) / 60); if (run_days > 0) printf("%d天 ", run_days); if (run_hours > 0) printf("%d时 ", run_hours); printf("%d分\n", run_minutes)}')
+ local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1% 3600) / 60); if (run_days > 0) printf("%d day ", run_days); if (run_hours > 0) printf("%d hour ", run_hours); printf("%d minute\n", run_minutes)}')
local timezone=$(current_timezone)
+ local tcp_count=$(ss -t | wc -l)
+ local udp_count=$(ss -u | wc -l)
- echo ""
+ clear
echo -e "System information query"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}Host Name:${gl_bai}$hostname"
+ echo -e "${gl_kjlan}Hostname:${gl_bai}$hostname"
echo -e "${gl_kjlan}System version:${gl_bai}$os_info"
echo -e "${gl_kjlan}Linux version:${gl_bai}$kernel_version"
echo -e "${gl_kjlan}-------------"
@@ -6460,14 +7667,15 @@ linux_ps() {
echo -e "${gl_kjlan}Number of CPU cores:${gl_bai}$cpu_cores"
echo -e "${gl_kjlan}CPU frequency:${gl_bai}$cpu_freq"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU occupancy:${gl_bai}$cpu_usage_percent%"
+ echo -e "${gl_kjlan}CPU usage:${gl_bai}$cpu_usage_percent%"
echo -e "${gl_kjlan}System load:${gl_bai}$load"
+ echo -e "${gl_kjlan}Number of TCP|UDP connections:${gl_bai}$tcp_count|$udp_count"
echo -e "${gl_kjlan}Physical memory:${gl_bai}$mem_info"
echo -e "${gl_kjlan}Virtual memory:${gl_bai}$swap_info"
- echo -e "${gl_kjlan}Hard disk occupation:${gl_bai}$disk_info"
+ echo -e "${gl_kjlan}Hard drive usage:${gl_bai}$disk_info"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}Total Receive:${gl_bai}$rx"
- echo -e "${gl_kjlan}Total send:${gl_bai}$tx"
+ echo -e "${gl_kjlan}Total received:${gl_bai}$rx"
+ echo -e "${gl_kjlan}Total sent:${gl_bai}$tx"
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}Network algorithm:${gl_bai}$congestion_algorithm $queue_algorithm"
echo -e "${gl_kjlan}-------------"
@@ -6480,10 +7688,10 @@ linux_ps() {
echo -e "${gl_kjlan}IPv6 address:${gl_bai}$ipv6_address"
fi
echo -e "${gl_kjlan}DNS address:${gl_bai}$dns_addresses"
- echo -e "${gl_kjlan}Geographical location:${gl_bai}$country $city"
+ echo -e "${gl_kjlan}Location:${gl_bai}$country $city"
echo -e "${gl_kjlan}System time:${gl_bai}$timezone $current_time"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}Runtime:${gl_bai}$runtime"
+ echo -e "${gl_kjlan}Running time:${gl_bai}$runtime"
echo
@@ -6496,39 +7704,91 @@ linux_tools() {
while true; do
clear
- # send_stats "Basic Tools"
- echo -e "Basic tools"
+ # send_stats "Basic tools"
+ echo -e "basic tools"
+
+ tools=(
+ curl wget sudo socat htop iftop unzip tar tmux ffmpeg
+ btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders
+ vim nano git
+ )
+
+ if command -v apt >/dev/null 2>&1; then
+ PM="apt"
+ elif command -v dnf >/dev/null 2>&1; then
+ PM="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PM="yum"
+ elif command -v pacman >/dev/null 2>&1; then
+ PM="pacman"
+ elif command -v apk >/dev/null 2>&1; then
+ PM="apk"
+ elif command -v zypper >/dev/null 2>&1; then
+ PM="zypper"
+ elif command -v opkg >/dev/null 2>&1; then
+ PM="opkg"
+ elif command -v pkg >/dev/null 2>&1; then
+ PM="pkg"
+ else
+ echo "❌ Unrecognized package manager"
+ exit 1
+ fi
+
+ echo "📦 Use a package manager:$PM"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+
+ for ((i=0; i<${#tools[@]}; i+=2)); do
+ # left column
+ if command -v "${tools[i]}" >/dev/null 2>&1; then
+ left=$(printf "✅ %-12s installed" "${tools[i]}")
+ else
+ left=$(printf "❌ %-12s not installed" "${tools[i]}")
+ fi
+
+ # Right column (to prevent the array from going out of bounds)
+ if [[ -n "${tools[i+1]}" ]]; then
+ if command -v "${tools[i+1]}" >/dev/null 2>&1; then
+ right=$(printf "✅ %-12s installed" "${tools[i+1]}")
+ else
+ right=$(printf "❌ %-12s not installed" "${tools[i+1]}")
+ fi
+ printf "%-42s %s\n" "$left" "$right"
+ else
+ printf "%s\n" "$left"
+ fi
+ done
+
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}curl download tool${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}wget download tool${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}3. ${gl_bai}sudo super management permission tool${gl_kjlan}4. ${gl_bai}socat communication connection tool"
+ echo -e "${gl_kjlan}3. ${gl_bai}sudo super administrative privilege tool${gl_kjlan}4. ${gl_bai}socat communication connection tool"
echo -e "${gl_kjlan}5. ${gl_bai}htop system monitoring tool${gl_kjlan}6. ${gl_bai}iftop network traffic monitoring tool"
- echo -e "${gl_kjlan}7. ${gl_bai}unzip ZIP compression decompression tool${gl_kjlan}8. ${gl_bai}tar GZ compression decompression tool"
+ echo -e "${gl_kjlan}7. ${gl_bai}unzip ZIP compression and decompression tool${gl_kjlan}8. ${gl_bai}tar GZ compression and decompression tool"
echo -e "${gl_kjlan}9. ${gl_bai}tmux multi-channel background running tool${gl_kjlan}10. ${gl_bai}ffmpeg video encoding live streaming tool"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}btop modern monitoring tools${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}range file management tool"
- echo -e "${gl_kjlan}13. ${gl_bai}ncdu disk occupation viewing tool${gl_kjlan}14. ${gl_bai}fzf global search tool"
+ echo -e "${gl_kjlan}11. ${gl_bai}btop modern monitoring tool${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}ranger file management tool"
+ echo -e "${gl_kjlan}13. ${gl_bai}ncdu disk usage viewing tool${gl_kjlan}14. ${gl_bai}fzf global search tool"
echo -e "${gl_kjlan}15. ${gl_bai}vim text editor${gl_kjlan}16. ${gl_bai}nano text editor${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}17. ${gl_bai}git version control system"
+ echo -e "${gl_kjlan}17. ${gl_bai}git version control system${gl_kjlan}18. ${gl_bai}opencode AI programming assistant${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}The Matrix Screen Guarantee${gl_kjlan}22. ${gl_bai}Train screen security"
- echo -e "${gl_kjlan}26. ${gl_bai}Tetris game${gl_kjlan}27. ${gl_bai}Snake-eating game"
- echo -e "${gl_kjlan}28. ${gl_bai}Space Invader Game"
+ echo -e "${gl_kjlan}21. ${gl_bai}The Matrix Screensaver${gl_kjlan}22. ${gl_bai}Running train screensaver"
+ echo -e "${gl_kjlan}26. ${gl_bai}Tetris mini game${gl_kjlan}27. ${gl_bai}Snake mini game"
+ echo -e "${gl_kjlan}28. ${gl_bai}space invaders mini game"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}Install all${gl_kjlan}32. ${gl_bai}All installations (excluding screen savers and games)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}31. ${gl_bai}Install all${gl_kjlan}32. ${gl_bai}Install all (excluding screensavers and games)${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}33. ${gl_bai}Uninstall all"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}Install the specified tool${gl_kjlan}42. ${gl_bai}Uninstall the specified tool"
+ echo -e "${gl_kjlan}41. ${gl_bai}Install specified tools${gl_kjlan}42. ${gl_bai}Uninstall the specified tool"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
clear
install curl
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
curl --help
send_stats "Install curl"
;;
@@ -6536,7 +7796,7 @@ linux_tools() {
clear
install wget
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
wget --help
send_stats "Install wget"
;;
@@ -6544,15 +7804,15 @@ linux_tools() {
clear
install sudo
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
sudo --help
- send_stats "Install sudo"
+ send_stats "install sudo"
;;
4)
clear
install socat
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
socat -h
send_stats "Install socat"
;;
@@ -6574,15 +7834,15 @@ linux_tools() {
clear
install unzip
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
unzip
- send_stats "Install unzip"
+ send_stats "installunzip"
;;
8)
clear
install tar
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
tar --help
send_stats "Install tar"
;;
@@ -6590,7 +7850,7 @@ linux_tools() {
clear
install tmux
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
tmux --help
send_stats "Install tmux"
;;
@@ -6598,7 +7858,7 @@ linux_tools() {
clear
install ffmpeg
clear
- echo "The tool has been installed and the usage method is as follows:"
+ echo "The tool has been installed and is used as follows:"
ffmpeg --help
send_stats "Install ffmpeg"
;;
@@ -6667,6 +7927,17 @@ linux_tools() {
send_stats "Install git"
;;
+ 18)
+ clear
+ cd ~
+ curl -fsSL https://opencode.ai/install | bash
+ source ~/.bashrc
+ source ~/.profile
+ opencode
+ send_stats "Install opencode"
+ ;;
+
+
21)
clear
install cmatrix
@@ -6695,6 +7966,7 @@ linux_tools() {
nsnake
send_stats "Install nsnake"
;;
+
28)
clear
install ninvaders
@@ -6711,7 +7983,7 @@ linux_tools() {
32)
clear
- send_stats "Install all (excluding games and screen savers)"
+ send_stats "Install all (excluding games and screensavers)"
install curl wget sudo socat htop iftop unzip tar tmux ffmpeg btop ranger ncdu fzf vim nano git
;;
@@ -6720,19 +7992,21 @@ linux_tools() {
clear
send_stats "Uninstall all"
remove htop iftop tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
+ opencode uninstall
+ rm -rf ~/.opencode
;;
41)
clear
read -e -p "Please enter the installed tool name (wget curl sudo htop):" installname
install $installname
- send_stats "Install the specified software"
+ send_stats "Install specified software"
;;
42)
clear
read -e -p "Please enter the uninstalled tool name (htop ufw tmux cmatrix):" removename
remove $removename
- send_stats "Uninstall the specified software"
+ send_stats "Uninstall specified software"
;;
0)
@@ -6763,21 +8037,21 @@ linux_bbr() {
echo "Current TCP blocking algorithm:$congestion_algorithm $queue_algorithm"
echo ""
- echo "BBR Management"
+ echo "BBR management"
echo "------------------------"
- echo "1. Turn on BBRv3 2. Turn off BBRv3 (restarts)"
+ echo "1. Turn on BBRv3 2. Turn off BBRv3 (it will restart)"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
bbr_on
- send_stats "Alpine enable bbr3"
+ send_stats "alpine opens bbr3"
;;
2)
- sed -i '/net.ipv4.tcp_congestion_control=bbr/d' /etc/sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control=/d' /etc/sysctl.conf
sysctl -p
server_reboot
;;
@@ -6801,36 +8075,358 @@ linux_bbr() {
-linux_docker() {
+docker_ssh_migration() {
- while true; do
- clear
- # send_stats "docker management"
- echo -e "Docker Management"
- docker_tato
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Install and update Docker environment${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}2. ${gl_bai}View Docker global status${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}3. ${gl_bai}Docker container management${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}4. ${gl_bai}Docker image management"
- echo -e "${gl_kjlan}5. ${gl_bai}Docker Network Management"
- echo -e "${gl_kjlan}6. ${gl_bai}Docker volume management"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}7. ${gl_bai}Clean useless docker containers and mirror network data volumes"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}8. ${gl_bai}Replace Docker source"
- echo -e "${gl_kjlan}9. ${gl_bai}Edit daemon.json file"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}Enable Docker-ipv6 access"
- echo -e "${gl_kjlan}12. ${gl_bai}Close Docker-ipv6 access"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}20. ${gl_bai}Uninstall the Docker environment"
- echo -e "${gl_kjlan}------------------------"
+ is_compose_container() {
+ local container=$1
+ docker inspect "$container" | jq -e '.[0].Config.Labels["com.docker.compose.project"]' >/dev/null 2>&1
+ }
+
+ list_backups() {
+ local BACKUP_ROOT="/tmp"
+ echo -e "${gl_kjlan}Current backup list:${gl_bai}"
+ ls -1dt ${BACKUP_ROOT}/docker_backup_* 2>/dev/null || echo "No backup"
+ }
+
+
+
+ # ----------------------------
+ # backup
+ # ----------------------------
+ backup_docker() {
+ send_stats "Docker backup"
+
+ echo -e "${gl_kjlan}Backing up Docker containers...${gl_bai}"
+ docker ps --format '{{.Names}}'
+ read -e -p "Please enter the name of the container to be backed up (separate multiple spaces and press Enter to back up all running containers):" containers
+
+ install tar jq gzip
+ install_docker
+
+ local BACKUP_ROOT="/tmp"
+ local DATE_STR=$(date +%Y%m%d_%H%M%S)
+ local TARGET_CONTAINERS=()
+ if [ -z "$containers" ]; then
+ mapfile -t TARGET_CONTAINERS < <(docker ps --format '{{.Names}}')
+ else
+ read -ra TARGET_CONTAINERS <<< "$containers"
+ fi
+ [[ ${#TARGET_CONTAINERS[@]} -eq 0 ]] && { echo -e "${gl_hong}Container not found${gl_bai}"; return; }
+
+ local BACKUP_DIR="${BACKUP_ROOT}/docker_backup_${DATE_STR}"
+ mkdir -p "$BACKUP_DIR"
+
+ local RESTORE_SCRIPT="${BACKUP_DIR}/docker_restore.sh"
+ echo "#!/bin/bash" > "$RESTORE_SCRIPT"
+ echo "set -e" >> "$RESTORE_SCRIPT"
+ echo "# Automatically generated restore script" >> "$RESTORE_SCRIPT"
+
+ # Record the packaged Compose project path to avoid repeated packaging
+ declare -A PACKED_COMPOSE_PATHS=()
+
+ for c in "${TARGET_CONTAINERS[@]}"; do
+ echo -e "${gl_lv}Backup container:$c${gl_bai}"
+ local inspect_file="${BACKUP_DIR}/${c}_inspect.json"
+ docker inspect "$c" > "$inspect_file"
+
+ if is_compose_container "$c"; then
+ echo -e "${gl_kjlan}detected$cis a docker-compose container${gl_bai}"
+ local project_dir=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty')
+ local project_name=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty')
+
+ if [ -z "$project_dir" ]; then
+ read -e -p "The compose directory is not detected, please enter the path manually:" project_dir
+ fi
+
+ # If the Compose project has already been packaged, skip
+ if [[ -n "${PACKED_COMPOSE_PATHS[$project_dir]}" ]]; then
+ echo -e "${gl_huang}Compose project [$project_name] Already backed up, skip repeated packaging...${gl_bai}"
+ continue
+ fi
+
+ if [ -f "$project_dir/docker-compose.yml" ]; then
+ echo "compose" > "${BACKUP_DIR}/backup_type_${project_name}"
+ echo "$project_dir" > "${BACKUP_DIR}/compose_path_${project_name}.txt"
+ tar -czf "${BACKUP_DIR}/compose_project_${project_name}.tar.gz" -C "$project_dir" .
+ echo "# docker-compose restore:$project_name" >> "$RESTORE_SCRIPT"
+ echo "cd \"$project_dir\" && docker compose up -d" >> "$RESTORE_SCRIPT"
+ PACKED_COMPOSE_PATHS["$project_dir"]=1
+ echo -e "${gl_lv}Compose project [$project_name] Packaged:${project_dir}${gl_bai}"
+ else
+ echo -e "${gl_hong}docker-compose.yml not found, skipping this container...${gl_bai}"
+ fi
+ else
+ # Ordinary container backup volume
+ local VOL_PATHS
+ VOL_PATHS=$(docker inspect "$c" --format '{{range .Mounts}}{{.Source}} {{end}}')
+ for path in $VOL_PATHS; do
+ echo "Packing volume:$path"
+ tar -czpf "${BACKUP_DIR}/${c}_$(basename $path).tar.gz" -C / "$(echo $path | sed 's/^\///')"
+ done
+
+ # port
+ local PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[] | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$inspect_file" 2>/dev/null)
+ for p in "${PORTS[@]}"; do PORT_ARGS+="-p $p "; done
+
+ # environment variables
+ local ENV_VARS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[] | @sh' "$inspect_file")
+ for e in "${ENVS[@]}"; do ENV_VARS+="-e $e "; done
+
+ # volume mapping
+ local VOL_ARGS=""
+ for path in $VOL_PATHS; do VOL_ARGS+="-v $path:$path "; done
+
+ # Mirror
+ local IMAGE
+ IMAGE=$(jq -r '.[0].Config.Image' "$inspect_file")
+
+ echo -e "\n# Restore container:$c" >> "$RESTORE_SCRIPT"
+ echo "docker run -d --name $c $PORT_ARGS $VOL_ARGS $ENV_VARS $IMAGE" >> "$RESTORE_SCRIPT"
+ fi
+ done
+
+
+ # Back up all files under /home/docker (excluding subdirectories)
+ if [ -d "/home/docker" ]; then
+ echo -e "${gl_kjlan}Back up files under /home/docker...${gl_bai}"
+ find /home/docker -maxdepth 1 -type f | tar -czf "${BACKUP_DIR}/home_docker_files.tar.gz" -T -
+ echo -e "${gl_lv}Files under /home/docker have been packaged to:${BACKUP_DIR}/home_docker_files.tar.gz${gl_bai}"
+ fi
+
+ chmod +x "$RESTORE_SCRIPT"
+ echo -e "${gl_lv}Backup completed:${BACKUP_DIR}${gl_bai}"
+ echo -e "${gl_lv}Available restore scripts:${RESTORE_SCRIPT}${gl_bai}"
+
+
+ }
+
+ # ----------------------------
+ # reduction
+ # ----------------------------
+ restore_docker() {
+
+ send_stats "Docker restore"
+ read -e -p "Please enter the backup directory to be restored:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}The backup directory does not exist${gl_bai}"; return; }
+
+ echo -e "${gl_kjlan}Starting the restore operation...${gl_bai}"
+
+ install tar jq gzip
+ install_docker
+
+ # --------- Prioritize restoring Compose projects ---------
+ for f in "$BACKUP_DIR"/backup_type_*; do
+ [[ ! -f "$f" ]] && continue
+ if grep -q "compose" "$f"; then
+ project_name=$(basename "$f" | sed 's/backup_type_//')
+ path_file="$BACKUP_DIR/compose_path_${project_name}.txt"
+ [[ -f "$path_file" ]] && original_path=$(cat "$path_file") || original_path=""
+ [[ -z "$original_path" ]] && read -e -p "Original path not found, please enter the restore directory path:" original_path
+
+ # Check whether the container of the compose project is already running
+ running_count=$(docker ps --filter "label=com.docker.compose.project=$project_name" --format '{{.Names}}' | wc -l)
+ if [[ "$running_count" -gt 0 ]]; then
+ echo -e "${gl_huang}Compose project [$project_name] Containers are already running, skip restore...${gl_bai}"
+ continue
+ fi
+
+ read -e -p "Confirm to restore Compose project [$project_name] to path [$original_path] ? (y/n): " confirm
+ [[ "$confirm" != "y" ]] && read -e -p "Please enter a new restore path:" original_path
+
+ mkdir -p "$original_path"
+ tar -xzf "$BACKUP_DIR/compose_project_${project_name}.tar.gz" -C "$original_path"
+ echo -e "${gl_lv}Compose project [$project_name] has been extracted to:$original_path${gl_bai}"
+
+ cd "$original_path" || return
+ docker compose down || true
+ docker compose up -d
+ echo -e "${gl_lv}Compose project [$project_name] Restore completed!${gl_bai}"
+ fi
+ done
+
+ # --------- Continue to restore normal containers ---------
+ echo -e "${gl_kjlan}Check and restore normal Docker containers...${gl_bai}"
+ local has_container=false
+ for json in "$BACKUP_DIR"/*_inspect.json; do
+ [[ ! -f "$json" ]] && continue
+ has_container=true
+ container=$(basename "$json" | sed 's/_inspect.json//')
+ echo -e "${gl_lv}Processing container:$container${gl_bai}"
+
+ # Check if the container already exists and is running
+ if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}container [$container] already running, skipping restore...${gl_bai}"
+ continue
+ fi
+
+ IMAGE=$(jq -r '.[0].Config.Image' "$json")
+ [[ -z "$IMAGE" || "$IMAGE" == "null" ]] && { echo -e "${gl_hong}Mirror information not found, skip:$container${gl_bai}"; continue; }
+
+ # port mapping
+ PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$json")
+ for p in "${PORTS[@]}"; do
+ [[ -n "$p" ]] && PORT_ARGS="$PORT_ARGS -p $p"
+ done
+
+ # environment variables
+ ENV_ARGS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[]' "$json")
+ for e in "${ENVS[@]}"; do
+ ENV_ARGS="$ENV_ARGS -e \"$e\""
+ done
+
+ # Volume mapping + volume data recovery
+ VOL_ARGS=""
+ mapfile -t VOLS < <(jq -r '.[0].Mounts[] | "\(.Source):\(.Destination)"' "$json")
+ for v in "${VOLS[@]}"; do
+ VOL_SRC=$(echo "$v" | cut -d':' -f1)
+ VOL_DST=$(echo "$v" | cut -d':' -f2)
+ mkdir -p "$VOL_SRC"
+ VOL_ARGS="$VOL_ARGS -v $VOL_SRC:$VOL_DST"
+
+ VOL_FILE="$BACKUP_DIR/${container}_$(basename $VOL_SRC).tar.gz"
+ if [[ -f "$VOL_FILE" ]]; then
+ echo "Recover volume data:$VOL_SRC"
+ tar -xzf "$VOL_FILE" -C /
+ fi
+ done
+
+ # Delete existing but not running containers
+ if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}container [$container] exists but is not running, delete the old container...${gl_bai}"
+ docker rm -f "$container"
+ fi
+
+ # Start container
+ echo "Execute the restore command: docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ eval "docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ done
+
+ [[ "$has_container" == false ]] && echo -e "${gl_huang}No backup information for common containers found${gl_bai}"
+
+ # Restore files under /home/docker
+ if [ -f "$BACKUP_DIR/home_docker_files.tar.gz" ]; then
+ echo -e "${gl_kjlan}Restoring files under /home/docker...${gl_bai}"
+ mkdir -p /home/docker
+ tar -xzf "$BACKUP_DIR/home_docker_files.tar.gz" -C /
+ echo -e "${gl_lv}Files under /home/docker have been restored${gl_bai}"
+ else
+ echo -e "${gl_huang}The backup of the file under /home/docker was not found, skipping...${gl_bai}"
+ fi
+
+
+ }
+
+
+ # ----------------------------
+ # migrate
+ # ----------------------------
+ migrate_docker() {
+ send_stats "Docker migration"
+ install jq
+ read -e -p "Please enter the backup directory to be migrated:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}The backup directory does not exist${gl_bai}"; return; }
+
+ kj_ssh_read_host_user_port "Target server IP:" "Target server SSH username [default root]:" "Target server SSH port [default 22]:" "root" "22"
+ local TARGET_IP="$KJ_SSH_HOST"
+ local TARGET_USER="$KJ_SSH_USER"
+ local TARGET_PORT="$KJ_SSH_PORT"
+
+ local LATEST_TAR="$BACKUP_DIR"
+
+ echo -e "${gl_huang}Transferring backup...${gl_bai}"
+ if [[ -z "$TARGET_PASS" ]]; then
+ # Log in using key
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no -r "$LATEST_TAR" "$TARGET_USER@$TARGET_IP:/tmp/"
+ fi
+
+ }
+
+ # ----------------------------
+ # Delete backup
+ # ----------------------------
+ delete_backup() {
+ send_stats "Docker backup file deletion"
+ read -e -p "Please enter the backup directory to be deleted:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}The backup directory does not exist${gl_bai}"; return; }
+ rm -rf "$BACKUP_DIR"
+ echo -e "${gl_lv}Deleted backup:${BACKUP_DIR}${gl_bai}"
+ }
+
+ # ----------------------------
+ # Main menu
+ # ----------------------------
+ main_menu() {
+ send_stats "Docker backup migration restore"
+ while true; do
+ clear
+ echo "------------------------"
+ echo -e "Docker backup/migration/restore tool"
+ echo "------------------------"
+ list_backups
+ echo -e ""
+ echo "------------------------"
+ echo -e "1. Back up docker project"
+ echo -e "2. Migrate docker project"
+ echo -e "3. Restore docker project"
+ echo -e "4. Delete the backup file of the docker project"
+ echo "------------------------"
+ echo -e "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Please select:" choice
+ case $choice in
+ 1) backup_docker ;;
+ 2) migrate_docker ;;
+ 3) restore_docker ;;
+ 4) delete_backup ;;
+ 0) return ;;
+ *) echo -e "${gl_hong}Invalid option${gl_bai}" ;;
+ esac
+ break_end
+ done
+ }
+
+ main_menu
+}
+
+
+
+
+
+linux_docker() {
+
+ while true; do
+ clear
+ # send_stats "docker management"
+ echo -e "Docker management"
+ docker_tato
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}Install and update the Docker environment${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}2. ${gl_bai}View Docker global status${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}3. ${gl_bai}Docker container management${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}4. ${gl_bai}Docker image management"
+ echo -e "${gl_kjlan}5. ${gl_bai}Docker network management"
+ echo -e "${gl_kjlan}6. ${gl_bai}Docker volume management"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}7. ${gl_bai}Clean up useless docker containers and mirror network data volumes"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}8. ${gl_bai}Change Docker source"
+ echo -e "${gl_kjlan}9. ${gl_bai}Edit daemon.json file"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}11. ${gl_bai}Enable Docker-ipv6 access"
+ echo -e "${gl_kjlan}12. ${gl_bai}Turn off Docker-ipv6 access"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}19. ${gl_bai}Backup/migrate/restore Docker environment"
+ echo -e "${gl_kjlan}20. ${gl_bai}Uninstall the Docker environment"
+ echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
@@ -6846,7 +8442,7 @@ linux_docker() {
local network_count=$(docker network ls -q 2>/dev/null | wc -l)
local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
- send_stats "Docker global status"
+ send_stats "docker global status"
echo "Docker version"
docker -v
docker compose version
@@ -6858,10 +8454,10 @@ linux_docker() {
echo -e "Docker container:${gl_lv}$container_count${gl_bai}"
docker ps -a
echo ""
- echo -e "Docker volume:${gl_lv}$volume_count${gl_bai}"
+ echo -e "Docker volumes:${gl_lv}$volume_count${gl_bai}"
docker volume ls
echo ""
- echo -e "Docker Network:${gl_lv}$network_count${gl_bai}"
+ echo -e "Docker network:${gl_lv}$network_count${gl_bai}"
docker network ls
echo ""
@@ -6876,7 +8472,7 @@ linux_docker() {
5)
while true; do
clear
- send_stats "Docker Network Management"
+ send_stats "Docker network management"
echo "Docker network list"
echo "------------------------------------------------------------"
docker network ls
@@ -6884,7 +8480,7 @@ linux_docker() {
echo "------------------------------------------------------------"
container_ids=$(docker ps -q)
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "Container name" "network name" "IP address"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
@@ -6901,36 +8497,36 @@ linux_docker() {
done
echo ""
- echo "Network operation"
+ echo "network operations"
echo "------------------------"
echo "1. Create a network"
- echo "2. Join the Internet"
+ echo "2. Join the network"
echo "3. Exit the network"
- echo "4. Delete the network"
+ echo "4. Delete network"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- send_stats "Create a network"
- read -e -p "Set a new network name:" dockernetwork
+ send_stats "Create network"
+ read -e -p "Set new network name:" dockernetwork
docker network create $dockernetwork
;;
2)
- send_stats "Join the Internet"
- read -e -p "Join the network name:" dockernetwork
- read -e -p "Those containers are added to the network (multiple container names are separated by spaces):" dockernames
+ send_stats "Join the network"
+ read -e -p "Add network name:" dockernetwork
+ read -e -p "Which containers join the network (please separate multiple container names with spaces):" dockernames
for dockername in $dockernames; do
docker network connect $dockernetwork $dockername
done
;;
3)
- send_stats "Join the Internet"
+ send_stats "Join the network"
read -e -p "Exit network name:" dockernetwork
- read -e -p "Those containers exit the network (multiple container names are separated by spaces):" dockernames
+ read -e -p "Those containers exit the network (please separate multiple container names with spaces):" dockernames
for dockername in $dockernames; do
docker network disconnect $dockernetwork $dockername
@@ -6939,8 +8535,8 @@ linux_docker() {
;;
4)
- send_stats "Delete the network"
- read -e -p "Please enter the network name to delete:" dockernetwork
+ send_stats "delete network"
+ read -e -p "Please enter the network name to be deleted:" dockernetwork
docker network rm $dockernetwork
;;
@@ -6958,7 +8554,7 @@ linux_docker() {
echo "Docker volume list"
docker volume ls
echo ""
- echo "Volume operation"
+ echo "Volume operations"
echo "------------------------"
echo "1. Create a new volume"
echo "2. Delete the specified volume"
@@ -6966,12 +8562,12 @@ linux_docker() {
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- send_stats "Create a new volume"
- read -e -p "Set the new volume name:" dockerjuan
+ send_stats "Create new volume"
+ read -e -p "Set new volume name:" dockerjuan
docker volume create $dockerjuan
;;
@@ -7007,7 +8603,7 @@ linux_docker() {
;;
7)
clear
- send_stats "Docker cleaning"
+ send_stats "Docker cleanup"
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}将清理无用的镜像容器网络,包括停止的容器,确定清理吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -7033,18 +8629,26 @@ linux_docker() {
restart docker
;;
+
+
+
11)
clear
- send_stats "Docker v6 open"
+ send_stats "Docker v6 on"
docker_ipv6_on
;;
12)
clear
- send_stats "Docker v6 level"
+ send_stats "Docker v6 Close"
docker_ipv6_off
;;
+ 19)
+ docker_ssh_migration
+ ;;
+
+
20)
clear
send_stats "Docker uninstall"
@@ -7089,35 +8693,36 @@ linux_test() {
echo -e "Test script collection"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}IP and unlock status detection"
- echo -e "${gl_kjlan}1. ${gl_bai}ChatGPT Unlock Status Detection"
+ echo -e "${gl_kjlan}1. ${gl_bai}ChatGPT unlock status detection"
echo -e "${gl_kjlan}2. ${gl_bai}Region streaming media unlock test"
echo -e "${gl_kjlan}3. ${gl_bai}yeahwu streaming media unlock detection"
- echo -e "${gl_kjlan}4. ${gl_bai}xykt IP quality physical examination script${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}4. ${gl_bai}xykt IP quality check script${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}Network speed measurement"
+ echo -e "${gl_kjlan}Network line speed test"
echo -e "${gl_kjlan}11. ${gl_bai}besttrace three network backhaul delay routing test"
- echo -e "${gl_kjlan}12. ${gl_bai}mtr_trace Three-network backhaul line test"
- echo -e "${gl_kjlan}13. ${gl_bai}Superspeed three-net speed measurement"
+ echo -e "${gl_kjlan}12. ${gl_bai}mtr_trace triple network backhaul line test"
+ echo -e "${gl_kjlan}13. ${gl_bai}Superspeed triple network speed test"
echo -e "${gl_kjlan}14. ${gl_bai}nxtrace fast backhaul test script"
- echo -e "${gl_kjlan}15. ${gl_bai}nxtrace Specifies IP backhaul test script"
- echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 three-network line test"
- echo -e "${gl_kjlan}17. ${gl_bai}i-abc multifunction speed test script"
- echo -e "${gl_kjlan}18. ${gl_bai}NetQuality Network Quality Physical Examination Script${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}15. ${gl_bai}nxtrace specifies IP backhaul test script"
+ echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 three network line test"
+ echo -e "${gl_kjlan}17. ${gl_bai}i-abc multi-function speed test script"
+ echo -e "${gl_kjlan}18. ${gl_bai}NetQuality network quality check script${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}Hardware performance testing"
- echo -e "${gl_kjlan}21. ${gl_bai}yabs performance testing"
- echo -e "${gl_kjlan}22. ${gl_bai}iicu/gb5 CPU performance test script"
+ echo -e "${gl_kjlan}21. ${gl_bai}yabs performance test"
+ echo -e "${gl_kjlan}22. ${gl_bai}icu/gb5 CPU performance test script"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}Comprehensive test"
+ echo -e "${gl_kjlan}Comprehensive testing"
echo -e "${gl_kjlan}31. ${gl_bai}bench performance test"
- echo -e "${gl_kjlan}32. ${gl_bai}Spiritysdx Fusion Monster Review${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}32. ${gl_bai}spiritysdx fusion monster evaluation${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}nodequality fusion monster evaluation${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
@@ -7138,25 +8743,25 @@ linux_test() {
;;
4)
clear
- send_stats "xykt_IP quality physical examination script"
+ send_stats "xykt_IP quality check script"
bash <(curl -Ls IP.Check.Place)
;;
11)
clear
- send_stats "Besttrace three network backhaul delay routing test"
+ send_stats "besttrace triple network backhaul delay routing test"
install wget
wget -qO- git.io/besttrace | bash
;;
12)
clear
- send_stats "mtr_trace three network return line test"
+ send_stats "mtr_trace triple network backhaul line test"
curl ${gh_proxy}raw.githubusercontent.com/zhucaidan/mtr_trace/main/mtr_trace.sh | bash
;;
13)
clear
- send_stats "Superspeed three-net speed measurement"
+ send_stats "Superspeed triple network speed test"
bash <(curl -Lso- https://git.io/superspeed_uxh)
;;
14)
@@ -7168,7 +8773,7 @@ linux_test() {
15)
clear
send_stats "nxtrace specifies IP backhaul test script"
- echo "List of IPs that can be referenced"
+ echo "Reference IP list"
echo "------------------------"
echo "Beijing Telecom: 219.141.136.12"
echo "Beijing Unicom: 202.106.50.1"
@@ -7177,30 +8782,30 @@ linux_test() {
echo "Shanghai Unicom: 210.22.97.1"
echo "Shanghai Mobile: 211.136.112.200"
echo "Guangzhou Telecom: 58.60.188.222"
- echo "Guangzhou Unicom: 210.21.196.6"
+ echo "Guangzhou China Unicom: 210.21.196.6"
echo "Guangzhou Mobile: 120.196.165.24"
echo "Chengdu Telecom: 61.139.2.69"
- echo "Chengdu Unicom: 119.6.6.6"
+ echo "Chengdu China Unicom: 119.6.6.6"
echo "Chengdu Mobile: 211.137.96.205"
echo "Hunan Telecom: 36.111.200.100"
echo "Hunan Unicom: 42.48.16.100"
echo "Hunan Mobile: 39.134.254.6"
echo "------------------------"
- read -e -p "Enter a specified IP:" testip
+ read -e -p "Enter a specific IP:" testip
curl nxtrace.org/nt |bash
nexttrace $testip
;;
16)
clear
- send_stats "ludashi2020 three-network line test"
+ send_stats "ludashi2020 three network line test"
curl ${gh_proxy}raw.githubusercontent.com/ludashi2020/backtrace/main/install.sh -sSf | sh
;;
17)
clear
- send_stats "i-abc multifunction speed test script"
+ send_stats "i-abc multifunctional speed test script"
bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/i-abc/Speedtest/main/speedtest.sh)
;;
@@ -7212,13 +8817,13 @@ linux_test() {
21)
clear
- send_stats "yabs performance testing"
+ send_stats "yabs performance test"
check_swap
curl -sL yabs.sh | bash -s -- -i -5
;;
22)
clear
- send_stats "iicu/gb5 CPU performance test script"
+ send_stats "icu/gb5 CPU performance test script"
check_swap
bash <(curl -sL bash.icu/gb5)
;;
@@ -7229,11 +8834,19 @@ linux_test() {
curl -Lso- bench.sh | bash
;;
32)
- send_stats "Spiritysdx Fusion Monster Review"
+ send_stats "spiritysdx fusion monster review"
clear
- curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ curl -L ${gh_proxy}github.com/spiritLHLS/ecs/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
;;
+ 33)
+ send_stats "nodequality fusion monster evaluation"
+ clear
+ bash <(curl -sL https://run.NodeQuality.com)
+ ;;
+
+
+
0)
kejilion
@@ -7259,48 +8872,48 @@ linux_Oracle() {
echo -e "Oracle Cloud Script Collection"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}Install idle machine active script"
- echo -e "${gl_kjlan}2. ${gl_bai}Uninstall idle machine active script"
+ echo -e "${gl_kjlan}2. ${gl_bai}Uninstall active scripts from idle machines"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}3. ${gl_bai}DD reinstall system script"
- echo -e "${gl_kjlan}4. ${gl_bai}Detective R start script"
- echo -e "${gl_kjlan}5. ${gl_bai}Turn on ROOT password login mode"
+ echo -e "${gl_kjlan}4. ${gl_bai}Detective R startup script"
+ echo -e "${gl_kjlan}5. ${gl_bai}Enable ROOT password login mode"
echo -e "${gl_kjlan}6. ${gl_bai}IPV6 recovery tool"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
clear
- echo "Active script: CPU occupies 10-20% memory occupies 20%"
- read -e -p "Are you sure to install it? (Y/N):" choice
+ echo "Active script: CPU usage 10-20% Memory usage 20%"
+ read -e -p "Are you sure you want to install it? (Y/N):" choice
case "$choice" in
[Yy])
install_docker
- # Set default values
+ # Set default value
local DEFAULT_CPU_CORE=1
local DEFAULT_CPU_UTIL="10-20"
local DEFAULT_MEM_UTIL=20
local DEFAULT_SPEEDTEST_INTERVAL=120
- # Prompt the user to enter the number of CPU cores and occupancy percentage, and if entered, use the default value.
- read -e -p "Please enter the number of CPU cores [default:$DEFAULT_CPU_CORE]: " cpu_core
+ # Prompts the user to enter the number of CPU cores and occupancy percentage. If the user presses Enter, the default value will be used.
+ read -e -p "Please enter the number of CPU cores [Default:$DEFAULT_CPU_CORE]: " cpu_core
local cpu_core=${cpu_core:-$DEFAULT_CPU_CORE}
- read -e -p "Please enter the CPU usage percentage range (for example, 10-20) [Default:$DEFAULT_CPU_UTIL]: " cpu_util
+ read -e -p "Please enter the CPU usage percentage range (e.g. 10-20) [Default:$DEFAULT_CPU_UTIL]: " cpu_util
local cpu_util=${cpu_util:-$DEFAULT_CPU_UTIL}
- read -e -p "Please enter the memory usage percentage [default:$DEFAULT_MEM_UTIL]: " mem_util
+ read -e -p "Please enter the memory usage percentage [Default:$DEFAULT_MEM_UTIL]: " mem_util
local mem_util=${mem_util:-$DEFAULT_MEM_UTIL}
- read -e -p "Please enter the Speedtest interval time (seconds) [default:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
+ read -e -p "Please enter Speedtest interval time (seconds) [Default:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
local speedtest_interval=${speedtest_interval:-$DEFAULT_SPEEDTEST_INTERVAL}
# Run Docker container
- docker run -itd --name=lookbusy --restart=always \
+ docker run -d --name=lookbusy --restart=always \
-e TZ=Asia/Shanghai \
-e CPU_UTIL="$cpu_util" \
-e CPU_CORE="$cpu_core" \
@@ -7329,13 +8942,13 @@ linux_Oracle() {
clear
echo "Reinstall the system"
echo "--------------------------------"
- echo -e "${gl_hong}Notice:${gl_bai}Reinstallation is risky to lose contact, and those who are worried should use it with caution. Reinstallation is expected to take 15 minutes, please back up the data in advance."
- read -e -p "Are you sure to continue? (Y/N):" choice
+ echo -e "${gl_hong}Notice:${gl_bai}Reinstalling may cause loss of connection, so use with caution if you are worried. Reinstallation is expected to take 15 minutes, please back up your data in advance."
+ read -e -p "Are you sure you want to continue? (Y/N):" choice
case "$choice" in
[Yy])
while true; do
- read -e -p "Please select the system to reinstall: 1. Debian12 | 2. Ubuntu20.04:" sys_choice
+ read -e -p "Please select the system you want to reinstall: 1. Debian12 | 2. Ubuntu20.04:" sys_choice
case "$sys_choice" in
1)
@@ -7352,10 +8965,10 @@ linux_Oracle() {
esac
done
- read -e -p "Please enter your reinstalled password:" vpspasswd
+ read -e -p "Please enter your password after reinstallation:" vpspasswd
install wget
bash <(wget --no-check-certificate -qO- "${gh_proxy}raw.githubusercontent.com/MoeClub/Note/master/InstallNET.sh") $xitong -v 64 -p $vpspasswd -port 22
- send_stats "Oracle Cloud Reinstall System Script"
+ send_stats "Oracle Cloud reinstall system script"
;;
[Nn])
echo "Canceled"
@@ -7368,18 +8981,18 @@ linux_Oracle() {
4)
clear
- echo "This feature is in the development stage, so stay tuned!"
+ send_stats "Detective R startup script"
+ bash <(wget -qO- ${gh_proxy}github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh)
;;
5)
clear
add_sshpasswd
-
;;
6)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "This function is provided by the master jhb, thanks to him!"
- send_stats "ipv6 fix"
+ echo "This function is provided by jhb, thank him!"
+ send_stats "ipv6 repair"
;;
0)
kejilion
@@ -7398,6 +9011,9 @@ linux_Oracle() {
}
+
+
+
docker_tato() {
local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
@@ -7415,20 +9031,20 @@ docker_tato() {
ldnmp_tato() {
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
-local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml 2>/dev/null | tr -d '[:space:]')
if [ -n "$dbrootpasswd" ]; then
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
fi
-local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+local db_output="${gl_lv}${db_count}${gl_bai}"
if command -v docker &>/dev/null; then
if docker ps --filter "name=nginx" --filter "status=running" | grep -q nginx; then
echo -e "${gl_huang}------------------------"
- echo -e "${gl_lv}The environment is installed${gl_bai} $output $db_output"
+ echo -e "${gl_lv}Environment is installed${gl_bai}Site:$outputdatabase:$db_output"
fi
fi
@@ -7461,26 +9077,26 @@ linux_ldnmp() {
ldnmp_tato
echo -e "${gl_huang}------------------------"
echo -e "${gl_huang}1. ${gl_bai}Install LDNMP environment${gl_huang}★${gl_bai} ${gl_huang}2. ${gl_bai}Install WordPress${gl_huang}★${gl_bai}"
- echo -e "${gl_huang}3. ${gl_bai}Install Discuz Forum${gl_huang}4. ${gl_bai}Install the Kadao Cloud Desktop"
- echo -e "${gl_huang}5. ${gl_bai}Install Apple CMS Film and Television Station${gl_huang}6. ${gl_bai}Install a Unicorn Digital Card Network"
- echo -e "${gl_huang}7. ${gl_bai}Install the flarum forum website${gl_huang}8. ${gl_bai}Install typecho lightweight blog website"
- echo -e "${gl_huang}9. ${gl_bai}Install LinkStack Shared Link Platform${gl_huang}20. ${gl_bai}Customize dynamic site"
+ echo -e "${gl_huang}3. ${gl_bai}Install Discuz Forum${gl_huang}4. ${gl_bai}Install Kedao Cloud Desktop"
+ echo -e "${gl_huang}5. ${gl_bai}Install Apple CMS Movie and TV Station${gl_huang}6. ${gl_bai}Install Unicorn Digital Card Network"
+ echo -e "${gl_huang}7. ${gl_bai}Install flarum forum website${gl_huang}8. ${gl_bai}Install typecho lightweight blog website"
+ echo -e "${gl_huang}9. ${gl_bai}Install LinkStack sharing link platform${gl_huang}20. ${gl_bai}Custom dynamic site"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}21. ${gl_bai}Install nginx only${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}Site redirection"
- echo -e "${gl_huang}23. ${gl_bai}Site reverse proxy-IP+port${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}Site reverse proxy - domain name"
- echo -e "${gl_huang}25. ${gl_bai}Install Bitwarden password management platform${gl_huang}26. ${gl_bai}Install Halo Blog Website"
- echo -e "${gl_huang}27. ${gl_bai}Install AI Painting Prompt Word Generator${gl_huang}28. ${gl_bai}Site reverse proxy-load balancing"
- echo -e "${gl_huang}30. ${gl_bai}Customize static site"
+ echo -e "${gl_huang}21. ${gl_bai}Only install nginx${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}site redirect"
+ echo -e "${gl_huang}23. ${gl_bai}Site reverse proxy-IP+port${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}Site reverse proxy-domain name"
+ echo -e "${gl_huang}25. ${gl_bai}Install Bitwarden Password Management Platform${gl_huang}26. ${gl_bai}Install Halo Blog Site"
+ echo -e "${gl_huang}27. ${gl_bai}Install the AI painting prompt word generator${gl_huang}28. ${gl_bai}Site reverse proxy-load balancing"
+ echo -e "${gl_huang}29. ${gl_bai}Stream four-layer proxy forwarding${gl_huang}30. ${gl_bai}Custom static site"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}31. ${gl_bai}Site data management${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}Back up the entire site data"
- echo -e "${gl_huang}33. ${gl_bai}Timed remote backup${gl_huang}34. ${gl_bai}Restore the entire site data"
+ echo -e "${gl_huang}31. ${gl_bai}Site data management${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}Back up site-wide data"
+ echo -e "${gl_huang}33. ${gl_bai}Scheduled remote backup${gl_huang}34. ${gl_bai}Restore whole site data"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}35. ${gl_bai}Protect LDNMP environment${gl_huang}36. ${gl_bai}Optimize LDNMP environment"
- echo -e "${gl_huang}37. ${gl_bai}Update LDNMP environment${gl_huang}38. ${gl_bai}Uninstall LDNMP environment"
+ echo -e "${gl_huang}35. ${gl_bai}Protect LDNMP environments${gl_huang}36. ${gl_bai}Optimize LDNMP environment"
+ echo -e "${gl_huang}37. ${gl_bai}Update LDNMP environment${gl_huang}38. ${gl_bai}Uninstall the LDNMP environment"
echo -e "${gl_huang}------------------------"
echo -e "${gl_huang}0. ${gl_bai}Return to main menu"
echo -e "${gl_huang}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
@@ -7495,25 +9111,33 @@ linux_ldnmp() {
3)
clear
# Discuz Forum
- webname="Discuz论坛"
+ webname="Discuz Forum"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/discuz.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/main/Discuz_X3.5_SC_UTF8_20240520.zip
- unzip latest.zip
+ LATEST_URL=$(curl -s https://api.gitee.com/api/v5/repos/Discuz/DiscuzX/releases/latest | grep -o 'https://[^"]*Discuz_X[^"]*SC_UTF8[^"]*\.zip' | head -n 1)
+ wget -O latest.zip ${LATEST_URL}
+ unzip -q latest.zip
+ mv upload/* .
+ rm -rf upload readme readme.html utility.html LICENSE qqqun.png
rm latest.zip
restart_ldnmp
@@ -7531,25 +9155,29 @@ linux_ldnmp() {
4)
clear
- # Kedao Cloud Desktop
- webname="可道云桌面"
+ # Kedao cloud desktop
+ webname="Kedao cloud desktop"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/kdy.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/1.50.02.zip
+ LATEST_VERSION=$(curl -s https://api.github.com/repos/kalcaddle/kodbox/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/${LATEST_VERSION}.zip
unzip -o latest.zip
rm latest.zip
mv /home/web/html/$yuming/kodbox* /home/web/html/$yuming/kodbox
@@ -7566,19 +9194,24 @@ linux_ldnmp() {
5)
clear
- # Apple CMS
- webname="苹果CMS"
+ # AppleCMS
+ webname="AppleCMS"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/maccms.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7602,26 +9235,32 @@ linux_ldnmp() {
echo "password:$dbusepasswd"
echo "Database prefix: mac_"
echo "------------------------"
- echo "Log in to the background address after installation is successful"
+ echo "After successful installation, log in to the backend address"
echo "https://$yuming/vip.php"
;;
6)
clear
- # One-legged counting card
- webname="独脚数卡"
+ # One-legged number card
+ webname="One-legged number card"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/dujiaoka.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7640,36 +9279,42 @@ linux_ldnmp() {
echo "password:$dbusepasswd"
echo ""
echo "redis address: redis"
- echo "Redis password: Not filled in by default"
- echo "Redis port: 6379"
+ echo "redis password: not filled in by default"
+ echo "redis port: 6379"
echo ""
echo "Website url: https://$yuming"
- echo "Background login path: /admin"
+ echo "Backend login path: /admin"
echo "------------------------"
echo "Username: admin"
echo "Password: admin"
echo "------------------------"
- echo "If red error0 appears in the upper right corner when logging in, please use the following command:"
- echo "I am also very angry that the unicorn number card is so troublesome, and there will be such problems!"
+ echo "If a red error0 appears in the upper right corner when logging in, please use the following command:"
+ echo "I am also very angry about why the Unicorn Number Card is so troublesome and has such problems!"
echo "sed -i 's/ADMIN_HTTPS=false/ADMIN_HTTPS=true/g' /home/web/html/$yuming/dujiaoka/.env"
;;
7)
clear
- # Flarum Forum
- webname="flarum论坛"
+ # flarum forum
+ webname="flarum forum"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/flarum.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec php rm -f /usr/local/etc/php/conf.d/optimized_php.ini
@@ -7690,9 +9335,13 @@ linux_ldnmp() {
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/sitemap"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/oauth"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/best-answer:*"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/upload"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/gamification"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/byobu:*"
docker exec php sh -c "cd /var/www/html/$yuming && composer require v17development/flarum-seo"
docker exec php sh -c "cd /var/www/html/$yuming && composer require clarkwinkelmann/flarum-ext-emojionearea"
+
restart_ldnmp
@@ -7702,7 +9351,7 @@ linux_ldnmp() {
echo "username:$dbuse"
echo "password:$dbusepasswd"
echo "Table prefix: flarum_"
- echo "Administrator information is set by yourself"
+ echo "Administrator information can be set by oneself"
;;
@@ -7715,12 +9364,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/typecho.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7753,13 +9408,17 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/refs/heads/main/index_php.conf
sed -i "s|/var/www/html/yuming.com/|/var/www/html/yuming.com/linkstack|g" /home/web/conf.d/$yuming.conf
sed -i "s|yuming.com|$yuming|g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7783,18 +9442,21 @@ linux_ldnmp() {
20)
clear
- webname="PHP动态站点"
+ webname="PHP dynamic site"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/index_php.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7804,8 +9466,8 @@ linux_ldnmp() {
clear
echo -e "[${gl_huang}1/6${gl_bai}] Upload PHP source code"
echo "-------------"
- echo "Currently, only zip-format source code packages are allowed. Please put the source code packages in /home/web/html/${yuming}In the directory"
- read -e -p "You can also enter the download link to remotely download the source code package. Directly press Enter to skip remote download:" url_download
+ echo "Currently, only source code packages in zip format are allowed to be uploaded. Please put the source code packages in /home/web/html/${yuming}under directory"
+ read -e -p "You can also enter the download link to download the source code package remotely. Press Enter directly to skip the remote download:" url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -7820,15 +9482,15 @@ linux_ldnmp() {
# find "$(realpath .)" -name "index.php" -print
find "$(realpath .)" -name "index.php" -print | xargs -I {} dirname {}
- read -e -p "Please enter the path of index.php, similar to (/home/web/html/$yuming/wordpress/): " index_lujing
+ read -e -p "Please enter the path to index.php, similar to (/home/web/html/$yuming/wordpress/): " index_lujing
sed -i "s#root /var/www/html/$yuming/#root $index_lujing#g" /home/web/conf.d/$yuming.conf
sed -i "s#/home/web/#/var/www/#g" /home/web/conf.d/$yuming.conf
clear
- echo -e "[${gl_huang}3/6${gl_bai}] Please select the PHP version"
+ echo -e "[${gl_huang}3/6${gl_bai}] Please select PHP version"
echo "-------------"
- read -e -p "1. The latest version of php | 2. php7.4:" pho_v
+ read -e -p "1. php latest version | 2. php7.4:" pho_v
case "$pho_v" in
1)
sed -i "s#php:9000#php:9000#g" /home/web/conf.d/$yuming.conf
@@ -7845,7 +9507,7 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}4/6${gl_bai}] Install the specified extension"
+ echo -e "[${gl_huang}4/6${gl_bai}] Install specified extension"
echo "-------------"
echo "Installed extensions"
docker exec php php -m
@@ -7859,14 +9521,14 @@ linux_ldnmp() {
clear
echo -e "[${gl_huang}5/6${gl_bai}] Edit site configuration"
echo "-------------"
- echo "Press any key to continue, and you can set the site configuration in detail, such as pseudo-static contents, etc."
+ echo "Press any key to continue. You can set the site configuration in detail, such as pseudo-static content."
read -n 1 -s -r -p ""
install nano
nano /home/web/conf.d/$yuming.conf
clear
- echo -e "[${gl_huang}6/6${gl_bai}] Database Management"
+ echo -e "[${gl_huang}6/6${gl_bai}] Database management"
echo "-------------"
read -e -p "1. I build a new site 2. I build an old site and have a database backup:" use_db
case $use_db in
@@ -7874,8 +9536,8 @@ linux_ldnmp() {
echo
;;
2)
- echo "The database backup must be a .gz-end compressed package. Please put it in the /home/ directory to support the import of backup data of Pagoda/1panel."
- read -e -p "You can also enter the download link to remotely download the backup data. Directly press Enter will skip remote download:" url_download_db
+ echo "Database backup must be a compressed package ending in .gz. Please put it in the /home/ directory to support the import of Pagoda/1panel backup data."
+ read -e -p "You can also enter the download link to download the backup data remotely. Press Enter directly to skip the remote download:" url_download_db
cd /home/
if [ -n "$url_download_db" ]; then
@@ -7885,7 +9547,7 @@ linux_ldnmp() {
latest_sql=$(ls -t *.sql | head -n 1)
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname < "/home/$latest_sql"
- echo "Database import table data"
+ echo "Database imported table data"
docker exec -i mysql mysql -u root -p"$dbrootpasswd" -e "USE $dbname; SHOW TABLES;"
rm -f *.sql
echo "Database import completed"
@@ -7917,18 +9579,23 @@ linux_ldnmp() {
22)
clear
- webname="站点重定向"
+ webname="site redirect"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
- read -e -p "Please enter the jump domain name:" reverseproxy
+ read -e -p "Please enter the redirect domain name:" reverseproxy
nginx_install_status
+
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/rewrite.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s/baidu.com/$reverseproxy/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7943,9 +9610,10 @@ linux_ldnmp() {
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "IP+ ports have been blocked from accessing the service"
+ echo "IP+port has been blocked from accessing the service"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
@@ -7953,19 +9621,23 @@ linux_ldnmp() {
24)
clear
- webname="反向代理-域名"
+ webname="Reverse proxy-domain name"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
echo -e "Domain name format:${gl_huang}google.com${gl_bai}"
- read -e -p "Please enter your anti-generation domain name:" fandai_yuming
+ read -e -p "Please enter your reverse proxy domain name:" fandai_yuming
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-domain.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s|fandaicom|$fandai_yuming|g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7981,20 +9653,17 @@ linux_ldnmp() {
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
docker run -d \
--name bitwarden \
- --restart always \
+ --restart=always \
-p 3280:80 \
-v /home/web/html/$yuming/bitwarden/data:/data \
vaultwarden/server
+
duankou=3280
- reverse_proxy
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
- nginx_web_on
;;
@@ -8004,30 +9673,29 @@ linux_ldnmp() {
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
- docker run -d --name halo --restart always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- duankou=8010
- reverse_proxy
+ docker run -d --name halo --restart=always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- nginx_web_on
+ duankou=8010
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
;;
27)
clear
- webname="AI绘画提示词生成器"
+ webname="AI painting prompt word generator"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8050,19 +9718,26 @@ linux_ldnmp() {
;;
+ 29)
+ stream_panel
+ ;;
+
30)
clear
- webname="静态站点"
+ webname="static site"
send_stats "Install$webname"
echo "Start deployment$webname"
add_yuming
repeat_add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8073,8 +9748,8 @@ linux_ldnmp() {
clear
echo -e "[${gl_huang}1/2${gl_bai}] Upload static source code"
echo "-------------"
- echo "Currently, only zip-format source code packages are allowed. Please put the source code packages in /home/web/html/${yuming}In the directory"
- read -e -p "You can also enter the download link to remotely download the source code package. Directly press Enter to skip remote download:" url_download
+ echo "Currently, only source code packages in zip format are allowed to be uploaded. Please put the source code packages in /home/web/html/${yuming}under directory"
+ read -e -p "You can also enter the download link to download the source code package remotely. Press Enter directly to skip the remote download:" url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -8117,25 +9792,23 @@ linux_ldnmp() {
send_stats "LDNMP environment backup"
local backup_filename="web_$(date +"%Y%m%d%H%M%S").tar.gz"
- echo -e "${gl_huang}Backing up$backup_filename ...${gl_bai}"
+ echo -e "${gl_kjlan}Backing up$backup_filename ...${gl_bai}"
cd /home/ && tar czvf "$backup_filename" web
while true; do
clear
- echo "The backup file has been created: /home/$backup_filename"
+ echo "Backup file created: /home/$backup_filename"
read -e -p "Do you want to transfer backup data to a remote server? (Y/N):" choice
case "$choice" in
[Yy])
- read -e -p "Please enter the remote server IP:" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "Error: Please enter the remote server IP."
- continue
- fi
+ kj_ssh_read_host_port "Please enter the remote server IP:" "Target server SSH port [default 22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
local latest_tar=$(ls -t /home/*.tar.gz | head -1)
if [ -n "$latest_tar" ]; then
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 添加等待时间
- scp -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
echo "The file has been transferred to the remote server home directory."
else
echo "The file to be transferred was not found."
@@ -8154,7 +9827,7 @@ linux_ldnmp() {
33)
clear
- send_stats "Timed remote backup"
+ send_stats "Scheduled remote backup"
read -e -p "Enter the remote server IP:" useip
read -e -p "Enter the remote server password:" usepasswd
@@ -8167,17 +9840,17 @@ linux_ldnmp() {
echo "------------------------"
echo "1. Weekly backup 2. Daily backup"
- read -e -p "Please enter your selection:" dingshi
+ read -e -p "Please enter your choice:" dingshi
case $dingshi in
1)
check_crontab_installed
- read -e -p "Select the day of the week for your weekly backup (0-6, 0 represents Sunday):" weekday
+ read -e -p "Select the day of the week for weekly backup (0-6, 0 represents Sunday):" weekday
(crontab -l ; echo "0 0 * * $weekday ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
2)
check_crontab_installed
- read -e -p "Select the time for daily backup (hours, 0-23):" hour
+ read -e -p "Select daily backup time (hour, 0-23):" hour
(crontab -l ; echo "0 $hour * * * ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
*)
@@ -8196,14 +9869,14 @@ linux_ldnmp() {
echo "-------------------------"
ls -lt /home/*.gz | awk '{print $NF}'
echo ""
- read -e -p "Enter to restore the latest backup, enter the backup file name to restore the specified backup, enter 0 to exit:" filename
+ read -e -p "Press the Enter key to restore the latest backup, enter the backup file name to restore the specified backup, enter 0 to exit:" filename
if [ "$filename" == "0" ]; then
break_end
linux_ldnmp
fi
- # If the user does not enter the file name, use the latest compressed package
+ # If the user does not enter a file name, the latest compressed package is used
if [ -z "$filename" ]; then
local filename=$(ls -t /home/*.tar.gz | head -1)
fi
@@ -8213,16 +9886,15 @@ linux_ldnmp() {
docker compose down > /dev/null 2>&1
rm -rf /home/web > /dev/null 2>&1
- echo -e "${gl_huang}Decompression is being done$filename ...${gl_bai}"
+ echo -e "${gl_kjlan}Unzipping$filename ...${gl_bai}"
cd /home/ && tar -xzf "$filename"
- check_port
install_dependency
install_docker
install_certbot
install_ldnmp
else
- echo "No compression package was found."
+ echo "No compressed package found."
fi
;;
@@ -8244,7 +9916,7 @@ linux_ldnmp() {
echo "Update LDNMP environment"
echo "------------------------"
ldnmp_v
- echo "Discover new version of components"
+ echo "New version of component found"
echo "------------------------"
check_docker_image_update nginx
if [ -n "$update_status" ]; then
@@ -8270,7 +9942,7 @@ linux_ldnmp() {
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
nginx_upgrade
@@ -8279,7 +9951,7 @@ linux_ldnmp() {
2)
local ldnmp_pods="mysql"
- read -e -p "Please enter${ldnmp_pods}Version number (such as: 8.0 8.3 8.4 9.0) (Enter to get the latest version):" version
+ read -e -p "Please enter${ldnmp_pods}Version number (such as: 8.0 8.3 8.4 9.0) (press enter to get the latest version):" version
local version=${version:-latest}
cd /home/web/
@@ -8296,7 +9968,7 @@ linux_ldnmp() {
;;
3)
local ldnmp_pods="php"
- read -e -p "Please enter${ldnmp_pods}Version number (such as: 7.4 8.0 8.1 8.2 8.3) (Enter to get the latest version):" version
+ read -e -p "Please enter${ldnmp_pods}Version number (such as: 7.4 8.0 8.1 8.2 8.3) (press enter to get the latest version):" version
local version=${version:-8.3}
cd /home/web/
cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
@@ -8340,7 +10012,6 @@ linux_ldnmp() {
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods > /dev/null 2>&1
- restart_redis
send_stats "renew$ldnmp_pods"
echo "renew${ldnmp_pods}Finish"
@@ -8349,11 +10020,10 @@ linux_ldnmp() {
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}长时间不更新环境的用户,请慎重更新LDNMP环境,会有数据库更新失败的风险。确定更新LDNMP环境吗?(Y/N): ")" choice
case "$choice" in
[Yy])
- send_stats "Completely update the LDNMP environment"
+ send_stats "Complete update of LDNMP environment"
cd /home/web/
docker compose down --rmi all
- check_port
install_dependency
install_docker
install_certbot
@@ -8375,7 +10045,7 @@ linux_ldnmp() {
38)
root_use
- send_stats "Uninstall LDNMP environment"
+ send_stats "Uninstall the LDNMP environment"
read -e -p "$(echo -e "${gl_hong}强烈建议:${gl_bai}先备份全部网站数据,再卸载LDNMP环境。确定删除所有网站数据吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -8409,2266 +10079,9413 @@ linux_ldnmp() {
-linux_panel() {
- while true; do
- clear
- # send_stats "App Market"
- echo -e "Application Market"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Official version of Baota Panel${gl_kjlan}2. ${gl_bai}aaPanel International Edition"
- echo -e "${gl_kjlan}3. ${gl_bai}1Panel new generation management panel${gl_kjlan}4. ${gl_bai}NginxProxyManager Visual Panel"
- echo -e "${gl_kjlan}5. ${gl_bai}OpenList multi-store file list program${gl_kjlan}6. ${gl_bai}Ubuntu Remote Desktop Web Edition"
- echo -e "${gl_kjlan}7. ${gl_bai}Nezha Probe VPS Monitoring Panel${gl_kjlan}8. ${gl_bai}QB Offline BT Magnetic Download Panel"
- echo -e "${gl_kjlan}9. ${gl_bai}Poste.io mail server program${gl_kjlan}10. ${gl_bai}RocketChat multiplayer online chat system"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}Zendao project management software${gl_kjlan}12. ${gl_bai}Qinglong Panel Timed Task Management Platform"
- echo -e "${gl_kjlan}13. ${gl_bai}Cloudreve network disk${gl_huang}★${gl_bai} ${gl_kjlan}14. ${gl_bai}Simple picture bed picture management program"
- echo -e "${gl_kjlan}15. ${gl_bai}emby multimedia management system${gl_kjlan}16. ${gl_bai}Speedtest speed test panel"
- echo -e "${gl_kjlan}17. ${gl_bai}AdGuardHome Adware${gl_kjlan}18. ${gl_bai}onlyoffice online office OFFICE"
- echo -e "${gl_kjlan}19. ${gl_bai}Thunder Pool WAF firewall panel${gl_kjlan}20. ${gl_bai}portainer container management panel"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}VScode web version${gl_kjlan}22. ${gl_bai}UptimeKuma monitoring tool"
- echo -e "${gl_kjlan}23. ${gl_bai}Memos web page memo${gl_kjlan}24. ${gl_bai}Webtop Remote Desktop Web Edition${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}25. ${gl_bai}Nextcloud network disk${gl_kjlan}26. ${gl_bai}QD-Today timing task management framework"
- echo -e "${gl_kjlan}27. ${gl_bai}Dockge Container Stack Management Panel${gl_kjlan}28. ${gl_bai}LibreSpeed Speed Test Tool"
- echo -e "${gl_kjlan}29. ${gl_bai}searxng aggregation search site${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}PhotoPrism Private Album System"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}StirlingPDF tool collection${gl_kjlan}32. ${gl_bai}drawio free online charting software${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}Sun-Panel Navigation Panel${gl_kjlan}34. ${gl_bai}Pingvin-Share file sharing platform"
- echo -e "${gl_kjlan}35. ${gl_bai}Minimalist circle of friends${gl_kjlan}36. ${gl_bai}LobeChatAI Chat Aggregation Website"
- echo -e "${gl_kjlan}37. ${gl_bai}MyIP Toolbox${gl_huang}★${gl_bai} ${gl_kjlan}38. ${gl_bai}Xiaoya alist family bucket"
- echo -e "${gl_kjlan}39. ${gl_bai}Bililive live broadcast recording tool${gl_kjlan}40. ${gl_bai}webssh web version SSH connection tool"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}Mouse Management Panel${gl_kjlan}42. ${gl_bai}Nexte remote connection tool"
- echo -e "${gl_kjlan}43. ${gl_bai}RustDesk Remote Desk (Server)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${gl_bai}RustDesk Remote Desk (Relay)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}45. ${gl_bai}Docker acceleration station${gl_kjlan}46. ${gl_bai}GitHub Acceleration Station${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}47. ${gl_bai}Prometheus Monitoring${gl_kjlan}48. ${gl_bai}Prometheus (host monitoring)"
- echo -e "${gl_kjlan}49. ${gl_bai}Prometheus (Container Monitoring)${gl_kjlan}50. ${gl_bai}Replenishment monitoring tool"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}51. ${gl_bai}PVE Chicken Panel${gl_kjlan}52. ${gl_bai}DPanel Container Management Panel"
- echo -e "${gl_kjlan}53. ${gl_bai}llama3 chat AI model${gl_kjlan}54. ${gl_bai}AMH Host Website Building Management Panel"
- echo -e "${gl_kjlan}55. ${gl_bai}FRP intranet penetration (server side)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${gl_bai}FRP intranet penetration (client)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}57. ${gl_bai}Deepseek chat AI big model${gl_kjlan}58. ${gl_bai}Dify big model knowledge base${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}59. ${gl_bai}NewAPI big model asset management${gl_kjlan}60. ${gl_bai}JumpServer open source bastion machine"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}61. ${gl_bai}Online translation server${gl_kjlan}62. ${gl_bai}RAGFlow big model knowledge base"
- echo -e "${gl_kjlan}63. ${gl_bai}OpenWebUI self-hosted AI platform${gl_huang}★${gl_bai} ${gl_kjlan}64. ${gl_bai}it-tools toolbox"
- echo -e "${gl_kjlan}65. ${gl_bai}n8n Automation Workflow Platform${gl_huang}★${gl_bai} ${gl_kjlan}66. ${gl_bai}yt-dlp video download tool"
- echo -e "${gl_kjlan}67. ${gl_bai}ddns-go Dynamic DNS Management Tool${gl_huang}★${gl_bai} ${gl_kjlan}68. ${gl_bai}AllinSSL Certificate Management Platform"
- echo -e "${gl_kjlan}69. ${gl_bai}SFTPGo file transfer tool${gl_kjlan}70. ${gl_bai}AstrBot Chat Robot Framework"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}71. ${gl_bai}Navidrome Private Music Server${gl_kjlan}72. ${gl_bai}bitwarden Password Manager${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}73. ${gl_bai}LibreTV Private Film and Television${gl_kjlan}74. ${gl_bai}MoonTV Private Movie"
- echo -e "${gl_kjlan}75. ${gl_bai}Melody Music Elf${gl_kjlan}76. ${gl_bai}Online DOS old games"
- echo -e "${gl_kjlan}77. ${gl_bai}Thunder offline download tool${gl_kjlan}78. ${gl_bai}PandaWiki Intelligent Document Management System"
- echo -e "${gl_kjlan}79. ${gl_bai}Beszel server monitoring"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
- case $sub_choice in
- 1)
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="宝塔面板"
- local panelurl="https://www.bt.cn/new/index.html"
+moltbot_menu() {
+ local app_id="114"
- panel_app_install() {
- if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
- }
+ send_stats "clawdbot/moltbot management"
- panel_app_manage() {
- bt
- }
+ check_openclaw_update() {
+ if ! command -v npm >/dev/null 2>&1; then
+ return 1
+ fi
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ # Add --no-update-notifier and make sure error redirection is in the correct location
+ local_version=$(npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null | grep openclaw | awk '{print $NF}' | sed 's/^.*@//')
- install_panel
+ if [ -z "$local_version" ]; then
+ return 1
+ fi
+ remote_version=$(npm view openclaw version --no-update-notifier 2>/dev/null)
+ if [ -z "$remote_version" ]; then
+ return 1
+ fi
- ;;
- 2)
+ if [ "$local_version" != "$remote_version" ]; then
+ echo "${gl_huang}New version detected:$remote_version${gl_bai}"
+ else
+ echo "${gl_lv}The current version is the latest:$local_version${gl_bai}"
+ fi
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="aapanel"
- local panelurl="https://www.aapanel.com/new/index.html"
- panel_app_install() {
- URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
- }
+ get_install_status() {
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "${gl_lv}Installed${gl_bai}"
+ else
+ echo "${gl_hui}Not installed${gl_bai}"
+ fi
+ }
- panel_app_manage() {
- bt
- }
+ get_running_status() {
+ if pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
+ echo "${gl_lv}Running${gl_bai}"
+ else
+ echo "${gl_hui}Not running${gl_bai}"
+ fi
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
- install_panel
+ show_menu() {
- ;;
- 3)
- local lujing="command -v 1pctl"
- local panelname="1Panel"
- local panelurl="https://1panel.cn/"
+ clear
- panel_app_install() {
- install bash
- bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
- }
+ local install_status=$(get_install_status)
+ local running_status=$(get_running_status)
+ local update_message=$(check_openclaw_update)
+
+ echo "======================================="
+ echo -e "🦞 OPENCLAW Management Tool by KEJILION 🦞"
+ echo -e "💡 Terminal execution \033[1;33mk claw\033[0m to quickly enter the menu"
+ echo -e "$install_status $running_status $update_message"
+ echo "======================================="
+ echo "1. Installation"
+ echo "2. Start"
+ echo "3. Stop"
+ echo "--------------------"
+ echo "4. View status log"
+ echo "5. Change model"
+ echo "6. API Management"
+ echo "7. Robot connection and docking"
+ echo "8. Plug-in management (installation/removal)"
+ echo "9. Skill management (installation/removal)"
+ echo "10. Edit the main configuration file"
+ echo "11. Configuration Wizard"
+ echo "12. Health detection and repair"
+ echo "13. WebUI access and settings"
+ echo "14. TUI command line dialog window"
+ echo "15. Memory/Memory"
+ echo "16. Permission management"
+ echo "17. Multi-agent management"
+ echo "--------------------"
+ echo "18. Backup and restore"
+ echo "19. Update"
+ echo "20. Uninstall"
+ echo "--------------------"
+ echo "0. Return to the previous menu"
+ echo "--------------------"
+ printf "Please enter options and press Enter:"
+ }
- panel_app_manage() {
- 1pctl user-info
- 1pctl update password
- }
- panel_app_uninstall() {
- 1pctl uninstall
- }
+ start_gateway() {
+ openclaw gateway stop
+ openclaw gateway start
+ sleep 3
+ }
- install_panel
- ;;
- 4)
+ install_node_and_tools() {
+ if command -v dnf &>/dev/null; then
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
+ dnf update -y
+ dnf group install -y "Development Tools" "Development Libraries"
+ dnf install -y cmake libatomic nodejs
+ fi
+
+ if command -v apt &>/dev/null; then
+ curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
+ apt update -y
+ apt install build-essential python3 libatomic1 nodejs -y
+ fi
+ }
+
+ sync_openclaw_api_models() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ [ ! -f "$config_file" ] && return 0
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$ENABLE_STATS" "$sh_v" <<'PY'
+import copy
+import json
+import os
+import platform
+import sys
+import time
+import urllib.request
+from datetime import datetime, timezone
+
+path = sys.argv[1]
+stats_enabled = (sys.argv[2].lower() == "true") if len(sys.argv) > 2 else True
+script_version = sys.argv[3] if len(sys.argv) > 3 else ""
+
+def send_stat(action):
+ if not stats_enabled:
+ return
+ payload = {
+ "action": action,
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
+ "country": "",
+ "os_info": platform.platform(),
+ "cpu_arch": platform.machine(),
+ "version": script_version,
+ }
+ try:
+ req = urllib.request.Request(
+ "https://api.kejilion.pro/api/log",
+ data=json.dumps(payload).encode("utf-8"),
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=3):
+ pass
+ except Exception:
+ pass
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('ℹ️ API providers not detected, model synchronization skipped')
+ raise SystemExit(0)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+changed = False
+fatal_errors = []
+summary = []
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+def prompt_delete_provider(name):
+ prompt = f"⚠️ {name} /models probe failed 3 times in a row. Delete this API provider and all related models? [y/N]:"
+ try:
+ ans = input(prompt).strip().lower()
+ except EOFError:
+ return False
+ return ans in ('y', 'yes')
+
+
+def rebind_defaults_before_delete(name):
+ global changed
+
+ replacement = None
+
+ def get_replacement():
+ nonlocal replacement
+ if replacement is None:
+ candidates = collect_available_refs(exclude_provider=name)
+ replacement = candidates[0] if candidates else None
+ return replacement
+
+ primary_ref = get_primary_ref(defaults)
+ if ref_provider(primary_ref) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: The default main model points to this provider, but there is no alternative model available, and the deletion has been aborted.')
+ return False
+ set_primary_ref(defaults, repl)
+ changed = True
+ summary.append(f'🔁 The default primary model has been switched before deletion: {primary_ref} -> {repl}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: {fk} points to the provider, but no alternative model is available, deletion aborted')
+ return False
+ defaults[fk] = repl
+ changed = True
+ summary.append(f'🔁 Switched before deletion {fk}: {val} -> {repl}')
+
+ return True
+
+
+def delete_provider_and_refs(name):
+ global changed
+
+ if not rebind_defaults_before_delete(name):
+ return False
+
+ removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+ for r in removed_refs:
+ defaults_models.pop(r, None)
+ if removed_refs:
+ changed = True
+
+ if name in providers:
+ providers.pop(name, None)
+ changed = True
+
+ summary.append(f'🗑️ Provider {name} has been deleted and {len(removed_refs)} model references under defaults.models have been removed')
+ return True
+
+
+def fetch_remote_models_with_retry(name, base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ data = json.loads(payload)
+ return data, None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+for name, provider in list(providers.items()):
+ if not isinstance(provider, dict):
+ summary.append(f'ℹ️ Skip {name}: provider structure is illegal')
+ continue
+
+ api = provider.get('api', '')
+ base_url = provider.get('baseUrl')
+ api_key = provider.get('apiKey')
+ model_list = provider.get('models', [])
+
+ if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ summary.append(f'ℹ️ Skip {name}: None baseUrl/apiKey/models')
+ continue
+
+ if api not in SUPPORTED_APIS:
+ summary.append(f'🔁 {name}: Illegal protocol {api or "(unset)"} found, will be re-detected')
+ provider['api'] = ''
+ api = ''
+ changed = True
+
+ data, err, attempts = fetch_remote_models_with_retry(name, base_url, api_key, retries=3)
+ if err is not None:
+ summary.append(f'⚠️ {name}: /models detection failed, retried {attempts} times ({type(err).__name__}: {err})')
+ send_stat('OpenClaw API confirmed intervention')
+ if prompt_delete_provider(name):
+ deleted = delete_provider_and_refs(name)
+ if deleted:
+ send_stat('OpenClaw API deletion failed Provider-Confirm')
+ summary.append(f'✅ {name}: The user has confirmed to delete the provider and all related model references')
+ else:
+ send_stat('OpenClaw API deletion failed Provider-rejected')
+ summary.append(f'ℹ️ {name}: The user has not confirmed the deletion and retains the existing provider configuration.')
+ continue
+
+ if attempts > 1:
+ summary.append(f'🔁 {name}: /models Successful after {attempts} retry')
+
+ if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ summary.append(f'⚠️ Skip {name}: /models return structure is not recognized')
+ continue
+
+ remote_ids = []
+ for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+ remote_set = set(remote_ids)
+
+ if not remote_set:
+ fatal_errors.append(f'❌ {name}’s upstream /models is empty and cannot provide a bottom-up model for this provider.')
+ continue
+
+ local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+ local_ids = [str(m['id']) for m in local_models]
+ local_set = set(local_ids)
+
+ template = None
+ for m in local_models:
+ template = copy.deepcopy(m)
+ break
+ if template is None:
+ summary.append(f'⚠️ Skip {name}: local models no valid template model')
+ continue
+
+ removed_ids = [mid for mid in local_ids if mid not in remote_set]
+ added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+ kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+ new_models = kept_models[:]
+
+ for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{name} / {mid}'
+ new_models.append(nm)
+
+ if not new_models:
+ fatal_errors.append(f'❌ {name} has no available model after synchronization, and the default model/fallback model cannot be guaranteed.')
+ continue
+
+ expected_refs = {model_ref(name, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+ local_refs = {model_ref(name, mid) for mid in local_ids}
+
+ first_ref = model_ref(name, str(new_models[0]['id']))
+
+ primary_ref = get_primary_ref(defaults)
+ if isinstance(primary_ref, str) and primary_ref in (local_refs - expected_refs):
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ summary.append(f'🔁 The default model has been completely replaced: {primary_ref} -> {first_ref}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in (local_refs - expected_refs):
+ defaults[fk] = first_ref
+ changed = True
+ summary.append(f'🔁 {fk} has been completely replaced: {val} -> {first_ref}')
+
+ stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/') and r not in expected_refs]
+ for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+ for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+ if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+ summary.append(f'✅ {name}: Added {len(added_ids)}, deleted {len(removed_ids)}, currently {len(new_models)}')
+
+ if added_ids:
+ summary.append(f'➕ Add new model ({len(added_ids)}):')
+ for mid in added_ids:
+ summary.append(f' + {mid}')
+ if removed_ids:
+ summary.append(f'➖ Delete model ({len(removed_ids)}):')
+ for mid in removed_ids:
+ summary.append(f' - {mid}')
+
+
+if fatal_errors:
+ for line in summary:
+ print(line)
+ for err in fatal_errors:
+ print(err)
+ print('❌ Model synchronization failed: There is a provider. After synchronization, there is no available model and writing has been aborted.')
+ raise SystemExit(2)
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+ for line in summary:
+ print(line)
+ print('✅ OpenClaw API model consistency synchronization is completed and the configuration has been written')
+else:
+ for line in summary:
+ print(line)
+ print('ℹ️ No synchronization required: configuration is already consistent with upstream /models')
+PY
+ }
+
+
+
+ install_moltbot() {
+ echo "Start installing OpenClaw..."
+ send_stats "Start installing OpenClaw..."
+ install git jq
+
+ install_node_and_tools
+
+ country=$(curl -s ipinfo.io/country)
+ if [[ "$country" == "CN" || "$country" == "HK" ]]; then
+ npm config set registry https://registry.npmmirror.com
+ fi
+
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+
+ npm install -g openclaw@latest
+ openclaw onboard --install-daemon
+ start_gateway
+ add_app_id
+ break_end
+
+ }
+
+
+ start_bot() {
+ echo "Starting OpenClaw..."
+ send_stats "Starting OpenClaw..."
+ start_gateway
+ break_end
+ }
+
+ stop_bot() {
+ echo "Stop OpenClaw..."
+ send_stats "Stop OpenClaw..."
+ tmux kill-session -t gateway > /dev/null 2>&1
+ openclaw gateway stop
+ break_end
+ }
+
+ view_logs() {
+ echo "View the OpenClaw status log"
+ send_stats "View OpenClaw logs"
+ openclaw status
+ openclaw gateway status
+ openclaw logs
+ break_end
+ }
+
+
+
+
+
+ # OpenClaw API protocol detection logic has been removed: API types are no longer automatically detected/determined.
+ # Note: The API type is explicitly configured by the user (models.providers..api), and the script no longer attempts to call /responses for inference.
+
+ # Construct model configuration JSON
+ build-openclaw-provider-models-json() {
+ local provider_name="$1"
+ local model_ids="$2"
+ local models_array="["
+ local first=true
+
+ while read -r model_id; do
+ [ -z "$model_id" ] && continue
+ [[ $first == false ]] && models_array+=","
+ first=false
+
+ local context_window=1048576
+ local max_tokens=128000
+ local input_cost=0.15
+ local output_cost=0.60
+
+ case "$model_id" in
+ *opus*|*pro*|*preview*|*thinking*|*sonnet*)
+ input_cost=2.00
+ output_cost=12.00
+ ;;
+ *gpt-5*|*codex*)
+ input_cost=1.25
+ output_cost=10.00
+ ;;
+ *flash*|*lite*|*haiku*|*mini*|*nano*)
+ input_cost=0.10
+ output_cost=0.40
+ ;;
+ esac
+
+ models_array+=$(cat < 0 then
+ .["\($prov)/\($mid)"] //= {}
+ else
+ .
+ end
+ )
+ )
+ ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
+ }
+
+ # Core function: get and add all models
+ add-all-models-from-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+
+ echo "🔍 Getting$provider_nameAll available models of..."
+
+ local models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -z "$models_json" ]]; then
+ echo "❌ Unable to obtain model list"
+ return 1
+ fi
+
+ local model_ids=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+')
+
+ if [[ -z "$model_ids" ]]; then
+ echo "❌ No models found"
+ return 1
+ fi
+
+ local model_count=$(echo "$model_ids" | wc -l)
+ echo "✅ Discover$model_countmodels"
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$model_ids")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ Added successfully$model_countmodels arrive$provider_name"
+ echo "📦 Model reference format:$provider_name/"
+ return 0
+ else
+ echo "❌ Configuration injection failed"
+ return 1
+ fi
+ }
+
+ # Add only the default model and keep the provider
+ add-default-model-only-to-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+ local default_model="$4"
+
+ if [[ -z "$default_model" ]]; then
+ echo "❌ The default model cannot be empty"
+ return 1
+ fi
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$default_model")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ Provider added:$provider_name"
+ echo "✅ Only write to the default model:$default_model"
+ return 0
+ else
+ echo "❌ Configuration injection failed"
+ return 1
+ fi
+ }
+
+ add-openclaw-provider-interactive() {
+ send_stats "OpenClaw API added"
+ echo "=== Interactively add OpenClaw Provider (full model) ==="
+
+ # 1. Provider name
+ read -erp "Please enter the Provider name (eg: deepseek):" provider_name
+ while [[ -z "$provider_name" ]]; do
+ echo "❌ Provider name cannot be empty"
+ read -erp "Please enter Provider name:" provider_name
+ done
+
+ # 2. Base URL
+ read -erp "Please enter Base URL (eg: https://api.xxx.com/v1):" base_url
+ while [[ -z "$base_url" ]]; do
+ echo "❌ Base URL cannot be empty"
+ read -erp "Please enter Base URL:" base_url
+ done
+ base_url="${base_url%/}"
+
+ # 3. API Key
+ read -rsp "Please enter API Key (input will not be displayed):" api_key
+ echo
+ while [[ -z "$api_key" ]]; do
+ echo "❌ API Key cannot be empty"
+ read -rsp "Please enter API Key:" api_key
+ echo
+ done
+
+ # 4. No longer detecting/determining API types; protocols are selected and maintained by users themselves
+
+ # 5. Get model list
+ echo "🔍 Getting list of available models..."
+ models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -n "$models_json" ]]; then
+ available_models=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+' | sort)
+
+ if [[ -n "$available_models" ]]; then
+ model_count=$(echo "$available_models" | wc -l)
+ echo "✅ Discover$model_countAvailable models:"
+ echo "--------------------------------"
+ # Show all, with serial number
+ i=1
+ model_list=()
+ while read -r model; do
+ echo "[$i] $model"
+ model_list+=("$model")
+ ((i++))
+ done <<< "$available_models"
+ echo "--------------------------------"
+ fi
+ fi
+
+ # 5. Select the default model
+ echo
+ read -erp "Please enter the default Model ID (or serial number, leave blank to use the first one):" input_model
+
+ if [[ -z "$input_model" && -n "$available_models" ]]; then
+ default_model=$(echo "$available_models" | head -1)
+ echo "🎯 Using the first model:$default_model"
+ elif [[ "$input_model" =~ ^[0-9]+$ ]] && [ "${#model_list[@]}" -gt 0 ] && [ "$input_model" -ge 1 ] && [ "$input_model" -le "${#model_list[@]}" ]; then
+ default_model="${model_list[$((input_model-1))]}"
+ echo "🎯 Selected models:$default_model"
+ else
+ default_model="$input_model"
+ fi
+
+ # 6. Confirm information
+ echo
+ echo "====== Confirmation ======"
+ echo "Provider : $provider_name"
+ echo "Base URL : $base_url"
+ echo "API Key : ${api_key:0:8}****"
+ echo "Default model:$default_model"
+ echo "Total number of models:$model_count"
+ echo "======================"
+
+ read -erp "Do you want to add all other available models at the same time? (y/N):" confirm
+
+ install jq
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ add-all-models-from-provider "$provider_name" "$base_url" "$api_key"
+ add_result=$?
+ finish_msg="✅ Done! all$model_countmodels loaded"
+ else
+ add-default-model-only-to-provider "$provider_name" "$base_url" "$api_key" "$default_model"
+ add_result=$?
+ finish_msg="✅ Done! Provider is retained and only the default model is loaded:$default_model"
+ fi
+
+ if [[ $add_result -eq 0 ]]; then
+ echo
+ echo "🔄 Set default model and restart gateway..."
+ openclaw models set "$provider_name/$default_model"
+ openclaw_sync_sessions_model "$provider_name/$default_model"
+ start_gateway
+ echo "$finish_msg"
+ echo "✅ Current API protocol type:$DETECTED_API"
+ fi
+
+ break_end
+ }
+
+
+
+openclaw_api_manage_list() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API List"
+
+ while IFS=$'\t' read -r rec_type idx name base_url model_count api_type latency_txt latency_level; do
+ case "$rec_type" in
+ MSG)
+ echo "$idx"
+ ;;
+ ROW)
+ local latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
+
+ printf '%b\n' "[$idx] ${name} | API: ${base_url}| Agreement:${api_type}| Number of models:${gl_huang}${model_count}${gl_bai}| Delay/Status:${latency_color}${latency_txt}${gl_bai}"
+ ;;
+ esac
+ done < <(python3 - "$config_file" <<-'PY'
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+
+def ping_models(base_url, api_key):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'OpenClaw-API-Manage/1.0',
+ },
+ )
+ start = time.perf_counter()
+ with urllib.request.urlopen(req, timeout=4) as resp:
+ resp.read(2048)
+ return int((time.perf_counter() - start) * 1000)
+
+
+def classify_latency(latency):
+ if latency == 'Not available':
+ return 'Not available', 'unavailable'
+ if latency == 'Not detected':
+ return 'Not detected', 'unchecked'
+ if isinstance(latency, int):
+ if latency <= 800:
+ level = 'low'
+ elif latency <= 2000:
+ level = 'medium'
+ else:
+ level = 'high'
+ return f'{latency}ms', level
+ return str(latency), 'unchecked'
+
+
+try:
+ with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+except FileNotFoundError:
+ print('MSG\tℹ️ openclaw.json not found, please complete the installation/initialization first.')
+ raise SystemExit(0)
+except Exception as e:
+ print(f'MSG\t❌ Failed to read configuration: {type(e).__name__}: {e}')
+ raise SystemExit(0)
+
+providers = ((obj.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or not providers:
+ print('MSG\tℹ️ No API provider is currently configured.')
+ raise SystemExit(0)
+
+print('MSG\t--- Configured API list ---')
+
+for idx, name in enumerate(sorted(providers.keys()), start=1):
+ provider = providers.get(name)
+ if not isinstance(provider, dict):
+ base_url = '-'
+ model_count = 0
+ latency_raw = 'Not available'
+ else:
+ base_url = provider.get('baseUrl') or provider.get('url') or provider.get('endpoint') or '-'
+ models = provider.get('models') if isinstance(provider.get('models'), list) else []
+ model_count = sum(1 for m in models if isinstance(m, dict) and m.get('id'))
+ api = provider.get('api', '')
+ api_key = provider.get('apiKey')
+
+ latency_raw = 'Not detected'
+ if api in SUPPORTED_APIS:
+ if isinstance(base_url, str) and base_url != '-' and isinstance(api_key, str) and api_key:
+ try:
+ latency_raw = ping_models(base_url, api_key)
+ except Exception:
+ latency_raw = 'Not available'
+ else:
+ latency_raw = 'Not available'
+
+ latency_text, latency_level = classify_latency(latency_raw)
+ api_label = api if api in SUPPORTED_APIS else '-'
+ print(
+ 'ROW\t' + '\t'.join([
+ str(idx),
+ str(name),
+ str(base_url),
+ str(model_count),
+ str(api_label),
+ str(latency_text),
+ str(latency_level),
+ ])
+ )
+PY
+)
+}
+sync-openclaw-provider-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API synchronization by Provider"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ Configuration file not found:$config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "Please enter the API name (provider) to be synchronized and press Enter to synchronize all:" provider_name
+ if [ -z "$provider_name" ]; then
+ if sync_openclaw_api_models; then
+ start_gateway
+ else
+ echo "❌ API model synchronization failed, restarting the gateway has been aborted. Please check provider /models and try again after returning."
+ return 1
+ fi
+ break_end
+ return 0
+ fi
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" <<'PY2'
+import copy
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+target = sys.argv[2]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('❌ API providers not detected, unable to synchronize')
+ raise SystemExit(2)
+
+provider = providers.get(target)
+if not isinstance(provider, dict):
+ print(f'❌ provider: {target} not found')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def fetch_remote_models_with_retry(base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ return json.loads(payload), None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+api = provider.get('api', '')
+base_url = provider.get('baseUrl')
+api_key = provider.get('apiKey')
+model_list = provider.get('models', [])
+
+if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ print(f'❌ provider {target} is missing baseUrl/apiKey/models and cannot perform synchronization')
+ raise SystemExit(3)
+
+if api not in SUPPORTED_APIS:
+ print(f'ℹ️ provider {target} currently api={api}, but the script no longer detects/corrects the protocol; please manually set it to openai-completions or openai-responses')
+
+protocol_msg = None
+
+data, err, attempts = fetch_remote_models_with_retry(base_url, api_key, retries=3)
+if err is not None:
+ print(f'❌ {target}: /models detection failed, retried {attempts} times ({type(err).__name__}: {err})')
+ raise SystemExit(4)
+
+if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ print(f'❌ {target}: /models The returned structure is not recognized')
+ raise SystemExit(4)
+
+remote_ids = []
+for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+remote_set = set(remote_ids)
+if not remote_set:
+ print(f'❌ {target}: upstream /models is empty, synchronization aborted')
+ raise SystemExit(5)
+
+local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+local_ids = [str(m['id']) for m in local_models]
+local_set = set(local_ids)
+
+template = copy.deepcopy(local_models[0]) if local_models else None
+if template is None:
+ print(f'❌ {target}: Local models do not have a valid template model, and new models cannot be added.')
+ raise SystemExit(3)
+
+removed_ids = [mid for mid in local_ids if mid not in remote_set]
+added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+new_models = kept_models[:]
+for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{target} / {mid}'
+ new_models.append(nm)
+
+if not new_models:
+ print(f'❌ {target}: No model available after synchronization, writing aborted')
+ raise SystemExit(5)
+
+expected_refs = {model_ref(target, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+local_refs = {model_ref(target, mid) for mid in local_ids}
+removed_refs = local_refs - expected_refs
+first_ref = model_ref(target, str(new_models[0]['id']))
+
+changed = False
+primary_ref = get_primary_ref(defaults)
+if isinstance(primary_ref, str) and primary_ref in removed_refs:
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ print(f'🔁 The default model has been completely replaced: {primary_ref} -> {first_ref}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in removed_refs:
+ defaults[fk] = first_ref
+ changed = True
+ print(f'🔁 {fk} has been completely replaced: {val} -> {first_ref}')
+
+stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(target + '/') and r not in expected_refs]
+for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ {target}: Added {len(added_ids)}, deleted {len(removed_ids)}, current {len(new_models)}')
+
+if added_ids:
+ print(f'➕ Add new model ({len(added_ids)}):')
+ for mid in added_ids:
+ print(f' + {mid}')
+if removed_ids:
+ print(f'➖ Delete model ({len(removed_ids)}):')
+ for mid in removed_ids:
+ print(f' - {mid}')
+
+if changed:
+ print('✅ The specified provider model consistency synchronization is completed and the configuration has been written')
+else:
+ print('ℹ️ No synchronization required: the provider configuration is already consistent with the upstream /models')
+PY2
+ local rc=$?
+ case "$rc" in
+ 0)
+ echo "✅ Synchronous execution completed"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ Synchronization failed: provider does not exist or is not configured"
+ ;;
+ 3)
+ echo "❌ Synchronization failed: provider configuration is incomplete or type is not supported"
+ ;;
+ 4)
+ echo "❌ Synchronization failed: Upstream /models request failed"
+ ;;
+ 5)
+ echo "❌ Synchronization failed: the upstream model is empty or there is no available model after synchronization"
+ ;;
+ *)
+ echo "❌ Synchronization failed: please check the configuration file structure or log output"
+ ;;
+ esac
+
+ break_end
+}
+
+openclaw_detect_api_protocol_by_provider() {
+ # Protocol detection logic has been removed: scripts no longer automatically detect/determine API types.
+ # The function is retained for compatibility with menu calls, but no rewriting is done.
+ echo "ℹ️ Protocol detection has been turned off: please manually${HOME}Set provider.api in /.openclaw/openclaw.json to openai-completions or openai-responses"
+ return 0
+}
+
+fix-openclaw-provider-protocol-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API protocol switching"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ Configuration file not found:$config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "Please enter the API name (provider) to switch protocols:" provider_name
+ if [ -z "$provider_name" ]; then
+ echo "❌ provider name cannot be empty"
+ break_end
+ return 1
+ fi
+
+ echo "Please select the API type you want to set up:"
+ echo "1. openai-completions"
+ echo "2. openai-responses"
+ read -erp "Please enter your choice (1/2):" proto_choice
+
+ local new_api=""
+ case "$proto_choice" in
+ 1) new_api="openai-completions" ;;
+ 2) new_api="openai-responses" ;;
+ *)
+ echo "❌ Invalid selection"
+ break_end
+ return 1
+ ;;
+ esac
+
+ install python3 >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" "$new_api" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+new_api = sys.argv[3]
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+if new_api not in SUPPORTED_APIS:
+ print('❌ Illegal protocol value')
+ raise SystemExit(3)
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+providers = ((work.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or name not in providers or not isinstance(providers.get(name), dict):
+ print(f'❌ provider: {name} not found')
+ raise SystemExit(2)
+
+providers[name]['api'] = new_api
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ The provider {name} protocol has been updated to: {new_api}')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ start_gateway
+ ;;
+ 2)
+ echo "❌ Switching failed: provider does not exist or is not configured"
+ ;;
+ 3)
+ echo "❌ Switching failed: protocol value is illegal"
+ ;;
+ *)
+ echo "❌ Switching failed: please check the configuration file structure or log output"
+ ;;
+ esac
+
+ break_end
+}
+
+ delete-openclaw-provider-interactive() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ send_stats "OpenClaw API delete entry"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ Configuration file not found:$config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "Please enter the API name (provider) to be deleted:" provider_name
+ if [ -z "$provider_name" ]; then
+ send_stats "OpenClaw API Delete Cancel"
+ echo "❌ provider name cannot be empty"
+ break_end
+ return 1
+ fi
+
+ python3 - "$config_file" "$provider_name" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or name not in providers:
+ print(f'❌ provider: {name} not found')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+replacement_candidates = collect_available_refs(exclude_provider=name)
+replacement = replacement_candidates[0] if replacement_candidates else None
+
+primary_ref = get_primary_ref(defaults)
+if ref_provider(primary_ref) == name:
+ if not replacement:
+ print('❌ Deletion aborted: the default main model points to the provider and no alternative model is available')
+ raise SystemExit(3)
+ set_primary_ref(defaults, replacement)
+ print(f'🔁 Default primary model switching: {primary_ref} -> {replacement}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ if not replacement:
+ print(f'❌ Deletion aborted: {fk} points to the provider and no alternative model is available')
+ raise SystemExit(3)
+ defaults[fk] = replacement
+ print(f'🔁 {fk} switch: {val} -> {replacement}')
+
+removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+for r in removed_refs:
+ defaults_models.pop(r, None)
+
+providers.pop(name, None)
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'🗑️ Deleted provider: {name}')
+print(f'🧹 Cleaned {len(removed_refs)} associated model references in defaults.models')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ send_stats "OpenClaw API deletion confirmation"
+ echo "✅Deletion completed"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ Deletion failed: provider does not exist"
+ ;;
+ 3)
+ send_stats "OpenClaw API Delete Cancel"
+ echo "❌ Deletion failed: no replacement model available, original configuration has been maintained"
+ ;;
+ *)
+ echo "❌ Deletion failed: please check the configuration file structure or log output"
+ ;;
+ esac
+
+ break_end
+ }
+
+ openclaw_api_providers_showcase() {
+ send_stats "OpenClaw API Vendor Recommendations"
+
+ clear
+ echo ""
+ echo -e "${gl_kjlan}╔════════════════════════════════════════════════════════════╗${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_huang}🌟 API vendor recommendation list${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_zi}Some entrances contain AFF${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}╚════════════════════════════════════════════════════════════╝${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● DeepSeek${gl_bai}"
+ echo -e " ${gl_kjlan}https://api-docs.deepseek.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● OpenRouter${gl_bai}"
+ echo -e " ${gl_kjlan}https://openrouter.ai/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Kimi${gl_bai}"
+ echo -e " ${gl_kjlan}https://platform.moonshot.cn/docs/guide/start-using-kimi-api${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Supercomputing Internet${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.scnet.cn/${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● Youyun Intelligent Computing${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://passport.compshare.cn/register?referral_code=4mscFZXfutfFi8swMVsPuf${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● Silicon based flow${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://cloud.siliconflow.cn/i/irWVdPic${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● GLM${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.bigmodel.cn/glm-coding?ic=HYOTDOAJMR${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● PackyAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.packyapi.com/register?aff=wHri${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● Cloud API${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://yunwu.ai/register?aff=ZuyK${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● Plato AI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://api.bltcy.ai/register?aff=TBzb114019${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● MiniMax${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.minimaxi.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● NVIDIA${gl_bai}"
+ echo -e " ${gl_kjlan}https://build.nvidia.com/settings/api-keys${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Ollama${gl_bai}"
+ echo -e " ${gl_kjlan}https://ollama.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Baishan Cloud${gl_bai}"
+ echo -e " ${gl_kjlan}https://ai.baishan.com/${gl_bai}"
+ echo ""
+ echo -e "${gl_kjlan}────────────────────────────────────────────────────────────${gl_bai}"
+ echo -e " ${gl_zi}legend:${gl_lv}● Official entrance${gl_bai} ${gl_huang}● AFF recommended entrance${gl_bai}"
+ echo ""
+ echo -e "${gl_huang}Tip: Copy the link and open it in your browser to access it${gl_bai}"
+ echo ""
+ read -erp "Press Enter to return..." dummy
+ }
+
+ openclaw_api_manage_menu() {
+ send_stats "OpenClaw API entrance"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw API Management"
+ echo "======================================="
+ openclaw_api_manage_list
+ echo "---------------------------------------"
+ echo "1. Add API"
+ echo "2. Synchronize API provider model list"
+ echo "3. Switch API type (completions/responses)"
+ echo "4. Delete API"
+ echo "5. Recommended by API vendors"
+ echo "0. Exit"
+ echo "---------------------------------------"
+ read -erp "Please enter your choice:" api_choice
+
+ case "$api_choice" in
+ 1)
+ add-openclaw-provider-interactive
+ ;;
+ 2)
+ sync-openclaw-provider-interactive
+ ;;
+ 3)
+ fix-openclaw-provider-protocol-interactive
+ ;;
+ 4)
+ delete-openclaw-provider-interactive
+ ;;
+ 5)
+ openclaw_api_providers_showcase
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "Invalid selection, please try again."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update && apt install -y gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then
+ dnf install -y gum
+ else
+ yum install -y gum
+ fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive refresh
+ zypper --non-interactive install gum
+ fi
+ }
+
+
+
+ change_model() {
+ send_stats "Change model"
+
+ local orange="#FF8C00"
+
+ openclaw_probe_status_line() {
+ local status_text="$1"
+ local status_color_ok='[32m'
+ local status_color_fail='[31m'
+ local status_color_reset='[0m'
+ if [ "$status_text" = "Available" ]; then
+ printf "%b最小检测结果:%s%b
+" "$status_color_ok" "$status_text" "$status_color_reset"
+ else
+ printf "%b最小检测结果:%s%b
+" "$status_color_fail" "$status_text" "$status_color_reset"
+ fi
+ }
+
+ openclaw_model_probe() {
+ local target_model="$1"
+ local probe_timeout=25
+ local tmp_payload tmp_response probe_result probe_status reply_preview reply_trimmed
+ local oc_config provider_name base_url api_key request_model
+ local first_endpoint second_endpoint
+ local first_exit first_http first_latency second_exit second_http second_latency
+ local first_reply second_reply
+
+ oc_config=$(openclaw_get_config_file)
+ [ ! -f "$oc_config" ] && {
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="openclaw configuration file not found"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ }
+
+ provider_name="${target_model%%/*}"
+ request_model="${target_model#*/}"
+ base_url=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].baseUrl // empty' "$oc_config" 2>/dev/null)
+ api_key=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].apiKey // empty' "$oc_config" 2>/dev/null)
+ if [ -z "$provider_name" ] || [ -z "$base_url" ] || [ -z "$api_key" ]; then
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="provider/baseUrl/apiKey not read"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ fi
+
+ base_url="${base_url%/}"
+ first_endpoint="/responses"
+ second_endpoint="/chat/completions"
+
+ openclaw_extract_probe_reply() {
+ python3 - "$1" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+path = Path(sys.argv[1])
+raw = path.read_text(encoding='utf-8', errors='replace').strip()
+reply = ''
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get('choices') or []
+ if choices and isinstance(choices[0], dict):
+ message = choices[0].get('message') or {}
+ if isinstance(message, dict):
+ reply = message.get('content') or ''
+ if not reply:
+ output = data.get('output') or []
+ if isinstance(output, list):
+ texts = []
+ for item in output:
+ if not isinstance(item, dict):
+ continue
+ for content in item.get('content') or []:
+ if not isinstance(content, dict):
+ continue
+ text = content.get('text')
+ if isinstance(text, str) and text.strip():
+ texts.append(text.strip())
+ if texts:
+ break
+ if texts:
+ reply = ' '.join(texts)
+ if not reply:
+ for key in ('error', 'message', 'detail'):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ reply = value.strip()
+ break
+ if isinstance(value, dict):
+ nested = value.get('message')
+ if isinstance(nested, str) and nested.strip():
+ reply = nested.strip()
+ break
+ except Exception:
+ reply = raw
+reply = ' '.join(str(reply).split())
+print(reply)
+PYTHON_EOF
+ }
+
+ openclaw_run_probe() {
+ local endpoint="$1"
+ tmp_payload=$(mktemp)
+ tmp_response=$(mktemp)
+ if [ "$endpoint" = "/responses" ]; then
+ printf '{"model":"%s","input":"hi","temperature":0,"max_output_tokens":16}' "$request_model" > "$tmp_payload"
+ else
+ printf '{"model":"%s","messages":[{"role":"user","content":"hi"}],"temperature":0,"max_tokens":16}' "$request_model" > "$tmp_payload"
+ fi
+
+ probe_result=$(python3 - "$base_url" "$api_key" "$tmp_payload" "$tmp_response" "$probe_timeout" "$endpoint" <<'PYTHON_EOF'
+import sys
+import time
+import urllib.error
+import urllib.request
+
+base_url, api_key, payload_path, response_path, timeout, endpoint = sys.argv[1:7]
+timeout = int(timeout)
+url = base_url + endpoint
+payload = open(payload_path, 'rb').read()
+req = urllib.request.Request(
+ url,
+ data=payload,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'Bearer {api_key}',
+ },
+ method='POST',
+)
+start = time.time()
+body = b''
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, 'status', 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, 'code', 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode('utf-8', errors='replace')
+ exit_code = 1
+elapsed = int((time.time() - start) * 1000)
+with open(response_path, 'wb') as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYTHON_EOF
+)
+ probe_status=$?
+ reply_preview=$(openclaw_extract_probe_reply "$tmp_response")
+ rm -f "$tmp_payload" "$tmp_response"
+ return $probe_status
+ }
+
+ openclaw_run_probe "$first_endpoint"
+ first_exit=${probe_result%%|*}
+ first_http=${probe_result#*|}
+ first_http=${first_http%%|*}
+ first_latency=${probe_result##*|}
+ first_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(empty return)"
+
+ if [ "$first_exit" = "0" ] && [ "$first_http" -ge 200 ] && [ "$first_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http}"
+ OPENCLAW_PROBE_LATENCY="${first_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ openclaw_run_probe "$second_endpoint"
+ second_exit=${probe_result%%|*}
+ second_http=${probe_result#*|}
+ second_http=${second_http%%|*}
+ second_latency=${probe_result##*|}
+ second_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(empty return)"
+
+ if [ "$second_exit" = "0" ] && [ "$second_http" -ge 200 ] && [ "$second_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint}-> HTTP ${first_http:-0}, switch${second_endpoint} -> HTTP ${second_http}"
+ OPENCLAW_PROBE_LATENCY="${second_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(empty return)"
+
+ OPENCLAW_PROBE_STATUS="FAIL"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0} / exit ${first_exit:-1};${second_endpoint} -> HTTP ${second_http:-0} / exit ${second_exit:-1}"
+ OPENCLAW_PROBE_LATENCY="${first_latency:-?}ms -> ${second_latency:-?}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 1
+ }
+
+ clear
+
+ while true; do
+ local models_raw models_list default_model model_count selected_model confirm_switch
+
+ # Read model keys from configuration file (without calling openclaw models list)
+ local oc_config
+ oc_config=$(openclaw_get_config_file)
+
+ models_raw=$(jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d')
+ if [ -z "$models_raw" ]; then
+ echo "Failed to get list of models: agents.defaults.models not found in configuration file."
+ break_end
+ return 1
+ fi
+
+ # Number each model to facilitate quick location (for example: "(10) or-api/...:free")
+ models_list=$(echo "$models_raw" | awk '{print "(" NR ") " $0}')
+ model_count=$(echo "$models_list" | sed '/^\s*$/d' | wc -l | tr -d ' ')
+
+ # Read default model from configuration file (faster); fallback to openclaw command on failure
+ default_model=$(jq -r '.agents.defaults.model.primary // empty' "$oc_config" 2>/dev/null)
+ [ -z "$default_model" ] && default_model="(unknown)"
+
+ clear
+
+ install_gum
+ install gum
+
+ # If gum does not exist, it will be downgraded to the original manual input process.
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "---Model Management ---"
+ echo "Currently available models:"
+ jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d'
+ echo "----------------"
+ read -e -p "Please enter the model name to set (e.g. openrouter/openai/gpt-4o) (enter 0 to exit):" selected_model
+
+ if [ "$selected_model" = "0" ]; then
+ echo "Operation canceled, exiting..."
+ break
+ fi
+
+ if [ -z "$selected_model" ]; then
+ echo "Error: Model name cannot be empty. Please try again."
+ echo ""
+ continue
+ fi
+
+ echo "The model being switched is:$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "Switch failed: openclaw models set returned error."
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ return 0
+ else
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "gum is unavailable, returning to the old input mode."
+ sleep 1
+ continue
+ fi
+ gum style --foreground "$orange" --bold "Model management"
+ gum style --foreground "$orange" "Available models (Auth=yes):${model_count}"
+ gum style --foreground "$orange" "Current default:${default_model}"
+ echo ""
+ gum style --faint "↑↓ Select / Enter to test / Esc to exit"
+ echo ""
+
+ selected_model=$(echo "$models_list" | gum filter --placeholder "Search models (such as cli-api/gpt-5.2)" --prompt "Select model >" --indicator "➜ " --prompt.foreground "$orange" --indicator.foreground "$orange" --cursor-text.foreground "$orange" --match.foreground "$orange" --header "" --height 35)
+
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "Operation canceled, exiting..."
+ break
+ fi
+ fi
+
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
+
+ echo ""
+ echo "Detecting model:$selected_model"
+ if openclaw_model_probe "$selected_model"; then
+ openclaw_probe_status_line "Available"
+ else
+ openclaw_probe_status_line "Not available"
+ fi
+ echo "state:$OPENCLAW_PROBE_MESSAGE"
+ echo "Delay:$OPENCLAW_PROBE_LATENCY"
+ echo "summary:$OPENCLAW_PROBE_REPLY"
+ echo ""
+
+ printf "Switch to this model? [y/N, Esc returns to list]:"
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ [nN]|"") confirm_switch="no" ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
+
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "The model selection list has been returned."
+ sleep 1
+ continue
+ fi
+
+ echo "The model being switched is:$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "Switch failed: openclaw models set returned error."
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ done
+ }
+
+
+ openclaw_get_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_get_agents_dir() {
+ local user_agents="${HOME}/.openclaw/agents"
+ local root_agents="/root/.openclaw/agents"
+ if [ -d "$user_agents" ]; then
+ echo "$user_agents"
+ elif [ "$HOME" = "/root" ] && [ -d "$root_agents" ]; then
+ echo "$root_agents"
+ else
+ echo "$user_agents"
+ fi
+ }
+
+ openclaw_sync_sessions_model() {
+ local model_ref="$1"
+ [ -z "$model_ref" ] && return 1
+
+ local agents_dir
+ agents_dir=$(openclaw_get_agents_dir)
+ [ ! -d "$agents_dir" ] && return 0
+
+ local provider="${model_ref%%/*}"
+ local model="${model_ref#*/}"
+ [ "$provider" = "$model_ref" ] && { provider=""; model="$model_ref"; }
+
+ local count=0
+ local agent_dir sessions_file backup_file
+
+ for agent_dir in "$agents_dir"/*/; do
+ [ ! -d "$agent_dir" ] && continue
+ sessions_file="$agent_dir/sessions/sessions.json"
+ [ ! -f "$sessions_file" ] && continue
+
+ backup_file="${sessions_file}.bak"
+ cp "$sessions_file" "$backup_file" 2>/dev/null || continue
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if [ -n "$provider" ]; then
+ jq --arg model "$model" --arg provider "$provider" \
+ 'to_entries | map(.value.modelOverride = $model | .value.providerOverride = $provider) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ else
+ jq --arg model "$model" \
+ 'to_entries | map(.value.modelOverride = $model | del(.value.providerOverride)) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ fi
+ fi
+ done
+
+ [ "$count" -gt 0 ] && echo "✅ Synced$countThe session model of an agent is$model_ref"
+ return 0
+ }
+
+ resolve_openclaw_plugin_id() {
+ local raw_input="$1"
+ local plugin_id="$raw_input"
+
+ plugin_id="${plugin_id#@openclaw/}"
+ if [[ "$plugin_id" == @*/* ]]; then
+ plugin_id="${plugin_id##*/}"
+ fi
+ plugin_id="${plugin_id%%@*}"
+ echo "$plugin_id"
+ }
+
+ sync_openclaw_plugin_allowlist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | if (.plugins.allow | index($pid)) == null then .plugins.allow += [$pid] else . end
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ Synchronized plugins.allow whitelist:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+if plugin_id not in a:
+ a.append(plugin_id)
+
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ Synchronized plugins.allow whitelist:$plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ The plugin has been installed, but the synchronization of plugins.allow failed, please check manually:$config_file"
+ return 1
+ }
+
+ sync_openclaw_plugin_denylist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | .plugins.allow = (.plugins.allow | map(select(. != $pid)))
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ Removed from plugins.allow:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+a = [x for x in a if x != plugin_id]
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ Removed from plugins.allow:$plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ plugins.allow removal failed, please check manually:$config_file"
+ return 1
+ }
+
+
+
+
+
+
+ install_plugin() {
+ send_stats "Plug-in management"
+ while true; do
+ clear
+ echo "========================================"
+ echo "Plug-in management (installation/removal)"
+ echo "========================================"
+ echo "Current plugin list:"
+ openclaw plugins list
+ echo "--------------------------------------------------------"
+ echo "Recommended commonly used plug-in IDs (just copy the ID in brackets):"
+ echo "--------------------------------------------------------"
+ echo "📱 Communication channels:"
+ echo "- [feishu] # Feishu/Lark integration"
+ echo "- [telegram] # Telegram bot"
+ echo "- [slack] #Slack Corporate Communications"
+ echo " - [msteams] # Microsoft Teams"
+ echo "- [discord] # Discord community management"
+ echo "- [whatsapp] #WhatsApp Automation"
+ echo ""
+ echo "🧠 Memory and AI:"
+ echo "- [memory-core] # Basic memory (file retrieval)"
+ echo "- [memory-lancedb] # Enhanced memory (vector database)"
+ echo "- [copilot-proxy] # Copilot interface forwarding"
+ echo ""
+ echo "⚙️ Function expansion:"
+ echo "- [lobster] # Approval flow (with manual confirmation)"
+ echo "- [voice-call] # Voice call capability"
+ echo "- [nostr] # Encrypted private chat"
+ echo "--------------------------------------------------------"
+
+ echo "1) Install/enable plugin"
+ echo "2) Delete/disable plugins"
+ echo "0) return"
+ read -e -p "Please select an action:" plugin_action
+
+ [ "$plugin_action" = "0" ] && break
+ [ -z "$plugin_action" ] && continue
+
+ read -e -p "Please enter plugin IDs (separated by spaces, enter 0 to exit):" raw_input
+ [ "$raw_input" = "0" ] && break
+ [ -z "$raw_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ for token in $raw_input; do
+ local plugin_id
+ local plugin_full
+ plugin_id=$(resolve_openclaw_plugin_id "$token")
+ plugin_full="$token"
+ [ -z "$plugin_id" ] && continue
+
+ if [ "$plugin_action" = "1" ]; then
+ echo "🔍 Checking plugin status:$plugin_id"
+ local plugin_list
+ plugin_list=$(openclaw plugins list 2>/dev/null)
+
+ if echo "$plugin_list" | grep -qw "$plugin_id" && echo "$plugin_list" | grep "$plugin_id" | grep -q "disabled"; then
+ echo "💡 Plugin [$plugin_id] Pre-installed, activating..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ if [ -d "/usr/lib/node_modules/openclaw/extensions/$plugin_id" ]; then
+ echo "💡 Found that the plug-in exists in the system's built-in directory, try to enable it directly..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ echo "📥 Not found locally, try to download and install:$plugin_full"
+ rm -rf "${HOME}/.openclaw/extensions/$plugin_id"
+ [ "$HOME" != "/root" ] && rm -rf "/root/.openclaw/extensions/$plugin_id"
+ if openclaw plugins install "$plugin_full"; then
+ echo "✅ Download successful, activating..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "❌ Installation failed:$plugin_full"
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "🗑️ Removing/disabling plugins:$plugin_id"
+ openclaw plugins disable "$plugin_id" >/dev/null 2>&1
+ if openclaw plugins uninstall "$plugin_id"; then
+ echo "✅ Uninstalled:$plugin_id"
+ else
+ echo "⚠️ Uninstallation failed, it may be a pre-installed plug-in, only disable:$plugin_id"
+ fi
+ sync_openclaw_plugin_denylist "$plugin_id" >/dev/null 2>&1
+ success_list="$success_list $plugin_id"
+ changed=true
+ fi
+ done
+
+ echo ""
+ echo "====== Operation Summary ======"
+ echo "✅ Success:$success_list"
+ [ -n "$failed_list" ] && echo "❌ Failure:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ Skip:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 Restarting OpenClaw service to load changes..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+
+ install_skill() {
+ send_stats "Skill management"
+ while true; do
+ clear
+ echo "========================================"
+ echo "Skill management (install/remove)"
+ echo "========================================"
+ echo "Currently installed skills:"
+ openclaw skills list
+ echo "----------------------------------------"
+
+ # Output a list of recommended practical skills
+ echo "Recommended practical skills (you can directly copy the name and enter it):"
+ echo "github # Manage GitHub Issues/PR/CI (gh CLI)"
+ echo "notion # Manipulate Notion pages, databases and blocks"
+ echo "apple-notes # macOS native note management (create/edit/search)"
+ echo "apple-reminders # macOS reminder management (to-do list)"
+ echo "1password # Automate reading and injecting 1Password keys"
+ echo "gog # Google Workspace (Gmail/cloud disk/documents) all-round assistant"
+ echo "things-mac # Deep integration of Things 3 task management"
+ echo "bluebubbles # Send and receive iMessages perfectly with BlueBubbles"
+ echo "himalaya # Terminal mail management (IMAP/SMTP powerful tool)"
+ echo "summarize # One-click summary of webpage/podcast/YouTube video content"
+ echo "openhue # Control Philips Hue smart lighting scenes"
+ echo "video-frames # Video frame extraction and short clip editing (ffmpeg driver)"
+ echo "openai-whisper # Convert local audio to text (offline privacy protection)"
+ echo "coding-agent # Automatically run programming assistants such as Claude Code/Codex"
+ echo "----------------------------------------"
+
+ echo "1) Installation skills"
+ echo "2) Delete skills"
+ echo "0) return"
+ read -e -p "Please select an action:" skill_action
+
+ [ "$skill_action" = "0" ] && break
+ [ -z "$skill_action" ] && continue
+
+ read -e -p "Please enter skill names (separated by spaces, enter 0 to exit):" skill_input
+ [ "$skill_input" = "0" ] && break
+ [ -z "$skill_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ if [ "$skill_action" = "2" ]; then
+ read -e -p "Secondary confirmation: Deletion only affects the user directory ~/.openclaw/workspace/skills. Are you sure to continue? (y/N):" confirm_del
+ if [[ ! "$confirm_del" =~ ^[Yy]$ ]]; then
+ echo "Deletion canceled."
+ break_end
+ continue
+ fi
+ fi
+
+ for token in $skill_input; do
+ local skill_name
+ skill_name="$token"
+ [ -z "$skill_name" ] && continue
+
+ if [ "$skill_action" = "1" ]; then
+ local skill_found=false
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ echo "💡 Skills [$skill_name] is installed in the user directory."
+ skill_found=true
+ elif [ -d "/usr/lib/node_modules/openclaw/skills/${skill_name}" ]; then
+ echo "💡 Skills [$skill_name] is installed in the system directory."
+ skill_found=true
+ fi
+
+ if [ "$skill_found" = true ]; then
+ read -e -p "Skill [$skill_name] Already installed, do you want to reinstall? (y/N):" reinstall
+ if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
+ skipped_list="$skipped_list $skill_name"
+ continue
+ fi
+ fi
+
+ echo "Installing skills:$skill_name ..."
+ if npx clawhub install "$skill_name" --yes --no-input 2>/dev/null || npx clawhub install "$skill_name"; then
+ echo "✅ Skills$skill_nameInstallation successful."
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "❌ Installation failed:$skill_name"
+ failed_list="$failed_list $skill_name"
+ fi
+ else
+ echo "🗑️ Deleting skills:$skill_name"
+ npx clawhub uninstall "$skill_name" --yes --no-input 2>/dev/null || npx clawhub uninstall "$skill_name" >/dev/null 2>&1
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ rm -rf "${HOME}/.openclaw/workspace/skills/${skill_name}"
+ echo "✅ User skill directory has been deleted:$skill_name"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "⏭️ User skill directory not found:$skill_name"
+ skipped_list="$skipped_list $skill_name"
+ fi
+ fi
+ done
+
+ echo ""
+ echo "====== Operation Summary ======"
+ echo "✅ Success:$success_list"
+ [ -n "$failed_list" ] && echo "❌ Failure:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ Skip:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 Restarting OpenClaw service to load changes..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+openclaw_json_get_bool() {
+ local expr="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r "$expr" "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_channel_has_cfg() {
+ local channel="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r --arg c "$channel" '
+ (.channels[$c] // null) as $v
+ | if ($v | type) != "object" then
+ false
+ else
+ ([ $v
+ | to_entries[]
+ | select((.key == "enabled" or .key == "dmPolicy" or .key == "groupPolicy" or .key == "streaming") | not)
+ | .value
+ | select(. != null and . != "" and . != false)
+ ] | length) > 0
+ end
+ ' "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_dir_has_files() {
+ local dir="$1"
+ [ -d "$dir" ] && find "$dir" -type f -print -quit 2>/dev/null | grep -q .
+ }
+
+ openclaw_plugin_local_installed() {
+ local plugin="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -s "$config_file" ] && jq -e --arg p "$plugin" '.plugins.installs[$p]' "$config_file" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ # Compatible with two common directory namings:
+ # - ~/.openclaw/extensions/qqbot
+ # - ~/.openclaw/extensions/openclaw-qqbot
+ # Avoid brainless substrings, prefer exact matching and openclaw- prefix matching.
+ [ -d "${HOME}/.openclaw/extensions/${plugin}" ] \
+ || [ -d "${HOME}/.openclaw/extensions/openclaw-${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/openclaw-${plugin}" ]
+ }
+
+ openclaw_bot_status_text() {
+ local enabled="$1"
+ local configured="$2"
+ local connected="$3"
+ local abnormal="$4"
+ if [ "$abnormal" = "true" ]; then
+ echo "abnormal"
+ elif [ "$enabled" != "true" ]; then
+ echo "Not enabled"
+ elif [ "$connected" = "true" ]; then
+ echo "Connected"
+ elif [ "$configured" = "true" ]; then
+ echo "configured"
+ else
+ echo "Not configured"
+ fi
+ }
+
+ openclaw_colorize_bot_status() {
+ local status="$1"
+ case "$status" in
+ 已连接) echo -e "${gl_lv}${status}${gl_bai}" ;;
+ 已配置) echo -e "${gl_huang}${status}${gl_bai}" ;;
+ 异常) echo -e "${gl_hong}${status}${gl_bai}" ;;
+ *) echo "$status" ;;
+ esac
+ }
+
+ openclaw_print_bot_status_line() {
+ local label="$1"
+ local status="$2"
+ echo -e "- ${label}: $(openclaw_colorize_bot_status "$status")"
+ }
+
+ openclaw_show_bot_local_status_block() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local json_ok="false"
+ if [ -s "$config_file" ] && jq empty "$config_file" >/dev/null 2>&1; then
+ json_ok="true"
+ fi
+
+ local tg_enabled tg_cfg tg_connected tg_abnormal tg_status
+ tg_enabled=$(openclaw_json_get_bool '.channels.telegram.enabled // .plugins.entries.telegram.enabled // false')
+ tg_cfg=$(openclaw_channel_has_cfg "telegram")
+ tg_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/telegram"; then
+ tg_connected="true"
+ fi
+ tg_abnormal="false"
+ if [ "$tg_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ tg_abnormal="true"
+ fi
+ tg_status=$(openclaw_bot_status_text "$tg_enabled" "$tg_cfg" "$tg_connected" "$tg_abnormal")
+
+ local feishu_enabled feishu_cfg feishu_connected feishu_abnormal feishu_status
+ feishu_enabled=$(openclaw_json_get_bool '.plugins.entries.feishu.enabled // .plugins.entries["openclaw-lark"].enabled // .channels.feishu.enabled // .channels.lark.enabled // false')
+ feishu_cfg=$(openclaw_channel_has_cfg "feishu")
+ if [ "$feishu_cfg" != "true" ]; then
+ feishu_cfg=$(openclaw_channel_has_cfg "lark")
+ fi
+ feishu_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/feishu" || openclaw_dir_has_files "${HOME}/.openclaw/lark" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-lark"; then
+ feishu_connected="true"
+ fi
+ feishu_abnormal="false"
+ if [ "$feishu_enabled" = "true" ] && ! openclaw_plugin_local_installed "feishu" && ! openclaw_plugin_local_installed "lark" && ! openclaw_plugin_local_installed "openclaw-lark"; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_connected" != "true" ] && [ "$feishu_enabled" = "true" ] && [ "$feishu_cfg" = "true" ] && { openclaw_plugin_local_installed "feishu" || openclaw_plugin_local_installed "lark" || openclaw_plugin_local_installed "openclaw-lark"; }; then
+ feishu_connected="true"
+ fi
+ feishu_status=$(openclaw_bot_status_text "$feishu_enabled" "$feishu_cfg" "$feishu_connected" "$feishu_abnormal")
+
+ local wa_enabled wa_cfg wa_connected wa_abnormal wa_status
+ wa_enabled=$(openclaw_json_get_bool '.plugins.entries.whatsapp.enabled // .channels.whatsapp.enabled // false')
+ wa_cfg=$(openclaw_channel_has_cfg "whatsapp")
+ wa_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/whatsapp"; then
+ wa_connected="true"
+ fi
+ wa_abnormal="false"
+ if [ "$wa_enabled" = "true" ] && ! openclaw_plugin_local_installed "whatsapp"; then
+ wa_abnormal="true"
+ fi
+ if [ "$wa_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wa_abnormal="true"
+ fi
+ wa_status=$(openclaw_bot_status_text "$wa_enabled" "$wa_cfg" "$wa_connected" "$wa_abnormal")
+
+ local dc_enabled dc_cfg dc_connected dc_abnormal dc_status
+ dc_enabled=$(openclaw_json_get_bool '.channels.discord.enabled // .plugins.entries.discord.enabled // false')
+ dc_cfg=$(openclaw_channel_has_cfg "discord")
+ dc_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/discord"; then
+ dc_connected="true"
+ fi
+ dc_abnormal="false"
+ if [ "$dc_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ dc_abnormal="true"
+ fi
+ dc_status=$(openclaw_bot_status_text "$dc_enabled" "$dc_cfg" "$dc_connected" "$dc_abnormal")
+
+ local slack_enabled slack_cfg slack_connected slack_abnormal slack_status
+ slack_enabled=$(openclaw_json_get_bool '.plugins.entries.slack.enabled // .channels.slack.enabled // false')
+ slack_cfg=$(openclaw_channel_has_cfg "slack")
+ slack_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/slack"; then
+ slack_connected="true"
+ fi
+ slack_abnormal="false"
+ if [ "$slack_enabled" = "true" ] && ! openclaw_plugin_local_installed "slack"; then
+ slack_abnormal="true"
+ fi
+ if [ "$slack_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ slack_abnormal="true"
+ fi
+ slack_status=$(openclaw_bot_status_text "$slack_enabled" "$slack_cfg" "$slack_connected" "$slack_abnormal")
+
+ local qq_enabled qq_cfg qq_connected qq_abnormal qq_status
+ qq_enabled=$(openclaw_json_get_bool '.plugins.entries.qqbot.enabled // .channels.qqbot.enabled // false')
+ qq_cfg=$(openclaw_channel_has_cfg "qqbot")
+ qq_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/qqbot/sessions" || openclaw_dir_has_files "${HOME}/.openclaw/qqbot/data"; then
+ qq_connected="true"
+ fi
+ qq_abnormal="false"
+ if [ "$qq_enabled" = "true" ] && ! openclaw_plugin_local_installed "qqbot"; then
+ qq_abnormal="true"
+ fi
+ if [ "$qq_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ qq_abnormal="true"
+ fi
+ qq_status=$(openclaw_bot_status_text "$qq_enabled" "$qq_cfg" "$qq_connected" "$qq_abnormal")
+
+ local wx_enabled wx_cfg wx_connected wx_abnormal wx_status
+ wx_enabled=$(openclaw_json_get_bool '.plugins.entries.weixin.enabled // .plugins.entries["openclaw-weixin"].enabled // .channels.weixin.enabled // .channels["openclaw-weixin"].enabled // false')
+ wx_cfg=$(openclaw_channel_has_cfg "weixin")
+ if [ "$wx_cfg" != "true" ]; then
+ wx_cfg=$(openclaw_channel_has_cfg "openclaw-weixin")
+ fi
+ wx_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/weixin" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-weixin"; then
+ wx_connected="true"
+ fi
+ wx_abnormal="false"
+ if [ "$wx_enabled" = "true" ] && ! openclaw_plugin_local_installed "weixin" && ! openclaw_plugin_local_installed "openclaw-weixin"; then
+ wx_abnormal="true"
+ fi
+ if [ "$wx_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wx_abnormal="true"
+ fi
+ wx_status=$(openclaw_bot_status_text "$wx_enabled" "$wx_cfg" "$wx_connected" "$wx_abnormal")
+
+ echo "Local status (only local configuration/cache, no network detection):"
+ openclaw_print_bot_status_line "Telegram" "$tg_status"
+ openclaw_print_bot_status_line "Lark" "$feishu_status"
+ openclaw_print_bot_status_line "WhatsApp" "$wa_status"
+ openclaw_print_bot_status_line "Discord" "$dc_status"
+ openclaw_print_bot_status_line "Slack" "$slack_status"
+ openclaw_print_bot_status_line "QQ Bot" "$qq_status"
+ openclaw_print_bot_status_line "WeChat (Weixin)" "$wx_status"
+ }
+
+ change_tg_bot_code() {
+ send_stats "Robot docking"
+ while true; do
+ clear
+ echo "========================================"
+ echo "Robot connection and docking"
+ echo "========================================"
+ openclaw_show_bot_local_status_block
+ echo "----------------------------------------"
+ echo "1. Telegram robot docking"
+ echo "2. Feishu (Lark) robot docking"
+ echo "3. WhatsApp bot docking"
+ echo "4. QQ robot docking"
+ echo "5. WeChat robot docking"
+ echo "----------------------------------------"
+ echo "0. Return to the previous menu"
+ echo "----------------------------------------"
+ read -e -p "Please enter your choice:" bot_choice
+
+ case $bot_choice in
+ 1)
+ read -e -p "Please enter the connection code received by the TG robot (for example, NYA99R2F) (enter 0 to exit):" code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "Error: Connection code cannot be empty."; sleep 1; continue; fi
+ openclaw pairing approve telegram "$code"
+ break_end
+ ;;
+ 2)
+ npx -y @larksuite/openclaw-lark install
+ openclaw config set channels.feishu.streaming true
+ openclaw config set channels.feishu.requireMention true --json
+ break_end
+ ;;
+ 3)
+ read -e -p "Please enter the connection code received by WhatsApp (for example NYA99R2F) (enter 0 to exit):" code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "Error: Connection code cannot be empty."; sleep 1; continue; fi
+ openclaw pairing approve whatsapp "$code"
+ break_end
+ ;;
+ 4)
+ echo "QQ official docking address:"
+ echo "https://q.qq.com/qqbot/openclaw/login.html"
+ break_end
+ ;;
+ 5)
+ npx -y @tencent-weixin/openclaw-weixin-cli@latest install
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "Invalid selection, please try again."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ openclaw_backup_root() {
+ echo "${HOME}/.openclaw/backups"
+ }
+
+ openclaw_is_interactive_terminal() {
+ [ -t 0 ] && [ -t 1 ]
+ }
+
+ openclaw_has_command() {
+ command -v "$1" >/dev/null 2>&1
+ }
+
+
+ openclaw_is_safe_relpath() {
+ local rel="$1"
+ [ -z "$rel" ] && return 1
+ [[ "$rel" = /* ]] && return 1
+ [[ "$rel" == *"//"* ]] && return 1
+ [[ "$rel" == *$'\n'* ]] && return 1
+ [[ "$rel" == *$'\r'* ]] && return 1
+ case "$rel" in
+ ../*|*/../*|*/..|..)
+ return 1
+ ;;
+ esac
+ return 0
+ }
+
+ openclaw_restore_path_allowed() {
+ local mode="$1"
+ local rel="$2"
+ case "$mode" in
+ memory)
+ case "$rel" in
+ MEMORY.md|AGENTS.md|USER.md|SOUL.md|TOOLS.md|memory/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ project)
+ case "$rel" in
+ openclaw.json|workspace/*|extensions/*|skills/*|prompts/*|tools/*|telegram/*|feishu/*|whatsapp/*|discord/*|slack/*|qqbot/*|logs/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ }
+
+ openclaw_pack_backup_archive() {
+ local backup_type="$1"
+ local export_mode="$2"
+ local payload_dir="$3"
+ local output_file="$4"
+
+ local tmp_root
+ tmp_root=$(mktemp -d) || return 1
+ local pack_dir="$tmp_root/package"
+ mkdir -p "$pack_dir"
+
+ cp -a "$payload_dir" "$pack_dir/payload"
+
+ (
+ cd "$pack_dir/payload" || exit 1
+ find . -type f | sed 's|^\./||' | sort > "$pack_dir/manifest.files"
+ : > "$pack_dir/manifest.sha256"
+ while IFS= read -r f; do
+ [ -z "$f" ] && continue
+ sha256sum "$f" >> "$pack_dir/manifest.sha256"
+ done < "$pack_dir/manifest.files"
+ ) || { rm -rf "$tmp_root"; return 1; }
+
+ cat > "$pack_dir/backup.meta" </dev/null
+ ) || { echo "❌ sha256 verification failed and restoration refused"; return 1; }
+
+ echo "$pkg_dir"
+ return 0
+ }
+
+ openclaw_get_all_agent_workspaces() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json, sys, os
+try:
+ with open(sys.argv[1]) as f: data = json.load(f)
+ agents = data.get("agents", {}).get("list", [])
+ results = [{"id": "main", "ws": os.path.expanduser("~/.openclaw/workspace")}]
+ for a in agents:
+ aid = a.get("id"); ws = a.get("workspace")
+ if aid and ws and aid != "main": results.append({"id": aid, "ws": os.path.expanduser(ws)})
+ print(json.dumps(results))
+except: print("[]")
+PY
+ else
+ echo '[{"id": "main", "ws": "'"${HOME}"'/.openclaw/workspace"}]'
+ fi
+ }
+
+ openclaw_memory_backup_export() {
+ send_stats "OpenClaw memory full backup"
+ local backup_root=$(openclaw_backup_root)
+ local ts=$(date +%Y%m%d-%H%M%S)
+ local out_file="$backup_root/openclaw-memory-full-${ts}.tar.gz"
+ mkdir -p "$backup_root"
+ local tmp_payload=$(mktemp -d) || return 1
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c "import json, sys, os, shutil;
+workspaces = json.loads(sys.argv[1]); tmp_payload = sys.argv[2]
+for item in workspaces:
+ aid = item['id']; ws = item['ws']
+ if not os.path.isdir(ws): continue
+ target_dir = os.path.join(tmp_payload, 'agents', aid)
+ os.makedirs(target_dir, exist_ok=True)
+ for f in ['MEMORY.md', 'memory']:
+ src = os.path.join(ws, f)
+ if os.path.exists(src):
+ if os.path.isfile(src): shutil.copy2(src, target_dir)
+ else: shutil.copytree(src, os.path.join(target_dir, f), dirs_exist_ok=True)
+" "$workspaces_json" "$tmp_payload"
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ No backup memory file found"; rm -rf "$tmp_payload"; break_end; return 1
+ fi
+ if openclaw_pack_backup_archive "memory-full" "multi-agent" "$tmp_payload" "$out_file"; then
+ echo "✅ Full memory backup completed (including multi-agent):$out_file"; openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ Full memory backup failed"
+ fi
+ rm -rf "$tmp_payload"; break_end
+ }
+
+ openclaw_memory_backup_import() {
+ send_stats "OpenClaw memory full restoration"
+ local archive_path=$(openclaw_read_import_path "Restore the full amount of memory (supports multi-agent)")
+ [ -z "$archive_path" ] && { echo "❌ No path entered"; break_end; return 1; }
+ local tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir=$(openclaw_prepare_import_archive "memory-full" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c 'import json, sys, os, shutil;
+workspaces = {item["id"]: item["ws"] for item in json.loads(sys.argv[1])};
+payload_dir = sys.argv[2]; agents_root = os.path.join(payload_dir, "agents")
+if os.path.isdir(agents_root):
+ for aid in os.listdir(agents_root):
+ if aid in workspaces:
+ src_agent_dir = os.path.join(agents_root, aid); dest_ws = workspaces[aid]
+ os.makedirs(dest_ws, exist_ok=True)
+ for f in os.listdir(src_agent_dir):
+ src = os.path.join(src_agent_dir, f); dest = os.path.join(dest_ws, f)
+ if os.path.isfile(src): shutil.copy2(src, dest)
+ else: shutil.copytree(src, dest, dirs_exist_ok=True)
+ print(f"✅ Agent memory restored: {aid}")' "$workspaces_json" "$pkg_dir/payload"
+ rm -rf "$tmp_unpack"; echo "✅ Full memory restoration completed"; break_end
+ }
+
+
+ openclaw_project_backup_export() {
+ send_stats "OpenClaw project backup"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ if [ ! -d "$openclaw_root" ]; then
+ echo "❌ OpenClaw root directory not found:$openclaw_root"
+ break_end
+ return 1
+ fi
+
+ echo "Backup mode:"
+ echo "1. Safe mode (default, recommended): workspace + openclaw.json + extensions/skills/prompts/tools (if exists)"
+ echo "2. Complete mode (contains more states, higher sensitivity risk)"
+ read -e -p "Please select backup mode (default 1):" export_mode
+ [ -z "$export_mode" ] && export_mode="1"
+
+ local mode_label="safe"
+ local tmp_payload
+ tmp_payload=$(mktemp -d) || return 1
+
+ if [ "$export_mode" = "2" ]; then
+ mode_label="full"
+ for d in workspace extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in telegram feishu whatsapp discord slack qqbot logs; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ else
+ [ -d "$openclaw_root/workspace" ] && cp -a "$openclaw_root/workspace" "$tmp_payload/"
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ fi
+
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ No backupable OpenClaw project content found"
+ rm -rf "$tmp_payload"
+ break_end
+ return 1
+ fi
+
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ local out_file="$backup_root/openclaw-project-${mode_label}-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ if openclaw_pack_backup_archive "openclaw-project" "$mode_label" "$tmp_payload" "$out_file"; then
+ echo "✅ OpenClaw project backup completed (${mode_label}): $out_file"
+ openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ OpenClaw project backup failed"
+ fi
+
+ rm -rf "$tmp_payload"
+ break_end
+ }
+
+ openclaw_project_backup_import() {
+ send_stats "OpenClaw project restore"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ mkdir -p "$openclaw_root"
+
+ echo "⚠️ High-risk operation: Project restore will overwrite OpenClaw configuration and workspace content."
+ echo "⚠️ Manifest/sha256 verification, whitelist restoration, gateway shutdown and health check will be performed before restoration."
+ read -e -p "Please enter the confirmation word [I am aware of the high risk and continue to restore] to continue:" confirm_text
+ if [ "$confirm_text" != "I am aware of the high risk and continue to restore" ]; then
+ echo "❌ The confirmation word does not match and the restore has been cancelled."
+ break_end
+ return 1
+ fi
+
+ local archive_path
+ archive_path=$(openclaw_read_import_path "Please enter the OpenClaw project backup package path")
+ [ -z "$archive_path" ] && { echo "❌ No backup path entered"; break_end; return 1; }
+
+ local tmp_unpack
+ tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir
+ pkg_dir=$(openclaw_prepare_import_archive "openclaw-project" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+
+ local invalid=0
+ local valid_list
+ valid_list=$(mktemp)
+ while IFS= read -r rel; do
+ [ -z "$rel" ] && continue
+ if ! openclaw_is_safe_relpath "$rel" || ! openclaw_restore_path_allowed project "$rel"; then
+ echo "❌ Illegal or unauthorized path detected:$rel"
+ invalid=1
+ break
+ fi
+ echo "$rel" >> "$valid_list"
+ done < "$pkg_dir/manifest.files"
+
+ if [ "$invalid" -ne 0 ]; then
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "❌ Restore aborted: Insecure path exists"
+ break_end
+ return 1
+ fi
+
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "⏸️ Stop OpenClaw gateway before restoring..."
+ openclaw gateway stop >/dev/null 2>&1
+ fi
+
+ while IFS= read -r rel; do
+ mkdir -p "$openclaw_root/$(dirname "$rel")"
+ cp -a "$pkg_dir/payload/$rel" "$openclaw_root/$rel"
+ done < "$valid_list"
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "▶️ Start OpenClaw gateway after restoration..."
+ openclaw gateway start >/dev/null 2>&1
+ sleep 2
+ echo "🩺 gateway health check:"
+ openclaw gateway status || true
+ fi
+
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "✅ OpenClaw project restoration completed"
+ break_end
+ }
+
+ openclaw_backup_detect_type() {
+ local file_name="$1"
+ if [[ "$file_name" == openclaw-memory-full-*.tar.gz ]]; then
+ echo "memory backup file"
+ elif [[ "$file_name" == openclaw-project-*.tar.gz ]]; then
+ echo "Project backup file"
+ else
+ echo "Other backup files"
+ fi
+ }
+
+ openclaw_backup_collect_files() {
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ mapfile -t OPENCLAW_BACKUP_FILES < <(find "$backup_root" -maxdepth 1 -type f -name '*.tar.gz' -printf '%f\n' | sort -r)
+ }
+
+
+ openclaw_backup_render_file_list() {
+ local backup_root i file_name file_path file_type file_size file_time
+ local has_memory=0 has_project=0 has_other=0
+ backup_root=$(openclaw_backup_root)
+ openclaw_backup_collect_files
+
+ echo "Backup directory:$backup_root"
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ echo "No backup files yet"
+ return 0
+ fi
+
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_type=$(openclaw_backup_detect_type "${OPENCLAW_BACKUP_FILES[$i]}")
+ case "$file_type" in
+ "memory backup file") has_memory=1 ;;
+ "Project backup file") has_project=1 ;;
+ "Other backup files") has_other=1 ;;
+ esac
+ done
+
+ if [ "$has_memory" -eq 1 ]; then
+ echo "memory backup file"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "memory backup file" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_project" -eq 1 ]; then
+ echo "Project backup file"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "Project backup file" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_other" -eq 1 ]; then
+ echo "Other backup files"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "Other backup files" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+ }
+
+ openclaw_backup_file_exists_in_list() {
+ local target_file="$1"
+ local item
+ for item in "${OPENCLAW_BACKUP_FILES[@]}"; do
+ [ "$item" = "$target_file" ] && return 0
+ done
+ return 1
+ }
+
+ openclaw_backup_delete_file() {
+ send_stats "OpenClaw delete backup files"
+ local backup_root backup_root_real user_input target_file target_path target_type
+ backup_root=$(openclaw_backup_root)
+
+ openclaw_backup_render_file_list
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ break_end
+ return 0
+ fi
+
+ read -e -p "Please enter the file name or full path to delete (0 to cancel):" user_input
+ if [ "$user_input" = "0" ]; then
+ echo "Deletion canceled."
+ break_end
+ return 0
+ fi
+ if [ -z "$user_input" ]; then
+ echo "❌ The input cannot be empty."
+ break_end
+ return 1
+ fi
+
+ backup_root_real=$(realpath -m "$backup_root")
+ if [[ "$user_input" == /* ]]; then
+ target_path=$(realpath -m "$user_input")
+ case "$target_path" in
+ "$backup_root_real"/*) ;;
+ *)
+ echo "❌ Path out of bounds: Only files in the backup root directory are allowed to be deleted."
+ break_end
+ return 1
+ ;;
+ esac
+ target_file=$(basename "$target_path")
+ else
+ target_file=$(basename -- "$user_input")
+ target_path="$backup_root/$target_file"
+ fi
+
+ if [ ! -f "$target_path" ]; then
+ echo "❌ The target file does not exist:$target_path"
+ break_end
+ return 1
+ fi
+
+ if ! openclaw_backup_file_exists_in_list "$target_file"; then
+ echo "❌ The target file is not in the current backup list."
+ break_end
+ return 1
+ fi
+
+ target_type=$(openclaw_backup_detect_type "$target_file")
+
+ echo "About to be deleted: [$target_type] $target_path"
+ read -e -p "First confirmation: Enter yes to confirm and continue:" confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "Deletion canceled."
+ break_end
+ return 0
+ fi
+ read -e -p "Secondary confirmation: Enter DELETE to delete:" confirm_step2
+ if [ "$confirm_step2" != "DELETE" ]; then
+ echo "Deletion canceled."
+ break_end
+ return 0
+ fi
+
+ if rm -f -- "$target_path"; then
+ echo "✅ Deletion successful:$target_file"
+ else
+ echo "❌ Delete failed:$target_file"
+ fi
+ break_end
+ }
+
+ openclaw_backup_list_files() {
+ openclaw_backup_render_file_list
+ break_end
+ }
+
+ openclaw_memory_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_memory_config_get() {
+ local key="$1"
+ local default_value="${2:-}"
+ local value
+ value=$(openclaw config get "$key" 2>/dev/null | head -n 1 | sed -e 's/^"//' -e 's/"$//')
+ if [ -z "$value" ] || [ "$value" = "null" ] || [ "$value" = "undefined" ]; then
+ echo "$default_value"
+ return 0
+ fi
+ echo "$value"
+ }
+
+ openclaw_memory_config_set() {
+ local key="$1"
+ shift
+ openclaw config set "$key" "$@" >/dev/null 2>&1
+ }
+
+ openclaw_memory_config_unset() {
+ local key="$1"
+ openclaw config unset "$key" >/dev/null 2>&1
+ }
+
+ openclaw_memory_cleanup_legacy_keys() {
+ openclaw_memory_config_unset "memory.local"
+ }
+
+ openclaw_memory_list_agents() {
+ if command -v openclaw >/dev/null 2>&1; then
+ local agents_json
+ agents_json=$(openclaw agents list --json 2>/dev/null || true)
+ if [ -n "$agents_json" ]; then
+ python3 - "$agents_json" <<'PY'
+import json, os, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ data = None
+seen = set()
+results = []
+if isinstance(data, list):
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ if not aid or aid in seen:
+ continue
+ ws = item.get('workspace') or ("~/.openclaw/workspace" if aid == 'main' else f"~/.openclaw/workspace-{aid}")
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+if results:
+ for aid, ws in results:
+ print(f"{aid}\t{ws}")
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+ [ $? -eq 0 ] && return 0
+ fi
+ fi
+ local config_path
+ config_path=$(openclaw_memory_config_file)
+ python3 - "$config_path" <<'PY'
+import json, os, sys
+config_path = sys.argv[1]
+results = [("main", os.path.expanduser("~/.openclaw/workspace"))]
+seen = {"main"}
+try:
+ if os.path.exists(config_path):
+ with open(config_path, encoding='utf-8') as f:
+ data = json.load(f)
+ agents = data.get('agents', {}).get('list', [])
+ if isinstance(agents, list):
+ for item in agents:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ ws = item.get('workspace')
+ if not aid or aid in seen:
+ continue
+ if not ws:
+ ws = f"~/.openclaw/workspace-{aid}"
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+except Exception:
+ pass
+for aid, ws in results:
+ print(f"{aid}\t{ws}")
+PY
+ }
+
+ openclaw_memory_status_value() {
+ local key="$1"
+ local agent_id="${2:-}"
+ if [ -n "$agent_id" ]; then
+ openclaw memory status --agent "$agent_id" 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ else
+ openclaw memory status 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ fi
+ }
+
+ openclaw_memory_expand_path() {
+ local raw_path="$1"
+ if [ -z "$raw_path" ]; then
+ echo ""
+ return 0
+ fi
+ raw_path=$(echo "$raw_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [[ "$raw_path" == ~* ]]; then
+ echo "${raw_path/#\~/$HOME}"
+ else
+ echo "$raw_path"
+ fi
+ }
+
+ openclaw_memory_rebuild_index_single() {
+ local agent_id="${1:-main}"
+ local store_raw store_file ts backup_file
+ store_raw=$(openclaw_memory_status_value "Store" "$agent_id")
+ store_file=$(openclaw_memory_expand_path "$store_raw")
+ if [ -z "$store_file" ] || [ ! -f "$store_file" ]; then
+ echo "⚠️ [$agent_id] Index library file not found, may be empty or non-existent."
+ echo "Store raw value: ${store_raw:-}"
+ echo "Reindexing will still be performed."
+ else
+ ts=$(date +%Y%m%d_%H%M%S)
+ backup_file="${store_file}.bak.${ts}"
+ if mv "$store_file" "$backup_file"; then
+ echo "✅ [$agent_id] Index backed up:$backup_file"
+ else
+ echo "⚠️ [$agent_id] Index backup failed, continue rebuilding."
+ fi
+ fi
+ openclaw memory index --agent "$agent_id" --force
+ }
+
+ openclaw_memory_rebuild_index_safe() {
+ local agent_id="${1:-main}"
+ openclaw_memory_rebuild_index_single "$agent_id"
+ openclaw gateway restart
+ echo "✅ The index has been rebuilt and the gateway has been automatically restarted."
+ echo ""
+ openclaw_memory_render_status
+ }
+
+ openclaw_memory_rebuild_index_all() {
+ local count=0
+ local agent_lines agent_id workspace
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id workspace; do
+ [ -z "$agent_id" ] && continue
+ openclaw_memory_rebuild_index_single "$agent_id"
+ count=$((count+1))
+ done </dev/null)
+ if [ -z "$json_output" ]; then
+ echo "Failed to obtain memory status (openclaw memory status --json no output)"
+ return 1
+ fi
+ python3 - "$json_output" <<'PY'
+import json, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ print("Failed to get memory status (JSON parsing error)")
+ raise SystemExit(1)
+if not isinstance(data, list) or len(data) == 0:
+ print("No agent memory state detected.")
+ raise SystemExit(0)
+first = True
+for entry in data:
+ if not isinstance(entry, dict):
+ continue
+ agent_id = entry.get("agentId", "?")
+ s = entry.get("status", {})
+ if not isinstance(s, dict):
+ s = {}
+ if not first:
+ print("")
+ first = False
+ print("Agent: %s" % agent_id)
+ backend = s.get("backend") or s.get("provider") or "-"
+ print("Underlying solution: %s" % backend)
+ files = s.get("files", 0)
+ chunks = s.get("chunks", 0)
+ print("Included: %s files / %s blocks" % (files, chunks))
+ dirty = s.get("dirty")
+ dirty_str = "yes" if dirty else "no"
+ print("To be refreshed: %s" % dirty_str)
+ vec = s.get("vector", {})
+ if isinstance(vec, dict) and vec.get("enabled"):
+ vec_str = "ready" if vec.get("available") else "Enabled (not available)"
+ else:
+ vec_str = "Not enabled"
+ print("Vector library: %s" % vec_str)
+ ws = s.get("workspaceDir") or "-"
+ print("Workspace: %s" % ws)
+ db = s.get("dbPath") or "-"
+ print("Index library: %s" % db)
+ scan = entry.get("scan", {})
+ if isinstance(scan, dict):
+ issues = scan.get("issues", [])
+ if issues:
+ for issue in issues[:3]:
+ print(" ⚠️ %s" % issue)
+PY
+ }
+
+ openclaw_memory_get_backend() {
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "local" ]; then
+ echo "builtin"
+ else
+ echo "$backend"
+ fi
+ }
+
+ openclaw_memory_get_local_model_path() {
+ openclaw_memory_config_get "agents.defaults.memorySearch.local.modelPath"
+ }
+
+ openclaw_memory_local_model_status() {
+ local model_path="$1"
+ if [ -z "$model_path" ]; then
+ echo "missing"
+ return
+ fi
+ if [[ "$model_path" == hf:* ]]; then
+ echo "hf"
+ return
+ fi
+ if [ -f "$model_path" ]; then
+ echo "ok"
+ else
+ echo "missing"
+ fi
+ }
+
+ openclaw_memory_qmd_available() {
+ if command -v qmd >/dev/null 2>&1; then
+ echo "true"
+ return
+ fi
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "qmd" ]; then
+ echo "true"
+ return
+ fi
+ echo "false"
+ }
+
+ openclaw_memory_probe_url() {
+ local url="$1"
+ if ! command -v curl >/dev/null 2>&1; then
+ echo "unknown"
+ return
+ fi
+ if [ -z "$url" ]; then
+ echo "unknown"
+ return
+ fi
+ if curl -I -m 2 -s "$url" >/dev/null 2>&1; then
+ echo "ok"
+ else
+ echo "fail"
+ fi
+ }
+
+ openclaw_memory_recommend() {
+ local qmd_ok model_path model_status hf_ok mirror_ok
+ qmd_ok=$(openclaw_memory_qmd_available)
+ model_path=$(openclaw_memory_get_local_model_path)
+ model_status=$(openclaw_memory_local_model_status "$model_path")
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+
+ OPENCLAW_MEMORY_RECOMMEND_REASON=()
+ if [ "$qmd_ok" = "true" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD is available")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD not detected")
+ fi
+ if [ -n "$model_path" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("Local model path:$model_path")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("Local model path not configured")
+ fi
+ case "$model_status" in
+ ok) OPENCLAW_MEMORY_RECOMMEND_REASON+=("Local model file exists") ;;
+ hf) OPENCLAW_MEMORY_RECOMMEND_REASON+=("Model comes from HF download source (may be slow/failed in China)") ;;
+ *) OPENCLAW_MEMORY_RECOMMEND_REASON+=("The local model file does not exist or is unavailable") ;;
+ esac
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co is accessible")
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("hf-mirror.com can be accessed")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co / hf-mirror.com may be unreachable (suspected domestic/restricted network)")
+ fi
+
+ if [ "$qmd_ok" = "true" ]; then
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && { [ "$hf_ok" = "ok" ] || [ "$mirror_ok" = "ok" ]; }; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && [ "$hf_ok" = "fail" ] && [ "$mirror_ok" = "fail" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ else
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ fi
+ }
+
+
+ openclaw_memory_detect_region() {
+ OPENCLAW_MEMORY_COUNTRY="unknown"
+ OPENCLAW_MEMORY_USE_MIRROR="false"
+ if command -v curl >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_COUNTRY=$(curl -s -m 2 ipinfo.io/country | tr -d '
+' | tr -d '
+')
+ fi
+ case "$OPENCLAW_MEMORY_COUNTRY" in
+ CN|HK)
+ OPENCLAW_MEMORY_USE_MIRROR="true"
+ ;;
+ esac
+ }
+
+ openclaw_memory_select_sources() {
+ local hf_ok mirror_ok
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+ OPENCLAW_MEMORY_HF_OK="$hf_ok"
+ OPENCLAW_MEMORY_MIRROR_OK="$mirror_ok"
+ if [ "$OPENCLAW_MEMORY_USE_MIRROR" = "true" ]; then
+ if [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ elif [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://gh.kejilion.pro/"
+ else
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://"
+ fi
+ }
+
+ openclaw_memory_download_file() {
+ local url="$1"
+ local dest="$2"
+ mkdir -p "$(dirname "$dest")"
+ if command -v curl >/dev/null 2>&1; then
+ curl -L --fail --retry 2 -o "$dest" "$url"
+ return $?
+ fi
+ if command -v wget >/dev/null 2>&1; then
+ wget -O "$dest" "$url"
+ return $?
+ fi
+ echo "❌ Curl or wget is not detected and cannot be downloaded."
+ return 1
+ }
+
+ openclaw_memory_check_sqlite() {
+ if ! command -v sqlite3 >/dev/null 2>&1; then
+ echo "⚠️ sqlite3 is not detected, QMD may not function properly."
+ return 1
+ fi
+ local ver
+ ver=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
+ echo "✅ sqlite3 available: ${ver:-unknown}"
+ echo "ℹ️ sqlite extension support cannot be reliably detected and will continue."
+ return 0
+ }
+
+ openclaw_memory_ensure_bun() {
+ if [ -x "$HOME/.bun/bin/bun" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun already exists"
+ return 0
+ fi
+ echo "⬇️ Install bun..."
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL https://bun.sh/install | bash
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO- https://bun.sh/install | bash
+ else
+ echo "❌ Unable to install bun as curl or wget is not detected."
+ return 1
+ fi
+ if [ -d "$HOME/.bun/bin" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun installation completed"
+ return 0
+ fi
+ echo "❌ bun installation failed"
+ return 1
+ }
+
+ openclaw_memory_ensure_qmd() {
+ local qmd_path
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -n "$qmd_path" ]; then
+ if qmd --version >/dev/null 2>&1; then
+ echo "✅ qmd already exists and is available:$qmd_path"
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ return 0
+ else
+ echo "⚠️ The qmd command exists but the module is damaged, reinstall..."
+ fi
+ fi
+ echo "⬇️ Install qmd via npm: @tobilu/qmd"
+ npm install -g @tobilu/qmd
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -z "$qmd_path" ]; then
+ echo "❌ qmd installation failed"
+ return 1
+ fi
+ if ! qmd --version >/dev/null 2>&1; then
+ echo "❌ qmd still cannot run after installation"
+ return 1
+ fi
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ echo "✅ qmd installation completed:$qmd_path"
+ return 0
+ }
+
+ openclaw_memory_render_auto_summary() {
+ echo "---------------------------------------"
+ echo "✅ Environment ready"
+ echo "Scheme: ${OPENCLAW_MEMORY_AUTO_SCHEME:-unknown}"
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "Mode: Write configuration only (not installed/not downloaded)"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "Index: Executed"
+ else
+ echo "Index: skipped"
+ fi
+ if [ "$OPENCLAW_MEMORY_RESTARTED" = "true" ]; then
+ echo "Restart: executed"
+ else
+ echo "Restart: skipped"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ echo "qmd: $OPENCLAW_MEMORY_QMD_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_MODEL_PATH" ]; then
+ echo "Model:$OPENCLAW_MEMORY_MODEL_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_COUNTRY" ]; then
+ echo "area:$OPENCLAW_MEMORY_COUNTRY"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_HF_BASE" ]; then
+ echo "Download source:$OPENCLAW_MEMORY_HF_BASE"
+ fi
+ echo "Final status:"
+ openclaw_memory_render_status
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_auto_confirm() {
+ local scheme_label="$1"
+ OPENCLAW_MEMORY_PREHEAT="true"
+ OPENCLAW_MEMORY_RESTARTED="false"
+ OPENCLAW_MEMORY_CONFIG_ONLY="false"
+ echo "Automatic deployment is about to be performed (verbose mode)"
+ echo "Target plan:$scheme_label"
+ echo "Region: ${OPENCLAW_MEMORY_COUNTRY:-unknown}"
+ echo "Mirror source detection: huggingface.co=${OPENCLAW_MEMORY_HF_OK:-unknown} hf-mirror.com=${OPENCLAW_MEMORY_MIRROR_OK:-unknown}"
+ echo "Download source: ${OPENCLAW_MEMORY_HF_BASE:-unknown}"
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_PATH" ]; then
+ echo "Estimated download path:$OPENCLAW_MEMORY_EXPECT_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_SIZE" ]; then
+ echo "Possible traffic/disk usage:$OPENCLAW_MEMORY_EXPECT_SIZE"
+ else
+ echo "Possible traffic/disk usage: Depends on actual situation"
+ fi
+ echo "After confirmation, it will automatically install/download, write configuration, build index and restart the gateway."
+ echo "Advanced options: Enter config to write configuration only (no installation, no download, no indexing, no restart)"
+ read -e -p "Enter yes to confirm to continue (default N):" confirm_step
+ case "$confirm_step" in
+ yes|YES)
+ OPENCLAW_MEMORY_PREHEAT="true"
+ ;;
+ config|CONFIG)
+ OPENCLAW_MEMORY_CONFIG_ONLY="true"
+ OPENCLAW_MEMORY_PREHEAT="false"
+ ;;
+ *)
+ echo "Autodeployment canceled."
+ return 1
+ ;;
+ esac
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "⚠️ Selected to only write configuration, no installation or download"
+ else
+ echo "✅ The index will be automatically built and the gateway will be restarted"
+ fi
+ return 0
+ }
+
+ openclaw_memory_auto_setup_qmd() {
+ echo "🔍 Detect QMD environment"
+ openclaw_memory_cleanup_legacy_keys
+ openclaw_memory_check_sqlite || true
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ if command -v qmd >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_QMD_PATH=$(command -v qmd)
+ else
+ OPENCLAW_MEMORY_QMD_PATH="qmd"
+ fi
+ else
+ openclaw_memory_ensure_qmd || return 1
+ fi
+ local backend
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ]; then
+ echo "✅ memory.backend is already qmd"
+ else
+ openclaw_memory_config_set "memory.backend" "qmd"
+ echo "✅ memory.backend=qmd has been set"
+ fi
+ local qmd_cmd
+ qmd_cmd=$(openclaw_memory_config_get "memory.qmd.command")
+ if [ -z "$qmd_cmd" ] || [[ "$qmd_cmd" != /* ]] || [ "$qmd_cmd" != "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ openclaw_memory_config_set "memory.qmd.command" "$OPENCLAW_MEMORY_QMD_PATH"
+ echo "✅ Written to memory.qmd.command:$OPENCLAW_MEMORY_QMD_PATH"
+ else
+ echo "✅ memory.qmd.command is correct"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "🔥 Warm index (possibly download models)"
+ openclaw_memory_prepare_workspace_all
+ local preh_agent_lines preh_agent_id preh_workspace
+ preh_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r preh_agent_id preh_workspace; do
+ [ -z "$preh_agent_id" ] && continue
+ openclaw memory index --agent "$preh_agent_id" --force
+ done </dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ OPENCLAW_MEMORY_RESTARTED="true"
+ openclaw_memory_render_auto_summary
+ return 0
+ }
+
+ openclaw_memory_auto_setup_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "Automatic deployment of memory solutions"
+ echo "======================================="
+ echo "1. QMD"
+ echo "2. Local"
+ echo "3. Auto (automatic selection)"
+ echo "0. Return to the previous level"
+ echo "---------------------------------------"
+ read -e -p "Please enter your choice:" auto_choice
+ case "$auto_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "Invalid selection, please try again."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_apply_scheme() {
+ local scheme="$1"
+ openclaw_memory_cleanup_legacy_keys
+ case "$scheme" in
+ qmd)
+ openclaw_memory_config_set "memory.backend" "qmd"
+ if [ $? -ne 0 ]; then
+ echo "❌ Failed to write configuration"
+ return 1
+ fi
+ openclaw_memory_config_set "memory.qmd.command" "qmd" >/dev/null 2>&1
+ ;;
+ local)
+ openclaw_memory_config_set "memory.backend" "builtin"
+ if [ $? -ne 0 ]; then
+ echo "❌ Failed to write configuration"
+ return 1
+ fi
+ openclaw_memory_config_set "agents.defaults.memorySearch.provider" "local" >/dev/null 2>&1
+ ;;
+ *)
+ echo "❌ Unknown solution:$scheme"
+ return 1
+ esac
+ echo "✅ Memory scheme configuration has been updated"
+ return 0
+ }
+
+ openclaw_memory_offer_restart() {
+ echo "The configuration has been written and needs to be restarted to take effect after the OpenClaw gateway is restarted."
+ read -e -p "Restart OpenClaw Gateway now? (Y/n):" restart_choice
+ if [[ "$restart_choice" =~ ^[Nn]$ ]]; then
+ echo "Restart skipped, can be performed later: openclaw gateway restart"
+ return 0
+ fi
+ if declare -F start_gateway >/dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ }
+
+ openclaw_memory_fix_index() {
+ local backend include_dm
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ] && ! command -v qmd >/dev/null 2>&1; then
+ echo "⚠️ The current scheme is detected as QMD, but the qmd command is not installed."
+ echo "You can switch to Local, or install bun + qmd and try again."
+ fi
+ include_dm=$(openclaw config get memory.qmd.includeDefaultMemory 2>/dev/null)
+ echo "======================================="
+ echo "Index repair diagnostics"
+ echo "======================================="
+ echo "Current includeDefaultMemory: ${include_dm:-not set}"
+ echo ""
+ if [ "$include_dm" = "false" ]; then
+ echo "⚠️ includeDefaultMemory=false detected"
+ echo "This causes the default memory files (MEMORY.md + memory/*.md) to not be indexed"
+ echo "So Indexed will always display 0/N"
+ echo ""
+ read -e -p "Revert to true and rebuild the index? (Y/n):" fix_choice
+ if [[ ! "$fix_choice" =~ ^[Nn]$ ]]; then
+ openclaw_memory_config_set "memory.qmd.includeDefaultMemory" true
+ if [ $? -ne 0 ]; then
+ echo "❌ Failed to write configuration"
+ break_end
+ return 1
+ fi
+ echo "✅ Restored includeDefaultMemory=true"
+ openclaw_memory_rebuild_index_all
+ else
+ echo "Canceled."
+ fi
+ else
+ echo "includeDefaultMemory configuration is normal."
+ echo "Will be executed: Clean old indexes → Fully rebuild all agent indexes"
+ echo ""
+ read -e -p "Confirm execution? (Y/n):" confirm_fix
+ if [[ ! "$confirm_fix" =~ ^[Nn]$ ]]; then
+ openclaw_memory_rebuild_index_all
+ else
+ echo "Canceled."
+ fi
+ fi
+ break_end
+ }
+
+ openclaw_memory_scheme_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw memory solution"
+ echo "======================================="
+ local backend current_label
+ backend=$(openclaw_memory_get_backend)
+ case "$backend" in
+ qmd) current_label="QMD" ;;
+ builtin|local) current_label="Local" ;;
+ *) current_label="Not configured" ;;
+ esac
+ echo "Current plan:$current_label"
+ echo ""
+ echo "QMD: lightweight index, relying on the qmd command (suitable for network constraints)"
+ echo "Local: local vector retrieval, dependent on embedding model file"
+ echo "Auto: automatic recommendation (based on availability + network detection)"
+ echo "---------------------------------------"
+ echo "1. Switch QMD (automatic deployment/skip if already installed)"
+ echo "2. Switch to Local (automatic deployment/skip if already installed)"
+ echo "3. Auto (automatic recommendation and automatic deployment)"
+ echo "0. Return to the previous level"
+ echo "---------------------------------------"
+ read -e -p "Please enter your choice:" scheme_choice
+ case "$scheme_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "Invalid selection, please try again."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_file_collect() {
+ OPENCLAW_MEMORY_FILES=()
+ OPENCLAW_MEMORY_FILE_LABELS=()
+ local agent_lines agent_id base_dir memory_dir memory_file rel
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id base_dir; do
+ [ -z "$agent_id" ] && continue
+ memory_dir="$base_dir/memory"
+ memory_file="$base_dir/MEMORY.md"
+ if [ -f "$memory_file" ]; then
+ OPENCLAW_MEMORY_FILES+=("$memory_file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/MEMORY.md")
+ fi
+ if [ -d "$memory_dir" ]; then
+ while IFS= read -r file; do
+ [ -f "$file" ] || continue
+ rel="${file#$base_dir/}"
+ OPENCLAW_MEMORY_FILES+=("$file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/$rel")
+ done < <(find "$memory_dir" -type f -name '*.md' | sort)
+ fi
+ done </dev/null || stat -c %y "$file" | awk '{print $1" "$2}')
+ printf "%s | %s | %s | %s\\n" "$((i+1))" "$rel" "$size" "$mtime"
+ done
+ }
+
+ openclaw_memory_view_file() {
+ local file="$1"
+ [ -f "$file" ] || {
+ echo "❌ File does not exist:$file"
+ return 1
+ }
+ local total_lines
+ total_lines=$(wc -l < "$file" 2>/dev/null || echo 0)
+ local default_lines=120
+ local start_line count
+ echo "document:$file"
+ echo "Total number of rows:$total_lines"
+ read -e -p "Please enter the starting line (Press Enter to default to the end of$default_linesOK):" start_line
+ read -e -p "Please enter the number of rows to display (default is to press Enter$default_lines): " count
+ [ -z "$count" ] && count=$default_lines
+ if [ -z "$start_line" ]; then
+ if [ "$total_lines" -le "$count" ]; then
+ start_line=1
+ else
+ start_line=$((total_lines - count + 1))
+ fi
+ fi
+ if ! [[ "$start_line" =~ ^[0-9]+$ ]] || ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ echo "❌ Please enter a valid number."
+ return 1
+ fi
+ if [ "$start_line" -lt 1 ]; then
+ start_line=1
+ fi
+ if [ "$count" -le 0 ]; then
+ echo "❌ Number of rows must be greater than 0."
+ return 1
+ fi
+ local end_line=$((start_line + count - 1))
+ if [ "$end_line" -gt "$total_lines" ]; then
+ end_line=$total_lines
+ fi
+ if [ "$total_lines" -eq 0 ]; then
+ echo "(empty file)"
+ return 0
+ fi
+ echo "---------------------------------------"
+ sed -n "${start_line},${end_line}p" "$file"
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_files_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw memory file"
+ echo "======================================="
+ openclaw_memory_file_render_list
+ echo "---------------------------------------"
+ read -e -p "Please enter the file number to view (return 0):" file_choice
+ if [ "$file_choice" = "0" ]; then
+ return 0
+ fi
+ if ! [[ "$file_choice" =~ ^[0-9]+$ ]]; then
+ echo "Invalid selection, please try again."
+ sleep 1
+ continue
+ fi
+ openclaw_memory_file_collect
+ if [ ${#OPENCLAW_MEMORY_FILES[@]} -eq 0 ]; then
+ read -p "Memory file not found, press Enter to return..."
+ return 0
+ fi
+ local idx=$((file_choice-1))
+ if [ "$idx" -lt 0 ] || [ "$idx" -ge ${#OPENCLAW_MEMORY_FILES[@]} ]; then
+ echo "Invalid number, please try again."
+ sleep 1
+ continue
+ fi
+ openclaw_memory_view_file "${OPENCLAW_MEMORY_FILES[$idx]}"
+ read -p "Press Enter to return to the list..."
+ done
+ }
+
+
+ openclaw_memory_search_test() {
+ read -e -p "Enter search keywords:" query
+ if [ -z "$query" ]; then
+ echo "Keywords cannot be empty."
+ return 1
+ fi
+ echo "Searching memory..."
+ openclaw memory search "$query" --max-results 5
+ }
+
+ openclaw_memory_deep_status() {
+ echo "Detecting embedded model readiness status..."
+ openclaw memory status --deep
+ }
+
+ openclaw_memory_menu() {
+ send_stats "OpenClaw memory management"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw memory management"
+ echo "======================================="
+ openclaw_memory_render_status
+ echo "1. Update memory index"
+ echo "2. View memory files"
+ echo "3. Index repair (Indexed exception)"
+ echo "4. Memory solution (QMD/Local/Auto)"
+ echo "5. Search testing (verify that the index is working)"
+ echo "6. Deep state detection (checking embedded models)"
+ echo "0. Return to the previous level"
+ echo "---------------------------------------"
+ read -e -p "Please enter your choice:" memory_choice
+ case "$memory_choice" in
+ 1)
+ echo "The memory index will be updated soon."
+ read -e -p "First confirmation: Enter yes to continue:" confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "Canceled."
+ break_end
+ continue
+ fi
+ openclaw_memory_prepare_workspace_all
+ read -e -p "Secondary confirmation: Enter force to use the full amount (leave blank for increment):" confirm_step2
+ if [ "$confirm_step2" = "force" ]; then
+ echo "⚠️ Full reconstruction is more thorough, but takes longer."
+ echo "Recommendation: Enter rebuild for safe reconstruction (back up the index database first)."
+ read -e -p "Third confirmation: Enter rebuild to perform a safe rebuild; press Enter to continue with normal force:" confirm_step3
+ if [ "$confirm_step3" = "rebuild" ]; then
+ openclaw_memory_rebuild_index_all
+ else
+ local fl_agent_lines fl_agent_id fl_workspace
+ fl_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r fl_agent_id fl_workspace; do
+ [ -z "$fl_agent_id" ] && continue
+ openclaw memory index --agent "$fl_agent_id" --force
+ done </dev/null 2>&1 || {
+ echo "⚠️ Permission backup failed:$backup_file"
+ return 1
+ }
+ echo "✅ Current permission configuration has been backed up:$backup_file"
+ return 0
+ }
+
+ openclaw_permission_restore_backup() {
+ local config_file backup_file
+ config_file=$(openclaw_permission_config_file)
+ backup_file=$(openclaw_permission_backup_file)
+ if [ ! -s "$backup_file" ]; then
+ echo "❌ No restoreable rights backup file found."
+ return 1
+ fi
+ cp -f "$backup_file" "$config_file" >/dev/null 2>&1 || {
+ echo "❌ Permission recovery failed:$backup_file"
+ return 1
+ }
+ echo "✅ The permission configuration before the switch has been restored"
+ openclaw_permission_restart_gateway || true
+ return 0
+ }
+
+ openclaw_permission_restart_gateway() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ openclaw is not detected and OpenClaw Gateway cannot be restarted."
+ return 1
+ fi
+ echo "Restarting OpenClaw Gateway..."
+ openclaw gateway restart >/dev/null 2>&1 || {
+ openclaw gateway stop >/dev/null 2>&1
+ openclaw gateway start >/dev/null 2>&1
+ }
+ }
+
+ openclaw_permission_get_value() {
+ local path="$1"
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+
+ if openclaw_has_command openclaw; then
+ local value
+ value=$(openclaw config get "$path" 2>&1 | head -n 1)
+ if [ -n "$value" ]; then
+ if echo "$value" | grep -qi "config path not found"; then
+ echo "(unset)"
+ return 0
+ fi
+ if [ "$value" = "null" ]; then
+ echo "(unset)"
+ else
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ fi
+ return 0
+ fi
+ fi
+
+ [ -f "$config_file" ] || { echo "(unset)"; return 0; }
+
+ if openclaw_has_command jq; then
+ local jq_value
+ jq_value=$(jq -r --arg p "$path" 'getpath($p|split(".")) // "(unset)"' "$config_file" 2>/dev/null) || jq_value="(unset)"
+ [ "$jq_value" = "null" ] && jq_value="(unset)"
+ echo "$jq_value"
+ return 0
+ fi
+
+ if openclaw_has_command python3; then
+ python3 - "$config_file" "$path" <<'PY'
+import json, sys
+path = sys.argv[2]
+with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+cur = obj
+for part in path.split('.'):
+ if isinstance(cur, dict) and part in cur:
+ cur = cur[part]
+ else:
+ print('(unset)')
+ raise SystemExit(0)
+if isinstance(cur, bool):
+ print('true' if cur else 'false')
+elif cur is None:
+ print('(unset)')
+else:
+ print(json.dumps(cur, ensure_ascii=False) if isinstance(cur, (dict, list)) else str(cur))
+PY
+ return 0
+ fi
+
+ echo "(unset)"
+ return 0
+ }
+
+ openclaw_permission_unset_optional() {
+ local key="$1"
+ local probe
+ if ! openclaw_has_command openclaw; then
+ return 1
+ fi
+ if openclaw config unset "$key" >/dev/null 2>&1; then
+ return 0
+ fi
+ probe=$(openclaw config get "$key" 2>&1 | head -n 1)
+ if [ -z "$probe" ] || [ "$probe" = "null" ] || [ "$probe" = "(unset)" ] || echo "$probe" | grep -qi "config path not found"; then
+ return 0
+ fi
+ return 1
+ }
+
+ openclaw_permission_detect_mode() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ [ ! -f "$config_file" ] && { echo "Unknown mode"; return; }
+
+ python3 - "$config_file" <<'PY'
+import json, sys
+
+def get_v(o, p):
+ for k in p.split('.'):
+ if isinstance(o, dict) and k in o:
+ o = o[k]
+ else:
+ return "(unset)"
+ return str(o).lower()
+
+try:
+ with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ d = json.load(f)
+ p = get_v(d, "tools.profile")
+ s = get_v(d, "tools.exec.security")
+ a = get_v(d, "tools.exec.ask")
+ e = get_v(d, "tools.elevated.enabled")
+ b = get_v(d, "commands.bash")
+ ap = get_v(d, "tools.exec.applyPatch.enabled")
+ w = get_v(d, "tools.exec.applyPatch.workspaceOnly")
+
+ if p == "coding" and s == "allowlist" and a == "on-miss" and e == "false" and b == "false" and ap == "false":
+ print("Standard safe mode")
+ elif p == "coding" and s == "allowlist" and a == "on-miss" and e == "true" and b == "true" and ap == "true" and w == "true":
+ print("Develop enhanced mode")
+ elif (p == "full" or p == "(unset)") and s == "full" and a == "off" and e == "true" and b == "true" and ap == "true":
+ print("Fully open mode")
+ else:
+ print("Custom mode")
+except Exception:
+ print("Custom mode")
+PY
+ }
+
+ openclaw_permission_update_exec_approvals() {
+ local sec="$1"
+ local ask="$2"
+ local fallback="$3"
+ local approvals_file="$HOME/.openclaw/exec-approvals.json"
+
+ mkdir -p "$HOME/.openclaw"
+
+ # Generate JSON and write via openclaw approvals set --stdin (preferred)
+ # If the CLI does not support it, it will fall back to writing the file directly.
+ local json_payload
+ json_payload=$(python3 -c '
+import json, sys, os
+path = sys.argv[1]
+try:
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ data = json.load(f)
+ else:
+ data = {"version": 1, "defaults": {}}
+except Exception:
+ data = {"version": 1, "defaults": {}}
+if "defaults" not in data:
+ data["defaults"] = {}
+data["defaults"]["security"] = sys.argv[2]
+data["defaults"]["ask"] = sys.argv[3]
+data["defaults"]["askFallback"] = sys.argv[4]
+data["defaults"]["autoAllowSkills"] = True
+print(json.dumps(data, indent=2))
+' "$approvals_file" "$sec" "$ask" "$fallback")
+
+ if openclaw_has_command openclaw && echo "$json_payload" | openclaw approvals set --stdin >/dev/null 2>&1; then
+ return 0
+ fi
+ # Fallback: Write the file directly
+ echo "$json_payload" > "$approvals_file"
+ }
+
+ openclaw_permission_render_status() {
+ echo "Application layer configuration: ~/.openclaw/openclaw.json"
+ echo "Host approval: ~/.openclaw/exec-approvals.json"
+ echo "---------------------------------------"
+ local current_profile current_sec current_ask current_elevated
+ current_profile=$(openclaw config get tools.profile 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_sec=$(openclaw config get tools.exec.security 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_ask=$(openclaw config get tools.exec.ask 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_elevated=$(openclaw config get tools.elevated.enabled 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ # Clean up null values
+ [ -z "$current_profile" ] || echo "$current_profile" | grep -qi "config path not found" && current_profile=""
+ [ -z "$current_sec" ] || echo "$current_sec" | grep -qi "config path not found" && current_sec=""
+ [ -z "$current_ask" ] || echo "$current_ask" | grep -qi "config path not found" && current_ask=""
+ [ -z "$current_elevated" ] || echo "$current_elevated" | grep -qi "config path not found" && current_elevated=""
+
+ local current_mode="Unknown / Custom"
+ if [ "$current_profile" = "full" ] && [ "$current_sec" = "full" ] && [ "$current_ask" = "off" ]; then
+ current_mode="\033[1;31mFull open mode\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" = "true" ]; then
+ current_mode="\033[1;33mDevelop enhanced mode\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" != "true" ]; then
+ current_mode="\033[1;32mStandard safe mode\033[0m"
+ elif [ -z "$current_profile" ] && [ -z "$current_sec" ]; then
+ current_mode="\033[1;36mThe official sandbox tells the truth\033[0m"
+ fi
+ echo -e "Current comprehensive security level:${current_mode}"
+ echo "---------------------------------------"
+ echo -e "${gl_huang}[Application Layer Tool Policy Status]${gl_bai}"
+ echo "Profile (default): ${current_profile:-(unset)}"
+ echo "Exec limit: ${current_sec:-(unset)}"
+ echo "Approval prompt: ${current_ask:-(unset)}"
+ echo "Privilege elevation switch: ${current_elevated:-(unset)}"
+
+ echo -e "\n${gl_huang}[Underlying Exec Approvals status]${gl_bai}"
+ if openclaw_has_command openclaw; then
+ local approvals_json
+ approvals_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$approvals_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ defaults = d.get("file", {}).get("defaults", {})
+ if not defaults:
+ defaults = d.get("defaults", {})
+ sec = defaults.get("security", "(unset)")
+ ask = defaults.get("ask", "(unset)")
+ fb = defaults.get("askFallback", "(unset)")
+ auto = defaults.get("autoAllowSkills", False)
+ print("Interception strategy (Security):" + str(sec))
+ print("Prompt strategy (Ask):" + str(ask))
+ print("No UI cover (AskFallback):" + str(fb))
+ print("AutoAllowSkills:" + ("on" if auto else "off"))
+ exists = d.get("exists", True)
+ if not exists:
+ print("(The approval document does not exist, use the system’s built-in safety net)")
+except Exception as e:
+ print("(Parse failed:" + str(e) + ")")
+' "$approvals_json"
+ else
+ echo "(openclaw approvals get --json no output)"
+ fi
+ elif [ -f "$HOME/.openclaw/exec-approvals.json" ]; then
+ python3 -c '
+import json, os
+path = os.path.expanduser("~/.openclaw/exec-approvals.json")
+try:
+ with open(path) as f:
+ d = json.load(f).get("defaults", {})
+ print("Interception strategy (Security):" + str(d.get("security", "(unset)")))
+ print("Prompt strategy (Ask):" + str(d.get("ask", "(unset)")))
+ print("No UI cover (AskFallback):" + str(d.get("askFallback", "(unset)")))
+except Exception:
+ print("(Configuration file parsing failed)")
+'
+ else
+ echo "(Not configured, forced to use the system's built-in security policy)"
+ fi
+ }
+
+ openclaw_permission_apply_standard() {
+ send_stats "OpenClaw Permissions - Standard Security Mode"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "Configuring application layer policy..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled false >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval true >/dev/null 2>&1 # 拦截危险的内联代码
+ openclaw config unset commands.bash >/dev/null 2>&1 # 废弃旧版参数
+
+ echo "Configuring host approval interception..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ Switched to standard safety mode (all dangerous commands will ask for your approval through UI/TG)${gl_bai}"
+ }
+
+ openclaw_permission_apply_developer() {
+ send_stats "OpenClaw Permissions - Development Enhanced Mode"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "Configuring application layer policy..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1 # 允许智能体申请提权
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "Configuring host approval interception..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ Switched to development enhancement mode (privilege escalation is allowed, but common dangerous commands still require approval)${gl_bai}"
+ }
+
+ openclaw_permission_apply_full() {
+ send_stats "OpenClaw Permissions - Fully Open Mode"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "Configuring application layer policy..."
+ openclaw config set tools.profile full >/dev/null 2>&1
+ openclaw config set tools.exec.security full >/dev/null 2>&1
+ openclaw config set tools.exec.ask off >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "Disintegrating host interception defense..."
+ # The full and off here will completely bypass the exec approval system of the underlying host.
+ openclaw_permission_update_exec_approvals "full" "off" "full"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ Has been switched to fully open mode (Warning: All host command interceptions have expired, and the agent has the highest permissions)${gl_bai}"
+ }
+
+ openclaw_permission_restore_official_defaults() {
+ send_stats "OpenClaw permissions-restore official default"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "Clean application layer force coverage..."
+ openclaw config unset tools.profile >/dev/null 2>&1
+ openclaw config unset tools.exec.security >/dev/null 2>&1
+ openclaw config unset tools.exec.ask >/dev/null 2>&1
+ openclaw config unset tools.elevated.enabled >/dev/null 2>&1
+ openclaw config unset tools.exec.strictInlineEval >/dev/null 2>&1
+
+ echo "Clean host interception configuration..."
+ # Prioritize clearing the approval configuration through the CLI, and fall back to directly deleting the file.
+ if echo '{"version":1,"defaults":{}}' | openclaw approvals set --stdin >/dev/null 2>&1; then
+ true
+ else
+ rm -f "$HOME/.openclaw/exec-approvals.json"
+ fi
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ Reverted to OpenClaw official security sandbox defense mechanism${gl_bai}"
+ }
+
+ openclaw_permission_run_audit() {
+ echo "======================================="
+ echo "Run the official OpenClaw security audit and physical..."
+ echo "======================================="
+ openclaw security audit
+ echo "---------------------------------------"
+ read -e -p "Attempt to automatically remediate discovered security vulnerabilities? (y/n):" fix_choice
+ if [[ "$fix_choice" == "y" || "$fix_choice" == "Y" || "$fix_choice" == "yes" ]]; then
+ openclaw security audit --fix
+ echo -e "${gl_lv}✅ Automatic repair completed.${gl_bai}"
+ fi
+ echo "Press any key to return..."
+ read -n 1 -s
+ }
+
+
+ openclaw_permission_manage_allowlist() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "Exec command whitelist management"
+ echo "======================================="
+ echo "Current whitelist:"
+ local allowlist_json
+ allowlist_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$allowlist_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ f = d.get("file", {})
+ agents = f.get("agents", {})
+ found = False
+ for agent_id, agent_data in agents.items():
+ al = agent_data.get("allowlist", [])
+ if al:
+ found = True
+ print("Agent [%s]:" % agent_id)
+ for item in al:
+ print(" - %s" % item)
+ if not found:
+ print("(Empty, no whitelist rules configured)")
+except Exception as e:
+ print("(Parse failed:" + str(e) + ")")
+' "$allowlist_json"
+ else
+ echo "(unable to obtain)"
+ fi
+ echo "---------------------------------------"
+ echo "1. Add whitelist rules"
+ echo "2. Remove whitelist rules"
+ echo "0. Return"
+ echo "---------------------------------------"
+ read -e -p "Please select:" al_choice
+ case "$al_choice" in
+ 1)
+ read -e -p "Enter the command path to be released (supports glob, such as /usr/bin/git):" pattern
+ [ -z "$pattern" ] && { echo "cannot be empty"; break_end; continue; }
+ read -e -p "Specify agent ID (leave blank = all agents *):" agent_id
+ agent_id="${agent_id:-*}"
+ openclaw approvals allowlist add --agent "$agent_id" "$pattern"
+ break_end
+ ;;
+ 2)
+ read -e -p "Enter the command path to be removed:" pattern
+ [ -z "$pattern" ] && { echo "cannot be empty"; break_end; continue; }
+ openclaw approvals allowlist remove "$pattern"
+ break_end
+ ;;
+ 0) return 0 ;;
+ *) echo "Invalid selection"; sleep 1 ;;
+ esac
+ done
+ }
+
+ openclaw_permission_menu() {
+ send_stats "OpenClaw permission management"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw permission management (dual-layer architecture deep adaptation)"
+ echo "======================================="
+ openclaw_permission_render_status
+ echo "---------------------------------------"
+ echo -e "${gl_kjlan}1.${gl_bai}Switch to standard security mode (daily recommendations, pop-up card approval)"
+ echo -e "${gl_kjlan}2.${gl_bai}Switch to development enhancement mode (allowing agents to apply for privilege escalation)"
+ echo -e "${gl_kjlan}3.${gl_bai}Switch to fully open mode (${gl_hong}High risk! Completely remove all host interceptions${gl_bai})"
+ echo -e "${gl_kjlan}4.${gl_bai}Restore the official default sandbox defense strategy"
+ echo -e "${gl_kjlan}5.${gl_bai}Run underlying security audits and automatic repairs"
+ echo -e "${gl_kjlan}6.${gl_bai}Manage Exec command whitelist"
+ echo -e "${gl_kjlan}0.${gl_bai}Return to previous level"
+ echo "---------------------------------------"
+ read -e -p "Please enter your choice:" perm_choice
+ case "$perm_choice" in
+ 1)
+ echo "Prepare to apply: Standard safe mode"
+ read -e -p "Enter yes to confirm:" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_standard; else echo "Canceled"; fi
+ break_end
+ ;;
+ 2)
+ echo "Preparing the App: Developing Enhanced Mode"
+ read -e -p "Enter yes to confirm:" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_developer; else echo "Canceled"; fi
+ break_end
+ ;;
+ 3)
+ echo -e "${gl_hong}⚠️ Fully open mode will completely collapse exec approval and automatically release high-risk code.${gl_bai}"
+ read -e -p "Enter FULL to confirm to continue:" confirm
+ if [ "$confirm" = "FULL" ]; then openclaw_permission_apply_full; else echo "Canceled"; fi
+ break_end
+ ;;
+ 4)
+ echo "All custom overrides will be cleared, restoring OpenClaw to its strictly sandboxed state when first installed."
+ read -e -p "Enter yes to confirm:" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_restore_official_defaults; else echo "Canceled"; fi
+ break_end
+ ;;
+ 5)
+ openclaw_permission_run_audit
+ ;;
+ 6)
+ openclaw_permission_manage_allowlist
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "Invalid selection, please try again."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_multiagent_config_file() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ echo "$config_file"
+ return 0
+ fi
+ openclaw config file 2>/dev/null | tail -n 1
+ }
+
+ openclaw_multiagent_default_agent() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+value="(unset)"
+try:
+ with open(path) as f:
+ data=json.load(f)
+ defaults=data.get("agents",{}).get("defaults",{}) if isinstance(data,dict) else {}
+ value=defaults.get("agent") or None
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and (item.get("isDefault") or item.get("default")):
+ value=item.get("id")
+ break
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and item.get("id"):
+ value=item.get("id")
+ break
+except Exception:
+ value="(unset)"
+print(value or "(unset)")
+PY
+ return 0
+ fi
+ local value
+ value=$(openclaw config get agents.defaults.agent 2>&1 | head -n 1)
+ if [ -z "$value" ] || echo "$value" | grep -qi "config path not found"; then
+ value=$(openclaw agents list --json 2>/dev/null | python3 -c 'import json,sys
+try:
+ data=json.load(sys.stdin)
+ print(next((x.get("id","(unset)") for x in data if x.get("isDefault")), "(unset)"))
+except Exception:
+ print("(unset)")' 2>/dev/null)
+ fi
+ [ -z "$value" ] && value="(unset)"
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ }
+
+ openclaw_multiagent_require_openclaw() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ The openclaw command is not detected, please install or initialize OpenClaw first."
+ return 1
+ fi
+ return 0
+ }
+
+ openclaw_multiagent_agents_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents list --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # Fallback: reading from configuration file
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ agents=data.get("agents",{}).get("list",[])
+ if not isinstance(agents,list):
+ agents=[]
+ print(json.dumps(agents, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_bindings_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents bindings --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # Fallback: reading from configuration file
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ bindings=data.get("agents",{}).get("bindings",[])
+ if not isinstance(bindings,list):
+ bindings=[]
+ results=[]
+ for item in bindings:
+ if not isinstance(item,dict):
+ continue
+ results.append({"agentId": item.get("agentId") or item.get("agent") or "?", "description": item.get("description") or "-"})
+ print(json.dumps(results, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_sessions_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw sessions --json 2>/dev/null | grep -v '^\[')
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # Fallback: Reading from the file system
+ python3 <<'PY'
+import json,os
+base=os.path.expanduser("~/.openclaw/agents")
+sessions=[]
+try:
+ agent_dirs=[d for d in os.listdir(base) if os.path.isdir(os.path.join(base,d))]
+except Exception:
+ agent_dirs=[]
+for agent_id in agent_dirs:
+ path=os.path.join(base,agent_id,"sessions","sessions.json")
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path) as f:
+ data=json.load(f)
+ except Exception:
+ continue
+ if isinstance(data,dict):
+ items=data.items()
+ elif isinstance(data,list):
+ items=[(item.get("key") or "?", item) for item in data if isinstance(item,dict)]
+ else:
+ continue
+ for key,item in items:
+ if not isinstance(item,dict):
+ continue
+ model=item.get("model") or "-"
+ sessions.append({"agentId": agent_id, "key": key, "model": model})
+print(json.dumps({"path":"(filesystem)","count":len(sessions),"sessions":sessions}, ensure_ascii=False))
+PY
+ }
+
+ openclaw_multiagent_render_status() {
+ local config_file default_agent
+ config_file=$(openclaw_multiagent_config_file)
+ default_agent=$(openclaw_multiagent_default_agent)
+ echo "Configuration file: ${config_file:-$(openclaw_permission_config_file)}"
+ echo "Default agent:$default_agent"
+ python3 -c '
+import json,sys
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+sess_obj=json.loads(sys.argv[3] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+print("Number of configured agents: %s" % len(agents))
+print("Number of route bindings: %s" % len(bindings))
+print("Total sessions: %s" % len(sessions))
+print("---------------------------------------")
+if not agents:
+ print("No multi-agent is currently configured.")
+else:
+ for item in agents[:8]:
+ aid = item.get("id","?")
+ identity = item.get("identityName") or item.get("name") or "-"
+ emoji = item.get("identityEmoji") or ""
+ ws = item.get("workspace") or "-"
+ model = item.get("model") or "-"
+ is_default = item.get("isDefault", False)
+ bcount = item.get("bindings", 0)
+ default_tag = "[default]" if is_default else ""
+ print("- Agent ID: \033[1;36m%s\033[0m%s" % (aid, default_tag))
+ print("Identity name: %s %s" % (identity, emoji))
+ print("Model: %s" % model)
+ print("Working directory: %s" % ws)
+ print("Number of bindings: %s" % bcount)
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)" "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_list_agents() {
+ send_stats "OpenClaw Multi-Agent - List Agents"
+ python3 -c 'import json,sys; agents=json.loads(sys.argv[1] or "[]");
+if not agents: print("No Agent has been configured yet."); raise SystemExit(0)
+for idx,item in enumerate(agents,1):
+ print("%s. %s" % (idx, item.get("id","?"))); print(" workspace : %s" % item.get("workspace","-")); ident=(item.get("identityName") or "-") + ((" " + item.get("identityEmoji")) if item.get("identityEmoji") else ""); print(" identity : %s" % ident.strip()); print(" model : %s" % (item.get("model") or "-")); print(" bindings : %s" % item.get("bindings",0)); print(" default : %s" % ("yes" if item.get("isDefault") else "no"))' "$(openclaw_multiagent_agents_json)"
+ }
+
+ openclaw_multiagent_add_agent() {
+ send_stats "OpenClaw multi-agent-new Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id workspace confirm
+ read -e -p "Please enter new Agent ID:" agent_id
+ [ -z "$agent_id" ] && echo "Canceled: Agent ID cannot be empty." && return 1
+ read -e -p "Please enter the workspace path (default ~/.openclaw/workspace-${agent_id}): " workspace
+ [ -z "$workspace" ] && workspace="~/.openclaw/workspace-${agent_id}"
+ echo "The agent will be created:$agent_id"
+ echo "Working directory:$workspace"
+ read -e -p "Enter yes to confirm to continue:" confirm
+ [ "$confirm" = "yes" ] || { echo "Canceled"; return 1; }
+ if openclaw agents add "$agent_id" --workspace "$workspace"; then
+ echo "✅ The agent was created successfully:$agent_id"
+ local name theme
+ read -e -p "Please enter the agent identity name (e.g.: Code Expert):" name
+ [ -z "$name" ] && name="$agent_id"
+ read -e -p "Please enter the agent personality theme (eg: rigorous, efficient):" theme
+ [ -z "$theme" ] && theme="assistant"
+ echo "Configuring agent identity..."
+ openclaw agents set-identity --agent "$agent_id" --name "$name" --theme "$theme"
+ else
+ echo "❌ Agent creation failed"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_delete_agent() {
+ send_stats "OpenClaw multi-agent-delete agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id confirm
+ read -e -p "Please enter the Agent ID to delete:" agent_id
+ [ -z "$agent_id" ] && echo "Canceled: Agent ID cannot be empty." && return 1
+ echo "⚠️ Deleting an agent may affect its working directory, route bindings, and session routing."
+ read -e -p "Enter DELETE to confirm deletion${agent_id}: " confirm
+ [ "$confirm" = "DELETE" ] || { echo "Canceled"; return 1; }
+ if openclaw agents delete "$agent_id"; then
+ echo "✅ Agent deleted successfully:$agent_id"
+ else
+ echo "❌ Agent deletion failed"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_list_bindings() {
+ send_stats "OpenClaw multi-agent - view routing bindings"
+ python3 -c '
+import json,sys
+bindings=json.loads(sys.argv[1] or "[]")
+if not bindings:
+ print("There is no route binding yet.")
+ raise SystemExit(0)
+for idx,item in enumerate(bindings,1):
+ desc = item.get("description") or "-"
+ print("%s. agent=%s | %s" % (idx, item.get("agentId","?"), desc))
+' "$(openclaw_multiagent_bindings_json)"
+ }
+
+ openclaw_multiagent_add_binding() {
+ send_stats "OpenClaw multi-agent-new routing binding"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "Please enter the agent ID:" agent_id
+ read -e -p "Please enter the route binding value (such as telegram:ops / discord:guild-a):" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "Canceled: Parameter cannot be empty." && return 1
+ echo "Will bind the agent [$agent_id] -> [$bind_value]"
+ read -e -p "Enter yes to confirm to continue:" confirm
+ [ "$confirm" = "yes" ] || { echo "Canceled"; return 1; }
+ if openclaw agents bind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ Route binding added successfully"
+ else
+ echo "❌ Failed to add route binding"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_remove_binding() {
+ send_stats "OpenClaw multi-agent - remove route binding"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "Please enter the agent ID:" agent_id
+ read -e -p "Please enter the route binding value to be removed:" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "Canceled: Parameter cannot be empty." && return 1
+ echo "will remove the agent [$agent_id] route binding [$bind_value]"
+ read -e -p "Enter yes to confirm to continue:" confirm
+ [ "$confirm" = "yes" ] || { echo "Canceled"; return 1; }
+ if openclaw agents unbind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ Route binding removed successfully"
+ else
+ echo "❌ Route binding removal failed"
+ return 1
+ fi
+ }
+
+
+ openclaw_multiagent_show_sessions() {
+ send_stats "OpenClaw Multi-Agent - Session Overview"
+ python3 -c '
+import json,sys
+sess_obj=json.loads(sys.argv[1] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+if not sessions:
+ print("There is no session data yet.")
+ raise SystemExit(0)
+by_agent={}
+for item in sessions:
+ aid = item.get("agentId","?")
+ by_agent[aid] = by_agent.get(aid, 0) + 1
+print("Session summary:")
+for agent_id,count in sorted(by_agent.items()):
+ print("- %s: %s" % (agent_id, count))
+print("---------------------------------------")
+for item in sessions[:10]:
+ key = item.get("key","-")
+ model = item.get("model") or "-"
+ aid = item.get("agentId","?")
+ tokens = ""
+ it = item.get("inputTokens")
+ ot = item.get("outputTokens")
+ if it is not None:
+ tokens = " | in=%s out=%s" % (it, ot or 0)
+ print("%s | %s | %s%s" % (aid, key, model, tokens))
+' "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_health_check() {
+ send_stats "OpenClaw Multi-Agent-Health Check"
+ openclaw_multiagent_require_openclaw || return 1
+ local config_file
+ config_file=$(openclaw_multiagent_config_file)
+ echo "Check the configuration file: ${config_file:-$(openclaw_permission_config_file)}"
+ openclaw config validate || echo "⚠️ Configuration verification failed, please check the output above."
+ python3 -c '
+import json,sys,os
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+print("---------------------------------------")
+if not agents:
+ print("⚠️ No configured agent found.")
+else:
+ for item in agents:
+ ws = item.get("workspace") or ""
+ aid = item.get("id","?")
+ if ws and os.path.isdir(os.path.expanduser(ws)):
+ state = "OK"
+ elif aid == "main":
+ state = "OK"
+ else:
+ state = "MISSING"
+ model = item.get("model") or "-"
+ bcount = item.get("bindings", 0)
+ print("agent=%s workspace=%s model=%s bindings=%s [%s]" % (aid, ws or "-", model, bcount, state))
+print("Number of route bindings=%s" % len(bindings))
+print("✅Multi-agent health check completed")
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)"
+ echo ""
+ echo "Run a security audit..."
+ openclaw security audit 2>/dev/null || echo "⚠️ Security audit command is not available"
+ }
+
+
+ openclaw_multiagent_set_identity() {
+ openclaw_multiagent_require_openclaw || return 1
+ openclaw_multiagent_list_agents
+ read -e -p "Enter the ID of the agent whose identity you want to modify:" agent_id
+ [ -z "$agent_id" ] && { echo "ID cannot be empty"; return 1; }
+ echo "Modify options (leave blank to skip):"
+ read -e -p "New name:" new_name
+ read -e -p "New Emoji:" new_emoji
+ local cmd="openclaw agents set-identity --agent $agent_id"
+ [ -n "$new_name" ] && cmd="$cmd --name $new_name"
+ [ -n "$new_emoji" ] && cmd="$cmd --emoji $new_emoji"
+ echo "Identity information can also be read automatically from IDENTITY.md."
+ read -e -p "Read from IDENTITY.md? (y/n):" from_id
+ if [ "$from_id" = "y" ]; then
+ cmd="openclaw agents set-identity --agent $agent_id --from-identity"
+ fi
+ eval "$cmd"
+ }
+
+ openclaw_multiagent_cleanup_sessions() {
+ openclaw_multiagent_require_openclaw || return 1
+ echo "Cleaning up expired/redundant session data soon..."
+ read -e -p "Enter yes to confirm:" confirm
+ [ "$confirm" != "yes" ] && { echo "Canceled"; return 0; }
+ openclaw sessions cleanup
+ }
+
+ openclaw_multiagent_menu() {
+ send_stats "OpenClaw multi-agent management"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw multi-agent management"
+ echo "======================================="
+ openclaw_multiagent_render_status
+ echo "---------------------------------------"
+ echo "1. Add new intelligent agent"
+ echo "2. Delete the agent"
+ echo "3. View routing bindings"
+ echo "4. Add new route binding"
+ echo "5. Remove route binding"
+ echo "6. View session overview"
+ echo "7. Run multi-agent health checks"
+ echo "8. Modify the agent’s identity (name/Emoji)"
+ echo "9. Clean up expired sessions"
+ echo "0. Return to the previous level"
+ echo "---------------------------------------"
+ read -e -p "Please enter your choice:" multi_choice
+ case "$multi_choice" in
+ 1) openclaw_multiagent_add_agent; break_end ;;
+ 2) openclaw_multiagent_delete_agent; break_end ;;
+ 3) openclaw_multiagent_list_bindings; break_end ;;
+ 4) openclaw_multiagent_add_binding; break_end ;;
+ 5) openclaw_multiagent_remove_binding; break_end ;;
+ 6) openclaw_multiagent_show_sessions; break_end ;;
+ 7) openclaw_multiagent_health_check; break_end ;;
+ 8) openclaw_multiagent_set_identity; break_end ;;
+ 9) openclaw_multiagent_cleanup_sessions; break_end ;;
+ 0) return 0 ;;
+ *) echo "Invalid selection, please try again."; sleep 1 ;;
+ esac
+ done
+ }
+
+
+openclaw_backup_restore_menu() {
+
+ send_stats "OpenClaw backup and restore"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw Backup and Restore"
+ echo "======================================="
+ openclaw_backup_render_file_list
+ echo "---------------------------------------"
+ echo "1. Back up the entire memory"
+ echo "2. Restore the full memory"
+ echo "3. Back up the OpenClaw project (default safe mode)"
+ echo "4. Restore OpenClaw Project (Advanced/High Risk)"
+ echo "5. Delete backup files"
+ echo "0. Return to the previous level"
+ echo "---------------------------------------"
+ read -e -p "Please enter your choice:" backup_choice
+
+ case "$backup_choice" in
+ 1) openclaw_memory_backup_export ;;
+ 2) openclaw_memory_backup_import ;;
+ 3) openclaw_project_backup_export ;;
+ 4) openclaw_project_backup_import ;;
+ 5) openclaw_backup_delete_file ;;
+ 0) return 0 ;;
+ *)
+ echo "Invalid selection, please try again."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ update_moltbot() {
+ echo "Update OpenClaw..."
+ send_stats "Update OpenClaw..."
+ install_node_and_tools
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+ npm install -g openclaw@latest
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ start_gateway
+ hash -r
+ add_app_id
+ echo "Update completed"
+ break_end
+ }
+
+
+ uninstall_moltbot() {
+ echo "Uninstall OpenClaw..."
+ send_stats "Uninstall OpenClaw..."
+ openclaw uninstall
+ npm uninstall -g openclaw
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ rm -rf "$HOME/.openclaw"
+ [ "$HOME" != "/root" ] && [ -d /root/.openclaw ] && echo "⚠️ It is detected that /root/.openclaw still exists in the root directory. If you need to clean it, please handle it manually."
+ hash -r
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "Uninstall completed"
+ break_end
+ }
+
+ nano_openclaw_json() {
+ send_stats "Edit the OpenClaw configuration file"
+ install nano
+ nano "$(openclaw_get_config_file)"
+ start_gateway
+ }
+
+
+
+
+
+
+ openclaw_find_webui_domain() {
+ local conf domain_list
+
+ domain_list=$(
+ grep -R "18789" /home/web/conf.d/*.conf 2>/dev/null \
+ | awk -F: '{print $1}' \
+ | sort -u \
+ | while read conf; do
+ basename "$conf" .conf
+ done
+ )
+
+ if [ -n "$domain_list" ]; then
+ echo "$domain_list"
+ fi
+ }
+
+
+
+ openclaw_show_webui_addr() {
+ local local_ip token domains
+
+ echo "=================================="
+ echo "OpenClaw WebUI access address"
+ local_ip="127.0.0.1"
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+ echo
+ echo "Local address:"
+ echo "http://${local_ip}:18789/#token=${token}"
+
+ domains=$(openclaw_find_webui_domain)
+ if [ -n "$domains" ]; then
+ echo "Domain name address:"
+ echo "$domains" | while read d; do
+ echo "https://${d}/#token=${token}"
+ done
+ fi
+
+ echo "=================================="
+ }
+
+
+
+ # Add a domain name (call the function you gave)
+ openclaw_domain_webui() {
+ add_yuming
+ ldnmp_Proxy ${yuming} 127.0.0.1 18789
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+
+ clear
+ echo "Visit address:"
+ echo "https://${yuming}/#token=$token"
+ echo "First access the URL to trigger the device ID, then press Enter to proceed with pairing."
+ read
+ echo -e "${gl_kjlan}Loading device list...${gl_bai}"
+ # Automatically add domain names to allowedOrigins
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ new_origin="https://${yuming}"
+ # Use jq to safely modify JSON to ensure that the structure exists and domain names are not added repeatedly
+ if command -v jq >/dev/null 2>&1; then
+ tmp_json=$(mktemp)
+ jq 'if .gateway.controlUi == null then .gateway.controlUi = {"allowedOrigins": ["http://127.0.0.1"]} else . end | if (.gateway.controlUi.allowedOrigins | contains([$origin]) | not) then .gateway.controlUi.allowedOrigins += [$origin] else . end' --arg origin "$new_origin" "$config_file" > "$tmp_json" && mv "$tmp_json" "$config_file"
+ echo -e "${gl_kjlan}Domain name has been${yuming}Add allowedOrigins configuration${gl_bai}"
+ openclaw gateway restart >/dev/null 2>&1
+ fi
+ fi
+
+ openclaw devices list
+
+ read -e -p "Please enter Request_Key:" Request_Key
+
+ [ -z "$Request_Key" ] && {
+ echo "Request_Key cannot be empty"
+ return 1
+ }
+
+ openclaw devices approve "$Request_Key"
+
+ }
+
+ # Delete domain name
+ openclaw_remove_domain() {
+ echo "Domain name format example.com without https://"
+ web_del
+ }
+
+ # Main menu
+ openclaw_webui_menu() {
+
+ send_stats "WebUI access and settings"
+ while true; do
+ clear
+ openclaw_show_webui_addr
+ echo
+ echo "1. Add domain name access"
+ echo "2. Delete domain name access"
+ echo "0. Exit"
+ echo
+ read -e -p "Please select:" choice
+
+ case "$choice" in
+ 1)
+ openclaw_domain_webui
+ echo
+ read -p "Press Enter to return to the menu..."
+ ;;
+ 2)
+ openclaw_remove_domain
+ read -p "Press Enter to return to the menu..."
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "Invalid option"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ # main loop
+ while true; do
+ show_menu
+ read choice
+ case $choice in
+ 1) install_moltbot ;;
+ 2) start_bot ;;
+ 3) stop_bot ;;
+ 4) view_logs ;;
+ 5) change_model ;;
+ 6) openclaw_api_manage_menu ;;
+ 7) change_tg_bot_code ;;
+ 8) install_plugin ;;
+ 9) install_skill ;;
+ 10) nano_openclaw_json ;;
+ 11) send_stats "Initial configuration wizard"
+ openclaw onboard --install-daemon
+ break_end
+ ;;
+ 12) send_stats "Health detection and repair"
+ openclaw doctor --fix
+ break_end
+ ;;
+ 13) openclaw_webui_menu ;;
+ 14) send_stats "TUI command line conversation"
+ openclaw tui
+ break_end
+ ;;
+ 15) openclaw_memory_menu ;;
+ 16) openclaw_permission_menu ;;
+ 17) openclaw_multiagent_menu ;;
+ 18) openclaw_backup_restore_menu ;;
+ 19) update_moltbot ;;
+ 20) uninstall_moltbot ;;
+ *) break ;;
+ esac
+ done
+
+}
+
+
+
+
+linux_panel() {
+
+local sub_choice="$1"
+
+clear
+cd ~
+install git
+echo -e "${gl_kjlan}The application list is being updated. Please wait...${gl_bai}"
+if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+fi
+
+while true; do
+
+ if [ -z "$sub_choice" ]; then
+ clear
+ echo -e "application market"
+ echo -e "${gl_kjlan}-------------------------"
+
+ local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+
+ # Set color with loop
+ for i in {1..150}; do
+ if echo "$app_numbers" | grep -q "^$i$"; then
+ declare "color$i=${gl_lv}"
+ else
+ declare "color$i=${gl_bai}"
+ fi
+ done
+
+ echo -e "${gl_kjlan}1. ${color1}Pagoda panel official version${gl_kjlan}2. ${color2}aaPanel Pagoda International Version"
+ echo -e "${gl_kjlan}3. ${color3}1Panel new generation management panel${gl_kjlan}4. ${color4}NginxProxyManager visualization panel"
+ echo -e "${gl_kjlan}5. ${color5}OpenList multi-store file list program${gl_kjlan}6. ${color6}Ubuntu Remote Desktop Web Edition"
+ echo -e "${gl_kjlan}7. ${color7}Nezha Probe VPS Monitoring Panel${gl_kjlan}8. ${color8}QB offline BT magnetic download panel"
+ echo -e "${gl_kjlan}9. ${color9}Poste.io mail server program${gl_kjlan}10. ${color10}RocketChat multi-person online chat system"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}11. ${color11}ZenTao project management software${gl_kjlan}12. ${color12}Qinglong panel scheduled task management platform"
+ echo -e "${gl_kjlan}13. ${color13}Cloudreve network disk${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}Simple picture bed picture management program"
+ echo -e "${gl_kjlan}15. ${color15}emby multimedia management system${gl_kjlan}16. ${color16}Speedtest speed test panel"
+ echo -e "${gl_kjlan}17. ${color17}AdGuardHome removes adware${gl_kjlan}18. ${color18}onlyofficeOnline office OFFICE"
+ echo -e "${gl_kjlan}19. ${color19}Leichi WAF firewall panel${gl_kjlan}20. ${color20}portainer container management panel"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}21. ${color21}VScode web version${gl_kjlan}22. ${color22}UptimeKuma monitoring tool"
+ echo -e "${gl_kjlan}23. ${color23}Memos web memo${gl_kjlan}24. ${color24}Webtop remote desktop web version${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}25. ${color25}Nextcloud network disk${gl_kjlan}26. ${color26}QD-Today scheduled task management framework"
+ echo -e "${gl_kjlan}27. ${color27}Dockge container stack management panel${gl_kjlan}28. ${color28}LibreSpeed speed test tool"
+ echo -e "${gl_kjlan}29. ${color29}searxng aggregated search station${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism Private Album System"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}31. ${color31}StirlingPDF Tools Collection${gl_kjlan}32. ${color32}drawio free online charting software${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${color33}Sun-Panel Navigation Panel${gl_kjlan}34. ${color34}Pingvin-Share file sharing platform"
+ echo -e "${gl_kjlan}35. ${color35}Minimalist circle of friends${gl_kjlan}36. ${color36}LobeChatAI chat aggregation website"
+ echo -e "${gl_kjlan}37. ${color37}MyIP Toolbox${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}Xiaoya alist family bucket"
+ echo -e "${gl_kjlan}39. ${color39}Bililive live broadcast recording tool${gl_kjlan}40. ${color40}webssh web version SSH connection tool"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}41. ${color41}Mouse management panel${gl_kjlan}42. ${color42}Nexterm remote connection tool"
+ echo -e "${gl_kjlan}43. ${color43}RustDesk remote desktop (server)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk remote desktop (relay)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}45. ${color45}Docker acceleration station${gl_kjlan}46. ${color46}GitHub acceleration station${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}47. ${color47}Prometheus monitoring${gl_kjlan}48. ${color48}Prometheus (host monitoring)"
+ echo -e "${gl_kjlan}49. ${color49}Prometheus (container monitoring)${gl_kjlan}50. ${color50}Replenishment monitoring tools"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}51. ${color51}PVE open chick panel${gl_kjlan}52. ${color52}DPanel container management panel"
+ echo -e "${gl_kjlan}53. ${color53}llama3 chat AI large model${gl_kjlan}54. ${color54}AMH host website building management panel"
+ echo -e "${gl_kjlan}55. ${color55}FRP intranet penetration (server)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP intranet penetration (client)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}57. ${color57}Deepseek chat AI large model${gl_kjlan}58. ${color58}Dify large model knowledge base${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}59. ${color59}NewAPI large model asset management${gl_kjlan}60. ${color60}JumpServer open source bastion machine"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}61. ${color61}Online translation server${gl_kjlan}62. ${color62}RAGFlow large model knowledge base"
+ echo -e "${gl_kjlan}63. ${color63}OpenWebUI self-hosted AI platform${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools toolbox"
+ echo -e "${gl_kjlan}65. ${color65}n8n automated workflow platform${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp video download tool"
+ echo -e "${gl_kjlan}67. ${color67}ddns-go dynamic DNS management tool${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL certificate management platform"
+ echo -e "${gl_kjlan}69. ${color69}SFTPGo file transfer tool${gl_kjlan}70. ${color70}AstrBot chatbot framework"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}71. ${color71}Navidrome private music server${gl_kjlan}72. ${color72}bitwarden password manager${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}73. ${color73}LibreTV Private Movies${gl_kjlan}74. ${color74}MoonTV private movies"
+ echo -e "${gl_kjlan}75. ${color75}Melody music wizard${gl_kjlan}76. ${color76}Online DOS old games"
+ echo -e "${gl_kjlan}77. ${color77}Thunder offline download tool${gl_kjlan}78. ${color78}PandaWiki intelligent document management system"
+ echo -e "${gl_kjlan}79. ${color79}Beszel server monitoring${gl_kjlan}80. ${color80}linkwarden bookmark management"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}81. ${color81}JitsiMeet video conference${gl_kjlan}82. ${color82}gpt-load high-performance AI transparent proxy"
+ echo -e "${gl_kjlan}83. ${color83}komari server monitoring tool${gl_kjlan}84. ${color84}Wallos personal financial management tool"
+ echo -e "${gl_kjlan}85. ${color85}immich picture video manager${gl_kjlan}86. ${color86}jellyfin media management system"
+ echo -e "${gl_kjlan}87. ${color87}SyncTV is a great tool for watching movies together${gl_kjlan}88. ${color88}Owncast self-hosted live streaming platform"
+ echo -e "${gl_kjlan}89. ${color89}FileCodeBox file express${gl_kjlan}90. ${color90}matrix decentralized chat protocol"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}91. ${color91}gitea private code repository${gl_kjlan}92. ${color92}FileBrowser file manager"
+ echo -e "${gl_kjlan}93. ${color93}Dufs minimalist static file server${gl_kjlan}94. ${color94}Gopeed high-speed download tool"
+ echo -e "${gl_kjlan}95. ${color95}paperless document management platform${gl_kjlan}96. ${color96}2FAuth self-hosted two-step authenticator"
+ echo -e "${gl_kjlan}97. ${color97}WireGuard networking (server)${gl_kjlan}98. ${color98}WireGuard networking (client)"
+ echo -e "${gl_kjlan}99. ${color99}DSM Synology Virtual Machine${gl_kjlan}100. ${color100}Syncthing peer-to-peer file synchronization tool"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}101. ${color101}AI video generation tool${gl_kjlan}102. ${color102}VoceChat multi-person online chat system"
+ echo -e "${gl_kjlan}103. ${color103}Umami website statistics tool${gl_kjlan}104. ${color104}Stream four-layer proxy forwarding tool"
+ echo -e "${gl_kjlan}105. ${color105}Siyuan Notes${gl_kjlan}106. ${color106}Drawnix open source whiteboard tool"
+ echo -e "${gl_kjlan}107. ${color107}PanSou network disk search${gl_kjlan}108. ${color108}LangBot chatbot"
+ echo -e "${gl_kjlan}109. ${color109}ZFile online network disk${gl_kjlan}110. ${color110}Karakeep bookmark management"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}111. ${color111}Multi-format file conversion tool${gl_kjlan}112. ${color112}Lucky large intranet penetration tool"
+ echo -e "${gl_kjlan}113. ${color113}Firefox browser${gl_kjlan}114. ${color114}OpenClaw bot management tool${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}115. ${color115}Hermes robot management tool${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}Third-party application list"
+ echo -e "${gl_kjlan}Want your app to appear here? Check out the developer guide:${gl_huang}https://dev.kejilion.sh/${gl_bai}"
+
+ for f in "$HOME"/apps/*.conf; do
+ [ -e "$f" ] || continue
+ local base_name=$(basename "$f" .conf)
+ # Get app description
+ local app_text=$(grep "app_text=" "$f" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
+
+ # Check installation status (match ID in appno.txt)
+ # It is assumed here that what is recorded in appno.txt is base_name (i.e. file name)
+ if echo "$app_numbers" | grep -q "^$base_name$"; then
+ # If installed: show base_name - description [Installed] (green)
+ echo -e "${gl_kjlan}$base_name${gl_bai} - ${gl_lv}$app_text[Installed]${gl_bai}"
+ else
+ # If not installed: display normally
+ echo -e "${gl_kjlan}$base_name${gl_bai} - $app_text"
+ fi
+ done
+
+
+
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}b. ${gl_bai}Back up all application data${gl_kjlan}r. ${gl_bai}Restore all app data"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "Please enter your choice:" sub_choice
+ fi
+
+ case $sub_choice in
+ 1|bt|baota)
+ local app_id="1"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="pagoda panel"
+ local panelurl="https://www.bt.cn/new/index.html"
+
+ panel_app_install() {
+ if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+
+
+ ;;
+ 2|aapanel)
+
+
+ local app_id="2"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="aapanel"
+ local panelurl="https://www.aapanel.com/new/index.html"
+
+ panel_app_install() {
+ URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+ ;;
+ 3|1p|1panel)
+
+ local app_id="3"
+ local lujing="command -v 1pctl"
+ local panelname="1Panel"
+ local panelurl="https://1panel.cn/"
+
+ panel_app_install() {
+ install bash
+ bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
+ }
+
+ panel_app_manage() {
+ 1pctl user-info
+ 1pctl update password
+ }
+
+ panel_app_uninstall() {
+ 1pctl uninstall
+ }
+
+ install_panel
+
+ ;;
+ 4|npm)
+
+ local app_id="4"
+ local docker_name="npm"
+ local docker_img="jc21/nginx-proxy-manager:latest"
+ local docker_port=81
+
+ docker_rum() {
+
+ docker run -d \
+ --name=$docker_name \
+ -p ${docker_port}:81 \
+ -p 80:80 \
+ -p 443:443 \
+ -v /home/docker/npm/data:/data \
+ -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
+ --restart=always \
+ $docker_img
+
+
+ }
+
+ local docker_describe="An Nginx reverse proxy tool panel that does not support adding domain name access."
+ local docker_url="Official website introduction: https://nginxproxymanager.com/"
+ local docker_use="echo \"Initial username: admin@example.com\""
+ local docker_passwd="echo \"Initial password: changeme\""
+ local app_size="1"
+
+ docker_app
+
+ ;;
+
+ 5|openlist)
+
+ local app_id="5"
+ local docker_name="openlist"
+ local docker_img="openlistteam/openlist:latest-aria2"
+ local docker_port=5244
+
+ docker_rum() {
+
+ mkdir -p /home/docker/openlist
+ chmod -R 777 /home/docker/openlist
+
+ docker run -d \
+ --restart=always \
+ -v /home/docker/openlist:/opt/openlist/data \
+ -p ${docker_port}:5244 \
+ -e PUID=0 \
+ -e PGID=0 \
+ -e UMASK=022 \
+ --name="openlist" \
+ openlistteam/openlist:latest-aria2
+
+ }
+
+
+ local docker_describe="A file listing program that supports multiple storages, web browsing and WebDAV, powered by gin and Solidjs"
+ local docker_url="Official website introduction:${gh_https_url}github.com/OpenListTeam/OpenList"
+ local docker_use="docker exec openlist ./openlist admin random"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 6|webtop-ubuntu)
+
+ local app_id="6"
+ local docker_name="webtop-ubuntu"
+ local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
+ local docker_port=3006
+
+ docker_rum() {
+
+ read -e -p "Set login username:" admin
+ read -e -p "Set login user password:" admin_password
+ docker run -d \
+ --name=webtop-ubuntu \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:ubuntu-kde
+
+
+ }
+
+
+ local docker_describe="webtop is an Ubuntu-based container. If the IP cannot be accessed, please add a domain name for access."
+ local docker_url="Official website introduction: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+ 7|nezha)
+ clear
+ send_stats "Build Nezha"
+
+ local app_id="7"
+ local docker_name="nezha-dashboard"
+ local docker_port=8008
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+ clear
+ echo -e "Nezha monitoring$check_docker $update_status"
+ echo "Open source, lightweight, easy-to-use server monitoring and operation and maintenance tool"
+ echo "Official website construction documentation: https://nezha.wiki/guide/dashboard.html"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. Use"
+ echo "------------------------"
+ echo "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Enter your selection:" choice
+
+ case $choice in
+ 1)
+ check_disk_space 1
+ install unzip jq
+ install_docker
+ curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+ ;;
+
+ 8|qb|QB)
+
+ local app_id="8"
+ local docker_name="qbittorrent"
+ local docker_img="lscr.io/linuxserver/qbittorrent:latest"
+ local docker_port=8081
+
+ docker_rum() {
+
+ docker run -d \
+ --name=qbittorrent \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e WEBUI_PORT=${docker_port} \
+ -e TORRENTING_PORT=56881 \
+ -p ${docker_port}:${docker_port} \
+ -p 56881:56881 \
+ -p 56881:56881/udp \
+ -v /home/docker/qbittorrent/config:/config \
+ -v /home/docker/qbittorrent/downloads:/downloads \
+ --restart=always \
+ lscr.io/linuxserver/qbittorrent:latest
+
+ }
+
+ local docker_describe="qbittorrent offline BT magnetic download service"
+ local docker_url="Official website introduction: https://hub.docker.com/r/linuxserver/qbittorrent"
+ local docker_use="sleep 3"
+ local docker_passwd="docker logs qbittorrent"
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 9|mail)
+ send_stats "Build a post office"
+ clear
+ install telnet
+ local app_id="9"
+ local docker_name=“mailserver”
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+
+ clear
+ echo -e "postal services$check_docker $update_status"
+ echo "poste.io is an open source mail server solution,"
+ echo "Video introduction: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+
+ echo ""
+ echo "Port detection"
+ port=25
+ timeout=3
+ if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
+ echo -e "${gl_lv}port$portCurrently available${gl_bai}"
+ else
+ echo -e "${gl_hong}port$portCurrently unavailable${gl_bai}"
+ fi
+ echo ""
+
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ yuming=$(cat /home/docker/mail.txt)
+ echo "Visit address:"
+ echo "https://$yuming"
+ fi
+
+ echo "------------------------"
+ echo "1. Install 2. Update 3. Uninstall"
+ echo "------------------------"
+ echo "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Enter your selection:" choice
+
+ case $choice in
+ 1)
+ setup_docker_dir
+ check_disk_space 2 /home/docker
+ read -e -p "Please set the email domain name, for example mail.yuming.com:" yuming
+ mkdir -p /home/docker
+ echo "$yuming" > /home/docker/mail.txt
+ echo "------------------------"
+ ip_address
+ echo "First parse these DNS records"
+ echo "A mail $ipv4_address"
+ echo "CNAME imap $yuming"
+ echo "CNAME pop $yuming"
+ echo "CNAME smtp $yuming"
+ echo "MX @ $yuming"
+ echo "TXT @ v=spf1 mx ~all"
+ echo "TXT ? ?"
+ echo ""
+ echo "------------------------"
+ echo "Press any key to continue..."
+ read -n 1 -s -r -p ""
+
+ install jq
+ install_docker
+
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.io
+
+
+ add_app_id
+
+ clear
+ echo "poste.io has been installed"
+ echo "------------------------"
+ echo "You can access poste.io using the following address:"
+ echo "https://$yuming"
+ echo ""
+
+ ;;
+
+ 2)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.i
+ yuming=$(cat /home/docker/mail.txt)
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.i
+
+
+ add_app_id
+
+ clear
+ echo "poste.io has been installed"
+ echo "------------------------"
+ echo "You can access poste.io using the following address:"
+ echo "https://$yuming"
+ echo ""
+ ;;
+ 3)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.io
+ rm /home/docker/mail.txt
+ rm -rf /home/docker/mail
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "App has been uninstalled"
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 10|rocketchat)
+
+ local app_id="10"
+ local app_name="Rocket.Chat chat system"
+ local app_text="Rocket.Chat is an open source team communication platform that supports real-time chat, audio and video calls, file sharing and other functions."
+ local app_url="Official introduction: https://www.rocket.chat/"
+ local docker_name="rocketchat"
+ local docker_port="3897"
+ local app_size="2"
+
+ docker_app_install() {
+ docker run --name db -d --restart=always \
+ -v /home/docker/mongo/dump:/dump \
+ mongo:latest --replSet rs5 --oplogSize 256
+ sleep 1
+ docker exec db mongosh --eval "printjson(rs.initiate())"
+ sleep 5
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+
+ clear
+ ip_address
+ echo "Installation completed"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat:latest
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ clear
+ ip_address
+ echo "rocket.chat has been installed"
+ check_docker_app_ip
+ }
+
+ docker_app_uninstall() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat
+ docker rm -f db
+ docker rmi -f mongo:latest
+ rm -rf /home/docker/mongo
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 11|zentao)
+ local app_id="11"
+ local docker_name="zentao-server"
+ local docker_img="idoop/zentao:latest"
+ local docker_port=82
+
+
+ docker_rum() {
+
+
+ docker run -d -p ${docker_port}:80 \
+ -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
+ -e BIND_ADDRESS="false" \
+ -v /home/docker/zentao-server/:/opt/zbox/ \
+ --add-host smtp.exmail.qq.com:163.177.90.125 \
+ --name zentao-server \
+ --restart=always \
+ idoop/zentao:latest
+
+
+ }
+
+ local docker_describe="ZenTao is a universal project management software"
+ local docker_url="Official website introduction: https://www.zentao.net/"
+ local docker_use="echo \"Initial username: admin\""
+ local docker_passwd="echo \"Initial password: 123456\""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 12|qinglong)
+ local app_id="12"
+ local docker_name="qinglong"
+ local docker_img="whyour/qinglong:latest"
+ local docker_port=5700
+
+ docker_rum() {
+
+
+ docker run -d \
+ -v /home/docker/qinglong/data:/ql/data \
+ -p ${docker_port}:5700 \
+ --name qinglong \
+ --hostname qinglong \
+ --restart=always \
+ whyour/qinglong:latest
+
+
+ }
+
+ local docker_describe="Qinglong Panel is a scheduled task management platform"
+ local docker_url="Official website introduction:${gh_proxy}github.com/whyour/qinglong"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 13|cloudreve)
+
+ local app_id="13"
+ local app_name="cloudreve network disk"
+ local app_text="cloudreve is a network disk system that supports multiple cloud storages"
+ local app_url="Video introduction: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
+ local docker_name="cloudreve"
+ local docker_port="5212"
+ local app_size="2"
+
+ docker_app_install() {
+ cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
+ curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
+ sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
+ cd /home/docker/cloud/
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ cd /home/docker/cloud/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ rm -rf /home/docker/cloud
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+ ;;
+
+ 14|easyimage)
+ local app_id="14"
+ local docker_name="easyimage"
+ local docker_img="ddsderek/easyimage:latest"
+ local docker_port=8014
+ docker_rum() {
+
+ docker run -d \
+ --name easyimage \
+ -p ${docker_port}:80 \
+ -e TZ=Asia/Shanghai \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -v /home/docker/easyimage/config:/app/web/config \
+ -v /home/docker/easyimage/i:/app/web/i \
+ --restart=always \
+ ddsderek/easyimage:latest
+
+ }
+
+ local docker_describe="Simple drawing bed is a simple drawing bed program"
+ local docker_url="Official website introduction:${gh_proxy}github.com/icret/EasyImages2.0"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 15|emby)
+ local app_id="15"
+ local docker_name="emby"
+ local docker_img="linuxserver/emby:latest"
+ local docker_port=8015
+
+ docker_rum() {
+
+ docker run -d --name=emby --restart=always \
+ -v /home/docker/emby/config:/config \
+ -v /home/docker/emby/share1:/mnt/share1 \
+ -v /home/docker/emby/share2:/mnt/share2 \
+ -v /mnt/notify:/mnt/notify \
+ -p ${docker_port}:8096 \
+ -e UID=1000 -e GID=100 -e GIDLIST=100 \
+ linuxserver/emby:latest
+
+ }
+
+
+ local docker_describe="emby is a master-slave architecture media server software that can be used to organize video and audio on the server and stream audio and video to client devices"
+ local docker_url="Official website introduction: https://emby.media/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 16|looking)
+ local app_id="16"
+ local docker_name="looking-glass"
+ local docker_img="wikihostinc/looking-glass-server"
+ local docker_port=8016
+
+
+ docker_rum() {
+
+ docker run -d --name looking-glass --restart=always -p ${docker_port}:80 wikihostinc/looking-glass-server
+
+ }
+
+ local docker_describe="Speedtest speed measurement panel is a VPS network speed test tool with multiple test functions and can also monitor VPS inbound and outbound traffic in real time."
+ local docker_url="Official website introduction:${gh_proxy}github.com/wikihost-opensource/als"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 17|adguardhome)
+
+ local app_id="17"
+ local docker_name="adguardhome"
+ local docker_img="adguard/adguardhome"
+ local docker_port=8017
+
+ docker_rum() {
+
+ docker run -d \
+ --name adguardhome \
+ -v /home/docker/adguardhome/work:/opt/adguardhome/work \
+ -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
+ -p 53:53/tcp \
+ -p 53:53/udp \
+ -p ${docker_port}:3000/tcp \
+ --restart=always \
+ adguard/adguardhome
+
+
+ }
+
+
+ local docker_describe="AdGuardHome is a network-wide ad blocking and anti-tracking software that will be more than just a DNS server in the future."
+ local docker_url="Official website introduction: https://hub.docker.com/r/adguard/adguardhome"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 18|onlyoffice)
+
+ local app_id="18"
+ local docker_name="onlyoffice"
+ local docker_img="onlyoffice/documentserver"
+ local docker_port=8018
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:80 \
+ --restart=always \
+ --name onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
+ onlyoffice/documentserver
+
+
+ }
+
+ local docker_describe="onlyoffice is an open source online office tool, so powerful!"
+ local docker_url="Official website introduction: https://www.onlyoffice.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 19|safeline)
+ send_stats "Build a thunder pool"
+
+ local app_id="19"
+ local docker_name=safeline-mgt
+ local docker_port=9443
+ while true; do
+ check_docker_app
+ clear
+ echo -e "Thunder pool service$check_docker"
+ echo "Leichi is a WAF site firewall program panel developed by Changting Technology, which can reverse the site for automated defense."
+ echo "Video introduction: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ check_docker_app_ip
+ fi
+ echo ""
+
+ echo "------------------------"
+ echo "1. Install 2. Update 3. Reset password 4. Uninstall"
+ echo "------------------------"
+ echo "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Enter your selection:" choice
+
+ case $choice in
+ 1)
+ install_docker
+ check_disk_space 5
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
+
+ add_app_id
+ clear
+ echo "The Leichi WAF panel has been installed"
+ check_docker_app_ip
+ docker exec safeline-mgt resetadmin
+
+ ;;
+
+ 2)
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
+ docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
+ echo ""
+
+ add_app_id
+ clear
+ echo "The Leichi WAF panel has been updated"
+ check_docker_app_ip
+ ;;
+ 3)
+ docker exec safeline-mgt resetadmin
+ ;;
+ 4)
+ cd /data/safeline
+ docker compose down --rmi all
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "If you are in the default installation directory, the project has been uninstalled now. If you customize the installation directory, you need to go to the installation directory and execute it yourself:"
+ echo "docker compose down && docker compose down --rmi all"
+ ;;
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 20|portainer)
+ local app_id="20"
+ local docker_name="portainer"
+ local docker_img="portainer/portainer"
+ local docker_port=8020
+
+ docker_rum() {
+
+ docker run -d \
+ --name portainer \
+ -p ${docker_port}:9000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/portainer:/data \
+ --restart=always \
+ portainer/portainer
+
+ }
+
+
+ local docker_describe="portainer is a lightweight docker container management panel"
+ local docker_url="Official website introduction: https://www.portainer.io/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 21|vscode)
+ local app_id="21"
+ local docker_name="vscode-web"
+ local docker_img="codercom/code-server"
+ local docker_port=8021
+
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart=always codercom/code-server
+
+ }
+
+
+ local docker_describe="VScode is a powerful online code writing tool"
+ local docker_url="Official website introduction:${gh_proxy}github.com/coder/code-server"
+ local docker_use="sleep 3"
+ local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 22|uptime-kuma)
+ local app_id="22"
+ local docker_name="uptime-kuma"
+ local docker_img="louislam/uptime-kuma:latest"
+ local docker_port=8022
+
+
+ docker_rum() {
+
+ docker run -d \
+ --name=uptime-kuma \
+ -p ${docker_port}:3001 \
+ -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
+ --restart=always \
+ louislam/uptime-kuma:latest
+
+ }
+
+
+ local docker_describe="Uptime Kuma Easy-to-use self-hosted monitoring tool"
+ local docker_url="Official website introduction:${gh_proxy}github.com/louislam/uptime-kuma"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 23|memos)
+ local app_id="23"
+ local docker_name="memos"
+ local docker_img="neosmemo/memos:stable"
+ local docker_port=8023
+
+ docker_rum() {
+
+ docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart=always neosmemo/memos:stable
+
+ }
+
+ local docker_describe="Memos is a lightweight, self-hosted memo center"
+ local docker_url="Official website introduction:${gh_proxy}github.com/usememos/memos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 24|webtop)
+ local app_id="24"
+ local docker_name="webtop"
+ local docker_img="lscr.io/linuxserver/webtop:latest"
+ local docker_port=8024
+
+ docker_rum() {
+
+ read -e -p "Set login username:" admin
+ read -e -p "Set login user password:" admin_password
+ docker run -d \
+ --name=webtop \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -e LC_ALL=zh_CN.UTF-8 \
+ -e DOCKER_MODS=linuxserver/mods:universal-package-install \
+ -e INSTALL_PACKAGES=font-noto-cjk \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:latest
+
+ }
+
+
+ local docker_describe="webtop is based on the Chinese version of Alpine container. If the IP cannot be accessed, please add a domain name for access."
+ local docker_url="Official website introduction: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 25|nextcloud)
+ local app_id="25"
+ local docker_name="nextcloud"
+ local docker_img="nextcloud:latest"
+ local docker_port=8025
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+
+ }
+
+ local docker_describe="With over 400,000 deployments, Nextcloud is the most popular local content collaboration platform you can download"
+ local docker_url="Official website introduction: https://nextcloud.com/"
+ local docker_use="echo \"Account: nextcloud Password:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 26|qd)
+ local app_id="26"
+ local docker_name="qd"
+ local docker_img="qdtoday/qd:latest"
+ local docker_port=8026
+
+ docker_rum() {
+
+ docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+
+ }
+
+ local docker_describe="QD-Today is an HTTP request scheduled task automatic execution framework"
+ local docker_url="Official website introduction: https://qd-today.github.io/qd/zh_CN/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 27|dockge)
+ local app_id="27"
+ local docker_name="dockge"
+ local docker_img="louislam/dockge:latest"
+ local docker_port=8027
+
+ docker_rum() {
+
+ docker run -d --name dockge --restart=always -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+
+ }
+
+ local docker_describe="dockge is a visual docker-compose container management panel"
+ local docker_url="Official website introduction:${gh_proxy}github.com/louislam/dockge"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 28|speedtest)
+ local app_id="28"
+ local docker_name="speedtest"
+ local docker_img="ghcr.io/librespeed/speedtest"
+ local docker_port=8028
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 --name speedtest --restart=always ghcr.io/librespeed/speedtest
+
+ }
+
+ local docker_describe="librespeed is a lightweight speed testing tool implemented in Javascript that can be used out of the box"
+ local docker_url="Official website introduction:${gh_proxy}github.com/librespeed/speedtest"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 29|searxng)
+ local app_id="29"
+ local docker_name="searxng"
+ local docker_img="searxng/searxng"
+ local docker_port=8029
+
+ docker_rum() {
+
+ docker run -d \
+ --name searxng \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v "/home/docker/searxng:/etc/searxng" \
+ searxng/searxng
+
+ }
+
+ local docker_describe="searxng is a private and private search engine site"
+ local docker_url="Official website introduction: https://hub.docker.com/r/alandoyle/searxng"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 30|photoprism)
+ local app_id="30"
+ local docker_name="photoprism"
+ local docker_img="photoprism/photoprism:latest"
+ local docker_port=8030
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d \
+ --name photoprism \
+ --restart=always \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ -p ${docker_port}:2342 \
+ -e PHOTOPRISM_UPLOAD_NSFW="true" \
+ -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
+ -v /home/docker/photoprism/storage:/photoprism/storage \
+ -v /home/docker/photoprism/Pictures:/photoprism/originals \
+ photoprism/photoprism
+
+ }
+
+
+ local docker_describe="Photoprism is a very powerful private photo album system"
+ local docker_url="Official website introduction: https://www.photoprism.app/"
+ local docker_use="echo \"Account: admin Password:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 31|s-pdf)
+ local app_id="31"
+ local docker_name="s-pdf"
+ local docker_img="frooodle/s-pdf:latest"
+ local docker_port=8031
+
+ docker_rum() {
+
+ docker run -d \
+ --name s-pdf \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
+ -v /home/docker/s-pdf/extraConfigs:/configs \
+ -v /home/docker/s-pdf/logs:/logs \
+ -e DOCKER_ENABLE_SECURITY=false \
+ frooodle/s-pdf:latest
+ }
+
+ local docker_describe="This is a powerful locally hosted web-based PDF manipulation tool using docker that allows you to perform various operations on PDF files such as split merge, convert, reorganize, add images, rotate, compress, etc."
+ local docker_url="Official website introduction:${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 32|drawio)
+ local app_id="32"
+ local docker_name="drawio"
+ local docker_img="jgraph/drawio"
+ local docker_port=8032
+
+ docker_rum() {
+
+ docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+
+ }
+
+
+ local docker_describe="This is a powerful charting software. You can draw mind maps, topology diagrams, and flow charts."
+ local docker_url="Official website introduction: https://www.drawio.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 33|sun-panel)
+ local app_id="33"
+ local docker_name="sun-panel"
+ local docker_img="hslr/sun-panel"
+ local docker_port=8033
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:3002 \
+ -v /home/docker/sun-panel/conf:/app/conf \
+ -v /home/docker/sun-panel/uploads:/app/uploads \
+ -v /home/docker/sun-panel/database:/app/database \
+ --name sun-panel \
+ hslr/sun-panel
+
+ }
+
+ local docker_describe="Sun-Panel server, NAS navigation panel, Homepage, browser homepage"
+ local docker_url="Official website introduction: https://doc.sun-panel.top/zh_cn/"
+ local docker_use="echo \"Account: admin@sun.cc Password: 12345678\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 34|pingvin-share)
+ local app_id="34"
+ local docker_name="pingvin-share"
+ local docker_img="stonith404/pingvin-share"
+ local docker_port=8034
+
+ docker_rum() {
+
+ docker run -d \
+ --name pingvin-share \
+ --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/pingvin-share/data:/opt/app/backend/data \
+ stonith404/pingvin-share
+ }
+
+ local docker_describe="Pingvin Share is a self-buildable file sharing platform and an alternative to WeTransfer"
+ local docker_url="Official website introduction:${gh_proxy}github.com/stonith404/pingvin-share"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 35|moments)
+ local app_id="35"
+ local docker_name="moments"
+ local docker_img="kingwrcy/moments:latest"
+ local docker_port=8035
+
+ docker_rum() {
+
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/moments/data:/app/data \
+ -v /etc/localtime:/etc/localtime:ro \
+ -v /etc/timezone:/etc/timezone:ro \
+ --name moments \
+ kingwrcy/moments:latest
+ }
+
+
+ local docker_describe="Minimalist Moments, high imitation WeChat Moments, record your wonderful life"
+ local docker_url="Official website introduction:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
+ local docker_use="echo \"Account: admin Password: a123456\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+
+ 36|lobe-chat)
+ local app_id="36"
+ local docker_name="lobe-chat"
+ local docker_img="lobehub/lobe-chat:latest"
+ local docker_port=8036
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:3210 \
+ --name lobe-chat \
+ --restart=always \
+ lobehub/lobe-chat
+ }
+
+ local docker_describe="LobeChat aggregates the mainstream AI large models on the market, ChatGPT/Claude/Gemini/Groq/Ollama"
+ local docker_url="Official website introduction:${gh_proxy}github.com/lobehub/lobe-chat"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 37|myip)
+ local app_id="37"
+ local docker_name="myip"
+ local docker_img="jason5ng32/myip:latest"
+ local docker_port=8037
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+
+ }
+
+
+ local docker_describe="It is a multifunctional IP toolbox that allows you to view your own IP information and connectivity, and displays it using a web panel."
+ local docker_url="Official website introduction:${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 38|xiaoya)
+ send_stats "Xiaoya family bucket"
+ clear
+ install_docker
+ check_disk_space 1
+ bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
+ ;;
+
+ 39|bililive)
+
+ if [ ! -d /home/docker/bililive-go/ ]; then
+ mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
+ wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
+ fi
+
+ local app_id="39"
+ local docker_name="bililive-go"
+ local docker_img="chigusa/bililive-go"
+ local docker_port=8039
+
+ docker_rum() {
+
+ docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+
+ }
+
+ local docker_describe="Bililive-go is a live broadcast recording tool that supports multiple live broadcast platforms"
+ local docker_url="Official website introduction:${gh_proxy}github.com/hr3lxphr6j/bililive-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 40|webssh)
+ local app_id="40"
+ local docker_name="webssh"
+ local docker_img="jrohy/webssh"
+ local docker_port=8040
+ docker_rum() {
+ docker run -d -p ${docker_port}:5032 --restart=always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
+ }
+
+ local docker_describe="Simple online ssh connection tool and sftp tool"
+ local docker_url="Official website introduction:${gh_proxy}github.com/Jrohy/webssh"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 41|haozi|acepanel)
+
+ local app_id="41"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AcePanel original mouse panel"
+ local panelurl="Official address:${gh_proxy}github.com/acepanel/panel"
+
+ panel_app_install() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+ }
+
+ panel_app_manage() {
+ acepanel help
+ }
+
+ panel_app_uninstall() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+
+ }
+
+ install_panel
+
+ ;;
+
+
+ 42|nexterm)
+ local app_id="42"
+ local docker_name="nexterm"
+ local docker_img="germannewsmaker/nexterm:latest"
+ local docker_port=8042
+
+ docker_rum() {
+
+ ENCRYPTION_KEY=$(openssl rand -hex 32)
+ docker run -d \
+ --name nexterm \
+ -e ENCRYPTION_KEY=${ENCRYPTION_KEY} \
+ -p ${docker_port}:6989 \
+ -v /home/docker/nexterm:/app/data \
+ --restart=always \
+ germannewsmaker/nexterm:latest
+
+ }
+
+ local docker_describe="nexterm is a powerful online SSH/VNC/RDP connection tool."
+ local docker_url="Official website introduction:${gh_proxy}github.com/gnmyt/Nexterm"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 43|hbbs)
+ local app_id="43"
+ local docker_name="hbbs"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbs
+
+ }
+
+
+ local docker_describe="Rustdesk's open source remote desktop (server) is similar to its own Sunflower private server."
+ local docker_url="Official website introduction: https://rustdesk.com/zh-cn/"
+ local docker_use="docker logs hbbs"
+ local docker_passwd="echo \"Record your IP and key, which will be used in the remote desktop client. Go to option 44 to install the relay!\""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 44|hbbr)
+ local app_id="44"
+ local docker_name="hbbr"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbr
+
+ }
+
+ local docker_describe="Rustdesk's open source remote desktop (relay) is similar to its own Sunflower private server."
+ local docker_url="Official website introduction: https://rustdesk.com/zh-cn/"
+ local docker_use="echo \"Go to the official website to download the remote desktop client: https://rustdesk.com/zh-cn/\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 45|registry)
+ local app_id="45"
+ local docker_name="registry"
+ local docker_img="registry:2"
+ local docker_port=8045
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name registry \
+ -v /home/docker/registry:/var/lib/registry \
+ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
+ --restart=always \
+ registry:2
+
+ }
+
+ local docker_describe="Docker Registry is a service for storing and distributing Docker images."
+ local docker_url="Official website introduction: https://hub.docker.com/_/registry"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 46|ghproxy)
+ local app_id="46"
+ local docker_name="ghproxy"
+ local docker_img="wjqserver/ghproxy:latest"
+ local docker_port=8046
+
+ docker_rum() {
+
+ docker run -d --name ghproxy --restart=always -p ${docker_port}:8080 -v /home/docker/ghproxy/config:/data/ghproxy/config wjqserver/ghproxy:latest
+
+ }
+
+ local docker_describe="GHProxy implemented using Go is used to accelerate the pulling of Github repositories in some areas."
+ local docker_url="Official website introduction:${gh_https_url}github.com/WJQSERVER-STUDIO/ghproxy"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 47|prometheus|grafana)
+
+ local app_id="47"
+ local app_name="Prometheus monitoring"
+ local app_text="Prometheus+Grafana enterprise-level monitoring system"
+ local app_url="Official website introduction: https://prometheus.io"
+ local docker_name="grafana"
+ local docker_port="8047"
+ local app_size="2"
+
+ docker_app_install() {
+ prometheus_install
+ clear
+ ip_address
+ echo "Installation completed"
+ check_docker_app_ip
+ echo "The initial username and password are: admin"
+ }
+
+ docker_app_update() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+
+ rm -rf /home/docker/monitoring
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+ ;;
+
+ 48|node-exporter)
+ local app_id="48"
+ local docker_name="node-exporter"
+ local docker_img="prom/node-exporter"
+ local docker_port=8048
+
+ docker_rum() {
+
+ docker run -d \
+ --name=node-exporter \
+ -p ${docker_port}:9100 \
+ --restart=always \
+ prom/node-exporter
+
+
+ }
+
+ local docker_describe="This is a Prometheus host data collection component, please deploy it on the monitored host."
+ local docker_url="Official website introduction:${gh_https_url}github.com/prometheus/node_exporter"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 49|cadvisor)
+ local app_id="49"
+ local docker_name="cadvisor"
+ local docker_img="gcr.io/cadvisor/cadvisor:latest"
+ local docker_port=8049
+
+ docker_rum() {
+
+ docker run -d \
+ --name=cadvisor \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ --volume=/:/rootfs:ro \
+ --volume=/var/run:/var/run:rw \
+ --volume=/sys:/sys:ro \
+ --volume=/var/lib/docker/:/var/lib/docker:ro \
+ gcr.io/cadvisor/cadvisor:latest \
+ -housekeeping_interval=10s \
+ -docker_only=true
+
+ }
+
+ local docker_describe="This is a Prometheus container data collection component, please deploy it on the monitored host."
+ local docker_url="Official website introduction:${gh_https_url}github.com/google/cadvisor"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 50|changedetection)
+ local app_id="50"
+ local docker_name="changedetection"
+ local docker_img="dgtlmoon/changedetection.io:latest"
+ local docker_port=8050
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:5000 \
+ -v /home/docker/datastore:/datastore \
+ --name changedetection dgtlmoon/changedetection.io:latest
+
+ }
+
+ local docker_describe="This is a small tool for website change detection, replenishment monitoring and notification"
+ local docker_url="Official website introduction:${gh_https_url}github.com/dgtlmoon/changedetection.io"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 51|pve)
+ clear
+ send_stats "PVE open chick"
+ check_disk_space 1
+ curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
+ ;;
+
+
+ 52|dpanel)
+ local app_id="52"
+ local docker_name="dpanel"
+ local docker_img="dpanel/dpanel:lite"
+ local docker_port=8052
+
+ docker_rum() {
+
+ docker run -d --name dpanel --restart=always \
+ -p ${docker_port}:8080 -e APP_NAME=dpanel \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/dpanel:/dpanel \
+ dpanel/dpanel:lite
+
+ }
+
+ local docker_describe="Docker visual panel system provides complete docker management functions."
+ local docker_url="Official website introduction:${gh_https_url}github.com/donknap/dpanel"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 53|llama3)
+ local app_id="53"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI is a large language model web page framework that is connected to the new llama3 large language model."
+ local docker_url="Official website introduction:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run llama3.2:1b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+ 54|amh)
+
+ local app_id="54"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AMH panel"
+ local panelurl="Official address: https://amh.sh/index.htm?amh"
+
+ panel_app_install() {
+ cd ~
+ wget https://dl.amh.sh/amh.sh && bash amh.sh
+ }
+
+ panel_app_manage() {
+ panel_app_install
+ }
+
+ panel_app_uninstall() {
+ panel_app_install
+ }
+
+ install_panel
+ ;;
+
+
+ 55|frps)
+ frps_panel
+ ;;
+
+ 56|frpc)
+ frpc_panel
+ ;;
+
+ 57|deepseek)
+ local app_id="57"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI is a large language model web page framework that is connected to the new DeepSeek R1 large language model."
+ local docker_url="Official website introduction:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+ 58|dify)
+ local app_id="58"
+ local app_name="DifyKnowledge Base"
+ local app_text="It is an open source large language model (LLM) application development platform. Self-hosted training data for AI generation"
+ local app_url="Official website: https://docs.dify.ai/zh-hans"
+ local docker_name="docker-nginx-1"
+ local docker_port="8058"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
+ sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+
+ docker compose up -d
+
+ chown -R 1001:1001 /home/docker/dify/docker/volumes/app/storage
+ chmod -R 755 /home/docker/dify/docker/volumes/app/storage
+ docker compose down
+ docker compose up -d
+
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ cd /home/docker/dify/
+ git pull ${gh_proxy}github.com/langgenius/dify.git main > /dev/null 2>&1
+ sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
+ cd /home/docker/dify/docker/ && docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/dify
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+
+ ;;
+
+ 59|new-api)
+ local app_id="59"
+ local app_name="NewAPI"
+ local app_text="New generation of large model gateway and AI asset management system"
+ local app_url="Official website:${gh_https_url}github.com/Calcium-Ion/new-api"
+ local docker_name="new-api"
+ local docker_port="8059"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ cd /home/docker/new-api/
+
+ git pull ${gh_proxy}github.com/Calcium-Ion/new-api.git main > /dev/null 2>&1
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ rm -rf /home/docker/new-api
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 60|jms)
+
+ local app_id="60"
+ local app_name="JumpServer open source bastion machine"
+ local app_text="It is an open source privileged access management (PAM) tool. This program occupies port 80 and does not support adding domain names for access."
+ local app_url="Official introduction:${gh_https_url}github.com/jumpserver/jumpserver"
+ local docker_name="jms_web"
+ local docker_port="80"
+ local app_size="2"
+
+ docker_app_install() {
+ curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ echo "Initial username: admin"
+ echo "Initial password: ChangeMe"
+ }
+
+
+ docker_app_update() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh upgrade
+ echo "App has been updated"
+ }
+
+
+ docker_app_uninstall() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh uninstall
+ cd /opt
+ rm -rf jumpserver-installer*/
+ rm -rf jumpserver
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+ ;;
+
+ 61|libretranslate)
+ local app_id="61"
+ local docker_name="libretranslate"
+ local docker_img="libretranslate/libretranslate:latest"
+ local docker_port=8061
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name libretranslate \
+ libretranslate/libretranslate \
+ --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+
+ }
+
+ local docker_describe="Free open source machine translation API, fully self-hosted, and its translation engine is powered by the open source Argos Translate library."
+ local docker_url="Official website introduction:${gh_https_url}github.com/LibreTranslate/LibreTranslate"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+
+ 62|ragflow)
+ local app_id="62"
+ local app_name="RAGFlow knowledge base"
+ local app_text="Open source RAG (Retrieval Augmented Generation) engine based on deep document understanding"
+ local app_url="Official website:${gh_https_url}github.com/infiniflow/ragflow"
+ local docker_name="ragflow-server"
+ local docker_port="8062"
+ local app_size="8"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- local docker_name="npm"
- local docker_img="jc21/nginx-proxy-manager:latest"
- local docker_port=81
+ docker_app_update() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ cd /home/docker/ragflow/
+ git pull ${gh_proxy}github.com/infiniflow/ragflow.git main > /dev/null 2>&1
+ cd /home/docker/ragflow/docker/
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ }
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/ragflow
+ echo "App has been uninstalled"
+ }
- docker run -d \
- --name=$docker_name \
- -p ${docker_port}:81 \
- -p 80:80 \
- -p 443:443 \
- -v /home/docker/npm/data:/data \
- -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
- --restart=always \
- $docker_img
+ docker_app_plus
+ ;;
- }
- local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
- local docker_url="官网介绍: https://nginxproxymanager.com/"
- local docker_use="echo \"初始用户名: admin@example.com\""
- local docker_passwd="echo \"初始密码: changeme\""
- local app_size="1"
+ 63|open-webui)
+ local app_id="63"
+ local docker_name="open-webui"
+ local docker_img="ghcr.io/open-webui/open-webui:main"
+ local docker_port=8063
- docker_app
+ docker_rum() {
- ;;
+ docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart=always ghcr.io/open-webui/open-webui:main
- 5)
+ }
- local docker_name="openlist"
- local docker_img="openlistteam/openlist:latest-aria2"
- local docker_port=5244
+ local docker_describe="OpenWebUI is a large language model web page framework, the official simplified version supports API access to all major models."
+ local docker_url="Official website introduction:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
- docker_rum() {
+ 64|it-tools)
+ local app_id="64"
+ local docker_name="it-tools"
+ local docker_img="corentinth/it-tools:latest"
+ local docker_port=8064
- docker run -d \
- --restart=always \
- -v /home/docker/openlist:/opt/openlist/data \
- -p ${docker_port}:5244 \
- -e PUID=0 \
- -e PGID=0 \
- -e UMASK=022 \
- --name="openlist" \
- openlistteam/openlist:latest-aria2
+ docker_rum() {
+ docker run -d --name it-tools --restart=always -p ${docker_port}:80 corentinth/it-tools:latest
+ }
- }
+ local docker_describe="Very useful tool for developers and IT workers"
+ local docker_url="Official website introduction:${gh_https_url}github.com/CorentinTh/it-tools"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
- local docker_url="官网介绍: https://github.com/OpenListTeam/OpenList"
- local docker_use="docker exec -it openlist ./openlist admin random"
- local docker_passwd=""
- local app_size="1"
- docker_app
+ 65|n8n)
+ local app_id="65"
+ local docker_name="n8n"
+ local docker_img="docker.n8n.io/n8nio/n8n"
+ local docker_port=8065
- ;;
+ docker_rum() {
- 6)
+ add_yuming
+ mkdir -p /home/docker/n8n
+ chmod -R 777 /home/docker/n8n
+
+ docker run -d --name n8n \
+ --restart=always \
+ -p ${docker_port}:5678 \
+ -v /home/docker/n8n:/home/node/.n8n \
+ -e N8N_HOST=${yuming} \
+ -e N8N_PORT=5678 \
+ -e N8N_PROTOCOL=https \
+ -e WEBHOOK_URL=https://${yuming}/ \
+ docker.n8n.io/n8nio/n8n
- local docker_name="webtop-ubuntu"
- local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
- local docker_port=3006
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- docker_rum() {
+ }
+ local docker_describe="It is a powerful automated workflow platform"
+ local docker_url="Official website introduction:${gh_https_url}github.com/n8n-io/n8n"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker run -d \
- --name=webtop-ubuntu \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=ubuntu-abc \
- -e PASSWORD=ubuntuABC123 \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:ubuntu-kde
+ 66|yt)
+ yt_menu_pro
+ ;;
- }
+ 67|ddns)
+ local app_id="67"
+ local docker_name="ddns-go"
+ local docker_img="jeessy/ddns-go"
+ local docker_port=8067
+ docker_rum() {
+ docker run -d \
+ --name ddns-go \
+ --restart=always \
+ -p ${docker_port}:9876 \
+ -v /home/docker/ddns-go:/root \
+ jeessy/ddns-go
- local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: ubuntu-abc\""
- local docker_passwd="echo \"密码: ubuntuABC123\""
- local app_size="2"
- docker_app
+ }
+ local docker_describe="Automatically update your public IP (IPv4/IPv6) to major DNS service providers in real time to achieve dynamic domain name resolution."
+ local docker_url="Official website introduction:${gh_https_url}github.com/jeessy2/ddns-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- ;;
- 7)
- clear
- send_stats "Build Nezha"
- local docker_name="nezha-dashboard"
- local docker_port=8008
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
- clear
- echo -e "Nezha Monitoring$check_docker $update_status"
- echo "Open source, lightweight and easy-to-use server monitoring and operation and maintenance tools"
- echo "Official website construction document: https://nezha.wiki/guide/dashboard.html"
- if docker inspect "$docker_name" &>/dev/null; then
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. Use"
- echo "------------------------"
- echo "0. Return to the previous menu"
- echo "------------------------"
- read -e -p "Enter your choice:" choice
+ 68|allinssl)
+ local app_id="68"
+ local docker_name="allinssl"
+ local docker_img="allinssl/allinssl:latest"
+ local docker_port=8068
- case $choice in
- 1)
- check_disk_space 1
- install unzip jq
- install_docker
- curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- ;;
+ docker_rum() {
+ docker run -d --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
+ }
- *)
- break
- ;;
+ local docker_describe="Open source free SSL certificate automation management platform"
+ local docker_url="Official website introduction: https://allinssl.com"
+ local docker_use="echo \"Security entrance: /allinssl\""
+ local docker_passwd="echo \"Username: allinssl Password: allinssldocker\""
+ local app_size="1"
+ docker_app
+ ;;
- esac
- break_end
- done
- ;;
- 8)
+ 69|sftpgo)
+ local app_id="69"
+ local docker_name="sftpgo"
+ local docker_img="drakkan/sftpgo:latest"
+ local docker_port=8069
- local docker_name="qbittorrent"
- local docker_img="lscr.io/linuxserver/qbittorrent:latest"
- local docker_port=8081
-
- docker_rum() {
-
- docker run -d \
- --name=qbittorrent \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e WEBUI_PORT=${docker_port} \
- -e TORRENTING_PORT=56881 \
- -p ${docker_port}:${docker_port} \
- -p 56881:56881 \
- -p 56881:56881/udp \
- -v /home/docker/qbittorrent/config:/config \
- -v /home/docker/qbittorrent/downloads:/downloads \
- --restart unless-stopped \
- lscr.io/linuxserver/qbittorrent:latest
+ docker_rum() {
- }
+ mkdir -p /home/docker/sftpgo/data
+ mkdir -p /home/docker/sftpgo/config
+ chown -R 1000:1000 /home/docker/sftpgo
- local docker_describe="qbittorrent离线BT磁力下载服务"
- local docker_url="官网介绍: https://hub.docker.com/r/linuxserver/qbittorrent"
- local docker_use="sleep 3"
- local docker_passwd="docker logs qbittorrent"
- local app_size="1"
- docker_app
+ docker run -d \
+ --name sftpgo \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -p 22022:2022 \
+ --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
+ --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
+ drakkan/sftpgo:latest
- ;;
+ }
- 9)
- send_stats "Build a post office"
- clear
- install telnet
- local docker_name=“mailserver”
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
+ local docker_describe="Open source free anytime, anywhere SFTP FTP WebDAV file transfer tool"
+ local docker_url="Official website introduction: https://sftpgo.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- clear
- echo -e "Post Office Services$check_docker $update_status"
- echo "poste.io is an open source mail server solution."
- echo "Video introduction: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
- echo ""
- echo "Port detection"
- port=25
- timeout=3
- if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
- echo -e "${gl_lv}port$portCurrently available${gl_bai}"
- else
- echo -e "${gl_hong}port$portNot currently available${gl_bai}"
- fi
- echo ""
+ 70|astrbot)
+ local app_id="70"
+ local docker_name="astrbot"
+ local docker_img="soulter/astrbot:latest"
+ local docker_port=8070
- if docker inspect "$docker_name" &>/dev/null; then
- yuming=$(cat /home/docker/mail.txt)
- echo "Access address:"
- echo "https://$yuming"
- fi
+ docker_rum() {
- echo "------------------------"
- echo "1. Install 2. Update 3. Uninstall"
- echo "------------------------"
- echo "0. Return to the previous menu"
- echo "------------------------"
- read -e -p "Enter your choice:" choice
+ mkdir -p /home/docker/astrbot/data
- case $choice in
- 1)
- check_disk_space 2
- read -e -p "Please set the email domain name, for example, mail.yuming.com:" yuming
- mkdir -p /home/docker
- echo "$yuming" > /home/docker/mail.txt
- echo "------------------------"
- ip_address
- echo "Parse these DNS records first"
- echo "A mail $ipv4_address"
- echo "CNAME imap $yuming"
- echo "CNAME pop $yuming"
- echo "CNAME smtp $yuming"
- echo "MX @ $yuming"
- echo "TXT @ v=spf1 mx ~all"
- echo "TXT ? ?"
- echo ""
- echo "------------------------"
- echo "Press any key to continue..."
- read -n 1 -s -r -p ""
+ docker run -d \
+ -p ${docker_port}:6185 \
+ -p 6195:6195 \
+ -p 6196:6196 \
+ -p 6199:6199 \
+ -p 11451:11451 \
+ -v /home/docker/astrbot/data:/AstrBot/data \
+ --restart=always \
+ --name astrbot \
+ soulter/astrbot:latest
- install jq
- install_docker
+ }
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.io
+ local docker_describe="Open source AI chatbot framework, supporting WeChat, QQ, and TG access to large AI models"
+ local docker_url="Official website introduction: https://astrbot.app/"
+ local docker_use="echo \"Username: astrbot Password: astrbot\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- clear
- echo "poste.io has been installed"
- echo "------------------------"
- echo "You can access poste.io using the following address:"
- echo "https://$yuming"
- echo ""
- ;;
+ 71|navidrome)
+ local app_id="71"
+ local docker_name="navidrome"
+ local docker_img="deluan/navidrome:latest"
+ local docker_port=8071
- 2)
- docker rm -f mailserver
- docker rmi -f analogic/poste.i
- yuming=$(cat /home/docker/mail.txt)
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.i
- clear
- echo "poste.io has been installed"
- echo "------------------------"
- echo "You can access poste.io using the following address:"
- echo "https://$yuming"
- echo ""
- ;;
- 3)
- docker rm -f mailserver
- docker rmi -f analogic/poste.io
- rm /home/docker/mail.txt
- rm -rf /home/docker/mail
- echo "The app has been uninstalled"
- ;;
+ docker_rum() {
- *)
- break
- ;;
+ docker run -d \
+ --name navidrome \
+ --restart=always \
+ --user $(id -u):$(id -g) \
+ -v /home/docker/navidrome/music:/music \
+ -v /home/docker/navidrome/data:/data \
+ -p ${docker_port}:4533 \
+ -e ND_LOGLEVEL=info \
+ deluan/navidrome:latest
- esac
- break_end
- done
+ }
- ;;
+ local docker_describe="It is a lightweight, high-performance music streaming server"
+ local docker_url="Official website introduction: https://www.navidrome.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 10)
- local app_name="Rocket.Chat聊天系统"
- local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
- local app_url="官方介绍: https://www.rocket.chat/"
- local docker_name="rocketchat"
- local docker_port="3897"
- local app_size="2"
-
- docker_app_install() {
- docker run --name db -d --restart=always \
- -v /home/docker/mongo/dump:/dump \
- mongo:latest --replSet rs5 --oplogSize 256
- sleep 1
- docker exec -it db mongosh --eval "printjson(rs.initiate())"
- sleep 5
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ 72|bitwarden)
- clear
- ip_address
- echo "Installed"
- check_docker_app_ip
- }
+ local app_id="72"
+ local docker_name="bitwarden"
+ local docker_img="vaultwarden/server"
+ local docker_port=8072
- docker_app_update() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat:latest
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "rocket.chat has been installed"
- check_docker_app_ip
- }
+ docker_rum() {
- docker_app_uninstall() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat
- docker rm -f db
- docker rmi -f mongo:latest
- rm -rf /home/docker/mongo
- echo "The app has been uninstalled"
- }
+ docker run -d \
+ --name bitwarden \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/bitwarden/data:/data \
+ vaultwarden/server
- docker_app_plus
- ;;
+ }
+ local docker_describe="A password manager that puts you in control of your data"
+ local docker_url="Official website introduction: https://bitwarden.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 11)
- local docker_name="zentao-server"
- local docker_img="idoop/zentao:latest"
- local docker_port=82
+ ;;
- docker_rum() {
+ 73|libretv)
- docker run -d -p ${docker_port}:80 \
- -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
- -e BIND_ADDRESS="false" \
- -v /home/docker/zentao-server/:/opt/zbox/ \
- --add-host smtp.exmail.qq.com:163.177.90.125 \
- --name zentao-server \
- --restart=always \
- idoop/zentao:latest
+ local app_id="73"
+ local docker_name="libretv"
+ local docker_img="bestzwei/libretv:latest"
+ local docker_port=8073
+ docker_rum() {
- }
+ read -e -p "Set LibreTV login password:" app_passwd
- local docker_describe="禅道是通用的项目管理软件"
- local docker_url="官网介绍: https://www.zentao.net/"
- local docker_use="echo \"初始用户名: admin\""
- local docker_passwd="echo \"初始密码: 123456\""
- local app_size="2"
- docker_app
+ docker run -d \
+ --name libretv \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -e PASSWORD=${app_passwd} \
+ bestzwei/libretv:latest
- ;;
+ }
- 12)
- local docker_name="qinglong"
- local docker_img="whyour/qinglong:latest"
- local docker_port=5700
+ local docker_describe="Free online video search and viewing platform"
+ local docker_url="Official website introduction:${gh_https_url}github.com/LibreSpark/LibreTV"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run -d \
- -v /home/docker/qinglong/data:/ql/data \
- -p ${docker_port}:5700 \
- --name qinglong \
- --hostname qinglong \
- --restart unless-stopped \
- whyour/qinglong:latest
+ 74|moontv)
- }
+ local app_id="74"
- local docker_describe="青龙面板是一个定时任务管理平台"
- local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_name="moontv private film and television"
+ local app_text="Free online video search and viewing platform"
+ local app_url="Video introduction:${gh_https_url}github.com/MoonTechLab/LunaTV"
+ local docker_name="moontv-core"
+ local docker_port="8074"
+ local app_size="2"
- ;;
- 13)
+ docker_app_install() {
+ read -e -p "Set login username:" admin
+ read -e -p "Set login user password:" admin_password
+ read -e -p "Enter authorization code:" shouquanma
- local app_name="cloudreve网盘"
- local app_text="cloudreve是一个支持多家云存储的网盘系统"
- local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
- local docker_name="cloudreve"
- local docker_port="5212"
- local app_size="2"
-
- docker_app_install() {
- cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
- curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
- sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
- cd /home/docker/cloud/
- docker compose up -d
- clear
- echo "Installed"
- check_docker_app_ip
- }
+ mkdir -p /home/docker/moontv
+ mkdir -p /home/docker/moontv/config
+ mkdir -p /home/docker/moontv/data
+ cd /home/docker/moontv
- docker_app_update() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- cd /home/docker/cloud/ && docker compose up -d
- }
+ curl -o /home/docker/moontv/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/moontv-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin_password|${admin_password}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin|${admin}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|shouquanma|${shouquanma}|g" /home/docker/moontv/docker-compose.yml
+ cd /home/docker/moontv/
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- docker_app_uninstall() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- rm -rf /home/docker/cloud
- echo "The app has been uninstalled"
- }
+ docker_app_update() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ cd /home/docker/moontv/ && docker compose up -d
+ }
- docker_app_plus
- ;;
- 14)
- local docker_name="easyimage"
- local docker_img="ddsderek/easyimage:latest"
- local docker_port=85
- docker_rum() {
-
- docker run -d \
- --name easyimage \
- -p ${docker_port}:80 \
- -e TZ=Asia/Shanghai \
- -e PUID=1000 \
- -e PGID=1000 \
- -v /home/docker/easyimage/config:/app/web/config \
- -v /home/docker/easyimage/i:/app/web/i \
- --restart unless-stopped \
- ddsderek/easyimage:latest
+ docker_app_uninstall() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ rm -rf /home/docker/moontv
+ echo "App has been uninstalled"
+ }
- }
+ docker_app_plus
- local docker_describe="简单图床是一个简单的图床程序"
- local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 15)
- local docker_name="emby"
- local docker_img="linuxserver/emby:latest"
- local docker_port=8096
- docker_rum() {
+ 75|melody)
- docker run -d --name=emby --restart=always \
- -v /home/docker/emby/config:/config \
- -v /home/docker/emby/share1:/mnt/share1 \
- -v /home/docker/emby/share2:/mnt/share2 \
- -v /mnt/notify:/mnt/notify \
- -p ${docker_port}:8096 \
- -e UID=1000 -e GID=100 -e GIDLIST=100 \
- linuxserver/emby:latest
+ local app_id="75"
+ local docker_name="melody"
+ local docker_img="foamzou/melody:latest"
+ local docker_port=8075
- }
+ docker_rum() {
+ docker run -d \
+ --name melody \
+ --restart=always \
+ -p ${docker_port}:5566 \
+ -v /home/docker/melody/.profile:/app/backend/.profile \
+ foamzou/melody:latest
- local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
- local docker_url="官网介绍: https://emby.media/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 16)
- local docker_name="looking-glass"
- local docker_img="wikihostinc/looking-glass-server"
- local docker_port=89
+ }
+ local docker_describe="Your music wizard, designed to help you better manage your music."
+ local docker_url="Official website introduction:${gh_https_url}github.com/foamzou/melody"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
- docker run -d --name looking-glass --restart always -p ${docker_port}:80 wikihostinc/looking-glass-server
+ ;;
- }
- local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
- local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ 76|dosgame)
- ;;
- 17)
+ local app_id="76"
+ local docker_name="dosgame"
+ local docker_img="oldiy/dosgame-web-docker:latest"
+ local docker_port=8076
- local docker_name="adguardhome"
- local docker_img="adguard/adguardhome"
- local docker_port=3000
+ docker_rum() {
+ docker run -d \
+ --name dosgame \
+ --restart=always \
+ -p ${docker_port}:262 \
+ oldiy/dosgame-web-docker:latest
- docker_rum() {
+ }
- docker run -d \
- --name adguardhome \
- -v /home/docker/adguardhome/work:/opt/adguardhome/work \
- -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
- -p 53:53/tcp \
- -p 53:53/udp \
- -p ${docker_port}:3000/tcp \
- --restart always \
- adguard/adguardhome
+ local docker_describe="It is a Chinese DOS game collection website"
+ local docker_url="Official website introduction:${gh_https_url}github.com/rwv/chinese-dos-games"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- }
+ ;;
+ 77|xunlei)
- local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
- local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="77"
+ local docker_name="xunlei"
+ local docker_img="cnk3x/xunlei"
+ local docker_port=8077
- ;;
+ docker_rum() {
+ read -e -p "Set login username:" app_use
+ read -e -p "Set login password:" app_passwd
- 18)
+ docker run -d \
+ --name xunlei \
+ --restart=always \
+ --privileged \
+ -e XL_DASHBOARD_USERNAME=${app_use} \
+ -e XL_DASHBOARD_PASSWORD=${app_passwd} \
+ -v /home/docker/xunlei/data:/xunlei/data \
+ -v /home/docker/xunlei/downloads:/xunlei/downloads \
+ -p ${docker_port}:2345 \
+ cnk3x/xunlei
- local docker_name="onlyoffice"
- local docker_img="onlyoffice/documentserver"
- local docker_port=8082
+ }
- docker_rum() {
+ local docker_describe="Xunlei, your offline high-speed BT magnetic download tool"
+ local docker_url="Official website introduction:${gh_https_url}github.com/cnk3x/xunlei"
+ local docker_use="echo \"Log in to Xunlei with your mobile phone and enter the invitation code. Invitation code: Xunlei Niutong\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:80 \
- --restart=always \
- --name onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
- onlyoffice/documentserver
+ ;;
- }
- local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
- local docker_url="官网介绍: https://www.onlyoffice.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ 78|PandaWiki)
- ;;
+ local app_id="78"
+ local app_name="PandaWiki"
+ local app_text="PandaWiki is an open source intelligent document management system driven by AI large models. It is strongly recommended not to customize port deployment."
+ local app_url="Official introduction:${gh_https_url}github.com/chaitin/PandaWiki"
+ local docker_name="panda-wiki-nginx"
+ local docker_port="2443"
+ local app_size="2"
- 19)
- send_stats "Build a Thunder Pool"
+ docker_app_install() {
+ bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
+ }
- local docker_name=safeline-mgt
- local docker_port=9443
- while true; do
- check_docker_app
- clear
- echo -e "Thunder Pool Service$check_docker"
- echo "Lei Chi is a WAF site firewall program panel developed by Changting Technology, which can reverse the agency site for automated defense."
- echo "Video introduction: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
- if docker inspect "$docker_name" &>/dev/null; then
- check_docker_app_ip
- fi
- echo ""
+ docker_app_update() {
+ docker_app_install
+ }
- echo "------------------------"
- echo "1. Install 2. Update 3. Reset Password 4. Uninstall"
- echo "------------------------"
- echo "0. Return to the previous menu"
- echo "------------------------"
- read -e -p "Enter your choice:" choice
- case $choice in
- 1)
- install_docker
- check_disk_space 5
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
- clear
- echo "The Thunder Pool WAF panel has been installed"
- check_docker_app_ip
- docker exec safeline-mgt resetadmin
+ docker_app_uninstall() {
+ docker_app_install
+ }
- ;;
+ docker_app_plus
+ ;;
- 2)
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
- docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
- echo ""
- clear
- echo "Thunder Pool WAF panel has been updated"
- check_docker_app_ip
- ;;
- 3)
- docker exec safeline-mgt resetadmin
- ;;
- 4)
- cd /data/safeline
- docker compose down --rmi all
- echo "If you are the default installation directory, the project has now been uninstalled. If you are customizing the installation directory, you need to go to the installation directory to execute it yourself:"
- echo "docker compose down && docker compose down --rmi all"
- ;;
- *)
- break
- ;;
- esac
- break_end
- done
- ;;
+ 79|beszel)
- 20)
- local docker_name="portainer"
- local docker_img="portainer/portainer"
- local docker_port=9050
+ local app_id="79"
+ local docker_name="beszel"
+ local docker_img="henrygd/beszel"
+ local docker_port=8079
- docker_rum() {
+ docker_rum() {
- docker run -d \
- --name portainer \
- -p ${docker_port}:9000 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/portainer:/data \
- --restart always \
- portainer/portainer
+ mkdir -p /home/docker/beszel && \
+ docker run -d \
+ --name beszel \
+ --restart=always \
+ -v /home/docker/beszel:/beszel_data \
+ -p ${docker_port}:8090 \
+ henrygd/beszel
- }
+ }
+ local docker_describe="Beszel is lightweight and easy-to-use server monitoring"
+ local docker_url="Official website introduction: https://beszel.dev/zh/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="portainer是一个轻量级的docker容器管理面板"
- local docker_url="官网介绍: https://www.portainer.io/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ ;;
- ;;
- 21)
- local docker_name="vscode-web"
- local docker_img="codercom/code-server"
- local docker_port=8180
+ 80|linkwarden)
+ local app_id="80"
+ local app_name="linkwarden bookmark management"
+ local app_text="An open source, self-hosted bookmark management platform that supports tagging, search, and team collaboration."
+ local app_url="Official website: https://linkwarden.app/"
+ local docker_name="linkwarden-linkwarden-1"
+ local docker_port="8080"
+ local app_size="3"
- docker_rum() {
+ docker_app_install() {
+ install git openssl
+ mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
- docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart always codercom/code-server
+ # Download the official docker-compose and env files
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
- }
+ # Generate random keys and passwords
+ local ADMIN_EMAIL="admin@example.com"
+ local ADMIN_PASSWORD=$(openssl rand -hex 8)
+ sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
+ sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
+ sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
+ sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
- local docker_describe="VScode是一款强大的在线代码编写工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
- local docker_use="sleep 3"
- local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
- local app_size="1"
- docker_app
- ;;
- 22)
- local docker_name="uptime-kuma"
- local docker_img="louislam/uptime-kuma:latest"
- local docker_port=3003
+ # Add administrator account information
+ echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
+ echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
- docker_rum() {
+ # Start container
+ docker compose up -d
- docker run -d \
- --name=uptime-kuma \
- -p ${docker_port}:3001 \
- -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
- --restart=always \
- louislam/uptime-kuma:latest
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
+
+ # Keep original variables
+ source .env
+ mv .env.new .env
+ echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
+ echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
+ echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
+ echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
+ echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
+ echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ rm -rf /home/docker/linkwarden
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
- }
+ ;;
- local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 23)
- local docker_name="memos"
- local docker_img="ghcr.io/usememos/memos:latest"
- local docker_port=5230
+ 81|jitsi)
+ local app_id="81"
+ local app_name="JitsiMeet video conference"
+ local app_text="An open source secure video conferencing solution that supports multi-person online conferencing, screen sharing and encrypted communication."
+ local app_url="Official website: https://jitsi.org/"
+ local docker_name="jitsi"
+ local docker_app_service="jitsi-web-1"
+ local docker_port="8081"
+ local app_size="3"
- docker_rum() {
+ docker_app_install() {
- docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart always ghcr.io/usememos/memos:latest
+ add_yuming
+ mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
+ rm -rf docker-jitsi-meet-* jitsi.zip
+ local jitsi_zip_url=$(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep -m1 '"zipball_url"' | cut -d\" -f4)
+ if [ -z "$jitsi_zip_url" ]; then
+ echo "Failed to get Jitsi Meet latest download URL"
+ return 1
+ fi
+ wget -O jitsi.zip "$jitsi_zip_url"
+ unzip -q jitsi.zip
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ cp env.example .env
+ ./gen-passwords.sh
+ mkdir -p /home/docker/jitsi/config/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
+ sed -i "s|^CONFIG=.*|CONFIG=/home/docker/jitsi/config|" .env
+ grep -q '^COMPOSE_PROJECT_NAME=' .env && sed -i "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=jitsi|" .env || echo "COMPOSE_PROJECT_NAME=jitsi" >> .env
+ sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
+ sed -i "s|^#\?PUBLIC_URL=.*|PUBLIC_URL=https://$yuming|" .env
+ grep -q '^PUBLIC_URL=' .env || echo "PUBLIC_URL=https://$yuming" >> .env
+ grep -q '^DISABLE_HTTPS=' .env && sed -i "s|^DISABLE_HTTPS=.*|DISABLE_HTTPS=1|" .env || echo "DISABLE_HTTPS=1" >> .env
+ grep -q '^ENABLE_HTTP_REDIRECT=' .env && sed -i "s|^ENABLE_HTTP_REDIRECT=.*|ENABLE_HTTP_REDIRECT=0|" .env || echo "ENABLE_HTTP_REDIRECT=0" >> .env
+ sed -i "/'\${HTTPS_PORT}:443'/d" docker-compose.yml
+ docker compose up -d
+ local jitsi_web_container=$(docker compose ps -q web)
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ [ -n "$jitsi_web_container" ] && block_container_port "$jitsi_web_container" "$ipv4_address"
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose pull
+ docker compose up -d
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose down --rmi all
+ rm -rf /home/docker/jitsi
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
- }
+ ;;
- local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
- local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 24)
- local docker_name="webtop"
- local docker_img="lscr.io/linuxserver/webtop:latest"
- local docker_port=3083
-
- docker_rum() {
-
-
- docker run -d \
- --name=webtop \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=webtop-abc \
- -e PASSWORD=webtopABC123 \
- -e LC_ALL=zh_CN.UTF-8 \
- -e DOCKER_MODS=linuxserver/mods:universal-package-install \
- -e INSTALL_PACKAGES=font-noto-cjk \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:latest
+ 82|gpt-load)
- }
+ local app_id="82"
+ local docker_name="gpt-load"
+ local docker_img="tbphp/gpt-load:latest"
+ local docker_port=8082
+ docker_rum() {
- local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: webtop-abc\""
- local docker_passwd="echo \"密码: webtopABC123\""
- local app_size="2"
- docker_app
- ;;
+ read -e -p "set up${docker_name}Login key (sk-a combination of letters and numbers starting with) such as: sk-159kejilionyyds163:" app_passwd
- 25)
- local docker_name="nextcloud"
- local docker_img="nextcloud:latest"
- local docker_port=8989
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+ mkdir -p /home/docker/gpt-load && \
+ docker run -d --name gpt-load \
+ -p ${docker_port}:3001 \
+ -e AUTH_KEY=${app_passwd} \
+ -v "/home/docker/gpt-load/data":/app/data \
+ tbphp/gpt-load:latest
- docker_rum() {
+ }
- docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+ local docker_describe="High-performance AI interface transparent proxy service"
+ local docker_url="Official website introduction: https://www.gpt-load.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
- local docker_url="官网介绍: https://nextcloud.com/"
- local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
- 26)
- local docker_name="qd"
- local docker_img="qdtoday/qd:latest"
- local docker_port=8923
- docker_rum() {
+ 83|komari)
- docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+ local app_id="83"
+ local docker_name="komari"
+ local docker_img="ghcr.io/komari-monitor/komari:latest"
+ local docker_port=8083
- }
+ docker_rum() {
- local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
- local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 27)
- local docker_name="dockge"
- local docker_img="louislam/dockge:latest"
- local docker_port=5003
+ mkdir -p /home/docker/komari && \
+ docker run -d \
+ --name komari \
+ -p ${docker_port}:25774 \
+ -v /home/docker/komari:/app/data \
+ -e ADMIN_USERNAME=admin \
+ -e ADMIN_PASSWORD=1212156 \
+ -e TZ=Asia/Shanghai \
+ --restart=always \
+ ghcr.io/komari-monitor/komari:latest
- docker_rum() {
+ }
- docker run -d --name dockge --restart unless-stopped -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+ local docker_describe="Lightweight self-hosted server monitoring tool"
+ local docker_url="Official website introduction:${gh_https_url}github.com/komari-monitor/komari/tree/main"
+ local docker_use="echo \"Default account: admin Default password: 1212156\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 28)
- local docker_name="speedtest"
- local docker_img="ghcr.io/librespeed/speedtest"
- local docker_port=8028
- docker_rum() {
+ 84|wallos)
- docker run -d -p ${docker_port}:8080 --name speedtest --restart always ghcr.io/librespeed/speedtest
+ local app_id="84"
+ local docker_name="wallos"
+ local docker_img="bellamy/wallos:latest"
+ local docker_port=8084
- }
+ docker_rum() {
- local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
- local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/wallos && \
+ docker run -d --name wallos \
+ -v /home/docker/wallos/db:/var/www/html/db \
+ -v /home/docker/wallos/logos:/var/www/html/images/uploads/logos \
+ -e TZ=UTC \
+ -p ${docker_port}:80 \
+ --restart=always \
+ bellamy/wallos:latest
- 29)
- local docker_name="searxng"
- local docker_img="searxng/searxng"
- local docker_port=8029
+ }
- docker_rum() {
+ local docker_describe="Open source personal subscription tracker for financial management"
+ local docker_url="Official website introduction:${gh_https_url}github.com/ellite/Wallos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d \
- --name searxng \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -v "/home/docker/searxng:/etc/searxng" \
- searxng/searxng
+ ;;
- }
+ 85|immich)
- local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
- local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="85"
+ local app_name="immich picture video manager"
+ local app_text="High-performance self-hosted photo and video management solution."
+ local app_url="Official website introduction:${gh_https_url}github.com/immich-app/immich"
+ local docker_name="immich_server"
+ local docker_port="8085"
+ local app_size="3"
- 30)
- local docker_name="photoprism"
- local docker_img="photoprism/photoprism:latest"
- local docker_port=2342
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
-
- docker_rum() {
-
- docker run -d \
- --name photoprism \
- --restart always \
- --security-opt seccomp=unconfined \
- --security-opt apparmor=unconfined \
- -p ${docker_port}:2342 \
- -e PHOTOPRISM_UPLOAD_NSFW="true" \
- -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
- -v /home/docker/photoprism/storage:/photoprism/storage \
- -v /home/docker/photoprism/Pictures:/photoprism/originals \
- photoprism/photoprism
+ docker_app_install() {
+ install git openssl wget
+ mkdir -p /home/docker/${docker_name} && cd /home/docker/${docker_name}
- }
+ wget -O docker-compose.yml ${gh_proxy}github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+ wget -O .env ${gh_proxy}github.com/immich-app/immich/releases/latest/download/example.env
+ sed -i "s/2283:2283/${docker_port}:2283/g" /home/docker/${docker_name}/docker-compose.yml
+ docker compose up -d
- local docker_describe="photoprism非常强大的私有相册系统"
- local docker_url="官网介绍: https://www.photoprism.app/"
- local docker_use="echo \"账号: admin 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ rm -rf /home/docker/${docker_name}
+ echo "App has been uninstalled"
+ }
+
+ docker_app_plus
+
+
+ ;;
+
+
+ 86|jellyfin)
+
+ local app_id="86"
+ local docker_name="jellyfin"
+ local docker_img="jellyfin/jellyfin"
+ local docker_port=8086
+
+ docker_rum() {
+
+ mkdir -p /home/docker/jellyfin/media
+ chmod -R 777 /home/docker/jellyfin
+
+ docker run -d \
+ --name jellyfin \
+ --user root \
+ --volume /home/docker/jellyfin/config:/config \
+ --volume /home/docker/jellyfin/cache:/cache \
+ --mount type=bind,source=/home/docker/jellyfin/media,target=/media \
+ -p ${docker_port}:8096 \
+ -p 7359:7359/udp \
+ --restart=always \
+ jellyfin/jellyfin
+
+
+ }
+
+ local docker_describe="Is an open source media server software"
+ local docker_url="Official website introduction: https://jellyfin.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 87|synctv)
+
+ local app_id="87"
+ local docker_name="synctv"
+ local docker_img="synctvorg/synctv"
+ local docker_port=8087
+
+ docker_rum() {
+
+ docker run -d \
+ --name synctv \
+ -v /home/docker/synctv:/root/.synctv \
+ -p ${docker_port}:8080 \
+ --restart=always \
+ synctvorg/synctv
+
+ }
+
+ local docker_describe="A program to watch movies and live broadcasts together remotely. It provides simultaneous viewing, live broadcast, chat and other functions"
+ local docker_url="Official website introduction:${gh_https_url}github.com/synctv-org/synctv"
+ local docker_use="echo \"Initial account and password: root. Please change the login password in time after logging in\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 31)
- local docker_name="s-pdf"
- local docker_img="frooodle/s-pdf:latest"
- local docker_port=8020
-
- docker_rum() {
-
- docker run -d \
- --name s-pdf \
- --restart=always \
- -p ${docker_port}:8080 \
- -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
- -v /home/docker/s-pdf/extraConfigs:/configs \
- -v /home/docker/s-pdf/logs:/logs \
- -e DOCKER_ENABLE_SECURITY=false \
- frooodle/s-pdf:latest
- }
- local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
- local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 88|owncast)
- 32)
- local docker_name="drawio"
- local docker_img="jgraph/drawio"
- local docker_port=7080
+ local app_id="88"
+ local docker_name="owncast"
+ local docker_img="owncast/owncast:latest"
+ local docker_port=8088
- docker_rum() {
+ docker_rum() {
- docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+ docker run -d \
+ --name owncast \
+ -p ${docker_port}:8080 \
+ -p 1935:1935 \
+ -v /home/docker/owncast/data:/app/data \
+ --restart=always \
+ owncast/owncast:latest
- }
+ }
- local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
- local docker_url="官网介绍: https://www.drawio.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="Open source, free self-built live broadcast platform"
+ local docker_url="Official website introduction: https://owncast.online"
+ local docker_use="echo \"The access address is followed by /admin to access the administrator page\""
+ local docker_passwd="echo \"Initial account: admin Initial password: abc123 Please change the login password in time after logging in\""
+ local app_size="1"
+ docker_app
- 33)
- local docker_name="sun-panel"
- local docker_img="hslr/sun-panel"
- local docker_port=3009
+ ;;
- docker_rum() {
- docker run -d --restart=always -p ${docker_port}:3002 \
- -v /home/docker/sun-panel/conf:/app/conf \
- -v /home/docker/sun-panel/uploads:/app/uploads \
- -v /home/docker/sun-panel/database:/app/database \
- --name sun-panel \
- hslr/sun-panel
- }
+ 89|file-code-box)
- local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
- local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
- local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="89"
+ local docker_name="file-code-box"
+ local docker_img="lanol/filecodebox:latest"
+ local docker_port=8089
- 34)
- local docker_name="pingvin-share"
- local docker_img="stonith404/pingvin-share"
- local docker_port=3060
-
- docker_rum() {
-
- docker run -d \
- --name pingvin-share \
- --restart always \
- -p ${docker_port}:3000 \
- -v /home/docker/pingvin-share/data:/opt/app/backend/data \
- stonith404/pingvin-share
- }
+ docker_rum() {
- local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
- local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --name file-code-box \
+ -p ${docker_port}:12345 \
+ -v /home/docker/file-code-box/data:/app/data \
+ --restart=always \
+ lanol/filecodebox:latest
+ }
- 35)
- local docker_name="moments"
- local docker_img="kingwrcy/moments:latest"
- local docker_port=8035
-
- docker_rum() {
-
- docker run -d --restart unless-stopped \
- -p ${docker_port}:3000 \
- -v /home/docker/moments/data:/app/data \
- -v /etc/localtime:/etc/localtime:ro \
- -v /etc/timezone:/etc/timezone:ro \
- --name moments \
- kingwrcy/moments:latest
- }
+ local docker_describe="Share texts and files with anonymous passwords, and pick up files like express delivery"
+ local docker_url="Official website introduction:${gh_https_url}github.com/vastsa/FileCodeBox"
+ local docker_use="echo \"The access address is followed by /#/admin to access the administrator page\""
+ local docker_passwd="echo \"Administrator password: FileCodeBox2023\""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
- local docker_url="Official website introduction:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
- local docker_use="echo \"账号: admin 密码: a123456\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 36)
- local docker_name="lobe-chat"
- local docker_img="lobehub/lobe-chat:latest"
- local docker_port=8036
+ 90|matrix)
- docker_rum() {
+ local app_id="90"
+ local docker_name="matrix"
+ local docker_img="matrixdotorg/synapse:latest"
+ local docker_port=8090
- docker run -d -p ${docker_port}:3210 \
- --name lobe-chat \
- --restart=always \
- lobehub/lobe-chat
- }
+ docker_rum() {
- local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
- local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ add_yuming
- 37)
- local docker_name="myip"
- local docker_img="jason5ng32/myip:latest"
- local docker_port=8037
+ if [ ! -d /home/docker/matrix/data ]; then
+ docker run --rm \
+ -v /home/docker/matrix/data:/data \
+ -e SYNAPSE_SERVER_NAME=${yuming} \
+ -e SYNAPSE_REPORT_STATS=yes \
+ --name matrix \
+ matrixdotorg/synapse:latest generate
+ fi
- docker_rum() {
+ docker run -d \
+ --name matrix \
+ -v /home/docker/matrix/data:/data \
+ -p ${docker_port}:8008 \
+ --restart=always \
+ matrixdotorg/synapse:latest
- docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+ echo "Create an initial user or administrator. Please set the following username and password and whether you are an administrator."
+ docker exec -it matrix register_new_matrix_user \
+ http://localhost:8008 \
+ -c /data/homeserver.yaml
- }
+ sed -i '/^enable_registration:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration: true' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^enable_registration_without_verification:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration_without_verification: true' /home/docker/matrix/data/homeserver.yaml
+ docker restart matrix
- local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
- local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 38)
- send_stats "Xiaoya Family Bucket"
- clear
- install_docker
- check_disk_space 1
- bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
- ;;
+ }
- 39)
+ local docker_describe="Matrix is a decentralized chat protocol"
+ local docker_url="Official website introduction: https://matrix.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- if [ ! -d /home/docker/bililive-go/ ]; then
- mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
- wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
- fi
+ ;;
- local docker_name="bililive-go"
- local docker_img="chigusa/bililive-go"
- local docker_port=8039
- docker_rum() {
- docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+ 91|gitea)
- }
+ local app_id="91"
- local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_name="gitea private code repository"
+ local app_text="A free new generation code hosting platform that provides an experience close to GitHub."
+ local app_url="Video introduction:${gh_https_url}github.com/go-gitea/gitea"
+ local docker_name="gitea"
+ local docker_port="8091"
+ local app_size="2"
- 40)
- local docker_name="webssh"
- local docker_img="jrohy/webssh"
- local docker_port=8040
- docker_rum() {
- docker run -d -p ${docker_port}:5032 --restart always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
- }
+ docker_app_install() {
- local docker_describe="简易在线ssh连接工具和sftp工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/gitea
+ mkdir -p /home/docker/gitea/gitea
+ mkdir -p /home/docker/gitea/data
+ mkdir -p /home/docker/gitea/postgres
+ cd /home/docker/gitea
- 41)
+ curl -o /home/docker/gitea/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/gitea-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/gitea/docker-compose.yml
+ cd /home/docker/gitea/
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="耗子面板"
- local panelurl="官方地址: ${gh_proxy}github.com/TheTNB/panel"
- panel_app_install() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
- cd ~
- }
+ docker_app_update() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ cd /home/docker/gitea/ && docker compose up -d
+ }
- panel_app_manage() {
- panel-cli
- }
- panel_app_uninstall() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
- cd ~
- }
+ docker_app_uninstall() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ rm -rf /home/docker/gitea
+ echo "App has been uninstalled"
+ }
- install_panel
+ docker_app_plus
- ;;
+ ;;
- 42)
- local docker_name="nexterm"
- local docker_img="germannewsmaker/nexterm:latest"
- local docker_port=8042
- docker_rum() {
- docker run -d \
- --name nexterm \
- -p ${docker_port}:6989 \
- -v /home/docker/nexterm:/app/data \
- --restart unless-stopped \
- germannewsmaker/nexterm:latest
+ 92|filebrowser)
- }
+ local app_id="92"
+ local docker_name="filebrowser"
+ local docker_img="hurlenko/filebrowser"
+ local docker_port=8092
- local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
- local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 43)
- local docker_name="hbbs"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker run -d \
+ --name filebrowser \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/filebrowser/data:/data \
+ -v /home/docker/filebrowser/config:/config \
+ -e FB_BASEURL=/filebrowser \
+ hurlenko/filebrowser
- docker_rum() {
+ }
- docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbs
+ local docker_describe="Is a web-based file manager"
+ local docker_url="Official website introduction: https://filebrowser.org/"
+ local docker_use="docker logs filebrowser"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
+ 93|dufs)
- local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="docker logs hbbs"
- local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
- local app_size="1"
- docker_app
- ;;
+ local app_id="93"
+ local docker_name="dufs"
+ local docker_img="sigoden/dufs"
+ local docker_port=8093
- 44)
- local docker_name="hbbr"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker_rum() {
- docker_rum() {
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}:/data \
+ -p ${docker_port}:5000 \
+ ${docker_img} /data -A
- docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbr
+ }
- }
+ local docker_describe="Minimalist static file server, supports upload and download"
+ local docker_url="Official website introduction:${gh_https_url}github.com/sigoden/dufs"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 45)
- local docker_name="registry"
- local docker_img="registry:2"
- local docker_port=8045
+ 94|gopeed)
- docker_rum() {
+ local app_id="94"
+ local docker_name="gopeed"
+ local docker_img="liwei2633/gopeed"
+ local docker_port=8094
- docker run -d \
- -p ${docker_port}:5000 \
- --name registry \
- -v /home/docker/registry:/var/lib/registry \
- -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
- --restart always \
- registry:2
+ docker_rum() {
- }
+ read -e -p "Set login username:" app_use
+ read -e -p "Set login password:" app_passwd
- local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
- local docker_url="官网介绍: https://hub.docker.com/_/registry"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}/downloads:/app/Downloads \
+ -v /home/docker/${docker_name}/storage:/app/storage \
+ -p ${docker_port}:9999 \
+ ${docker_img} -u ${app_use} -p ${app_passwd}
- 46)
- local docker_name="ghproxy"
- local docker_img="wjqserver/ghproxy:latest"
- local docker_port=8046
+ }
- docker_rum() {
+ local docker_describe="Distributed high-speed download tool, supporting multiple protocols"
+ local docker_url="Official website introduction:${gh_https_url}github.com/GopeedLab/gopeed"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d --name ghproxy --restart always -p ${docker_port}:8080 wjqserver/ghproxy:latest
+ ;;
- }
- local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
- local docker_url="官网介绍: https://github.com/WJQSERVER-STUDIO/ghproxy"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 47)
+ 95|paperless)
+ local app_id="95"
+ local app_name="paperless document management platform"
+ local app_text="An open source electronic document management system, its main purpose is to digitize and manage your paper documents."
+ local app_url="Video introduction: https://docs.paperless-ngx.com/"
+ local docker_name="paperless-webserver-1"
+ local docker_port="8095"
+ local app_size="2"
- local app_name="普罗米修斯监控"
- local app_text="Prometheus+Grafana企业级监控系统"
- local app_url="官网介绍: https://prometheus.io"
- local docker_name="grafana"
- local docker_port="8047"
- local app_size="2"
+ docker_app_install() {
- docker_app_install() {
- prometheus_install
- clear
- ip_address
- echo "Installed"
- check_docker_app_ip
- echo "The initial username and password are: admin"
- }
+ mkdir -p /home/docker/paperless
+ mkdir -p /home/docker/paperless/export
+ mkdir -p /home/docker/paperless/consume
+ cd /home/docker/paperless
- docker_app_update() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
- docker_app_install
- }
+ curl -o /home/docker/paperless/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/docker-compose.postgres-tika.yml
+ curl -o /home/docker/paperless/docker-compose.env ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/.env
- docker_app_uninstall() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/paperless/docker-compose.yml
+ cd /home/docker/paperless
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- rm -rf /home/docker/monitoring
- echo "The app has been uninstalled"
- }
- docker_app_plus
- ;;
+ docker_app_update() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ docker_app_install
+ }
- 48)
- local docker_name="node-exporter"
- local docker_img="prom/node-exporter"
- local docker_port=8048
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ rm -rf /home/docker/paperless
+ echo "App has been uninstalled"
+ }
- docker run -d \
- --name=node-exporter \
- -p ${docker_port}:9100 \
- --restart unless-stopped \
- prom/node-exporter
+ docker_app_plus
+ ;;
- }
- local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/prometheus/node_exporter"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
-
- 49)
- local docker_name="cadvisor"
- local docker_img="gcr.io/cadvisor/cadvisor:latest"
- local docker_port=8049
-
- docker_rum() {
-
- docker run -d \
- --name=cadvisor \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- --volume=/:/rootfs:ro \
- --volume=/var/run:/var/run:rw \
- --volume=/sys:/sys:ro \
- --volume=/var/lib/docker/:/var/lib/docker:ro \
- gcr.io/cadvisor/cadvisor:latest \
- -housekeeping_interval=10s \
- -docker_only=true
- }
+ 96|2fauth)
- local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/google/cadvisor"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="96"
+ local app_name="2FAuth self-hosted two-step authenticator"
+ local app_text="Self-hosted two-factor authentication (2FA) account management and verification code generation tool."
+ local app_url="Official website:${gh_https_url}github.com/Bubka/2FAuth"
+ local docker_name="2fauth"
+ local docker_port="8096"
+ local app_size="1"
- 50)
- local docker_name="changedetection"
- local docker_img="dgtlmoon/changedetection.io:latest"
- local docker_port=8050
+ docker_app_install() {
- docker_rum() {
+ add_yuming
- docker run -d --restart always -p ${docker_port}:5000 \
- -v /home/docker/datastore:/datastore \
- --name changedetection dgtlmoon/changedetection.io:latest
+ mkdir -p /home/docker/2fauth
+ mkdir -p /home/docker/2fauth/data
+ chmod -R 777 /home/docker/2fauth/
+ cd /home/docker/2fauth
- }
+ curl -o /home/docker/2fauth/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/2fauth-docker-compose.yml
- local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
- local docker_url="官网介绍: https://github.com/dgtlmoon/changedetection.io"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/2fauth/docker-compose.yml
+ sed -i "s/yuming.com/${yuming}/g" /home/docker/2fauth/docker-compose.yml
+ cd /home/docker/2fauth
+ docker compose up -d
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 51)
clear
- send_stats "PVE Chicken"
- check_disk_space 1
- curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
- ;;
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- 52)
- local docker_name="dpanel"
- local docker_img="dpanel/dpanel:lite"
- local docker_port=8052
+ docker_app_update() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ docker_app_install
+ }
- docker_rum() {
- docker run -it -d --name dpanel --restart=always \
- -p ${docker_port}:8080 -e APP_NAME=dpanel \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/dpanel:/dpanel \
- dpanel/dpanel:lite
+ docker_app_uninstall() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ rm -rf /home/docker/2fauth
+ echo "App has been uninstalled"
+ }
- }
+ docker_app_plus
- local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
- local docker_url="官网介绍: https://github.com/donknap/dpanel"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 53)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
- docker_rum() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ 97|wgs)
- }
+ local app_id="97"
+ local docker_name="wireguard"
+ local docker_img="lscr.io/linuxserver/wireguard:latest"
+ local docker_port=8097
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run llama3.2:1b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_rum() {
- 54)
+ read -e -p "Please enter the number of clients in the network (default 5):" COUNT
+ COUNT=${COUNT:-5}
+ read -e -p "Please enter the WireGuard network segment (default 10.13.13.0):" NETWORK
+ NETWORK=${NETWORK:-10.13.13.0}
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="AMH面板"
- local panelurl="官方地址: https://amh.sh/index.htm?amh"
+ PEERS=$(seq -f "wg%02g" 1 "$COUNT" | paste -sd,)
- panel_app_install() {
- cd ~
- wget https://dl.amh.sh/amh.sh && bash amh.sh
- }
+ ip link delete wg0 &>/dev/null
- panel_app_manage() {
- panel_app_install
- }
+ ip_address
+ docker run -d \
+ --name=wireguard \
+ --network host \
+ --cap-add=NET_ADMIN \
+ --cap-add=SYS_MODULE \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SERVERURL=${ipv4_address} \
+ -e SERVERPORT=51820 \
+ -e PEERS=${PEERS} \
+ -e INTERNAL_SUBNET=${NETWORK} \
+ -e ALLOWEDIPS=${NETWORK}/24 \
+ -e PERSISTENTKEEPALIVE_PEERS=all \
+ -e LOG_CONFS=true \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules \
+ --restart=always \
+ lscr.io/linuxserver/wireguard:latest
- panel_app_uninstall() {
- panel_app_install
- }
- install_panel
- ;;
+ sleep 3
+ docker exec wireguard sh -c "
+ f='/config/wg_confs/wg0.conf'
+ sed -i 's/51820/${docker_port}/g' \$f
+ "
- 55)
- frps_panel
- ;;
+ docker exec wireguard sh -c "
+ for d in /config/peer_*; do
+ sed -i 's/51820/${docker_port}/g' \$d/*.conf
+ done
+ "
- 56)
- frpc_panel
- ;;
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ sed -i "/^DNS/d" "$d"/*.conf
+ done
+ '
- 57)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ for f in "$d"/*.conf; do
+ grep -q "^PersistentKeepalive" "$f" || \
+ sed -i "/^AllowedIPs/ a PersistentKeepalive = 25" "$f"
+ done
+ done
+ '
+
+ docker exec wireguard bash -c '
+ for d in /config/peer_*; do
+ cd "$d" || continue
+ conf_file=$(ls *.conf)
+ base_name="${conf_file%.conf}"
+ qrencode -o "$base_name.png" < "$conf_file"
+ done
+ '
- docker_rum() {
+ docker restart wireguard
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ sleep 2
+ echo
+ echo -e "${gl_huang}All client QR code configurations:${gl_bai}"
+ docker exec wireguard bash -c 'for i in $(ls /config | grep peer_ | sed "s/peer_//"); do echo "--- $i ---"; /app/show-peer $i; done'
+ sleep 2
+ echo
+ echo -e "${gl_huang}All client configuration codes:${gl_bai}"
+ docker exec wireguard sh -c 'for d in /config/peer_*; do echo "# $(basename $d) "; cat $d/*.conf; echo; done'
+ sleep 2
+ echo -e "${gl_lv}${COUNT}Configure all outputs for each client. The usage method is as follows:${gl_bai}"
+ echo -e "${gl_lv}1. Download the wg APP on your mobile phone and scan the QR code above to quickly connect to the Internet.${gl_bai}"
+ echo -e "${gl_lv}2. Download the client for Windows and copy the configuration code to connect to the network.${gl_bai}"
+ echo -e "${gl_lv}3. Use a script to deploy the WG client on Linux and copy the configuration code to connect to the network.${gl_bai}"
+ echo -e "${gl_lv}Official client download method: https://www.wireguard.com/install/${gl_bai}"
+ break_end
- }
+ }
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ local docker_describe="Modern, high-performance virtual private network tools"
+ local docker_url="Official website introduction: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 58)
- local app_name="Dify知识库"
- local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
- local app_url="官方网站: https://docs.dify.ai/zh-hans"
- local docker_name="docker-nginx-1"
- local docker_port="8058"
- local app_size="3"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
- # sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+ 98|wgc)
- docker compose up -d
- clear
- echo "Installed"
- check_docker_app_ip
- }
+ local app_id="98"
+ local docker_name="wireguardc"
+ local docker_img="kjlion/wireguard:alpine"
+ local docker_port=51820
- docker_app_update() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- cd /home/docker/dify/
- git pull origin main
- sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- cd /home/docker/dify/docker/ && docker compose up -d
- }
+ docker_rum() {
- docker_app_uninstall() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- rm -rf /home/docker/dify
- echo "The app has been uninstalled"
- }
+ mkdir -p /home/docker/wireguard/config/
- docker_app_plus
+ local CONFIG_FILE="/home/docker/wireguard/config/wg0.conf"
- ;;
+ # Create directory if it does not exist
+ mkdir -p "$(dirname "$CONFIG_FILE")"
- 59)
- local app_name="New API"
- local app_text="新一代大模型网关与AI资产管理系统"
- local app_url="官方网站: https://github.com/Calcium-Ion/new-api"
- local docker_name="new-api"
- local docker_port="8059"
- local app_size="3"
+ echo "Please paste your client configuration and press Enter twice to save:"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+ # initialize variables
+ input=""
+ empty_line_count=0
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ # Read user input line by line
+ while IFS= read -r line; do
+ if [[ -z "$line" ]]; then
+ ((empty_line_count++))
+ if [[ $empty_line_count -ge 2 ]]; then
+ break
+ fi
+ else
+ empty_line_count=0
+ input+="$line"$'\n'
+ fi
+ done
+ # Write configuration file
+ echo "$input" > "$CONFIG_FILE"
- docker compose up -d
- clear
- echo "Installed"
- check_docker_app_ip
- }
+ echo "Client configuration saved to$CONFIG_FILE"
- docker_app_update() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- cd /home/docker/new-api/
- git pull origin main
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ ip link delete wg0 &>/dev/null
- docker compose up -d
- clear
- echo "Installed"
- check_docker_app_ip
+ docker run -d \
+ --name wireguardc \
+ --network host \
+ --cap-add NET_ADMIN \
+ --cap-add SYS_MODULE \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules:ro \
+ --restart=always \
+ kjlion/wireguard:alpine
- }
+ sleep 3
- docker_app_uninstall() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- rm -rf /home/docker/new-api
- echo "The app has been uninstalled"
- }
+ docker logs wireguardc
- docker_app_plus
+ break_end
- ;;
+ }
+ local docker_describe="Modern, high-performance virtual private network tools"
+ local docker_url="Official website introduction: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 60)
+ ;;
- local app_name="JumpServer开源堡垒机"
- local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
- local app_url="官方介绍: https://github.com/jumpserver/jumpserver"
- local docker_name="jms_web"
- local docker_port="80"
- local app_size="2"
- docker_app_install() {
- curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
- clear
- echo "Installed"
- check_docker_app_ip
- echo "Initial username: admin"
- echo "Initial password: ChangeMe"
- }
+ 99|dsm)
+ local app_id="99"
- docker_app_update() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh upgrade
- echo "The app has been updated"
- }
+ local app_name="dsm synology virtual machine"
+ local app_text="Virtual DSM in Docker container"
+ local app_url="Official website:${gh_https_url}github.com/vdsm/virtual-dsm"
+ local docker_name="dsm"
+ local docker_port="8099"
+ local app_size="16"
+ docker_app_install() {
- docker_app_uninstall() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh uninstall
- cd /opt
- rm -rf jumpserver-installer*/
- rm -rf jumpserver
- echo "The app has been uninstalled"
- }
+ read -e -p "Set the number of CPU cores (default 2):" CPU_CORES
+ local CPU_CORES=${CPU_CORES:-2}
- docker_app_plus
- ;;
+ read -e -p "Set memory size (default 4G):" RAM_SIZE
+ local RAM_SIZE=${RAM_SIZE:-4}
- 61)
- local docker_name="libretranslate"
- local docker_img="libretranslate/libretranslate:latest"
- local docker_port=8061
+ mkdir -p /home/docker/dsm
+ mkdir -p /home/docker/dsm/dev
+ chmod -R 777 /home/docker/dsm/
+ cd /home/docker/dsm
- docker_rum() {
+ curl -o /home/docker/dsm/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/dsm-docker-compose.yml
- docker run -d \
- -p ${docker_port}:5000 \
- --name libretranslate \
- libretranslate/libretranslate \
- --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+ sed -i "s/5000:5000/${docker_port}:5000/g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|CPU_CORES: "2"|CPU_CORES: "${CPU_CORES}"|g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|RAM_SIZE: "2G"|RAM_SIZE: "${RAM_SIZE}G"|g" /home/docker/dsm/docker-compose.yml
+ cd /home/docker/dsm
+ docker compose up -d
- }
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
- local docker_url="官网介绍: https://github.com/LibreTranslate/LibreTranslate"
- local docker_use=""
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_app_update() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ docker_app_install
+ }
- 62)
- local app_name="RAGFlow知识库"
- local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
- local app_url="官方网站: https://github.com/infiniflow/ragflow"
- local docker_name="ragflow-server"
- local docker_port="8062"
- local app_size="8"
+ docker_app_uninstall() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ rm -rf /home/docker/dsm
+ echo "App has been uninstalled"
+ }
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- clear
- echo "Installed"
- check_docker_app_ip
- }
+ docker_app_plus
- docker_app_update() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- cd /home/docker/ragflow/
- git pull origin main
- cd /home/docker/ragflow/docker/
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- }
+ ;;
- docker_app_uninstall() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- rm -rf /home/docker/ragflow
- echo "The app has been uninstalled"
- }
- docker_app_plus
- ;;
+ 100|syncthing)
+ local app_id="100"
+ local docker_name="syncthing"
+ local docker_img="syncthing/syncthing:latest"
+ local docker_port=8100
- 63)
- local docker_name="open-webui"
- local docker_img="ghcr.io/open-webui/open-webui:main"
- local docker_port=8063
+ docker_rum() {
+ docker run -d \
+ --name=syncthing \
+ --hostname=my-syncthing \
+ --restart=always \
+ -p ${docker_port}:8384 \
+ -p 22000:22000/tcp \
+ -p 22000:22000/udp \
+ -p 21027:21027/udp \
+ -v /home/docker/syncthing:/var/syncthing \
+ syncthing/syncthing:latest
+ }
- docker_rum() {
+ local docker_describe="An open source peer-to-peer file synchronization tool, similar to Dropbox and Resilio Sync, but completely decentralized."
+ local docker_url="Official website introduction:${gh_https_url}github.com/syncthing/syncthing"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+ ;;
- }
- local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use=""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ 101|moneyprinterturbo)
+ local app_id="101"
+ local app_name="AI video generation tool"
+ local app_text="MoneyPrinterTurbo is a tool that uses AI large models to synthesize high-definition short videos"
+ local app_url="Official website:${gh_https_url}github.com/harry0703/MoneyPrinterTurbo"
+ local docker_name="moneyprinterturbo"
+ local docker_port="8101"
+ local app_size="3"
- 64)
- local docker_name="it-tools"
- local docker_img="corentinth/it-tools:latest"
- local docker_port=8064
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git && cd MoneyPrinterTurbo/
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
- docker_rum() {
- docker run -d --name it-tools --restart unless-stopped -p ${docker_port}:80 corentinth/it-tools:latest
- }
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
- local docker_url="官网介绍: https://github.com/CorentinTh/it-tools"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_update() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ cd /home/docker/MoneyPrinterTurbo/
+ git pull ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose up -d
+ }
- 65)
- local docker_name="n8n"
- local docker_img="docker.n8n.io/n8nio/n8n"
- local docker_port=8065
+ docker_app_uninstall() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ rm -rf /home/docker/MoneyPrinterTurbo
+ echo "App has been uninstalled"
+ }
- docker_rum() {
+ docker_app_plus
- add_yuming
- mkdir -p /home/docker/n8n
- chmod -R 777 /home/docker/n8n
-
- docker run -d --name n8n \
- --restart always \
- -p ${docker_port}:5678 \
- -v /home/docker/n8n:/home/node/.n8n \
- -e N8N_HOST=${yuming} \
- -e N8N_PORT=5678 \
- -e N8N_PROTOCOL=https \
- -e N8N_WEBHOOK_URL=https://${yuming}/ \
- docker.n8n.io/n8nio/n8n
+ ;;
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
- }
- local docker_describe="是一款功能强大的自动化工作流平台"
- local docker_url="官网介绍: https://github.com/n8n-io/n8n"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 102|vocechat)
- 66)
- yt_menu_pro
- ;;
+ local app_id="102"
+ local docker_name="vocechat-server"
+ local docker_img="privoce/vocechat-server:latest"
+ local docker_port=8102
+ docker_rum() {
- 67)
- local docker_name="ddns-go"
- local docker_img="jeessy/ddns-go"
- local docker_port=8067
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ --name vocechat-server \
+ -v /home/docker/vocechat/data:/home/vocechat-server/data \
+ privoce/vocechat-server:latest
- docker_rum() {
- docker run -d \
- --name ddns-go \
- --restart=always \
- -p ${docker_port}:9876 \
- -v /home/docker/ddns-go:/root \
- jeessy/ddns-go
+ }
- }
+ local docker_describe="It is a personal cloud social media chat service that supports independent deployment."
+ local docker_url="Official website introduction:${gh_https_url}github.com/Privoce/vocechat-web"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
- local docker_url="官网介绍: https://github.com/jeessy2/ddns-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 68)
- local docker_name="allinssl"
- local docker_img="allinssl/allinssl:latest"
- local docker_port=8068
- docker_rum() {
- docker run -itd --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
- }
+ 103|umami)
+ local app_id="103"
+ local app_name="Umami website statistics tool"
+ local app_text="Open source, lightweight, privacy-friendly website analysis tool, similar to Google Analytics."
+ local app_url="Official website:${gh_https_url}github.com/umami-software/umami"
+ local docker_name="umami-umami-1"
+ local docker_port="8103"
+ local app_size="1"
- local docker_describe="开源免费的 SSL 证书自动化管理平台"
- local docker_url="官网介绍: https://allinssl.com"
- local docker_use="echo \"安全入口: /allinssl\""
- local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
- local app_size="1"
- docker_app
- ;;
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/umami-software/umami.git && cd umami
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/umami/docker-compose.yml
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ echo "Initial username: admin"
+ echo "Initial password: umami"
+ }
- 69)
- local docker_name="sftpgo"
- local docker_img="drakkan/sftpgo:latest"
- local docker_port=8069
+ docker_app_update() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ cd /home/docker/umami/
+ git pull ${gh_proxy}github.com/umami-software/umami.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/umami/docker-compose.yml
+ cd /home/docker/umami/ && docker compose up -d
+ }
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ rm -rf /home/docker/umami
+ echo "App has been uninstalled"
+ }
- mkdir -p /home/docker/sftpgo/data
- mkdir -p /home/docker/sftpgo/config
- chown -R 1000:1000 /home/docker/sftpgo
+ docker_app_plus
- docker run -d \
- --name sftpgo \
- --restart=always \
- -p ${docker_port}:8080 \
- -p 22022:2022 \
- --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
- --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
- drakkan/sftpgo:latest
+ ;;
- }
+ 104|nginx-stream)
+ stream_panel
+ ;;
- local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
- local docker_url="官网介绍: https://sftpgo.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 105|siyuan)
- 70)
- local docker_name="astrbot"
- local docker_img="soulter/astrbot:latest"
- local docker_port=8070
+ local app_id="105"
+ local docker_name="siyuan"
+ local docker_img="b3log/siyuan"
+ local docker_port=8105
- docker_rum() {
+ docker_rum() {
- mkdir -p /home/docker/astrbot/data
+ read -e -p "Set login password:" app_passwd
- sudo docker run -d \
- -p ${docker_port}:6185 \
- -p 6195:6195 \
- -p 6196:6196 \
- -p 6199:6199 \
- -p 11451:11451 \
- -v /home/docker/astrbot/data:/AstrBot/data \
- --restart unless-stopped \
- --name astrbot \
- soulter/astrbot:latest
+ docker run -d \
+ --name siyuan \
+ --restart=always \
+ -v /home/docker/siyuan/workspace:/siyuan/workspace \
+ -p ${docker_port}:6806 \
+ -e PUID=1001 \
+ -e PGID=1002 \
+ b3log/siyuan \
+ --workspace=/siyuan/workspace/ \
+ --accessAuthCode="${app_passwd}"
- }
+ }
- local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
- local docker_url="官网介绍: https://astrbot.app/"
- local docker_use="echo \"用户名: astrbot 密码: astrbot\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="Siyuan Notes is a privacy-first knowledge management system"
+ local docker_url="Official website introduction:${gh_https_url}github.com/siyuan-note/siyuan"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 71)
- local docker_name="navidrome"
- local docker_img="deluan/navidrome:latest"
- local docker_port=8071
- docker_rum() {
+ 106|drawnix)
- docker run -d \
- --name navidrome \
- --restart=unless-stopped \
- --user $(id -u):$(id -g) \
- -v /home/docker/navidrome/music:/music \
- -v /home/docker/navidrome/data:/data \
- -p ${docker_port}:4533 \
- -e ND_LOGLEVEL=info \
- deluan/navidrome:latest
+ local app_id="106"
+ local docker_name="drawnix"
+ local docker_img="pubuzhixing/drawnix"
+ local docker_port=8106
- }
+ docker_rum() {
- local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
- local docker_url="官网介绍: https://www.navidrome.org/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --restart=always \
+ --name drawnix \
+ -p ${docker_port}:80 \
+ pubuzhixing/drawnix
+ }
- 72)
+ local docker_describe="It is a powerful open source whiteboard tool that integrates mind maps, flow charts, etc."
+ local docker_url="Official website introduction:${gh_https_url}github.com/plait-board/drawnix"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="bitwarden"
- local docker_img="vaultwarden/server"
- local docker_port=8072
+ ;;
- docker_rum() {
- docker run -d \
- --name bitwarden \
- --restart always \
- -p ${docker_port}:80 \
- -v /home/docker/bitwarden/data:/data \
- vaultwarden/server
+ 107|pansou)
- }
+ local app_id="107"
+ local docker_name="pansou"
+ local docker_img="ghcr.io/fish2018/pansou-web"
+ local docker_port=8107
- local docker_describe="一个你可以控制数据的密码管理器"
- local docker_url="官网介绍: https://bitwarden.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
+ docker run -d \
+ --name pansou \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/pansou/data:/app/data \
+ -v /home/docker/pansou/logs:/app/logs \
+ -e ENABLED_PLUGINS="hunhepan,jikepan,panwiki,pansearch,panta,qupansou,
+susu,thepiratebay,wanou,xuexizhinan,panyq,zhizhen,labi,muou,ouge,shandian,
+duoduo,huban,cyg,erxiao,miaoso,fox4k,pianku,clmao,wuji,cldi,xiaozhang,
+libvio,leijing,xb6v,xys,ddys,hdmoli,yuhuage,u3c3,javdb,clxiong,jutoushe,
+sdso,xiaoji,xdyh,haisou,bixin,djgou,nyaa,xinjuc,aikanzy,qupanshe,xdpan,
+discourse,yunsou,ahhhhfs,nsgame,gying" \
+ ghcr.io/fish2018/pansou-web
- ;;
+ }
+ local docker_describe="PanSou is a high-performance network disk resource search API service."
+ local docker_url="Official website introduction:${gh_https_url}github.com/fish2018/pansou"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 73)
- local docker_name="libretv"
- local docker_img="bestzwei/libretv:latest"
- local docker_port=8073
- docker_rum() {
- read -e -p "Set the LibreTV login password:" app_passwd
+ 108|langbot)
+ local app_id="108"
+ local app_name="LangBot chatbot"
+ local app_text="It is an open source large language model native instant messaging robot development platform."
+ local app_url="Official website:${gh_https_url}github.com/langbot-app/LangBot"
+ local docker_name="langbot_plugin_runtime"
+ local docker_port="8108"
+ local app_size="1"
- docker run -d \
- --name libretv \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -e PASSWORD=${app_passwd} \
- bestzwei/libretv:latest
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langbot-app/LangBot && cd LangBot/docker
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
- }
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/LibreSpark/LibreTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_update() {
+ cd /home/docker/LangBot/docker && docker compose down --rmi all
+ cd /home/docker/LangBot/
+ git pull ${gh_proxy}github.com/langbot-app/LangBot main > /dev/null 2>&1
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ cd /home/docker/LangBot/docker/ && docker compose up -d
+ }
- ;;
+ docker_app_uninstall() {
+ cd /home/docker/LangBot/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/LangBot
+ echo "App has been uninstalled"
+ }
+ docker_app_plus
- 74)
+ ;;
- local docker_name="moontv"
- local docker_img="ghcr.io/senshinya/moontv:latest"
- local docker_port=8074
- docker_rum() {
+ 109|zfile)
- read -e -p "Set the MoonTV login password:" app_passwd
+ local app_id="109"
+ local docker_name="zfile"
+ local docker_img="zhaojun1998/zfile:latest"
+ local docker_port=8109
- docker run -d \
- --name moontv \
- --restart unless-stopped \
- -p ${docker_port}:3000 \
- -e PASSWORD=${app_passwd} \
- ghcr.io/senshinya/moontv:latest
+ docker_rum() {
- }
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/senshinya/MoonTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d --name=zfile --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/zfile/db:/root/.zfile-v4/db \
+ -v /home/docker/zfile/logs:/root/.zfile-v4/logs \
+ -v /home/docker/zfile/file:/data/file \
+ -v /home/docker/zfile/application.properties:/root/.zfile-v4/application.properties \
+ zhaojun1998/zfile:latest
- ;;
+ }
- 75)
+ local docker_describe="It is an online network disk program suitable for individuals or small teams."
+ local docker_url="Official website introduction:${gh_https_url}github.com/zfile-dev/zfile"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="melody"
- local docker_img="foamzou/melody:latest"
- local docker_port=8075
+ ;;
- docker_rum() {
- docker run -d \
- --name melody \
- --restart unless-stopped \
- -p ${docker_port}:5566 \
- -v /home/docker/melody/.profile:/app/backend/.profile \
- foamzou/melody:latest
+ 110|karakeep)
+ local app_id="110"
+ local app_name="karakeep bookmark management"
+ local app_text="is a self-hosted bookmarking app with artificial intelligence capabilities designed for data hoarders."
+ local app_url="Official website:${gh_https_url}github.com/karakeep-app/karakeep"
+ local docker_name="docker-web-1"
+ local docker_port="8110"
+ local app_size="1"
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/karakeep-app/karakeep.git && cd karakeep/docker && cp .env.sample .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
- }
+ docker compose up -d
+ clear
+ echo "Installation completed"
+ check_docker_app_ip
+ }
- local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
- local docker_url="官网介绍: https://github.com/foamzou/melody"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_update() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ cd /home/docker/karakeep/
+ git pull ${gh_proxy}github.com/karakeep-app/karakeep.git main > /dev/null 2>&1
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ cd /home/docker/karakeep/docker/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/karakeep
+ echo "App has been uninstalled"
+ }
- ;;
+ docker_app_plus
+ ;;
- 76)
- local docker_name="dosgame"
- local docker_img="oldiy/dosgame-web-docker:latest"
- local docker_port=8076
- docker_rum() {
- docker run -d \
- --name dosgame \
- --restart unless-stopped \
- -p ${docker_port}:262 \
- oldiy/dosgame-web-docker:latest
+ 111|convertx)
- }
+ local app_id="111"
+ local docker_name="convertx"
+ local docker_img="ghcr.io/c4illin/convertx:latest"
+ local docker_port=8111
- local docker_describe="是一个中文DOS游戏合集网站"
- local docker_url="官网介绍: https://github.com/rwv/chinese-dos-games"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ docker_rum() {
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/convertx:/app/data \
+ ${docker_img}
- ;;
+ }
- 77)
+ local docker_describe="It is a powerful multi-format file conversion tool (supports documents, images, audio and video, etc.) It is strongly recommended to add domain name access"
+ local docker_url="Project address:${gh_https_url}github.com/c4illin/ConvertX"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- local docker_name="xunlei"
- local docker_img="cnk3x/xunlei"
- local docker_port=8077
+ ;;
- docker_rum() {
- read -e -p "set up${docker_name}Login username:" app_use
- read -e -p "set up${docker_name}Login password:" app_passwd
+ 112|lucky)
- docker run -d \
- --name xunlei \
- --restart unless-stopped \
- --privileged \
- -e XL_DASHBOARD_USERNAME=${app_use} \
- -e XL_DASHBOARD_PASSWORD=${app_passwd} \
- -v /home/docker/xunlei/data:/xunlei/data \
- -v /home/docker/xunlei/downloads:/xunlei/downloads \
- -p ${docker_port}:2345 \
- cnk3x/xunlei
+ local app_id="112"
+ local docker_name="lucky"
+ local docker_img="gdy666/lucky:v2"
+ # Since Lucky uses the host network mode, the port here is only for record/explanation reference and is actually controlled by the application itself (default 16601)
+ local docker_port=8112
- }
+ docker_rum() {
- local docker_describe="迅雷你的离线高速BT磁力下载工具"
- local docker_url="官网介绍: https://github.com/cnk3x/xunlei"
- local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d --name=${docker_name} --restart=always \
+ --network host \
+ -v /home/docker/lucky/conf:/app/conf \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ ${docker_img}
- ;;
+ echo "Waiting for Lucky to initialize..."
+ sleep 10
+ docker exec lucky /app/lucky -rSetHttpAdminPort ${docker_port}
+
+ }
+ local docker_describe="Lucky is a large intranet penetration and port forwarding management tool that supports DDNS, reverse proxy, WOL and other functions."
+ local docker_url="Project address:${gh_https_url}github.com/gdy666/lucky"
+ local docker_use="echo \"Default account password: 666\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 78)
- local app_name="PandaWiki"
- local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
- local app_url="官方介绍: https://github.com/chaitin/PandaWiki"
- local docker_name="panda-wiki-nginx"
- local docker_port="2443"
- local app_size="2"
+ 113|firefox)
- docker_app_install() {
- bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
- }
+ local app_id="113"
+ local docker_name="firefox"
+ local docker_img="jlesage/firefox:latest"
+ local docker_port=8113
- docker_app_update() {
- docker_app_install
- }
+ docker_rum() {
+ read -e -p "Set login password:" admin_password
- docker_app_uninstall() {
- docker_app_install
- }
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:5800 \
+ -v /home/docker/firefox:/config:rw \
+ -e ENABLE_CJK_FONT=1 \
+ -e WEB_AUDIO=1 \
+ -e VNC_PASSWORD="${admin_password}" \
+ ${docker_img}
+ }
- docker_app_plus
- ;;
+ local docker_describe="It is a Firefox browser running in Docker that supports direct access to the desktop browser interface through the web page."
+ local docker_url="Project address:${gh_https_url}github.com/jlesage/docker-firefox"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+ 114|Moltbot|ClawdBot|moltbot|clawdbot|openclaw|OpenClaw)
+ moltbot_menu
+ ;;
- 79)
+ 115|hermes)
+ bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/hermes_manager.sh)
+ ;;
- local docker_name="beszel"
- local docker_img="henrygd/beszel"
- local docker_port=8079
+ b)
+ clear
+ send_stats "All application backup"
- docker_rum() {
+ local backup_filename="app_$(date +"%Y%m%d%H%M%S").tar.gz"
+ echo -e "${gl_kjlan}Backing up$backup_filename ...${gl_bai}"
+ cd / && tar czvf "$backup_filename" home
- mkdir -p /home/docker/beszel && \
- docker run -d \
- --name beszel \
- --restart=unless-stopped \
- -v /home/docker/beszel:/beszel_data \
- -p ${docker_port}:8090 \
- henrygd/beszel
+ while true; do
+ clear
+ echo "Backup file created: /$backup_filename"
+ read -e -p "Do you want to transfer backup data to a remote server? (Y/N):" choice
+ case "$choice" in
+ [Yy])
+ kj_ssh_read_host_port "Please enter the remote server IP:" "Target server SSH port [default 22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
+ local latest_tar=$(ls -t /app*.tar.gz | head -1)
+ if [ -n "$latest_tar" ]; then
+ ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
+ sleep 2 # 添加等待时间
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/"
+ echo "File transferred to remote server/root directory."
+ else
+ echo "The file to be transferred was not found."
+ fi
+ break
+ ;;
+ *)
+ echo "Note: The current backup only includes docker projects, and does not include data backup of website building panels such as Pagoda and 1panel."
+ break
+ ;;
+ esac
+ done
- }
+ ;;
- local docker_describe="Beszel轻量易用的服务器监控"
- local docker_url="官网介绍: https://beszel.dev/zh/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ r)
+ root_use
+ send_stats "Restore all apps"
+ echo "Available application backups"
+ echo "-------------------------"
+ ls -lt /app*.gz | awk '{print $NF}'
+ echo ""
+ read -e -p "Press the Enter key to restore the latest backup, enter the backup file name to restore the specified backup, enter 0 to exit:" filename
- ;;
+ if [ "$filename" == "0" ]; then
+ break_end
+ linux_panel
+ fi
+
+ # If the user does not enter a file name, the latest compressed package is used
+ if [ -z "$filename" ]; then
+ local filename=$(ls -t /app*.tar.gz | head -1)
+ fi
+
+ if [ -n "$filename" ]; then
+ echo -e "${gl_kjlan}Unzipping$filename ...${gl_bai}"
+ cd / && tar -xzf "$filename"
+ echo "The application data has been restored. Currently, please manually enter the specified application menu and update the application to restore the application."
+ else
+ echo "No compressed package found."
+ fi
+ ;;
- 0)
- kejilion
- ;;
- *)
- echo "Invalid input!"
- ;;
- esac
- break_end
+ 0)
+ kejilion
+ ;;
+ *)
+ cd ~
+ install git
+ if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+ else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+ fi
+ local custom_app="$HOME/apps/${sub_choice}.conf"
+ if [ -f "$custom_app" ]; then
+ . "$custom_app"
+ else
+ echo -e "${gl_hong}Error: Not found with number${sub_choice}application configuration${gl_bai}"
+ fi
+ ;;
+ esac
+ break_end
+ sub_choice=""
- done
+done
}
+
linux_work() {
while true; do
clear
send_stats "Backend workspace"
echo -e "Backend workspace"
- echo -e "The system will provide you with a workspace that can be run on the backend, which you can use to perform long-term tasks."
- echo -e "Even if you disconnect SSH, tasks in the workspace will not be interrupted, and tasks in the background will be resident."
- echo -e "${gl_huang}hint:${gl_bai}After entering the workspace, use Ctrl+b and press d alone to exit the workspace!"
+ echo -e "The system will provide you with a workspace that can run permanently in the background, which you can use to perform long-term tasks."
+ echo -e "Even if you disconnect SSH, the tasks in the workspace will not be interrupted, and the tasks will remain in the background."
+ echo -e "${gl_huang}hint:${gl_bai}After entering the workspace, use Ctrl+b and then press d alone to exit the workspace!"
echo -e "${gl_kjlan}------------------------"
echo "List of currently existing workspaces"
echo -e "${gl_kjlan}------------------------"
tmux list-sessions
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Workspace No. 1"
- echo -e "${gl_kjlan}2. ${gl_bai}Workspace No. 2"
- echo -e "${gl_kjlan}3. ${gl_bai}Workspace No. 3"
- echo -e "${gl_kjlan}4. ${gl_bai}Workspace No. 4"
+ echo -e "${gl_kjlan}1. ${gl_bai}Work Area 1"
+ echo -e "${gl_kjlan}2. ${gl_bai}Work Area 2"
+ echo -e "${gl_kjlan}3. ${gl_bai}Work Area 3"
+ echo -e "${gl_kjlan}4. ${gl_bai}Work Area 4"
echo -e "${gl_kjlan}5. ${gl_bai}Workspace No. 5"
- echo -e "${gl_kjlan}6. ${gl_bai}Workspace No. 6"
- echo -e "${gl_kjlan}7. ${gl_bai}Workspace No. 7"
- echo -e "${gl_kjlan}8. ${gl_bai}Workspace No. 8"
+ echo -e "${gl_kjlan}6. ${gl_bai}Work Area 6"
+ echo -e "${gl_kjlan}7. ${gl_bai}Work Area 7"
+ echo -e "${gl_kjlan}8. ${gl_bai}Work Area 8"
echo -e "${gl_kjlan}9. ${gl_bai}Workspace No. 9"
- echo -e "${gl_kjlan}10. ${gl_bai}Workspace No. 10"
+ echo -e "${gl_kjlan}10. ${gl_bai}Workspace 10"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}SSH resident mode${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}22. ${gl_bai}Create/enter the workspace"
+ echo -e "${gl_kjlan}22. ${gl_bai}Create/enter workspace"
echo -e "${gl_kjlan}23. ${gl_bai}Inject commands into the background workspace"
- echo -e "${gl_kjlan}24. ${gl_bai}Delete the specified workspace"
+ echo -e "${gl_kjlan}24. ${gl_bai}Delete specified workspace"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
@@ -10676,7 +19493,7 @@ linux_work() {
clear
install tmux
local SESSION_NAME="work1"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
@@ -10684,63 +19501,63 @@ linux_work() {
clear
install tmux
local SESSION_NAME="work2"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
3)
clear
install tmux
local SESSION_NAME="work3"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
4)
clear
install tmux
local SESSION_NAME="work4"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
5)
clear
install tmux
local SESSION_NAME="work5"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
6)
clear
install tmux
local SESSION_NAME="work6"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
7)
clear
install tmux
local SESSION_NAME="work7"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
8)
clear
install tmux
local SESSION_NAME="work8"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
9)
clear
install tmux
local SESSION_NAME="work9"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
10)
clear
install tmux
local SESSION_NAME="work10"
- send_stats "Start the workspace$SESSION_NAME"
+ send_stats "Start workspace$SESSION_NAME"
tmux_run
;;
@@ -10748,30 +19565,30 @@ linux_work() {
while true; do
clear
if grep -q 'tmux attach-session -t sshd || tmux new-session -s sshd' ~/.bashrc; then
- local tmux_sshd_status="${gl_lv}开启${gl_bai}"
+ local tmux_sshd_status="${gl_lv}turn on${gl_bai}"
else
- local tmux_sshd_status="${gl_hui}关闭${gl_bai}"
+ local tmux_sshd_status="${gl_hui}closure${gl_bai}"
fi
send_stats "SSH resident mode"
echo -e "SSH resident mode${tmux_sshd_status}"
- echo "After SSH connection is enabled, it will directly enter the resident mode and return to the previous working state."
+ echo "After opening the SSH connection, it will directly enter the resident mode and return directly to the previous working state."
echo "------------------------"
- echo "1. Turn on 2. Turn off"
+ echo "1. On 2. Off"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" gongzuoqu_del
+ read -e -p "Please enter your choice:" gongzuoqu_del
case "$gongzuoqu_del" in
1)
install tmux
local SESSION_NAME="sshd"
- send_stats "Start the workspace$SESSION_NAME"
- grep -q "tmux attach-session -t sshd" ~/.bashrc || echo -e "\n# Automatically enter the tmux session\nif [[ -z \"\$TMUX\" ]]; then\n tmux attach-session -t sshd || tmux new-session -s sshd\nfi" >> ~/.bashrc
+ send_stats "Start workspace$SESSION_NAME"
+ grep -q "tmux attach-session -t sshd" ~/.bashrc || echo -e "\n# Automatically enter tmux session\nif [[ -z \"\$TMUX\" ]]; then\n tmux attach-session -t sshd || tmux new-session -s sshd\nfi" >> ~/.bashrc
source ~/.bashrc
tmux_run
;;
2)
- sed -i '/# 自动进入 tmux 会话/,+4d' ~/.bashrc
+ sed -i '/# Automatically enter tmux session/,+4d' ~/.bashrc
tmux kill-window -t sshd
;;
*)
@@ -10781,37 +19598,527 @@ linux_work() {
done
;;
- 22)
- read -e -p "Please enter the name of the workspace you created or entered, such as 1001 kj001 work1:" SESSION_NAME
- tmux_run
- send_stats "Custom workspace"
- ;;
+ 22)
+ read -e -p "Please enter the name of the workspace you created or entered, such as 1001 kj001 work1:" SESSION_NAME
+ tmux_run
+ send_stats "Custom workspace"
+ ;;
+
+
+ 23)
+ read -e -p "Please enter the command you want to execute in the background, such as: curl -fsSL https://get.docker.com | sh:" tmuxd
+ tmux_run_d
+ send_stats "Inject commands into the background workspace"
+ ;;
+
+ 24)
+ read -e -p "Please enter the name of the workspace you want to delete:" gongzuoqu_name
+ tmux kill-window -t $gongzuoqu_name
+ send_stats "Delete workspace"
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ echo "Invalid input!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+# Intelligent switching mirror source function
+switch_mirror() {
+ # Optional parameter, default is false
+ local upgrade_software=${1:-false}
+ local clean_cache=${2:-false}
+
+ # Get user country
+ local country
+ country=$(curl -s ipinfo.io/country)
+
+ echo "Countries detected:$country"
+
+ if [ "$country" = "CN" ]; then
+ echo "Use domestic mirror sources..."
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.huaweicloud.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ echo "Use overseas mirror sources..."
+ if [ -f /etc/os-release ] && grep -qi "oracle" /etc/os-release; then
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.xtom.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --use-official-source true \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ fi
+ fi
+}
+
+
+fail2ban_panel() {
+ root_use
+ send_stats "ssh defense"
+ while true; do
+
+ check_f2b_status
+ echo -e "SSH defense program$check_f2b_status"
+ echo "fail2ban is an SSH tool to prevent brute force cracking"
+ echo "Official website introduction:${gh_proxy}github.com/fail2ban/fail2ban"
+ echo "------------------------"
+ echo "1. Install a defense program"
+ echo "------------------------"
+ echo "2. View SSH interception records"
+ echo "3. Real-time log monitoring"
+ echo "------------------------"
+ echo "4. Basic parameter configuration (ban duration/time window/number of retries)"
+ echo "5. Edit configuration file (nano)"
+ echo "------------------------"
+ echo "9. Uninstall the defense program"
+ echo "------------------------"
+ echo "0. Return to the previous menu"
+ echo "------------------------"
+ read -e -p "Please enter your choice:" sub_choice
+ case $sub_choice in
+ 1)
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ break_end
+ ;;
+ 2)
+ echo "------------------------"
+ f2b_sshd
+ echo "------------------------"
+ break_end
+ ;;
+ 3)
+ tail -f /var/log/fail2ban.log
+ break
+ ;;
+ 4)
+ send_stats "SSH defense basic parameter configuration"
+ f2b_basic_config
+ break_end
+ ;;
+ 5)
+ send_stats "SSH Defense Edit Configuration File"
+ f2b_edit_config
+ break_end
+ ;;
+ 9)
+ remove fail2ban
+ rm -rf /etc/fail2ban
+ echo "Fail2Ban defense program has been uninstalled"
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+}
+
+
+
+
+
+net_menu() {
+
+ send_stats "Network card management tool"
+ show_nics() {
+ echo "================ Current network card information ================"
+ printf "%-18s %-12s %-20s %-26s\n" "Network card name" "state" "IP address" "MAC address"
+ echo "------------------------------------------------"
+ for nic in $(ls /sys/class/net); do
+ state=$(cat /sys/class/net/$nic/operstate 2>/dev/null)
+ ipaddr=$(ip -4 addr show $nic | awk '/inet /{print $2}' | head -n1)
+ mac=$(cat /sys/class/net/$nic/address 2>/dev/null)
+ printf "%-15s %-10s %-18s %-20s\n" "$nic" "$state" "${ipaddr:-无}" "$mac"
+ done
+ echo "================================================"
+ }
+
+ while true; do
+ clear
+ show_nics
+ echo
+ echo "=========== Network card management menu ==========="
+ echo "1. Enable network card"
+ echo "2. Disable network card"
+ echo "3. View network card details"
+ echo "4. Refresh network card information"
+ echo "0. Return to the previous menu"
+ echo "===================================="
+ read -erp "Please select an action:" choice
+
+ case $choice in
+ 1)
+ send_stats "Enable network card"
+ read -erp "Please enter the name of the network card to be enabled:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" up && echo "✔ Network card$nicEnabled"
+ else
+ echo "✘ The network card does not exist"
+ fi
+ read -erp "Press Enter to continue..."
+ ;;
+ 2)
+ send_stats "Disable network card"
+ read -erp "Please enter the name of the network card to be disabled:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" down && echo "✔ Network card$nicDisabled"
+ else
+ echo "✘ The network card does not exist"
+ fi
+ read -erp "Press Enter to continue..."
+ ;;
+ 3)
+ send_stats "View network card details"
+ read -erp "Please enter the name of the network card you want to view:" nic
+ if ip link show "$nic" &>/dev/null; then
+ echo "========== $nicDetails =========="
+ ip addr show "$nic"
+ ethtool "$nic" 2>/dev/null | head -n 10
+ else
+ echo "✘ The network card does not exist"
+ fi
+ read -erp "Press Enter to continue..."
+ ;;
+ 4)
+ send_stats "Refresh network card information"
+ continue
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+log_menu() {
+ send_stats "System log management tool"
+
+ show_log_overview() {
+ echo "============= System log overview ============="
+ echo "Hostname: $(hostname)"
+ echo "System time: $(date)"
+ echo
+ echo "[/var/log directory occupation]"
+ du -sh /var/log 2>/dev/null
+ echo
+ echo "[journal log occupation]"
+ journalctl --disk-usage 2>/dev/null
+ echo "========================================"
+ }
+
+ while true; do
+ clear
+ show_log_overview
+ echo
+ echo "=========== System log management menu ==========="
+ echo "1. View the latest system log (journal)"
+ echo "2. View the specified service log"
+ echo "3. View login/security logs"
+ echo "4. Real-time tracking logs"
+ echo "5. Clean up old journal logs"
+ echo "0. Return to the previous menu"
+ echo "======================================="
+ read -erp "Please select an action:" choice
+
+ case $choice in
+ 1)
+ send_stats "View recent logs"
+ read -erp "View the most recent log lines? [Default 100]:" lines
+ lines=${lines:-100}
+ journalctl -n "$lines" --no-pager
+ read -erp "Press Enter to continue..."
+ ;;
+ 2)
+ send_stats "View specified service logs"
+ read -erp "Please enter the service name (such as sshd, nginx):" svc
+ if systemctl list-unit-files | grep -q "^$svc"; then
+ journalctl -u "$svc" -n 100 --no-pager
+ else
+ echo "✘ The service does not exist or has no logs"
+ fi
+ read -erp "Press Enter to continue..."
+ ;;
+ 3)
+ send_stats "View login/security logs"
+ echo "====== Recent login log ======"
+ last -n 10
+ echo
+ echo "====== Authentication Log ======"
+ if [ -f /var/log/secure ]; then
+ tail -n 20 /var/log/secure
+ elif [ -f /var/log/auth.log ]; then
+ tail -n 20 /var/log/auth.log
+ else
+ echo "Security log file not found"
+ fi
+ read -erp "Press Enter to continue..."
+ ;;
+ 4)
+ send_stats "Real-time tracking log"
+ echo "1) System log"
+ echo "2) Specify service log"
+ read -erp "Select tracking type:" t
+ if [ "$t" = "1" ]; then
+ journalctl -f
+ elif [ "$t" = "2" ]; then
+ read -erp "Enter service name:" svc
+ journalctl -u "$svc" -f
+ else
+ echo "Invalid selection"
+ fi
+ ;;
+ 5)
+ send_stats "Clean up old journal logs"
+ echo "⚠️ Clean the journal (safe way)"
+ echo "1) Keep the last 7 days"
+ echo "2) Keep the last 3 days"
+ echo "3) Limit the maximum log size to 500M"
+ read -erp "Please select a cleaning method:" c
+ case $c in
+ 1) journalctl --vacuum-time=7d ;;
+ 2) journalctl --vacuum-time=3d ;;
+ 3) journalctl --vacuum-size=500M ;;
+ *) echo "Invalid option" ;;
+ esac
+ echo "✔ Journal log cleaning completed"
+ sleep 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+env_menu() {
+
+ BASHRC="$HOME/.bashrc"
+ PROFILE="$HOME/.profile"
+
+ send_stats "System variable management tool"
+
+ show_env_vars() {
+ clear
+ send_stats "Currently in effect environment variables"
+ echo "========== Currently in effect environment variables (excerpt) =========="
+ printf "%-20s %s\n" "variable name" "value"
+ echo "-----------------------------------------------"
+ for v in USER HOME SHELL LANG PWD; do
+ printf "%-20s %s\n" "$v" "${!v}"
+ done
+
+ echo
+ echo "PATH:"
+ echo "$PATH" | tr ':' '\n' | nl -ba
+
+ echo
+ echo "========== Variables defined in the configuration file (parsing) =========="
+
+ parse_file_vars() {
+ local file="$1"
+ [ -f "$file" ] || return
+
+ echo
+ echo ">>> Source file:$file"
+ echo "-----------------------------------------------"
+
+ # Extract export VAR=xxx or VAR=xxx
+ grep -Ev '^\s*#|^\s*$' "$file" \
+ | grep -E '^(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' \
+ | while read -r line; do
+ var=$(echo "$line" | sed -E 's/^(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*).*/\2/')
+ val=$(echo "$line" | sed -E 's/^[^=]+=//')
+ printf "%-20s %s\n" "$var" "$val"
+ done
+ }
+
+ parse_file_vars "$HOME/.bashrc"
+ parse_file_vars "$HOME/.profile"
+
+ echo
+ echo "==============================================="
+ read -erp "Press Enter to continue..."
+ }
+
+
+ view_file() {
+ local file="$1"
+ send_stats "View variable file$file"
+ clear
+ if [ -f "$file" ]; then
+ echo "========== View files:$file =========="
+ cat -n "$file"
+ echo "===================================="
+ else
+ echo "File does not exist:$file"
+ fi
+ read -erp "Press Enter to continue..."
+ }
+
+ edit_file() {
+ local file="$1"
+ send_stats "Edit variable file$file"
+ install nano
+ nano "$file"
+ }
+
+ source_files() {
+ echo "Reloading environment variables..."
+ send_stats "Reloading environment variables"
+ source "$BASHRC"
+ source "$PROFILE"
+ echo "✔ Environment variables have been reloaded"
+ read -erp "Press Enter to continue..."
+ }
+
+ while true; do
+ clear
+ echo "=========== System environment variable management =========="
+ echo "Current user:$USER"
+ echo "--------------------------------------"
+ echo "1. Check the current commonly used environment variables"
+ echo "2. View ~/.bashrc"
+ echo "3. View ~/.profile"
+ echo "4. Edit ~/.bashrc"
+ echo "5. Edit ~/.profile"
+ echo "6. Reload environment variables (source)"
+ echo "--------------------------------------"
+ echo "0. Return to the previous menu"
+ echo "--------------------------------------"
+ read -erp "Please select an action:" choice
+
+ case "$choice" in
+ 1)
+ show_env_vars
+ ;;
+ 2)
+ view_file "$BASHRC"
+ ;;
+ 3)
+ view_file "$PROFILE"
+ ;;
+ 4)
+ edit_file "$BASHRC"
+ ;;
+ 5)
+ edit_file "$PROFILE"
+ ;;
+ 6)
+ source_files
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "Invalid option"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
+create_user_with_sshkey() {
+ local new_username="$1"
+ local is_sudo="${2:-false}"
+ local sshkey_vl
- 23)
- read -e -p "Please enter the command you want to execute in the background, such as: curl -fsSL https://get.docker.com | sh:" tmuxd
- tmux_run_d
- send_stats "Inject commands into the background workspace"
- ;;
+ if [[ -z "$new_username" ]]; then
+ echo "Usage: create_user_with_sshkey "
+ return 1
+ fi
- 24)
- read -e -p "Please enter the name of the workspace you want to delete:" gongzuoqu_name
- tmux kill-window -t $gongzuoqu_name
- send_stats "Delete the workspace"
- ;;
+ # Create user
+ useradd -m -s /bin/bash "$new_username" || return 1
- 0)
- kejilion
- ;;
- *)
- echo "Invalid input!"
- ;;
- esac
- break_end
+ echo "Example of importing public key:"
+ echo " - URL: ${gh_https_url}github.com/torvalds.keys"
+ echo "- Paste directly: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+ read -e -p "Please import${new_username}public key:" sshkey_vl
- done
+ case "$sshkey_vl" in
+ http://*|https://*)
+ send_stats "Import SSH public key from URL"
+ fetch_remote_ssh_keys "$sshkey_vl" "/home/$new_username"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa*)
+ send_stats "Directly import the public key"
+ import_sshkey "$sshkey_vl" "/home/$new_username"
+ ;;
+ *)
+ echo "Error: unknown parameter '$sshkey_vl'"
+ return 1
+ ;;
+ esac
+
+
+ # Fix permissions
+ chown -R "$new_username:$new_username" "/home/$new_username/.ssh"
+
+ install sudo
+
+ # sudo password-free
+ if [[ "$is_sudo" == "true" ]]; then
+ cat >"/etc/sudoers.d/$new_username" <> /etc/ssh/sshd_config
+ passwd -l "$new_username" &>/dev/null
+ restart_ssh
+ echo "user$new_usernameCreation completed"
}
@@ -10825,57 +20132,66 @@ linux_work() {
+
+
+
linux_Settings() {
while true; do
clear
# send_stats "System Tools"
- echo -e "System Tools"
+ echo -e "system tools"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Set script startup shortcut keys${gl_kjlan}2. ${gl_bai}Modify the login password"
- echo -e "${gl_kjlan}3. ${gl_bai}ROOT password login mode${gl_kjlan}4. ${gl_bai}Install the specified version of Python"
- echo -e "${gl_kjlan}5. ${gl_bai}Open all ports${gl_kjlan}6. ${gl_bai}Modify the SSH connection port"
- echo -e "${gl_kjlan}7. ${gl_bai}Optimize DNS address${gl_kjlan}8. ${gl_bai}One-click reinstallation system${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}9. ${gl_bai}Disable ROOT account to create a new account${gl_kjlan}10. ${gl_bai}Switch priority ipv4/ipv6"
+ echo -e "${gl_kjlan}1. ${gl_bai}Set script startup shortcut key${gl_kjlan}2. ${gl_bai}Change login password"
+ echo -e "${gl_kjlan}3. ${gl_bai}User password login mode${gl_kjlan}4. ${gl_bai}Install the specified version of Python"
+ echo -e "${gl_kjlan}5. ${gl_bai}Open all ports${gl_kjlan}6. ${gl_bai}Modify SSH connection port"
+ echo -e "${gl_kjlan}7. ${gl_bai}Optimize DNS address${gl_kjlan}8. ${gl_bai}Reinstall the system with one click${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}9. ${gl_bai}Disable ROOT account and create new account${gl_kjlan}10. ${gl_bai}Switch priority ipv4/ipv6"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}11. ${gl_bai}Check port occupation status${gl_kjlan}12. ${gl_bai}Modify virtual memory size"
- echo -e "${gl_kjlan}13. ${gl_bai}User Management${gl_kjlan}14. ${gl_bai}User/Password Generator"
+ echo -e "${gl_kjlan}13. ${gl_bai}User management${gl_kjlan}14. ${gl_bai}User/password generator"
echo -e "${gl_kjlan}15. ${gl_bai}System time zone adjustment${gl_kjlan}16. ${gl_bai}Set up BBR3 acceleration"
- echo -e "${gl_kjlan}17. ${gl_bai}Firewall Advanced Manager${gl_kjlan}18. ${gl_bai}Modify the host name"
- echo -e "${gl_kjlan}19. ${gl_bai}Switch system update source${gl_kjlan}20. ${gl_bai}Timing task management"
+ echo -e "${gl_kjlan}17. ${gl_bai}Firewall Advanced Manager${gl_kjlan}18. ${gl_bai}Modify hostname"
+ echo -e "${gl_kjlan}19. ${gl_bai}Switch system update source${gl_kjlan}20. ${gl_bai}Scheduled task management"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}Native host parsing${gl_kjlan}22. ${gl_bai}SSH Defense Program"
- echo -e "${gl_kjlan}23. ${gl_bai}Automatic shutdown of current limit${gl_kjlan}24. ${gl_bai}ROOT private key login mode"
- echo -e "${gl_kjlan}25. ${gl_bai}TG-bot system monitoring and early warning${gl_kjlan}26. ${gl_bai}Fix OpenSSH high-risk vulnerabilities (Xiuyuan)"
- echo -e "${gl_kjlan}27. ${gl_bai}Red Hat Linux kernel upgrade${gl_kjlan}28. ${gl_bai}Optimization of kernel parameters in Linux system${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}29. ${gl_bai}Virus scanning tool${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}File Manager"
+ echo -e "${gl_kjlan}21. ${gl_bai}Native host resolution${gl_kjlan}22. ${gl_bai}SSH defense program"
+ echo -e "${gl_kjlan}23. ${gl_bai}Current limiting automatic shutdown${gl_kjlan}24. ${gl_bai}User key login mode"
+ echo -e "${gl_kjlan}25. ${gl_bai}TG-bot system monitoring and early warning${gl_kjlan}26. ${gl_bai}Fix OpenSSH high-risk vulnerabilities"
+ echo -e "${gl_kjlan}27. ${gl_bai}Red Hat Linux kernel upgrade${gl_kjlan}28. ${gl_bai}Linux system kernel parameter optimization${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}29. ${gl_bai}Virus scanning tools${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}file manager"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}31. ${gl_bai}Switch system language${gl_kjlan}32. ${gl_bai}Command line beautification tool${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}Set up a system recycling bin${gl_kjlan}34. ${gl_bai}System backup and recovery"
+ echo -e "${gl_kjlan}33. ${gl_bai}Set up system recycle bin${gl_kjlan}34. ${gl_bai}System backup and recovery"
echo -e "${gl_kjlan}35. ${gl_bai}ssh remote connection tool${gl_kjlan}36. ${gl_bai}Hard disk partition management tool"
echo -e "${gl_kjlan}37. ${gl_bai}Command line history${gl_kjlan}38. ${gl_bai}rsync remote synchronization tool"
+ echo -e "${gl_kjlan}39. ${gl_bai}Command Favorites${gl_huang}★${gl_bai} ${gl_kjlan}40. ${gl_bai}Network card management tool"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}Message board${gl_kjlan}66. ${gl_bai}One-stop system optimization${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}99. ${gl_bai}Restart the server${gl_kjlan}100. ${gl_bai}Privacy and Security"
+ echo -e "${gl_kjlan}41. ${gl_bai}System log management tool${gl_huang}★${gl_bai} ${gl_kjlan}42. ${gl_bai}System variable management tool"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}61. ${gl_bai}message board${gl_kjlan}66. ${gl_bai}One-stop system tuning${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}99. ${gl_bai}Restart the server${gl_kjlan}100. ${gl_bai}Privacy and security"
echo -e "${gl_kjlan}101. ${gl_bai}Advanced usage of k command${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}Uninstall tech lion script"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
while true; do
clear
- read -e -p "Please enter your shortcut key (enter 0 to exit):" kuaijiejian
+ read -e -p "Please enter your shortcut keys (enter 0 to exit):" kuaijiejian
if [ "$kuaijiejian" == "0" ]; then
break_end
linux_Settings
fi
find /usr/local/bin/ -type l -exec bash -c 'test "$(readlink -f {})" = "/usr/local/bin/k" && rm -f {}' \;
- ln -s /usr/local/bin/k /usr/local/bin/$kuaijiejian
- echo "Shortcut keys are set"
- send_stats "Script shortcut keys have been set"
+ if [ "$kuaijiejian" != "k" ]; then
+ ln -sf /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ fi
+ ln -sf /usr/local/bin/k /usr/bin/$kuaijiejian > /dev/null 2>&1
+ echo "Shortcut keys have been set"
+ send_stats "Script shortcut key has been set"
break_end
linux_Settings
done
@@ -10888,29 +20204,28 @@ linux_Settings() {
passwd
;;
3)
- root_use
- send_stats "root password mode"
+ clear
add_sshpasswd
;;
4)
root_use
send_stats "py version management"
- echo "Python version management"
+ echo "python version management"
echo "Video introduction: https://www.bilibili.com/video/BV1Pm42157cK?t=0.1"
echo "---------------------------------------"
- echo "This feature seamlessly installs any version officially supported by python!"
+ echo "This function can seamlessly install any version officially supported by python!"
local VERSION=$(python3 -V 2>&1 | awk '{print $2}')
echo -e "Current python version number:${gl_huang}$VERSION${gl_bai}"
echo "------------"
- echo "Recommended version: 3.12 3.11 3.10 3.9 3.8 2.7"
- echo "Query more versions: https://www.python.org/downloads/"
+ echo "Recommended versions: 3.12 3.11 3.10 3.9 3.8 2.7"
+ echo "Check more versions: https://www.python.org/downloads/"
echo "------------"
read -e -p "Enter the python version number you want to install (enter 0 to exit):" py_new_v
if [[ "$py_new_v" == "0" ]]; then
- send_stats "Script PY Management"
+ send_stats "Script PY management"
break_end
linux_Settings
fi
@@ -10973,13 +20288,13 @@ EOF
local VERSION=$(python -V 2>&1 | awk '{print $2}')
echo -e "Current python version number:${gl_huang}$VERSION${gl_bai}"
- send_stats "Switch script PY version"
+ send_stats "Script PY version switching"
;;
5)
root_use
- send_stats "Open port"
+ send_stats "open port"
iptables_open
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
echo "All ports are open"
@@ -10991,36 +20306,36 @@ EOF
while true; do
clear
- sed -i 's/#Port/Port/' /etc/ssh/sshd_config
+ sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
# Read the current SSH port number
local current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
- # Print the current SSH port number
+ # Print current SSH port number
echo -e "The current SSH port number is:${gl_huang}$current_port ${gl_bai}"
echo "------------------------"
- echo "Numbers with port numbers ranging from 1 to 65535. (Enter 0 to exit)"
+ echo "The port number ranges from 1 to 65535. (Enter 0 to exit)"
- # Prompt the user to enter a new SSH port number
+ # Prompt user for new SSH port number
read -e -p "Please enter the new SSH port number:" new_port
# Determine whether the port number is within the valid range
if [[ $new_port =~ ^[0-9]+$ ]]; then # 检查输入是否为数字
if [[ $new_port -ge 1 && $new_port -le 65535 ]]; then
send_stats "SSH port has been modified"
- new_ssh_port
+ new_ssh_port $new_port
elif [[ $new_port -eq 0 ]]; then
send_stats "Exit SSH port modification"
break
else
- echo "The port number is invalid, please enter a number between 1 and 65535."
- send_stats "Invalid SSH port input"
+ echo "The port number is invalid. Please enter a number between 1 and 65535."
+ send_stats "Invalid SSH port entered"
break_end
fi
else
- echo "The input is invalid, please enter the number."
- send_stats "Invalid SSH port input"
+ echo "Invalid input, please enter a number."
+ send_stats "Invalid SSH port entered"
break_end
fi
done
@@ -11039,21 +20354,20 @@ EOF
;;
9)
root_use
- send_stats "New users disable root"
- read -e -p "Please enter the new username (enter 0 to exit):" new_username
+ send_stats "Disable root for new users"
+ read -e -p "Please enter a new username (enter 0 to exit):" new_username
if [ "$new_username" == "0" ]; then
break_end
linux_Settings
fi
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
+ create_user_with_sshkey $new_username true
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- passwd -l root
+ ssh-keygen -l -f /home/$new_username/.ssh/authorized_keys &>/dev/null && {
+ passwd -l root &>/dev/null
+ sed -i 's/^[[:space:]]*#\?[[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
+ }
- echo "The operation has been completed."
;;
@@ -11064,29 +20378,28 @@ EOF
clear
echo "Set v4/v6 priority"
echo "------------------------"
- local ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
- if [ "$ipv6_disabled" -eq 1 ]; then
+
+ if grep -Eq '^\s*precedence\s+::ffff:0:0/96\s+100\s*$' /etc/gai.conf 2>/dev/null; then
echo -e "Current network priority settings:${gl_huang}IPv4${gl_bai}priority"
else
echo -e "Current network priority settings:${gl_huang}IPv6${gl_bai}priority"
fi
+
echo ""
echo "------------------------"
- echo "1. IPv4 priority 2. IPv6 priority 3. IPv6 repair tool"
+ echo "1. IPv4 first 2. IPv6 first 3. IPv6 repair tool"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Choose a preferred network:" choice
+ read -e -p "Choose your preferred network:" choice
case $choice in
1)
- sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
- echo "Switched to IPv4 priority"
- send_stats "Switched to IPv4 priority"
+ prefer_ipv4
;;
2)
- sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
+ rm -f /etc/gai.conf
echo "Switched to IPv6 priority"
send_stats "Switched to IPv6 priority"
;;
@@ -11094,8 +20407,8 @@ EOF
3)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "This function is provided by the master jhb, thanks to him!"
- send_stats "ipv6 fix"
+ echo "This function is provided by jhb, thank him!"
+ send_stats "ipv6 repair"
;;
*)
@@ -11123,11 +20436,11 @@ EOF
echo -e "Current virtual memory:${gl_huang}$swap_info${gl_bai}"
echo "------------------------"
- echo "1. Assign 1024M 2. Assign 2048M 3. Assign 4096M 4. Custom size"
+ echo "1. Allocate 1024M 2. Allocate 2048M 3. Allocate 4096M 4. Custom size"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case "$choice" in
1)
@@ -11141,7 +20454,7 @@ EOF
;;
3)
- send_stats "4G virtual memory has been set"
+ send_stats "4G virtual memory has been set up"
add_swap 4096
;;
@@ -11149,7 +20462,7 @@ EOF
4)
read -e -p "Please enter the virtual memory size (unit M):" new_swap
add_swap "$new_swap"
- send_stats "Custom virtual memory has been set"
+ send_stats "Custom virtual memory set"
;;
*)
@@ -11162,70 +20475,67 @@ EOF
13)
while true; do
root_use
- send_stats "User Management"
- echo "User List"
+ send_stats "User management"
+ echo "User list"
echo "----------------------------------------------------------------------------"
- printf "%-24s %-34s %-20s %-10s\n" "用户名" "用户权限" "用户组" "sudo权限"
+ printf "%-24s %-34s %-20s %-10s\n" "username" "User permissions" "User group" "sudo permissions"
while IFS=: read -r username _ userid groupid _ _ homedir shell; do
local groups=$(groups "$username" | cut -d : -f 2)
- local sudo_status=$(sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL : ALL)' && echo "Yes" || echo "No")
+ local sudo_status
+ if sudo -n -lU "$username" 2>/dev/null | grep -q "(ALL) \(NOPASSWD: \)\?ALL"; then
+ sudo_status="Yes"
+ else
+ sudo_status="No"
+ fi
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$groups" "$sudo_status"
done < /etc/passwd
echo ""
- echo "Account operation"
+ echo "Account operations"
echo "------------------------"
- echo "1. Create a normal account 2. Create a premium account"
+ echo "1. Create a normal user 2. Create an advanced user"
echo "------------------------"
- echo "3. Give the highest permissions 4. Cancel the highest permissions"
+ echo "3. Grant the highest authority 4. Remove the highest authority"
echo "------------------------"
- echo "5. Delete the account"
+ echo "5. Delete account"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- # Prompt the user to enter a new username
+ # Prompt user for new username
read -e -p "Please enter a new username:" new_username
+ create_user_with_sshkey $new_username false
- # Create a new user and set a password
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "The operation has been completed."
;;
2)
- # Prompt the user to enter a new username
+ # Prompt user for new username
read -e -p "Please enter a new username:" new_username
-
- # Create a new user and set a password
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- # Grant new users sudo permissions
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- echo "The operation has been completed."
+ create_user_with_sshkey $new_username true
;;
3)
- read -e -p "Please enter your username:" username
- # Grant new users sudo permissions
- echo "$username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ read -e -p "Please enter username:" username
+ install sudo
+ cat >"/etc/sudoers.d/$username" </dev/null | xargs rm -f
+ fi
+ sed -i "/^$username\s*ALL=(ALL)/d" /etc/sudoers
;;
5)
- read -e -p "Please enter the username to delete:" username
- # Delete the user and its home directory
+ read -e -p "Please enter the username you want to delete:" username
userdel -r "$username"
;;
@@ -11233,21 +20543,22 @@ EOF
break # 跳出循环,退出菜单
;;
esac
+
done
;;
14)
clear
- send_stats "User Information Generator"
- echo "Random username"
+ send_stats "User information generator"
+ echo "random username"
echo "------------------------"
for i in {1..5}; do
username="user$(< /dev/urandom tr -dc _a-z0-9 | head -c6)"
- echo "Random username$i: $username"
+ echo "random username$i: $username"
done
echo ""
- echo "Random name"
+ echo "random name"
echo "------------------------"
local first_names=("John" "Jane" "Michael" "Emily" "David" "Sophia" "William" "Olivia" "James" "Emma" "Ava" "Liam" "Mia" "Noah" "Isabella")
local last_names=("Smith" "Johnson" "Brown" "Davis" "Wilson" "Miller" "Jones" "Garcia" "Martinez" "Williams" "Lee" "Gonzalez" "Rodriguez" "Hernandez")
@@ -11269,11 +20580,11 @@ EOF
done
echo ""
- echo "16-bit random password"
+ echo "16-digit random password"
echo "------------------------"
for i in {1..5}; do
local password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
- echo "Random password$i: $password"
+ echo "random password$i: $password"
done
echo ""
@@ -11281,7 +20592,7 @@ EOF
echo "------------------------"
for i in {1..5}; do
local password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32)
- echo "Random password$i: $password"
+ echo "random password$i: $password"
done
echo ""
@@ -11305,30 +20616,30 @@ EOF
echo "Current system time:$current_time"
echo ""
- echo "Time zone switching"
+ echo "time zone switch"
echo "------------------------"
echo "Asia"
- echo "1. Shanghai time in China 2. Hong Kong time in China"
- echo "3. Tokyo time in Japan 4. Seoul time in South Korea"
- echo "5. Singapore time 6. Kolkata time in India"
- echo "7. Dubai time in the UAE 8. Sydney time in Australia"
- echo "9. Time in Bangkok, Thailand"
+ echo "1. Shanghai, China time 2. Hong Kong time, China"
+ echo "3. Tokyo, Japan time 4. Seoul, South Korea time"
+ echo "5. Singapore time 6. Kolkata, India time"
+ echo "7. Dubai, United Arab Emirates time 8. Sydney, Australia time"
+ echo "9. Bangkok, Thailand time"
echo "------------------------"
echo "Europe"
- echo "11. London time in the UK 12. Paris time in France"
- echo "13. Berlin time, Germany 14. Moscow time, Russia"
- echo "15. Utrecht time in the Netherlands 16. Madrid time in Spain"
+ echo "11. London, UK time 12. Paris, France time"
+ echo "13. Berlin, Germany time 14. Moscow, Russia time"
+ echo "15. Utracht Time, Netherlands 16. Madrid Time, Spain"
echo "------------------------"
echo "America"
- echo "21. Western Time 22. Eastern Time"
- echo "23. Canadian time 24. Mexican time"
+ echo "21. US Western Time 22. US Eastern Time"
+ echo "23. Canada time 24. Mexico time"
echo "25. Brazil time 26. Argentina time"
echo "------------------------"
- echo "31. UTC Global Standard Time"
+ echo "31. UTC global standard time"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
@@ -11371,21 +20682,21 @@ EOF
18)
root_use
- send_stats "Modify the host name"
+ send_stats "Modify hostname"
while true; do
clear
local current_hostname=$(uname -n)
- echo -e "Current host name:${gl_huang}$current_hostname${gl_bai}"
+ echo -e "Current hostname:${gl_huang}$current_hostname${gl_bai}"
echo "------------------------"
- read -e -p "Please enter the new host name (enter 0 to exit):" new_hostname
+ read -e -p "Please enter a new hostname (enter 0 to exit):" new_hostname
if [ -n "$new_hostname" ] && [ "$new_hostname" != "0" ]; then
if [ -f /etc/alpine-release ]; then
# Alpine
echo "$new_hostname" > /etc/hostname
hostname "$new_hostname"
else
- # Other systems, such as Debian, Ubuntu, CentOS, etc.
+ # Other systems such as Debian, Ubuntu, CentOS, etc.
hostnamectl set-hostname "$new_hostname"
sed -i "s/$current_hostname/$new_hostname/g" /etc/hostname
systemctl restart systemd-hostnamed
@@ -11403,11 +20714,11 @@ EOF
echo "::1 $new_hostname localhost localhost.localdomain ipv6-localhost ipv6-loopback" >> /etc/hosts
fi
- echo "The host name has been changed to:$new_hostname"
- send_stats "Host name has been changed"
+ echo "The hostname has been changed to:$new_hostname"
+ send_stats "Hostname changed"
sleep 1
else
- echo "Exited, hostname not changed."
+ echo "Exited without changing hostname."
break
fi
done
@@ -11415,30 +20726,35 @@ EOF
19)
root_use
- send_stats "Change the system update source"
+ send_stats "Change system update source"
clear
- echo "Select the update source area"
- echo "Connect to LinuxMirrors to switch system update source"
+ echo "Select update source region"
+ echo "Access LinuxMirrors to switch system update sources"
echo "------------------------"
- echo "1. Mainland China [Default] 2. Mainland China [Education Network] 3. Overseas Regions"
+ echo "1. Mainland China [Default] 2. Mainland China [Education Network] 3. Overseas regions 4. Intelligent switching of update sources"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Enter your choice:" choice
+ read -e -p "Enter your selection:" choice
case $choice in
1)
- send_stats "Default source in mainland China"
+ send_stats "Mainland China default source"
bash <(curl -sSL https://linuxmirrors.cn/main.sh)
;;
2)
- send_stats "Source of education in mainland China"
+ send_stats "Mainland China Education Source"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --edu
;;
3)
- send_stats "Overseas origin"
+ send_stats "Overseas sources"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --abroad
;;
+ 4)
+ send_stats "Intelligent switching of update sources"
+ switch_mirror false false
+ ;;
+
*)
echo "Canceled"
;;
@@ -11448,62 +20764,62 @@ EOF
;;
20)
- send_stats "Timing task management"
+ send_stats "Scheduled task management"
while true; do
clear
check_crontab_installed
clear
- echo "Timed task list"
+ echo "Scheduled task list"
crontab -l
echo ""
echo "operate"
echo "------------------------"
- echo "1. Add timing tasks 2. Delete timing tasks 3. Edit timing tasks"
+ echo "1. Add a scheduled task 2. Delete a scheduled task 3. Edit a scheduled task"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- read -e -p "Please enter the execution command for the new task:" newquest
+ read -e -p "Please enter the execution command of the new task:" newquest
echo "------------------------"
- echo "1. Monthly Tasks 2. Weekly Tasks"
+ echo "1. Monthly tasks 2. Weekly tasks"
echo "3. Daily tasks 4. Hourly tasks"
echo "------------------------"
- read -e -p "Please enter your selection:" dingshi
+ read -e -p "Please enter your choice:" dingshi
case $dingshi in
1)
- read -e -p "Choose what day of each month to perform tasks? (1-30):" day
+ read -e -p "On what day of the month do you choose to execute the task? (1-30):" day
(crontab -l ; echo "0 0 $day * * $newquest") | crontab - > /dev/null 2>&1
;;
2)
- read -e -p "Choose what week to perform the task? (0-6, 0 represents Sunday):" weekday
+ read -e -p "Choose a day of the week to perform the task? (0-6, 0 represents Sunday):" weekday
(crontab -l ; echo "0 0 * * $weekday $newquest") | crontab - > /dev/null 2>&1
;;
3)
- read -e -p "Choose what time to perform tasks every day? (Hours, 0-23):" hour
+ read -e -p "What time do you choose to perform the task every day? (hours, 0-23):" hour
(crontab -l ; echo "0 $hour * * * $newquest") | crontab - > /dev/null 2>&1
;;
4)
- read -e -p "Enter what minute of the hour to perform the task? (mins, 0-60):" minute
+ read -e -p "Enter what time of the hour the task should be executed? (minutes, 0-60):" minute
(crontab -l ; echo "$minute * * * * $newquest") | crontab - > /dev/null 2>&1
;;
*)
break # 跳出
;;
esac
- send_stats "Add timed tasks"
+ send_stats "Add a scheduled task"
;;
2)
- read -e -p "Please enter the keywords that need to be deleted:" kquest
+ read -e -p "Please enter the keyword of the task to be deleted:" kquest
crontab -l | grep -v "$kquest" | crontab -
- send_stats "Delete timing tasks"
+ send_stats "Delete scheduled tasks"
;;
3)
crontab -e
- send_stats "Edit timing tasks"
+ send_stats "Edit scheduled tasks"
;;
*)
break # 跳出循环,退出菜单
@@ -11515,32 +20831,32 @@ EOF
21)
root_use
- send_stats "Local host parsing"
+ send_stats "Local host resolution"
while true; do
clear
- echo "Native host parsing list"
- echo "If you add parse matches here, dynamic parsing will no longer be used"
+ echo "Native host resolution list"
+ echo "If you add parsing matching here, dynamic parsing will no longer be used"
cat /etc/hosts
echo ""
echo "operate"
echo "------------------------"
- echo "1. Add a new parsing 2. Delete the parsing address"
+ echo "1. Add new resolution 2. Delete resolution address"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" host_dns
+ read -e -p "Please enter your choice:" host_dns
case $host_dns in
1)
- read -e -p "Please enter a new parsing record Format: 110.25.5.33 kejilion.pro:" addhost
+ read -e -p "Please enter a new parsing record format: 110.25.5.33 kejilion.pro:" addhost
echo "$addhost" >> /etc/hosts
- send_stats "Local host parsing has been added"
+ send_stats "Local host resolution is added"
;;
2)
- read -e -p "Please enter the keywords for parsing content that need to be deleted:" delhost
+ read -e -p "Please enter the keywords of the parsed content that need to be deleted:" delhost
sed -i "/$delhost/d" /etc/hosts
- send_stats "Local host parsing and deletion"
+ send_stats "Local host resolution and deletion"
;;
*)
break # 跳出循环,退出菜单
@@ -11550,109 +20866,53 @@ EOF
;;
22)
- root_use
- send_stats "ssh defense"
- while true; do
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
- clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "SSH Defense Program$check_docker"
- echo "fail2ban is an SSH tool to prevent brute force"
- echo "Official website introduction:${gh_proxy}github.com/fail2ban/fail2ban"
- echo "------------------------"
- echo "1. Install the defense program"
- echo "------------------------"
- echo "2. View SSH interception records"
- echo "3. Real-time log monitoring"
- echo "------------------------"
- echo "9. Uninstall the defense program"
- echo "------------------------"
- echo "0. Return to the previous menu"
- echo "------------------------"
- read -e -p "Please enter your selection:" sub_choice
- case $sub_choice in
- 1)
- install_docker
- f2b_install_sshd
-
- cd ~
- f2b_status
- break_end
- ;;
- 2)
- echo "------------------------"
- f2b_sshd
- echo "------------------------"
- break_end
- ;;
- 3)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
- break
- ;;
- 9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
- echo "Fail2Ban defense program has been uninstalled"
- ;;
- *)
- break
- ;;
- esac
- fi
- done
+ fail2ban_panel
;;
23)
root_use
- send_stats "Current limit shutdown function"
+ send_stats "Current limiting shutdown function"
while true; do
clear
- echo "Current limit shutdown function"
+ echo "Current limiting shutdown function"
echo "Video introduction: https://www.bilibili.com/video/BV1mC411j7Qd?t=0.1"
echo "------------------------------------------------"
- echo "Current traffic usage, restarting the server traffic calculation will be cleared!"
+ echo "The current traffic usage will be cleared when the server is restarted!"
output_status
- echo -e "${gl_kjlan}Total Receive:${gl_bai}$rx"
- echo -e "${gl_kjlan}Total send:${gl_bai}$tx"
+ echo -e "${gl_kjlan}Total received:${gl_bai}$rx"
+ echo -e "${gl_kjlan}Total sent:${gl_bai}$tx"
- # Check if the Limiting_Shut_down.sh file exists
+ # Check if Limiting_Shut_down.sh file exists
if [ -f ~/Limiting_Shut_down.sh ]; then
# Get the value of threshold_gb
local rx_threshold_gb=$(grep -oP 'rx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
local tx_threshold_gb=$(grep -oP 'tx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
- echo -e "${gl_lv}The current set entry-station current limit threshold is:${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
- echo -e "${gl_lv}The current outbound current limit threshold is:${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
+ echo -e "${gl_lv}The currently set inbound traffic limit threshold is:${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
+ echo -e "${gl_lv}The currently set outbound traffic limiting threshold is:${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
else
- echo -e "${gl_hui}Current limit shutdown function is not enabled${gl_bai}"
+ echo -e "${gl_hui}The current limiting shutdown function is not currently enabled${gl_bai}"
fi
echo
echo "------------------------------------------------"
- echo "The system will detect whether the actual traffic reaches the threshold every minute, and the server will be automatically shut down after it arrives!"
+ echo "The system will detect whether the actual traffic reaches the threshold every minute, and will automatically shut down the server after reaching the threshold!"
echo "------------------------"
- echo "1. Turn on the current limit shutdown function 2. Deactivate the current limit shutdown function"
+ echo "1. Enable the current limiting shutdown function 2. Disable the current limiting shutdown function"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" Limiting
+ read -e -p "Please enter your choice:" Limiting
case "$Limiting" in
1)
- # Enter the new virtual memory size
- echo "If the actual server has 100G traffic, the threshold can be set to 95G and shut down the power in advance to avoid traffic errors or overflows."
- read -e -p "Please enter the incoming traffic threshold (unit is G, default is 100G):" rx_threshold_gb
+ # Enter new virtual memory size
+ echo "If the actual server only has 100G traffic, you can set the threshold to 95G and shut down in advance to avoid traffic errors or overflows."
+ read -e -p "Please enter the inbound traffic threshold (unit is G, default is 100G):" rx_threshold_gb
rx_threshold_gb=${rx_threshold_gb:-100}
read -e -p "Please enter the outbound traffic threshold (unit is G, default is 100G):" tx_threshold_gb
tx_threshold_gb=${tx_threshold_gb:-100}
- read -e -p "Please enter the traffic reset date (default reset on the 1st of each month):" cz_day
+ read -e -p "Please enter the traffic reset date (default resets on the 1st of every month):" cz_day
cz_day=${cz_day:-1}
cd ~
@@ -11665,15 +20925,15 @@ EOF
(crontab -l ; echo "* * * * * ~/Limiting_Shut_down.sh") | crontab - > /dev/null 2>&1
crontab -l | grep -v 'reboot' | crontab -
(crontab -l ; echo "0 1 $cz_day * * reboot") | crontab - > /dev/null 2>&1
- echo "Current limit shutdown has been set"
- send_stats "Current limit shutdown has been set"
+ echo "Current limiting shutdown has been set"
+ send_stats "Current limiting shutdown has been set"
;;
2)
check_crontab_installed
crontab -l | grep -v '~/Limiting_Shut_down.sh' | crontab -
crontab -l | grep -v 'reboot' | crontab -
rm ~/Limiting_Shut_down.sh
- echo "Current limit shutdown function has been turned off"
+ echo "Current limiting shutdown function is turned off"
;;
*)
break
@@ -11684,69 +20944,23 @@ EOF
24)
-
- root_use
- send_stats "Private key login"
- while true; do
- clear
- echo "ROOT private key login mode"
- echo "Video introduction: https://www.bilibili.com/video/BV1Q4421X78n?t=209.4"
- echo "------------------------------------------------"
- echo "A key pair will be generated, a more secure way to SSH login"
- echo "------------------------"
- echo "1. Generate a new key 2. Import an existing key 3. View the native key"
- echo "------------------------"
- echo "0. Return to the previous menu"
- echo "------------------------"
- read -e -p "Please enter your selection:" host_dns
-
- case $host_dns in
- 1)
- send_stats "Generate a new key"
- add_sshkey
- break_end
-
- ;;
- 2)
- send_stats "Import an existing public key"
- import_sshkey
- break_end
-
- ;;
- 3)
- send_stats "View the local secret key"
- echo "------------------------"
- echo "Public key information"
- cat ~/.ssh/authorized_keys
- echo "------------------------"
- echo "Private key information"
- cat ~/.ssh/sshkey
- echo "------------------------"
- break_end
-
- ;;
- *)
- break # 跳出循环,退出菜单
- ;;
- esac
- done
-
+ sshkey_panel
;;
25)
root_use
- send_stats "Telegram warning"
+ send_stats "Telegraph warning"
echo "TG-bot monitoring and early warning function"
echo "Video introduction: https://youtu.be/vLL-eb3Z_TY"
echo "------------------------------------------------"
- echo "You need to configure the tg robot API and the user ID to receive early warnings to realize real-time monitoring and early warning of native CPU, memory, hard disk, traffic, and SSH login"
- echo "After reaching the threshold, the user will be sent to the user"
- echo -e "${gl_hui}- Regarding traffic, restarting the server will recalculate-${gl_bai}"
- read -e -p "Are you sure to continue? (Y/N):" choice
+ echo "You need to configure the tg robot API and the user ID to receive alerts to achieve real-time monitoring and alerts of local CPU, memory, hard disk, traffic, and SSH login."
+ echo "When the threshold is reached, a warning message will be sent to the user."
+ echo -e "${gl_hui}- Regarding traffic, restarting the server will recalculate -${gl_bai}"
+ read -e -p "Are you sure you want to continue? (Y/N):" choice
case "$choice" in
[Yy])
- send_stats "Telegram warning is enabled"
+ send_stats "Telegram warning enabled"
cd ~
install nano tmux bc jq
check_crontab_installed
@@ -11779,8 +20993,8 @@ EOF
source ~/.profile
clear
- echo "TG-bot early warning system has been started"
- echo -e "${gl_hui}You can also place the TG-check-notify.sh warning file in the root directory on other machines and use it directly!${gl_bai}"
+ echo "TG-bot early warning system has been activated"
+ echo -e "${gl_hui}You can also put the TG-check-notify.sh warning file in the root directory on other machines and use it directly!${gl_bai}"
;;
[Nn])
echo "Canceled"
@@ -11793,7 +21007,7 @@ EOF
26)
root_use
- send_stats "Fix high-risk vulnerabilities in SSH"
+ send_stats "Fix high-risk SSH vulnerabilities"
cd ~
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/upgrade_openssh9.8p1.sh
chmod +x ~/upgrade_openssh9.8p1.sh
@@ -11853,90 +21067,109 @@ EOF
;;
+ 39)
+ clear
+ linux_fav
+ ;;
+
+ 40)
+ clear
+ net_menu
+ ;;
+
41)
+ clear
+ log_menu
+ ;;
+
+ 42)
+ clear
+ env_menu
+ ;;
+
+
+ 61)
clear
- send_stats "Message board"
- echo "The technology lion message board has been moved to the official community! Please leave a message in the official community!"
- echo "https://bbs.kejilion.pro/"
+ send_stats "message board"
+ echo "Visit the official message board of Technology Lion. If you have any ideas about the script, please leave a message to exchange!"
+ echo "https://board.kejilion.pro"
+ echo "Public password: kejilion.sh"
;;
66)
root_use
send_stats "One-stop tuning"
- echo "One-stop system optimization"
+ echo "One-stop system tuning"
echo "------------------------------------------------"
- echo "The following will be operated and optimized"
- echo "1. Update the system to the latest"
+ echo "The following content will be operated and optimized"
+ echo "1. Optimize the system update source and update the system to the latest"
echo "2. Clean up system junk files"
echo -e "3. Set up virtual memory${gl_huang}1G${gl_bai}"
echo -e "4. Set the SSH port number to${gl_huang}5522${gl_bai}"
- echo -e "5. Open all ports"
- echo -e "6. Turn on${gl_huang}BBR${gl_bai}accelerate"
- echo -e "7. Set the time zone to${gl_huang}Shanghai${gl_bai}"
- echo -e "8. Automatically optimize DNS address${gl_huang}Overseas: 1.1.1.1 8.8.8.8 Domestic: 223.5.5.5${gl_bai}"
- echo -e "9. Install the basic tools${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
- echo -e "10. Switch to kernel parameter optimization in Linux system${gl_huang}Balanced optimization mode${gl_bai}"
+ echo -e "5. Start fail2ban to defend against SSH brute force cracking"
+ echo -e "6. Open all ports"
+ echo -e "7. Turn on${gl_huang}BBR${gl_bai}accelerate"
+ echo -e "8. Set time zone to${gl_huang}Shanghai${gl_bai}"
+ echo -e "9. Automatically optimize DNS addresses${gl_huang}Overseas: 1.1.1.1 8.8.8.8 Domestic: 223.5.5.5${gl_bai}"
+ echo -e "10. Set the network to${gl_huang}IPv4 priority${gl_bai}"
+ echo -e "11. Install basic tools${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "12. Linux system kernel parameter optimization${gl_huang}Automatically tune according to network environment${gl_bai}"
echo "------------------------------------------------"
- read -e -p "Are you sure to have one-click maintenance? (Y/N):" choice
+ read -e -p "Are you sure you want one-click maintenance? (Y/N):" choice
case "$choice" in
[Yy])
clear
- send_stats "One-stop tuning start"
+ send_stats "One-stop tuning starts"
echo "------------------------------------------------"
+ switch_mirror false false
linux_update
- echo -e "[${gl_lv}OK${gl_bai}] 1/10. Update the system to the latest"
+ echo -e "[${gl_lv}OK${gl_bai}] 1/12. Update the system to the latest"
echo "------------------------------------------------"
linux_clean
- echo -e "[${gl_lv}OK${gl_bai}] 2/10. Clean up system junk files"
+ echo -e "[${gl_lv}OK${gl_bai}] 2/12. Clean up system junk files"
echo "------------------------------------------------"
add_swap 1024
- echo -e "[${gl_lv}OK${gl_bai}] 3/10. Set up virtual memory${gl_huang}1G${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 3/12. Set up virtual memory${gl_huang}1G${gl_bai}"
echo "------------------------------------------------"
- local new_port=5522
- new_ssh_port
- echo -e "[${gl_lv}OK${gl_bai}] 4/10. Set the SSH port number to${gl_huang}5522${gl_bai}"
+ new_ssh_port 5522
+ echo -e "[${gl_lv}OK${gl_bai}] 4/12. Set the SSH port number to${gl_huang}5522${gl_bai}"
+ echo "------------------------------------------------"
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ echo -e "[${gl_lv}OK${gl_bai}] 5/12. Start fail2ban to defend against SSH brute force cracking"
+
echo "------------------------------------------------"
- echo -e "[${gl_lv}OK${gl_bai}] 5/10. Open all ports"
+ echo -e "[${gl_lv}OK${gl_bai}] 6/12. Open all ports"
echo "------------------------------------------------"
bbr_on
- echo -e "[${gl_lv}OK${gl_bai}] 6/10. Open${gl_huang}BBR${gl_bai}accelerate"
+ echo -e "[${gl_lv}OK${gl_bai}] 7/12. Open${gl_huang}BBR${gl_bai}accelerate"
echo "------------------------------------------------"
set_timedate Asia/Shanghai
- echo -e "[${gl_lv}OK${gl_bai}] 7/10. Set the time zone to${gl_huang}Shanghai${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 8/12. Set time zone to${gl_huang}Shanghai${gl_bai}"
echo "------------------------------------------------"
- local country=$(curl -s ipinfo.io/country)
- if [ "$country" = "CN" ]; then
- local dns1_ipv4="223.5.5.5"
- local dns2_ipv4="183.60.83.19"
- local dns1_ipv6="2400:3200::1"
- local dns2_ipv6="2400:da00::6666"
- else
- local dns1_ipv4="1.1.1.1"
- local dns2_ipv4="8.8.8.8"
- local dns1_ipv6="2606:4700:4700::1111"
- local dns2_ipv6="2001:4860:4860::8888"
- fi
-
- set_dns
- echo -e "[${gl_lv}OK${gl_bai}] 8/10. Automatically optimize DNS address${gl_huang}${gl_bai}"
+ auto_optimize_dns
+ echo -e "[${gl_lv}OK${gl_bai}] 9/12. Automatically optimize DNS address${gl_huang}${gl_bai}"
+ echo "------------------------------------------------"
+ prefer_ipv4
+ echo -e "[${gl_lv}OK${gl_bai}] 10/12. Set the network to${gl_huang}IPv4 priority${gl_bai}}"
echo "------------------------------------------------"
install_docker
install wget sudo tar unzip socat btop nano vim
- echo -e "[${gl_lv}OK${gl_bai}] 9/10. Install the basic tools${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 11/12. Install basic tools${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
echo "------------------------------------------------"
- echo "------------------------------------------------"
- optimize_balanced
- echo -e "[${gl_lv}OK${gl_bai}] 10/10. Optimization of kernel parameters for Linux system"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ echo -e "[${gl_lv}OK${gl_bai}] 12/12. Linux system kernel parameter optimization"
echo -e "${gl_lv}One-stop system tuning has been completed${gl_bai}"
;;
@@ -11961,39 +21194,39 @@ EOF
while true; do
clear
if grep -q '^ENABLE_STATS="true"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_lv}正在采集数据${gl_bai}"
+ local status_message="${gl_lv}Collecting data${gl_bai}"
elif grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_hui}采集已关闭${gl_bai}"
+ local status_message="${gl_hui}Collection is closed${gl_bai}"
else
- local status_message="无法确定的状态"
+ local status_message="Uncertain status"
fi
- echo "Privacy and Security"
- echo "The script will collect data on user functions, optimize the script experience, and create more fun and useful functions."
- echo "Will collect the script version number, usage time, system version, CPU architecture, country of the machine and the name of the function used,"
+ echo "Privacy and security"
+ echo "The script will collect data on users’ use of functions, optimize the script experience, and create more fun and useful functions."
+ echo "The script version number, time of use, system version, CPU architecture, country of the machine and name of the function used will be collected,"
echo "------------------------------------------------"
echo -e "Current status:$status_message"
echo "--------------------"
- echo "1. Turn on collection"
- echo "2. Close the collection"
+ echo "1. Start collection"
+ echo "2. Close collection"
echo "--------------------"
echo "0. Return to the previous menu"
echo "--------------------"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
cd ~
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' ~/kejilion.sh
- echo "Collection has been enabled"
- send_stats "Privacy and security collection has been enabled"
+ echo "Collection has been started"
+ send_stats "Privacy and security collection has been turned on"
;;
2)
cd ~
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
echo "Collection closed"
- send_stats "Privacy and Security have been closed for collection"
+ send_stats "Privacy and security collection has been turned off"
;;
*)
break
@@ -12012,8 +21245,8 @@ EOF
send_stats "Uninstall tech lion script"
echo "Uninstall tech lion script"
echo "------------------------------------------------"
- echo "Will completely uninstall the kejilion script and will not affect your other functions"
- read -e -p "Are you sure to continue? (Y/N):" choice
+ echo "The kejilion script will be completely uninstalled without affecting your other functions."
+ read -e -p "Are you sure you want to continue? (Y/N):" choice
case "$choice" in
[Yy])
@@ -12021,7 +21254,7 @@ EOF
(crontab -l | grep -v "kejilion.sh") | crontab -
rm -f /usr/local/bin/k
rm ~/kejilion.sh
- echo "The script has been uninstalled, goodbye!"
+ echo "The script has been uninstalled, bye!"
break_end
clear
exit
@@ -12058,87 +21291,87 @@ EOF
linux_file() {
root_use
- send_stats "File Manager"
+ send_stats "file manager"
while true; do
clear
- echo "File Manager"
+ echo "file manager"
echo "------------------------"
- echo "Current path"
+ echo "current path"
pwd
echo "------------------------"
ls --color=auto -x
echo "------------------------"
- echo "1. Enter the directory 2. Create the directory 3. Modify the directory permissions 4. Rename the directory"
+ echo "1. Enter the directory 2. Create the directory 3. Modify directory permissions 4. Rename the directory"
echo "5. Delete the directory 6. Return to the previous menu directory"
echo "------------------------"
- echo "11. Create a file 12. Edit a file 13. Modify file permissions 14. Rename a file"
- echo "15. Delete the file"
+ echo "11. Create files 12. Edit files 13. Modify file permissions 14. Rename files"
+ echo "15. Delete files"
echo "------------------------"
echo "21. Compress file directory 22. Unzip file directory 23. Move file directory 24. Copy file directory"
- echo "25. Pass the file to another server"
+ echo "25. Transfer files to other servers"
echo "------------------------"
echo "0. Return to the previous menu"
echo "------------------------"
- read -e -p "Please enter your selection:" Limiting
+ read -e -p "Please enter your choice:" Limiting
case "$Limiting" in
1) # 进入目录
read -e -p "Please enter the directory name:" dirname
- cd "$dirname" 2>/dev/null || echo "Unable to enter the directory"
- send_stats "Go to the directory"
+ cd "$dirname" 2>/dev/null || echo "Unable to enter directory"
+ send_stats "Enter directory"
;;
2) # 创建目录
- read -e -p "Please enter the directory name to create:" dirname
+ read -e -p "Please enter the directory name to be created:" dirname
mkdir -p "$dirname" && echo "Directory created" || echo "Creation failed"
- send_stats "Create a directory"
+ send_stats "Create directory"
;;
3) # 修改目录权限
read -e -p "Please enter the directory name:" dirname
- read -e -p "Please enter permissions (such as 755):" perm
+ read -e -p "Please enter permissions (e.g. 755):" perm
chmod "$perm" "$dirname" && echo "Permissions have been modified" || echo "Modification failed"
send_stats "Modify directory permissions"
;;
4) # 重命名目录
read -e -p "Please enter the current directory name:" current_name
- read -e -p "Please enter the new directory name:" new_name
+ read -e -p "Please enter a new directory name:" new_name
mv "$current_name" "$new_name" && echo "Directory has been renamed" || echo "Rename failed"
- send_stats "Rename the directory"
+ send_stats "Rename directory"
;;
5) # 删除目录
- read -e -p "Please enter the directory name to delete:" dirname
- rm -rf "$dirname" && echo "Directory has been deleted" || echo "Deletion failed"
- send_stats "Delete Directory"
+ read -e -p "Please enter the directory name to be deleted:" dirname
+ rm -rf "$dirname" && echo "Directory deleted" || echo "Delete failed"
+ send_stats "delete directory"
;;
6) # 返回上一级选单目录
cd ..
send_stats "Return to the previous menu directory"
;;
11) # 创建文件
- read -e -p "Please enter the file name to create:" filename
+ read -e -p "Please enter the file name to be created:" filename
touch "$filename" && echo "File created" || echo "Creation failed"
- send_stats "Create a file"
+ send_stats "Create file"
;;
12) # 编辑文件
- read -e -p "Please enter the file name to edit:" filename
+ read -e -p "Please enter the file name to be edited:" filename
install nano
nano "$filename"
- send_stats "Edit files"
+ send_stats "Edit file"
;;
13) # 修改文件权限
- read -e -p "Please enter the file name:" filename
- read -e -p "Please enter permissions (such as 755):" perm
+ read -e -p "Please enter a file name:" filename
+ read -e -p "Please enter permissions (e.g. 755):" perm
chmod "$perm" "$filename" && echo "Permissions have been modified" || echo "Modification failed"
send_stats "Modify file permissions"
;;
14) # 重命名文件
read -e -p "Please enter the current file name:" current_name
read -e -p "Please enter a new file name:" new_name
- mv "$current_name" "$new_name" && echo "File renamed" || echo "Rename failed"
- send_stats "Rename the file"
+ mv "$current_name" "$new_name" && echo "File has been renamed" || echo "Rename failed"
+ send_stats "Rename file"
;;
15) # 删除文件
- read -e -p "Please enter the file name to delete:" filename
- rm -f "$filename" && echo "File deleted" || echo "Deletion failed"
+ read -e -p "Please enter the file name to be deleted:" filename
+ rm -f "$filename" && echo "File deleted" || echo "Delete failed"
send_stats "Delete files"
;;
21) # 压缩文件/目录
@@ -12148,81 +21381,68 @@ linux_file() {
send_stats "Compressed files/directories"
;;
22) # 解压文件/目录
- read -e -p "Please enter the file name (.tar.gz):" filename
+ read -e -p "Please enter the file name to be extracted (.tar.gz):" filename
install tar
- tar -xzvf "$filename" && echo "Decompressed$filename" || echo "Decompression failed"
+ tar -xzvf "$filename" && echo "Unzipped$filename" || echo "Decompression failed"
send_stats "Unzip files/directories"
;;
23) # 移动文件或目录
- read -e -p "Please enter the file or directory path to move:" src_path
+ read -e -p "Please enter the file or directory path to be moved:" src_path
if [ ! -e "$src_path" ]; then
- echo "Error: The file or directory does not exist."
- send_stats "Failed to move a file or directory: The file or directory does not exist"
+ echo "Error: File or directory does not exist."
+ send_stats "Failed to move file or directory: File or directory does not exist"
continue
fi
- read -e -p "Please enter the target path (including the new file name or directory name):" dest_path
+ read -e -p "Please enter the destination path (including new file or directory name):" dest_path
if [ -z "$dest_path" ]; then
- echo "Error: Please enter the target path."
- send_stats "Moving file or directory failed: The destination path is not specified"
+ echo "Error: Please enter destination path."
+ send_stats "Failed to move file or directory: Destination path not specified"
continue
fi
- mv "$src_path" "$dest_path" && echo "The file or directory has been moved to$dest_path" || echo "Failed to move files or directories"
- send_stats "Move files or directories"
+ mv "$src_path" "$dest_path" && echo "File or directory moved to$dest_path" || echo "Failed to move file or directory"
+ send_stats "Move a file or directory"
;;
24) # 复制文件目录
read -e -p "Please enter the file or directory path to copy:" src_path
if [ ! -e "$src_path" ]; then
- echo "Error: The file or directory does not exist."
- send_stats "Failed to copy a file or directory: The file or directory does not exist"
+ echo "Error: File or directory does not exist."
+ send_stats "Copying file or directory failed: File or directory does not exist"
continue
fi
- read -e -p "Please enter the target path (including the new file name or directory name):" dest_path
+ read -e -p "Please enter the destination path (including new file or directory name):" dest_path
if [ -z "$dest_path" ]; then
- echo "Error: Please enter the target path."
- send_stats "Failed to copy file or directory: Destination path not specified"
+ echo "Error: Please enter destination path."
+ send_stats "Copying file or directory failed: Destination path not specified"
continue
fi
- # Use the -r option to copy the directory recursively
- cp -r "$src_path" "$dest_path" && echo "The file or directory has been copied to$dest_path" || echo "Failed to copy a file or directory"
- send_stats "Copy files or directories"
+ # Use the -r option to copy directories recursively
+ cp -r "$src_path" "$dest_path" && echo "File or directory copied to$dest_path" || echo "Failed to copy file or directory"
+ send_stats "Copy a file or directory"
;;
25) # 传送文件至远端服务器
read -e -p "Please enter the file path to be transferred:" file_to_transfer
if [ ! -f "$file_to_transfer" ]; then
- echo "Error: The file does not exist."
- send_stats "Failed to transfer the file: The file does not exist"
+ echo "Error: File does not exist."
+ send_stats "Failed to transfer file: file does not exist"
continue
fi
- read -e -p "Please enter the remote server IP:" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "Error: Please enter the remote server IP."
- send_stats "File transfer failed: Remote server IP was not entered"
- continue
- fi
-
- read -e -p "Please enter the remote server username (default root):" remote_user
- remote_user=${remote_user:-root}
-
- read -e -p "Please enter the remote server password:" -s remote_password
- echo
- if [ -z "$remote_password" ]; then
- echo "Error: Please enter the remote server password."
- send_stats "File transfer failed: Remote server password not entered"
- continue
- fi
+ kj_ssh_read_host_user_port "Please enter the remote server IP:" "Please enter the remote server username (default root):" "Please enter the login port (default 22):" "root" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local remote_user="$KJ_SSH_USER"
+ local remote_port="$KJ_SSH_PORT"
- read -e -p "Please enter the login port (default 22):" remote_port
- remote_port=${remote_port:-22}
+ kj_ssh_read_password "Please enter the remote server password:"
+ local remote_password="$KJ_SSH_PASSWORD"
# Clear old entries for known hosts
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
@@ -12235,7 +21455,7 @@ EOF
if [ $? -eq 0 ]; then
echo "The file has been transferred to the remote server home directory."
- send_stats "File transfer successfully"
+ send_stats "File transfer successful"
else
echo "File transfer failed."
send_stats "File transfer failed"
@@ -12247,7 +21467,7 @@ EOF
0) # 返回上一级选单
- send_stats "Return to the previous menu menu"
+ send_stats "Return to the previous menu"
break
;;
*) # 处理无效输入
@@ -12278,10 +21498,10 @@ run_commands_on_servers() {
local SERVERS_FILE="$HOME/cluster/servers.py"
local SERVERS=$(grep -oP '{"name": "\K[^"]+|"hostname": "\K[^"]+|"port": \K[^,]+|"username": "\K[^"]+|"password": "\K[^"]+' "$SERVERS_FILE")
- # Convert extracted information into an array
+ # Convert the extracted information into an array
IFS=$'\n' read -r -d '' -a SERVER_ARRAY <<< "$SERVERS"
- # Iterate through the server and execute commands
+ # Traverse the server and execute commands
for ((i=0; i<${#SERVER_ARRAY[@]}; i+=5)); do
local name=${SERVER_ARRAY[i]}
local hostname=${SERVER_ARRAY[i+1]}
@@ -12311,30 +21531,30 @@ fi
while true; do
clear
- send_stats "Cluster Control Center"
+ send_stats "Cluster control center"
echo "Server cluster control"
cat ~/cluster/servers.py
echo
echo -e "${gl_kjlan}------------------------${gl_bai}"
- echo -e "${gl_kjlan}Server List Management${gl_bai}"
- echo -e "${gl_kjlan}1. ${gl_bai}Add a server${gl_kjlan}2. ${gl_bai}Delete the server${gl_kjlan}3. ${gl_bai}Edit the server"
- echo -e "${gl_kjlan}4. ${gl_bai}Backup cluster${gl_kjlan}5. ${gl_bai}Restore the cluster"
+ echo -e "${gl_kjlan}Server list management${gl_bai}"
+ echo -e "${gl_kjlan}1. ${gl_bai}Add server${gl_kjlan}2. ${gl_bai}Delete server${gl_kjlan}3. ${gl_bai}Edit server"
+ echo -e "${gl_kjlan}4. ${gl_bai}Backup cluster${gl_kjlan}5. ${gl_bai}Restore cluster"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}Execute tasks in batches${gl_bai}"
- echo -e "${gl_kjlan}11. ${gl_bai}Install the tech lion script${gl_kjlan}12. ${gl_bai}Update the system${gl_kjlan}13. ${gl_bai}Clean the system"
- echo -e "${gl_kjlan}14. ${gl_bai}Install docker${gl_kjlan}15. ${gl_bai}Install BBR3${gl_kjlan}16. ${gl_bai}Set up 1G virtual memory"
- echo -e "${gl_kjlan}17. ${gl_bai}Set the time zone to Shanghai${gl_kjlan}18. ${gl_bai}Open all ports${gl_kjlan}51. ${gl_bai}Custom commands"
+ echo -e "${gl_kjlan}11. ${gl_bai}Install technology lion script${gl_kjlan}12. ${gl_bai}Update system${gl_kjlan}13. ${gl_bai}Clean the system"
+ echo -e "${gl_kjlan}14. ${gl_bai}Install docker${gl_kjlan}15. ${gl_bai}Install BBR3${gl_kjlan}16. ${gl_bai}Set 1G virtual memory"
+ echo -e "${gl_kjlan}17. ${gl_bai}Set time zone to Shanghai${gl_kjlan}18. ${gl_bai}Open all ports${gl_kjlan}51. ${gl_bai}Custom instructions"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "Please enter your selection:" sub_choice
+ read -e -p "Please enter your choice:" sub_choice
case $sub_choice in
1)
- send_stats "Add a cluster server"
+ send_stats "Add cluster server"
read -e -p "Server name:" server_name
read -e -p "Server IP:" server_ip
- read -e -p "Server Port (22):" server_port
+ read -e -p "Server port (22):" server_port
local server_port=${server_port:-22}
read -e -p "Server username (root):" server_username
local server_username=${server_username:-root}
@@ -12344,12 +21564,12 @@ while true; do
;;
2)
- send_stats "Delete the cluster server"
- read -e -p "Please enter the keywords you need to delete:" rmserver
+ send_stats "Delete cluster server"
+ read -e -p "Please enter the keywords to be deleted:" rmserver
sed -i "/$rmserver/d" ~/cluster/servers.py
;;
3)
- send_stats "Edit the cluster server"
+ send_stats "Edit cluster server"
install nano
nano ~/cluster/servers.py
;;
@@ -12357,15 +21577,15 @@ while true; do
4)
clear
send_stats "Backup cluster"
- echo -e "Please${gl_huang}/root/cluster/servers.py${gl_bai}Download the file and complete the backup!"
+ echo -e "please change${gl_huang}/root/cluster/servers.py${gl_bai}Download the file and complete the backup!"
break_end
;;
5)
clear
- send_stats "Restore the cluster"
+ send_stats "Restore cluster"
echo "Please upload your servers.py and press any key to start uploading!"
- echo -e "Please upload your${gl_huang}servers.py${gl_bai}File to${gl_huang}/root/cluster/${gl_bai}Complete the restore!"
+ echo -e "Please upload your${gl_huang}servers.py${gl_bai}file to${gl_huang}/root/cluster/${gl_bai}Restore completed!"
break_end
;;
@@ -12396,8 +21616,8 @@ while true; do
;;
51)
- send_stats "Customize the execution of commands"
- read -e -p "Please enter the batch execution command:" mingling
+ send_stats "Custom execution command"
+ read -e -p "Please enter the command for batch execution:" mingling
run_commands_on_servers "${mingling}"
;;
@@ -12420,25 +21640,25 @@ echo "Advertising column"
echo "------------------------"
echo "It will provide users with a simpler and more elegant promotion and purchasing experience!"
echo ""
-echo -e "Server Offers"
+echo -e "Server Discount"
echo "------------------------"
-echo -e "${gl_lan}Leica Cloud Hong Kong CN2 GIA South Korea Dual ISP US CN2 GIA Discounts${gl_bai}"
+echo -e "${gl_lan}Laika Cloud Hong Kong CN2 GIA Korean dual ISP US CN2 GIA promotions${gl_bai}"
echo -e "${gl_bai}Website: https://www.lcayun.com/aff/ZEXUQBIM${gl_bai}"
echo "------------------------"
-echo -e "${gl_lan}RackNerd $10.99 per year United States 1 core 1G memory 20G hard drive 1T traffic per month${gl_bai}"
-echo -e "${gl_bai}Website: https://my.racknerd.com/aff.php?aff=5501&pid=879${gl_bai}"
+echo -e "${gl_lan}RackNerd $10.99 per year, USA, 1 core, 1G memory, 20G hard drive, 1T traffic per month${gl_bai}"
+echo -e "${gl_bai}URL: https://my.racknerd.com/aff.php?aff=5501&pid=879${gl_bai}"
echo "------------------------"
-echo -e "${gl_zi}Hostinger 52.7 dollars per year United States 1 core 4G memory 50G hard drive 4T traffic per month${gl_bai}"
-echo -e "${gl_bai}Website: https://cart.hostinger.com/pay/d83c51e9-0c28-47a6-8414-b8ab010ef94f?_ga=GA1.3.942352702.1711283207${gl_bai}"
+echo -e "${gl_zi}Hostinger $52.7 per year United States 1 core 4G memory 50G hard drive 4T traffic per month${gl_bai}"
+echo -e "${gl_bai}URL: https://cart.hostinger.com/pay/d83c51e9-0c28-47a6-8414-b8ab010ef94f?_ga=GA1.3.942352702.1711283207${gl_bai}"
echo "------------------------"
-echo -e "${gl_huang}Brickworker, $49 per quarter, US CN2GIA, Japan SoftBank, 2 cores, 1G memory, 20G hard drive, 1T traffic per month${gl_bai}"
+echo -e "${gl_huang}Bricklayer 49 dollars per quarter US CN2GIA Japan SoftBank 2 cores 1G memory 20G hard drive 1T traffic per month${gl_bai}"
echo -e "${gl_bai}Website: https://bandwagonhost.com/aff.php?aff=69004&pid=87${gl_bai}"
echo "------------------------"
echo -e "${gl_lan}DMIT $28 per quarter US CN2GIA 1 core 2G memory 20G hard drive 800G traffic per month${gl_bai}"
-echo -e "${gl_bai}Website: https://www.dmit.io/aff.php?aff=4966&pid=100${gl_bai}"
+echo -e "${gl_bai}URL: https://www.dmit.io/aff.php?aff=4966&pid=100${gl_bai}"
echo "------------------------"
-echo -e "${gl_zi}V.PS $6.9 per month Tokyo SoftBank 2 core 1G memory 20G hard drive 1T traffic per month${gl_bai}"
-echo -e "${gl_bai}Website: https://vps.hosting/cart/tokyo-cloud-kvm-vps/?id=148&?affid=1355&?affid=1355${gl_bai}"
+echo -e "${gl_zi}V.PS 6.9 dollars per month Tokyo Softbank 2 cores 1G memory 20G hard drive 1T traffic per month${gl_bai}"
+echo -e "${gl_bai}URL: https://vps.hosting/cart/tokyo-cloud-kvm-vps/?id=148&?affid=1355&?affid=1355${gl_bai}"
echo "------------------------"
echo -e "${gl_kjlan}More popular VPS offers${gl_bai}"
echo -e "${gl_bai}Website: https://kejilion.pro/topvps/${gl_bai}"
@@ -12446,17 +21666,17 @@ echo "------------------------"
echo ""
echo -e "Domain name discount"
echo "------------------------"
-echo -e "${gl_lan}GNAME 8.8 dollars first year COM domain name 6.68 dollars first year CC domain name${gl_bai}"
+echo -e "${gl_lan}GNAME $8.8 first-year COM domain name $6.68 first-year CC domain name${gl_bai}"
echo -e "${gl_bai}Website: https://www.gname.com/register?tt=86836&ttcode=KEJILION86836&ttbj=sh${gl_bai}"
echo "------------------------"
echo ""
-echo -e "Technology lion surrounding"
+echo -e "Technology lion peripherals"
echo "------------------------"
-echo -e "${gl_kjlan}B station:${gl_bai}https://b23.tv/2mqnQyh ${gl_kjlan}Oil pipe:${gl_bai}https://www.youtube.com/@kejilion${gl_bai}"
+echo -e "${gl_kjlan}Station B:${gl_bai}https://b23.tv/2mqnQyh ${gl_kjlan}Oil pipe:${gl_bai}https://www.youtube.com/@kejilion${gl_bai}"
echo -e "${gl_kjlan}Official website:${gl_bai}https://kejilion.pro/ ${gl_kjlan}navigation:${gl_bai}https://dh.kejilion.pro/${gl_bai}"
echo -e "${gl_kjlan}blog:${gl_bai}https://blog.kejilion.pro/ ${gl_kjlan}Software Center:${gl_bai}https://app.kejilion.pro/${gl_bai}"
echo "------------------------"
-echo -e "${gl_kjlan}Script official website:${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub address:${gl_bai}https://github.com/kejilion/sh${gl_bai}"
+echo -e "${gl_kjlan}Script official website:${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub address:${gl_bai}${gh_https_url}github.com/kejilion/sh${gl_bai}"
echo "------------------------"
echo ""
}
@@ -12464,6 +21684,64 @@ echo ""
+games_server_tools() {
+
+ while true; do
+ clear
+ echo -e "Collection of game server opening scripts"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}Eudemons Parlu server opening script"
+ echo -e "${gl_kjlan}2. ${gl_bai}Minecraft server opening script"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}Return to main menu"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "Please enter your choice:" sub_choice
+
+ case $sub_choice in
+
+ 1) send_stats "Eudemons Parlu server opening script" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
+ exit
+ ;;
+ 2) send_stats "Minecraft server opening script" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/mc.sh ; chmod +x mc.sh ; ./mc.sh
+ exit
+ ;;
+
+ 0)
+ kejilion
+ ;;
+
+ *)
+ echo "Invalid input!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
kejilion_update() {
@@ -12471,20 +21749,23 @@ send_stats "Script update"
cd ~
while true; do
clear
- echo "Update log"
+ echo "Change log"
echo "------------------------"
echo "All logs:${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt"
echo "------------------------"
- curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
- local sh_v_new=$(curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | cut -d '"' -f 2)
+ curl -s --max-time 15 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
+ # Only download the first 5 lines to get the version number to avoid downloading the entire script
+ local sh_v_new=$(curl -s --max-time 15 -r 0-200 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | head -1 | cut -d '"' -f 2)
- if [ "$sh_v" = "$sh_v_new" ]; then
- echo -e "${gl_lv}You are already the latest version!${gl_huang}v$sh_v${gl_bai}"
- send_stats "The script is up to date and no update is required"
+ if [ -z "$sh_v_new" ]; then
+ echo -e "${gl_hong}Unable to obtain the latest version information, please check the network connection${gl_bai}"
+ elif [ "$sh_v" = "$sh_v_new" ]; then
+ echo -e "${gl_lv}You are already on the latest version!${gl_huang}v$sh_v${gl_bai}"
+ send_stats "The script is already up to date and does not need to be updated"
else
- echo "Discover a new version!"
- echo -e "Current version v$sh_vLatest version${gl_huang}v$sh_v_new${gl_bai}"
+ echo "New version discovered!"
+ echo -e "Current version v$sh_vlatest version${gl_huang}v$sh_v_new${gl_bai}"
fi
@@ -12493,58 +21774,97 @@ while true; do
if [ -n "$existing_cron" ]; then
echo "------------------------"
- echo -e "${gl_lv}Automatic update is enabled, and the script will be automatically updated at 2 a.m. every day!${gl_bai}"
+ echo -e "${gl_lv}Automatic updates are turned on, and the script will be automatically updated at 2 a.m. every day!${gl_bai}"
fi
echo "------------------------"
- echo "1. Update now 2. Turn on automatic update 3. Turn off automatic update"
+ echo "1. Update now 2. Turn on automatic updates 3. Turn off automatic updates"
echo "------------------------"
echo "0. Return to main menu"
echo "------------------------"
- read -e -p "Please enter your selection:" choice
+ read -e -p "Please enter your choice:" choice
case "$choice" in
1)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
+ local download_url
if [ "$country" = "CN" ]; then
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh && chmod +x kejilion.sh
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh"
+ else
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh"
+ fi
+
+ # Back up current script
+ cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null
+
+ # Download to temporary file, verify and then replace
+ local tmp_file=$(mktemp ~/kejilion_tmp.XXXXXX)
+ if curl -sS --max-time 60 --fail -o "$tmp_file" "$download_url" && \
+ [ -s "$tmp_file" ] && \
+ head -1 "$tmp_file" | grep -q '^#!/bin/bash'; then
+ chmod +x "$tmp_file"
+ mv -f "$tmp_file" ~/kejilion.sh
+ canshu_v6
+ CheckFirstRun_true
+ yinsiyuanquan2
+ cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
+ echo -e "${gl_lv}The script has been updated to the latest version!${gl_huang}v$sh_v_new${gl_bai}"
+ send_stats "The script is up to date$sh_v_new"
else
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh
+ rm -f "$tmp_file"
+ # Restore backup
+ if [ -f ~/kejilion.sh.bak ]; then
+ mv -f ~/kejilion.sh.bak ~/kejilion.sh
+ fi
+ echo -e "${gl_hong}Update failed! There was an error in downloading or the file verification failed. The original version has been restored.${gl_bai}"
+ send_stats "Script update failed"
fi
- canshu_v6
- CheckFirstRun_true
- yinsiyuanquan2
- cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
- echo -e "${gl_lv}The script has been updated to the latest version!${gl_huang}v$sh_v_new${gl_bai}"
- send_stats "The script is up to date$sh_v_new"
break_end
~/kejilion.sh
exit
;;
2)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
local ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
+ local cron_proxy cron_sed_cmd
if [ "$country" = "CN" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ~/kejilion.sh"
elif [ -n "$ipv6_address" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ~/kejilion.sh"
else
- SH_Update_task="curl -sS -O https://raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh"
+ cron_proxy="https://"
+ cron_sed_cmd=""
+ fi
+
+ # Build a robust auto-update command: Download to temporary file → Verify → Backup → Replace → Restore local settings → Deploy
+ SH_Update_task="cd ~ && tmp=\$(mktemp ~/kejilion_tmp.XXXXXX) && curl -sS --max-time 60 --fail -o \"\$tmp\" ${cron_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && [ -s \"\$tmp\" ] && head -1 \"\$tmp\" | grep -q '^#!/bin/bash' && cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null && chmod +x \"\$tmp\" && mv -f \"\$tmp\" ~/kejilion.sh"
+ # Additional settings recovery
+ if [ -n "$cron_sed_cmd" ]; then
+ SH_Update_task="$SH_Update_task && $cron_sed_cmd"
fi
+ # Restore permission_granted and ENABLE_STATS settings from old script
+ SH_Update_task="$SH_Update_task && grep -q 'permission_granted=\"true\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/permission_granted=\"false\"/permission_granted=\"true\"/' ~/kejilion.sh; grep -q 'ENABLE_STATS=\"false\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/ENABLE_STATS=\"true\"/ENABLE_STATS=\"false\"/' ~/kejilion.sh"
+ # Deploy to /usr/local/bin/k and /usr/bin/k
+ SH_Update_task="$SH_Update_task; cp -f ~/kejilion.sh /usr/local/bin/k 2>/dev/null; ln -sf /usr/local/bin/k /usr/bin/k 2>/dev/null"
+ # Clean temporary files when download fails
+ SH_Update_task="$SH_Update_task || rm -f \"\$tmp\" 2>/dev/null"
+
check_crontab_installed
(crontab -l | grep -v "kejilion.sh") | crontab -
- # (crontab -l 2>/dev/null; echo "0 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- echo -e "${gl_lv}Automatic update is enabled, and the script will be automatically updated at 2 a.m. every day!${gl_bai}"
- send_stats "Turn on automatic script update"
+ (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c '$SH_Update_task'") | crontab -
+ echo -e "${gl_lv}Automatic updates are turned on, and the script will be automatically updated at 2 a.m. every day!${gl_bai}"
+ send_stats "Enable automatic script updates"
break_end
;;
3)
clear
(crontab -l | grep -v "kejilion.sh") | crontab -
- echo -e "${gl_lv}Automatic update is closed${gl_bai}"
- send_stats "Close script automatic update"
+ echo -e "${gl_lv}Automatic updates are turned off${gl_bai}"
+ send_stats "Turn off automatic script updates"
break_end
;;
*)
@@ -12567,36 +21887,35 @@ echo "╦╔═╔═╗ ╦╦╦ ╦╔═╗╔╗╔ ╔═╗╦ ╦"
echo "╠╩╗║╣ ║║║ ║║ ║║║║ ╚═╗╠═╣"
echo "╩ ╩╚═╝╚╝╩╩═╝╩╚═╝╝╚╝o╚═╝╩ ╩"
echo -e "Technology lion script toolbox v$sh_v"
-echo -e "Command line input${gl_huang}k${gl_kjlan}Quickly start scripts${gl_bai}"
+echo -e "Command line input${gl_huang}k${gl_kjlan}Quick start script${gl_bai}"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}1. ${gl_bai}System information query"
echo -e "${gl_kjlan}2. ${gl_bai}System update"
-echo -e "${gl_kjlan}3. ${gl_bai}System Cleanup"
-echo -e "${gl_kjlan}4. ${gl_bai}Basic tools"
-echo -e "${gl_kjlan}5. ${gl_bai}BBR Management"
-echo -e "${gl_kjlan}6. ${gl_bai}Docker Management"
-echo -e "${gl_kjlan}7. ${gl_bai}WARP Management"
+echo -e "${gl_kjlan}3. ${gl_bai}System cleanup"
+echo -e "${gl_kjlan}4. ${gl_bai}basic tools"
+echo -e "${gl_kjlan}5. ${gl_bai}BBR management"
+echo -e "${gl_kjlan}6. ${gl_bai}Docker management"
+echo -e "${gl_kjlan}7. ${gl_bai}WARP management"
echo -e "${gl_kjlan}8. ${gl_bai}Test script collection"
echo -e "${gl_kjlan}9. ${gl_bai}Oracle Cloud Script Collection"
echo -e "${gl_huang}10. ${gl_bai}LDNMP website building"
-echo -e "${gl_kjlan}11. ${gl_bai}Application Market"
+echo -e "${gl_kjlan}11. ${gl_bai}application market"
echo -e "${gl_kjlan}12. ${gl_bai}Backend workspace"
-echo -e "${gl_kjlan}13. ${gl_bai}System Tools"
+echo -e "${gl_kjlan}13. ${gl_bai}system tools"
echo -e "${gl_kjlan}14. ${gl_bai}Server cluster control"
echo -e "${gl_kjlan}15. ${gl_bai}Advertising column"
-echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}p. ${gl_bai}Phantom Beast Palu server opening script"
+echo -e "${gl_kjlan}16. ${gl_bai}Collection of game server opening scripts"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}00. ${gl_bai}Script update"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}0. ${gl_bai}Exit script"
echo -e "${gl_kjlan}------------------------${gl_bai}"
-read -e -p "Please enter your selection:" choice
+read -e -p "Please enter your choice:" choice
case $choice in
- 1) linux_ps ;;
+ 1) linux_info ;;
2) clear ; send_stats "System update" ; linux_update ;;
- 3) clear ; send_stats "System Cleanup" ; linux_clean ;;
+ 3) clear ; send_stats "System cleanup" ; linux_clean ;;
4) linux_tools ;;
5) linux_bbr ;;
6) linux_docker ;;
@@ -12611,10 +21930,7 @@ case $choice in
13) linux_Settings ;;
14) linux_cluster ;;
15) kejilion_Affiliates ;;
- p) send_stats "Phantom Beast Palu server opening script" ; cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
- exit
- ;;
+ 16) games_server_tools ;;
00) kejilion_update ;;
0) clear ; exit ;;
*) echo "Invalid input!" ;;
@@ -12625,55 +21941,64 @@ done
k_info() {
-send_stats "k command reference use case"
+send_stats "k command reference examples"
echo "-------------------"
echo "Video introduction: https://www.bilibili.com/video/BV1ib421E7it?t=0.1"
-echo "The following is the k command reference use case:"
+echo "The following is a reference use case for the k command:"
echo "Start script k"
-echo "Install software package k install nano wget | k add nano wget | k Install nano wget"
-echo "Uninstall the package k remove nano wget | k del nano wget | k uninstall nano wget | k Uninstall nano wget"
+echo "Install packages k install nano wget | k add nano wget | k install nano wget"
+echo "Uninstall a package k remove nano wget | k del nano wget | k uninstall nano wget | k uninstall nano wget"
echo "Update system k update | k update"
-echo "Clean system garbage k clean | k clean"
-echo "Reinstall the system panel k dd | k Reinstall"
+echo "Clean system junk k clean | k clean"
+echo "Reinstall the system panel k dd | k reinstall"
echo "bbr3 control panel k bbr3 | k bbrv3"
-echo "Kernel Tuning Panel k nhyh | k kernel optimization"
+echo "Kernel Tuning Panel k nhyh | k Kernel Optimization"
echo "Set virtual memory k swap 2048"
echo "Set virtual time zone k time Asia/Shanghai | k time zone Asia/Shanghai"
-echo "System Recycling Bin k trash | k hsz | k Recycling Bin"
+echo "System Recycle Bin k trash | k hsz | k Recycle Bin"
echo "System backup function k backup | k bf | k backup"
echo "ssh remote connection tool k ssh | k remote connection"
echo "rsync remote synchronization tool k rsync | k remote synchronization"
echo "Hard disk management tool k disk | k hard disk management"
-echo "Intranet penetration (server side) k frps"
+echo "Intranet penetration (server) k frps"
echo "Intranet penetration (client) k frpc"
-echo "Software start k start sshd | k start sshd"
+echo "Software startup k start sshd | k start sshd"
echo "Software stop k stop sshd | k stop sshd"
echo "Software restart k restart sshd | k restart sshd"
-echo "Software status view k status sshd | k status sshd"
-echo "Software boot k enable docker | k autostart docke | k startup docker"
+echo "Check software status k status sshd | k status sshd"
+echo "k enable docker | k autostart docker | k enable docker when booting the software"
echo "Domain name certificate application k ssl"
-echo "Domain name certificate expiration query k ssl ps"
+echo "Domain name certificate expiry query k ssl ps"
+echo "docker management plane k docker"
echo "docker environment installation k docker install |k docker installation"
echo "docker container management k docker ps |k docker container"
echo "docker image management k docker img |k docker image"
echo "LDNMP site management k web"
-echo "LDNMP cache cleanup k web cache"
-echo "Install WordPress k wp |k wordpress |k wp xxx.com"
-echo "Install the reverse proxy k fd |k rp |k anti-generation |k fd xxx.com"
+echo "LDNMP cache cleaning k web cache"
+echo "Install WordPress k wp | k wordpress | k wp xxx.com"
+echo "Install reverse proxy k fd |k rp |k reverse proxy |k fd xxx.com"
echo "Install load balancing k loadbalance |k load balancing"
-echo "Firewall panel k fhq |k firewall"
-echo "Open port k dkdk 8080 |k Open port 8080"
+echo "Install L4 load balancing k stream |k L4 load balancing"
+echo "firewall panel k fhq |k firewall"
+echo "open port k dkdk 8080 |k open port 8080"
echo "Close port k gbdk 7800 |k Close port 7800"
-echo "放行IP k fxip 127.0.0.0/8 |k 放行IP 127.0.0.0/8"
+echo "Release IP k fxip 127.0.0.0/8 |k Release IP 127.0.0.0/8"
echo "Block IP k zzip 177.5.25.36 |k Block IP 177.5.25.36"
-
+echo "command favorites k fav | k command favorites"
+echo "Application market management k app"
+echo "Quick management of application numbers k app 26 | k app 1panel | k app npm"
+echo "fail2ban management k fail2ban | k f2b"
+echo "Display system information k info"
+echo "ROOT key management k sshkey"
+echo "SSH public key import (URL) k sshkey "
+echo "SSH public key import (GitHub) k sshkey github "
}
if [ "$#" -eq 0 ]; then
- # If there are no parameters, run interactive logic
+ # Without arguments, run interactive logic
kejilion_sh
else
# If there are parameters, execute the corresponding function
@@ -12685,7 +22010,7 @@ else
;;
remove|del|uninstall|卸载)
shift
- send_stats "Uninstall the software"
+ send_stats "Uninstall software"
remove "$@"
;;
update|更新)
@@ -12719,7 +22044,7 @@ else
rsync_run)
shift
- send_stats "Timed rsync synchronization"
+ send_stats "Scheduled rsync synchronization"
run_task "$@"
;;
@@ -12738,9 +22063,10 @@ else
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "IP+ ports have been blocked from accessing the service"
+ echo "IP+port has been blocked from accessing the service"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
;;
@@ -12749,6 +22075,11 @@ else
ldnmp_Proxy_backend
;;
+
+ stream|L4负载均衡)
+ ldnmp_Proxy_backend_stream
+ ;;
+
swap)
shift
send_stats "Quickly set up virtual memory"
@@ -12799,9 +22130,13 @@ else
iptables_panel
;;
+ 命令收藏夹|fav)
+ linux_fav
+ ;;
+
status|状态)
shift
- send_stats "Software status view"
+ send_stats "Check software status"
status "$@"
;;
start|启动)
@@ -12811,7 +22146,7 @@ else
;;
stop|停止)
shift
- send_stats "Software pause"
+ send_stats "software pause"
stop "$@"
;;
restart|重启)
@@ -12822,21 +22157,21 @@ else
enable|autostart|开机启动)
shift
- send_stats "Software boots up"
+ send_stats "Software starts automatically when booting"
enable "$@"
;;
ssl)
shift
if [ "$1" = "ps" ]; then
- send_stats "Check the certificate status"
+ send_stats "View certificate status"
ssl_ps
elif [ -z "$1" ]; then
add_ssl
- send_stats "Quickly apply for a certificate"
+ send_stats "Apply for a certificate quickly"
elif [ -n "$1" ]; then
add_ssl "$1"
- send_stats "Quickly apply for a certificate"
+ send_stats "Apply for a certificate quickly"
else
k_info
fi
@@ -12854,11 +22189,11 @@ else
docker_ps
;;
img|镜像)
- send_stats "Quick mirror management"
+ send_stats "Quick image management"
docker_image
;;
*)
- k_info
+ linux_docker
;;
esac
;;
@@ -12878,6 +22213,59 @@ else
fi
;;
+
+ app)
+ shift
+ send_stats "Apply$@"
+ linux_panel "$@"
+ ;;
+
+ claw|oc|OpenClaw)
+ moltbot_menu
+ ;;
+
+ info)
+ linux_info
+ ;;
+
+ fail2ban|f2b)
+ fail2ban_panel
+ ;;
+
+
+ sshkey)
+
+ shift
+ case "$1" in
+ "" )
+ # sshkey → interactive menu
+ send_stats "SSHKey interactive menu"
+ sshkey_panel
+ ;;
+ github )
+ shift
+ send_stats "Import SSH public key from GitHub"
+ fetch_github_ssh_keys "$1"
+ ;;
+ http://*|https://* )
+ send_stats "Import SSH public key from URL"
+ fetch_remote_ssh_keys "$1"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa* )
+ send_stats "Directly import the public key"
+ import_sshkey "$1"
+ ;;
+ * )
+ echo "Error: unknown parameter '$1'"
+ echo "usage:"
+ echo "k sshkey enters the interactive menu"
+ echo "k sshkey \"\" Directly import the SSH public key"
+ echo "k sshkey Import SSH public key from URL"
+ echo "k sshkey github Import SSH public key from GitHub"
+ ;;
+ esac
+
+ ;;
*)
k_info
;;
diff --git a/hermes_manager.sh b/hermes_manager.sh
new file mode 100755
index 000000000..169ce0364
--- /dev/null
+++ b/hermes_manager.sh
@@ -0,0 +1,1034 @@
+#!/bin/bash
+# Hermes Agent 终端管理脚本 (轻量版)
+# 设计哲学:极简、直观、调用原生功能
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+CYAN='\033[0;36m'
+NC='\033[0m'
+
+# 确保 hermes 命令可用 (处理环境变量未加载的情况)
+if ! command -v hermes >/dev/null 2>&1; then
+ if [ -d "$HOME/.hermes/hermes-agent/venv/bin" ]; then
+ export PATH="$HOME/.hermes/hermes-agent/venv/bin:$PATH"
+ fi
+fi
+
+# 检查是否安装
+
+# --- 科技lion 增强版 API 管理核心 ---
+CONFIG_FILE="$HOME/.hermes/config.yaml"
+
+config_tool() {
+ # 自动适配 CONFIG_FILE 路径
+ if [ ! -f "$CONFIG_FILE" ]; then
+ local p
+ for p in "/root/.hermes/config.yaml" /home/*/.hermes/config.yaml; do
+ if [ -f "$p" ]; then
+ CONFIG_FILE="$p"
+ break
+ fi
+ done
+ fi
+
+ # 寻找可用的 Python 解释器,优先使用带有 pyyaml (yaml) 的环境
+ local python_bin=""
+
+ # 1. 尝试从 command -v hermes 指向的文件的 shebang/内容中提取 python 路径
+ local hermes_cmd
+ hermes_cmd=$(command -v hermes)
+ if [ -n "$hermes_cmd" ] && [ -f "$hermes_cmd" ]; then
+ # A. 检查第一行是否是 shebang
+ local shebang
+ shebang=$(head -n 1 "$hermes_cmd" 2>/dev/null)
+ if [[ "$shebang" =~ ^#\! ]]; then
+ local potential_py="${shebang#\#!}"
+ if [ -f "$potential_py" ]; then
+ if "$potential_py" -c "import yaml" >/dev/null 2>&1; then
+ python_bin="$potential_py"
+ fi
+ fi
+ fi
+ # B. 检查是否是 Bash wrapper,追踪其实际指向的 bin 并提取 python3
+ if [ -z "$python_bin" ]; then
+ local wrapped_bin
+ wrapped_bin=$(grep -Eo '"/[^"]+/venv/bin/hermes"' "$hermes_cmd" | tr -d '"' | head -n 1)
+ if [ -z "$wrapped_bin" ]; then
+ wrapped_bin=$(grep -Eo '/[a-zA-Z0-9_\.\-]+/hermes-agent/venv/bin/hermes' "$hermes_cmd" | head -n 1)
+ fi
+ if [ -n "$wrapped_bin" ] && [ -f "$wrapped_bin" ]; then
+ local wrapped_shebang
+ wrapped_shebang=$(head -n 1 "$wrapped_bin" 2>/dev/null)
+ if [[ "$wrapped_shebang" =~ ^#\! ]]; then
+ local potential_py="${wrapped_shebang#\#!}"
+ if [ -f "$potential_py" ] && "$potential_py" -c "import yaml" >/dev/null 2>&1; then
+ python_bin="$potential_py"
+ fi
+ fi
+ if [ -z "$python_bin" ]; then
+ local potential_py="${wrapped_bin%/hermes}/python3"
+ if [ -f "$potential_py" ] && "$potential_py" -c "import yaml" >/dev/null 2>&1; then
+ python_bin="$potential_py"
+ fi
+ fi
+ fi
+ fi
+ fi
+
+ # 2. 尝试从常见绝对路径查找
+ if [ -z "$python_bin" ]; then
+ local paths=(
+ "$HOME/.hermes/hermes-agent/venv/bin/python3"
+ "$HOME/.hermes/hermes-agent/venv/bin/python"
+ "/root/.hermes/hermes-agent/venv/bin/python3"
+ "/root/.hermes/hermes-agent/venv/bin/python"
+ "/usr/local/lib/hermes-agent/venv/bin/python3"
+ "/usr/local/lib/hermes-agent/venv/bin/python"
+ "/usr/lib/hermes-agent/venv/bin/python3"
+ "/usr/lib/hermes-agent/venv/bin/python"
+ "$HOME/.hermes/hermes-agent/.venv/bin/python3"
+ "/root/.hermes/hermes-agent/.venv/bin/python3"
+ "/usr/local/lib/hermes-agent/.venv/bin/python3"
+ "/usr/lib/hermes-agent/.venv/bin/python3"
+ /home/*/.hermes/hermes-agent/venv/bin/python3
+ /home/*/.hermes/hermes-agent/venv/bin/python
+ /home/*/.hermes/hermes-agent/.venv/bin/python3
+ )
+ local p
+ for p in "${paths[@]}"; do
+ if [ -f "$p" ]; then
+ if "$p" -c "import yaml" >/dev/null 2>&1; then
+ python_bin="$p"
+ break
+ fi
+ fi
+ done
+ fi
+
+ # 3. 兜底退回到系统全局 python3 或 python
+ if [ -z "$python_bin" ]; then
+ if command -v python3 >/dev/null 2>&1; then
+ python_bin="python3"
+ else
+ python_bin="python"
+ fi
+ fi
+
+ $python_bin - "$CONFIG_FILE" "$@" <<'EOF'
+import sys, yaml, json, os
+
+path = sys.argv[1]
+action = sys.argv[2]
+
+def load():
+ if not os.path.exists(path):
+ return {}
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ return yaml.safe_load(f) or {}
+ except:
+ return {}
+
+def save(d):
+ with open(path, 'w', encoding='utf-8') as f:
+ yaml.dump(d, f, sort_keys=False, allow_unicode=True)
+
+try:
+ data = load()
+ if action == "get_info":
+ m = data.get('model', {})
+ res = {"m": m.get('default', '-'), "p": m.get('provider', '-'), "u": m.get('base_url', '-')}
+ print(json.dumps(res))
+
+ elif action == "list_p":
+ print(json.dumps(data.get('custom_providers', [])))
+
+ elif action == "add_p":
+ n, u, k, m = sys.argv[3:7]
+ ps = data.get('custom_providers', [])
+ if not isinstance(ps, list): ps = []
+ ps = [p for p in ps if p.get('name') != n]
+ ps.append({"name": n, "base_url": u, "api_key": k, "model": m})
+ data['custom_providers'] = ps
+ save(data)
+
+ elif action == "bulk_add":
+ n_base, u, k, models_json = sys.argv[3:7]
+ new_m_ids = json.loads(models_json)
+ ps = data.get('custom_providers', [])
+ if not isinstance(ps, list): ps = []
+ # 移除旧的同前缀条目
+ ps = [p for p in ps if not (isinstance(p, dict) and p.get('name', '').startswith(n_base + "/"))]
+ # 移除可能存在的同名根条目
+ ps = [p for p in ps if p.get('name') != n_base]
+ for m_id in new_m_ids:
+ ps.append({"name": f"{n_base}/{m_id}", "base_url": u, "api_key": k, "model": m_id})
+ data['custom_providers'] = ps
+ save(data)
+
+ elif action == "del_p":
+ n = sys.argv[3]
+ ps = data.get('custom_providers', [])
+ if isinstance(ps, list):
+ data['custom_providers'] = [p for p in ps if p.get('name') != n and not p.get('name', '').startswith(n + "/")]
+ save(data)
+
+ elif action == "list_groups":
+ ps = data.get('custom_providers', [])
+ groups = []
+ seen = set()
+ for p in (ps if isinstance(ps, list) else []):
+ name = p.get('name', '')
+ g = name.split('/')[0] if '/' in name else name
+ if g and g not in seen:
+ seen.add(g)
+ cnt = sum(1 for x in ps if x.get('name', '') == g or x.get('name', '').startswith(g + '/'))
+ groups.append({"name": g, "count": cnt})
+ print(json.dumps(groups))
+
+ elif action == "list_groups_latency":
+ import threading, urllib.request, time
+ ps = data.get('custom_providers', [])
+ groups = {}
+ for p in (ps if isinstance(ps, list) else []):
+ name = p.get('name', '')
+ g = name.split('/')[0] if '/' in name else name
+ if g not in groups:
+ groups[g] = {'name': g, 'base_url': p.get('base_url', ''), 'api_key': p.get('api_key', ''), 'count': 0}
+ groups[g]['count'] += 1
+ results = {}
+ def worker(g, url, key):
+ if not url or not (url.startswith('http://') or url.startswith('https://')):
+ results[g] = "N/A"
+ return
+ start = time.time()
+ try:
+ url = url.rstrip('/') + '/models'
+ req = urllib.request.Request(url, headers={'Authorization': f'Bearer {key}'} if key else {})
+ with urllib.request.urlopen(req, timeout=1.5) as r:
+ r.read()
+ results[g] = f"{int((time.time() - start) * 1000)}ms"
+ except urllib.error.HTTPError:
+ results[g] = f"{int((time.time() - start) * 1000)}ms"
+ except Exception:
+ results[g] = "timeout"
+ threads = []
+ for g, info in groups.items():
+ t = threading.Thread(target=worker, args=(g, info['base_url'], info['api_key']))
+ t.start()
+ threads.append(t)
+ for t in threads:
+ t.join()
+ out = []
+ for g, info in groups.items():
+ out.append({'name': g, 'base_url': info['base_url'], 'count': info['count'], 'latency': results.get(g, 'N/A')})
+ print(json.dumps(out))
+ elif action == "switch":
+ n, u, k, m = sys.argv[3:7]
+ data['model'] = {"default": m, "provider": "custom", "base_url": u, "api_key": k}
+ save(data)
+
+except Exception as e:
+ # 确保出错时返回合法的 JSON 避免 Bash 报错
+ print(json.dumps([]))
+ sys.exit(1)
+EOF
+}
+
+install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+ echo -e "${YELLOW}正在安装 gum (交互式选择器)...${NC}"
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg 2>/dev/null
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update -qq && apt install -y -qq gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then dnf install -y gum; else yum install -y gum; fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive install gum
+ fi
+}
+
+hermes_model_probe() {
+ local target_model="$1"
+ local ps_json="$2"
+ local probe_timeout=15
+ local provider_name request_model base_url api_key
+
+ # 从 custom_providers 中查找对应条目
+ provider_name="$target_model"
+ local entry=$(echo "$ps_json" | jq -c --arg n "$provider_name" '.[] | select(.name == $n)' 2>/dev/null)
+ if [ -z "$entry" ]; then
+ HERMES_PROBE_STATUS="ERROR"
+ HERMES_PROBE_MESSAGE="未找到模型配置"
+ HERMES_PROBE_LATENCY="-"
+ HERMES_PROBE_REPLY="-"
+ return 1
+ fi
+
+ base_url=$(echo "$entry" | jq -r .base_url)
+ api_key=$(echo "$entry" | jq -r .api_key)
+ request_model=$(echo "$entry" | jq -r .model)
+ base_url="${base_url%/}"
+
+ local tmp_response
+ tmp_response=$(mktemp)
+
+ # 使用 Python 探测,精确计时
+ local probe_result
+ probe_result=$(python3 - "$base_url" "$api_key" "$request_model" "$tmp_response" "$probe_timeout" <<'PYEOF'
+import sys, time, json
+try:
+ import urllib.request, urllib.error
+except ImportError:
+ print("1|0|0")
+ sys.exit(0)
+
+base_url, api_key, model, resp_path, timeout = sys.argv[1:6]
+timeout = int(timeout)
+url = base_url + "/chat/completions"
+payload = json.dumps({"model": model, "messages": [{"role": "user", "content": "hi"}], "temperature": 0, "max_tokens": 16}).encode()
+req = urllib.request.Request(url, data=payload, headers={
+ "Content-Type": "application/json",
+ "Authorization": f"Bearer {api_key}",
+}, method="POST")
+
+start = time.time()
+body = b""
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, "status", 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, "code", 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode("utf-8", errors="replace")
+ exit_code = 1
+
+elapsed = int((time.time() - start) * 1000)
+with open(resp_path, "wb") as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYEOF
+)
+
+ local p_exit p_http p_latency
+ p_exit=${probe_result%%|*}
+ p_http=${probe_result#*|}
+ p_http=${p_http%%|*}
+ p_latency=${probe_result##*|}
+
+ # 提取回复摘要
+ local reply_preview
+ reply_preview=$(python3 - "$tmp_response" <<'PYEOF'
+import json, sys
+from pathlib import Path
+raw = Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").strip()
+reply = ""
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get("choices") or []
+ if choices and isinstance(choices[0], dict):
+ msg = choices[0].get("message") or {}
+ if isinstance(msg, dict):
+ reply = msg.get("content") or ""
+ if not reply:
+ for key in ("error", "message", "detail"):
+ v = data.get(key)
+ if isinstance(v, str) and v.strip():
+ reply = v.strip(); break
+ if isinstance(v, dict):
+ n = v.get("message")
+ if isinstance(n, str) and n.strip():
+ reply = n.strip(); break
+ except:
+ reply = raw
+reply = " ".join(str(reply).split())[:120]
+print(reply if reply else "(空返回)")
+PYEOF
+)
+ rm -f "$tmp_response"
+
+ if [ "$p_exit" = "0" ] && [ "$p_http" -ge 200 ] 2>/dev/null && [ "$p_http" -lt 300 ] 2>/dev/null; then
+ HERMES_PROBE_STATUS="OK"
+ HERMES_PROBE_MESSAGE="HTTP ${p_http}"
+ HERMES_PROBE_LATENCY="${p_latency}ms"
+ HERMES_PROBE_REPLY="$reply_preview"
+ return 0
+ else
+ HERMES_PROBE_STATUS="FAIL"
+ HERMES_PROBE_MESSAGE="HTTP ${p_http:-0} / exit ${p_exit:-1}"
+ HERMES_PROBE_LATENCY="${p_latency:-?}ms"
+ HERMES_PROBE_REPLY="$reply_preview"
+ return 1
+ fi
+}
+
+hermes_probe_status_line() {
+ local status_text="$1"
+ local color_ok='\033[32m' color_fail='\033[31m' reset='\033[0m'
+ if [ "$status_text" = "可用" ]; then
+ printf "%b最小检测结果:%s%b\n" "$color_ok" "$status_text" "$reset"
+ else
+ printf "%b最小检测结果:%s%b\n" "$color_fail" "$status_text" "$reset"
+ fi
+}
+
+sync_single_api_provider_models() {
+ local provider_name="$1"
+ local ps_json="$2"
+
+ local entry base_url api_key m_json m_list_str old_list added_list deleted_list
+ entry=$(echo "$ps_json" | jq -c --arg n "$provider_name" '[.[] | select(.name == $n or (.name | startswith($n + "/")))] | .[0] // empty' 2>/dev/null)
+ if [ -z "$entry" ] || [ "$entry" = "null" ]; then
+ echo -e "${RED}❌ $provider_name: 未找到供应商配置${NC}"
+ return 1
+ fi
+
+ base_url=$(echo "$entry" | jq -r '.base_url // empty')
+ api_key=$(echo "$entry" | jq -r '.api_key // empty')
+ base_url="${base_url%/}"
+ if [ -z "$base_url" ]; then
+ echo -e "${RED}❌ $provider_name: Base URL 为空${NC}"
+ return 1
+ fi
+
+ m_json=$(curl -s -m 20 -H "Authorization: Bearer ${api_key}" "$base_url/models")
+ m_list_str=$(echo "$m_json" | jq -r '.data[]?.id' 2>/dev/null | sed '/^$/d' | sort -u)
+ if [ -z "$m_list_str" ]; then
+ echo -e "${RED}❌ $provider_name: 无法获取模型列表${NC}"
+ return 1
+ fi
+
+ old_list=$(echo "$ps_json" | jq -r --arg n "$provider_name" '
+ .[]
+ | select(.name == $n or (.name | startswith($n + "/")))
+ | if .name == $n then (.model // empty) else ((.name | sub("^" + $n + "/"; "")) // .model // empty) end
+ ' 2>/dev/null | sed '/^$/d' | sort -u)
+
+ added_list=$(comm -13 <(printf '%s\n' "$old_list") <(printf '%s\n' "$m_list_str"))
+ deleted_list=$(comm -23 <(printf '%s\n' "$old_list") <(printf '%s\n' "$m_list_str"))
+
+ local added_count deleted_count current_count m_json_list
+ added_count=$(printf '%s\n' "$added_list" | sed '/^$/d' | wc -l | tr -d ' ')
+ deleted_count=$(printf '%s\n' "$deleted_list" | sed '/^$/d' | wc -l | tr -d ' ')
+ current_count=$(printf '%s\n' "$m_list_str" | sed '/^$/d' | wc -l | tr -d ' ')
+
+ m_json_list=$(printf '%s\n' "$m_list_str" | jq -R . | jq -s -c .)
+ config_tool bulk_add "$provider_name" "$base_url" "$api_key" "$m_json_list"
+
+ echo -e "${GREEN}✅ $provider_name: 新增 $added_count 个,删除 $deleted_count 个,当前 $current_count 个${NC}"
+ if [ "$added_count" -gt 0 ]; then
+ echo -e "${GREEN}+ 新增模型($added_count):${NC}"
+ printf '%s\n' "$added_list" | sed '/^$/d' | sed 's/^/ + /'
+ fi
+ if [ "$deleted_count" -gt 0 ]; then
+ echo -e "${YELLOW}- 删除模型($deleted_count):${NC}"
+ printf '%s\n' "$deleted_list" | sed '/^$/d' | sed 's/^/ - /'
+ fi
+ return 0
+}
+
+sync_api_provider_models() {
+ local target_provider="$1"
+ local ps_json groups_json g_count synced_count failed_count provider_name
+
+ ps_json=$(config_tool list_p)
+ if [ "$(echo "$ps_json" | jq '. | length' 2>/dev/null)" -eq 0 ] 2>/dev/null; then
+ echo -e "${RED}无 API 配置! 请先添加供应商。${NC}"
+ return 1
+ fi
+
+ groups_json=$(config_tool list_groups)
+ g_count=$(echo "$groups_json" | jq '. | length' 2>/dev/null)
+ if [ "$g_count" -eq 0 ] 2>/dev/null || [ -z "$g_count" ]; then
+ echo -e "${RED}无 API 供应商分组可同步。${NC}"
+ return 1
+ fi
+
+ synced_count=0
+ failed_count=0
+ if [ -n "$target_provider" ]; then
+ if sync_single_api_provider_models "$target_provider" "$ps_json"; then
+ synced_count=$((synced_count + 1))
+ else
+ failed_count=$((failed_count + 1))
+ fi
+ else
+ while read -r provider_name; do
+ [ -z "$provider_name" ] && continue
+ ps_json=$(config_tool list_p)
+ if sync_single_api_provider_models "$provider_name" "$ps_json"; then
+ synced_count=$((synced_count + 1))
+ else
+ failed_count=$((failed_count + 1))
+ fi
+ done < <(echo "$groups_json" | jq -r '.[].name')
+ fi
+
+ if [ "$failed_count" -eq 0 ]; then
+ echo -e "${GREEN}✅ API 供应商模型列表同步完成并已写入配置${NC}"
+ else
+ echo -e "${YELLOW}⚠️ API 供应商模型列表同步完成:成功 $synced_count 个,失败 $failed_count 个${NC}"
+ fi
+}
+
+api_management_submenu() {
+ while true; do
+ clear
+ info=$(config_tool get_info)
+ echo -e "${BLUE}=======================================${NC}"
+ echo -e " ${PURPLE}API & 模型管理 (OpenClaw 风格)${NC}"
+ echo -e "${BLUE}=======================================${NC}"
+ echo -e "${CYAN}当前激活模型:${NC} ${GREEN}$(echo $info | jq -r .m)${NC}"
+ echo -e "---------------------------------------"
+ echo -e "${CYAN}已配置 API 列表:${NC}"
+ local groups_lat_json
+ groups_lat_json=$(config_tool list_groups_latency)
+ if [ "$(echo "$groups_lat_json" | jq '. | length' 2>/dev/null)" -eq 0 ] 2>/dev/null || [ -z "$groups_lat_json" ]; then
+ echo -e " ${YELLOW}(暂无配置)${NC}"
+ else
+ while read -r row; do
+ local g_name g_url g_count g_latency lat_color lat_num
+ g_name=$(echo "$row" | jq -r .name)
+ g_url=$(echo "$row" | jq -r .base_url)
+ g_count=$(echo "$row" | jq -r .count)
+ g_latency=$(echo "$row" | jq -r .latency)
+ lat_color="${GREEN}"
+ if [ "$g_latency" = "timeout" ] || [ "$g_latency" = "N/A" ]; then
+ lat_color="${RED}"
+ elif [[ "$g_latency" =~ ^[0-9]+ms$ ]]; then
+ lat_num=$(echo "$g_latency" | tr -d 'ms')
+ if [ "$lat_num" -gt 800 ]; then
+ lat_color="${RED}"
+ elif [ "$lat_num" -gt 300 ]; then
+ lat_color="${YELLOW}"
+ fi
+ fi
+ echo -e " ● [${g_name}] (${g_count} 个模型) | 延迟: ${lat_color}${g_latency}${NC} | ${g_url}"
+ done < <(echo "$groups_lat_json" | jq -c '.[]')
+ fi
+ echo -e "---------------------------------------"
+ echo -e "1. ${YELLOW}切换模型 (带测速)${NC}"
+ echo -e "2. 添加 API 供应商 (自动同步)${NC}"
+ echo -e "3. ${YELLOW}同步 API 供应商模型列表${NC}"
+ echo -e "4. 删除 API 供应商"
+ echo -e "0. 返回主菜单"
+ echo -e "---------------------------------------"
+ read -p "选择序号: " sub_choice
+ case "$sub_choice" in
+ 1)
+ local orange="#FF8C00"
+ local ps_json models_list model_count default_model selected_model confirm_switch
+
+ ps_json=$(config_tool list_p)
+ model_count=$(echo "$ps_json" | jq '. | length')
+
+ if [ "$model_count" -eq 0 ] 2>/dev/null || [ -z "$model_count" ]; then
+ echo -e "${RED}无 API 配置! 请先添加供应商。${NC}"
+ sleep 1
+ continue
+ fi
+
+ # 构建带编号的模型列表
+ models_list=$(echo "$ps_json" | jq -r '.[].name' | awk '{print "(" NR ") " $0}')
+ default_model=$(config_tool get_info | jq -r .m)
+
+ while true; do
+ clear
+ install_gum
+
+ # 若 gum 不可用,降级为手动输入
+ if ! command -v gum >/dev/null 2>&1; then
+ echo "--- 模型管理 ---"
+ echo "当前可用模型:"
+ echo "$models_list"
+ echo "当前默认:${default_model}"
+ echo "----------------"
+ read -e -p "请输入模型编号或名称 (输入 0 退出): " selected_model
+
+ if [ "$selected_model" = "0" ]; then
+ break
+ fi
+ if [ -z "$selected_model" ]; then
+ echo "错误:不能为空,请重试。"
+ sleep 1
+ continue
+ fi
+ # 如果输入的是纯数字,从列表中取名称
+ if [[ "$selected_model" =~ ^[0-9]+$ ]]; then
+ selected_model=$(echo "$ps_json" | jq -r --argjson i "$((selected_model-1))" '.[$i].name // empty')
+ if [ -z "$selected_model" ]; then
+ echo "序号无效,请重试。"
+ sleep 1
+ continue
+ fi
+ fi
+ else
+ # gum 模式 — 完全复刻 openclaw 风格
+ gum style --foreground "$orange" --bold "模型管理"
+ gum style --foreground "$orange" "可用模型:${model_count}"
+ gum style --foreground "$orange" "当前默认:${default_model}"
+ echo ""
+ gum style --faint "↑↓ 选择 / 输入搜索 / Enter 测试 / Esc 退出"
+ echo ""
+
+ selected_model=$(echo "$models_list" | gum filter \
+ --placeholder "搜索模型(如 cli-api/gpt-4o)" \
+ --prompt "选择模型 > " \
+ --indicator "➜ " \
+ --prompt.foreground "$orange" \
+ --indicator.foreground "$orange" \
+ --cursor-text.foreground "$orange" \
+ --match.foreground "$orange" \
+ --header "" \
+ --height 35)
+
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+ fi
+
+ # 去掉编号前缀 "(N) "
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
+
+ echo ""
+ echo "正在检测模型: $selected_model"
+ if hermes_model_probe "$selected_model" "$ps_json"; then
+ hermes_probe_status_line "可用"
+ else
+ hermes_probe_status_line "不可用"
+ fi
+ echo "状态:$HERMES_PROBE_MESSAGE"
+ echo "延迟:$HERMES_PROBE_LATENCY"
+ echo "摘要:$HERMES_PROBE_REPLY"
+ echo ""
+
+ printf "是否切换到该模型?[y/N,Esc 返回列表]: "
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'\x1b' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
+
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "已返回模型选择列表。"
+ sleep 1
+ continue
+ fi
+
+ # 执行切换
+ local entry_data
+ entry_data=$(echo "$ps_json" | jq -c --arg n "$selected_model" '.[] | select(.name == $n)')
+ local sw_u sw_k sw_m
+ sw_u=$(echo "$entry_data" | jq -r .base_url)
+ sw_k=$(echo "$entry_data" | jq -r .api_key)
+ sw_m=$(echo "$entry_data" | jq -r .model)
+
+ echo "正在切换模型为: $selected_model ..."
+ config_tool switch "$selected_model" "$sw_u" "$sw_k" "$sw_m"
+
+ # 重启 gateway
+ echo -e "${YELLOW}正在重启 Gateway...${NC}"
+ hermes gateway stop >/dev/null 2>&1
+ hermes gateway start >/dev/null 2>&1
+ echo -e "${GREEN}✅ 模型已切换为: $sw_m${NC}"
+ sleep 2
+ break
+ done
+ ;;
+ 2)
+ echo -e "${CYAN}--- 添加新 API 供应商 ---${NC}"
+ read -p "请输入供应商名称 (如: DeepSeek): " n
+ [ -z "$n" ] && continue
+ read -p "请输入 Base URL (如: https://api.deepseek.com/v1): " u
+ [ -z "$u" ] && continue
+ u="${u%/}"
+ echo -ne "${YELLOW}请输入 API Key (输入隐藏): ${NC}"
+ read -s k
+ echo ""
+ [ -z "$k" ] && continue
+
+ echo -e "${YELLOW}🔍 正在获取完整模型列表...${NC}"
+ m_json=$(curl -s -m 10 -H "Authorization: Bearer $k" "$u/models")
+ # 提取所有 ID
+ m_list_str=$(echo "$m_json" | jq -r '.data[].id' 2>/dev/null | sort)
+
+ if [ -n "$m_list_str" ]; then
+ # 转换为数组
+ m_array=()
+ while read -r line; do m_array+=("$line"); done <<< "$m_list_str"
+ m_count=${#m_array[@]}
+
+ echo -e "${GREEN}✅ 发现 $m_count 个模型。请选择一个作为当前默认:${NC}"
+ PS3="请输入序号: "
+ select m_default in "${m_array[@]}"; do
+ [ -n "$m_default" ] && break
+ done
+
+ echo -e "---------------------------------------"
+ read -p "是否同时添加该供应商的所有 $m_count 个模型?(y/N): " bulk_confirm
+ if [[ "$bulk_confirm" =~ ^[Yy]$ ]]; then
+ # 转换数组为 JSON
+ m_json_list=$(echo "$m_list_str" | jq -R . | jq -s -c .)
+ config_tool bulk_add "$n" "$u" "$k" "$m_json_list"
+ config_tool switch "$n/$m_default" "$u" "$k" "$m_default"
+ echo -e "${GREEN}✅ 已全量导入 $m_count 个模型。${NC}"
+ else
+ config_tool add_p "$n" "$u" "$k" "$m_default"
+ echo -e "${GREEN}✅ 已添加单个模型: $m_default${NC}"
+ fi
+ else
+ echo -e "${RED}❌ 无法获取列表。${NC}"
+ read -p "请手动输入模型 ID: " m_manual
+ [ -n "$m_manual" ] && config_tool add_p "$n" "$u" "$k" "$m_manual"
+ fi
+ sleep 2
+ ;;
+ 3)
+ echo -e "${CYAN}--- 同步 API 供应商模型列表 ---${NC}"
+ echo -e "${CYAN}已配置的供应商分组:${NC}"
+ groups_json=$(config_tool list_groups)
+ g_count=$(echo "$groups_json" | jq '. | length' 2>/dev/null)
+ if [ "$g_count" -eq 0 ] 2>/dev/null || [ -z "$g_count" ]; then
+ echo -e " ${YELLOW}(暂无配置)${NC}"
+ sleep 1
+ continue
+ fi
+ echo "$groups_json" | jq -r '.[] | " ● \(.name) (\(.count) 个模型)"'
+ echo ""
+ read -p "请输入要同步的 API 名称(provider),直接回车同步全部: " sync_provider
+ sync_api_provider_models "$sync_provider"
+ echo ""
+ read -p "按回车键继续..."
+ ;;
+ 4)
+ echo -e "${CYAN}已配置的供应商分组:${NC}"
+ groups_json=$(config_tool list_groups)
+ g_count=$(echo "$groups_json" | jq '. | length')
+ if [ "$g_count" -eq 0 ]; then
+ echo -e " ${YELLOW}(暂无配置)${NC}"
+ sleep 1
+ continue
+ fi
+ # 列出供应商分组
+ g_names=()
+ while read -r row; do
+ g_name=$(echo "$row" | jq -r .name)
+ g_cnt=$(echo "$row" | jq -r .count)
+ g_names+=("$g_name")
+ echo -e " ${GREEN}${#g_names[@]}.${NC} $g_name (${g_cnt} 个模型)"
+ done < <(echo "$groups_json" | jq -c '.[]')
+ echo -e " ${GREEN}0.${NC} 取消"
+ read -p "选择要删除的供应商序号: " d_idx
+ if [ "$d_idx" == "0" ] || [ -z "$d_idx" ]; then continue; fi
+ d_name="${g_names[$((d_idx-1))]}"
+ if [ -n "$d_name" ]; then
+ read -p "确认删除 [$d_name] 及其所有模型? (y/N): " del_confirm
+ if [[ "$del_confirm" =~ ^[Yy]$ ]]; then
+ config_tool del_p "$d_name"
+ echo -e "${RED}🗑️ 已删除 $d_name${NC}"
+ sleep 1
+ fi
+ fi
+ ;;
+ 0) break ;;
+ esac
+ done
+}
+check_installed() {
+ if command -v hermes >/dev/null 2>&1; then return 0; else return 1; fi
+}
+
+# 获取版本号(优先直接读取安装元数据,避免启动 hermes CLI 导致菜单变慢)
+get_version() {
+ if ! check_installed; then
+ return
+ fi
+
+ local hermes_bin python_bin venv_dir metadata version
+ hermes_bin="$(command -v hermes 2>/dev/null)"
+
+ # pip/venv 生成的 hermes 入口第一行通常指向 venv/bin/python3。
+ # 直接读 dist-info/METADATA,比执行 `hermes --version` 更快。
+ if [ -n "$hermes_bin" ] && [ -r "$hermes_bin" ]; then
+ python_bin="$(sed -n '1s/^#!//p' "$hermes_bin" 2>/dev/null)"
+ if [ -n "$python_bin" ] && [ -x "$python_bin" ]; then
+ venv_dir="$(dirname "$(dirname "$python_bin")")"
+ for metadata in "$venv_dir"/lib/python*/site-packages/hermes_agent-*.dist-info/METADATA; do
+ [ -r "$metadata" ] || continue
+ version="$(sed -n 's/^Version: //p' "$metadata" 2>/dev/null | sed -n '1p')"
+ if [ -n "$version" ]; then
+ echo "Hermes Agent v${version#v}"
+ return
+ fi
+ done
+ fi
+ fi
+
+ # 兜底:兼容非标准安装方式。
+ hermes --version 2>/dev/null | sed -n '1p'
+}
+
+# 提取语义版本号,例如 v0.13.0 / 0.13.0
+extract_semver() {
+ echo "$1" | grep -Eo 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -n 1
+}
+
+# 比较两个版本号:$1 < $2 返回 0
+version_lt() {
+ [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n 1)" != "$2" ] && [ "$1" != "$2" ]
+}
+
+# 获取 PyPI 最新版本:优先读缓存,过期后后台刷新,避免拖慢主菜单显示
+get_latest_version() {
+ local cache_dir cache_file lock_dir now cache_mtime lock_mtime ttl lock_ttl version
+ cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/hermes-manager"
+ cache_file="$cache_dir/hermes-agent-latest-version"
+ lock_dir="$cache_dir/hermes-agent-latest-version.lock"
+ ttl=21600 # 6 小时内不重复联网检查
+ lock_ttl=300 # 后台刷新失败时,5 分钟内不重复启动刷新任务
+ now="$(date +%s 2>/dev/null || echo 0)"
+
+ mkdir -p "$cache_dir" 2>/dev/null || true
+
+ # 缓存未过期:直接返回,菜单无网络等待。
+ if [ -r "$cache_file" ]; then
+ cache_mtime="$(stat -c %Y "$cache_file" 2>/dev/null || echo 0)"
+ if [ $((now - cache_mtime)) -lt "$ttl" ]; then
+ sed -n '1p' "$cache_file" 2>/dev/null
+ return
+ fi
+ # 缓存过期也先返回旧值,刷新放后台做,避免卡界面。
+ version="$(sed -n '1p' "$cache_file" 2>/dev/null)"
+ fi
+
+ # 后台刷新:用锁避免每次打开菜单都发起 PyPI 请求。
+ if mkdir "$lock_dir" 2>/dev/null; then
+ (
+ python3 - "$cache_file" <<'PY' 2>/dev/null
+import json
+import sys
+import tempfile
+import urllib.request
+from pathlib import Path
+
+cache_file = Path(sys.argv[1])
+try:
+ with urllib.request.urlopen('https://pypi.org/pypi/hermes-agent/json', timeout=2) as response:
+ data = json.load(response)
+ version = (data.get('info') or {}).get('version') or ''
+ if version:
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
+ with tempfile.NamedTemporaryFile('w', dir=str(cache_file.parent), delete=False) as f:
+ f.write('v' + version.lstrip('v') + '\n')
+ tmp = f.name
+ Path(tmp).replace(cache_file)
+except Exception:
+ pass
+PY
+ rmdir "$lock_dir" 2>/dev/null || true
+ ) >/dev/null 2>&1 &
+ elif [ -d "$lock_dir" ]; then
+ lock_mtime="$(stat -c %Y "$lock_dir" 2>/dev/null || echo "$now")"
+ if [ $((now - lock_mtime)) -gt "$lock_ttl" ]; then
+ rmdir "$lock_dir" 2>/dev/null || true
+ fi
+ fi
+
+ # 首次无缓存时不输出,不阻塞菜单;下次打开菜单会读到后台刷新结果。
+ [ -n "$version" ] && echo "$version"
+}
+
+# 检查是否有新版本
+get_update_notice() {
+ if ! check_installed; then
+ return
+ fi
+
+ local current_version latest_version current_plain latest_plain
+ current_version="$(extract_semver "$(get_version)")"
+ latest_version="$(extract_semver "$(get_latest_version)")"
+ current_plain="${current_version#v}"
+ latest_plain="${latest_version#v}"
+
+ if [ -n "$current_plain" ] && [ -n "$latest_plain" ] && version_lt "$current_plain" "$latest_plain"; then
+ echo -e " ${YELLOW}有新版本 ${latest_version^^}${NC}"
+ fi
+}
+
+# 获取网关状态
+get_gateway_status() {
+ if ! check_installed; then
+ echo -e "${RED}未安装${NC}"
+ return
+ fi
+
+ # 优先检查 Hermes 安装的 user systemd 服务,速度快且比扫进程更准。
+ if command -v systemctl >/dev/null 2>&1; then
+ case "$(systemctl --user is-active hermes-gateway.service 2>/dev/null)" in
+ active)
+ echo -e "${GREEN}运行中${NC}$(get_update_notice)"
+ return
+ ;;
+ inactive|failed|activating|deactivating)
+ echo -e "${RED}已停止${NC}$(get_update_notice)"
+ return
+ ;;
+ esac
+ fi
+
+ # 兜底:兼容非 systemd 或手动启动的 gateway。
+ if pgrep -u "$(id -u)" -f "(^|[[:space:]])(([^[:space:]]*/)?python[0-9.]*[[:space:]]+-m[[:space:]]+hermes_cli\.main[[:space:]]+gateway[[:space:]]+run|([^[:space:]]*/)?hermes[[:space:]]+gateway[[:space:]]+run|hermes-gateway)([[:space:]]|$)" >/dev/null 2>&1; then
+ echo -e "${GREEN}运行中${NC}$(get_update_notice)"
+ else
+ echo -e "${RED}已停止${NC}$(get_update_notice)"
+ fi
+}
+
+
+refresh_hermes_path() {
+ if ! command -v hermes >/dev/null 2>&1; then
+ if [ -d "$HOME/.hermes/hermes-agent/venv/bin" ]; then
+ export PATH="$HOME/.hermes/hermes-agent/venv/bin:$PATH"
+ fi
+ fi
+}
+
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "115" /home/docker/appno.txt || echo "115" >> /home/docker/appno.txt
+
+}
+
+
+
+# 主菜单UI
+show_menu() {
+ clear
+ echo -e "${CYAN}=================================================${NC}"
+ echo -e "${YELLOW} Hermes Agent 终端管理工具 ${NC}"
+ echo -e "${CYAN}=================================================${NC}"
+ echo -e " 运行状态 : $(get_gateway_status)"
+ echo -e " 当前版本 : $(get_version)"
+ echo -e "${CYAN}-------------------------------------------------${NC}"
+ echo -e "${GREEN}1.${NC} 安装 Hermes Agent"
+ echo -e "${GREEN}2.${NC} 启动 Gateway (消息网关/后台服务)"
+ echo -e "${GREEN}3.${NC} 停止 Gateway"
+ echo -e "${GREEN}4.${NC} API/模型管理 (提供商与模型切换)"
+ echo -e "${GREEN}5.${NC} 启动终端对话UI (Interactive Chat)"
+ echo -e "${GREEN}6.${NC} 运行初始化配置向导 (Setup Wizard)"
+ echo -e "${GREEN}7.${NC} 检查并更新 Hermes"
+ echo -e "${GREEN}8.${NC} 卸载 Hermes"
+ echo -e "${GREEN}0.${NC} 退出"
+ echo -e "${CYAN}=================================================${NC}"
+ if ! read -p " 请输入数字 [0-8]: " choice; then
+ echo -e "\n${GREEN}退出脚本。${NC}"
+ exit 0
+ fi
+ echo ""
+
+ case $choice in
+ 1)
+ echo -e "${YELLOW}开始安装 Hermes Agent...${NC}"
+ curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
+ refresh_hermes_path
+ hermes gateway install
+ hermes gateway start
+ add_app_id
+ ;;
+ 2)
+ if check_installed; then
+ echo -e "${YELLOW}正在启动 Gateway...${NC}"
+ systemctl --user start hermes-gateway
+ hermes gateway stop
+ hermes gateway start
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 3)
+ if check_installed; then
+ echo -e "${YELLOW}正在停止 Gateway...${NC}"
+ hermes gateway stop
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 4)
+ if check_installed; then
+ echo -e "${YELLOW}进入模型配置向导...${NC}"
+ api_management_submenu
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 5)
+ if check_installed; then
+ echo -e "${YELLOW}即将进入交互式终端,输入 /exit 即可退出返回。${NC}"
+ sleep 1
+ hermes
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 6)
+ if check_installed; then
+ echo -e "${YELLOW}正在启动初始化配置向导...${NC}"
+ hermes setup
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 7)
+ if check_installed; then
+ echo -e "${YELLOW}正在检查更新...${NC}"
+ hermes update
+ add_app_id
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 8)
+ if check_installed; then
+ read -p "确定要卸载 Hermes 吗?所有数据将被清除。(y/N): " confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ hermes uninstall
+ sed -i "/\b115\b/d" /home/docker/appno.txt
+ else echo "已取消。"; fi
+ else echo -e "${RED}请先安装 Hermes。${NC}"; fi
+ ;;
+ 0)
+ echo -e "${GREEN}感谢使用,再见!${NC}"
+ exit 0
+ ;;
+ *)
+ echo -e "${RED}输入错误,请重新选择。${NC}"
+ ;;
+ esac
+ echo ""
+ read -p "按回车键返回主菜单..."
+}
+
+# 主循环
+while true; do
+ show_menu
+done
diff --git a/ir/kejilion.sh b/ir/kejilion.sh
index 184aecff1..93c1fff20 100644
--- a/ir/kejilion.sh
+++ b/ir/kejilion.sh
@@ -3763,8 +3763,8 @@ new_ssh_port() {
# از فایلهای پیکربندی SSH پشتیبان تهیه کنید
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
@@ -4128,7 +4128,7 @@ dd_xitong() {
43)
send_stats "نصب ویندوز 7"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='windows 7 professional'
reboot
exit
;;
diff --git a/jp/kejilion.sh b/jp/kejilion.sh
index 89c7f4814..3c156c1c3 100644
--- a/jp/kejilion.sh
+++ b/jp/kejilion.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-sh_v="4.0.3"
+sh_v="4.5.2"
gl_hui='\e[37m'
@@ -29,12 +29,14 @@ else
gh_proxy="https://"
fi
+gh_https_url="https://"
+
}
quanju_canshu
-# コマンドを実行する関数を定義します
+# コマンドを実行する関数を定義する
run_command() {
if [ "$zhushi" -eq 0 ]; then
"$@"
@@ -45,6 +47,8 @@ run_command() {
canshu_v6() {
if grep -q '^canshu="V6"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
+ elif grep -q '^canshu="V6"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
fi
}
@@ -52,14 +56,16 @@ canshu_v6() {
CheckFirstRun_true() {
if grep -q '^permission_granted="true"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
+ elif grep -q '^permission_granted="true"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
fi
}
-# 関数の埋もポイント情報を収集し、現在のスクリプトバージョン番号、使用時間、システムバージョン、CPUアーキテクチャ、マシンの国、ユーザーが使用する関数名を記録する関数。彼らは絶対に機密情報を伴わない、安心してください!私を信じてください!
-# なぜこの関数を設計する必要があるのですか?目的は、ユーザーが使用する機能をよりよく理解し、関数をさらに最適化して、ユーザーのニーズを満たすより多くの関数を起動することです。
-# 全文の場合、send_stats関数の呼び出し場所、透明性、オープンソースを検索できます。懸念がある場合は、使用を拒否できます。
+# この機能は、機能の埋め込み情報を収集し、現在のスクリプトのバージョン番号、使用時間、システム バージョン、CPU アーキテクチャ、マシンの国、およびユーザーが使用した機能名を記録します。機密情報は含まれませんので、ご安心ください。信じてください!
+# なぜこの機能が設計されたのでしょうか?その目的は、ユーザーが使いたい機能をより深く理解し、機能をさらに最適化し、ユーザーのニーズを満たす機能をさらに投入することです。
+# send_stats 関数の呼び出し位置を全文検索できます。これは透明性があり、オープンソースです。ご不安がある場合はご利用をお断りすることも可能です。
@@ -86,6 +92,8 @@ yinsiyuanquan2() {
if grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
+elif grep -q '^ENABLE_STATS="false"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
fi
}
@@ -102,6 +110,7 @@ sed -i '/^alias k=/d' ~/.profile > /dev/null 2>&1
sed -i '/^alias k=/d' ~/.bash_profile > /dev/null 2>&1
cp -f ./kejilion.sh ~/kejilion.sh > /dev/null 2>&1
cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
@@ -111,22 +120,22 @@ CheckFirstRun_false() {
fi
}
-# ユーザーに条件に同意するように促します
+# ユーザーに規約への同意を求めるプロンプトを表示する
UserLicenseAgreement() {
clear
- echo -e "${gl_kjlan}Tech Lion Script Toolboxへようこそ${gl_bai}"
- echo "スクリプトを初めて使用して、ユーザーライセンス契約を読んで同意してください。"
- echo "ユーザーライセンス契約:https://blog.kejilion.pro/user-license-agreement/"
+ echo -e "${gl_kjlan}テクノロジー ライオン スクリプト ツールボックスへようこそ${gl_bai}"
+ echo "初めてスクリプトを使用する場合は、ユーザー使用許諾契約を読み、同意してください。"
+ echo "ユーザー使用許諾契約書: https://blog.kejilion.pro/user-license-agreement/"
echo -e "----------------------"
- read -r -p "上記の条件に同意しますか? (y/n):" user_input
+ read -e -p "上記の条件に同意しますか? (y/n):" user_input
if [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]; then
- send_stats "ライセンスの同意"
+ send_stats "ライセンス契約"
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
sed -i 's/^permission_granted="false"/permission_granted="true"/' /usr/local/bin/k
else
- send_stats "許可の拒否"
+ send_stats "許可が拒否されました"
clear
exit
fi
@@ -154,7 +163,7 @@ public_ip=$(get_public_ip)
isp_info=$(curl -s --max-time 3 http://ipinfo.io/org)
-if echo "$isp_info" | grep -Eiq 'china|mobile|unicom|telecom'; then
+if echo "$isp_info" | grep -Eiq 'CHINANET|mobile|unicom|telecom'; then
ipv4_address=$(get_local_ip)
else
ipv4_address="$public_ip"
@@ -170,13 +179,13 @@ ipv6_address=$(curl -s --max-time 1 https://v6.ipinfo.io/ip && echo)
install() {
if [ $# -eq 0 ]; then
- echo "パッケージパラメーターは提供されていません!"
+ echo "パッケージパラメータが指定されていません!"
return 1
fi
for package in "$@"; do
if ! command -v "$package" &>/dev/null; then
- echo -e "${gl_huang}インストール$package...${gl_bai}"
+ echo -e "${gl_kjlan}インストール中$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
dnf install -y epel-release
@@ -204,7 +213,7 @@ install() {
pkg update
pkg install -y "$package"
else
- echo "不明なパッケージマネージャー!"
+ echo "不明なパッケージマネージャーです!"
return 1
fi
fi
@@ -213,35 +222,45 @@ install() {
check_disk_space() {
+ local required_gb=$1
+ local path=${2:-/}
- required_gb=$1
- required_space_mb=$((required_gb * 1024))
- available_space_mb=$(df -m / | awk 'NR==2 {print $4}')
+ mkdir -p "$path"
- if [ $available_space_mb -lt $required_space_mb ]; then
- echo -e "${gl_huang}ヒント:${gl_bai}ディスクスペースが不十分です!"
- echo "現在利用可能なスペース:$((available_space_mb/1024))g"
- echo "最小需要スペース:${required_gb}G"
- echo "インストールは継続できません。ディスクスペースを掃除して、もう一度お試しください。"
- send_stats "ディスクスペースが不十分です"
+ local required_space_mb=$((required_gb * 1024))
+ local available_space_mb=$(df -m "$path" | awk 'NR==2 {print $4}')
+
+ if [ "$available_space_mb" -lt "$required_space_mb" ]; then
+ echo -e "${gl_huang}ヒント:${gl_bai}ディスク容量が足りません!"
+ echo "現在利用可能なスペース: $((available_space_mb/1024))G"
+ echo "最低限必要なスペース:${required_gb}G"
+ echo "インストールを続行できません。ディスク容量をクリアして、再試行してください。"
+ send_stats "ディスク容量が足りない"
break_end
kejilion
fi
}
+
install_dependency() {
+ switch_mirror false false
+ check_port
+ check_swap
+ prefer_ipv4
+ auto_optimize_dns
install wget unzip tar jq grep
+
}
remove() {
if [ $# -eq 0 ]; then
- echo "パッケージパラメーターは提供されていません!"
+ echo "パッケージパラメータが指定されていません!"
return 1
fi
for package in "$@"; do
- echo -e "${gl_huang}アンインストール$package...${gl_bai}"
+ echo -e "${gl_kjlan}アンインストール中$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf remove -y "$package"
elif command -v yum &>/dev/null; then
@@ -259,14 +278,14 @@ remove() {
elif command -v pkg &>/dev/null; then
pkg delete -y "$package"
else
- echo "不明なパッケージマネージャー!"
+ echo "不明なパッケージマネージャーです!"
return 1
fi
done
}
-# さまざまな分布に適したUniversal SystemCTL関数
+# さまざまなディストリビューションに適したユニバーサル systemctl 関数
systemctl() {
local COMMAND="$1"
local SERVICE_NAME="$2"
@@ -279,43 +298,43 @@ systemctl() {
}
-# サービスを再起動します
+# サービスを再起動する
restart() {
systemctl restart "$1"
if [ $? -eq 0 ]; then
- echo "$1サービスは再開されました。"
+ echo "$1サービスが再開されました。"
else
- echo "エラー:再起動$1サービスは失敗しました。"
+ echo "エラー: 再起動$1サービスが失敗しました。"
fi
}
-# サービスを開始します
+# サービス開始
start() {
systemctl start "$1"
if [ $? -eq 0 ]; then
echo "$1サービスが開始されました。"
else
- echo "エラー:開始$1サービスは失敗しました。"
+ echo "エラー: 開始$1サービスが失敗しました。"
fi
}
-# サービスを停止します
+# サービスを停止する
stop() {
systemctl stop "$1"
if [ $? -eq 0 ]; then
- echo "$1サービスは停止しました。"
+ echo "$1サービスが停止されました。"
else
- echo "エラー:停止します$1サービスは失敗しました。"
+ echo "エラー: 停止$1サービスが失敗しました。"
fi
}
-# サービスのステータスを確認します
+# サービスステータスを確認する
status() {
systemctl status "$1"
if [ $? -eq 0 ]; then
- echo "$1サービスステータスが表示されます。"
+ echo "$1サービスのステータスが表示されます。"
else
- echo "エラー:表示できません$1サービスステータス。"
+ echo "エラー: 表示できません$1サービスのステータス。"
fi
}
@@ -328,14 +347,14 @@ enable() {
/bin/systemctl enable "$SERVICE_NAME"
fi
- echo "$SERVICE_NAME電源を入れるように設定します。"
+ echo "$SERVICE_NAME起動時に自動で起動するように設定してあります。"
}
break_end() {
echo -e "${gl_lv}操作が完了しました${gl_bai}"
- echo "任意のキーを押して続行します..."
+ echo "続行するには任意のキーを押してください..."
read -n 1 -s -r -p ""
echo ""
clear
@@ -349,22 +368,22 @@ kejilion() {
-check_port() {
- install lsof
+stop_containers_or_kill_process() {
+ local port=$1
+ local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
- stop_containers_or_kill_process() {
- local port=$1
- local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
+ if [ -n "$containers" ]; then
+ docker stop $containers
+ else
+ install lsof
+ for pid in $(lsof -t -i:$port); do
+ kill -9 $pid
+ done
+ fi
+}
- if [ -n "$containers" ]; then
- docker stop $containers
- else
- for pid in $(lsof -t -i:$port); do
- kill -9 $pid
- done
- fi
- }
+check_port() {
stop_containers_or_kill_process 80
stop_containers_or_kill_process 443
}
@@ -377,23 +396,23 @@ if [ "$country" = "CN" ]; then
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
- "https://docker-0.unsee.tech",
- "https://docker.1panel.live",
- "https://registry.dockermirror.com",
- "https://docker.imgdb.de",
- "https://docker.m.daocloud.io",
- "https://hub.firefly.store",
- "https://hub.littlediary.cn",
+ "https://docker.1ms.run",
+ "https://docker.m.ixdev.cn",
"https://hub.rat.dev",
- "https://dhub.kubesre.xyz",
- "https://cjie.eu.org",
- "https://docker.1panelproxy.com",
+ "https://dockerproxy.net",
+ "https://docker-registry.nmqu.com",
+ "https://docker.amingg.com",
"https://docker.hlmirror.com",
- "https://hub.fast360.xyz",
- "https://dockerpull.cn",
- "https://cr.laoyou.ip-ddns.com",
- "https://docker.melikeme.cn",
- "https://docker.kejilion.pro"
+ "https://hub1.nat.tf",
+ "https://hub2.nat.tf",
+ "https://hub3.nat.tf",
+ "https://docker.m.daocloud.io",
+ "https://docker.kejilion.pro",
+ "https://docker.367231.xyz",
+ "https://hub.1panel.dev",
+ "https://dockerproxy.cool",
+ "https://docker.apiba.cn",
+ "https://proxy.vvvv.ee"
]
}
EOF
@@ -407,80 +426,40 @@ restart docker
}
-install_add_docker_guanfang() {
+
+linuxmirrors_install_docker() {
+
local country=$(curl -s ipinfo.io/country)
if [ "$country" = "CN" ]; then
- cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/install && chmod +x install
- sh install --mirror Aliyun
- rm -f install
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source mirrors.huaweicloud.com/docker-ce \
+ --source-registry docker.1ms.run \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
else
- curl -fsSL https://get.docker.com | sh
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source download.docker.com \
+ --source-registry registry.hub.docker.com \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
fi
-install_add_docker_cn
+install_add_docker_cn
}
install_add_docker() {
- echo -e "${gl_huang}Docker環境のインストール...${gl_bai}"
- if [ -f /etc/os-release ] && grep -q "Fedora" /etc/os-release; then
- install_add_docker_guanfang
- elif command -v dnf &>/dev/null; then
- dnf update -y
- dnf install -y yum-utils device-mapper-persistent-data lvm2
- rm -f /etc/yum.repos.d/docker*.repo > /dev/null
- country=$(curl -s ipinfo.io/country)
- arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo | tee /etc/yum.repos.d/docker-ce.repo > /dev/null
- else
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo > /dev/null
- fi
- dnf install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
- elif [ -f /etc/os-release ] && grep -q "Kali" /etc/os-release; then
- apt update
- apt upgrade -y
- apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
- rm -f /usr/share/keyrings/docker-archive-keyring.gpg
- local country=$(curl -s ipinfo.io/country)
- local arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- else
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- fi
- apt update
- apt install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
-
- elif command -v apt &>/dev/null || command -v yum &>/dev/null; then
- install_add_docker_guanfang
+ echo -e "${gl_kjlan}Docker 環境をインストールしています...${gl_bai}"
+ if command -v apt &>/dev/null || command -v yum &>/dev/null || command -v dnf &>/dev/null; then
+ linuxmirrors_install_docker
else
install docker docker-compose
install_add_docker_cn
@@ -504,47 +483,47 @@ while true; do
echo "Dockerコンテナリスト"
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
- echo "コンテナ操作"
+ echo "コンテナの運用"
echo "------------------------"
- echo "1.新しいコンテナを作成します"
+ echo "1. 新しいコンテナを作成する"
echo "------------------------"
- echo "2。指定されたコンテナを起動します。6。すべての容器を起動します"
- echo "3.指定された容器を停止します7。すべての容器を停止します"
- echo "4.指定されたコンテナ8を削除します。すべてのコンテナを削除します"
- echo "5。指定されたコンテナを再起動9。すべてのコンテナを再起動します"
+ echo "2. 指定したコンテナを起動します。 6. すべてのコンテナを起動します。"
+ echo "3. 指定したコンテナを停止します。 7. すべてのコンテナを停止します。"
+ echo "4. 指定したコンテナを削除します。 8. すべてのコンテナを削除します。"
+ echo "5. 指定したコンテナを再起動します。 9. すべてのコンテナを再起動します。"
echo "------------------------"
- echo "11。指定されたコンテナを入力します12。コンテナログを表示します"
- echo "13.コンテナネットワークを表示14。コンテナ占有を表示します"
+ echo "11. 指定したコンテナを入力します。 12. コンテナのログを表示します。"
+ echo "13. コンテナネットワークを確認します。 14. コンテナ占有率を確認します。"
echo "------------------------"
- echo "15.コンテナポートアクセスをオンにする16.コンテナポートアクセスをオフにする"
+ echo "15. コンテナ ポート アクセスを有効にする 16. コンテナ ポート アクセスを閉じる"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- send_stats "新しいコンテナを作成します"
- read -e -p "作成コマンドを入力してください:" dockername
+ send_stats "新しいコンテナを作成する"
+ read -e -p "作成コマンドを入力してください:" dockername
$dockername
;;
2)
- send_stats "指定された容器を起動します"
- read -e -p "コンテナ名(スペースで区切られた複数のコンテナ名)を入力してください。" dockername
+ send_stats "指定したコンテナを起動する"
+ read -e -p "コンテナ名を入力してください (複数のコンテナ名はスペースで区切ってください):" dockername
docker start $dockername
;;
3)
- send_stats "指定された容器を停止します"
- read -e -p "コンテナ名(スペースで区切られた複数のコンテナ名)を入力してください。" dockername
+ send_stats "指定したコンテナを停止する"
+ read -e -p "コンテナ名を入力してください (複数のコンテナ名はスペースで区切ってください):" dockername
docker stop $dockername
;;
4)
- send_stats "指定されたコンテナを削除します"
- read -e -p "コンテナ名(スペースで区切られた複数のコンテナ名)を入力してください。" dockername
+ send_stats "指定したコンテナを削除します"
+ read -e -p "コンテナ名を入力してください (複数のコンテナ名はスペースで区切ってください):" dockername
docker rm -f $dockername
;;
5)
- send_stats "指定された容器を再起動します"
- read -e -p "コンテナ名(スペースで区切られた複数のコンテナ名)を入力してください。" dockername
+ send_stats "指定したコンテナを再起動します"
+ read -e -p "コンテナ名を入力してください (複数のコンテナ名はスペースで区切ってください):" dockername
docker restart $dockername
;;
6)
@@ -556,7 +535,7 @@ while true; do
docker stop $(docker ps -q)
;;
8)
- send_stats "すべてのコンテナを削除します"
+ send_stats "すべてのコンテナを削除する"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定删除所有容器吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -565,7 +544,7 @@ while true; do
[Nn])
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -574,23 +553,23 @@ while true; do
docker restart $(docker ps -q)
;;
11)
- send_stats "コンテナを入力します"
- read -e -p "コンテナ名を入力してください:" dockername
+ send_stats "コンテナに入る"
+ read -e -p "コンテナ名を入力してください:" dockername
docker exec -it $dockername /bin/sh
break_end
;;
12)
- send_stats "コンテナログを表示します"
- read -e -p "コンテナ名を入力してください:" dockername
+ send_stats "コンテナログの表示"
+ read -e -p "コンテナ名を入力してください:" dockername
docker logs $dockername
break_end
;;
13)
- send_stats "コンテナネットワークを表示します"
+ send_stats "コンテナネットワークを表示する"
echo ""
container_ids=$(docker ps -q)
echo "------------------------------------------------------------"
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "コンテナ名" "ネットワーク名" "IPアドレス"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
local container_name=$(echo "$container_info" | awk '{print $1}')
@@ -604,14 +583,14 @@ while true; do
break_end
;;
14)
- send_stats "コンテナの占有を表示します"
+ send_stats "コンテナ占有率の表示"
docker stats --no-stream
break_end
;;
15)
- send_stats "コンテナポートアクセスを許可します"
- read -e -p "コンテナ名を入力してください:" docker_name
+ send_stats "コンテナポートへのアクセスを許可する"
+ read -e -p "コンテナ名を入力してください:" docker_name
ip_address
clear_container_rules "$docker_name" "$ipv4_address"
local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
@@ -620,8 +599,8 @@ while true; do
;;
16)
- send_stats "コンテナポートアクセスをブロックします"
- read -e -p "コンテナ名を入力してください:" docker_name
+ send_stats "コンテナポートへのアクセスをブロックする"
+ read -e -p "コンテナ名を入力してください:" docker_name
ip_address
block_container_port "$docker_name" "$ipv4_address"
local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
@@ -640,44 +619,44 @@ done
docker_image() {
while true; do
clear
- send_stats "Docker画像管理"
- echo "Docker画像リスト"
+ send_stats "Dockerイメージ管理"
+ echo "Dockerイメージリスト"
docker image ls
echo ""
echo "ミラー操作"
echo "------------------------"
- echo "1.指定された画像を取得する3。指定された画像を削除します"
- echo "2。指定された画像4を更新します。すべての画像を削除します"
+ echo "1. 指定した画像を取得 3. 指定した画像を削除"
+ echo "2. 指定した画像を更新 4. すべての画像を削除"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- send_stats "鏡を引っ張ります"
- read -e -p "ミラー名を入力してください(スペースで複数のミラー名を分離してください):" imagenames
+ send_stats "イメージをプルする"
+ read -e -p "イメージ名を入力してください (複数のイメージ名はスペースで区切ってください):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}画像を取得する:$name${gl_bai}"
+ echo -e "${gl_kjlan}画像の取得:$name${gl_bai}"
docker pull $name
done
;;
2)
- send_stats "画像を更新します"
- read -e -p "ミラー名を入力してください(スペースで複数のミラー名を分離してください):" imagenames
+ send_stats "画像を更新"
+ read -e -p "イメージ名を入力してください (複数のイメージ名はスペースで区切ってください):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}更新された画像:$name${gl_bai}"
+ echo -e "${gl_kjlan}画像の更新:$name${gl_bai}"
docker pull $name
done
;;
3)
- send_stats "ミラーを削除します"
- read -e -p "ミラー名を入力してください(スペースで複数のミラー名を分離してください):" imagenames
+ send_stats "画像の削除"
+ read -e -p "イメージ名を入力してください (複数のイメージ名はスペースで区切ってください):" imagenames
for name in $imagenames; do
docker rmi -f $name
done
;;
4)
- send_stats "すべての画像を削除します"
+ send_stats "すべての画像を削除する"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定删除所有镜像吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -686,7 +665,7 @@ while true; do
[Nn])
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -754,16 +733,16 @@ install_crontab() {
service cron start
;;
*)
- echo "サポートされていない分布:$ID"
+ echo "サポートされていないディストリビューション:$ID"
return
;;
esac
else
- echo "オペレーティングシステムを決定することはできません。"
+ echo "オペレーティング システムを特定できません。"
return
fi
- echo -e "${gl_lv}Crontabがインストールされ、Cronサービスが実行されています。${gl_bai}"
+ echo -e "${gl_lv}crontab がインストールされており、cron サービスが実行されています。${gl_bai}"
}
@@ -775,27 +754,27 @@ docker_ipv6_on() {
local CONFIG_FILE="/etc/docker/daemon.json"
local REQUIRED_IPV6_CONFIG='{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64"}'
- # 構成ファイルが存在するかどうかを確認し、ファイルが存在しない場合はファイルを作成し、デフォルト設定を書き込む
+ # 構成ファイルが存在するかどうかを確認し、存在しない場合はファイルを作成し、デフォルト設定を書き込みます
if [ ! -f "$CONFIG_FILE" ]; then
echo "$REQUIRED_IPV6_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
else
- # JQを使用して、構成ファイルの更新を処理します
+ # jq を使用して構成ファイルの更新を処理する
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # 現在の構成には既にIPv6設定があるかどうかを確認してください
+ # 現在の構成にすでに ipv6 設定があるかどうかを確認します
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq '.ipv6 // false')
- # 構成を更新し、IPv6を有効にします
+ # 構成を更新してIPv6を有効にする
if [[ "$CURRENT_IPV6" == "false" ]]; then
UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq '. + {ipv6: true, "fixed-cidr-v6": "2001:db8:1::/64"}')
else
UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq '. + {"fixed-cidr-v6": "2001:db8:1::/64"}')
fi
- # 元の構成と新しい構成を比較します
+ # 元の構成と新しい構成を比較する
if [[ "$ORIGINAL_CONFIG" == "$UPDATED_CONFIG" ]]; then
- echo -e "${gl_huang}現在、IPv6アクセスが有効になっています${gl_bai}"
+ echo -e "${gl_huang}IPv6 アクセスは現在有効です${gl_bai}"
else
echo "$UPDATED_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
@@ -810,28 +789,28 @@ docker_ipv6_off() {
local CONFIG_FILE="/etc/docker/daemon.json"
- # 構成ファイルが存在するかどうかを確認します
+ # 設定ファイルが存在するかどうかを確認する
if [ ! -f "$CONFIG_FILE" ]; then
- echo -e "${gl_hong}構成ファイルは存在しません${gl_bai}"
+ echo -e "${gl_hong}設定ファイルが存在しません${gl_bai}"
return
fi
- # 現在の構成をお読みください
+ # 現在の構成を読み取る
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # JQを使用して、構成ファイルの更新を処理します
+ # jq を使用して構成ファイルの更新を処理する
local UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq 'del(.["fixed-cidr-v6"]) | .ipv6 = false')
- # 現在のIPv6ステータスを確認してください
+ # 現在のIPv6ステータスを確認する
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq -r '.ipv6 // false')
- # 元の構成と新しい構成を比較します
+ # 元の構成と新しい構成を比較する
if [[ "$CURRENT_IPV6" == "false" ]]; then
- echo -e "${gl_huang}IPv6アクセスは現在閉じられています${gl_bai}"
+ echo -e "${gl_huang}IPv6アクセスは現在停止中です${gl_bai}"
else
echo "$UPDATED_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
- echo -e "${gl_huang}IPv6アクセスは正常に閉じられています${gl_bai}"
+ echo -e "${gl_huang}IPv6 アクセスが正常に終了しました${gl_bai}"
fi
}
@@ -870,117 +849,117 @@ iptables_open() {
open_port() {
local ports=($@) # 将传入的参数转换为数组
if [ ${#ports[@]} -eq 0 ]; then
- echo "少なくとも1つのポート番号を提供してください"
+ echo "少なくとも 1 つのポート番号を入力してください"
return 1
fi
install iptables
for port in "${ports[@]}"; do
- # 既存のクロージングルールを削除します
+ # 既存のシャットダウン ルールを削除する
iptables -D INPUT -p tcp --dport $port -j DROP 2>/dev/null
iptables -D INPUT -p udp --dport $port -j DROP 2>/dev/null
- # オープンルールを追加します
+ # オープンルールを追加
if ! iptables -C INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j ACCEPT
fi
if ! iptables -C INPUT -p udp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j ACCEPT
- echo "ポートが開かれました$port"
+ echo "ポートがオープンしました$port"
fi
done
save_iptables_rules
- send_stats "ポートが開かれました"
+ send_stats "ポートがオープンしました"
}
close_port() {
local ports=($@) # 将传入的参数转换为数组
if [ ${#ports[@]} -eq 0 ]; then
- echo "少なくとも1つのポート番号を提供してください"
+ echo "少なくとも 1 つのポート番号を入力してください"
return 1
fi
install iptables
for port in "${ports[@]}"; do
- # 既存のオープンルールを削除します
+ # 既存のオープンルールを削除する
iptables -D INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp --dport $port -j ACCEPT 2>/dev/null
- # 緊密なルールを追加します
+ # シャットダウンルールを追加する
if ! iptables -C INPUT -p tcp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j DROP
fi
if ! iptables -C INPUT -p udp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j DROP
- echo "ポートは閉じた$port"
+ echo "ポートが閉じられています$port"
fi
done
- # 既存のルールを削除する(ある場合)
+ # 既存のルール (存在する場合) を削除します。
iptables -D INPUT -i lo -j ACCEPT 2>/dev/null
iptables -D FORWARD -i lo -j ACCEPT 2>/dev/null
- # 最初に新しいルールを挿入します
+ # 最初のルールに新しいルールを挿入します
iptables -I INPUT 1 -i lo -j ACCEPT
iptables -I FORWARD 1 -i lo -j ACCEPT
save_iptables_rules
- send_stats "ポートは閉じた"
+ send_stats "ポートが閉じられています"
}
allow_ip() {
local ips=($@) # 将传入的参数转换为数组
if [ ${#ips[@]} -eq 0 ]; then
- echo "少なくとも1つのIPアドレスまたはIPセグメントを提供してください"
+ echo "少なくとも 1 つの IP アドレスまたは IP セグメントを入力してください"
return 1
fi
install iptables
for ip in "${ips[@]}"; do
- # 既存のブロッキングルールを削除します
+ # 既存のブロック ルールを削除する
iptables -D INPUT -s $ip -j DROP 2>/dev/null
- # 許可ルールを追加します
+ # 許可ルールを追加する
if ! iptables -C INPUT -s $ip -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j ACCEPT
- echo "IPをリリースしました$ip"
+ echo "リリースされたIP$ip"
fi
done
save_iptables_rules
- send_stats "IPをリリースしました"
+ send_stats "リリースされたIP"
}
block_ip() {
local ips=($@) # 将传入的参数转换为数组
if [ ${#ips[@]} -eq 0 ]; then
- echo "少なくとも1つのIPアドレスまたはIPセグメントを提供してください"
+ echo "少なくとも 1 つの IP アドレスまたは IP セグメントを入力してください"
return 1
fi
install iptables
for ip in "${ips[@]}"; do
- # 既存の許可ルールを削除します
+ # 既存の許可ルールを削除する
iptables -D INPUT -s $ip -j ACCEPT 2>/dev/null
- # ブロッキングルールを追加します
+ # ブロックルールを追加する
if ! iptables -C INPUT -s $ip -j DROP 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j DROP
- echo "IPブロック$ip"
+ echo "IPがブロックされました$ip"
fi
done
save_iptables_rules
- send_stats "IPブロック"
+ send_stats "IPがブロックされました"
}
@@ -990,7 +969,7 @@ block_ip() {
enable_ddos_defense() {
- # 防御DDOをオンにします
+ # DDoS 保護を有効にする
iptables -A DOCKER-USER -p tcp --syn -m limit --limit 500/s --limit-burst 100 -j ACCEPT
iptables -A DOCKER-USER -p tcp --syn -j DROP
iptables -A DOCKER-USER -p udp -m limit --limit 3000/s -j ACCEPT
@@ -1000,12 +979,12 @@ enable_ddos_defense() {
iptables -A INPUT -p udp -m limit --limit 3000/s -j ACCEPT
iptables -A INPUT -p udp -j DROP
- send_stats "DDOS防御をオンにします"
+ send_stats "DDoS 防御をオンにする"
}
-# DDOS防御をオフにします
+# DDoS 防御をオフにする
disable_ddos_defense() {
- # 防御DDOをオフにします
+ # DDoS 保護をオフにする
iptables -D DOCKER-USER -p tcp --syn -m limit --limit 500/s --limit-burst 100 -j ACCEPT 2>/dev/null
iptables -D DOCKER-USER -p tcp --syn -j DROP 2>/dev/null
iptables -D DOCKER-USER -p udp -m limit --limit 3000/s -j ACCEPT 2>/dev/null
@@ -1015,101 +994,94 @@ disable_ddos_defense() {
iptables -D INPUT -p udp -m limit --limit 3000/s -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp -j DROP 2>/dev/null
- send_stats "DDOS防御をオフにします"
+ send_stats "DDoS 防御をオフにする"
}
-# 国家IPルールを管理する機能
+# 国内の知財ルールを管理する機能
manage_country_rules() {
local action="$1"
- local country_code="$2"
- local ipset_name="${country_code,,}_block"
- local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
+ shift # 去掉第一个参数,剩下的全是国家代码
install ipset
- case "$action" in
- block)
- # IPSETが存在しない場合は作成します
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ for country_code in "$@"; do
+ local ipset_name="${country_code,,}_block"
+ local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
- # IPエリアファイルをダウンロードします
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "エラー:ダウンロード$country_codeIPゾーンファイルが失敗しました"
- exit 1
- fi
+ case "$action" in
+ block)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
- # IPSETにIPを追加します
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "エラー: ダウンロード$country_codeIPゾーンファイルが失敗しました"
+ continue
+ fi
- # iptablesでIPをブロックします
- iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- iptables -I OUTPUT -m set --match-set "$ipset_name" dst -j DROP
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- echo "正常にブロックされました$country_codeIPアドレス"
- rm "${country_code,,}.zone"
- ;;
+ iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- allow)
- # 許可された国のIPSETを作成する(存在しない場合)
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ echo "正常にブロックされました$country_codeIPアドレス"
+ rm "${country_code,,}.zone"
+ ;;
- # IPエリアファイルをダウンロードします
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "エラー:ダウンロード$country_codeIPゾーンファイルが失敗しました"
- exit 1
- fi
+ allow)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
+
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "エラー: ダウンロード$country_codeIPゾーンファイルが失敗しました"
+ continue
+ fi
- # 既存の国家ルールを削除します
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
- ipset flush "$ipset_name"
+ ipset flush "$ipset_name"
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- # IPSETにIPを追加します
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
- # 指定された国のIPのみが許可されています
- iptables -P INPUT DROP
- iptables -P OUTPUT DROP
- iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- iptables -A OUTPUT -m set --match-set "$ipset_name" dst -j ACCEPT
+ iptables -P INPUT DROP
+ iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- echo "正常に許可されています$country_codeIPアドレス"
- rm "${country_code,,}.zone"
- ;;
+ echo "正常に許可されました$country_codeIPアドレス"
+ rm "${country_code,,}.zone"
+ ;;
- unblock)
- # 国のiptablesルールを削除します
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
+ unblock)
+ iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- # Ipsetを破壊します
- if ipset list "$ipset_name" &> /dev/null; then
- ipset destroy "$ipset_name"
- fi
+ if ipset list "$ipset_name" &> /dev/null; then
+ ipset destroy "$ipset_name"
+ fi
- echo "正常に持ち上げられました$country_codeIPアドレスの制限"
- ;;
+ echo "正常に削除されました$country_codeIPアドレス制限"
+ ;;
- *)
- ;;
- esac
+ *)
+ echo "使用法: manage_country_rules {block|allow|unblock} "
+ ;;
+ esac
+ done
}
+
+
+
+
+
+
iptables_panel() {
root_use
install iptables
@@ -1123,35 +1095,35 @@ iptables_panel() {
echo ""
echo "ファイアウォール管理"
echo "------------------------"
- echo "1.指定されたポート2を開きます。指定されたポートを閉じます"
- echo "3.すべてのポートを開きます。4。すべてのポートを閉じます"
+ echo "1. 指定されたポートをオープンします。 2. 指定されたポートを閉じます。"
+ echo "3. すべてのポートを開く 4. すべてのポートを閉じる"
echo "------------------------"
- echo "5。IPホワイトリスト6。IPブラックリスト"
- echo "7.指定されたIPをクリアします"
+ echo "5. IP ホワイトリスト 6. IP ブラックリスト"
+ echo "7. 指定したIPをクリアします"
echo "------------------------"
- echo "11. ping 12を許可します。Pingを無効にします"
+ echo "11. PING を許可する 12. PING を無効にする"
echo "------------------------"
- echo "13。DDOS防衛を開始14。DDOS防衛をオフにします"
+ echo "13. DDOS 防御を開始します。 14. DDOS 防御をオフにします。"
echo "------------------------"
- echo "15.ブロック指定された国IP16。指定された国のIPのみが許可されます"
- echo "17.指定国でのIP制限をリリースします"
+ echo "15. 指定した国の IP をブロックする 16. 指定した国の IP のみを許可する"
+ echo "17. 指定国における知的財産制限を解除する"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- read -e -p "オープンポート番号を入力してください:" o_port
+ read -e -p "開いているポート番号を入力してください:" o_port
open_port $o_port
- send_stats "指定されたポートを開きます"
+ send_stats "指定したポートを開く"
;;
2)
- read -e -p "閉じたポート番号を入力してください:" c_port
+ read -e -p "閉じられたポート番号を入力してください:" c_port
close_port $c_port
- send_stats "指定されたポートを閉じます"
+ send_stats "指定したポートを閉じる"
;;
3)
- # すべてのポートを開きます
+ # すべてのポートを開く
current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
iptables -F
iptables -X
@@ -1164,7 +1136,7 @@ iptables_panel() {
iptables -A FORWARD -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport $current_port -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "すべてのポートを開きます"
+ send_stats "すべてのポートを開く"
;;
4)
# すべてのポートを閉じます
@@ -1185,35 +1157,35 @@ iptables_panel() {
5)
# IPホワイトリスト
- read -e -p "リリースするには、IPまたはIPセグメントを入力してください。" o_ip
+ read -e -p "許可された IP または IP セグメントを入力してください:" o_ip
allow_ip $o_ip
;;
6)
# IPブラックリスト
- read -e -p "ブロックされたIPまたはIPセグメントを入力してください:" c_ip
+ read -e -p "ブロックされた IP または IP 範囲を入力してください:" c_ip
block_ip $c_ip
;;
7)
- # 指定されたIPをクリアします
- read -e -p "クリアされたIPを入力してください:" d_ip
+ # 指定したIPをクリア
+ read -e -p "クリアされた IP を入力してください:" d_ip
iptables -D INPUT -s $d_ip -j ACCEPT 2>/dev/null
iptables -D INPUT -s $d_ip -j DROP 2>/dev/null
iptables-save > /etc/iptables/rules.v4
- send_stats "指定されたIPをクリアします"
+ send_stats "指定したIPをクリア"
;;
11)
- # pingを許可します
+ # PINGを許可する
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "pingを許可します"
+ send_stats "PINGを許可する"
;;
12)
- # pingを無効にします
+ # PINGを無効にする
iptables -D INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null
iptables -D OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT 2>/dev/null
iptables-save > /etc/iptables/rules.v4
- send_stats "pingを無効にします"
+ send_stats "PINGを無効にする"
;;
13)
enable_ddos_defense
@@ -1223,20 +1195,20 @@ iptables_panel() {
;;
15)
- read -e -p "ブロックされた国コード(CN、米国、JPなど)を入力してください。" country_code
+ read -e -p "ブロックされている国コードを入力してください (CN US JP のように、複数の国コードをスペースで区切ることができます)。" country_code
manage_country_rules block $country_code
- send_stats "許可された国$country_codeIP"
+ send_stats "国を許可する$country_codeIP"
;;
16)
- read -e -p "許可された国コード(CN、米国、JPなど)を入力してください。" country_code
+ read -e -p "許可されている国コードを入力してください (CN US JP のように、複数の国コードをスペースで区切ることができます)。" country_code
manage_country_rules allow $country_code
- send_stats "国をブロックします$country_codeIP"
+ send_stats "ブロック国$country_codeIP"
;;
17)
- read -e -p "クリアされた国コード(CN、米国、JPなど)を入力してください。" country_code
+ read -e -p "クリアされた国コードを入力してください (CN US JP のように、複数の国コードをスペースで区切ることができます)。" country_code
manage_country_rules unblock $country_code
- send_stats "国をきれいにします$country_codeIP"
+ send_stats "澄んだ国$country_codeIP"
;;
*)
@@ -1252,28 +1224,26 @@ iptables_panel() {
-
-
add_swap() {
local new_swap=$1 # 获取传入的参数
- # 現在のシステムですべてのスワップパーティションを取得します
+ # 現在のシステム内のすべてのスワップ パーティションを取得します
local swap_partitions=$(grep -E '^/dev/' /proc/swaps | awk '{print $1}')
- # 反復して、すべてのスワップパーティションを削除します
+ # すべてのスワップ パーティションを走査して削除します
for partition in $swap_partitions; do
swapoff "$partition"
wipefs -a "$partition"
mkswap -f "$partition"
done
- # /swapfileが使用されなくなったことを確認してください
+ # /swapfile が使用されていないことを確認してください
swapoff /swapfile
- # 古い /swapfileを削除します
+ # 古い /swapfile を削除する
rm -f /swapfile
- # 新しいスワップパーティションを作成します
+ # 新しいスワップ パーティションを作成する
fallocate -l ${new_swap}M /swapfile
chmod 600 /swapfile
mkswap /swapfile
@@ -1288,7 +1258,7 @@ add_swap() {
rc-update add local
fi
- echo -e "仮想メモリサイズは変更されています${gl_huang}${new_swap}${gl_bai}M"
+ echo -e "仮想メモリのサイズは次のように調整されました。${gl_huang}${new_swap}${gl_bai}M"
}
@@ -1298,7 +1268,7 @@ check_swap() {
local swap_total=$(free -m | awk 'NR==3{print $2}')
-# 仮想メモリを作成する必要があるかどうかを判断します
+# 仮想メモリを作成する必要があるかどうかを判断する
[ "$swap_total" -gt 0 ] || add_swap 1024
@@ -1314,21 +1284,21 @@ local swap_total=$(free -m | awk 'NR==3{print $2}')
ldnmp_v() {
- # nginxバージョンを取得します
+ # nginxのバージョンを取得する
local nginx_version=$(docker exec nginx nginx -v 2>&1)
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e "nginx : ${gl_huang}v$nginx_version${gl_bai}"
- # MySQLバージョンを取得します
+ # mysqlのバージョンを取得する
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local mysql_version=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SELECT VERSION();" 2>/dev/null | tail -n 1)
echo -n -e " mysql : ${gl_huang}v$mysql_version${gl_bai}"
- # PHPバージョンを取得します
+ # PHPのバージョンを取得する
local php_version=$(docker exec php php -v 2>/dev/null | grep -oP "PHP \K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e " php : ${gl_huang}v$php_version${gl_bai}"
- # Redisバージョンを取得します
+ # Redis バージョンを取得する
local redis_version=$(docker exec redis redis-server -v 2>&1 | grep -oP "v=+\K[0-9]+\.[0-9]+")
echo -e " redis : ${gl_huang}v$redis_version${gl_bai}"
@@ -1341,20 +1311,18 @@ ldnmp_v() {
install_ldnmp_conf() {
- # 必要なディレクトリとファイルを作成します
- cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/redis web/log/nginx && touch web/docker-compose.yml
+ # 必要なディレクトリとファイルを作成する
+ cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/stream.d web/redis web/log/nginx web/letsencrypt && touch web/docker-compose.yml
wget -O /home/web/nginx.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default10.conf
- wget -O /home/web/redis/valkey.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/valkey.conf
-
default_server_ssl
- # docker-compose.ymlファイルをダウンロードして置き換えます
+ # docker-compose.yml ファイルをダウンロードして置き換えます
wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
dbrootpasswd=$(openssl rand -base64 16) ; dbuse=$(openssl rand -hex 4) ; dbusepasswd=$(openssl rand -base64 8)
- # docker-compose.ymlファイルに置き換えます
+ # docker-compose.yml ファイル内で置き換えます
sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
@@ -1362,31 +1330,69 @@ install_ldnmp_conf() {
}
+update_docker_compose_with_db_creds() {
+ cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
+ if ! grep -q "letsencrypt" /home/web/docker-compose.yml; then
+ wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
-install_ldnmp() {
+ dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+
+ sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
+ fi
+
+ if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
+ sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ fi
- check_swap
+}
- cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
- if ! grep -q "network_mode" /home/web/docker-compose.yml; then
- wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
- dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
- fi
- if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
- sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- fi
+auto_optimize_dns() {
+ # 国コードを取得します (CN、US など)。
+ local country=$(curl -s ipinfo.io/country)
+
+ # 国に基づいてDNSを設定する
+ if [ "$country" = "CN" ]; then
+ local dns1_ipv4="223.5.5.5"
+ local dns2_ipv4="183.60.83.19"
+ local dns1_ipv6="2400:3200::1"
+ local dns2_ipv6="2400:da00::6666"
+ else
+ local dns1_ipv4="1.1.1.1"
+ local dns2_ipv4="8.8.8.8"
+ local dns1_ipv6="2606:4700:4700::1111"
+ local dns2_ipv6="2001:4860:4860::8888"
+ fi
+
+ set_dns
+
+
+}
+
+
+prefer_ipv4() {
+grep -q '^precedence ::ffff:0:0/96 100' /etc/gai.conf 2>/dev/null \
+ || echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
+echo "IPv4優先に切り替えました"
+send_stats "IPv4優先に切り替えました"
+}
+
+
+
+
+install_ldnmp() {
+
+ update_docker_compose_with_db_creds
cd /home/web && docker compose up -d
sleep 1
@@ -1395,11 +1401,19 @@ install_ldnmp() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- restart_ldnmp
+
+ # mysqlのチューニング
+ wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
+ docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
+ rm -rf /home/custom_mysql_config.cnf
+
+ restart_ldnmp
+ sleep 2
+
clear
- echo "LDNMP環境がインストールされています"
+ echo "LDNMP環境がインストールされている"
echo "------------------------"
ldnmp_v
@@ -1422,13 +1436,12 @@ install_certbot() {
install_ssltls() {
docker stop nginx > /dev/null 2>&1
- check_port > /dev/null 2>&1
cd ~
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ ! -f "$file_path" ]; then
local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
- local ipv6_pattern='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))))$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
if [[ ($yuming =~ $ipv4_pattern || $yuming =~ $ipv6_pattern) ]]; then
mkdir -p /etc/letsencrypt/live/$yuming/
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -1438,7 +1451,7 @@ install_ssltls() {
openssl req -x509 -key /etc/letsencrypt/live/$yuming/privkey.pem -out /etc/letsencrypt/live/$yuming/fullchain.pem -days 5475 -subj "/C=US/ST=State/L=City/O=Organization/OU=Organizational Unit/CN=Common Name"
fi
else
- docker run -it --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
fi
fi
mkdir -p /home/web/certs/
@@ -1454,12 +1467,12 @@ install_ssltls_text() {
echo -e "${gl_huang}$yuming公開鍵情報${gl_bai}"
cat /etc/letsencrypt/live/$yuming/fullchain.pem
echo ""
- echo -e "${gl_huang}$yuming秘密のキー情報${gl_bai}"
+ echo -e "${gl_huang}$yuming秘密鍵情報${gl_bai}"
cat /etc/letsencrypt/live/$yuming/privkey.pem
echo ""
- echo -e "${gl_huang}証明書ストレージパス${gl_bai}"
- echo "公開鍵:/etc/letsencrypt/live/$yuming/fullchain.pem"
- echo "秘密鍵:/etc/letsencrypt/live/$yuming/privkey.pem"
+ echo -e "${gl_huang}証明書の保存パス${gl_bai}"
+ echo "公開キー: /etc/letsencrypt/live/$yuming/fullchain.pem"
+ echo "秘密鍵: /etc/letsencrypt/live/$yuming/privkey.pem"
echo ""
}
@@ -1468,14 +1481,14 @@ install_ssltls_text() {
add_ssl() {
-echo -e "${gl_huang}SSL証明書をすばやく申請し、有効期限が切れる前に署名を自動的に更新します${gl_bai}"
+echo -e "${gl_huang}SSL 証明書をすばやく申請し、有効期限が切れる前に自動的に更新します${gl_bai}"
yuming="${1:-}"
if [ -z "$yuming" ]; then
add_yuming
fi
install_docker
install_certbot
-docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
install_ssltls_text
@@ -1484,8 +1497,8 @@ ssl_ps
ssl_ps() {
- echo -e "${gl_huang}適用された証明書の有効期限${gl_bai}"
- echo "サイト情報証明書の有効期限"
+ echo -e "${gl_huang}適用された証明書の有効期限ステータス${gl_bai}"
+ echo "サイト情報 証明書の有効期限"
echo "------------------------"
for cert_dir in /etc/letsencrypt/live/*; do
local cert_file="$cert_dir/fullchain.pem"
@@ -1524,22 +1537,79 @@ certs_status() {
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ -f "$file_path" ]; then
- send_stats "ドメイン名証明書の成功したアプリケーション"
+ send_stats "ドメイン名証明書の申請が成功しました"
else
- send_stats "ドメイン名証明書のアプリケーションは失敗しました"
- echo -e "${gl_hong}知らせ:${gl_bai}証明書申請が失敗しました。次の考えられる理由を確認して、もう一度やり直してください。"
- echo -e "1。ドメイン名スペリングエラーdomainドメイン名が正しく入力されているかどうかを確認してください"
- echo -e "2。DNS解像度の問題domainドメイン名がこのサーバーIPに対して正しく解決されたことを確認します"
- echo -e "3.ネットワーク構成の問題cloudflareワープやその他の仮想ネットワークを使用する場合は、一時的にシャットダウンしてください"
- echo -e "4。ファイアウォールの制限orポート80/443が開かれているかどうかを確認して、検証がアクセス可能であることを確認してください"
- echo -e "5.アプリケーションの数が制限を超えています➠暗号化を毎週制限(5回/ドメイン名/週)があります"
- echo -e "6.国内登録制限domainドメイン名が中国本土で登録されているかどうかを確認してください"
- break_end
- clear
- echo "もう一度展開してみてください$webname"
- add_yuming
- install_ssltls
- certs_status
+ send_stats "ドメイン名証明書の申請に失敗しました"
+ echo -e "${gl_hong}知らせ:${gl_bai}証明書の申請に失敗しました。次の考えられる理由を確認して、再試行してください。"
+ echo -e "1. ドメイン名のスペルが間違っています ➠ ドメイン名が正しく入力されているか確認してください"
+ echo -e "2. DNS 解決の問題 ➠ ドメイン名がサーバー IP に正しく解決されていることを確認します。"
+ echo -e "3. ネットワーク構成の問題 ➠ Cloudflare Warp などの仮想ネットワークを使用している場合は、一時的にシャットダウンしてください"
+ echo -e "4. ファイアウォールの制限 ➠ ポート 80/443 が開いているかどうかを確認し、アクセス可能であることを確認します。"
+ echo -e "5. アプリケーション数が制限を超えている ➠ Let's Encrypt には週制限あり (5 回/ドメイン名/週)"
+ echo -e "6. 国内登録制限 ➠ 中国本土環境の場合は、ドメイン名が登録されているかをご確認ください。"
+ echo "------------------------"
+ echo "1. 再適用 2. 既存の証明書をインポート 0. 終了"
+ echo "------------------------"
+ read -e -p "選択肢を入力してください:" sub_choice
+ case $sub_choice in
+ 1)
+ send_stats "再申請"
+ echo "もう一度デプロイしてみてください$webname"
+ add_yuming
+ install_ssltls
+ certs_status
+
+ ;;
+ 2)
+ send_stats "既存の証明書をインポートする"
+
+ # ファイルパスを定義する
+ local cert_file="/home/web/certs/${yuming}_cert.pem"
+ local key_file="/home/web/certs/${yuming}_key.pem"
+
+ mkdir -p /home/web/certs
+
+ # 1. 証明書を入力します (ECC 証明書と RSA 証明書はどちらも BEGIN CERTIFICATE で始まります)
+ echo "証明書 (CRT/PEM) の内容を貼り付けてください (Enter を 2 回押して終了します)。"
+ local cert_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$cert_content" == *"-----BEGIN"* ]] && break
+ cert_content+="${line}"$'\n'
+ done
+
+ # 2. 秘密キーを入力します (RSA、ECC、PKCS#8 と互換性があります)
+ echo "証明書の秘密キー (Private Key) の内容を貼り付けてください (Enter を 2 回押して終了します)。"
+ local key_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$key_content" == *"-----BEGIN"* ]] && break
+ key_content+="${line}"$'\n'
+ done
+
+ # 3. インテリジェントな検証
+ # 「BEGIN CERTIFICATE」と「PRIVATE KEY」を含めるだけで渡されます
+ if [[ "$cert_content" == *"-----BEGIN CERTIFICATE-----"* && "$key_content" == *"PRIVATE KEY-----"* ]]; then
+ echo -n "$cert_content" > "$cert_file"
+ echo -n "$key_content" > "$key_file"
+
+ chmod 644 "$cert_file"
+ chmod 600 "$key_file"
+
+ # 現在の証明書の種類を特定して表示します。
+ if [[ "$key_content" == *"EC PRIVATE KEY"* ]]; then
+ echo "ECC証明書が正常に保存されたことが検出されました。"
+ else
+ echo "RSA証明書が正常に保存されたことが検出されました。"
+ fi
+ auth_method="ssl_imported"
+ else
+ echo "エラー: 証明書または秘密キーの形式が無効です。"
+ certs_status
+ fi
+ ;;
+ *)
+ exit
+ ;;
+ esac
fi
}
@@ -1556,11 +1626,45 @@ fi
add_yuming() {
ip_address
- echo -e "最初にドメイン名をローカルIPに解決します。${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
- read -e -p "IPまたは解決されたドメイン名を入力してください:" yuming
+ echo -e "まず、ドメイン名をローカル IP に解決します。${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
+ read -e -p "IP または解決されたドメイン名を入力してください:" yuming
+}
+
+
+check_ip_and_get_access_port() {
+ local yuming="$1"
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ read -e -p "デフォルトで 80 を使用するには、アクセス/リスニング ポートを入力し、Enter キーを押してください。" access_port
+ access_port=${access_port:-80}
+ fi
+}
+
+
+
+update_nginx_listen_port() {
+ local yuming="$1"
+ local access_port="$2"
+ local conf="/home/web/conf.d/${yuming}.conf"
+
+ # access_port が空の場合はスキップします
+ [ -z "$access_port" ] && return 0
+
+ # すべてのリッスン行を削除します
+ sed -i '/^[[:space:]]*listen[[:space:]]\+/d' "$conf"
+
+ # サーバー { の後に新しいリッスンを挿入します
+ sed -i "/server {/a\\
+ listen ${access_port};\\
+ listen [::]:${access_port};
+" "$conf"
}
+
add_db() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbname="${dbname}"
@@ -1571,28 +1675,8 @@ add_db() {
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "CREATE DATABASE $dbname; GRANT ALL PRIVILEGES ON $dbname.* TO \"$dbuse\"@\"%\";"
}
-reverse_proxy() {
- ip_address
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
- sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$ipv4_address/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$duankou|g" /home/web/conf.d/$yuming.conf
- nginx_http_on
- docker exec nginx nginx -s reload
-}
-
-
-restart_redis() {
- rm -rf /home/web/redis/*
- docker exec redis redis-cli FLUSHALL > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory 1gb > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory-policy allkeys-lru > /dev/null 2>&1
-}
-
-
restart_ldnmp() {
- restart_redis
docker exec nginx chown -R nginx:nginx /var/www/html > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/proxy > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/fastcgi > /dev/null 2>&1
@@ -1600,7 +1684,8 @@ restart_ldnmp() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi > /dev/null 2>&1
docker exec php chown -R www-data:www-data /var/www/html > /dev/null 2>&1
docker exec php74 chown -R www-data:www-data /var/www/html > /dev/null 2>&1
- cd /home/web && docker compose restart nginx php php74
+ cd /home/web && docker compose restart
+
}
@@ -1621,8 +1706,8 @@ nginx_upgrade() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi
docker restart $ldnmp_pods > /dev/null 2>&1
- send_stats "更新します$ldnmp_pods"
- echo "更新します${ldnmp_pods}仕上げる"
+ send_stats "更新する$ldnmp_pods"
+ echo "更新する${ldnmp_pods}仕上げる"
}
@@ -1635,14 +1720,14 @@ phpmyadmin_upgrade() {
cd /home/web/
docker rm -f $ldnmp_pods > /dev/null 2>&1
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
- curl -sS -O https://raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
docker compose -f docker-compose.phpmyadmin.yml up -d
clear
ip_address
check_docker_app_ip
- echo "ログイン情報:"
- echo "ユーザー名:$dbuse"
+ echo "ログイン情報:"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
echo
send_stats "起動する$ldnmp_pods"
@@ -1655,29 +1740,29 @@ cf_purge_cache() {
local EMAIL
local ZONE_IDS
- # 構成ファイルが存在するかどうかを確認します
+ # 設定ファイルが存在するかどうかを確認する
if [ -f "$CONFIG_FILE" ]; then
- # 構成ファイルからAPI_TOKENとZONE_IDを読み取ります
+ # 構成ファイルから API_TOKEN とzone_idを読み取ります
read API_TOKEN EMAIL ZONE_IDS < "$CONFIG_FILE"
- # ゾーン_idsを配列に変換します
+ # ZONE_IDS を配列に変換する
ZONE_IDS=($ZONE_IDS)
else
- # キャッシュをクリーニングするかどうかをユーザーに促します
- read -e -p "CloudFlareのキャッシュをきれいにする必要がありますか? (y/n):" answer
+ # キャッシュをクリアするかどうかをユーザーに確認する
+ read -e -p "Cloudflareのキャッシュをクリアする必要がありますか? (y/n):" answer
if [[ "$answer" == "y" ]]; then
- echo "CF情報が保存されます$CONFIG_FILE、後でCF情報を変更できます"
- read -e -p "API_TOKENを入力してください:" API_TOKEN
- read -e -p "CFユーザー名を入力してください:" EMAIL
- read -e -p "ゾーン_id(スペースで区切られた複数)を入力してください。" -a ZONE_IDS
+ echo "CF 情報は次の場所に保存されます。$CONFIG_FILECF 情報は後で変更できます。"
+ read -e -p "API_TOKEN を入力してください:" API_TOKEN
+ read -e -p "CF ユーザー名を入力してください:" EMAIL
+ read -e -p "zone_id を入力してください (複数の場合はスペースで区切ります):" -a ZONE_IDS
mkdir -p /home/web/config/
echo "$API_TOKEN $EMAIL ${ZONE_IDS[*]}" > "$CONFIG_FILE"
fi
fi
- # 各ZONE_IDをループして、Clear Cacheコマンドを実行します
+ # 各zone_idをループし、キャッシュクリアコマンドを実行します。
for ZONE_ID in "${ZONE_IDS[@]}"; do
- echo "ゾーン_idのキャッシュのクリア:$ZONE_ID"
+ echo "zone_id のキャッシュをクリアします:$ZONE_ID"
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $API_TOKEN" \
@@ -1691,38 +1776,37 @@ cf_purge_cache() {
web_cache() {
- send_stats "サイトキャッシュをクリーンアップします"
+ send_stats "サイトキャッシュをクリアする"
cf_purge_cache
cd /home/web && docker compose restart
- restart_redis
}
web_del() {
- send_stats "サイトデータを削除します"
+ send_stats "サイトデータを削除する"
yuming_list="${1:-}"
if [ -z "$yuming_list" ]; then
- read -e -p "サイトデータを削除するには、ドメイン名を入力してください(複数のドメイン名がスペースで区切られています):" yuming_list
+ read -e -p "サイト データを削除するには、ドメイン名を入力してください (複数のドメイン名はスペースで区切ります)。" yuming_list
if [[ -z "$yuming_list" ]]; then
return
fi
fi
for yuming in $yuming_list; do
- echo "ドメイン名の削除:$yuming"
+ echo "ドメイン名が削除されています:$yuming"
rm -r /home/web/html/$yuming > /dev/null 2>&1
rm /home/web/conf.d/$yuming.conf > /dev/null 2>&1
rm /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
rm /home/web/certs/${yuming}_cert.pem > /dev/null 2>&1
- # ドメイン名をデータベース名に変換します
+ # ドメイン名をデータベース名に変換する
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
- # エラーを避けるために、データベースを削除する前にデータベースが存在するかどうかを確認します
- echo "データベースの削除:$dbname"
+ # エラーを避けるために、データベースを削除する前にデータベースが存在するかどうかを確認してください。
+ echo "データベースを削除しています:$dbname"
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE ${dbname};" > /dev/null 2>&1
done
@@ -1738,23 +1822,23 @@ nginx_waf() {
wget -O /home/web/nginx.conf "${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf"
fi
- # モードパラメーターに従ってWAFをオンまたはオフにすることにしました
+ # モードパラメータに従ってWAFをオンにするかオフにするかを決定します。
if [ "$mode" == "on" ]; then
- # WAFをオンにしてください:コメントを削除します
+ # WAF をオンにする: コメントを削除する
sed -i 's|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)# modsecurity on;|\1modsecurity on;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)# modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|\1modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|' /home/web/nginx.conf > /dev/null 2>&1
elif [ "$mode" == "off" ]; then
- # WAFを閉じる:コメントを追加します
+ # WAF をオフにする: コメントを追加する
sed -i 's|^load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)modsecurity on;|\1# modsecurity on;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|\1# modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|' /home/web/nginx.conf > /dev/null 2>&1
else
- echo "無効なパラメーター:「オン」または「オフ」を使用します"
+ echo "無効な引数: 'on' または 'off' を使用してください"
return 1
fi
- # nginx画像を確認し、状況に応じてそれらを処理します
+ # nginx イメージを確認し、それに応じて処理します
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1768,7 +1852,7 @@ check_waf_status() {
if grep -q "^\s*#\s*modsecurity on;" /home/web/nginx.conf; then
waf_status=""
elif grep -q "modsecurity on;" /home/web/nginx.conf; then
- waf_status=" WAF已开启"
+ waf_status="WAFがオンになっている"
else
waf_status=""
fi
@@ -1776,8 +1860,8 @@ check_waf_status() {
check_cf_mode() {
- if [ -f "/path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf" ]; then
- CFmessage=" cf模式已开启"
+ if [ -f "/etc/fail2ban/action.d/cloudflare-docker.conf" ]; then
+ CFmessage="cfモードがオンになっています"
else
CFmessage=""
fi
@@ -1801,11 +1885,11 @@ patch_wp_memory_limit() {
local TARGET_DIR="/home/web/html" # 路径写死
find "$TARGET_DIR" -type f -name "wp-config.php" | while read -r FILE; do
- # 古い定義を削除します
+ # 古い定義を削除する
sed -i "/define(['\"]WP_MEMORY_LIMIT['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_MAX_MEMORY_LIMIT['\"].*/d" "$FILE"
- # 「Happy Publishing」で行の前に新しい定義を挿入する
+ # 「Happy Publishing」を含む行の前に新しい定義を挿入します。
awk -v insert="define('WP_MEMORY_LIMIT', '$MEMORY_LIMIT');\ndefine('WP_MAX_MEMORY_LIMIT', '$MAX_MEMORY_LIMIT');" \
'
/Happy publishing/ {
@@ -1828,12 +1912,12 @@ patch_wp_debug() {
local TARGET_DIR="/home/web/html" # 路径写死
find "$TARGET_DIR" -type f -name "wp-config.php" | while read -r FILE; do
- # 古い定義を削除します
+ # 古い定義を削除する
sed -i "/define(['\"]WP_DEBUG['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_DEBUG_DISPLAY['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_DEBUG_LOG['\"].*/d" "$FILE"
- # 「Happy Publishing」で行の前に新しい定義を挿入する
+ # 「Happy Publishing」を含む行の前に新しい定義を挿入します。
awk -v insert="define('WP_DEBUG_DISPLAY', $DEBUG_DISPLAY);\ndefine('WP_DEBUG_LOG', $DEBUG_LOG);" \
'
/Happy publishing/ {
@@ -1847,6 +1931,43 @@ patch_wp_debug() {
}
+
+
+patch_wp_url() {
+ local HOME_URL="$1"
+ local SITE_URL="$2"
+ local TARGET_DIR="/home/web/html"
+
+ find "$TARGET_DIR" -type f -name "wp-config-sample.php" | while read -r FILE; do
+ # 古い定義を削除する
+ sed -i "/define(['\"]WP_HOME['\"].*/d" "$FILE"
+ sed -i "/define(['\"]WP_SITEURL['\"].*/d" "$FILE"
+
+ # 挿入コンテンツの生成
+ INSERT="
+define('WP_HOME', '$HOME_URL');
+define('WP_SITEURL', '$SITE_URL');
+"
+
+ # 「出版おめでとうございます」の前に挿入
+ awk -v insert="$INSERT" '
+ /Happy publishing/ {
+ print insert
+ }
+ { print }
+ ' "$FILE" > "$FILE.tmp" && mv -f "$FILE.tmp" "$FILE"
+
+ echo "[+] Updated WP_HOME and WP_SITEURL in $FILE"
+ done
+}
+
+
+
+
+
+
+
+
nginx_br() {
local mode=$1
@@ -1856,7 +1977,7 @@ nginx_br() {
fi
if [ "$mode" == "on" ]; then
- # Brotliをオンにする:コメントを削除します
+ # Brotli をオンにする: コメントを削除する
sed -i 's|# load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|# load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1870,7 +1991,7 @@ nginx_br() {
sed -i '/brotli_types/,+6 s/^\(\s*\)#\s*/\1/' /home/web/nginx.conf
elif [ "$mode" == "off" ]; then
- # Brotliを閉じる:コメントを追加します
+ # ブロトリを閉じる: コメントを追加
sed -i 's|^load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|# load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|# load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1886,11 +2007,11 @@ nginx_br() {
}' /home/web/nginx.conf
else
- echo "無効なパラメーター:「オン」または「オフ」を使用します"
+ echo "無効な引数: 'on' または 'off' を使用してください"
return 1
fi
- # nginx画像を確認し、状況に応じてそれらを処理します
+ # nginx イメージを確認し、それに応じて処理します
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1912,7 +2033,7 @@ nginx_zstd() {
fi
if [ "$mode" == "on" ]; then
- # ZSTDをオンにしてください:コメントを削除します
+ # Zstd をオンにする: コメントを削除する
sed -i 's|# load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|# load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1927,7 +2048,7 @@ nginx_zstd() {
elif [ "$mode" == "off" ]; then
- # ZSTDを閉じる:コメントを追加します
+ # Zstdを閉じる: コメントを追加
sed -i 's|^load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|# load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|# load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1943,11 +2064,11 @@ nginx_zstd() {
else
- echo "無効なパラメーター:「オン」または「オフ」を使用します"
+ echo "無効な引数: 'on' または 'off' を使用してください"
return 1
fi
- # nginx画像を確認し、状況に応じてそれらを処理します
+ # nginx イメージを確認し、それに応じて処理します
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1974,7 +2095,7 @@ nginx_gzip() {
elif [ "$mode" == "off" ]; then
sed -i 's|^\(\s*\)gzip on;|\1# gzip on;|' /home/web/nginx.conf > /dev/null 2>&1
else
- echo "無効なパラメーター:「オン」または「オフ」を使用します"
+ echo "無効な引数: 'on' または 'off' を使用してください"
return 1
fi
@@ -1990,45 +2111,42 @@ nginx_gzip() {
web_security() {
send_stats "LDNMP環境防御"
while true; do
+ check_f2b_status
check_waf_status
check_cf_mode
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "サーバーWebサイト防衛プログラム${check_docker}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
+ echo -e "サーバー Web サイト防御プログラム${check_f2b_status}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
echo "------------------------"
- echo "1.防衛プログラムをインストールします"
+ echo "1. 防御プログラムをインストールする"
echo "------------------------"
- echo "5。SSHインターセプトレコードを表示6。ウェブサイト傍受記録を見る"
- echo "7。防衛ルールのリストを表示8。ログのリアルタイム監視を表示"
+ echo "5. SSH 傍受記録の表示 6. Web サイト傍受記録の表示"
+ echo "7. 防御ルールのリストを表示します。 8. リアルタイム監視のログを表示します。"
echo "------------------------"
- echo "11.インターセプトパラメーターを構成12。すべてのブロックされたipsをクリアします"
+ echo "11. インターセプトパラメータを設定します。 12. ブロックされた IP をすべてクリアします。"
echo "------------------------"
- echo "21。CloudFlareモード22。5秒シールドの高負荷"
+ echo "21. クラウドフレア モード 22. 高負荷時に 5 秒間のシールドを有効にする"
echo "------------------------"
- echo "31。WAF32をオンにしてください。WAFをオフにします"
- echo "33。DDOS防衛をオンにする34。DDOS防衛をオフにする"
+ echo "31. WAF をオンにする 32. WAF をオフにする"
+ echo "33. DDOS 防御をオンにする 34. DDOS 防御をオフにする"
echo "------------------------"
- echo "9.防衛プログラムをアンインストールします"
+ echo "9. 防御プログラムをアンインストールする"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
f2b_install_sshd
- cd /path/to/fail2ban/config/fail2ban/filter.d
+ cd /etc/fail2ban/filter.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/fail2ban-nginx-cc.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-418.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-deny.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-unauthorized.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-bad-request.conf
+
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- sed -i "/cloudflare/d" /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ sed -i "/cloudflare/d" /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
;;
5)
@@ -2042,93 +2160,94 @@ web_security() {
local xxx="fail2ban-nginx-cc"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-418"
+ local xxx="nginx-418"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-bad-request"
+ local xxx="nginx-bad-request"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-badbots"
+ local xxx="nginx-badbots"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-botsearch"
+ local xxx="nginx-botsearch"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-deny"
+ local xxx="nginx-deny"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-http-auth"
+ local xxx="nginx-http-auth"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-unauthorized"
+ local xxx="nginx-unauthorized"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-php-url-fopen"
+ local xxx="php-url-fopen"
f2b_status_xxx
echo "------------------------"
;;
7)
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
;;
8)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
+ tail -f /var/log/fail2ban.log
;;
9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
+ remove fail2ban
+ rm -rf /etc/fail2ban
crontab -l | grep -v "CF-Under-Attack.sh" | crontab - 2>/dev/null
- echo "Fail2Ban防衛プログラムがアンインストールされています"
+ echo "Fail2Ban 防御プログラムがアンインストールされました"
+ break
;;
11)
install nano
- nano /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ nano /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
break
;;
12)
- docker exec -it fail2ban fail2ban-client unban --all
+ fail2ban-client unban --all
;;
21)
- send_stats "CloudFlareモード"
- echo "CF背景の右上隅に移動し、左側のAPIトークンを選択し、グローバルAPIキーを取得します"
+ send_stats "クラウドフレアモード"
+ echo "cf バックエンドの右上隅にある私のプロフィールに移動し、左側で API トークンを選択し、グローバル API キーを取得します。"
echo "https://dash.cloudflare.com/login"
- read -e -p "CFアカウント番号を入力します:" cfuser
- read -e -p "CFのグローバルAPIキーを入力してください:" cftoken
+ read -e -p "CF の口座番号を入力してください:" cfuser
+ read -e -p "CF のグローバル API キーを入力します。" cftoken
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default11.conf
docker exec nginx nginx -s reload
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- cd /path/to/fail2ban/config/fail2ban/action.d
+ cd /etc/fail2ban/action.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/cloudflare-docker.conf
- sed -i "s/kejilion@outlook.com/$cfuser/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
- sed -i "s/APIKEY00000/$cftoken/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/kejilion@outlook.com/$cfuser/g" /etc/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/APIKEY00000/$cftoken/g" /etc/fail2ban/action.d/cloudflare-docker.conf
f2b_status
- echo "CloudFlareモードは、CFバックグラウンド、サイトセキュリティイベントでインターセプトレコードを表示するように構成されています"
+ echo "Cloudflare モードが設定されており、傍受記録は cf バックグラウンド、site-security-events で表示できます。"
;;
22)
- send_stats "5秒シールドでの高負荷"
- echo -e "${gl_huang}ウェブサイトは5分ごとに自動的に検出されます。高負荷が検出されると、シールドが自動的にオンになり、低負荷が5秒間自動的にオフになります。${gl_bai}"
+ send_stats "高負荷により5秒シールドが可能"
+ echo -e "${gl_huang}Web サイトは 5 分ごとに自動的に検出します。高負荷を検出すると自動的にシールドが開き、低負荷を検出すると5秒間自動的にシールドが閉じます。${gl_bai}"
echo "--------------"
- echo "CFパラメーターを取得します:"
- echo -e "CFバックグラウンドの右上隅に移動し、左側のAPIトークンを選択して、取得します${gl_huang}Global API Key${gl_bai}"
- echo -e "CFバックグラウンドドメイン名の概要ページの右下に移動して${gl_huang}リージョンID${gl_bai}"
+ echo "CF パラメータを取得します。"
+ echo -e "cf バックエンドの右上隅にある私のプロフィールに移動し、左側にある API トークンを選択して、${gl_huang}Global API Key${gl_bai}"
+ echo -e "cf バックエンド ドメイン名の概要ページの右下に移動して取得します。${gl_huang}エリアID${gl_bai}"
echo "https://dash.cloudflare.com/login"
echo "--------------"
- read -e -p "CFアカウント番号を入力します:" cfuser
- read -e -p "CFのグローバルAPIキーを入力してください:" cftoken
- read -e -p "CFにドメイン名の領域IDを入力します。" cfzonID
+ read -e -p "CF の口座番号を入力してください:" cfuser
+ read -e -p "CF のグローバル API キーを入力します。" cftoken
+ read -e -p "CF にドメイン名のゾーン ID を入力します。" cfzonID
cd ~
install jq bc
@@ -2145,9 +2264,9 @@ web_security() {
if [ -z "$existing_cron" ]; then
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "高負荷自動シールドオープニングスクリプトが追加されました"
+ echo "高負荷自動シールド開放スクリプトを追加しました"
else
- echo "自動シールドスクリプトはすでに存在しています、それを追加する必要はありません"
+ echo "自動シールド開放スクリプトはすでに存在するため、追加する必要はありません"
fi
;;
@@ -2160,8 +2279,8 @@ web_security() {
32)
nginx_waf off
- echo "サイトWAFは閉鎖されています"
- send_stats "サイトWAFは閉鎖されています"
+ echo "サイト WAF がダウンしています"
+ send_stats "サイト WAF がダウンしています"
;;
33)
@@ -2176,26 +2295,23 @@ web_security() {
break
;;
esac
- fi
break_end
done
}
-check_nginx_mode() {
+check_ldnmp_mode() {
-CONFIG_FILE="/home/web/nginx.conf"
+ local MYSQL_CONTAINER="mysql"
+ local MYSQL_CONF="/etc/mysql/conf.d/custom_mysql_config.cnf"
-# 現在のworker_processesの設定値を取得します
-current_value=$(grep -E '^\s*worker_processes\s+[0-9]+;' "$CONFIG_FILE" | awk '{print $2}' | tr -d ';')
-
-# 値に応じてモード情報を設定します
-if [ "$current_value" = "8" ]; then
- mode_info=" 高性能模式"
-else
- mode_info=" 标准模式"
-fi
+ # MySQL 設定ファイルに 4096M が含まれているかどうかを確認する
+ if docker exec "$MYSQL_CONTAINER" grep -q "4096M" "$MYSQL_CONF" 2>/dev/null; then
+ mode_info="ハイパフォーマンスモード"
+ else
+ mode_info="標準モード"
+ fi
@@ -2204,25 +2320,25 @@ fi
check_nginx_compression() {
- CONFIG_FILE="/home/web/nginx.conf"
+ local CONFIG_FILE="/home/web/nginx.conf"
- # ZSTDが有効になっていてコメントされていないかどうかを確認します(ZSTDで行全体が開始されます;)
+ # zstd がオンでコメントが解除されているかどうかを確認します (行全体が zstd on で始まります)。
if grep -qE '^\s*zstd\s+on;' "$CONFIG_FILE"; then
- zstd_status=" zstd压缩已开启"
+ zstd_status="zstd圧縮がオンになっています"
else
zstd_status=""
fi
- # Brotliが有効であり、コメントされていないかどうかを確認してください
+ # Brotli が有効になっていてコメントが解除されているかどうかを確認します
if grep -qE '^\s*brotli\s+on;' "$CONFIG_FILE"; then
- br_status=" br压缩已开启"
+ br_status="br圧縮がオンになっています"
else
br_status=""
fi
- # GZIPが有効になっており、コメントされていないかどうかを確認してください
+ # gzip が有効になっていてコメントが解除されているかどうかを確認します
if grep -qE '^\s*gzip\s+on;' "$CONFIG_FILE"; then
- gzip_status=" gzip压缩已开启"
+ gzip_status="gzip圧縮がオンになっています"
else
gzip_status=""
fi
@@ -2233,36 +2349,38 @@ check_nginx_compression() {
web_optimization() {
while true; do
- check_nginx_mode
+ check_ldnmp_mode
check_nginx_compression
clear
- send_stats "LDNMP環境を最適化します"
- echo -e "LDNMP環境を最適化します${gl_lv}${mode_info}${gzip_status}${br_status}${zstd_status}${gl_bai}"
+ send_stats "LDNMP環境の最適化"
+ echo -e "LDNMP環境の最適化${gl_lv}${mode_info}${gzip_status}${br_status}${zstd_status}${gl_bai}"
echo "------------------------"
- echo "1。標準モード2。高性能モード(推奨2H4g以上)"
+ echo "1.スタンダードモード 2.ハイパフォーマンスモード(2H4G以上推奨)"
echo "------------------------"
- echo "3。GZIP圧縮をオンにします4。GZIP圧縮をオフにします"
- echo "5。BR圧縮をオンにします6。BR圧縮をオフにします"
- echo "7。ZSTD圧縮をオンにします8。ZSTD圧縮をオフにします"
+ echo "3. gzip 圧縮をオンにする 4. gzip 圧縮をオフにする"
+ echo "5. br 圧縮をオンにする 6. br 圧縮をオフにする"
+ echo "7. zstd 圧縮をオンにする 8. zstd 圧縮をオフにする"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
send_stats "サイト標準モード"
- # nginxチューニング
- sed -i 's/worker_connections.*/worker_connections 10240;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 4;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((1024 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
+
- # PHPチューニング
+ # PHPのチューニング
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
docker cp /home/optimized_php.ini php:/usr/local/etc/php/conf.d/optimized_php.ini
docker cp /home/optimized_php.ini php74:/usr/local/etc/php/conf.d/optimized_php.ini
rm -rf /home/optimized_php.ini
- # PHPチューニング
+ # PHPのチューニング
wget -O /home/www.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/www-1.conf
docker cp /home/www.conf php:/usr/local/etc/php-fpm.d/www.conf
docker cp /home/www.conf php74:/usr/local/etc/php-fpm.d/www.conf
@@ -2274,7 +2392,7 @@ web_optimization() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- # mysqlチューニング
+ # mysqlのチューニング
wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
rm -rf /home/custom_mysql_config.cnf
@@ -2282,7 +2400,6 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_balanced
@@ -2290,19 +2407,21 @@ web_optimization() {
;;
2)
- send_stats "サイトの高性能モード"
+ send_stats "サイトハイパフォーマンスモード"
- # nginxチューニング
- sed -i 's/worker_connections.*/worker_connections 20480;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 8;/' /home/web/nginx.conf
+ # nginxのチューニング
+ local cpu_cores=$(nproc)
+ local connections=$((2048 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
- # PHPチューニング
+ # PHPのチューニング
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
docker cp /home/optimized_php.ini php:/usr/local/etc/php/conf.d/optimized_php.ini
docker cp /home/optimized_php.ini php74:/usr/local/etc/php/conf.d/optimized_php.ini
rm -rf /home/optimized_php.ini
- # PHPチューニング
+ # PHPのチューニング
wget -O /home/www.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/www.conf
docker cp /home/www.conf php:/usr/local/etc/php-fpm.d/www.conf
docker cp /home/www.conf php74:/usr/local/etc/php-fpm.d/www.conf
@@ -2314,17 +2433,16 @@ web_optimization() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- # mysqlチューニング
+ # mysqlのチューニング
wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config.cnf
docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
rm -rf /home/custom_mysql_config.cnf
cd /home/web && docker compose restart
- restart_redis
optimize_web_server
- echo "LDNMP環境は、高性能モードに設定されています"
+ echo "LDNMP 環境が高パフォーマンス モードに設定されている"
;;
3)
@@ -2371,23 +2489,30 @@ web_optimization() {
+check_docker_app() {
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name" ; then
+ check_docker="${gl_lv}インストール済み${gl_bai}"
+ else
+ check_docker="${gl_hui}インストールされていません${gl_bai}"
+ fi
+}
-check_docker_app() {
+# check_docker_app() {
-if docker inspect "$docker_name" &>/dev/null; then
- check_docker="${gl_lv}已安装${gl_bai}"
-else
- check_docker="${gl_hui}未安装${gl_bai}"
-fi
+# if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+# check_docker="${gl_lv} は ${gl_bai} をインストールしました"
+# else
+# check_docker="${gl_hui} がインストールされていません ${gl_bai}"
+# fi
-}
+# }
check_docker_app_ip() {
echo "------------------------"
-echo "アクセスアドレス:"
+echo "訪問先住所:"
ip_address
@@ -2416,59 +2541,66 @@ done
check_docker_image_update() {
-
local container_name=$1
+ update_status=""
- local country=$(curl -s ipinfo.io/country)
- if [[ "$country" == "CN" ]]; then
- update_status=""
- return
- fi
+ # 1. 地域検査
+ local country=$(curl -s --max-time 2 ipinfo.io/country)
+ [[ "$country" == "CN" ]] && return
- # コンテナの作成時間と画像名を取得します
+ # 2. ローカルミラー情報の取得
local container_info=$(docker inspect --format='{{.Created}},{{.Config.Image}}' "$container_name" 2>/dev/null)
- local container_created=$(echo "$container_info" | cut -d',' -f1)
- local image_name=$(echo "$container_info" | cut -d',' -f2)
-
- # ミラーウェアハウスとタグを抽出します
- local image_repo=${image_name%%:*}
- local image_tag=${image_name##*:}
-
- # デフォルトのラベルは最新です
- [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
-
- # 公式画像のサポートを追加します
- [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+ [[ -z "$container_info" ]] && return
- # Docker Hub APIから画像公開時間を取得します
- local hub_info=$(curl -s "https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag")
- local last_updated=$(echo "$hub_info" | jq -r '.last_updated' 2>/dev/null)
-
- # 買収の時間を確認します
- if [[ -n "$last_updated" && "$last_updated" != "null" ]]; then
- local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
- local last_updated_ts=$(date -d "$last_updated" +%s 2>/dev/null)
+ local container_created=$(echo "$container_info" | cut -d',' -f1)
+ local full_image_name=$(echo "$container_info" | cut -d',' -f2)
+ local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
+
+ # 3. インテリジェントな配線判断
+ if [[ "$full_image_name" == ghcr.io* ]]; then
+ # --- シナリオ A: GitHub (ghcr.io) 上のミラー ---
+ # ウェアハウスのパスを抽出します (例: ghcr.io/onexru/oneimg -> onexru/oneimg)
+ local repo_path=$(echo "$full_image_name" | sed 's/ghcr.io\///' | cut -d':' -f1)
+ # 注: ghcr.io の API は比較的複雑です。通常、最も早い方法は、GitHub リポジトリのリリースを確認することです
+ local api_url="https://api.github.com/repos/$repo_path/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
+
+ elif [[ "$full_image_name" == *"oneimg"* ]]; then
+ # --- シナリオB:特別指定(Docker HubでもGitHub Releaseで判断したい) ---
+ local api_url="https://api.github.com/repos/onexru/oneimg/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
- # タイムスタンプを比較します
- if [[ $container_created_ts -lt $last_updated_ts ]]; then
- update_status="${gl_huang}发现新版本!${gl_bai}"
- else
- update_status=""
- fi
else
- update_status=""
+ # --- シナリオ C: 標準 Docker ハブ ---
+ local image_repo=${full_image_name%%:*}
+ local image_tag=${full_image_name##*:}
+ [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+
+ local api_url="https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag"
+ local remote_date=$(curl -s "$api_url" | jq -r '.last_updated' 2>/dev/null)
fi
+ # 4. タイムスタンプの比較
+ if [[ -n "$remote_date" && "$remote_date" != "null" ]]; then
+ local remote_ts=$(date -d "$remote_date" +%s 2>/dev/null)
+ if [[ $container_created_ts -lt $remote_ts ]]; then
+ update_status="${gl_huang}新しいバージョンが見つかりました!${gl_bai}"
+ fi
+ fi
}
+
+
+
block_container_port() {
local container_name_or_id=$1
local allowed_ip=$2
- # コンテナのIPアドレスを取得します
+ # コンテナのIPアドレスを取得する
local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name_or_id")
if [ -z "$container_ip" ]; then
@@ -2478,34 +2610,34 @@ block_container_port() {
install iptables
- # 他のすべてのIPSを確認してブロックします
+ # 他のすべての IP をチェックしてブロックします
if ! iptables -C DOCKER-USER -p tcp -d "$container_ip" -j DROP &>/dev/null; then
iptables -I DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
- # 指定されたIPを確認してリリースします
+ # 指定したIPの確認と解放
if ! iptables -C DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # ローカルネットワークを確認してリリースします127.0.0.0/8
+ # ローカルネットワーク127.0.0.0/8をチェックして許可します。
if ! iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
- # 他のすべてのIPSを確認してブロックします
+ # 他のすべての IP をチェックしてブロックします
if ! iptables -C DOCKER-USER -p udp -d "$container_ip" -j DROP &>/dev/null; then
iptables -I DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
- # 指定されたIPを確認してリリースします
+ # 指定したIPの確認と解放
if ! iptables -C DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # ローカルネットワークを確認してリリースします127.0.0.0/8
+ # ローカルネットワーク127.0.0.0/8をチェックして許可します。
if ! iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2515,7 +2647,7 @@ block_container_port() {
fi
- echo "IP+ポートは、サービスへのアクセスをブロックされています"
+ echo "IP+ポートはサービスへのアクセスをブロックされています"
save_iptables_rules
}
@@ -2526,7 +2658,7 @@ clear_container_rules() {
local container_name_or_id=$1
local allowed_ip=$2
- # コンテナのIPアドレスを取得します
+ # コンテナのIPアドレスを取得する
local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name_or_id")
if [ -z "$container_ip" ]; then
@@ -2536,17 +2668,17 @@ clear_container_rules() {
install iptables
- # 他のすべてのIPをブロックするルールを明確にします
+ # 他のすべての IP をブロックする明確なルール
if iptables -C DOCKER-USER -p tcp -d "$container_ip" -j DROP &>/dev/null; then
iptables -D DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
- # 指定されたIPをリリースするためのルールをクリアします
+ # 指定したIPを許可するルールをクリアします
if iptables -C DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # ローカルネットワークをリリースするためのルールをクリア127.0.0.0/8
+ # ローカルネットワーク 127.0.0.0/8 を許可するルールをクリアします
if iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2555,17 +2687,17 @@ clear_container_rules() {
- # 他のすべてのIPをブロックするルールを明確にします
+ # 他のすべての IP をブロックする明確なルール
if iptables -C DOCKER-USER -p udp -d "$container_ip" -j DROP &>/dev/null; then
iptables -D DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
- # 指定されたIPをリリースするためのルールをクリアします
+ # 指定したIPを許可するルールをクリアします
if iptables -C DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # ローカルネットワークをリリースするためのルールをクリア127.0.0.0/8
+ # ローカルネットワーク 127.0.0.0/8 を許可するルールをクリアします
if iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2576,7 +2708,7 @@ clear_container_rules() {
fi
- echo "IP+ポートはサービスにアクセスすることが許可されています"
+ echo "IP+ポートによるサービスへのアクセスが許可されました"
save_iptables_rules
}
@@ -2590,25 +2722,25 @@ block_host_port() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "エラー:アクセスを許可されているポート番号とIPを提供してください。"
- echo "使用法:block_host_port <ポート番号> <承認IP>"
+ echo "エラー: アクセスを許可するには、ポート番号と IP を入力してください。"
+ echo "使用法: block_host_port <ポート番号> <許可された IP>"
return 1
fi
install iptables
- # 他のすべてのIPアクセスを拒否しました
+ # 他のすべての IP からのアクセスを拒否する
if ! iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -j DROP
fi
- # 指定されたIPアクセスを許可します
+ # 指定したIPへのアクセスを許可する
if ! iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # ローカルアクセスを許可します
+ # ローカルアクセスを許可する
if ! iptables -C INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
@@ -2617,27 +2749,27 @@ block_host_port() {
- # 他のすべてのIPアクセスを拒否しました
+ # 他のすべての IP からのアクセスを拒否する
if ! iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -j DROP
fi
- # 指定されたIPアクセスを許可します
+ # 指定したIPへのアクセスを許可する
if ! iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # ローカルアクセスを許可します
+ # ローカルアクセスを許可する
if ! iptables -C INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 確立された関連接続および関連する接続のトラフィックを許可します
+ # 確立された接続と関連する接続のトラフィックを許可する
if ! iptables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT &>/dev/null; then
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
fi
- echo "IP+ポートは、サービスへのアクセスをブロックされています"
+ echo "IP+ポートはサービスへのアクセスをブロックされています"
save_iptables_rules
}
@@ -2649,47 +2781,47 @@ clear_host_port_rules() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "エラー:アクセスを許可されているポート番号とIPを提供してください。"
- echo "使用法:CLEAR_HOST_PORT_RULES <ポート番号> <認定IP>"
+ echo "エラー: アクセスを許可するには、ポート番号と IP を入力してください。"
+ echo "使用法: clear_host_port_rules <ポート番号> <許可された IP>"
return 1
fi
install iptables
- # 他のすべてのIPアクセスをブロックするルールをクリアします
+ # 他のすべての IP からのアクセスをブロックするルールをクリアします
if iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -j DROP
fi
- # ネイティブアクセスを可能にするルールを明確にします
+ # ローカルアクセスを許可する明確なルール
if iptables -C INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 指定されたIPアクセスを許可するルールを明確にします
+ # 指定したIPからのアクセスを許可する明確なルール
if iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # 他のすべてのIPアクセスをブロックするルールをクリアします
+ # 他のすべての IP からのアクセスをブロックするルールをクリアします
if iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -j DROP
fi
- # ネイティブアクセスを可能にするルールを明確にします
+ # ローカルアクセスを許可する明確なルール
if iptables -C INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 指定されたIPアクセスを許可するルールを明確にします
+ # 指定したIPからのアクセスを許可する明確なルール
if iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- echo "IP+ポートはサービスにアクセスすることが許可されています"
+ echo "IP+ポートによるサービスへのアクセスが許可されました"
save_iptables_rules
}
@@ -2698,16 +2830,33 @@ clear_host_port_rules() {
setup_docker_dir() {
- mkdir -p /home/docker/ 2>/dev/null
+ mkdir -p /home /home/docker 2>/dev/null
+
if [ -d "/vol1/1000/" ] && [ ! -d "/vol1/1000/docker" ]; then
cp -f /home/docker /home/docker1 2>/dev/null
rm -rf /home/docker 2>/dev/null
mkdir -p /vol1/1000/docker 2>/dev/null
ln -s /vol1/1000/docker /home/docker 2>/dev/null
fi
+
+ if [ -d "/volume1/" ] && [ ! -d "/volume1/docker" ]; then
+ cp -f /home/docker /home/docker1 2>/dev/null
+ rm -rf /home/docker 2>/dev/null
+ mkdir -p /volume1/docker 2>/dev/null
+ ln -s /volume1/docker /home/docker 2>/dev/null
+ fi
+
+
}
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "${app_id}" /home/docker/appno.txt || echo "${app_id}" >> /home/docker/appno.txt
+
+}
+
docker_app() {
@@ -2716,11 +2865,11 @@ send_stats "${docker_name}管理"
while true; do
clear
check_docker_app
- check_docker_image_update $docker_name
+ check_docker_image_update "${docker_app_service:-$docker_name}"
echo -e "$docker_name $check_docker $update_status"
echo "$docker_describe"
echo "$docker_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
@@ -2731,29 +2880,40 @@ while true; do
fi
echo ""
echo "------------------------"
- echo "1。インストール2。更新3。アンインストール"
+ echo "1. インストール 2. アップデート 3. アンインストール"
echo "------------------------"
- echo "5.ドメイン名アクセスを追加6。ドメイン名アクセスを削除する"
- echo "7. IP+ポートアクセスを許可8。BlockIP+ポートアクセス"
+ echo "5. ドメイン名アクセスを追加します。 6. ドメイン名アクセスを削除します。"
+ echo "7. IP+ポートアクセスを許可します。 8. IP+ポートアクセスをブロックします。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "アプリケーション外部サービスポートを入力し、デフォルトを入力します${docker_port}ポート:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+ while true; do
+ read -e -p "アプリケーションの外部サービス ポートを入力し、Enter キーを押して、それをデフォルトで使用します。${docker_port}ポート:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}間違い:${gl_bai}ポート$app_portすでに占有されています。ポートを変更してください"
+ send_stats "アプリケーションポートが占有されています"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
install jq
install_docker
docker_rum
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+ add_app_id
+
clear
- echo "$docker_nameインストール"
+ echo "$docker_nameインストール完了"
check_docker_app_ip
echo ""
$docker_use
@@ -2764,43 +2924,48 @@ while true; do
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
docker_rum
+
+ add_app_id
+
clear
- echo "$docker_nameインストール"
+ echo "$docker_nameインストール完了"
check_docker_app_ip
echo ""
$docker_use
$docker_passwd
- send_stats "更新します$docker_name"
+ send_stats "更新する$docker_name"
;;
3)
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
rm -rf "/home/docker/$docker_name"
rm -f /home/docker/${docker_name}_port.conf
- echo "アプリはアンインストールされています"
- send_stats "アンインストール$docker_name"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "アプリがアンインストールされました"
+ send_stats "アンインストールする$docker_name"
;;
5)
- echo "${docker_name}ドメインアクセス設定"
- send_stats "${docker_name}ドメインアクセス設定"
+ echo "${docker_name}ドメイン名アクセス設定"
+ send_stats "${docker_name}ドメイン名アクセス設定"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
block_container_port "$docker_name" "$ipv4_address"
;;
6)
- echo "ドメイン名フォーマットexample.comにはhttps://が付属していません"
+ echo "ドメイン名の形式 example.com (https:// なし)"
web_del
;;
7)
- send_stats "IPアクセスを許可します${docker_name}"
+ send_stats "IPアクセスを許可する${docker_name}"
clear_container_rules "$docker_name" "$ipv4_address"
;;
8)
- send_stats "IPアクセスをブロックします${docker_name}"
+ send_stats "IPアクセスをブロックする${docker_name}"
block_container_port "$docker_name" "$ipv4_address"
;;
@@ -2817,7 +2982,6 @@ done
-
docker_app_plus() {
send_stats "$app_name"
while true; do
@@ -2827,9 +2991,10 @@ docker_app_plus() {
echo -e "$app_name $check_docker $update_status"
echo "$app_text"
echo "$app_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_check_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
- local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
+ local docker_port=$(docker port "$docker_check_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
fi
@@ -2838,51 +3003,76 @@ docker_app_plus() {
fi
echo ""
echo "------------------------"
- echo "1。インストール2。更新3。アンインストール"
+ echo "1. インストール 2. アップデート 3. アンインストール"
echo "------------------------"
- echo "5.ドメイン名アクセスを追加6。ドメイン名アクセスを削除する"
- echo "7. IP+ポートアクセスを許可8。BlockIP+ポートアクセス"
+ echo "5. ドメイン名アクセスを追加します。 6. ドメイン名アクセスを削除します。"
+ echo "7. IP+ポートアクセスを許可します。 8. IP+ポートアクセスをブロックします。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "アプリケーション外部サービスポートを入力し、デフォルトを入力します${docker_port}ポート:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+
+ while true; do
+ read -e -p "アプリケーションの外部サービス ポートを入力し、Enter キーを押して、それをデフォルトで使用します。${docker_port}ポート:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}間違い:${gl_bai}ポート$app_portすでに占有されています。ポートを変更してください"
+ send_stats "アプリケーションポートが占有されています"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
+
install jq
install_docker
docker_app_install
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+
+ add_app_id
+ send_stats "$app_nameインストール"
;;
+
2)
docker_app_update
+ add_app_id
+ send_stats "$app_name更新する"
;;
+
3)
docker_app_uninstall
rm -f /home/docker/${docker_name}_port.conf
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "$app_nameアンインストールする"
;;
+
5)
- echo "${docker_name}ドメインアクセス設定"
- send_stats "${docker_name}ドメインアクセス設定"
+ echo "${docker_name}ドメイン名アクセス設定"
+ send_stats "${docker_name}ドメイン名アクセス設定"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
+
;;
6)
- echo "ドメイン名フォーマットexample.comにはhttps://が付属していません"
+ echo "ドメイン名の形式 example.com (https:// なし)"
web_del
;;
7)
- send_stats "IPアクセスを許可します${docker_name}"
+ send_stats "IPアクセスを許可する${docker_name}"
clear_container_rules "$docker_name" "$ipv4_address"
;;
8)
- send_stats "IPアクセスをブロックします${docker_name}"
- block_container_port "$docker_name" "$ipv4_address"
+ send_stats "IPアクセスをブロックする${docker_name}"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
;;
*)
break
@@ -2920,7 +3110,7 @@ docker network create $NETWORK_NAME
docker run -d \
--name=node-exporter \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
prom/node-exporter
# Run Prometheus container
@@ -2929,7 +3119,7 @@ docker run -d \
-v $PROMETHEUS_DIR/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $PROMETHEUS_DIR/data:/prometheus \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
--user 0:0 \
prom/prometheus:latest
@@ -2939,7 +3129,7 @@ docker run -d \
-p ${docker_port}:3000 \
-v $GRAFANA_DIR:/var/lib/grafana \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
grafana/grafana:latest
}
@@ -2966,7 +3156,7 @@ tmux_run_d() {
local base_name="tmuxd"
local tmuxd_ID=1
-# セッションが存在するかどうかを確認する関数
+# セッションが存在するかどうかを確認する機能
session_exists() {
tmux has-session -t $1 2>/dev/null
}
@@ -2976,7 +3166,7 @@ while session_exists "$base_name-$tmuxd_ID"; do
local tmuxd_ID=$((tmuxd_ID + 1))
done
-# 新しいTMUXセッションを作成します
+# 新しい tmux セッションを作成する
tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
@@ -2985,52 +3175,41 @@ tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
f2b_status() {
- docker exec -it fail2ban fail2ban-client reload
+ fail2ban-client reload
sleep 3
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
}
f2b_status_xxx() {
- docker exec -it fail2ban fail2ban-client status $xxx
+ fail2ban-client status $xxx
}
-f2b_install_sshd() {
-
- docker run -d \
- --name=fail2ban \
- --net=host \
- --cap-add=NET_ADMIN \
- --cap-add=NET_RAW \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e VERBOSITY=-vv \
- -v /path/to/fail2ban/config:/config \
- -v /var/log:/var/log:ro \
- -v /home/web/log/nginx/:/remotelogs/nginx:ro \
- --restart unless-stopped \
- lscr.io/linuxserver/fail2ban:latest
-
- sleep 3
- if grep -q 'Alpine' /etc/issue; then
- cd /path/to/fail2ban/config/fail2ban/filter.d
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd.conf
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd-ddos.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-ssh.conf
- elif command -v dnf &>/dev/null; then
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+check_f2b_status() {
+ if command -v fail2ban-client >/dev/null 2>&1; then
+ check_f2b_status="${gl_lv}インストール済み${gl_bai}"
else
+ check_f2b_status="${gl_hui}インストールされていません${gl_bai}"
+ fi
+}
+
+f2b_install_sshd() {
+
+ docker rm -f fail2ban >/dev/null 2>&1
+ install fail2ban
+ start fail2ban
+ enable fail2ban
+
+ if command -v dnf &>/dev/null; then
+ cd /etc/fail2ban/jail.d/
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+ fi
+
+ if command -v apt &>/dev/null; then
install rsyslog
systemctl start rsyslog
systemctl enable rsyslog
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/linux-ssh.conf
- systemctl restart rsyslog
fi
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf
}
f2b_sshd() {
@@ -3043,6 +3222,80 @@ f2b_sshd() {
fi
}
+# 基本パラメータ設定: 禁止期間 (bantime)、時間枠 (findtime)、再試行回数 (maxretry)
+# 例証します:
+# - /etc/fail2ban/jail.d/sshd.local への書き込みを優先します (デフォルトのjail設定をオーバーライドし、アップグレード時に失われにくくなります)
+# - Alpine で、jail 名が異なる場合でも、sshd.local と書き込みます。 Fail2Ban は、jail 名に従って一致します。
+f2b_basic_config() {
+ root_use
+ install nano
+
+ if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo -e "${gl_hui}failed2ban-client が検出されません。まず、fail2ban をインストールしてください。${gl_bai}"
+ return
+ fi
+
+ local jail_name="sshd"
+ if grep -qi 'Alpine' /etc/issue 2>/dev/null; then
+ # Alpine のデフォルトの刑務所は通常 sshd です。カスタム alpine-sshd ルールが検出された場合にのみ切り替えます
+ if [ -f /etc/fail2ban/filter.d/alpine-sshd.conf ] || [ -f /etc/fail2ban/jail.d/alpine-ssh.conf ] || [ -f /etc/fail2ban/jail.d/alpine-sshd.local ]; then
+ jail_name="alpine-sshd"
+ fi
+ fi
+
+ echo "SSH ジェイルを構成しようとしています:$jail_name"
+ read -e -p "Bantime 禁止時間 (秒/分/時間、3600 または 1 時間など) [デフォルトは 1 時間]:" bantime
+ read -e -p "タイムウィンドウ findtime (秒/分/時間、例: 600 または 10 分) [デフォルトは 10 分]:" findtime
+ read -e -p "再試行回数 maxretry (整数) [デフォルト 5]:" maxretry
+
+ bantime=${bantime:-1h}
+ findtime=${findtime:-10m}
+ maxretry=${maxretry:-5}
+
+ mkdir -p /etc/fail2ban/jail.d
+ cat > /etc/fail2ban/jail.d/sshd.local <> /etc/fail2ban/jail.d/sshd.local
+ fi
+ fi
+
+ echo -e "${gl_lv}設定が書き込まれました${gl_bai}: /etc/fail2ban/jail.d/sshd.local"
+ fail2ban-client reload >/dev/null 2>&1 || true
+ sleep 2
+ fail2ban-client status $jail_name || true
+}
+
+# メイン構成/オーバーレイ構成エディター (nano) を直接開きます。
+# 最初に /etc/fail2ban/jail.d/sshd.local を編集し (より安全です)、存在しない場合は作成します
+f2b_edit_config() {
+ root_use
+ install nano
+
+ if [ ! -d /etc/fail2ban ]; then
+ echo -e "${gl_hui}/etc/fail2ban が存在しません。まず、fail2ban をインストールしてください。${gl_bai}"
+ return
+ fi
+
+ mkdir -p /etc/fail2ban/jail.d
+ local cfg="/etc/fail2ban/jail.d/sshd.local"
+ [ -f "$cfg" ] || printf "[sshd]\n# bantime/findtime/maxretry\n" > "$cfg"
+
+ nano "$cfg"
+ echo -e "${gl_lv}保存されました${gl_bai}、fail2ban をリロードしています..."
+ fail2ban-client reload >/dev/null 2>&1 || true
+}
@@ -3051,7 +3304,7 @@ server_reboot() {
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}现在重启服务器吗?(Y/N): ")" rboot
case "$rboot" in
[Yy])
- echo "再起動"
+ echo "再起動しました"
reboot
;;
*)
@@ -3098,8 +3351,8 @@ ldnmp_install_status_one() {
if docker inspect "php" &>/dev/null; then
clear
- send_stats "LDNMP環境を再度インストールできません"
- echo -e "${gl_huang}ヒント:${gl_bai}ウェブサイトの建設環境がインストールされています。再度インストールする必要はありません!"
+ send_stats "LDNMP環境を再インストールできません"
+ echo -e "${gl_huang}ヒント:${gl_bai}ウェブサイト構築環境を導入しました。再度インストールする必要はありません。"
break_end
linux_ldnmp
fi
@@ -3109,12 +3362,11 @@ ldnmp_install_status_one() {
ldnmp_install_all() {
cd ~
-send_stats "LDNMP環境をインストールします"
+send_stats "LDNMP環境をインストールする"
root_use
clear
-echo -e "${gl_huang}LDNMP環境はインストールされていません。LDNMP環境のインストールを開始します...${gl_bai}"
-check_disk_space 3
-check_port
+echo -e "${gl_huang}LDNMP環境がインストールされていません。 LDNMP 環境のインストールを開始します...${gl_bai}"
+check_disk_space 3 /home
install_dependency
install_docker
install_certbot
@@ -3126,12 +3378,10 @@ install_ldnmp
nginx_install_all() {
cd ~
-send_stats "Nginx環境をインストールします"
+send_stats "nginx環境をインストールする"
root_use
clear
-echo -e "${gl_huang}nginxはインストールされていません、nginx環境のインストールを開始します...${gl_bai}"
-check_disk_space 1
-check_port
+echo -e "${gl_huang}nginx がインストールされていません。nginx 環境のインストールを開始してください...${gl_bai}"
install_dependency
install_docker
install_certbot
@@ -3140,8 +3390,8 @@ nginx_upgrade
clear
local nginx_version=$(docker exec nginx nginx -v 2>&1)
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
-echo "Nginxがインストールされています"
-echo -e "現在のバージョン:${gl_huang}v$nginx_version${gl_bai}"
+echo "nginxがインストールされました"
+echo -e "現在のバージョン:${gl_huang}v$nginx_version${gl_bai}"
echo ""
}
@@ -3152,7 +3402,7 @@ echo ""
ldnmp_install_status() {
if ! docker inspect "php" &>/dev/null; then
- send_stats "LDNMP環境を最初にインストールしてください"
+ send_stats "最初に LDNMP 環境をインストールしてください"
ldnmp_install_all
fi
@@ -3162,7 +3412,7 @@ ldnmp_install_status() {
nginx_install_status() {
if ! docker inspect "nginx" &>/dev/null; then
- send_stats "最初にNGINX環境をインストールしてください"
+ send_stats "まずnginx環境をインストールしてください"
nginx_install_all
fi
@@ -3173,7 +3423,7 @@ nginx_install_status() {
ldnmp_web_on() {
clear
- echo "あなたの$webname建てられた!"
+ echo "あなたの$webname建てられました!"
echo "https://$yuming"
echo "------------------------"
echo "$webnameインストール情報は次のとおりです。"
@@ -3181,10 +3431,21 @@ ldnmp_web_on() {
}
nginx_web_on() {
- clear
- echo "あなたの$webname建てられた!"
- echo "https://$yuming"
+ clear
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+ echo "あなたの$webname建てられました!"
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ mv /home/web/conf.d/"$yuming".conf /home/web/conf.d/"${yuming}_${access_port}".conf
+ echo "http://$yuming:$access_port"
+ elif grep -q '^[[:space:]]*#.*if (\$scheme = http)' "/home/web/conf.d/"$yuming".conf"; then
+ echo "http://$yuming"
+ else
+ echo "https://$yuming"
+ fi
}
@@ -3195,73 +3456,95 @@ ldnmp_wp() {
webname="WordPress"
yuming="${1:-}"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
if [ -z "$yuming" ]; then
add_yuming
fi
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/wordpress.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
nginx_http_on
+
cd /home/web/html
mkdir $yuming
cd $yuming
wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/refs/heads/main/wp-latest.zip
- # wget -O latest.zip https://cn.wordpress.org/latest-zh_CN.zip
- # wget -O latest.zip https://wordpress.org/latest.zip
unzip latest.zip
rm latest.zip
- echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
+ echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379'); define('WP_REDIS_MAXTTL', 86400); define('WP_CACHE_KEY_SALT', '${yuming}_');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|database_name_here|$dbname|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|username_here|$dbuse|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|password_here|$dbusepasswd|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|localhost|mysql|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
+ patch_wp_url "https://$yuming" "https://$yuming"
cp /home/web/html/$yuming/wordpress/wp-config-sample.php /home/web/html/$yuming/wordpress/wp-config.php
+
restart_ldnmp
nginx_web_on
-# エコー「データベース名:$ dbname」
-# エコー「ユーザー名:$ dbuse」
-# エコー「パスワード:$ dbusepasswd」
-# エコー「データベースアドレス:mysql」
-# エコー「テーブルプレフィックス:WP_」
}
+
ldnmp_Proxy() {
clear
- webname="反向代理-IP+端口"
+ webname="リバースプロキシ IP+ポート"
yuming="${1:-}"
reverseproxy="${2:-}"
port="${3:-}"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
if [ -z "$yuming" ]; then
add_yuming
fi
+
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy" ]; then
- read -e -p "抗ジェネレーションIPを入力してください:" reverseproxy
+ read -e -p "アンチジェネレーション IP を入力してください (Enter キーを押すと、デフォルトのローカル IP 127.0.0.1 になります)。" reverseproxy
+ reverseproxy=${reverseproxy:-127.0.0.1}
fi
if [ -z "$port" ]; then
- read -e -p "発生防止ポートを入力してください。" port
+ read -e -p "アンチジェネレーションポートを入力してください:" port
fi
nginx_install_status
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
+ wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/backend_$backend/g" /home/web/conf.d/"$yuming".conf
+
+
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$reverseproxy/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$port|g" /home/web/conf.d/$yuming.conf
+
+ reverseproxy_port="$reverseproxy:$port"
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 動的に追加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i '/remote_addr/d' /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3271,24 +3554,25 @@ ldnmp_Proxy() {
ldnmp_Proxy_backend() {
clear
- webname="反向代理-负载均衡"
- yuming="${1:-}"
- reverseproxy_port="${2:-}"
+ webname="リバースプロキシ負荷分散"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
if [ -z "$yuming" ]; then
add_yuming
fi
- # ユーザーが入力した複数のIPを取得:ポート(スペースで区切られています)
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy_port" ]; then
- read -e -p "スペースで区切られた複数の生成防止IP+ポートを入力してください(たとえば、127.0.0.1:3000 127.0.1:3002):" reverseproxy_port
+ read -e -p "複数のアンチジェネレーション IP + ポートをスペースで区切って入力してください (例: 127.0.0.1:3000 127.0.0.1:3002):" reverseproxy_port
fi
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
@@ -3298,14 +3582,15 @@ ldnmp_Proxy_backend() {
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- # 上流の構成を動的に生成します
upstream_servers=""
for server in $reverseproxy_port; do
upstream_servers="$upstream_servers server $server;\n"
done
- # テンプレートのプレースホルダーを交換します
- sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i "s/# 動的に追加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+
+
+ update_nginx_listen_port "$yuming" "$access_port"
nginx_http_on
docker exec nginx nginx -s reload
@@ -3314,6 +3599,200 @@ ldnmp_Proxy_backend() {
+
+
+
+list_stream_services() {
+
+ STREAM_DIR="/home/web/stream.d"
+ printf "%-25s %-18s %-25s %-20s\n" "サービス名" "通信タイプ" "ローカルアドレス" "バックエンドアドレス"
+
+ if [ -z "$(ls -A "$STREAM_DIR")" ]; then
+ return
+ fi
+
+ for conf in "$STREAM_DIR"/*; do
+ # サービス名はファイル名を取得します
+ service_name=$(basename "$conf" .conf)
+
+ # 上流ブロックでサーバーのバックエンド IP:ポートを取得します。
+ backend=$(grep -Po '(?<=server )[^;]+' "$conf" | head -n1)
+
+ # リッスンポートの取得
+ listen_port=$(grep -Po '(?<=listen )[^;]+' "$conf" | head -n1)
+
+ # デフォルトのローカルIP
+ ip_address
+ local_ip="$ipv4_address"
+
+ # ファイル名の接尾辞または内容から最初に判断して通信タイプを取得します
+ if grep -qi 'udp;' "$conf"; then
+ proto="udp"
+ else
+ proto="tcp"
+ fi
+
+ # スプライス リスニング IP:ポート
+ local_addr="$local_ip:$listen_port"
+
+ printf "%-22s %-14s %-21s %-20s\n" "$service_name" "$proto" "$local_addr" "$backend"
+ done
+}
+
+
+
+
+
+
+
+
+
+stream_panel() {
+ send_stats "ストリーム 4 層プロキシ"
+ local app_id="104"
+ local docker_name="nginx"
+
+ while true; do
+ clear
+ check_docker_app
+ check_docker_image_update $docker_name
+ echo -e "ストリーム 4 層プロキシ転送ツール$check_docker $update_status"
+ echo "NGINX Stream は NGINX の TCP/UDP プロキシ モジュールであり、高性能のトランスポート層トラフィック転送とロード バランシングを実現するために使用されます。"
+ echo "------------------------"
+ if [ -d "/home/web/stream.d" ]; then
+ list_stream_services
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. インストール 2. アップデート 3. アンインストール"
+ echo "------------------------"
+ echo "4. 転送サービスの追加 5. 転送サービスの変更 6. 転送サービスの削除"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択内容を入力してください:" choice
+ case $choice in
+ 1)
+ nginx_install_status
+ add_app_id
+ send_stats "Stream 4 層エージェントのインストール"
+ ;;
+ 2)
+ update_docker_compose_with_db_creds
+ nginx_upgrade
+ add_app_id
+ send_stats "ストリームの 4 層プロキシを更新します"
+ ;;
+ 3)
+ read -e -p "nginx コンテナを削除してもよろしいですか?これはウェブサイトの機能に影響を与える可能性があります。 (y/N):" confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ docker rm -f nginx
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "ストリームの 4 層プロキシを更新します"
+ echo "nginxコンテナは削除されました。"
+ else
+ echo "操作はキャンセルされました。"
+ fi
+
+ ;;
+
+ 4)
+ ldnmp_Proxy_backend_stream
+ add_app_id
+ send_stats "レイヤー 4 プロキシを追加する"
+ ;;
+ 5)
+ send_stats "転送設定の編集"
+ read -e -p "編集するサービス名を入力してください:" stream_name
+ install nano
+ nano /home/web/stream.d/$stream_name.conf
+ docker restart nginx
+ send_stats "レイヤ 4 プロキシを変更する"
+ ;;
+ 6)
+ send_stats "転送設定の削除"
+ read -e -p "削除するサービス名を入力してください:" stream_name
+ rm /home/web/stream.d/$stream_name.conf > /dev/null 2>&1
+ docker restart nginx
+ send_stats "レイヤ 4 プロキシを削除する"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ break_end
+ done
+}
+
+
+
+ldnmp_Proxy_backend_stream() {
+ clear
+ webname="ストリーム 4 層プロキシ負荷分散"
+
+ send_stats "インストール$webname"
+ echo "導入を開始する$webname"
+
+ # エージェント名を取得する
+ read -erp "プロキシ転送名を入力してください (例: mysql_proxy):" proxy_name
+ if [ -z "$proxy_name" ]; then
+ echo "名前を空にすることはできません"; return 1
+ fi
+
+ # リスニングポートの取得
+ read -erp "ローカルのリスニング ポート (3306 など) を入力してください。" listen_port
+ if ! [[ "$listen_port" =~ ^[0-9]+$ ]]; then
+ echo "ポートは数値である必要があります"; return 1
+ fi
+
+ echo "契約の種類を選択してください:"
+ echo "1. TCP 2. UDP"
+ read -erp "シリアル番号を入力してください [1-2]:" proto_choice
+
+ case "$proto_choice" in
+ 1) proto="tcp"; listen_suffix="" ;;
+ 2) proto="udp"; listen_suffix=" udp" ;;
+ *) echo "無効な選択"; return 1 ;;
+ esac
+
+ read -e -p "1 つ以上のバックエンド IP + ポートをスペースで区切って入力してください (例: 10.13.0.2:3306 10.13.0.3:3306)。" reverseproxy_port
+
+ nginx_install_status
+ cd /home && mkdir -p web/stream.d
+ grep -q '^[[:space:]]*stream[[:space:]]*{' /home/web/nginx.conf || echo -e '\nstream {\n include /etc/nginx/stream.d/*.conf;\n}' | tee -a /home/web/nginx.conf
+ wget -O /home/web/stream.d/$proxy_name.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend-stream.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/${proxy_name}_${backend}/g" /home/web/stream.d/"$proxy_name".conf
+ sed -i "s|listen 80|listen $listen_port $listen_suffix|g" /home/web/stream.d/$proxy_name.conf
+ sed -i "s|listen \[::\]:|listen [::]:${listen_port} ${listen_suffix}|g" "/home/web/stream.d/${proxy_name}.conf"
+
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 動的に追加/$upstream_servers/g" /home/web/stream.d/$proxy_name.conf
+
+ docker exec nginx nginx -s reload
+ clear
+ echo "あなたの$webname建てられました!"
+ echo "------------------------"
+ echo "訪問先住所:"
+ ip_address
+ if [ -n "$ipv4_address" ]; then
+ echo "$ipv4_address:${listen_port}"
+ fi
+ if [ -n "$ipv6_address" ]; then
+ echo "$ipv6_address:${listen_port}"
+ fi
+ echo ""
+}
+
+
+
+
+
find_container_by_host_port() {
port="$1"
docker_name=$(docker ps --format '{{.ID}} {{.Names}}' | while read id name; do
@@ -3331,20 +3810,19 @@ ldnmp_web_status() {
root_use
while true; do
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
- local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+ local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
- local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+ local db_output="${gl_lv}${db_count}${gl_bai}"
clear
- send_stats "LDNMPサイト管理"
+ send_stats "LDNMP サイト管理"
echo "LDNMP環境"
echo "------------------------"
ldnmp_v
- # ls -t /home/web/conf.d | sed 's/\.[^.]*$//'
- echo -e "${output}証明書の有効期限"
+ echo -e "サイト:${output}証明書の有効期限"
echo -e "------------------------"
for cert_file in /home/web/certs/*_cert.pem; do
local domain=$(basename "$cert_file" | sed 's/_cert.pem//')
@@ -3355,9 +3833,28 @@ ldnmp_web_status() {
fi
done
+ for conf_file in /home/web/conf.d/*_*.conf; do
+ [ -e "$conf_file" ] || continue
+ basename "$conf_file" .conf
+ done
+
+ for conf_file in /home/web/conf.d/*.conf; do
+ [ -e "$conf_file" ] || continue
+
+ filename=$(basename "$conf_file")
+
+ if [ "$filename" = "map.conf" ] || [ "$filename" = "default.conf" ]; then
+ continue
+ fi
+
+ if ! grep -q "ssl_certificate" "$conf_file"; then
+ basename "$conf_file" .conf
+ fi
+ done
+
echo "------------------------"
echo ""
- echo -e "${db_output}"
+ echo -e "データベース:${db_output}"
echo -e "------------------------"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys"
@@ -3369,48 +3866,44 @@ ldnmp_web_status() {
echo -e "データ${gl_hui}/home/web/html${gl_bai}証明書${gl_hui}/home/web/certs${gl_bai}構成${gl_hui}/home/web/conf.d${gl_bai}"
echo "------------------------"
echo ""
- echo "動作します"
+ echo "操作する"
echo "------------------------"
- echo "1.ドメイン名証明書を適用/更新する2。サイトドメイン名を変更します"
- echo "3.サイトキャッシュをクリーンアップ4。関連するサイトを作成する"
- echo "5.アクセスログを表示6。エラーログを表示します"
- echo "7.グローバル構成の編集8。サイト構成の編集"
- echo "9.サイトデータベースの管理10。サイト分析レポートを表示します"
+ echo "1. ドメイン名証明書の適用/更新 2. サイトのドメイン名の複製"
+ echo "3. サイトのキャッシュをクリアします。 4. 関連するサイトを作成します。"
+ echo "5. アクセスログの表示 6. エラーログの表示"
+ echo "7. グローバル構成の編集 8. サイト構成の編集"
+ echo "9. サイトデータベースの管理 10. サイト分析レポートの表示"
echo "------------------------"
- echo "20.指定されたサイトデータを削除します"
+ echo "20. 指定したサイトデータを削除する"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- send_stats "ドメイン名証明書を申請します"
- read -e -p "ドメイン名を入力してください:" yuming
+ send_stats "ドメイン名証明書を申請する"
+ read -e -p "ドメイン名を入力してください:" yuming
install_certbot
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
;;
2)
- send_stats "サイトドメイン名を変更します"
- echo -e "${gl_hong}強くお勧めします:${gl_bai}最初にサイトデータ全体をバックアップしてから、サイトドメイン名を変更します!"
- read -e -p "古いドメイン名を入力してください:" oddyuming
- read -e -p "新しいドメイン名を入力してください:" yuming
+ send_stats "クローンサイトのドメイン名"
+ read -e -p "古いドメイン名を入力してください:" oddyuming
+ read -e -p "新しいドメイン名を入力してください:" yuming
install_certbot
install_ssltls
certs_status
- # MySQLの交換
- add_db
+ add_db
local odd_dbname=$(echo "$oddyuming" | sed -e 's/[^A-Za-z0-9]/_/g')
local odd_dbname="${odd_dbname}"
docker exec mysql mysqldump -u root -p"$dbrootpasswd" $odd_dbname | docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname
- docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE $odd_dbname;"
-
local tables=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -D $dbname -e "SHOW TABLES;" | awk '{ if (NR>1) print $1 }')
for table in $tables; do
@@ -3420,19 +3913,16 @@ ldnmp_web_status() {
done
done
- # ウェブサイトディレクトリの交換
- mv /home/web/html/$oddyuming /home/web/html/$yuming
+ # Web サイトのディレクトリの置き換え
+ cp -r /home/web/html/$oddyuming /home/web/html/$yuming
find /home/web/html/$yuming -type f -exec sed -i "s/$odd_dbname/$dbname/g" {} +
find /home/web/html/$yuming -type f -exec sed -i "s/$oddyuming/$yuming/g" {} +
- mv /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
+ cp /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
sed -i "s/$oddyuming/$yuming/g" /home/web/conf.d/$yuming.conf
- rm /home/web/certs/${oddyuming}_key.pem
- rm /home/web/certs/${oddyuming}_cert.pem
-
- docker exec nginx nginx -s reload
+ cd /home/web && docker compose restart
;;
@@ -3441,10 +3931,10 @@ ldnmp_web_status() {
web_cache
;;
4)
- send_stats "関連するサイトを作成します"
- echo -e "アクセスのための既存のサイトの新しいドメイン名を関連付ける"
- read -e -p "既存のドメイン名を入力してください:" oddyuming
- read -e -p "新しいドメイン名を入力してください:" yuming
+ send_stats "関連サイトの作成"
+ echo -e "新しいドメイン名を既存のサイトに関連付けてアクセスする"
+ read -e -p "既存のドメイン名を入力してください:" oddyuming
+ read -e -p "新しいドメイン名を入力してください:" yuming
install_certbot
install_ssltls
certs_status
@@ -3458,25 +3948,25 @@ ldnmp_web_status() {
;;
5)
- send_stats "アクセスログを表示します"
+ send_stats "アクセスログを見る"
tail -n 200 /home/web/log/nginx/access.log
break_end
;;
6)
- send_stats "エラーログを表示します"
+ send_stats "エラーログを表示する"
tail -n 200 /home/web/log/nginx/error.log
break_end
;;
7)
- send_stats "グローバル構成を編集します"
+ send_stats "グローバル構成の編集"
install nano
nano /home/web/nginx.conf
docker exec nginx nginx -s reload
;;
8)
- send_stats "サイト構成を編集します"
- read -e -p "サイト構成を編集するには、編集するドメイン名を入力してください。" yuming
+ send_stats "サイト構成を編集する"
+ read -e -p "サイト構成を編集するには、編集するドメイン名を入力してください:" yuming
install nano
nano /home/web/conf.d/$yuming.conf
docker exec nginx nginx -s reload
@@ -3486,14 +3976,14 @@ ldnmp_web_status() {
break_end
;;
10)
- send_stats "サイトデータを表示します"
+ send_stats "サイトデータの表示"
install goaccess
goaccess --log-format=COMBINED /home/web/log/nginx/access.log
;;
20)
web_del
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
;;
*)
@@ -3508,7 +3998,7 @@ ldnmp_web_status() {
check_panel_app() {
if $lujing > /dev/null 2>&1; then
- check_panel="${gl_lv}已安装${gl_bai}"
+ check_panel="${gl_lv}インストール済み${gl_bai}"
else
check_panel=""
fi
@@ -3522,32 +4012,38 @@ while true; do
clear
check_panel_app
echo -e "$panelname $check_panel"
- echo "${panelname}最近では人気のある強力な運用および保守管理パネルです。"
- echo "公式ウェブサイトの紹介:$panelurl "
+ echo "${panelname}人気の強力な運用保守管理盤です。"
+ echo "公式サイト紹介:$panelurl "
echo ""
echo "------------------------"
- echo "1。インストール2。管理3。アンインストール"
+ echo "1. インストール 2. 管理 3. アンインストール"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case $choice in
1)
check_disk_space 1
install wget
iptables_open
panel_app_install
+
+ add_app_id
send_stats "${panelname}インストール"
;;
2)
panel_app_manage
+
+ add_app_id
send_stats "${panelname}コントロール"
;;
3)
panel_app_uninstall
- send_stats "${panelname}アンインストール"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "${panelname}アンインストールする"
;;
*)
break
@@ -3563,9 +4059,9 @@ done
check_frp_app() {
if [ -d "/home/frp/" ]; then
- check_frp="${gl_lv}已安装${gl_bai}"
+ check_frp="${gl_lv}インストール済み${gl_bai}"
else
- check_frp="${gl_hui}未安装${gl_bai}"
+ check_frp="${gl_hui}インストールされていません${gl_bai}"
fi
}
@@ -3591,8 +4087,8 @@ donlond_frp() {
generate_frps_config() {
- send_stats "FRPサーバーをインストールします"
- # ランダムポートと資格情報を生成します
+ send_stats "FRPサーバーをインストールする"
+ # ランダムなポートと認証情報を生成する
local bind_port=8055
local dashboard_port=8056
local token=$(openssl rand -hex 16)
@@ -3613,17 +4109,17 @@ EOF
donlond_frp frps
- # 出力生成情報
+ # 生成された情報を出力する
ip_address
echo "------------------------"
- echo "クライアントの展開に必要なパラメーター"
- echo "サービスIP:$ipv4_address"
+ echo "クライアントの展開に必要なパラメータ"
+ echo "サービスIP:$ipv4_address"
echo "token: $token"
echo
echo "FRPパネル情報"
- echo "FRPパネルアドレス:http://$ipv4_address:$dashboard_port"
- echo "FRPパネルのユーザー名:$dashboard_user"
- echo "FRPパネルパスワード:$dashboard_pwd"
+ echo "FRPパネルアドレス:http://$ipv4_address:$dashboard_port"
+ echo "FRP パネルのユーザー名:$dashboard_user"
+ echo "FRPパネルのパスワード:$dashboard_pwd"
echo
open_port 8055 8056
@@ -3633,9 +4129,9 @@ EOF
configure_frpc() {
- send_stats "FRPクライアントをインストールします"
- read -e -p "外部ネットワークドッキングIPを入力してください:" server_addr
- read -e -p "外部ネットワークドッキングトークンを入力してください:" token
+ send_stats "FRPクライアントをインストールする"
+ read -e -p "外部ネットワークのドッキング IP を入力してください:" server_addr
+ read -e -p "外部ネットワーク ドッキング トークンを入力してください:" token
echo
mkdir -p /home/frp
@@ -3655,17 +4151,17 @@ EOF
}
add_forwarding_service() {
- send_stats "FRPイントラネットサービスを追加します"
- # ユーザーにサービス名と転送情報を入力するように促します
- read -e -p "サービス名を入力してください:" service_name
- read -e -p "転送タイプ(TCP/UDP)を入力してください[デフォルトTCPを入力]:" service_type
+ send_stats "FRPイントラネットサービスを追加"
+ # ユーザーにサービス名と転送情報の入力を求めるプロンプトを表示します
+ read -e -p "サービス名を入力してください:" service_name
+ read -e -p "転送タイプ (tcp/udp) を入力してください [デフォルトで tcp を入力する]:" service_type
local service_type=${service_type:-tcp}
- read -e -p "イントラネットIPを入力してください[デフォルト127.0.0.1を入力]:" local_ip
+ read -e -p "イントラネット IP を入力してください [Enter キーを押すときのデフォルトは 127.0.0.1]:" local_ip
local local_ip=${local_ip:-127.0.0.1}
- read -e -p "イントラネットポートを入力してください:" local_port
- read -e -p "外部ネットワークポートを入力してください:" remote_port
+ read -e -p "イントラネット ポートを入力してください:" local_port
+ read -e -p "外部ネットワーク ポートを入力してください:" remote_port
- # ユーザー入力を構成ファイルに書き込みます
+ # ユーザー入力を構成ファイルに書き込む
cat <> /home/frp/frpc.toml
[$service_name]
type = ${service_type}
@@ -3675,8 +4171,8 @@ remote_port = ${remote_port}
EOF
- # 出力生成情報
- echo "仕える$service_nameFRPC.TOMLに正常に追加されました"
+ # 生成された情報を出力する
+ echo "仕える$service_namefrpc.toml に正常に追加されました"
docker restart frpc
@@ -3687,12 +4183,12 @@ EOF
delete_forwarding_service() {
- send_stats "FRPイントラネットサービスを削除します"
- # ユーザーに削除する必要があるサービス名を入力するように促します
- read -e -p "削除する必要があるサービス名を入力してください:" service_name
- # SEDを使用して、サービスとその関連構成を削除します
+ send_stats "FRPイントラネットサービスの削除"
+ # 削除する必要があるサービスの名前を入力するようにユーザーに求めます
+ read -e -p "削除するサービス名を入力してください:" service_name
+ # sed を使用してサービスとその関連構成を削除します
sed -i "/\[$service_name\]/,/^$/d" /home/frp/frpc.toml
- echo "仕える$service_nameFRPC.TOMLから削除されました"
+ echo "仕える$service_namefrpc.toml から正常に削除されました"
docker restart frpc
@@ -3703,7 +4199,7 @@ list_forwarding_services() {
local config_file="$1"
# ヘッダーを印刷します
- printf "%-20s %-25s %-30s %-10s\n" "服务名称" "内网地址" "外网地址" "协议"
+ printf "%-20s %-25s %-30s %-10s\n" "サービス名" "イントラネットアドレス" "外部ネットワークアドレス" "プロトコル"
awk '
BEGIN {
@@ -3723,7 +4219,7 @@ list_forwarding_services() {
}
/^\[.*\]/ {
- # サービス情報がある場合は、新しいサービスを処理する前に現在のサービスを印刷します
+ # サービス情報がすでに存在する場合は、新しいサービスを処理する前に現在のサービスを出力します。
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3736,7 +4232,7 @@ list_forwarding_services() {
if ($1 != "[common]") {
gsub(/[\[\]]/, "", $1)
current_service=$1
- # 前の値をクリアします
+ # 前回の値をクリア
local_ip=""
local_port=""
remote_port=""
@@ -3765,7 +4261,7 @@ list_forwarding_services() {
}
END {
- # 最後のサービスの情報を印刷します
+ # 最後のサービスに関する情報を出力します
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3778,17 +4274,17 @@ list_forwarding_services() {
-# FRPサーバーポートを取得します
+# FRPサーバーポートの取得
get_frp_ports() {
mapfile -t ports < <(ss -tulnape | grep frps | awk '{print $5}' | awk -F':' '{print $NF}' | sort -u)
}
-# アクセスアドレスを生成します
+# アクセスアドレスの生成
generate_access_urls() {
- # 最初にすべてのポートを取得します
+ # まずすべてのポートを取得します
get_frp_ports
- # 8055/8056以外のポートがあるかどうかを確認してください
+ # 8055/8056以外のポートがあるか確認する
local has_valid_ports=false
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3801,14 +4297,14 @@ generate_access_urls() {
if [ "$has_valid_ports" = true ]; then
echo "FRPサービス外部アクセスアドレス:"
- # IPv4アドレスを処理します
+ # IPv4 アドレスの処理
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
echo "http://${ipv4_address}:${port}"
fi
done
- # IPv6アドレスを処理する(存在する場合)
+ # IPv6 アドレスが存在する場合は処理します
if [ -n "$ipv6_address" ]; then
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3817,7 +4313,7 @@ generate_access_urls() {
done
fi
- # HTTPS構成の処理
+ # HTTPS 構成の処理
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
local frps_search_pattern="${ipv4_address}:${port}"
@@ -3845,6 +4341,7 @@ frps_main_ports() {
frps_panel() {
send_stats "FRPサーバー"
+ local app_id="55"
local docker_name="frps"
local docker_port=8056
while true; do
@@ -3852,30 +4349,32 @@ frps_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRPサーバー$check_frp $update_status"
- echo "FRPイントラネット侵入サービス環境を構築して、パブリックIPなしでインターネットにデバイスを公開する"
- echo "公式ウェブサイトの紹介:https://github.com/fatedier/frp/"
- echo "ビデオ教育:https://www.bilibili.com/video/bv1ymw6e2ewl?t=124.0"
+ echo "FRPイントラネットペネトレーションサービス環境を構築し、パブリックIPを持たないデバイスをインターネットに公開"
+ echo "公式サイト紹介:${gh_https_url}github.com/fatedier/frp/"
+ echo "ビデオチュートリアル: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
if [ -d "/home/frp/" ]; then
check_docker_app_ip
frps_main_ports
fi
echo ""
echo "------------------------"
- echo "1。インストール2。更新3。アンインストール"
+ echo "1. インストール 2. アップデート 3. アンインストール"
echo "------------------------"
- echo "5。イントラネットサービスのドメイン名アクセス6。ドメイン名アクセスを削除する"
+ echo "5. イントラネット サービスのドメイン名アクセス 6. ドメイン名アクセスの削除"
echo "------------------------"
- echo "7. IP+ポートアクセスを許可8。BlockIP+ポートアクセス"
+ echo "7. IP+ポートアクセスを許可します。 8. IP+ポートアクセスをブロックします。"
echo "------------------------"
- echo "00。サービスのステータスを更新します0。前のメニューに戻ります"
+ echo "00. サービスステータスを更新します。 0. 前のメニューに戻ります。"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
install jq grep ss
install_docker
generate_frps_config
- echo "FRPサーバーがインストールされています"
+
+ add_app_id
+ echo "FRPサーバーを導入しました"
;;
2)
crontab -l | grep -v 'frps' | crontab - > /dev/null 2>&1
@@ -3883,7 +4382,9 @@ frps_panel() {
docker rm -f frps && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frps.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frps.toml /home/frp/frps.toml
donlond_frp frps
- echo "FRPサーバーが更新されました"
+
+ add_app_id
+ echo "FRPサーバーを更新しました"
;;
3)
crontab -l | grep -v 'frps' | crontab - > /dev/null 2>&1
@@ -3893,37 +4394,38 @@ frps_panel() {
close_port 8055 8056
- echo "アプリはアンインストールされています"
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "アプリがアンインストールされました"
;;
5)
- echo "ドメイン名アクセスへの逆イントラネット侵入サービス"
- send_stats "FRP外部ドメイン名へのアクセス"
+ echo "ドメイン名アクセスへのイントラネット侵入サービスのリバース"
+ send_stats "FRP 外部ドメイン名アクセス"
add_yuming
- read -e -p "イントラネット侵入サービスポートを入力してください:" frps_port
+ read -e -p "イントラネット侵入サービス ポートを入力してください:" frps_port
ldnmp_Proxy ${yuming} 127.0.0.1 ${frps_port}
block_host_port "$frps_port" "$ipv4_address"
;;
6)
- echo "ドメイン名フォーマットexample.comにはhttps://が付属していません"
+ echo "ドメイン名の形式 example.com (https:// なし)"
web_del
;;
7)
- send_stats "IPアクセスを許可します"
- read -e -p "リリースするポートを入力してください:" frps_port
+ send_stats "IPアクセスを許可する"
+ read -e -p "解放する必要があるポートを入力してください:" frps_port
clear_host_port_rules "$frps_port" "$ipv4_address"
;;
8)
- send_stats "IPアクセスをブロックします"
- echo "アンチジェネレーションドメイン名にアクセスした場合、この関数を使用して、より安全なIP+ポートアクセスをブロックできます。"
- read -e -p "ブロックする必要があるポートを入力してください。" frps_port
+ send_stats "IPアクセスをブロックする"
+ echo "ドメイン名アクセスを反転している場合は、この機能を使用して IP+ポート アクセスをブロックすることができ、より安全です。"
+ read -e -p "ブロックするポートを入力してください:" frps_port
block_host_port "$frps_port" "$ipv4_address"
;;
00)
- send_stats "FRPサービスステータスを更新します"
- echo "FRPサービスステータスは更新されました"
+ send_stats "FRPサービスステータスを更新"
+ echo "FRPサービスステータスが更新されました"
;;
*)
@@ -3937,6 +4439,7 @@ frps_panel() {
frpc_panel() {
send_stats "FRPクライアント"
+ local app_id="56"
local docker_name="frpc"
local docker_port=8055
while true; do
@@ -3944,9 +4447,9 @@ frpc_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRPクライアント$check_frp $update_status"
- echo "サーバーでドッキングした後、ドッキングした後、インターネットへのアクセスにイントラネット侵入サービスを作成できます"
- echo "公式ウェブサイトの紹介:https://github.com/fatedier/frp/"
- echo "ビデオ教育:https://www.bilibili.com/video/bv1ymw6e2ewl?t=173.9"
+ echo "サーバーに接続します。接続後、インターネットにアクセスするためのイントラネット侵入サービスを作成できます。"
+ echo "公式サイト紹介:${gh_https_url}github.com/fatedier/frp/"
+ echo "ビデオチュートリアル: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
echo "------------------------"
if [ -d "/home/frp/" ]; then
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
@@ -3954,18 +4457,20 @@ frpc_panel() {
fi
echo ""
echo "------------------------"
- echo "1。インストール2。更新3。アンインストール"
+ echo "1. インストール 2. アップデート 3. アンインストール"
echo "------------------------"
- echo "4.外部サービスの追加5.外部サービスを削除6。サービスを手動で構成する"
+ echo "4. 外部サービスの追加 5. 外部サービスの削除 6. サービスの手動構成"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
install jq grep ss
install_docker
configure_frpc
+
+ add_app_id
echo "FRPクライアントがインストールされています"
;;
2)
@@ -3974,6 +4479,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
donlond_frp frpc
+
+ add_app_id
echo "FRPクライアントが更新されました"
;;
@@ -3983,7 +4490,9 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine
rm -rf /home/frp
close_port 8055
- echo "アプリはアンインストールされています"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "アプリがアンインストールされました"
;;
4)
@@ -4013,6 +4522,7 @@ frpc_panel() {
yt_menu_pro() {
+ local app_id="66"
local VIDEO_DIR="/home/yt-dlp"
local URL_FILE="$VIDEO_DIR/urls.txt"
local ARCHIVE_FILE="$VIDEO_DIR/archive.txt"
@@ -4022,68 +4532,74 @@ yt_menu_pro() {
while true; do
if [ -x "/usr/local/bin/yt-dlp" ]; then
- local YTDLP_STATUS="${gl_lv}已安装${gl_bai}"
+ local YTDLP_STATUS="${gl_lv}インストール済み${gl_bai}"
else
- local YTDLP_STATUS="${gl_hui}未安装${gl_bai}"
+ local YTDLP_STATUS="${gl_hui}インストールされていません${gl_bai}"
fi
clear
- send_stats "YT-DLPダウンロードツール"
+ send_stats "yt-dlp ダウンロードツール"
echo -e "yt-dlp $YTDLP_STATUS"
- echo -e "YT-DLPは、YouTube、Bilibili、Twitterなどを含む何千ものサイトをサポートする強力なビデオダウンロードツールです。"
- echo -e "公式ウェブサイトの住所:https://github.com/yt-dlp/yt-dlp"
+ echo -e "yt-dlp は、YouTube、Bilibili、Twitter などの何千ものサイトをサポートする強力な動画ダウンロード ツールです。"
+ echo -e "公式サイトアドレス:${gh_https_url}github.com/yt-dlp/yt-dlp"
echo "-------------------------"
- echo "ダウンロードされたビデオリスト:"
- ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(まだありません)"
+ echo "ダウンロードしたビデオのリスト:"
+ ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(まだありません)"
echo "-------------------------"
- echo "1。インストール2。更新3。アンインストール"
+ echo "1. インストール 2. アップデート 3. アンインストール"
echo "-------------------------"
- echo "5。シングルビデオダウンロード6。バッチビデオダウンロード7。カスタムパラメーターダウンロード"
- echo "8。mp3オーディオ9としてダウンロードします。ビデオディレクトリ10を削除します。クッキー管理(開発中)"
+ echo "5. 単一ビデオのダウンロード 6. バッチビデオのダウンロード 7. カスタムパラメータのダウンロード"
+ echo "8. MP3 オーディオとしてダウンロード 9. ビデオ ディレクトリを削除 10. Cookie 管理 (開発中)"
echo "-------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "-------------------------"
- read -e -p "オプション番号を入力してください:" choice
+ read -e -p "オプション番号を入力してください:" choice
case $choice in
1)
- send_stats "YT-DLPのインストール..."
- echo "YT-DLPのインストール..."
+ send_stats "yt-dlp をインストールしています..."
+ echo "yt-dlp をインストールしています..."
install ffmpeg
- sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
- sudo chmod a+rx /usr/local/bin/yt-dlp
- echo "インストールが完了しました。任意のキーを押して続行します..."
+ curl -L ${gh_https_url}github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ chmod a+rx /usr/local/bin/yt-dlp
+
+ add_app_id
+ echo "インストールが完了しました。続行するには任意のキーを押してください..."
read ;;
2)
- send_stats "yt-dlpを更新..."
- echo "yt-dlpを更新..."
- sudo yt-dlp -U
- echo "更新が完了しました。任意のキーを押して続行します..."
+ send_stats "yt-dlp を更新しています..."
+ echo "yt-dlp を更新しています..."
+ yt-dlp -U
+
+ add_app_id
+ echo "アップデートが完了しました。続行するには任意のキーを押してください..."
read ;;
3)
- send_stats "yt-dlpのアンインストール..."
- echo "yt-dlpのアンインストール..."
- sudo rm -f /usr/local/bin/yt-dlp
- echo "アンインストールが完了しました。任意のキーを押して続行します..."
+ send_stats "yt-dlp をアンインストールしています..."
+ echo "yt-dlp をアンインストールしています..."
+ rm -f /usr/local/bin/yt-dlp
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "アンインストールが完了しました。続行するには任意のキーを押してください..."
read ;;
5)
- send_stats "単一のビデオダウンロード"
- read -e -p "ビデオリンクを入力してください:" url
+ send_stats "単一のビデオのダウンロード"
+ read -e -p "ビデオリンクを入力してください:" url
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "ダウンロードが完了したら、任意のキーを押して続行します..." ;;
+ read -e -p "ダウンロードが完了しました。続行するには任意のキーを押してください..." ;;
6)
- send_stats "バッチビデオのダウンロード"
+ send_stats "ビデオのバッチダウンロード"
install nano
if [ ! -f "$URL_FILE" ]; then
- echo -e "#複数のビデオリンクアドレスを入力\ n#https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.Episode.0" > "$URL_FILE"
+ echo -e "# 複数のビデオ リンク アドレスを入力します\n# https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.episode.0" > "$URL_FILE"
fi
nano $URL_FILE
- echo "バッチダウンロードを開始します..."
+ echo "今すぐバッチダウンロードを開始してください..."
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
@@ -4091,31 +4607,31 @@ yt_menu_pro() {
-a "$URL_FILE" \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "バッチのダウンロードが完了し、任意のキーを押して続行します..." ;;
+ read -e -p "バッチダウンロードが完了しました。続行するには任意のキーを押してください..." ;;
7)
send_stats "カスタムビデオのダウンロード"
- read -e -p "完全なYT-DLPパラメーター(YT-DLPを除く)を入力してください。" custom
+ read -e -p "完全な yt-dlp パラメータを入力してください (yt-dlp を除く)。" custom
yt-dlp -P "$VIDEO_DIR" $custom \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "実行が完了したら、キーを押して続行します..." ;;
+ read -e -p "実行が完了しました。続行するには任意のキーを押してください..." ;;
8)
send_stats "MP3ダウンロード"
- read -e -p "ビデオリンクを入力してください:" url
+ read -e -p "ビデオリンクを入力してください:" url
yt-dlp -P "$VIDEO_DIR" -x --audio-format mp3 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "オーディオのダウンロードが完了しました、任意のキーを押して続行します..." ;;
+ read -e -p "音声のダウンロードが完了しました。続行するには任意のキーを押してください..." ;;
9)
- send_stats "ビデオを削除します"
- read -e -p "削除ビデオの名前を入力してください:" rmdir
+ send_stats "ビデオを削除する"
+ read -e -p "削除されたビデオの名前を入力してください:" rmdir
rm -rf "$VIDEO_DIR/$rmdir"
;;
*)
@@ -4151,7 +4667,7 @@ set_timedate() {
-# DPKG割り込みの問題を修正します
+# dpkgの中断問題を修正
fix_dpkg() {
pkill -9 -f 'apt|dpkg'
rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock
@@ -4160,7 +4676,7 @@ fix_dpkg() {
linux_update() {
- echo -e "${gl_huang}システムの更新...${gl_bai}"
+ echo -e "${gl_kjlan}システムアップデート中です...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
elif command -v yum &>/dev/null; then
@@ -4179,7 +4695,7 @@ linux_update() {
elif command -v opkg &>/dev/null; then
opkg update
else
- echo "不明なパッケージマネージャー!"
+ echo "不明なパッケージマネージャーです!"
return
fi
}
@@ -4187,7 +4703,7 @@ linux_update() {
linux_clean() {
- echo -e "${gl_huang}システムのクリーンアップ...${gl_bai}"
+ echo -e "${gl_kjlan}システムクリーニング中...${gl_bai}"
if command -v dnf &>/dev/null; then
rpm --rebuilddb
dnf autoremove -y
@@ -4216,11 +4732,11 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v apk &>/dev/null; then
- echo "パッケージマネージャーのキャッシュを掃除します..."
+ echo "パッケージマネージャーのキャッシュをクリーンアップ..."
apk cache clean
- echo "システムログを削除してください..."
+ echo "システムログを削除します..."
rm -rf /var/log/*
- echo "APKキャッシュを削除してください..."
+ echo "APKキャッシュを削除..."
rm -rf /var/cache/apk/*
echo "一時ファイルを削除します..."
rm -rf /tmp/*
@@ -4240,23 +4756,23 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v opkg &>/dev/null; then
- echo "システムログを削除してください..."
+ echo "システムログを削除します..."
rm -rf /var/log/*
echo "一時ファイルを削除します..."
rm -rf /tmp/*
elif command -v pkg &>/dev/null; then
- echo "未使用の依存関係をクリーンアップ..."
+ echo "未使用の依存関係をクリーンアップします..."
pkg autoremove -y
- echo "パッケージマネージャーのキャッシュを掃除します..."
+ echo "パッケージマネージャーのキャッシュをクリーンアップ..."
pkg clean -y
- echo "システムログを削除してください..."
+ echo "システムログを削除します..."
rm -rf /var/log/*
echo "一時ファイルを削除します..."
rm -rf /tmp/*
else
- echo "不明なパッケージマネージャー!"
+ echo "不明なパッケージマネージャーです!"
return
fi
return
@@ -4266,10 +4782,17 @@ linux_clean() {
bbr_on() {
-cat > /etc/sysctl.conf << EOF
-net.ipv4.tcp_congestion_control=bbr
-EOF
-sysctl -p
+# カーネルチューニングモジュールとの競合を防ぐためのsysctl.dへの書き込みを統合
+local CONF="/etc/sysctl.d/99-kejilion-bbr.conf"
+mkdir -p /etc/sysctl.d
+echo "net.core.default_qdisc=fq" > "$CONF"
+echo "net.ipv4.tcp_congestion_control=bbr" >> "$CONF"
+
+# 競合を引き起こす可能性のある古い sysctl.conf の残りをクリーンアップします。
+sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf 2>/dev/null
+
+sysctl -p "$CONF" >/dev/null 2>&1 || sysctl --system >/dev/null 2>&1
}
@@ -4278,8 +4801,8 @@ set_dns() {
ip_address
-rm /etc/resolv.conf
-touch /etc/resolv.conf
+chattr -i /etc/resolv.conf
+> /etc/resolv.conf
if [ -n "$ipv4_address" ]; then
echo "nameserver $dns1_ipv4" >> /etc/resolv.conf
@@ -4291,31 +4814,38 @@ if [ -n "$ipv6_address" ]; then
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
fi
+if [ ! -s /etc/resolv.conf ]; then
+ echo "nameserver 223.5.5.5" >> /etc/resolv.conf
+ echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+fi
+
+chattr +i /etc/resolv.conf
+
}
set_dns_ui() {
root_use
-send_stats "DNSを最適化します"
+send_stats "DNSの最適化"
while true; do
clear
- echo "DNSアドレスを最適化します"
+ echo "DNSアドレスを最適化する"
echo "------------------------"
echo "現在のDNSアドレス"
cat /etc/resolv.conf
echo "------------------------"
echo ""
- echo "1。外国DNS最適化:"
+ echo "1. 外部 DNS の最適化:"
echo " v4: 1.1.1.1 8.8.8.8"
echo " v6: 2606:4700:4700::1111 2001:4860:4860::8888"
- echo "2。国内のDNS最適化:"
+ echo "2.国内DNSの最適化:"
echo " v4: 223.5.5.5 183.60.83.19"
echo " v6: 2400:3200::1 2400:da00::6666"
- echo "3。DNS構成を手動で編集します"
+ echo "3. DNS 設定を手動で編集する"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" Limiting
+ read -e -p "選択肢を入力してください:" Limiting
case "$Limiting" in
1)
local dns1_ipv4="1.1.1.1"
@@ -4323,7 +4853,7 @@ while true; do
local dns1_ipv6="2606:4700:4700::1111"
local dns2_ipv6="2001:4860:4860::8888"
set_dns
- send_stats "外国のDNS最適化"
+ send_stats "外部DNSの最適化"
;;
2)
local dns1_ipv4="223.5.5.5"
@@ -4331,12 +4861,14 @@ while true; do
local dns1_ipv6="2400:3200::1"
local dns2_ipv6="2400:da00::6666"
set_dns
- send_stats "国内のDNS最適化"
+ send_stats "国内DNS最適化"
;;
3)
install nano
+ chattr -i /etc/resolv.conf
nano /etc/resolv.conf
- send_stats "DNS構成を手動で編集します"
+ chattr +i /etc/resolv.conf
+ send_stats "DNS 構成を手動で編集する"
;;
*)
break
@@ -4359,45 +4891,38 @@ correct_ssh_config() {
local sshd_config="/etc/ssh/sshd_config"
- # PasswordAuthenticationが見つかった場合は、はいに設定します
- if grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
- # 発見された場合、pubkeyauthenticationはyesに設定されています
- if grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
+ if grep -Eq "^\s*PasswordAuthentication\s+no" "$sshd_config"; then
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
-e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' "$sshd_config"
+ else
+ sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin yes/' \
+ -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication yes/' \
+ -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' "$sshd_config"
fi
- # PasswordAuthenticationもPubKeyAuthenticationが一致しない場合は、デフォルト値を設定します
- if ! grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config" && ! grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
-
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
}
new_ssh_port() {
- # バックアップSSH構成ファイル
+ local new_port=$1
+
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
open_port $new_port
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
- echo "SSHポートは次のように変更されています。$new_port"
+ echo "SSH ポートは次のように変更されました。$new_port"
sleep 1
@@ -4405,21 +4930,7 @@ new_ssh_port() {
-add_sshkey() {
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f /root/.ssh/sshkey -N ""
- cat ~/.ssh/sshkey.pub >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
-
- ip_address
- echo -e "秘密のキー情報が生成されました。必ずコピーして保存してください。${gl_huang}${ipv4_address}_ssh.key${gl_bai}将来のSSHログイン用のファイル"
-
- echo "--------------------------------"
- cat ~/.ssh/sshkey
- echo "--------------------------------"
+sshkey_on() {
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
@@ -4427,194 +4938,473 @@ add_sshkey() {
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
- echo -e "${gl_lv}ルートプライベートキーログインが有効になり、ルートパスワードログインが閉じられ、再接続が有効になります${gl_bai}"
+ echo -e "${gl_lv}ユーザーキーログインモードがオンになり、パスワードログインモードがオフになりました。再接続が有効になります。${gl_bai}"
}
-import_sshkey() {
- read -e -p "SSH公開キーの内容を入力してください(通常は「SSH-RSA」または「SSH-ED25519」から始まります):" public_key
+add_sshkey() {
+ chmod 700 "${HOME}"
+ mkdir -p "${HOME}/.ssh"
+ chmod 700 "${HOME}/.ssh"
+ touch "${HOME}/.ssh/authorized_keys"
- if [[ -z "$public_key" ]]; then
- echo -e "${gl_hong}エラー:公開キーのコンテンツは入力されませんでした。${gl_bai}"
- return 1
- fi
+ ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f "${HOME}/.ssh/sshkey" -N ""
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- echo "$public_key" >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
+ cat "${HOME}/.ssh/sshkey.pub" >> "${HOME}/.ssh/authorized_keys"
+ chmod 600 "${HOME}/.ssh/authorized_keys"
- sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
- -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
- -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
- -e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
+ ip_address
+ echo -e "秘密鍵情報が生成されました。必ずコピーして保存してください。として保存できます${gl_huang}${ipv4_address}_ssh.key${gl_bai}今後の SSH ログイン用のファイル"
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
- restart_ssh
- echo -e "${gl_lv}公開キーが正常にインポートされ、ルート秘密キーログインが有効になり、ルートパスワードログインが閉じられ、再接続が有効になります${gl_bai}"
+ echo "--------------------------------"
+ cat "${HOME}/.ssh/sshkey"
+ echo "--------------------------------"
+ sshkey_on
}
-add_sshpasswd() {
-echo "ルートパスワードを設定します"
-passwd
-sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
-sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
-rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
-restart_ssh
-echo -e "${gl_lv}ルートログインがセットアップされます!${gl_bai}"
+import_sshkey() {
-}
+ local public_key="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local auth_keys="${ssh_dir}/authorized_keys"
+ if [[ -z "$public_key" ]]; then
+ read -e -p "SSH 公開キーの内容を入力してください (通常は「ssh-rsa」または「ssh-ed25519」で始まります):" public_key
+ fi
-root_use() {
-clear
-[ "$EUID" -ne 0 ] && echo -e "${gl_huang}ヒント:${gl_bai}この機能には、ルートユーザーを実行する必要があります!" && break_end && kejilion
-}
+ if [[ -z "$public_key" ]]; then
+ echo -e "${gl_hong}エラー: 公開キーの内容が入力されていません。${gl_bai}"
+ return 1
+ fi
+ if [[ ! "$public_key" =~ ^ssh-(rsa|ed25519|ecdsa) ]]; then
+ echo -e "${gl_hong}エラー: 正規の SSH 公開キーのようには見えません。${gl_bai}"
+ return 1
+ fi
+ if grep -Fxq "$public_key" "$auth_keys" 2>/dev/null; then
+ echo "公開キーはすでに存在するため、再度追加する必要はありません"
+ return 0
+ fi
-dd_xitong() {
- send_stats "システムを再インストールします"
- dd_xitong_MollyLau() {
- wget --no-check-certificate -qO InstallNET.sh "${gh_proxy}raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh" && chmod a+x InstallNET.sh
+ mkdir -p "$ssh_dir"
+ chmod 700 "$ssh_dir"
+ touch "$auth_keys"
+ echo "$public_key" >> "$auth_keys"
+ chmod 600 "$auth_keys"
- }
+ sshkey_on
+}
- dd_xitong_bin456789() {
- curl -O ${gh_proxy}raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh
- }
+
+
+fetch_remote_ssh_keys() {
+
+ local keys_url="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local authorized_keys="${ssh_dir}/authorized_keys"
+ local temp_file
+
+ if [[ -z "${keys_url}" ]]; then
+ read -e -p "リモート公開キーの URL を入力してください:" keys_url
+ fi
+
+ echo "このスクリプトは、リモート URL から SSH 公開キーを取得し、それを${authorized_keys}"
+ echo ""
+ echo "リモート公開鍵アドレス:"
+ echo " ${keys_url}"
+ echo ""
+
+ # 一時ファイルを作成する
+ temp_file=$(mktemp)
+
+ # 公開キーをダウンロードする
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 10 "${keys_url}" -o "${temp_file}" || {
+ echo "エラー: URL から公開キーをダウンロードできません (ネットワークの問題または無効なアドレス)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q --timeout=10 -O "${temp_file}" "${keys_url}" || {
+ echo "エラー: URL から公開キーをダウンロードできません (ネットワークの問題または無効なアドレス)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ else
+ echo "エラー: システムにcurlまたはwgetが見つからないため、公開キーをダウンロードできません" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ # コンテンツが有効かどうかを確認する
+ if [[ ! -s "${temp_file}" ]]; then
+ echo "エラー: ダウンロードされたファイルは空であり、URL には公開キーが含まれていない可能性があります" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ mkdir -p "${ssh_dir}"
+ chmod 700 "${ssh_dir}"
+ touch "${authorized_keys}"
+ chmod 600 "${authorized_keys}"
+
+ # 元のauthorized_keysをバックアップする
+ if [[ -f "${authorized_keys}" ]]; then
+ cp "${authorized_keys}" "${authorized_keys}.bak.$(date +%Y%m%d-%H%M%S)"
+ echo "元のauthorized_keysファイルがバックアップされました"
+ fi
+
+ # 公開キーを追加(重複を避ける)
+ local added=0
+ while IFS= read -r line; do
+ [[ -z "${line}" || "${line}" =~ ^# ]] && continue
+
+ if ! grep -Fxq "${line}" "${authorized_keys}" 2>/dev/null; then
+ echo "${line}" >> "${authorized_keys}"
+ ((added++))
+ fi
+ done < "${temp_file}"
+
+ rm -f "${temp_file}"
+
+ echo ""
+ if (( added > 0 )); then
+ echo "正常に追加されました${added}新しい公開鍵が到着する${authorized_keys}"
+ sshkey_on
+ else
+ echo "新しい公開キーを追加する必要はありません (すべてがすでに存在している可能性があります)"
+ fi
+
+ echo ""
+}
+
+
+
+
+fetch_github_ssh_keys() {
+
+ local username="$1"
+ local base_dir="${2:-$HOME}"
+
+ echo "続行する前に、SSH 公開キーを GitHub アカウントに追加していることを確認してください。"
+ echo "1. ログイン${gh_https_url}github.com/settings/keys"
+ echo "2. [新しい SSH キー] または [SSH キーの追加] をクリックします。"
+ echo "3. タイトルは必要に応じて入力できます (例: Home Laptop 2026)"
+ echo "4. ローカル公開鍵の内容 (通常は ~/.ssh/id_ed25519.pub または id_rsa.pub の内容全体) を「Key」フィールドに貼り付けます。"
+ echo "5. [SSH キーの追加] をクリックして追加を完了します。"
+ echo ""
+ echo "追加すると、すべての公開キーが GitHub で公開されます。"
+ echo " ${gh_https_url}github.com/あなたのユーザー名.keys"
+ echo ""
+
+
+ if [[ -z "${username}" ]]; then
+ read -e -p "GitHub ユーザー名 (@ のないユーザー名) を入力してください:" username
+ fi
+
+ if [[ -z "${username}" ]]; then
+ echo "エラー: GitHub ユーザー名を空にすることはできません" >&2
+ return 1
+ fi
+
+ keys_url="${gh_https_url}github.com/${username}.keys"
+
+ fetch_remote_ssh_keys "${keys_url}" "${base_dir}"
+
+}
+
+
+sshkey_panel() {
+ root_use
+ send_stats "ユーザーキーログイン"
+ while true; do
+ clear
+ local REAL_STATUS=$(grep -i "^PubkeyAuthentication" /etc/ssh/sshd_config | tr '[:upper:]' '[:lower:]')
+ if [[ "$REAL_STATUS" =~ "yes" ]]; then
+ IS_KEY_ENABLED="${gl_lv}有効${gl_bai}"
+ else
+ IS_KEY_ENABLED="${gl_hui}有効になっていません${gl_bai}"
+ fi
+ echo -e "ユーザーキーログインモード${IS_KEY_ENABLED}"
+ echo "高度なゲームプレイ: https://blog.kejilion.pro/ssh-key"
+ echo "------------------------------------------------"
+ echo "キーペアが生成され、SSH 経由でログインするためのより安全な方法になります。"
+ echo "------------------------"
+ echo "1. 新しいキーペアを生成します。 2. 既存の公開キーを手動で入力します。"
+ echo "3. GitHub から既存の公開キーをインポートします。 4. URL から既存の公開キーをインポートします。"
+ echo "5. 公開鍵ファイルを編集します。 6. ローカル鍵を表示します。"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択肢を入力してください:" host_dns
+ case $host_dns in
+ 1)
+ send_stats "新しいキーを生成する"
+ add_sshkey
+ break_end
+ ;;
+ 2)
+ send_stats "既存の公開キーをインポートする"
+ import_sshkey
+ break_end
+ ;;
+ 3)
+ send_stats "GitHub リモート公開キーをインポートする"
+ fetch_github_ssh_keys
+ break_end
+ ;;
+ 4)
+ send_stats "URL リモート公開キーをインポートする"
+ read -e -p "リモート公開キーの URL を入力してください:" keys_url
+ fetch_remote_ssh_keys "${keys_url}"
+ break_end
+ ;;
+
+ 5)
+ send_stats "公開鍵ファイルを編集する"
+ install nano
+ nano ${HOME}/.ssh/authorized_keys
+ break_end
+ ;;
+
+ 6)
+ send_stats "ローカルキーを表示する"
+ echo "------------------------"
+ echo "公開鍵情報"
+ cat ${HOME}/.ssh/authorized_keys
+ echo "------------------------"
+ echo "秘密鍵情報"
+ cat ${HOME}/.ssh/sshkey
+ echo "------------------------"
+ break_end
+ ;;
+ *)
+ break # 跳出循环,退出菜单
+ ;;
+ esac
+ done
+
+
+}
+
+
+
+
+
+
+add_sshpasswd() {
+
+ root_use
+ send_stats "パスワードログインモードを設定する"
+ echo "パスワードログインモードを設定する"
+
+ local target_user="$1"
+
+ # パラメータが渡されない場合は、対話的に入力します
+ if [[ -z "$target_user" ]]; then
+ read -e -p "パスワードを変更するユーザー名を入力してください (デフォルトは root):" target_user
+ fi
+
+ # Enter キーを押して入力しないでください。デフォルトは root です
+ target_user=${target_user:-root}
+
+ # ユーザーが存在することを確認する
+ if ! id "$target_user" >/dev/null 2>&1; then
+ echo "エラー: ユーザー$target_user存在しません"
+ return 1
+ fi
+
+ passwd "$target_user"
+
+ if [[ "$target_user" == "root" ]]; then
+ sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
+ fi
+
+ sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
+
+ restart_ssh
+
+ echo -e "${gl_lv}パスワードが設定され、パスワードログインモードに変更されました。${gl_bai}"
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+root_use() {
+clear
+[ "$EUID" -ne 0 ] && echo -e "${gl_huang}ヒント:${gl_bai}この機能を実行するには root ユーザーが必要です。" && break_end && kejilion
+}
+
+
+
+
+
+
+
+
+
+
+
+
+dd_xitong() {
+ send_stats "システムを再インストールする"
+ dd_xitong_MollyLau() {
+ wget --no-check-certificate -qO InstallNET.sh "${gh_proxy}raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh" && chmod a+x InstallNET.sh
+
+ }
+
+ dd_xitong_bin456789() {
+ curl -O ${gh_proxy}raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh
+ }
dd_xitong_1() {
- echo -e "再インストール後の初期ユーザー名:${gl_huang}root${gl_bai}最初のパスワード:${gl_huang}LeitboGi0ro${gl_bai}初期ポート:${gl_huang}22${gl_bai}"
- echo -e "任意のキーを押して続行します..."
+ echo -e "再インストール後の初期ユーザー名:${gl_huang}root${gl_bai}初期パスワード:${gl_huang}LeitboGi0ro${gl_bai}初期ポート:${gl_huang}22${gl_bai}"
+ echo -e "${gl_huang}再インストール後は、暴力的な侵入を防ぐため、初期パスワードを適時に変更してください。パスワードを変更するには、コマンドラインに passwd と入力します。${gl_bai}"
+ echo -e "続行するには任意のキーを押してください..."
read -n 1 -s -r -p ""
install wget
dd_xitong_MollyLau
}
dd_xitong_2() {
- echo -e "再インストール後の初期ユーザー名:${gl_huang}Administrator${gl_bai}最初のパスワード:${gl_huang}Teddysun.com${gl_bai}初期ポート:${gl_huang}3389${gl_bai}"
- echo -e "任意のキーを押して続行します..."
+ echo -e "再インストール後の初期ユーザー名:${gl_huang}Administrator${gl_bai}初期パスワード:${gl_huang}Teddysun.com${gl_bai}初期ポート:${gl_huang}3389${gl_bai}"
+ echo -e "続行するには任意のキーを押してください..."
read -n 1 -s -r -p ""
install wget
dd_xitong_MollyLau
}
dd_xitong_3() {
- echo -e "再インストール後の初期ユーザー名:${gl_huang}root${gl_bai}最初のパスワード:${gl_huang}123@@@${gl_bai}初期ポート:${gl_huang}22${gl_bai}"
- echo -e "任意のキーを押して続行します..."
+ echo -e "再インストール後の初期ユーザー名:${gl_huang}root${gl_bai}初期パスワード:${gl_huang}123@@@${gl_bai}初期ポート:${gl_huang}22${gl_bai}"
+ echo -e "続行するには任意のキーを押してください..."
read -n 1 -s -r -p ""
dd_xitong_bin456789
}
dd_xitong_4() {
- echo -e "再インストール後の初期ユーザー名:${gl_huang}Administrator${gl_bai}最初のパスワード:${gl_huang}123@@@${gl_bai}初期ポート:${gl_huang}3389${gl_bai}"
- echo -e "任意のキーを押して続行します..."
+ echo -e "再インストール後の初期ユーザー名:${gl_huang}Administrator${gl_bai}初期パスワード:${gl_huang}123@@@${gl_bai}初期ポート:${gl_huang}3389${gl_bai}"
+ echo -e "続行するには任意のキーを押してください..."
read -n 1 -s -r -p ""
dd_xitong_bin456789
}
while true; do
root_use
- echo "システムを再インストールします"
+ echo "システムを再インストールする"
echo "--------------------------------"
- echo -e "${gl_hong}知らせ:${gl_bai}再インストールは接触を失う危険であり、心配している人はそれを注意して使用する必要があります。再インストールには15分かかると予想されます。事前にデータをバックアップしてください。"
- echo -e "${gl_hui}スクリプトサポートについては、MollylauとBin456789に感謝します!${gl_bai} "
+ echo -e "${gl_hong}知らせ:${gl_bai}再インストールすると接続が切れる可能性がありますので、不安な方はご注意ください。再インストールには 15 分程度かかることが予想されますので、事前にデータをバックアップしてください。"
+ echo -e "${gl_hui}スクリプトをサポートしてくれたボス bin456789 とボス leitbogioro に感謝します。${gl_bai} "
+ echo -e "${gl_hui}bin456789 プロジェクトのアドレス:${gh_https_url}github.com/bin456789/reinstall${gl_bai}"
+ echo -e "${gl_hui}leitbogioro プロジェクトのアドレス:${gh_https_url}github.com/leitbogioro/Tools${gl_bai}"
echo "------------------------"
- echo "1. Debian 12 2. Debian 11"
- echo "3. Debian 10 4. Debian 9"
+ echo "1. Debian 13 2. Debian 12"
+ echo "3. Debian 11 4. Debian 10"
echo "------------------------"
- echo "11. Ubuntu 24.04 12. Ubuntu 22.04"
- echo "13. Ubuntu 20.04 14. Ubuntu 18.04"
+ echo "11. Ubuntu 26.04 12. Ubuntu 24.04"
+ echo "13. Ubuntu 22.04 14. Ubuntu 20.04"
echo "------------------------"
echo "21. Rocky Linux 10 22. Rocky Linux 9"
echo "23. Alma Linux 10 24. Alma Linux 9"
echo "25. oracle Linux 10 26. oracle Linux 9"
- echo "27. Fedora Linux 42 28. Fedora Linux 41"
+ echo "27. Fedora Linux 44 28. Fedora Linux 43"
echo "29. CentOS 10 30. CentOS 9"
echo "------------------------"
echo "31. Alpine Linux 32. Arch Linux"
echo "33. Kali Linux 34. openEuler"
- echo "35。OpenSuseTumbleWeed36。FNOSFEINIU PUBLIC BETAバージョン"
+ echo "35. openSUSE Tumbleweed 36. fnos Feiniu パブリックベータ版"
echo "------------------------"
echo "41. Windows 11 42. Windows 10"
- echo "43. Windows 7 44. Windows Server 2022"
- echo "45. Windows Server 2019 46. Windows Server 2016"
+ echo "43. Windows 7 44. Windows Server 2025"
+ echo "45. Windows Server 2022 46. Windows Server 2019"
echo "47. Windows 11 ARM"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "再インストールするシステムを選択してください:" sys_choice
+ read -e -p "再インストールするシステムを選択してください:" sys_choice
case "$sys_choice" in
+
+
1)
- send_stats "Debian 12を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -debian 12
+ send_stats "debian13を再インストールする"
+ dd_xitong_3
+ bash reinstall.sh debian 13
reboot
exit
;;
+
2)
- send_stats "Debian 11を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -debian 11
+ send_stats "debian12を再インストールする"
+ dd_xitong_3
+ bash reinstall.sh debian 12
reboot
exit
;;
3)
- send_stats "Debian 10を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -debian 10
+ send_stats "debian11を再インストールする"
+ dd_xitong_3
+ bash reinstall.sh debian 11
reboot
exit
;;
4)
- send_stats "Debian 9を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -debian 9
+ send_stats "debian10を再インストールする"
+ dd_xitong_3
+ bash reinstall.sh debian 10
reboot
exit
;;
11)
- send_stats "Ubuntu 24.04を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 24.04
+ send_stats "ubuntu 26.04を再インストールします"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 26.04
reboot
exit
;;
12)
- send_stats "Ubuntu 22.04を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 22.04
+ send_stats "ubuntu 24.04を再インストールします"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 24.04
reboot
exit
;;
13)
- send_stats "Ubuntu 20.04を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 20.04
+ send_stats "ubuntu 22.04を再インストールします"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 22.04
reboot
exit
;;
14)
- send_stats "Ubuntu 18.04を再インストールします"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 18.04
+ send_stats "ubuntu 20.04を再インストールします"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 20.04
reboot
exit
;;
-
21)
- send_stats "Rockylinux10を再インストールします"
+ send_stats "Rockylinux10を再インストールする"
dd_xitong_3
bash reinstall.sh rocky
reboot
@@ -4622,7 +5412,7 @@ dd_xitong() {
;;
22)
- send_stats "Rockylinux9を再インストールします"
+ send_stats "Rockylinux9 を再インストールする"
dd_xitong_3
bash reinstall.sh rocky 9
reboot
@@ -4630,7 +5420,7 @@ dd_xitong() {
;;
23)
- send_stats "alma10を再インストールします"
+ send_stats "alma10を再インストールする"
dd_xitong_3
bash reinstall.sh almalinux
reboot
@@ -4638,7 +5428,7 @@ dd_xitong() {
;;
24)
- send_stats "alma9を再インストールします"
+ send_stats "alma9を再インストールする"
dd_xitong_3
bash reinstall.sh almalinux 9
reboot
@@ -4646,7 +5436,7 @@ dd_xitong() {
;;
25)
- send_stats "Oracle10を再インストールします"
+ send_stats "oracle10を再インストールする"
dd_xitong_3
bash reinstall.sh oracle
reboot
@@ -4654,7 +5444,7 @@ dd_xitong() {
;;
26)
- send_stats "Oracle9を再インストールします"
+ send_stats "oracle9を再インストールする"
dd_xitong_3
bash reinstall.sh oracle 9
reboot
@@ -4662,23 +5452,23 @@ dd_xitong() {
;;
27)
- send_stats "Fedora42を再インストールします"
+ send_stats "fedora44を再インストールする"
dd_xitong_3
- bash reinstall.sh fedora
+ bash reinstall.sh fedora 44
reboot
exit
;;
28)
- send_stats "Fedora41を再インストールします"
+ send_stats "fedora43を再インストールする"
dd_xitong_3
- bash reinstall.sh fedora 41
+ bash reinstall.sh fedora 43
reboot
exit
;;
29)
- send_stats "CENTOS10を再インストールします"
+ send_stats "centos10を再インストールする"
dd_xitong_3
bash reinstall.sh centos 10
reboot
@@ -4686,7 +5476,7 @@ dd_xitong() {
;;
30)
- send_stats "CENTOS9を再インストールします"
+ send_stats "CentOS9を再インストールする"
dd_xitong_3
bash reinstall.sh centos 9
reboot
@@ -4694,7 +5484,7 @@ dd_xitong() {
;;
31)
- send_stats "アルパインを再インストールします"
+ send_stats "アルパインを再インストールする"
dd_xitong_1
bash InstallNET.sh -alpine
reboot
@@ -4702,7 +5492,7 @@ dd_xitong() {
;;
32)
- send_stats "アーチを再インストールします"
+ send_stats "アーチを再インストールする"
dd_xitong_3
bash reinstall.sh arch
reboot
@@ -4710,7 +5500,7 @@ dd_xitong() {
;;
33)
- send_stats "Kaliを再インストールします"
+ send_stats "kaliを再インストールする"
dd_xitong_3
bash reinstall.sh kali
reboot
@@ -4718,7 +5508,7 @@ dd_xitong() {
;;
34)
- send_stats "Openeulerを再インストールします"
+ send_stats "オープニューラーを再インストールする"
dd_xitong_3
bash reinstall.sh openeuler
reboot
@@ -4726,7 +5516,7 @@ dd_xitong() {
;;
35)
- send_stats "OpenSuseを再インストールします"
+ send_stats "opensuse を再インストールする"
dd_xitong_3
bash reinstall.sh opensuse
reboot
@@ -4734,60 +5524,63 @@ dd_xitong() {
;;
36)
- send_stats "飛ぶ牛をリロードします"
+ send_stats "Feiniu を再インストールする"
dd_xitong_3
bash reinstall.sh fnos
reboot
exit
;;
-
41)
- send_stats "Windows11を再インストールします"
+ send_stats "Windows 11を再インストールする"
dd_xitong_2
bash InstallNET.sh -windows 11 -lang "cn"
reboot
exit
;;
+
42)
dd_xitong_2
- send_stats "Windows 10を再インストールします"
+ send_stats "Windows 10を再インストールする"
bash InstallNET.sh -windows 10 -lang "cn"
reboot
exit
;;
+
43)
- send_stats "Windows 7を再インストールします"
+ send_stats "Windows7を再インストールする"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://archive.org/download/en_windows_7_professional_with_sp1_x64_dvd_u_676939_201906/en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso" --image-name='windows 7 professional'
reboot
exit
;;
44)
- send_stats "Windows Server 22を再インストールします"
+ send_stats "Windowsサーバー25を再インストールします"
dd_xitong_2
- bash InstallNET.sh -windows 2022 -lang "cn"
+ bash InstallNET.sh -windows 2025 -lang "cn"
reboot
exit
;;
+
45)
- send_stats "Windows Server 19を再インストールします"
+ send_stats "Windowsサーバー22を再インストールします"
dd_xitong_2
- bash InstallNET.sh -windows 2019 -lang "cn"
+ bash InstallNET.sh -windows 2022 -lang "cn"
reboot
exit
;;
+
46)
- send_stats "Windows Server 16を再インストールします"
+ send_stats "Windowsサーバー19を再インストールします"
dd_xitong_2
- bash InstallNET.sh -windows 2016 -lang "cn"
+ bash InstallNET.sh -windows 2019 -lang "cn"
reboot
exit
;;
47)
- send_stats "Windows11アームを再インストールします"
+ send_stats "Windows11 ARMを再インストールする"
dd_xitong_4
bash reinstall.sh dd --img https://r2.hotdog.eu.org/win11-arm-with-pagefile-15g.xz
reboot
@@ -4804,7 +5597,147 @@ dd_xitong() {
bbrv3() {
root_use
- send_stats "BBRV3管理"
+ send_stats "bbrv3管理"
+
+ xanmod_add_repo() {
+ local keyring="/usr/share/keyrings/xanmod-archive-keyring.gpg"
+ local list_file="/etc/apt/sources.list.d/xanmod-release.list"
+ local key_url="https://dl.xanmod.org/archive.key"
+ local fallback_key_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key"
+ local os_codename=""
+
+ if command -v lsb_release >/dev/null 2>&1; then
+ os_codename=$(lsb_release -sc)
+ elif [ -r /etc/os-release ]; then
+ os_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
+ fi
+
+ # 正式に削除された古いシステム コードネームとの互換性 (古いパッケージ ライブラリを試すためにリリースを使用するようにフォールバックします)
+ if ! echo "bookworm trixie forky sid noble plucky questing resolute faye gigi wilma xia zara zena" | grep -qw "$os_codename"; then
+ os_codename="releases"
+ fi
+
+ # 公式には、jammy、focal、bulseye、その他の古いシステムの apt サポートは完全に削除されました。
+ if echo "jammy focal bullseye buster" | grep -qw "$os_codename" || [ "$os_codename" = "releases" ]; then
+ echo -e "${gl_hong}XanMod は現在のシステムのサポートを正式に停止しました ($os_codename) の場合は、Debian12/Ubuntu24 以降にアップグレードしてください。${gl_bai}"
+ return 1
+ fi
+
+ if [ -z "$os_codename" ]; then
+ echo "システムコードネームを取得できません。XanMod ソースを構成できません"
+ return 1
+ fi
+
+ install wget gnupg ca-certificates
+ mkdir -p /usr/share/keyrings /etc/apt/sources.list.d
+ if ! wget -qO - "$key_url" | gpg --dearmor -o "$keyring" --yes; then
+ echo "公式キーのダウンロードに失敗しました。別のダウンロード ソースを試してください..."
+ wget -qO - "$fallback_key_url" | gpg --dearmor -o "$keyring" --yes || return 1
+ fi
+ chmod 644 "$keyring"
+ echo "deb [signed-by=$keyring] http://deb.xanmod.org $os_codename main" > "$list_file"
+ }
+
+ xanmod_detect_psabi_level() {
+ local psabi_output=""
+ psabi_output=$(awk 'BEGIN {
+ while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
+ if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
+ if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
+ if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
+ if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
+ if (level > 0) { print level; exit }
+ exit 1
+ }' /proc/cpuinfo 2>/dev/null) || return 1
+ printf '%s' "$psabi_output" | tr -dc '0-9' | head -c 1
+ }
+
+ xanmod_package_available() {
+ local package="$1"
+ apt-cache policy "$package" 2>/dev/null | grep -q 'Candidate: [^ ]'
+ }
+
+ xanmod_detect_package() {
+ local psabi_level=""
+ local level=""
+ local package=""
+ local prefix_list="linux-xanmod linux-xanmod-lts"
+
+ psabi_level=$(xanmod_detect_psabi_level) || return 1
+ [ -n "$psabi_level" ] || return 1
+ [ "$psabi_level" -gt 3 ] && psabi_level=3
+
+ apt update -y >/dev/null 2>&1
+
+ for prefix in $prefix_list; do
+ level="$psabi_level"
+ while [ "$level" -ge 1 ]; do
+ package="${prefix}-x64v${level}"
+ if xanmod_package_available "$package"; then
+ if [ "$level" != "$psabi_level" ] || [ "$prefix" = "linux-xanmod-lts" ]; then
+ echo "適切なインストール パッケージが自動的に照合されます。$package" >&2
+ fi
+ printf '%s\n' "$package"
+ return 0
+ fi
+ level=$((level - 1))
+ done
+ done
+
+ echo "この CPU に適合する XanMod カーネル パッケージがソフトウェア ソースに見つかりませんでした。" >&2
+ return 1
+ }
+
+ xanmod_installed() {
+ dpkg-query -W -f='${Package}\n' 'linux-*xanmod*' 2>/dev/null | grep -q '^linux-.*xanmod'
+ }
+
+ xanmod_install_or_update() {
+ local action="$1"
+ local package=""
+
+ check_disk_space 3
+ check_swap
+ xanmod_add_repo || {
+ echo "XanMod 公式ウェアハウスの構成に失敗しました。後でもう一度試してください。"
+ return 1
+ }
+
+ package=$(xanmod_detect_package) || {
+ echo "現在の CPU が認識されないか、一致するカーネル パッケージが見つかりません。インストールはキャンセルされました。"
+ return 1
+ }
+
+ apt update -y
+ if [ "$action" = "update" ]; then
+ apt install -y --only-upgrade "$package" || apt install -y "$package" || {
+ echo "XanMod カーネルの更新に失敗しました。ソフトウェア ソースを確認するか、後でもう一度試してください。"
+ return 1
+ }
+ else
+ apt install -y "$package" || {
+ echo "XanMod カーネルのインストールに失敗しました。ソフトウェア ソースを確認するか、後でもう一度試してください。"
+ return 1
+ }
+ fi
+
+ bbr_on || {
+ echo "BBR3 パラメータの書き込みに失敗しました。システム構成を確認してください。"
+ return 1
+ }
+ echo "XanMod BBRv3 カーネル処理が完了しました。再起動後に有効になります"
+ server_reboot
+ }
+
+ xanmod_uninstall() {
+ apt purge -y 'linux-*xanmod*'
+ apt autoremove -y
+ update-grub 2>/dev/null || true
+ rm -f /etc/apt/sources.list.d/xanmod-release.list
+ rm -f /usr/share/keyrings/xanmod-archive-keyring.gpg
+ echo "XanMod カーネルがアンインストールされました。再起動後に有効になります"
+ server_reboot
+ }
local cpu_arch=$(uname -m)
if [ "$cpu_arch" = "aarch64" ]; then
@@ -4813,157 +5746,109 @@ bbrv3() {
linux_Settings
fi
- if dpkg -l | grep -q 'linux-xanmod'; then
+ if [ -r /etc/os-release ]; then
+ . /etc/os-release
+ if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
+ echo "現在の環境では対応しておりません。 Debian および Ubuntu システムのみがサポートされています。"
+ break_end
+ linux_Settings
+ fi
+ else
+ echo "オペレーティング システムの種類を特定できません"
+ break_end
+ linux_Settings
+ fi
+
+ if xanmod_installed; then
while true; do
clear
local kernel_version=$(uname -r)
- echo "XanmodのBBRV3カーネルをインストールしました"
- echo "現在のカーネルバージョン:$kernel_version"
+ echo "xanmod の BBRv3 カーネルがインストールされている"
+ echo "現在のカーネル バージョン:$kernel_version"
echo ""
echo "カーネル管理"
echo "------------------------"
- echo "1。BBRV3カーネルを更新する2。BBRV3カーネルをアンインストールします"
+ echo "1. BBRv3 カーネルを更新します。 2. BBRv3 カーネルをアンインストールします。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- apt purge -y 'linux-*xanmod1*'
- update-grub
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # ステップ3:リポジトリを追加します
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- echo "Xanmodカーネルが更新されました。再起動後に有効になります"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
-
- server_reboot
-
- ;;
+ xanmod_install_or_update update
+ ;;
2)
- apt purge -y 'linux-*xanmod1*'
- update-grub
- echo "Xanmodカーネルはアンインストールされています。再起動後に有効になります"
- server_reboot
- ;;
-
+ xanmod_uninstall
+ ;;
*)
- break # 跳出循环,退出菜单
- ;;
+ break
+ ;;
esac
done
else
clear
- echo "BBR3加速度をセットアップします"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv14k421x7bs?t=0.1"
+ echo "BBR3アクセラレーションの設定"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV14K421x7BS?t=0.1"
echo "------------------------------------------------"
- echo "Debian/Ubuntuのみをサポートします"
- echo "データをバックアップしてください。Linuxカーネルをアップグレードできるようになります。"
- echo "VPSには512mのメモリがあります。メモリが不十分なため、接触の欠落を防ぐために、事前に1G仮想メモリを追加してください!"
+ echo "Debian/Ubuntu のみをサポートします"
+ echo "データをバックアップしてください。Linux カーネルをアップグレードして BBR3 を有効にします。"
echo "------------------------------------------------"
- read -e -p "必ず続行しますか? (y/n):" choice
+ read -e -p "続行してもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
- check_disk_space 3
- if [ -r /etc/os-release ]; then
- . /etc/os-release
- if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
- echo "現在の環境はそれをサポートせず、DebianとUbuntuシステムのみをサポートしています"
- break_end
- linux_Settings
- fi
- else
- echo "オペレーティングシステムの種類を決定できません"
- break_end
- linux_Settings
- fi
-
- check_swap
- install wget gnupg
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # ステップ3:リポジトリを追加します
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- bbr_on
-
- echo "Xanmodカーネルがインストールされ、BBR3が正常に有効になります。再起動後に有効になります"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
- server_reboot
-
+ xanmod_install_or_update install
;;
[Nn])
echo "キャンセル"
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
fi
}
-
elrepo_install() {
- # Elrepo GPG公開キーをインポートします
- echo "Elrepo GPG公開キーをインポートしてください..."
+ # ELRepo GPG 公開キーをインポートする
+ echo "ELRepo GPG 公開キーをインポートします..."
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
- # システムバージョンを検出します
+ # システムバージョンを確認する
local os_version=$(rpm -q --qf "%{VERSION}" $(rpm -qf /etc/os-release) 2>/dev/null | awk -F '.' '{print $1}')
local os_name=$(awk -F= '/^NAME/{print $2}' /etc/os-release)
- # サポートされているオペレーティングシステムで実行されていることを確認してください
+ # サポートされているオペレーティング システムで実行されていることを確認してください
if [[ "$os_name" != *"Red Hat"* && "$os_name" != *"AlmaLinux"* && "$os_name" != *"Rocky"* && "$os_name" != *"Oracle"* && "$os_name" != *"CentOS"* ]]; then
- echo "サポートされていないオペレーティングシステム:$os_name"
+ echo "サポートされていないオペレーティング システム:$os_name"
break_end
linux_Settings
fi
- # 検出されたオペレーティングシステム情報を印刷します
- echo "検出されたオペレーティングシステム:$os_name $os_version"
- # システムバージョンに応じて、対応するElrepo Warehouse構成をインストールする
+ # 検出されたオペレーティング システム情報を印刷する
+ echo "検出されたオペレーティング システム:$os_name $os_version"
+ # システムのバージョンに応じて、対応する ELRepo ウェアハウス構成をインストールします。
if [[ "$os_version" == 8 ]]; then
- echo "Elrepoリポジトリ構成(バージョン8)をインストールしてください..."
+ echo "ELRepo リポジトリ構成 (バージョン 8) をインストールしています..."
yum -y install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm
elif [[ "$os_version" == 9 ]]; then
- echo "Elrepoリポジトリ構成(バージョン9)をインストールしてください..."
+ echo "ELRepo リポジトリ構成 (バージョン 9) をインストールしています..."
yum -y install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm
elif [[ "$os_version" == 10 ]]; then
- echo "Elrepoリポジトリ構成(バージョン10)をインストールしてください..."
+ echo "ELRepo リポジトリ構成 (バージョン 10) をインストールしています..."
yum -y install https://www.elrepo.org/elrepo-release-10.el10.elrepo.noarch.rpm
else
- echo "サポートされていないシステムバージョン:$os_version"
+ echo "サポートされていないシステム バージョン:$os_version"
break_end
linux_Settings
fi
- # Elrepoカーネルリポジトリを有効にし、最新のメインラインカーネルをインストールします
- echo "Elrepoカーネルリポジトリを有効にし、最新のメインラインカーネルをインストールしてください..."
+ # ELRepo カーネル リポジトリを有効にし、最新のメインライン カーネルをインストールします。
+ echo "ELRepo カーネル リポジトリを有効にし、最新のメインライン カーネルをインストールします..."
# yum -y --enablerepo=elrepo-kernel install kernel-ml
yum --nogpgcheck -y --enablerepo=elrepo-kernel install kernel-ml
- echo "Elrepoリポジトリ構成がインストールされ、最新のメインラインカーネルに更新されます。"
+ echo "ELRepo リポジトリ構成をインストールし、最新のメインライン カーネルに更新しました。"
server_reboot
}
@@ -4971,37 +5856,37 @@ elrepo_install() {
elrepo() {
root_use
- send_stats "レッドハットカーネル管理"
+ send_stats "Red Hat カーネル管理"
if uname -r | grep -q 'elrepo'; then
while true; do
clear
kernel_version=$(uname -r)
- echo "Elrepo Kernelをインストールしました"
- echo "現在のカーネルバージョン:$kernel_version"
+ echo "elrepo カーネルがインストールされています"
+ echo "現在のカーネル バージョン:$kernel_version"
echo ""
echo "カーネル管理"
echo "------------------------"
- echo "1. Elrepo Kernel 2を更新します。ElrepoKernelをアンインストールします"
+ echo "1. elrepo カーネルを更新します。 2. elrepo カーネルをアンインストールします。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
elrepo_install
- send_stats "Red Hatカーネルを更新します"
+ send_stats "Red Hat カーネルを更新する"
server_reboot
;;
2)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
- echo "Elrepoカーネルはアンインストールされています。再起動後に有効になります"
- send_stats "レッドハットカーネルをアンインストールします"
+ echo "elrepo カーネルがアンインストールされました。再起動後に有効になります"
+ send_stats "Red Hat カーネルをアンインストールする"
server_reboot
;;
@@ -5014,26 +5899,26 @@ elrepo() {
else
clear
- echo "データをバックアップしてください、そしてあなたのためにLinuxカーネルをアップグレードします"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1mh4y1w7qa?t=529.2"
+ echo "データをバックアップしてください。Linux カーネルをアップグレードします。"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1mH4y1w7qA?t=529.2"
echo "------------------------------------------------"
- echo "レッドハットシリーズの分布のみをサポートしますCentos/Redhat/Alma/Rocky/Oracle"
- echo "Linuxカーネルをアップグレードすると、システムのパフォーマンスとセキュリティが向上する可能性があります。条件が許可され、生産環境を慎重にアップグレードする場合は、試してみることをお勧めします!"
+ echo "Red Hat シリーズのディストリビューション CentOS/RedHat/Alma/Rocky/oracle のみをサポートします"
+ echo "Linux カーネルをアップグレードすると、システムのパフォーマンスとセキュリティが向上します。可能であれば試してみて、慎重に実稼働環境をアップグレードすることをお勧めします。"
echo "------------------------------------------------"
- read -e -p "必ず続行しますか? (y/n):" choice
+ read -e -p "続行してもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
check_swap
elrepo_install
- send_stats "Red Hatカーネルをアップグレードします"
+ send_stats "Red Hat カーネルをアップグレードする"
server_reboot
;;
[Nn])
echo "キャンセル"
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
fi
@@ -5044,7 +5929,7 @@ elrepo() {
clamav_freshclam() {
- echo -e "${gl_huang}ウイルスデータベースを更新してください...${gl_bai}"
+ echo -e "${gl_kjlan}ウイルスデータベースを更新しています...${gl_bai}"
docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
@@ -5058,15 +5943,15 @@ clamav_scan() {
return
fi
- echo -e "${gl_huang}スキャンディレクトリ$@...${gl_bai}"
+ echo -e "${gl_kjlan}ディレクトリ $@ をスキャンしています...${gl_bai}"
- # マウントパラメーターを構築します
+ # ビルドマウントパラメータ
local MOUNT_PARAMS=""
for dir in "$@"; do
MOUNT_PARAMS+="--mount type=bind,source=${dir},target=/mnt/host${dir} "
done
- # CLAMSCANコマンドパラメーターを作成します
+ # clamscan コマンドパラメータを構築する
local SCAN_PARAMS=""
for dir in "$@"; do
SCAN_PARAMS+="/mnt/host${dir} "
@@ -5075,8 +5960,8 @@ clamav_scan() {
mkdir -p /home/docker/clamav/log/ > /dev/null 2>&1
> /home/docker/clamav/log/scan.log > /dev/null 2>&1
- # Dockerコマンドを実行します
- docker run -it --rm \
+ # Dockerコマンドを実行する
+ docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
$MOUNT_PARAMS \
@@ -5084,8 +5969,8 @@ clamav_scan() {
clamav/clamav-debian:latest \
clamscan -r --log=/var/log/clamav/scan.log $SCAN_PARAMS
- echo -e "${gl_lv}$@スキャンが完了し、ウイルスレポートが保存されます${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
- echo -e "${gl_lv}ウイルスがある場合は、お願いします${gl_huang}scan.log${gl_lv}ファイルで見つかったキーワードを検索して、ウイルスの場所を確認する${gl_bai}"
+ echo -e "${gl_lv}$@ 扫描完成,病毒报告存放在${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
+ echo -e "${gl_lv}ウイルスがある場合はお願いします${gl_huang}scan.log${gl_lv}ファイル内で FOUND キーワードを検索して、ウイルスの場所を確認します。${gl_bai}"
}
@@ -5100,20 +5985,20 @@ clamav() {
send_stats "ウイルススキャン管理"
while true; do
clear
- echo "クラマブウイルススキャンツール"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1tqvze4eqm?t=0.1"
+ echo "Clamav ウイルス スキャン ツール"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1TqvZe4EQm?t=0.1"
echo "------------------------"
- echo "これは、主にさまざまな種類のマルウェアを検出および除去するために使用されるオープンソースのウイルス対策ソフトウェアツールです。"
- echo "ウイルス、トロイの木馬、スパイウェア、悪意のあるスクリプト、その他の有害なソフトウェアを含む。"
+ echo "これは、主にさまざまな種類のマルウェアを検出して削除するために使用されるオープンソースのウイルス対策ソフトウェア ツールです。"
+ echo "ウイルス、トロイの木馬、スパイウェア、悪意のあるスクリプト、その他の有害なソフトウェアが含まれます。"
echo "------------------------"
- echo -e "${gl_lv}1。フルディスクスキャン${gl_bai} ${gl_huang}2.重要なディレクトリをスキャンします${gl_bai} ${gl_kjlan}3。カスタムディレクトリスキャン${gl_bai}"
+ echo -e "${gl_lv}1.フルスキャン${gl_bai} ${gl_huang}2. 重要なディレクトリをスキャンする${gl_bai} ${gl_kjlan}3. カスタムディレクトリスキャン${gl_bai}"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- send_stats "フルディスクスキャン"
+ send_stats "フルスキャン"
install_docker
docker volume create clam_db > /dev/null 2>&1
clamav_freshclam
@@ -5122,7 +6007,7 @@ clamav() {
;;
2)
- send_stats "重要なディレクトリスキャン"
+ send_stats "重要なディレクトリのスキャン"
install_docker
docker volume create clam_db > /dev/null 2>&1
clamav_freshclam
@@ -5131,7 +6016,7 @@ clamav() {
;;
3)
send_stats "カスタムディレクトリスキャン"
- read -e -p "スペースで区切られたスキャンにディレクトリを入力してください(例: /etc /var /usr /home /root):" directories
+ read -e -p "スキャンするディレクトリをスペースで区切って入力してください (例: /etc /var /usr /home /root)。" directories
install_docker
clamav_freshclam
clamav_scan $directories
@@ -5146,171 +6031,348 @@ clamav() {
}
+# ============================================================================
+# Linuxカーネルチューニングモジュール(リファクタリング版)
+# 統合されたコア機能 + シーン区別パラメータ + 設定ファイルの永続性 + ハードウェア適応
+# 元の optimize_high_performance / optimize_balance / optimize_web_server /restore_defaults を置き換えます
+# ============================================================================
+# メモリサイズ(MB)を取得
+_get_mem_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
+}
-# 高性能モード最適化関数
-optimize_high_performance() {
- echo -e "${gl_lv}に切り替えます${tiaoyou_moshi}...${gl_bai}"
-
- echo -e "${gl_lv}ファイル記述子を最適化します...${gl_bai}"
- ulimit -n 65535
-
- echo -e "${gl_lv}仮想メモリを最適化します...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=15 2>/dev/null
- sysctl -w vm.dirty_background_ratio=5 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+# 統合されたカーネルチューニングのコア機能
+# パラメータ: $1 = モード名、$2 = シーン (高/バランス/Web/ストリーム/ゲーム)
+_kernel_optimize_core() {
+ local mode_name="$1"
+ local scene="${2:-high}"
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
+ local MEM_MB=$(_get_mem_mb)
+
+ echo -e "${gl_lv}に切り替える${mode_name}...${gl_bai}"
+
+ # ──シーンに合わせてパラメータを設定──
+ local SWAPPINESS DIRTY_RATIO DIRTY_BG_RATIO OVERCOMMIT MIN_FREE_KB VFS_PRESSURE
+ local RMEM_MAX WMEM_MAX TCP_RMEM TCP_WMEM
+ local SOMAXCONN BACKLOG SYN_BACKLOG
+ local PORT_RANGE SCHED_AUTOGROUP THP NUMA FIN_TIMEOUT
+ local KEEPALIVE_TIME KEEPALIVE_INTVL KEEPALIVE_PROBES
+
+ case "$scene" in
+ high|stream|game)
+ # 高性能/ライブブロードキャスト/ゲーム: ラジカルパラメータ
+ SWAPPINESS=10
+ DIRTY_RATIO=15
+ DIRTY_BG_RATIO=5
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=67108864
+ WMEM_MAX=67108864
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ SOMAXCONN=8192
+ BACKLOG=250000
+ SYN_BACKLOG=8192
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=10
+ KEEPALIVE_TIME=300
+ KEEPALIVE_INTVL=30
+ KEEPALIVE_PROBES=5
+ ;;
+ web)
+ # Web サイトサーバー: 同時実行の優先度が高い
+ SWAPPINESS=10
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=33554432
+ WMEM_MAX=33554432
+ TCP_RMEM="4096 131072 33554432"
+ TCP_WMEM="4096 131072 33554432"
+ SOMAXCONN=16384
+ BACKLOG=10000
+ SYN_BACKLOG=16384
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=15
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ balanced)
+ # バランスモード: 中程度の最適化
+ SWAPPINESS=30
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=0
+ VFS_PRESSURE=75
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ SOMAXCONN=4096
+ BACKLOG=5000
+ SYN_BACKLOG=4096
+ PORT_RANGE="1024 49151"
+ SCHED_AUTOGROUP=1
+ THP="always"
+ NUMA=1
+ FIN_TIMEOUT=30
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ esac
- echo -e "${gl_lv}ネットワーク設定を最適化します...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=250000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
-
- echo -e "${gl_lv}キャッシュ管理を最適化します...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
-
- echo -e "${gl_lv}CPU設定を最適化します...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
-
- echo -e "${gl_lv}その他の最適化...${gl_bai}"
- # レイテンシを減らすために、大きな透明なページを無効にします
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
-
-
-}
+ # ── メモリサイズに応じて適応的に調整 ──
+ if [ "$MEM_MB" -ge 16384 ]; then
+ MIN_FREE_KB=131072
+ [ "$scene" != "balanced" ] && SWAPPINESS=5
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ MIN_FREE_KB=32768
+ # 小さいメモリ縮小バッファ
+ if [ "$scene" != "balanced" ]; then
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ fi
+ else
+ MIN_FREE_KB=16384
+ SWAPPINESS=30
+ OVERCOMMIT=0
+ RMEM_MAX=4194304
+ WMEM_MAX=4194304
+ TCP_RMEM="4096 32768 4194304"
+ TCP_WMEM="4096 32768 4194304"
+ SOMAXCONN=1024
+ BACKLOG=1000
+ fi
-# イコライゼーションモード最適化関数
-optimize_balanced() {
- echo -e "${gl_lv}イコライゼーションモードに切り替えます...${gl_bai}"
+ # ── ライブブロードキャストシナリオの追加:UDPバッファの拡大 ──
+ local STREAM_EXTRA=""
+ if [ "$scene" = "stream" ]; then
+ STREAM_EXTRA="
+# ライブストリーミング UDP の最適化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
- echo -e "${gl_lv}ファイル記述子を最適化します...${gl_bai}"
- ulimit -n 32768
+ # ── ゲームサーバーシーン番外編:低遅延優先 ──
+ local GAME_EXTRA=""
+ if [ "$scene" = "game" ]; then
+ GAME_EXTRA="
+# ゲームサーバーの低遅延の最適化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384
+net.ipv4.tcp_slow_start_after_idle = 0"
+ fi
- echo -e "${gl_lv}仮想メモリを最適化します...${gl_bai}"
- sysctl -w vm.swappiness=30 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=32768 2>/dev/null
+ # ── BBRモジュールのロード ──
+ local CC="bbr"
+ local QDISC="fq"
+ local KVER
+ KVER=$(uname -r | grep -oP '^\d+\.\d+')
+ if printf '%s\n%s' "4.9" "$KVER" | sort -V -C; then
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ fi
+ if ! sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
+ else
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
- echo -e "${gl_lv}ネットワーク設定を最適化します...${gl_bai}"
- sysctl -w net.core.rmem_max=8388608 2>/dev/null
- sysctl -w net.core.wmem_max=8388608 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=125000 2>/dev/null
- sysctl -w net.core.somaxconn=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 32768 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 49151' 2>/dev/null
+ # ── 既存の構成をバックアップします ──
+ [ -f "$CONF" ] && cp "$CONF" "${CONF}.bak.$(date +%s)"
+
+ # ── 設定ファイルの書き込み(永続化) ──
+ echo -e "${gl_lv}最適化構成を書き込みます...${gl_bai}"
+ cat > "$CONF" << SYSCTL
+# kejilion カーネルチューニング構成
+# モード: $モード名 |シーン: $scene
+# メモリ: ${MEM_MB}MB |生成時間: $(日付 '+%Y-%m-%d %H:%M:%S')
+
+# ──TCP輻輳制御──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $CC
+
+# ── TCPバッファ ──
+net.core.rmem_max = $RMEM_MAX
+net.core.wmem_max = $WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+
+# ── 接続キュー ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $BACKLOG
+net.ipv4.tcp_max_syn_backlog = $SYN_BACKLOG
+
+# ── TCP接続の最適化 ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = $FIN_TIMEOUT
+net.ipv4.tcp_keepalive_time = $KEEPALIVE_TIME
+net.ipv4.tcp_keepalive_intvl = $KEEPALIVE_INTVL
+net.ipv4.tcp_keepalive_probes = $KEEPALIVE_PROBES
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── ポートとメモリ ──
+net.ipv4.ip_local_port_range = $PORT_RANGE
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── 仮想メモリ ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = $DIRTY_RATIO
+vm.dirty_background_ratio = $DIRTY_BG_RATIO
+vm.overcommit_memory = $OVERCOMMIT
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = $VFS_PRESSURE
+
+# ──CPU/カーネルのスケジューリング──
+kernel.sched_autogroup_enabled = $SCHED_AUTOGROUP
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = $NUMA" || echo "# uma_balancing はサポートされていません")
+
+# ──安全保護──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── ファイル記述子 ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── 接続追跡 ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+echo "net.netfilter.nf_conntrack_max = $((SOMAXCONN * 32))"
+echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+echo "# conntrack が有効になっていません"
+fi)
+$STREAM_EXTRA
+$GAME_EXTRA
+SYSCTL
+
+ # ── 設定を適用します (1 行ずつ、サポートされていないパラメータをスキップします) ──
+ echo -e "${gl_lv}最適化パラメータを適用します...${gl_bai}"
+ local applied=0 skipped=0
+ while IFS= read -r line; do
+ # コメントと空白行をスキップする
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
+ [[ -z "${line// /}" ]] && continue
+ if sysctl -w "$line" >/dev/null 2>&1; then
+ applied=$((applied + 1))
+ else
+ skipped=$((skipped + 1))
+ fi
+ done < "$CONF"
+ echo -e "${gl_lv}適用済み${applied}アイテムパラメータ ${skipped:+、スキップ${skipped}項目がサポートされていないパラメータ}${gl_bai}"
- echo -e "${gl_lv}キャッシュ管理を最適化します...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=75 2>/dev/null
+ # ── 透明大判ページ ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo "$THP" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
+ fi
- echo -e "${gl_lv}CPU設定を最適化します...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
+ # ── ファイル記述子の制限事項 ──
+ if ! grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
- echo -e "${gl_lv}その他の最適化...${gl_bai}"
- # 透明なページを復元します
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # numaバランスを復元します
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# kejilion-optimize
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ fi
+ # ── BBRのこだわり ──
+ if [ "$CC" = "bbr" ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ # 古い sysctl.conf 内の bbr 設定をクリーンアップします (競合を避けるため)。
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+ fi
+ echo -e "${gl_lv}${mode_name}最適化が完了しました!構成は永続化されています${CONF}${gl_bai}"
+ echo -e "${gl_lv}メモリ:${MEM_MB}MB |輻輳アルゴリズム:${CC}|列:${QDISC}${gl_bai}"
}
-# デフォルト設定関数を復元します
-restore_defaults() {
- echo -e "${gl_lv}デフォルト設定に復元します...${gl_bai}"
-
- echo -e "${gl_lv}ファイル記述子を復元します...${gl_bai}"
- ulimit -n 1024
-
- echo -e "${gl_lv}仮想メモリを復元します...${gl_bai}"
- sysctl -w vm.swappiness=60 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=16384 2>/dev/null
-
- echo -e "${gl_lv}ネットワーク設定を復元します...${gl_bai}"
- sysctl -w net.core.rmem_max=212992 2>/dev/null
- sysctl -w net.core.wmem_max=212992 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=1000 2>/dev/null
- sysctl -w net.core.somaxconn=128 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=0 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='32768 60999' 2>/dev/null
-
- echo -e "${gl_lv}キャッシュ管理を復元します...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=100 2>/dev/null
-
- echo -e "${gl_lv}CPU設定を復元します...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}他の最適化を復元します...${gl_bai}"
- # 透明なページを復元します
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # numaバランスを復元します
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# ── 各モードのエントリ機能(元の呼び出しインターフェースはそのまま) ──
+optimize_high_performance() {
+ _kernel_optimize_core "${tiaoyou_moshi:-高性能优化模式}" "high"
}
+optimize_balanced() {
+ _kernel_optimize_core "バランスのとれた最適化モード" "balanced"
+}
-
-# ウェブサイトの構築最適化機能
optimize_web_server() {
- echo -e "${gl_lv}ウェブサイトの構築最適化モードに切り替えます...${gl_bai}"
+ _kernel_optimize_core "ウェブサイト構築最適化モード" "web"
+}
+
+# ── 初期設定に戻す(完全クリーン) ──
+restore_defaults() {
+ echo -e "${gl_lv}デフォルト設定に戻す...${gl_bai}"
- echo -e "${gl_lv}ファイル記述子を最適化します...${gl_bai}"
- ulimit -n 65535
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
- echo -e "${gl_lv}仮想メモリを最適化します...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # 最適化設定ファイル(外部リンク自動チューニング設定含む)を削除します。
+ rm -f "$CONF"
+ rm -f /etc/sysctl.d/99-network-optimize.conf
- echo -e "${gl_lv}ネットワーク設定を最適化します...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=5000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # sysctl.conf に残っている可能性のある bbr 設定をクリーンアップする
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
- echo -e "${gl_lv}キャッシュ管理を最適化します...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # システムのデフォルト設定をリロードする
+ sysctl --system 2>/dev/null | tail -1
- echo -e "${gl_lv}CPU設定を最適化します...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # 透明な巨大ページを復元する
+ [ -f /sys/kernel/mm/transparent_hugepage/enabled ] && \
+ echo always > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
- echo -e "${gl_lv}その他の最適化...${gl_bai}"
- # レイテンシを減らすために、大きな透明なページを無効にします
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # numaバランスを無効にします
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # クリーンなファイル記述子の構成
+ if grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# kejilion-optimize/,+4d' /etc/security/limits.conf
+ fi
+ # BBR 永続性をクリーンアップする
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+ echo -e "${gl_lv}システムがデフォルト設定に復元されました${gl_bai}"
}
@@ -5318,30 +6380,38 @@ Kernel_optimize() {
root_use
while true; do
clear
- send_stats "Linuxカーネルチューニング管理"
- echo "Linuxシステムにおけるカーネルパラメーターの最適化"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1kb421j7yg?t=0.1"
+ send_stats "Linux カーネルのチューニング管理"
+ local current_mode=$(grep "^# モード:" /etc/sysctl.d/99-kejilion-optimize.conf 2>/dev/null | sed 's/# モード: //' | awk -F'|' '{print $1}' | xargs)
+ [ -z "$current_mode" ] && [ -f /etc/sysctl.d/99-network-optimize.conf ] && current_mode="オートチューニングモード"
+ echo "Linuxシステムのカーネルパラメータの最適化"
+ if [ -n "$current_mode" ]; then
+ echo -e "現在のモード:${gl_lv}${current_mode}${gl_bai}"
+ else
+ echo -e "現在のモード:${gl_hui}最適化されていない${gl_bai}"
+ fi
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
echo "------------------------------------------------"
- echo "さまざまなシステムパラメーターチューニングモードが提供され、ユーザーは独自の使用シナリオに従って選択および切り替えることができます。"
- echo -e "${gl_huang}ヒント:${gl_bai}生産環境では注意して使用してください!"
- echo "--------------------"
- echo "1.高性能最適化モード:システムパフォーマンスを最大化し、ファイル記述子、仮想メモリ、ネットワーク設定、キャッシュ管理、CPU設定を最適化します。"
- echo "2。バランスの取れた最適化モード:毎日の使用に適したパフォーマンスとリソース消費のバランス。"
- echo "3.ウェブサイトの最適化モード:Webサイトサーバーを最適化して、接続処理機能、応答速度、全体的なパフォーマンスを並行します。"
- echo "4。ライブブロードキャスト最適化モード:ライブブロードキャストストリーミングの特別なニーズを最適化して、遅延を減らし、伝送パフォーマンスを向上させます。"
- echo "5。ゲームサーバーの最適化モード:ゲームサーバーを最適化して、同時処理機能と応答速度を改善します。"
- echo "6.デフォルト設定を復元します:システム設定をデフォルトの構成に復元します。"
+ echo "さまざまなシステムパラメータチューニングモードを提供し、ユーザーは独自の使用シナリオに応じて切り替えることができます。"
+ echo -e "${gl_huang}ヒント:${gl_bai}本番環境では注意して使用してください。"
+ echo -e "--------------------"
+ echo -e "1. ハイパフォーマンス最適化モード: システムパフォーマンス、積極的なメモリ、およびネットワークパラメータを最大化します。"
+ echo -e "2. バランスのとれた最適化モード: パフォーマンスとリソース消費のバランスをとり、日常的な使用に適しています。"
+ echo -e "3. Web サイト最適化モード: Web サイトサーバー、超高同時接続キュー用に最適化されています。"
+ echo -e "4. ライブ ブロードキャスト最適化モード: ライブ ストリーミングを最適化するために、UDP バッファーを拡大して遅延を削減します。"
+ echo -e "5. ゲームサーバー最適化モード:低遅延を優先してゲームサーバーに最適化します。"
+ echo -e "6. デフォルト設定の復元: システム設定をデフォルト構成に復元します。"
+ echo -e "7. 自動チューニング: テストデータに基づいてカーネルパラメータを自動的にチューニングします。${gl_huang}★${gl_bai}"
echo "--------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "--------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
cd ~
clear
- local tiaoyou_moshi="高性能优化模式"
+ local tiaoyou_moshi="高性能最適化モード"
optimize_high_performance
- send_stats "高性能モードの最適化"
+ send_stats "ハイパフォーマンスモードの最適化"
;;
2)
cd ~
@@ -5353,28 +6423,35 @@ Kernel_optimize() {
cd ~
clear
optimize_web_server
- send_stats "ウェブサイトの最適化モデル"
+ send_stats "ウェブサイト最適化モード"
;;
4)
cd ~
clear
- local tiaoyou_moshi="直播优化模式"
- optimize_high_performance
- send_stats "ライブストリーミング最適化"
+ _kernel_optimize_core "ライブブロードキャスト最適化モード" "stream"
+ send_stats "ライブストリーミングの最適化"
;;
5)
cd ~
clear
- local tiaoyou_moshi="游戏服优化模式"
- optimize_high_performance
+ _kernel_optimize_core "ゲームサーバー最適化モード" "game"
send_stats "ゲームサーバーの最適化"
;;
6)
cd ~
clear
restore_defaults
- send_stats "デフォルト設定を復元します"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh -o /tmp/network-optimize.sh && source /tmp/network-optimize.sh && restore_network_defaults
+ send_stats "デフォルト設定を復元する"
+ ;;
+
+ 7)
+ cd ~
+ clear
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ send_stats "カーネル自動チューニング"
;;
+
*)
break
;;
@@ -5387,6 +6464,8 @@ Kernel_optimize() {
+
+
update_locale() {
local lang=$1
local locale_file=$2
@@ -5400,7 +6479,7 @@ update_locale() {
locale-gen
echo "LANG=${lang}" > /etc/default/locale
export LANG=${lang}
- echo -e "${gl_lv}システム言語は次のように変更されています。$langSSHの再接続が有効になります。${gl_bai}"
+ echo -e "${gl_lv}システム言語は次のように変更されました。$lang有効にするには、SSH に再接続します。${gl_bai}"
hash -r
break_end
@@ -5409,17 +6488,17 @@ update_locale() {
install glibc-langpack-zh
localectl set-locale LANG=${lang}
echo "LANG=${lang}" | tee /etc/locale.conf
- echo -e "${gl_lv}システム言語は次のように変更されています。$langSSHの再接続が有効になります。${gl_bai}"
+ echo -e "${gl_lv}システム言語は次のように変更されました。$lang有効にするには、SSH に再接続します。${gl_bai}"
hash -r
break_end
;;
*)
- echo "サポートされていないシステム:$ID"
+ echo "サポートされていないシステム:$ID"
break_end
;;
esac
else
- echo "サポートされていないシステム、システムタイプは認識できません。"
+ echo "サポートされていないシステムです。システムの種類を識別できません。"
break_end
fi
}
@@ -5429,29 +6508,29 @@ update_locale() {
linux_language() {
root_use
-send_stats "システム言語を切り替えます"
+send_stats "システム言語を切り替える"
while true; do
clear
- echo "現在のシステム言語:$LANG"
+ echo "現在のシステム言語:$LANG"
echo "------------------------"
- echo "1。英語2。簡素化された中国語3。伝統的な中国語"
+ echo "1. 英語 2. 簡体字中国語 3. 繁体字中国語"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
update_locale "en_US.UTF-8" "en_US.UTF-8"
- send_stats "英語に切り替えます"
+ send_stats "英語に切り替えてください"
;;
2)
update_locale "zh_CN.UTF-8" "zh_CN.UTF-8"
- send_stats "簡素化された中国人に切り替えます"
+ send_stats "簡体字中国語に切り替える"
;;
3)
update_locale "zh_TW.UTF-8" "zh_TW.UTF-8"
- send_stats "伝統的な中国人に切り替えます"
+ send_stats "繁体字中国語に切り替える"
;;
*)
break
@@ -5473,7 +6552,7 @@ else
echo "${bianse}" >> ~/.profile
# source ~/.profile
fi
-echo -e "${gl_lv}変更が完了します。 SSHを再接続して変更を表示します!${gl_bai}"
+echo -e "${gl_lv}変更が完了しました。 SSH に再接続して変更を確認してください。${gl_bai}"
hash -r
break_end
@@ -5484,10 +6563,10 @@ break_end
shell_bianse() {
root_use
- send_stats "コマンドラインの美化ツール"
+ send_stats "コマンドライン美化ツール"
while true; do
clear
- echo "コマンドラインの美化ツール"
+ echo "コマンドライン美化ツール"
echo "------------------------"
echo -e "1. \033[1;32mroot \033[1;34mlocalhost \033[1;31m~ \033[0m${gl_bai}#"
echo -e "2. \033[1;35mroot \033[1;36mlocalhost \033[1;33m~ \033[0m${gl_bai}#"
@@ -5497,9 +6576,9 @@ shell_bianse() {
echo -e "6. \033[1;33mroot \033[1;34mlocalhost \033[1;35m~ \033[0m${gl_bai}#"
echo -e "7. root localhost ~ #"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
@@ -5544,7 +6623,7 @@ shell_bianse() {
linux_trash() {
root_use
- send_stats "システムリサイクルステーション"
+ send_stats "システムのごみ箱"
local bashrc_profile="/root/.bashrc"
local TRASH_DIR="$HOME/.local/share/Trash/files"
@@ -5553,23 +6632,23 @@ linux_trash() {
local trash_status
if ! grep -q "trash-put" "$bashrc_profile"; then
- trash_status="${gl_hui}未启用${gl_bai}"
+ trash_status="${gl_hui}有効になっていません${gl_bai}"
else
- trash_status="${gl_lv}已启用${gl_bai}"
+ trash_status="${gl_lv}有効${gl_bai}"
fi
clear
- echo -e "現在のリサイクルビン${trash_status}"
- echo -e "有効になった後、RMによって削除されたファイルは、最初にリサイクルビンに入り、重要なファイルの誤った削除を防ぎます!"
+ echo -e "現在のごみ箱${trash_status}"
+ echo -e "有効にすると、重要なファイルを誤って削除することを防ぐために、rm によって削除されたファイルは最初にごみ箱に入れられます。"
echo "------------------------------------------------"
- ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "リサイクルビンは空です"
+ ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "ごみ箱が空です"
echo "------------------------"
- echo "1.リサイクルビン2を有効にします。リサイクルビンを閉じます"
- echo "3。コンテンツを復元4。リサイクルビンをクリアします"
+ echo "1. ごみ箱を有効にする 2. ごみ箱を閉じる"
+ echo "3. コンテンツを復元する 4. ごみ箱を空にする"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
@@ -5577,7 +6656,7 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='trash-put'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "リサイクルビンが有効になり、削除されたファイルがリサイクルビンに移動されます。"
+ echo "ごみ箱が有効になっていると、削除されたファイルはごみ箱に移動されます。"
sleep 2
;;
2)
@@ -5585,23 +6664,23 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='rm -i'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "リサイクルビンが閉じられ、ファイルが直接削除されます。"
+ echo "ごみ箱が閉じられ、ファイルは直接削除されます。"
sleep 2
;;
3)
- read -e -p "復元するにはファイル名を入力してください。" file_to_restore
+ read -e -p "復元するファイル名を入力してください:" file_to_restore
if [ -e "$TRASH_DIR/$file_to_restore" ]; then
mv "$TRASH_DIR/$file_to_restore" "$HOME/"
echo "$file_to_restoreホームディレクトリに復元されました。"
else
- echo "ファイルは存在しません。"
+ echo "ファイルが存在しません。"
fi
;;
4)
- read -e -p "リサイクルビンをクリアすることを確認しますか? [Y/N]:" confirm
+ read -e -p "ごみ箱を空にしてもよろしいですか? [y/n]:" confirm
if [[ "$confirm" == "y" ]]; then
trash-empty
- echo "リサイクルビンがクリアされました。"
+ echo "ごみ箱が空になりました。"
fi
;;
*)
@@ -5611,21 +6690,24 @@ linux_trash() {
done
}
+linux_fav() {
+send_stats "コマンドのお気に入り"
+bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+}
-
-# バックアップを作成します
+# バックアップの作成
create_backup() {
- send_stats "バックアップを作成します"
+ send_stats "バックアップの作成"
local TIMESTAMP=$(date +"%Y%m%d%H%M%S")
- # ユーザーにバックアップディレクトリを入力するように求めます
- echo "バックアップ例を作成します:"
- echo "- 単一のディレクトリをバックアップします: /var /www"
- echo "- バックアップ複数のディレクトリ: /etc /home /var /log"
- echo "-directEnterはデフォルトのディレクトリ( /etc /usr /home)を使用します"
- read -r -p "ディレクトリを入力してバックアップしてください(複数のディレクトリがスペースで区切られています。直接入力する場合は、デフォルトのディレクトリを使用してください):" input
+ # ユーザーにバックアップ ディレクトリの入力を求めるプロンプトを表示する
+ echo "バックアップの作成例:"
+ echo "- 単一ディレクトリをバックアップします: /var/www"
+ echo "- 複数のディレクトリをバックアップします: /etc /home /var/log"
+ echo "- Enter キーを押して、デフォルトのディレクトリ (/etc /usr /home) を使用します。"
+ read -e -p "バックアップするディレクトリを入力してください (複数のディレクトリをスペースで区切って、Enter キーを押してデフォルトのディレクトリを使用します)。" input
- # ユーザーがディレクトリを入力しない場合は、デフォルトのディレクトリを使用します
+ # ユーザーがディレクトリを入力しない場合は、デフォルトのディレクトリが使用されます。
if [ -z "$input" ]; then
BACKUP_PATHS=(
"/etc" # 配置文件和软件包配置
@@ -5633,11 +6715,11 @@ create_backup() {
"/home" # 用户数据
)
else
- # ユーザーが入力したディレクトリをスペースごとに配列に分離します
+ # ユーザーが配列に入力したディレクトリをスペースで区切ります。
IFS=' ' read -r -a BACKUP_PATHS <<< "$input"
fi
- # バックアップファイルプレフィックスを生成します
+ # バックアップ ファイルのプレフィックスを生成する
local PREFIX=""
for path in "${BACKUP_PATHS[@]}"; do
# ディレクトリ名を抽出し、スラッシュを削除します
@@ -5648,77 +6730,77 @@ create_backup() {
# 最後のアンダースコアを削除します
local PREFIX=${PREFIX%_}
- # バックアップファイル名を生成します
+ # バックアップファイル名の生成
local BACKUP_NAME="${PREFIX}_$TIMESTAMP.tar.gz"
- # ユーザーが選択したディレクトリを印刷します
- echo "選択したバックアップディレクトリは次のとおりです。"
+ # ユーザーが選択した印刷ディレクトリ
+ echo "選択したバックアップ ディレクトリは次のとおりです。"
for path in "${BACKUP_PATHS[@]}"; do
echo "- $path"
done
- # バックアップを作成します
- echo "バックアップを作成します$BACKUP_NAME..."
+ # バックアップの作成
+ echo "バックアップの作成$BACKUP_NAME..."
install tar
tar -czvf "$BACKUP_DIR/$BACKUP_NAME" "${BACKUP_PATHS[@]}"
- # コマンドが成功しているかどうかを確認してください
+ # コマンドが成功したかどうかを確認する
if [ $? -eq 0 ]; then
- echo "バックアップは正常に作成されました:$BACKUP_DIR/$BACKUP_NAME"
+ echo "バックアップが正常に作成されました:$BACKUP_DIR/$BACKUP_NAME"
else
- echo "バックアップの作成に失敗しました!"
+ echo "バックアップの作成に失敗しました!"
exit 1
fi
}
-# バックアップを復元します
+# バックアップを復元する
restore_backup() {
- send_stats "バックアップを復元します"
- # 復元するバックアップを選択します
- read -e -p "復元するには、バックアップファイル名を入力してください。" BACKUP_NAME
+ send_stats "バックアップを復元する"
+ # 復元するバックアップを選択してください
+ read -e -p "復元するバックアップ ファイル名を入力してください:" BACKUP_NAME
- # バックアップファイルが存在するかどうかを確認します
+ # バックアップファイルが存在するか確認する
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
- echo "バックアップファイルは存在しません!"
+ echo "バックアップファイルが存在しません!"
exit 1
fi
- echo "バックアップの回復$BACKUP_NAME..."
+ echo "バックアップの復元$BACKUP_NAME..."
tar -xzvf "$BACKUP_DIR/$BACKUP_NAME" -C /
if [ $? -eq 0 ]; then
- echo "バックアップと復元を正常に!"
+ echo "バックアップと復元が成功しました。"
else
- echo "バックアップリカバリに失敗しました!"
+ echo "バックアップ復元に失敗しました!"
exit 1
fi
}
-# バックアップをリストします
+# バックアップの一覧表示
list_backups() {
- echo "利用可能なバックアップ:"
+ echo "利用可能なバックアップ:"
ls -1 "$BACKUP_DIR"
}
-# バックアップを削除します
+# バックアップの削除
delete_backup() {
- send_stats "バックアップを削除します"
+ send_stats "バックアップの削除"
- read -e -p "削除するには、バックアップファイル名を入力してください。" BACKUP_NAME
+ read -e -p "削除するバックアップ ファイル名を入力してください:" BACKUP_NAME
- # バックアップファイルが存在するかどうかを確認します
+ # バックアップファイルが存在するか確認する
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
- echo "バックアップファイルは存在しません!"
+ echo "バックアップファイルが存在しません!"
exit 1
fi
- # バックアップを削除します
+ # バックアップの削除
rm -f "$BACKUP_DIR/$BACKUP_NAME"
if [ $? -eq 0 ]; then
- echo "バックアップは正常に削除されました!"
+ echo "バックアップが正常に削除されました。"
else
- echo "バックアップの削除が失敗しました!"
+ echo "バックアップの削除に失敗しました!"
exit 1
fi
}
@@ -5734,18 +6816,18 @@ linux_backup() {
echo "------------------------"
list_backups
echo "------------------------"
- echo "1.バックアップを作成する2。バックアップを復元3。バックアップを削除します"
+ echo "1. バックアップの作成 2. バックアップの復元 3. バックアップの削除"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case $choice in
1) create_backup ;;
2) restore_backup ;;
3) delete_backup ;;
*) break ;;
esac
- read -e -p "Enterを押して続行します..."
+ read -e -p "Enter を押して続行します..."
done
}
@@ -5757,135 +6839,255 @@ linux_backup() {
-# 接続リストを表示します
-list_connections() {
- echo "接続の保存:"
- echo "------------------------"
- cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
- echo "------------------------"
+# SSH入力正規化機能
+kj_ssh_validate_host() {
+ local host="$1"
+ [[ -n "$host" && ! "$host" =~ [[:space:]] && "$host" =~ ^[A-Za-z0-9._:-]+$ ]]
}
+kj_ssh_validate_port() {
+ local port="$1"
+ [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
+}
-# 新しい接続を追加します
-add_connection() {
- send_stats "新しい接続を追加します"
- echo "新しい接続を作成する例:"
- echo "- 接続名:my_server"
- echo "- IPアドレス:192.168.1.100"
- echo "- ユーザー名:root"
- echo "- ポート:22"
- echo "------------------------"
- read -e -p "接続名を入力してください:" name
- read -e -p "IPアドレスを入力してください:" ip
- read -e -p "ユーザー名(デフォルト:root)を入力してください:" user
- local user=${user:-root} # 如果用户未输入,则使用默认值 root
- read -e -p "ポート番号を入力してください(デフォルト:22):" port
- local port=${port:-22} # 如果用户未输入,则使用默认值 22
-
- echo "認証方法を選択してください:"
- echo "1。パスワード"
- echo "2。キー"
- read -e -p "選択(1/2)を入力してください:" auth_choice
+kj_ssh_validate_user() {
+ local user="$1"
+ [[ -n "$user" && "$user" =~ ^[A-Za-z_][A-Za-z0-9._-]*$ ]]
+}
+
+kj_ssh_read_host_port() {
+ local host_prompt="$1"
+ local port_prompt="$2"
+ local default_port="${3:-22}"
+
+ while true; do
+ read -e -p "$host_prompt" KJ_SSH_HOST
+ if kj_ssh_validate_host "$KJ_SSH_HOST"; then
+ break
+ fi
+ echo "エラー: 有効なサーバー アドレスを入力してください。"
+ done
+
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ break
+ fi
+ echo "エラー: ポートは 1 ~ 65535 の数値である必要があります。"
+ done
+}
+
+kj_ssh_read_host_user_port() {
+ local host_prompt="$1"
+ local user_prompt="$2"
+ local port_prompt="$3"
+ local default_user="${4:-root}"
+ local default_port="${5:-22}"
+
+ kj_ssh_read_host_port "$host_prompt" "$port_prompt" "$default_port"
+
+ while true; do
+ read -e -p "$user_prompt" KJ_SSH_USER
+ KJ_SSH_USER=${KJ_SSH_USER:-$default_user}
+ if kj_ssh_validate_user "$KJ_SSH_USER"; then
+ break
+ fi
+ echo "エラー: ユーザー名の形式が正しくありません。"
+ done
+}
+
+kj_ssh_parse_remote() {
+ local remote_raw="$1"
+ local default_user="${2:-root}"
+ local remote_user remote_host
+
+ if [[ "$remote_raw" == *@* ]]; then
+ remote_user="${remote_raw%@*}"
+ remote_host="${remote_raw#*@}"
+ else
+ remote_user="$default_user"
+ remote_host="$remote_raw"
+ fi
+
+ if ! kj_ssh_validate_user "$remote_user"; then
+ echo "エラー: SSH ユーザー名の形式が正しくありません。"
+ return 1
+ fi
+
+ if ! kj_ssh_validate_host "$remote_host"; then
+ echo "エラー: SSH ホスト アドレスの形式が正しくありません。"
+ return 1
+ fi
+
+ KJ_SSH_USER="$remote_user"
+ KJ_SSH_HOST="$remote_host"
+ KJ_SSH_REMOTE="$remote_user@$remote_host"
+}
+
+kj_ssh_read_auth() {
+ local key_file="$1"
+ local password_or_key=""
+
+ echo "認証方法を選択してください:"
+ echo "1. パスワード"
+ echo "2. キー"
+ read -e -p "選択肢を入力してください (1/2):" auth_choice
case $auth_choice in
1)
- read -s -p "パスワードを入力してください:" password_or_key
- echo # 换行
+ read -s -p "パスワードを入力してください:" password_or_key
+ echo
+ if [ -z "$password_or_key" ]; then
+ echo "エラー: パスワードを空にすることはできません。"
+ return 1
+ fi
+ KJ_SSH_AUTH_METHOD="password"
+ KJ_SSH_AUTH_SECRET="$password_or_key"
;;
2)
- echo "キーコンテンツを貼り付けてください(貼り付け後に2回Enterを押します)を押してください):"
- local password_or_key=""
+ echo "キーの内容を貼り付けてください (貼り付け後に Enter を 2 回押します)。"
while IFS= read -r line; do
- # 入力が空で、キーコンテンツにすでに開始が含まれている場合、入力は終了します
if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
break
fi
- # それが最初の行であるか、キーコンテンツが入力されている場合は、さらに追加し続けます
if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- local password_or_key+="${line}"$'\n'
+ password_or_key+="${line}"$'\n'
fi
done
- # キーコンテンツのかどうかを確認してください
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/$name.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- local password_or_key="$key_file"
+ if [[ "$password_or_key" != *"-----BEGIN"* || "$password_or_key" != *"PRIVATE KEY-----"* ]]; then
+ echo "キーの内容が無効です!"
+ return 1
fi
+
+ mkdir -p "$(dirname "$key_file")"
+ echo -n "$password_or_key" > "$key_file"
+ chmod 600 "$key_file"
+ KJ_SSH_AUTH_METHOD="key"
+ KJ_SSH_AUTH_SECRET="$key_file"
;;
*)
- echo "無効な選択!"
- return
+ echo "無効な選択です!"
+ return 1
;;
esac
+}
+
+kj_ssh_read_password() {
+ local prompt="${1:-请输入密码: }"
+ while true; do
+ read -e -s -p "$prompt" KJ_SSH_PASSWORD
+ echo
+ [ -n "$KJ_SSH_PASSWORD" ] && break
+ echo "エラー: パスワードを空にすることはできません。"
+ done
+}
+
+kj_ssh_read_port() {
+ local port_prompt="$1"
+ local default_port="${2:-22}"
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ return 0
+ fi
+ echo "エラー: ポートは 1 ~ 65535 の数値である必要があります。"
+ done
+}
+
+# 接続リストを表示
+list_connections() {
+ echo "保存された接続:"
+ echo "------------------------"
+ cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
+ echo "------------------------"
+}
+
- echo "$name|$ip|$user|$port|$password_or_key" >> "$CONFIG_FILE"
- echo "接続が保存されます!"
+# 新しい接続を追加
+add_connection() {
+ send_stats "新しい接続を追加"
+ echo "新しい接続を作成する例:"
+ echo "- 接続名: my_server"
+ echo "- IP アドレス: 192.168.1.100"
+ echo "- ユーザー名: root"
+ echo "- ポート: 22"
+ echo "------------------------"
+ read -e -p "接続名を入力してください:" name
+
+ kj_ssh_read_host_user_port "IP アドレスを入力してください:" "ユーザー名を入力してください (デフォルト: root):" "ポート番号を入力してください (デフォルト: 22):" "root" "22"
+ if ! kj_ssh_read_auth "$KEY_DIR/$name.key"; then
+ return
+ fi
+
+ echo "$name|$KJ_SSH_HOST|$KJ_SSH_USER|$KJ_SSH_PORT|$KJ_SSH_AUTH_SECRET" >> "$CONFIG_FILE"
+ echo "接続が保存されました!"
}
-# 接続を削除します
+# 接続の削除
delete_connection() {
- send_stats "接続を削除します"
- read -e -p "削除するには、接続番号を入力してください。" num
+ send_stats "接続の削除"
+ read -e -p "削除する接続番号を入力してください:" num
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "エラー:対応する接続は見つかりませんでした。"
+ echo "エラー: 対応する接続が見つかりません。"
return
fi
IFS='|' read -r name ip user port password_or_key <<< "$connection"
- # 接続がキーファイルを使用している場合、キーファイルを削除します
+ # 接続にキー ファイルが使用されている場合は、キー ファイルを削除します
if [[ "$password_or_key" == "$KEY_DIR"* ]]; then
rm -f "$password_or_key"
fi
sed -i "${num}d" "$CONFIG_FILE"
- echo "接続が削除されました!"
+ echo "接続が削除されました!"
}
-# 接続を使用します
+# 接続を使用する
use_connection() {
- send_stats "接続を使用します"
- read -e -p "使用するには、接続番号を入力してください。" num
+ send_stats "接続を使用する"
+ read -e -p "使用する接続番号を入力してください:" num
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "エラー:対応する接続は見つかりませんでした。"
+ echo "エラー: 対応する接続が見つかりません。"
return
fi
IFS='|' read -r name ip user port password_or_key <<< "$connection"
- echo "接続$name ($ip)..."
+ echo "接続先$name ($ip)..."
if [[ -f "$password_or_key" ]]; then
- # キーに接続します
+ # キーを使用して接続する
ssh -o StrictHostKeyChecking=no -i "$password_or_key" -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
- echo "接続に失敗しました!以下を確認してください。"
- echo "1。キーファイルパスは正しいですか?$password_or_key"
- echo "2。キーファイルの権限が正しいかどうか(600である必要があります)。"
- echo "3.ターゲットサーバーがキーを使用してログインできるかどうか。"
+ echo "接続に失敗しました!以下の点をご確認ください。"
+ echo "1. キーファイルのパスは正しいですか?$password_or_key"
+ echo "2. キー ファイルのアクセス許可は正しいか (600 である必要があります)。"
+ echo "3. ターゲットサーバーがキーを使用したログインを許可するかどうか。"
fi
else
- # パスワードで接続します
+ # パスワードを使用して接続する
if ! command -v sshpass &> /dev/null; then
- echo "エラー:SSHPassはインストールされていません。最初にSSHPassをインストールしてください。"
- echo "インストール方法:"
+ echo "エラー: sshpass がインストールされていません。最初に sshpass をインストールしてください。"
+ echo "インストール方法:"
echo " - Ubuntu/Debian: apt install sshpass"
echo " - CentOS/RHEL: yum install sshpass"
return
fi
sshpass -p "$password_or_key" ssh -o StrictHostKeyChecking=no -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
- echo "接続に失敗しました!以下を確認してください。"
- echo "1.ユーザー名とパスワードが正しいかどうか。"
- echo "2。ターゲットサーバーがパスワードログインを許可するかどうか。"
- echo "3.ターゲットサーバーのSSHサービスが正常に実行されているかどうか。"
+ echo "接続に失敗しました!以下の点をご確認ください。"
+ echo "1. ユーザー名とパスワードは正しいですか?"
+ echo "2. ターゲットサーバーがパスワードログインを許可するかどうか。"
+ echo "3. 対象サーバのSSHサービスが正常に動作しているか。"
fi
fi
}
@@ -5897,7 +7099,7 @@ ssh_manager() {
CONFIG_FILE="$HOME/.ssh_connections"
KEY_DIR="$HOME/.ssh/ssh_manager_keys"
- # 構成ファイルとキーディレクトリが存在するかどうかを確認し、それが存在しない場合は、それを作成します
+ # 設定ファイルとキーディレクトリが存在するかどうかを確認し、存在しない場合は作成します。
if [[ ! -f "$CONFIG_FILE" ]]; then
touch "$CONFIG_FILE"
fi
@@ -5910,20 +7112,20 @@ ssh_manager() {
while true; do
clear
echo "SSHリモート接続ツール"
- echo "SSHを介して他のLinuxシステムに接続できます"
+ echo "SSH経由で他のLinuxシステムに接続可能"
echo "------------------------"
list_connections
- echo "1.新しい接続を作成する2。接続を使用する3。接続を削除します"
+ echo "1. 新しい接続を作成します。 2. 接続を使用します。 3. 接続を削除します。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case $choice in
1) add_connection ;;
2) use_connection ;;
3) delete_connection ;;
0) break ;;
- *) echo "無効な選択、もう一度やり直してください。" ;;
+ *) echo "選択が無効です。もう一度お試しください。" ;;
esac
done
}
@@ -5939,137 +7141,165 @@ ssh_manager() {
-# 利用可能なハードディスクパーティションをリストします
+# 利用可能なハードディスクのパーティションをリストする
list_partitions() {
- echo "利用可能なハードディスクパーティション:"
+ echo "利用可能なハードドライブのパーティション:"
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT | grep -v "sr\|loop"
}
-# パーティションをマウントします
+
+# 永続的にマウントされたパーティション
mount_partition() {
- send_stats "パーティションをマウントします"
- read -e -p "マウントするパーティション名を入力してください(たとえば、SDA1):" PARTITION
+ send_stats "パーティションをマウントする"
+ read -e -p "マウントするパーティションの名前を入力してください (例: sda1):" PARTITION
- # パーティションが存在するかどうかを確認します
- if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
- echo "パーティションは存在しません!"
- return
+ DEVICE="/dev/$PARTITION"
+ MOUNT_POINT="/mnt/$PARTITION"
+
+ # パーティションが存在するかどうかを確認する
+ if ! lsblk -no NAME | grep -qw "$PARTITION"; then
+ echo "パーティションが存在しません!"
+ return 1
fi
- # パーティションが既にマウントされているかどうかを確認してください
- if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "パーティションはすでに取り付けられています!"
- return
+ # 実装されているか確認する
+ if mount | grep -qw "$DEVICE"; then
+ echo "パーティションが取り付けられました!"
+ return 1
fi
- # マウントポイントを作成します
- MOUNT_POINT="/mnt/$PARTITION"
- mkdir -p "$MOUNT_POINT"
+ # UUIDを取得する
+ UUID=$(blkid -s UUID -o value "$DEVICE")
+ if [ -z "$UUID" ]; then
+ echo "UUIDを取得できません!"
+ return 1
+ fi
- # パーティションをマウントします
- mount "/dev/$PARTITION" "$MOUNT_POINT"
+ # ファイルシステムの種類を取得する
+ FSTYPE=$(blkid -s TYPE -o value "$DEVICE")
+ if [ -z "$FSTYPE" ]; then
+ echo "ファイル システム タイプを取得できません!"
+ return 1
+ fi
- if [ $? -eq 0 ]; then
- echo "パーティションマウントに正常に:$MOUNT_POINT"
- else
- echo "パーティションマウントは失敗しました!"
+ # マウントポイントの作成
+ mkdir -p "$MOUNT_POINT"
+
+ # マウント
+ if ! mount "$DEVICE" "$MOUNT_POINT"; then
+ echo "パーティションのマウントに失敗しました!"
rmdir "$MOUNT_POINT"
+ return 1
+ fi
+
+ echo "パーティションが正常にマウントされました$MOUNT_POINT"
+
+ # /etc/fstab をチェックして、UUID またはマウント ポイントがすでに存在するかどうかを確認します。
+ if grep -qE "UUID=$UUID|[[:space:]]$MOUNT_POINT[[:space:]]" /etc/fstab; then
+ echo "パーティション レコードはすでに /etc/fstab に存在するため、書き込みをスキップします"
+ return 0
fi
+
+ # /etc/fstab に書き込みます
+ echo "UUID=$UUID $MOUNT_POINT $FSTYPE defaults,nofail 0 2" >> /etc/fstab
+
+ echo "永続的なマウントを実現するために /etc/fstab に書き込まれます"
}
-# パーティションをアンインストールします
+
+# パーティションをアンマウントする
unmount_partition() {
- send_stats "パーティションをアンインストールします"
- read -e -p "パーティション名(たとえば、SDA1)を入力してください。" PARTITION
+ send_stats "パーティションをアンマウントする"
+ read -e -p "アンマウントするパーティションの名前を入力してください (例: sda1):" PARTITION
- # パーティションが既にマウントされているかどうかを確認してください
+ # パーティションがマウントされているかどうかを確認する
MOUNT_POINT=$(lsblk -o MOUNTPOINT | grep -w "$PARTITION")
if [ -z "$MOUNT_POINT" ]; then
- echo "パーティションはマウントされていません!"
+ echo "パーティションがマウントされていません!"
return
fi
- # パーティションをアンインストールします
+ # パーティションをアンマウントする
umount "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "パーティションのアンインストールに正常に:$MOUNT_POINT"
+ echo "パーティションが正常にアンインストールされました:$MOUNT_POINT"
rmdir "$MOUNT_POINT"
else
- echo "パーティションのアンインストールに失敗しました!"
+ echo "パーティションのアンインストールに失敗しました!"
fi
}
-# マウントされたパーティションをリストします
+# マウントされたパーティションをリストする
list_mounted_partitions() {
- echo "マウントされたパーティション:"
+ echo "マウントされたパーティション:"
df -h | grep -v "tmpfs\|udev\|overlay"
}
-# フォーマットパーティション
+# パーティションをフォーマットする
format_partition() {
- send_stats "フォーマットパーティション"
- read -e -p "パーティション名を入力してフォーマット(たとえば、SDA1):" PARTITION
+ send_stats "パーティションをフォーマットする"
+ read -e -p "フォーマットするパーティションの名前を入力してください (例: sda1):" PARTITION
- # パーティションが存在するかどうかを確認します
+ # パーティションが存在するかどうかを確認する
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
- echo "パーティションは存在しません!"
+ echo "パーティションが存在しません!"
return
fi
- # パーティションが既にマウントされているかどうかを確認してください
+ # パーティションがマウントされているかどうかを確認する
if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "パーティションがマウントされました。最初にアンインストールしてください!"
+ echo "パーティションはマウントされています。最初にアンマウントしてください。"
return
fi
- # ファイルシステムタイプを選択します
- echo "ファイルシステムタイプを選択してください:"
+ # ファイルシステムの種類を選択してください
+ echo "ファイル システムのタイプを選択してください:"
echo "1. ext4"
echo "2. xfs"
echo "3. ntfs"
echo "4. vfat"
- read -e -p "選択を入力してください:" FS_CHOICE
+ read -e -p "選択肢を入力してください:" FS_CHOICE
case $FS_CHOICE in
1) FS_TYPE="ext4" ;;
2) FS_TYPE="xfs" ;;
3) FS_TYPE="ntfs" ;;
4) FS_TYPE="vfat" ;;
- *) echo "無効な選択!"; return ;;
+ *) echo "無効な選択です!"; return ;;
esac
- # フォーマットを確認します
- read -e -p "フォーマットパーティション /dev /$PARTITIONのために$FS_TYPEそれですか? (y/n):" CONFIRM
+ # フォーマットの確認
+ read -e -p "フォーマットされたパーティション /dev/ を確認します$PARTITIONのために$FS_TYPE? (y/n):" CONFIRM
if [ "$CONFIRM" != "y" ]; then
echo "操作はキャンセルされました。"
return
fi
- # フォーマットパーティション
- echo "パーティション /dev /のフォーマット /$PARTITIONのために$FS_TYPE ..."
+ # パーティションをフォーマットする
+ echo "パーティション /dev/ をフォーマットしています$PARTITIONのために$FS_TYPE ..."
mkfs.$FS_TYPE "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "パーティション形式は成功しました!"
+ echo "パーティションが正常にフォーマットされました。"
else
- echo "パーティションのフォーマットが失敗しました!"
+ echo "パーティションのフォーマットに失敗しました!"
fi
}
-# パーティションステータスを確認します
+# パーティションのステータスを確認する
check_partition() {
- send_stats "パーティションステータスを確認します"
- read -e -p "パーティション名を入力して確認してください(たとえばSDA1):" PARTITION
+ send_stats "パーティションのステータスを確認する"
+ read -e -p "確認するパーティション名を入力してください (例: sda1):" PARTITION
- # パーティションが存在するかどうかを確認します
+ # パーティションが存在するかどうかを確認する
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
- echo "パーティションは存在しません!"
+ echo "パーティションが存在しません!"
return
fi
- # パーティションステータスを確認します
- echo "パーティション /dev /$PARTITION状態:"
+ # パーティションのステータスを確認する
+ echo "パーティション /dev/ を確認してください$PARTITION状態:"
fsck "/dev/$PARTITION"
}
@@ -6078,17 +7308,17 @@ disk_manager() {
send_stats "ハードディスク管理機能"
while true; do
clear
- echo "ハードディスクパーティション管理"
- echo -e "${gl_huang}この関数は、テスト期間中に内部的にテストされています。生産環境では使用しないでください。${gl_bai}"
+ echo "ハードディスクのパーティション管理"
+ echo -e "${gl_huang}この機能は内部テスト中であるため、運用環境では使用しないでください。${gl_bai}"
echo "------------------------"
list_partitions
echo "------------------------"
- echo "1。パーティションをマウント2。パーティションをアンインストールする3。マウントされたパーティションを表示"
- echo "4。パーティション5をフォーマットします。パーティションステータスを確認します"
+ echo "1. パーティションをマウントします。 2. パーティションをアンマウントします。 3. マウントされたパーティションを表示します。"
+ echo "4. パーティションをフォーマットします。 5. パーティションのステータスを確認します。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case $choice in
1) mount_partition ;;
2) unmount_partition ;;
@@ -6097,128 +7327,100 @@ disk_manager() {
5) check_partition ;;
*) break ;;
esac
- read -e -p "Enterを押して続行します..."
+ read -e -p "Enter を押して続行します..."
done
}
-# タスクリストを表示します
+# タスクリストを表示
list_tasks() {
- echo "保存された同期タスク:"
+ echo "保存された同期タスク:"
echo "---------------------------------"
awk -F'|' '{print NR " - " $1 " ( " $2 " -> " $3":"$4 " )"}' "$CONFIG_FILE"
echo "---------------------------------"
}
-# 新しいタスクを追加します
+# 新しいタスクを追加する
add_task() {
- send_stats "新しい同期タスクを追加します"
- echo "新しい同期タスクを作成する例:"
- echo "- タスク名:backup_www"
- echo "- ローカルディレクトリ: /var /www"
- echo "- リモートアドレス:user@192.168.1.100"
- echo "- リモートディレクトリ: /バックアップ /www"
- echo "- ポート番号(デフォルト22)"
+ send_stats "新しい同期タスクを追加する"
+ echo "新しい同期タスクの作成例:"
+ echo "- タスク名:backup_www"
+ echo "- ローカルディレクトリ: /var/www"
+ echo "- リモートアドレス: user@192.168.1.100"
+ echo "- リモートディレクトリ: /backup/www"
+ echo "- ポート番号 (デフォルトは 22)"
echo "---------------------------------"
- read -e -p "タスク名を入力してください:" name
- read -e -p "ローカルディレクトリを入力してください:" local_path
- read -e -p "リモートディレクトリを入力してください:" remote_path
- read -e -p "リモートユーザー@IPを入力してください:" remote
- read -e -p "SSHポートを入力してください(デフォルト22):" port
- port=${port:-22}
-
- echo "認証方法を選択してください:"
- echo "1。パスワード"
- echo "2。キー"
- read -e -p "(1/2)を選択してください:" auth_choice
+ read -e -p "タスク名を入力してください:" name
+ read -e -p "ローカル ディレクトリを入力してください:" local_path
+ read -e -p "リモート ディレクトリを入力してください:" remote_path
- case $auth_choice in
- 1)
- read -s -p "パスワードを入力してください:" password_or_key
- echo # 换行
- auth_method="password"
- ;;
- 2)
- echo "キーコンテンツを貼り付けてください(貼り付け後に2回Enterを押します)を押してください):"
- local password_or_key=""
- while IFS= read -r line; do
- # 入力が空で、キーコンテンツにすでに開始が含まれている場合、入力は終了します
- if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
- break
- fi
- # それが最初の行であるか、キーコンテンツが入力されている場合は、さらに追加し続けます
- if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- password_or_key+="${line}"$'\n'
- fi
- done
+ while true; do
+ read -e -p "リモート ユーザー@IP を入力してください:" remote
+ if kj_ssh_parse_remote "$remote" "root"; then
+ remote="$KJ_SSH_REMOTE"
+ break
+ fi
+ done
- # キーコンテンツのかどうかを確認してください
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/${name}_sync.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- password_or_key="$key_file"
- auth_method="key"
- else
- echo "無効なキーコンテンツ!"
- return
- fi
- ;;
- *)
- echo "無効な選択!"
- return
- ;;
- esac
+ kj_ssh_read_port "SSH ポートを入力してください (デフォルトは 22):" "22"
+ port="$KJ_SSH_PORT"
+
+ if ! kj_ssh_read_auth "$KEY_DIR/${name}_sync.key"; then
+ return
+ fi
+ auth_method="$KJ_SSH_AUTH_METHOD"
+ password_or_key="$KJ_SSH_AUTH_SECRET"
- echo "同期モードを選択してください:"
- echo "1。標準モード(-AVZ)"
- echo "2。ターゲットファイル(-avz - delete)を削除します"
- read -e -p "(1/2)を選択してください:" mode
+ echo "同期モードを選択してください:"
+ echo "1. 標準モード (-avz)"
+ echo "2. 対象ファイルを削除(-avz --delete)"
+ read -e -p "(1/2) を選択してください:" mode
case $mode in
1) options="-avz" ;;
2) options="-avz --delete" ;;
- *) echo "無効な選択、デフォルト-AVZを使用します"; options="-avz" ;;
+ *) echo "選択が無効です。デフォルトの -avz を使用してください"; options="-avz" ;;
esac
echo "$name|$local_path|$remote|$remote_path|$port|$options|$auth_method|$password_or_key" >> "$CONFIG_FILE"
install rsync rsync
- echo "タスクが節約されました!"
+ echo "ミッションが保存されました!"
}
-# タスクを削除します
+
+# タスクの削除
delete_task() {
- send_stats "同期タスクを削除します"
- read -e -p "削除するには、タスク番号を入力してください。" num
+ send_stats "同期タスクの削除"
+ read -e -p "削除するタスク番号を入力してください:" num
local task=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$task" ]]; then
- echo "エラー:対応するタスクは見つかりませんでした。"
+ echo "エラー: 対応するタスクが見つかりませんでした。"
return
fi
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # タスクがキーファイルを使用している場合、キーファイルを削除します
+ # タスクがキー ファイルを使用している場合は、キー ファイルを削除します
if [[ "$auth_method" == "key" && "$password_or_key" == "$KEY_DIR"* ]]; then
rm -f "$password_or_key"
fi
sed -i "${num}d" "$CONFIG_FILE"
- echo "削除されたタスク!"
+ echo "タスクが削除されました!"
}
run_task() {
- send_stats "同期タスクを実行します"
+ send_stats "同期タスクを実行する"
CONFIG_FILE="$HOME/.rsync_tasks"
CRON_FILE="$HOME/.rsync_cron"
- # パラメーターを分析します
+ # パラメータを解析する
local direction="push" # 默认是推送到远端
local num
@@ -6229,51 +7431,51 @@ run_task() {
num="$1"
fi
- # 着信タスク番号がない場合は、ユーザーに入力するように促します
+ # タスク番号が渡されない場合、ユーザーは入力を求められます。
if [[ -z "$num" ]]; then
- read -e -p "実行するタスク番号を入力してください:" num
+ read -e -p "実行するタスク番号を入力してください:" num
fi
local task=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$task" ]]; then
- echo "エラー:タスクは見つかりませんでした!"
+ echo "エラー: タスクが見つかりませんでした。"
return
fi
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # 同期の方向に従ってソースとターゲットのパスを調整します
+ # 同期方向に基づいてソースパスと宛先パスを調整する
if [[ "$direction" == "pull" ]]; then
- echo "同期をローカルに引く:$remote:$local_path -> $remote_path"
+ echo "ローカルへのプルと同期:$remote:$local_path -> $remote_path"
source="$remote:$local_path"
destination="$remote_path"
else
- echo "同期をリモートエンドに押します:$local_path -> $remote:$remote_path"
+ echo "リモートエンドへのプッシュと同期:$local_path -> $remote:$remote_path"
source="$local_path"
destination="$remote:$remote_path"
fi
- # SSH接続の共通パラメーターを追加します
+ # SSH接続の共通パラメータを追加する
local ssh_options="-p $port -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
if [[ "$auth_method" == "password" ]]; then
if ! command -v sshpass &> /dev/null; then
- echo "エラー:SSHPassはインストールされていません。最初にSSHPassをインストールしてください。"
- echo "インストール方法:"
+ echo "エラー: sshpass がインストールされていません。最初に sshpass をインストールしてください。"
+ echo "インストール方法:"
echo " - Ubuntu/Debian: apt install sshpass"
echo " - CentOS/RHEL: yum install sshpass"
return
fi
sshpass -p "$password_or_key" rsync $options -e "ssh $ssh_options" "$source" "$destination"
else
- # キーファイルが存在するかどうか、およびアクセス許可が正しいかどうかを確認します
+ # キーファイルが存在するかどうか、および権限が正しいかどうかを確認してください
if [[ ! -f "$password_or_key" ]]; then
- echo "エラー:キーファイルが存在しません:$password_or_key"
+ echo "エラー: キー ファイルが存在しません:$password_or_key"
return
fi
if [[ "$(stat -c %a "$password_or_key")" != "600" ]]; then
- echo "警告:キーファイルのアクセス許可が正しくなく、修理されています..."
+ echo "警告: キー ファイルのアクセス許可が正しくありません。修正中です..."
chmod 600 "$password_or_key"
fi
@@ -6281,32 +7483,32 @@ run_task() {
fi
if [[ $? -eq 0 ]]; then
- echo "同期は完了です!"
+ echo "同期が完了しました!"
else
- echo "同期は失敗しました!以下を確認してください。"
- echo "1。ネットワーク接続は正常ですか?"
- echo "2。リモートホストにアクセスできますか?"
- echo "3。認証情報は正しいですか?"
- echo "4.ローカルおよびリモートディレクトリには正しいアクセス許可がありますか"
+ echo "同期に失敗しました!以下の点をご確認ください。"
+ echo "1. ネットワーク接続は正常ですか?"
+ echo "2. リモートホストにアクセスできるかどうか"
+ echo "3. 認証情報は正しいですか?"
+ echo "4. ローカル ディレクトリとリモート ディレクトリには正しいアクセス許可がありますか?"
fi
}
-# 時限タスクを作成します
+# スケジュールされたタスクを作成する
schedule_task() {
- send_stats "同期タイミングタスクを追加します"
+ send_stats "同期スケジュールされたタスクを追加する"
- read -e -p "定期的に同期するには、タスク番号を入力してください。" num
+ read -e -p "定期的に同期するタスク番号を入力してください:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
- echo "エラー:有効なタスク番号を入力してください!"
+ echo "エラー: 有効なタスク番号を入力してください。"
return
fi
- echo "時限実行間隔を選択してください:"
- echo "1)1時間に1回実行します"
- echo "2)1日1回実行します"
- echo "3)週に1回実行します"
- read -e -p "オプションを入力してください(1/2/3):" interval
+ echo "スケジュールされた実行間隔を選択してください:"
+ echo "1) 1時間に1回実行"
+ echo "2) 1日1回実行"
+ echo "3) 週に1回実行"
+ read -e -p "オプションを入力してください (1/2/3):" interval
local random_minute=$(shuf -i 0-59 -n 1) # 生成 0-59 之间的随机分钟数
local cron_time=""
@@ -6314,42 +7516,42 @@ schedule_task() {
1) cron_time="$random_minute * * * *" ;; # 每小时,随机分钟执行
2) cron_time="$random_minute 0 * * *" ;; # 每天,随机分钟执行
3) cron_time="$random_minute 0 * * 1" ;; # 每周,随机分钟执行
- *) echo "エラー:有効なオプションを入力してください!" ; return ;;
+ *) echo "エラー: 有効なオプションを入力してください。" ; return ;;
esac
local cron_job="$cron_time k rsync_run $num"
local cron_job="$cron_time k rsync_run $num"
- # 同じタスクが既に存在するかどうかを確認してください
+ # 同じタスクがすでに存在するかどうかを確認する
if crontab -l | grep -q "k rsync_run $num"; then
- echo "エラー:このタスクのタイミング同期はすでに存在しています!"
+ echo "エラー: このタスクのスケジュールされた同期はすでに存在します。"
return
fi
- # ユーザーにクロンタブを作成します
+ # ユーザーのcrontabに作成
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "タイミングタスクが作成されました。$cron_job"
+ echo "スケジュールされたタスクが作成されました:$cron_job"
}
-# スケジュールされたタスクを表示します
+# スケジュールされたタスクを表示する
view_tasks() {
- echo "現在のタイミングタスク:"
+ echo "現在スケジュールされているタスク:"
echo "---------------------------------"
crontab -l | grep "k rsync_run"
echo "---------------------------------"
}
-# タイミングタスクを削除します
+# スケジュールされたタスクを削除する
delete_task_schedule() {
- send_stats "同期タイミングタスクを削除します"
- read -e -p "削除するには、タスク番号を入力してください。" num
+ send_stats "同期のスケジュールされたタスクを削除する"
+ read -e -p "削除するタスク番号を入力してください:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
- echo "エラー:有効なタスク番号を入力してください!"
+ echo "エラー: 有効なタスク番号を入力してください。"
return
fi
crontab -l | grep -v "k rsync_run $num" | crontab -
- echo "削除されたタスク番号$numタイミングタスク"
+ echo "タスク番号が削除されました$numスケジュールされたタスク"
}
@@ -6360,20 +7562,20 @@ rsync_manager() {
while true; do
clear
- echo "RSYNCリモート同期ツール"
- echo "リモートディレクトリ間の同期は、増分同期、効率的、安定性をサポートします。"
+ echo "Rsync リモート同期ツール"
+ echo "リモート ディレクトリ間の同期は、効率的で安定した増分同期をサポートしています。"
echo "---------------------------------"
list_tasks
echo
view_tasks
echo
- echo "1.新しいタスクを作成します2。タスクを削除します"
- echo "3.リモートエンドにローカル同期を実行する4。ローカルエンドにリモート同期を実行する"
- echo "5.タイミングタスクを作成6。タイミングタスクを削除します"
+ echo "1. 新しいタスクを作成します。 2. タスクを削除します。"
+ echo "3. リモート サイトへのローカル同期を実行します。 4. ローカル サイトへのリモート同期を実行します。"
+ echo "5. スケジュールされたタスクを作成します。 6. スケジュールされたタスクを削除します。"
echo "---------------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "---------------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case $choice in
1) add_task ;;
2) delete_task ;;
@@ -6382,9 +7584,9 @@ rsync_manager() {
5) schedule_task ;;
6) delete_task_schedule ;;
0) break ;;
- *) echo "無効な選択、もう一度やり直してください。" ;;
+ *) echo "選択が無効です。もう一度お試しください。" ;;
esac
- read -e -p "Enterを押して続行します..."
+ read -e -p "Enter を押して続行します..."
done
}
@@ -6396,10 +7598,13 @@ rsync_manager() {
-linux_ps() {
+linux_info() {
+
+
clear
- send_stats "システム情報クエリ"
+ echo -e "${gl_kjlan}システム情報を問い合わせています...${gl_bai}"
+ send_stats "システム情報の問い合わせ"
ip_address
@@ -6443,47 +7648,50 @@ linux_ps() {
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1 % 3600) / 60); if (run_days > 0) printf("%d天 ", run_days); if (run_hours > 0) printf("%d时 ", run_hours); printf("%d分\n", run_minutes)}')
+ local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1% 3600) / 60); if (run_days > 0) printf("%d day ", run_days); if (実行時間 > 0) printf("%d 時間 ", 実行時間); printf("%d 分\n", run_ minutes)}')
local timezone=$(current_timezone)
+ local tcp_count=$(ss -t | wc -l)
+ local udp_count=$(ss -u | wc -l)
- echo ""
- echo -e "システム情報クエリ"
+ clear
+ echo -e "システム情報の問い合わせ"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}ホスト名:${gl_bai}$hostname"
- echo -e "${gl_kjlan}システムバージョン:${gl_bai}$os_info"
- echo -e "${gl_kjlan}Linuxバージョン:${gl_bai}$kernel_version"
+ echo -e "${gl_kjlan}ホスト名:${gl_bai}$hostname"
+ echo -e "${gl_kjlan}システムバージョン:${gl_bai}$os_info"
+ echo -e "${gl_kjlan}Linux バージョン:${gl_bai}$kernel_version"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPUアーキテクチャ:${gl_bai}$cpu_arch"
- echo -e "${gl_kjlan}CPUモデル:${gl_bai}$cpu_info"
- echo -e "${gl_kjlan}CPUコアの数:${gl_bai}$cpu_cores"
- echo -e "${gl_kjlan}CPU頻度:${gl_bai}$cpu_freq"
+ echo -e "${gl_kjlan}CPU アーキテクチャ:${gl_bai}$cpu_arch"
+ echo -e "${gl_kjlan}CPUモデル:${gl_bai}$cpu_info"
+ echo -e "${gl_kjlan}CPUコアの数:${gl_bai}$cpu_cores"
+ echo -e "${gl_kjlan}CPU周波数:${gl_bai}$cpu_freq"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU占有:${gl_bai}$cpu_usage_percent%"
- echo -e "${gl_kjlan}システムの負荷:${gl_bai}$load"
- echo -e "${gl_kjlan}物理的記憶:${gl_bai}$mem_info"
- echo -e "${gl_kjlan}仮想メモリ:${gl_bai}$swap_info"
- echo -e "${gl_kjlan}ハードディスクの職業:${gl_bai}$disk_info"
+ echo -e "${gl_kjlan}CPU使用率:${gl_bai}$cpu_usage_percent%"
+ echo -e "${gl_kjlan}システム負荷:${gl_bai}$load"
+ echo -e "${gl_kjlan}TCP|UDP 接続の数:${gl_bai}$tcp_count|$udp_count"
+ echo -e "${gl_kjlan}物理メモリ:${gl_bai}$mem_info"
+ echo -e "${gl_kjlan}仮想メモリ:${gl_bai}$swap_info"
+ echo -e "${gl_kjlan}ハードドライブの使用状況:${gl_bai}$disk_info"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}合計受信:${gl_bai}$rx"
- echo -e "${gl_kjlan}合計送信:${gl_bai}$tx"
+ echo -e "${gl_kjlan}受け取った合計:${gl_bai}$rx"
+ echo -e "${gl_kjlan}送信合計:${gl_bai}$tx"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}ネットワークアルゴリズム:${gl_bai}$congestion_algorithm $queue_algorithm"
+ echo -e "${gl_kjlan}ネットワークアルゴリズム:${gl_bai}$congestion_algorithm $queue_algorithm"
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}オペレーター:${gl_bai}$isp_info"
if [ -n "$ipv4_address" ]; then
- echo -e "${gl_kjlan}IPv4アドレス:${gl_bai}$ipv4_address"
+ echo -e "${gl_kjlan}IPv4アドレス:${gl_bai}$ipv4_address"
fi
if [ -n "$ipv6_address" ]; then
- echo -e "${gl_kjlan}IPv6アドレス:${gl_bai}$ipv6_address"
+ echo -e "${gl_kjlan}IPv6アドレス:${gl_bai}$ipv6_address"
fi
- echo -e "${gl_kjlan}DNSアドレス:${gl_bai}$dns_addresses"
- echo -e "${gl_kjlan}地理的場所:${gl_bai}$country $city"
- echo -e "${gl_kjlan}システム時間:${gl_bai}$timezone $current_time"
+ echo -e "${gl_kjlan}DNS アドレス:${gl_bai}$dns_addresses"
+ echo -e "${gl_kjlan}位置:${gl_bai}$country $city"
+ echo -e "${gl_kjlan}システム時間:${gl_bai}$timezone $current_time"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}ランタイム:${gl_bai}$runtime"
+ echo -e "${gl_kjlan}実行時間:${gl_bai}$runtime"
echo
@@ -6496,111 +7704,163 @@ linux_tools() {
while true; do
clear
- # send_stats「基本ツール」
+ # send_stats "基本ツール"
echo -e "基本的なツール"
+
+ tools=(
+ curl wget sudo socat htop iftop unzip tar tmux ffmpeg
+ btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders
+ vim nano git
+ )
+
+ if command -v apt >/dev/null 2>&1; then
+ PM="apt"
+ elif command -v dnf >/dev/null 2>&1; then
+ PM="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PM="yum"
+ elif command -v pacman >/dev/null 2>&1; then
+ PM="pacman"
+ elif command -v apk >/dev/null 2>&1; then
+ PM="apk"
+ elif command -v zypper >/dev/null 2>&1; then
+ PM="zypper"
+ elif command -v opkg >/dev/null 2>&1; then
+ PM="opkg"
+ elif command -v pkg >/dev/null 2>&1; then
+ PM="pkg"
+ else
+ echo "❌ 認識されないパッケージマネージャー"
+ exit 1
+ fi
+
+ echo "📦 パッケージマネージャーを使用します:$PM"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+
+ for ((i=0; i<${#tools[@]}; i+=2)); do
+ # 左の列
+ if command -v "${tools[i]}" >/dev/null 2>&1; then
+ left=$(printf "✅ %-12s がインストールされました" "${tools[i]}")
+ else
+ left=$(printf "❌ %-12s がインストールされていません" "${tools[i]}")
+ fi
+
+ # 右列 (配列が範囲外になるのを防ぐため)
+ if [[ -n "${tools[i+1]}" ]]; then
+ if command -v "${tools[i+1]}" >/dev/null 2>&1; then
+ right=$(printf "✅ %-12s がインストールされました" "${tools[i+1]}")
+ else
+ right=$(printf "❌ %-12s がインストールされていません" "${tools[i+1]}")
+ fi
+ printf "%-42s %s\n" "$left" "$right"
+ else
+ printf "%s\n" "$left"
+ fi
+ done
+
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}カールダウンロードツール${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}WGETダウンロードツール${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}3. ${gl_bai}SUDOスーパー管理許可ツール${gl_kjlan}4. ${gl_bai}Socat Communication Connection Tool"
- echo -e "${gl_kjlan}5. ${gl_bai}HTOPシステム監視ツール${gl_kjlan}6. ${gl_bai}IFTOPネットワークトラフィック監視ツール"
- echo -e "${gl_kjlan}7. ${gl_bai}ジップzip圧縮減圧ツールを解凍します${gl_kjlan}8. ${gl_bai}TAR GZ圧縮減圧ツール"
- echo -e "${gl_kjlan}9. ${gl_bai}TMUXマルチチャネルバックグラウンドランニングツール${gl_kjlan}10. ${gl_bai}Live StreamingツールをエンコードするFFMPEGビデオ"
+ echo -e "${gl_kjlan}1. ${gl_bai}カールダウンロードツール${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}wgetダウンロードツール${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}3. ${gl_bai}sudo スーパー管理者特権ツール${gl_kjlan}4. ${gl_bai}socat通信接続ツール"
+ echo -e "${gl_kjlan}5. ${gl_bai}htop システム監視ツール${gl_kjlan}6. ${gl_bai}iftop ネットワークトラフィック監視ツール"
+ echo -e "${gl_kjlan}7. ${gl_bai}unzip ZIP圧縮・解凍ツール${gl_kjlan}8. ${gl_bai}tar GZ 圧縮および解凍ツール"
+ echo -e "${gl_kjlan}9. ${gl_bai}tmux マルチチャネル バックグラウンド実行ツール${gl_kjlan}10. ${gl_bai}ffmpeg ビデオエンコードライブストリーミングツール"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}BTOPモダン監視ツール${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}範囲ファイル管理ツール"
- echo -e "${gl_kjlan}13. ${gl_bai}NCDUディスク職業視聴ツール${gl_kjlan}14. ${gl_bai}FZFグローバル検索ツール"
- echo -e "${gl_kjlan}15. ${gl_bai}VIMテキストエディター${gl_kjlan}16. ${gl_bai}ナノテキストエディター${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}17. ${gl_bai}gitバージョン制御システム"
+ echo -e "${gl_kjlan}11. ${gl_bai}btop 最新の監視ツール${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}レンジャーファイル管理ツール"
+ echo -e "${gl_kjlan}13. ${gl_bai}ncdu ディスク使用量表示ツール${gl_kjlan}14. ${gl_bai}fzf グローバル検索ツール"
+ echo -e "${gl_kjlan}15. ${gl_bai}vim テキストエディタ${gl_kjlan}16. ${gl_bai}ナノテキストエディタ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}17. ${gl_bai}git バージョン管理システム${gl_kjlan}18. ${gl_bai}opencode AIプログラミングアシスタント${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}マトリックス画面保証${gl_kjlan}22. ${gl_bai}列車のスクリーンのセキュリティ"
- echo -e "${gl_kjlan}26. ${gl_bai}テトリスゲーム${gl_kjlan}27. ${gl_bai}ヘビを食べるゲーム"
- echo -e "${gl_kjlan}28. ${gl_bai}スペースインベーダーゲーム"
+ echo -e "${gl_kjlan}21. ${gl_bai}マトリックス スクリーンセーバー${gl_kjlan}22. ${gl_bai}走る電車のスクリーンセーバー"
+ echo -e "${gl_kjlan}26. ${gl_bai}テトリスのミニゲーム${gl_kjlan}27. ${gl_bai}ヘビのミニゲーム"
+ echo -e "${gl_kjlan}28. ${gl_bai}スペースインベーダーのミニゲーム"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}すべてをインストールします${gl_kjlan}32. ${gl_bai}すべてのインストール(スクリーンセーバーとゲームを除く)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}すべてをアンインストールします"
+ echo -e "${gl_kjlan}31. ${gl_bai}すべてインストールする${gl_kjlan}32. ${gl_bai}すべてインストール (スクリーンセーバーとゲームを除く)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}すべてアンインストールする"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}指定されたツールをインストールします${gl_kjlan}42. ${gl_bai}指定されたツールをアンインストールします"
+ echo -e "${gl_kjlan}41. ${gl_bai}指定されたツールをインストールする${gl_kjlan}42. ${gl_bai}指定されたツールをアンインストールします"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
clear
install curl
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
curl --help
- send_stats "カールをインストールします"
+ send_stats "カールをインストールする"
;;
2)
clear
install wget
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
wget --help
- send_stats "WGETをインストールします"
+ send_stats "wgetをインストールする"
;;
3)
clear
install sudo
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
sudo --help
- send_stats "sudoをインストールします"
+ send_stats "sudoをインストールする"
;;
4)
clear
install socat
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
socat -h
- send_stats "SOCATをインストールします"
+ send_stats "socatをインストールする"
;;
5)
clear
install htop
clear
htop
- send_stats "HTOPをインストールします"
+ send_stats "htopをインストールする"
;;
6)
clear
install iftop
clear
iftop
- send_stats "IFTOPをインストールします"
+ send_stats "iftop をインストールする"
;;
7)
clear
install unzip
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
unzip
- send_stats "unzipをインストールします"
+ send_stats "インストール解凍"
;;
8)
clear
install tar
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
tar --help
- send_stats "タールをインストールします"
+ send_stats "tarをインストールする"
;;
9)
clear
install tmux
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
tmux --help
- send_stats "tmuxをインストールします"
+ send_stats "tmuxをインストールする"
;;
10)
clear
install ffmpeg
clear
- echo "ツールがインストールされており、使用方法は次のとおりです。"
+ echo "ツールはインストールされており、次のように使用されます。"
ffmpeg --help
- send_stats "ffmpegをインストールします"
+ send_stats "ffmpegをインストールする"
;;
11)
@@ -6608,7 +7868,7 @@ linux_tools() {
install btop
clear
btop
- send_stats "BTOPをインストールします"
+ send_stats "btopをインストールする"
;;
12)
clear
@@ -6617,7 +7877,7 @@ linux_tools() {
clear
ranger
cd ~
- send_stats "レンジャーをインストールします"
+ send_stats "レンジャーをインストールする"
;;
13)
clear
@@ -6626,7 +7886,7 @@ linux_tools() {
clear
ncdu
cd ~
- send_stats "NCDUをインストールします"
+ send_stats "ncdu をインストールする"
;;
14)
clear
@@ -6635,7 +7895,7 @@ linux_tools() {
clear
fzf
cd ~
- send_stats "FZFをインストールします"
+ send_stats "fzfをインストールする"
;;
15)
clear
@@ -6644,7 +7904,7 @@ linux_tools() {
clear
vim -h
cd ~
- send_stats "VIMをインストールします"
+ send_stats "vimをインストールする"
;;
16)
clear
@@ -6653,7 +7913,7 @@ linux_tools() {
clear
nano -h
cd ~
- send_stats "ナノをインストールします"
+ send_stats "ナノをインストールする"
;;
@@ -6664,75 +7924,89 @@ linux_tools() {
clear
git --help
cd ~
- send_stats "gitをインストールします"
+ send_stats "gitをインストールする"
;;
+ 18)
+ clear
+ cd ~
+ curl -fsSL https://opencode.ai/install | bash
+ source ~/.bashrc
+ source ~/.profile
+ opencode
+ send_stats "オープンコードをインストールする"
+ ;;
+
+
21)
clear
install cmatrix
clear
cmatrix
- send_stats "cmatrixをインストールします"
+ send_stats "cmatrix をインストールする"
;;
22)
clear
install sl
clear
sl
- send_stats "SLをインストールします"
+ send_stats "SLをインストールする"
;;
26)
clear
install bastet
clear
bastet
- send_stats "バステットをインストールします"
+ send_stats "バステトをインストールする"
;;
27)
clear
install nsnake
clear
nsnake
- send_stats "nsnakeをインストールします"
+ send_stats "nsnakeをインストールする"
;;
+
28)
clear
install ninvaders
clear
ninvaders
- send_stats "Ninvadersをインストールします"
+ send_stats "ニンベーダーをインストールする"
;;
31)
clear
- send_stats "すべてをインストールします"
+ send_stats "すべてインストールする"
install curl wget sudo socat htop iftop unzip tar tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
;;
32)
clear
- send_stats "すべてをインストールします(ゲームやスクリーンセーバーを除く)"
+ send_stats "すべてインストール (ゲームとスクリーンセーバーを除く)"
install curl wget sudo socat htop iftop unzip tar tmux ffmpeg btop ranger ncdu fzf vim nano git
;;
33)
clear
- send_stats "すべてをアンインストールします"
+ send_stats "すべてアンインストールする"
remove htop iftop tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
+ opencode uninstall
+ rm -rf ~/.opencode
;;
41)
clear
- read -e -p "インストールされているツール名(Wget Curl Sudo htop)を入力してください。" installname
+ read -e -p "インストールされているツール名 (wgetcurlsudohtop) を入力してください:" installname
install $installname
- send_stats "指定されたソフトウェアをインストールします"
+ send_stats "指定されたソフトウェアをインストールする"
;;
42)
clear
- read -e -p "アンインストールされていないツール名(HTOP UFW TMUX CMATRIX)を入力してください。" removename
+ read -e -p "アンインストールされたツール名 (htop ufw tmux cmatrix) を入力してください:" removename
remove $removename
- send_stats "指定されたソフトウェアをアンインストールします"
+ send_stats "指定したソフトウェアをアンインストールする"
;;
0)
@@ -6740,7 +8014,7 @@ linux_tools() {
;;
*)
- echo "無効な入力!"
+ echo "無効な入力です!"
;;
esac
break_end
@@ -6760,24 +8034,24 @@ linux_bbr() {
clear
local congestion_algorithm=$(sysctl -n net.ipv4.tcp_congestion_control)
local queue_algorithm=$(sysctl -n net.core.default_qdisc)
- echo "現在のTCPブロッキングアルゴリズム:$congestion_algorithm $queue_algorithm"
+ echo "現在の TCP ブロック アルゴリズム:$congestion_algorithm $queue_algorithm"
echo ""
echo "BBR管理"
echo "------------------------"
- echo "1。BBRV3 2をオンにします。BBRV3(再起動)をオフにします"
+ echo "1. BBRv3 をオンにする 2. BBRv3 をオフにする (再起動します)"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
bbr_on
- send_stats "AlpineはBBR3を有効にします"
+ send_stats "アルパインがBBR3をオープン"
;;
2)
- sed -i '/net.ipv4.tcp_congestion_control=bbr/d' /etc/sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control=/d' /etc/sysctl.conf
sysctl -p
server_reboot
;;
@@ -6801,81 +8075,403 @@ linux_bbr() {
-linux_docker() {
+docker_ssh_migration() {
- while true; do
- clear
- # send_stats「Docker Management」
- echo -e "Docker管理"
- docker_tato
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Docker環境をインストールして更新します${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}2. ${gl_bai}Dockerグローバルステータスを表示します${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}3. ${gl_bai}Dockerコンテナ管理${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}4. ${gl_bai}Docker画像管理"
- echo -e "${gl_kjlan}5. ${gl_bai}Dockerネットワーク管理"
- echo -e "${gl_kjlan}6. ${gl_bai}Dockerボリューム管理"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}7. ${gl_bai}清潔な役に立たないドッカーコンテナとミラーネットワークデータボリューム"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}8. ${gl_bai}Dockerソースを交換します"
- echo -e "${gl_kjlan}9. ${gl_bai}daemon.jsonファイルを編集します"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}docker-ipv6アクセスを有効にします"
- echo -e "${gl_kjlan}12. ${gl_bai}docker-ipv6アクセスを閉じます"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}20. ${gl_bai}Docker環境をアンインストールします"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ is_compose_container() {
+ local container=$1
+ docker inspect "$container" | jq -e '.[0].Config.Labels["com.docker.compose.project"]' >/dev/null 2>&1
+ }
- case $sub_choice in
- 1)
- clear
- send_stats "Docker環境をインストールします"
- install_add_docker
+ list_backups() {
+ local BACKUP_ROOT="/tmp"
+ echo -e "${gl_kjlan}現在のバックアップ リスト:${gl_bai}"
+ ls -1dt ${BACKUP_ROOT}/docker_backup_* 2>/dev/null || echo "バックアップなし"
+ }
- ;;
- 2)
- clear
- local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
- local image_count=$(docker images -q 2>/dev/null | wc -l)
- local network_count=$(docker network ls -q 2>/dev/null | wc -l)
- local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
- send_stats "Dockerグローバルステータス"
- echo "Dockerバージョン"
- docker -v
- docker compose version
- echo ""
- echo -e "Docker画像:${gl_lv}$image_count${gl_bai} "
- docker image ls
- echo ""
- echo -e "Dockerコンテナ:${gl_lv}$container_count${gl_bai}"
- docker ps -a
- echo ""
- echo -e "Dockerボリューム:${gl_lv}$volume_count${gl_bai}"
- docker volume ls
- echo ""
- echo -e "Dockerネットワーク:${gl_lv}$network_count${gl_bai}"
- docker network ls
- echo ""
+ # ----------------------------
+ # バックアップ
+ # ----------------------------
+ backup_docker() {
+ send_stats "Dockerバックアップ"
- ;;
- 3)
- docker_ps
- ;;
- 4)
- docker_image
- ;;
+ echo -e "${gl_kjlan}Docker コンテナをバックアップしています...${gl_bai}"
+ docker ps --format '{{.Names}}'
+ read -e -p "バックアップするコンテナの名前を入力してください (実行中のすべてのコンテナをバックアップするには、複数のスペースを区切って Enter キーを押します)。" containers
- 5)
- while true; do
- clear
+ install tar jq gzip
+ install_docker
+
+ local BACKUP_ROOT="/tmp"
+ local DATE_STR=$(date +%Y%m%d_%H%M%S)
+ local TARGET_CONTAINERS=()
+ if [ -z "$containers" ]; then
+ mapfile -t TARGET_CONTAINERS < <(docker ps --format '{{.Names}}')
+ else
+ read -ra TARGET_CONTAINERS <<< "$containers"
+ fi
+ [[ ${#TARGET_CONTAINERS[@]} -eq 0 ]] && { echo -e "${gl_hong}コンテナが見つかりません${gl_bai}"; return; }
+
+ local BACKUP_DIR="${BACKUP_ROOT}/docker_backup_${DATE_STR}"
+ mkdir -p "$BACKUP_DIR"
+
+ local RESTORE_SCRIPT="${BACKUP_DIR}/docker_restore.sh"
+ echo "#!/bin/bash" > "$RESTORE_SCRIPT"
+ echo "set -e" >> "$RESTORE_SCRIPT"
+ echo "# 自动生成的还原脚本" >> "$RESTORE_SCRIPT"
+
+ # パッケージ化の繰り返しを避けるために、パッケージ化された Compose プロジェクトのパスを記録します。
+ declare -A PACKED_COMPOSE_PATHS=()
+
+ for c in "${TARGET_CONTAINERS[@]}"; do
+ echo -e "${gl_lv}バックアップコンテナ:$c${gl_bai}"
+ local inspect_file="${BACKUP_DIR}/${c}_inspect.json"
+ docker inspect "$c" > "$inspect_file"
+
+ if is_compose_container "$c"; then
+ echo -e "${gl_kjlan}検出されました$cdocker-composeコンテナです${gl_bai}"
+ local project_dir=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty')
+ local project_name=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty')
+
+ if [ -z "$project_dir" ]; then
+ read -e -p "作成ディレクトリが検出されません。パスを手動で入力してください。" project_dir
+ fi
+
+ # Compose プロジェクトがすでにパッケージ化されている場合は、スキップしてください
+ if [[ -n "${PACKED_COMPOSE_PATHS[$project_dir]}" ]]; then
+ echo -e "${gl_huang}プロジェクトの作成 [$project_name] すでにバックアップされているので、繰り返しのパッケージ化をスキップします...${gl_bai}"
+ continue
+ fi
+
+ if [ -f "$project_dir/docker-compose.yml" ]; then
+ echo "compose" > "${BACKUP_DIR}/backup_type_${project_name}"
+ echo "$project_dir" > "${BACKUP_DIR}/compose_path_${project_name}.txt"
+ tar -czf "${BACKUP_DIR}/compose_project_${project_name}.tar.gz" -C "$project_dir" .
+ echo "# docker-compose 復元:$project_name" >> "$RESTORE_SCRIPT"
+ echo "cd \"$project_dir\" && docker compose up -d" >> "$RESTORE_SCRIPT"
+ PACKED_COMPOSE_PATHS["$project_dir"]=1
+ echo -e "${gl_lv}プロジェクトの作成 [$project_name] パッケージ内容:${project_dir}${gl_bai}"
+ else
+ echo -e "${gl_hong}docker-compose.yml が見つからないため、このコンテナをスキップします...${gl_bai}"
+ fi
+ else
+ # 通常のコンテナバックアップボリューム
+ local VOL_PATHS
+ VOL_PATHS=$(docker inspect "$c" --format '{{range .Mounts}}{{.Source}} {{end}}')
+ for path in $VOL_PATHS; do
+ echo "梱包体積:$path"
+ tar -czpf "${BACKUP_DIR}/${c}_$(basename $path).tar.gz" -C / "$(echo $path | sed 's/^\///')"
+ done
+
+ # ポート
+ local PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[] | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$inspect_file" 2>/dev/null)
+ for p in "${PORTS[@]}"; do PORT_ARGS+="-p $p "; done
+
+ # 環境変数
+ local ENV_VARS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[] | @sh' "$inspect_file")
+ for e in "${ENVS[@]}"; do ENV_VARS+="-e $e "; done
+
+ # ボリュームマッピング
+ local VOL_ARGS=""
+ for path in $VOL_PATHS; do VOL_ARGS+="-v $path:$path "; done
+
+ # 鏡
+ local IMAGE
+ IMAGE=$(jq -r '.[0].Config.Image' "$inspect_file")
+
+ echo -e "\n# コンテナを復元:$c" >> "$RESTORE_SCRIPT"
+ echo "docker run -d --name $c $PORT_ARGS $VOL_ARGS $ENV_VARS $IMAGE" >> "$RESTORE_SCRIPT"
+ fi
+ done
+
+
+ # /home/docker 下のすべてのファイルをバックアップします (サブディレクトリを除く)。
+ if [ -d "/home/docker" ]; then
+ echo -e "${gl_kjlan}/home/docker 下のファイルをバックアップします...${gl_bai}"
+ find /home/docker -maxdepth 1 -type f | tar -czf "${BACKUP_DIR}/home_docker_files.tar.gz" -T -
+ echo -e "${gl_lv}/home/docker 下のファイルは次のようにパッケージ化されています。${BACKUP_DIR}/home_docker_files.tar.gz${gl_bai}"
+ fi
+
+ chmod +x "$RESTORE_SCRIPT"
+ echo -e "${gl_lv}バックアップが完了しました:${BACKUP_DIR}${gl_bai}"
+ echo -e "${gl_lv}利用可能な復元スクリプト:${RESTORE_SCRIPT}${gl_bai}"
+
+
+ }
+
+ # ----------------------------
+ # 削減
+ # ----------------------------
+ restore_docker() {
+
+ send_stats "Docker の復元"
+ read -e -p "復元するバックアップ ディレクトリを入力してください:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}バックアップディレクトリが存在しません${gl_bai}"; return; }
+
+ echo -e "${gl_kjlan}復元操作を開始しています...${gl_bai}"
+
+ install tar jq gzip
+ install_docker
+
+ # --------- Compose プロジェクトの復元を優先します ---------
+ for f in "$BACKUP_DIR"/backup_type_*; do
+ [[ ! -f "$f" ]] && continue
+ if grep -q "compose" "$f"; then
+ project_name=$(basename "$f" | sed 's/backup_type_//')
+ path_file="$BACKUP_DIR/compose_path_${project_name}.txt"
+ [[ -f "$path_file" ]] && original_path=$(cat "$path_file") || original_path=""
+ [[ -z "$original_path" ]] && read -e -p "元のパスが見つかりません。復元ディレクトリのパスを入力してください:" original_path
+
+ # 構成プロジェクトのコンテナがすでに実行されているかどうかを確認します
+ running_count=$(docker ps --filter "label=com.docker.compose.project=$project_name" --format '{{.Names}}' | wc -l)
+ if [[ "$running_count" -gt 0 ]]; then
+ echo -e "${gl_huang}プロジェクトの作成 [$project_name] コンテナはすでに実行されているため、復元をスキップします...${gl_bai}"
+ continue
+ fi
+
+ read -e -p "Compose プロジェクトの復元を確認します [$project_name] からパス [$original_path] ? (y/n): " confirm
+ [[ "$confirm" != "y" ]] && read -e -p "新しい復元パスを入力してください:" original_path
+
+ mkdir -p "$original_path"
+ tar -xzf "$BACKUP_DIR/compose_project_${project_name}.tar.gz" -C "$original_path"
+ echo -e "${gl_lv}プロジェクトの作成 [$project_name] は次のように抽出されました。$original_path${gl_bai}"
+
+ cd "$original_path" || return
+ docker compose down || true
+ docker compose up -d
+ echo -e "${gl_lv}プロジェクトの作成 [$project_name】レストア完了!${gl_bai}"
+ fi
+ done
+
+ # --------- 通常のコンテナの復元を続行 ---------
+ echo -e "${gl_kjlan}通常の Docker コンテナを確認して復元します...${gl_bai}"
+ local has_container=false
+ for json in "$BACKUP_DIR"/*_inspect.json; do
+ [[ ! -f "$json" ]] && continue
+ has_container=true
+ container=$(basename "$json" | sed 's/_inspect.json//')
+ echo -e "${gl_lv}処理容器:$container${gl_bai}"
+
+ # コンテナがすでに存在し、実行されているかどうかを確認します
+ if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] すでに実行中のため、復元をスキップしています...${gl_bai}"
+ continue
+ fi
+
+ IMAGE=$(jq -r '.[0].Config.Image' "$json")
+ [[ -z "$IMAGE" || "$IMAGE" == "null" ]] && { echo -e "${gl_hong}ミラー情報が見つかりません。スキップしてください:$container${gl_bai}"; continue; }
+
+ # ポートマッピング
+ PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$json")
+ for p in "${PORTS[@]}"; do
+ [[ -n "$p" ]] && PORT_ARGS="$PORT_ARGS -p $p"
+ done
+
+ # 環境変数
+ ENV_ARGS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[]' "$json")
+ for e in "${ENVS[@]}"; do
+ ENV_ARGS="$ENV_ARGS -e \"$e\""
+ done
+
+ # ボリュームマッピング + ボリュームデータリカバリ
+ VOL_ARGS=""
+ mapfile -t VOLS < <(jq -r '.[0].Mounts[] | "\(.Source):\(.Destination)"' "$json")
+ for v in "${VOLS[@]}"; do
+ VOL_SRC=$(echo "$v" | cut -d':' -f1)
+ VOL_DST=$(echo "$v" | cut -d':' -f2)
+ mkdir -p "$VOL_SRC"
+ VOL_ARGS="$VOL_ARGS -v $VOL_SRC:$VOL_DST"
+
+ VOL_FILE="$BACKUP_DIR/${container}_$(basename $VOL_SRC).tar.gz"
+ if [[ -f "$VOL_FILE" ]]; then
+ echo "ボリュームデータを復元します。$VOL_SRC"
+ tar -xzf "$VOL_FILE" -C /
+ fi
+ done
+
+ # 既存だが実行されていないコンテナを削除する
+ if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] は存在しますが実行されていない場合は、古いコンテナを削除してください...${gl_bai}"
+ docker rm -f "$container"
+ fi
+
+ # コンテナの起動
+ echo "復元コマンドを実行します: docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ eval "docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ done
+
+ [[ "$has_container" == false ]] && echo -e "${gl_huang}共通コンテナのバックアップ情報が見つかりません${gl_bai}"
+
+ # /home/docker 下のファイルを復元する
+ if [ -f "$BACKUP_DIR/home_docker_files.tar.gz" ]; then
+ echo -e "${gl_kjlan}/home/docker の下にファイルを復元しています...${gl_bai}"
+ mkdir -p /home/docker
+ tar -xzf "$BACKUP_DIR/home_docker_files.tar.gz" -C /
+ echo -e "${gl_lv}/home/docker 下のファイルが復元されました${gl_bai}"
+ else
+ echo -e "${gl_huang}/home/docker の下にあるファイルのバックアップが見つかりませんでした。スキップしています...${gl_bai}"
+ fi
+
+
+ }
+
+
+ # ----------------------------
+ # 移行する
+ # ----------------------------
+ migrate_docker() {
+ send_stats "Docker の移行"
+ install jq
+ read -e -p "移行するバックアップ ディレクトリを入力してください:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}バックアップディレクトリが存在しません${gl_bai}"; return; }
+
+ kj_ssh_read_host_user_port "ターゲットサーバーIP:" "ターゲットサーバーの SSH ユーザー名 [デフォルトのルート]:" "ターゲット サーバーの SSH ポート [デフォルト 22]:" "root" "22"
+ local TARGET_IP="$KJ_SSH_HOST"
+ local TARGET_USER="$KJ_SSH_USER"
+ local TARGET_PORT="$KJ_SSH_PORT"
+
+ local LATEST_TAR="$BACKUP_DIR"
+
+ echo -e "${gl_huang}バックアップを転送中...${gl_bai}"
+ if [[ -z "$TARGET_PASS" ]]; then
+ # キーを使用してログインする
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no -r "$LATEST_TAR" "$TARGET_USER@$TARGET_IP:/tmp/"
+ fi
+
+ }
+
+ # ----------------------------
+ # バックアップの削除
+ # ----------------------------
+ delete_backup() {
+ send_stats "Dockerバックアップファイルの削除"
+ read -e -p "削除するバックアップ ディレクトリを入力してください:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}バックアップディレクトリが存在しません${gl_bai}"; return; }
+ rm -rf "$BACKUP_DIR"
+ echo -e "${gl_lv}削除されたバックアップ:${BACKUP_DIR}${gl_bai}"
+ }
+
+ # ----------------------------
+ # メインメニュー
+ # ----------------------------
+ main_menu() {
+ send_stats "Docker バックアップ 移行 復元"
+ while true; do
+ clear
+ echo "------------------------"
+ echo -e "Docker バックアップ/移行/復元ツール"
+ echo "------------------------"
+ list_backups
+ echo -e ""
+ echo "------------------------"
+ echo -e "1. Docker プロジェクトをバックアップする"
+ echo -e "2. Docker プロジェクトを移行する"
+ echo -e "3. Docker プロジェクトを復元する"
+ echo -e "4. Dockerプロジェクトのバックアップファイルを削除する"
+ echo "------------------------"
+ echo -e "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択してください:" choice
+ case $choice in
+ 1) backup_docker ;;
+ 2) migrate_docker ;;
+ 3) restore_docker ;;
+ 4) delete_backup ;;
+ 0) return ;;
+ *) echo -e "${gl_hong}無効なオプション${gl_bai}" ;;
+ esac
+ break_end
+ done
+ }
+
+ main_menu
+}
+
+
+
+
+
+linux_docker() {
+
+ while true; do
+ clear
+ # send_stats "ドッカー管理"
+ echo -e "Docker管理"
+ docker_tato
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}Docker環境のインストールと更新${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}2. ${gl_bai}Docker のグローバル ステータスを表示する${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}3. ${gl_bai}Dockerコンテナ管理${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}4. ${gl_bai}Dockerイメージ管理"
+ echo -e "${gl_kjlan}5. ${gl_bai}Dockerネットワーク管理"
+ echo -e "${gl_kjlan}6. ${gl_bai}Docker ボリューム管理"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}7. ${gl_bai}不要な Docker コンテナをクリーンアップし、ネットワーク データ ボリュームをミラーリングします"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}8. ${gl_bai}Dockerソースを変更する"
+ echo -e "${gl_kjlan}9. ${gl_bai}daemon.json ファイルを編集する"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}11. ${gl_bai}Docker-ipv6 アクセスを有効にする"
+ echo -e "${gl_kjlan}12. ${gl_bai}Docker-ipv6 アクセスをオフにする"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}19. ${gl_bai}Docker環境のバックアップ/移行/復元"
+ echo -e "${gl_kjlan}20. ${gl_bai}Docker環境をアンインストールする"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "選択肢を入力してください:" sub_choice
+
+ case $sub_choice in
+ 1)
+ clear
+ send_stats "Docker環境をインストールする"
+ install_add_docker
+
+ ;;
+ 2)
+ clear
+ local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
+ local image_count=$(docker images -q 2>/dev/null | wc -l)
+ local network_count=$(docker network ls -q 2>/dev/null | wc -l)
+ local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
+
+ send_stats "ドッカーのグローバルステータス"
+ echo "Docker のバージョン"
+ docker -v
+ docker compose version
+
+ echo ""
+ echo -e "Docker イメージ:${gl_lv}$image_count${gl_bai} "
+ docker image ls
+ echo ""
+ echo -e "Docker コンテナ:${gl_lv}$container_count${gl_bai}"
+ docker ps -a
+ echo ""
+ echo -e "Docker ボリューム:${gl_lv}$volume_count${gl_bai}"
+ docker volume ls
+ echo ""
+ echo -e "Docker ネットワーク:${gl_lv}$network_count${gl_bai}"
+ docker network ls
+ echo ""
+
+ ;;
+ 3)
+ docker_ps
+ ;;
+ 4)
+ docker_image
+ ;;
+
+ 5)
+ while true; do
+ clear
send_stats "Dockerネットワーク管理"
echo "Dockerネットワークリスト"
echo "------------------------------------------------------------"
@@ -6884,7 +8480,7 @@ linux_docker() {
echo "------------------------------------------------------------"
container_ids=$(docker ps -q)
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "コンテナ名" "ネットワーク名" "IPアドレス"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
@@ -6901,36 +8497,36 @@ linux_docker() {
done
echo ""
- echo "ネットワーク操作"
+ echo "ネットワーク運用"
echo "------------------------"
- echo "1.ネットワークを作成します"
- echo "2。インターネットに参加してください"
- echo "3。ネットワークを終了します"
- echo "4.ネットワークを削除します"
+ echo "1. ネットワークを作成する"
+ echo "2. ネットワークに参加する"
+ echo "3. ネットワークを終了します"
+ echo "4. ネットワークの削除"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- send_stats "ネットワークを作成します"
- read -e -p "新しいネットワーク名を設定します:" dockernetwork
+ send_stats "ネットワークの作成"
+ read -e -p "新しいネットワーク名を設定します。" dockernetwork
docker network create $dockernetwork
;;
2)
- send_stats "インターネットに参加してください"
- read -e -p "ネットワーク名に参加してください:" dockernetwork
- read -e -p "これらのコンテナはネットワークに追加されます(複数のコンテナ名はスペースで区切られています):" dockernames
+ send_stats "ネットワークに参加する"
+ read -e -p "ネットワーク名を追加します:" dockernetwork
+ read -e -p "どのコンテナがネットワークに参加しますか (複数のコンテナ名はスペースで区切ってください):" dockernames
for dockername in $dockernames; do
docker network connect $dockernetwork $dockername
done
;;
3)
- send_stats "インターネットに参加してください"
- read -e -p "出口ネットワーク名:" dockernetwork
- read -e -p "これらのコンテナはネットワークを終了します(複数のコンテナ名はスペースで区切られています):" dockernames
+ send_stats "ネットワークに参加する"
+ read -e -p "出口ネットワーク名:" dockernetwork
+ read -e -p "これらのコンテナはネットワークから終了します (複数のコンテナ名はスペースで区切ってください)。" dockernames
for dockername in $dockernames; do
docker network disconnect $dockernetwork $dockername
@@ -6939,8 +8535,8 @@ linux_docker() {
;;
4)
- send_stats "ネットワークを削除します"
- read -e -p "削除するには、ネットワーク名を入力してください。" dockernetwork
+ send_stats "ネットワークを削除する"
+ read -e -p "削除するネットワーク名を入力してください:" dockernetwork
docker network rm $dockernetwork
;;
@@ -6954,29 +8550,29 @@ linux_docker() {
6)
while true; do
clear
- send_stats "Dockerボリューム管理"
+ send_stats "Docker ボリューム管理"
echo "Dockerボリュームリスト"
docker volume ls
echo ""
echo "ボリューム操作"
echo "------------------------"
- echo "1.新しいボリュームを作成します"
- echo "2。指定されたボリュームを削除します"
- echo "3.すべてのボリュームを削除します"
+ echo "1. 新しいボリュームを作成する"
+ echo "2. 指定したボリュームを削除します"
+ echo "3. すべてのボリュームを削除します"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- send_stats "新しいボリュームを作成します"
- read -e -p "新しいボリューム名を設定します:" dockerjuan
+ send_stats "新しいボリュームを作成する"
+ read -e -p "新しいボリューム名を設定します。" dockerjuan
docker volume create $dockerjuan
;;
2)
- read -e -p "削除ボリューム名を入力します(スペースで複数のボリューム名を分離してください):" dockerjuans
+ read -e -p "削除ボリューム名を入力します (複数のボリューム名はスペースで区切ってください):" dockerjuans
for dockerjuan in $dockerjuans; do
docker volume rm $dockerjuan
@@ -6985,7 +8581,7 @@ linux_docker() {
;;
3)
- send_stats "すべてのボリュームを削除します"
+ send_stats "すべてのボリュームを削除する"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定删除所有未使用的卷吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -6994,7 +8590,7 @@ linux_docker() {
[Nn])
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -7007,7 +8603,7 @@ linux_docker() {
;;
7)
clear
- send_stats "Dockerクリーニング"
+ send_stats "Docker のクリーンアップ"
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}将清理无用的镜像容器网络,包括停止的容器,确定清理吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -7016,13 +8612,13 @@ linux_docker() {
[Nn])
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
8)
clear
- send_stats "Dockerソース"
+ send_stats "Docker ソース"
bash <(curl -sSL https://linuxmirrors.cn/docker.sh)
;;
@@ -7033,21 +8629,29 @@ linux_docker() {
restart docker
;;
+
+
+
11)
clear
- send_stats "Docker V6が開いています"
+ send_stats "Docker v6 がオン"
docker_ipv6_on
;;
12)
clear
- send_stats "Docker V6レベル"
+ send_stats "Docker v6 閉じる"
docker_ipv6_off
;;
+ 19)
+ docker_ssh_migration
+ ;;
+
+
20)
clear
- send_stats "Dockerアンインストール"
+ send_stats "Docker のアンインストール"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定卸载docker环境吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -7059,7 +8663,7 @@ linux_docker() {
[Nn])
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -7068,7 +8672,7 @@ linux_docker() {
kejilion
;;
*)
- echo "無効な入力!"
+ echo "無効な入力です!"
;;
esac
break_end
@@ -7085,140 +8689,141 @@ linux_test() {
while true; do
clear
- # send_stats「テストスクリプトコレクション」
- echo -e "テストスクリプトコレクション"
+ # send_stats "テストスクリプト集"
+ echo -e "テストスクリプト集"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}IPおよびロック解除ステータス検出"
- echo -e "${gl_kjlan}1. ${gl_bai}CHATGPTはステータス検出のロックを解除します"
- echo -e "${gl_kjlan}2. ${gl_bai}リージョンストリーミングメディアのロック解除テスト"
- echo -e "${gl_kjlan}3. ${gl_bai}YeahWUストリーミングメディアのロック解除検出"
- echo -e "${gl_kjlan}4. ${gl_bai}XYKT IP品質の身体検査スクリプト${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}IPおよびロック解除ステータスの検出"
+ echo -e "${gl_kjlan}1. ${gl_bai}ChatGPTロック解除状態検出"
+ echo -e "${gl_kjlan}2. ${gl_bai}リージョンストリーミングメディアロック解除テスト"
+ echo -e "${gl_kjlan}3. ${gl_bai}Yeawu ストリーミング メディアのロック解除の検出"
+ echo -e "${gl_kjlan}4. ${gl_bai}xykt IP 品質チェック スクリプト${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}ネットワーク速度測定"
- echo -e "${gl_kjlan}11. ${gl_bai}BestTrace 3ネットワークバックホール遅延ルーティングテスト"
- echo -e "${gl_kjlan}12. ${gl_bai}MTR_TRACE 3ネットワークバックホールラインテスト"
- echo -e "${gl_kjlan}13. ${gl_bai}SuperSpeed Three-Net速度測定"
- echo -e "${gl_kjlan}14. ${gl_bai}nxtrace高速バックホールテストスクリプト"
- echo -e "${gl_kjlan}15. ${gl_bai}Nxtraceは、IPバックホールテストスクリプトを指定します"
- echo -e "${gl_kjlan}16. ${gl_bai}Ludashi2020 3ネットワークラインテスト"
- echo -e "${gl_kjlan}17. ${gl_bai}I-ABC多機能速度テストスクリプト"
- echo -e "${gl_kjlan}18. ${gl_bai}ネットワーク品質の高品質の身体検査スクリプト${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}ネットワーク回線速度テスト"
+ echo -e "${gl_kjlan}11. ${gl_bai}besttrace 3 ネットワーク バックホール遅延ルーティング テスト"
+ echo -e "${gl_kjlan}12. ${gl_bai}mtr_trace トリプルネットワークバックホール回線テスト"
+ echo -e "${gl_kjlan}13. ${gl_bai}超高速トリプルネットワーク速度テスト"
+ echo -e "${gl_kjlan}14. ${gl_bai}nxtrace 高速バックホール テスト スクリプト"
+ echo -e "${gl_kjlan}15. ${gl_bai}nxtrace は IP バックホール テスト スクリプトを指定します"
+ echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 3つのネットワーク回線テスト"
+ echo -e "${gl_kjlan}17. ${gl_bai}i-abc 多機能速度テスト スクリプト"
+ echo -e "${gl_kjlan}18. ${gl_bai}NetQuality ネットワーク品質チェック スクリプト${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}ハードウェアパフォーマンステスト"
- echo -e "${gl_kjlan}21. ${gl_bai}YABSパフォーマンステスト"
- echo -e "${gl_kjlan}22. ${gl_bai}IICU/GB5 CPUパフォーマンステストスクリプト"
+ echo -e "${gl_kjlan}21. ${gl_bai}yabsパフォーマンステスト"
+ echo -e "${gl_kjlan}22. ${gl_bai}icu/gb5 CPU パフォーマンステストスクリプト"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}包括的なテスト"
+ echo -e "${gl_kjlan}総合的なテスト"
echo -e "${gl_kjlan}31. ${gl_bai}ベンチパフォーマンステスト"
- echo -e "${gl_kjlan}32. ${gl_bai}SpiritySDX Fusion Monster Review${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}32. ${gl_bai}Spiritysdx融合モンスターの評価${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}ノードクオリティ融合モンスターの評価${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
clear
- send_stats "CHATGPTはステータス検出のロックを解除します"
+ send_stats "ChatGPTロック解除状態検出"
bash <(curl -Ls https://cdn.jsdelivr.net/gh/missuo/OpenAI-Checker/openai.sh)
;;
2)
clear
- send_stats "リージョンストリーミングメディアのロック解除テスト"
+ send_stats "リージョンストリーミングメディアロック解除テスト"
bash <(curl -L -s check.unlock.media)
;;
3)
clear
- send_stats "YeahWUストリーミングメディアのロック解除検出"
+ send_stats "Yeawu ストリーミング メディアのロック解除の検出"
install wget
wget -qO- ${gh_proxy}github.com/yeahwu/check/raw/main/check.sh | bash
;;
4)
clear
- send_stats "XYKT_IP品質の身体検査スクリプト"
+ send_stats "xykt_IP 品質チェック スクリプト"
bash <(curl -Ls IP.Check.Place)
;;
11)
clear
- send_stats "BestTrace 3ネットワークバックホール遅延ルーティングテスト"
+ send_stats "besttrace トリプル ネットワーク バックホール遅延ルーティング テスト"
install wget
wget -qO- git.io/besttrace | bash
;;
12)
clear
- send_stats "MTR_TRACE 3ネットワークリターンラインテスト"
+ send_stats "mtr_trace トリプルネットワークバックホール回線テスト"
curl ${gh_proxy}raw.githubusercontent.com/zhucaidan/mtr_trace/main/mtr_trace.sh | bash
;;
13)
clear
- send_stats "SuperSpeed Three-Net速度測定"
+ send_stats "超高速トリプルネットワーク速度テスト"
bash <(curl -Lso- https://git.io/superspeed_uxh)
;;
14)
clear
- send_stats "nxtrace高速バックホールテストスクリプト"
+ send_stats "nxtrace 高速バックホール テスト スクリプト"
curl nxtrace.org/nt |bash
nexttrace --fast-trace --tcp
;;
15)
clear
- send_stats "Nxtraceは、IPバックホールテストスクリプトを指定します"
- echo "参照できるIPのリスト"
+ send_stats "nxtrace は IP バックホール テスト スクリプトを指定します"
+ echo "参照IPリスト"
echo "------------------------"
- echo "北京テレコム:219.141.136.12"
- echo "北京ユニコム:202.106.50.1"
- echo "北京モバイル:221.179.155.161"
- echo "上海通信:202.96.209.133"
- echo "上海ユニコム:210.22.97.1"
- echo "上海モバイル:211.136.112.200"
- echo "広州通信:58.60.188.222"
- echo "広州ユニコム:210.21.196.6"
- echo "広州モバイル:120.196.165.24"
- echo "成都通信:61.139.2.69"
- echo "成都ユニコム:119.6.6.6"
- echo "成都モバイル:211.137.96.205"
- echo "Hunan Telecom:36.111.200.100"
- echo "Hunan Unicom:42.48.16.100"
- echo "Hunan Mobile:39.134.254.6"
+ echo "北京電信: 219.141.136.12"
+ echo "北京ユニコム: 202.106.50.1"
+ echo "北京モバイル: 221.179.155.161"
+ echo "上海電信: 202.96.209.133"
+ echo "上海ユニコム: 210.22.97.1"
+ echo "上海モバイル: 211.136.112.200"
+ echo "広州電信: 58.60.188.222"
+ echo "広州チャイナユニコム: 210.21.196.6"
+ echo "広州モバイル: 120.196.165.24"
+ echo "成都電信: 61.139.2.69"
+ echo "成都チャイナユニコム: 119.6.6.6"
+ echo "成都携帯電話: 211.137.96.205"
+ echo "湖南电信: 36.111.200.100"
+ echo "湖南ユニコム: 42.48.16.100"
+ echo "湖南省モバイル: 39.134.254.6"
echo "------------------------"
- read -e -p "指定されたIPを入力してください:" testip
+ read -e -p "特定の IP を入力します。" testip
curl nxtrace.org/nt |bash
nexttrace $testip
;;
16)
clear
- send_stats "Ludashi2020 3ネットワークラインテスト"
+ send_stats "ludashi2020 3つのネットワーク回線テスト"
curl ${gh_proxy}raw.githubusercontent.com/ludashi2020/backtrace/main/install.sh -sSf | sh
;;
17)
clear
- send_stats "I-ABC多機能速度テストスクリプト"
+ send_stats "i-abc 多機能速度テスト スクリプト"
bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/i-abc/Speedtest/main/speedtest.sh)
;;
18)
clear
- send_stats "ネットワーク品質のテストスクリプト"
+ send_stats "ネットワーク品質テストスクリプト"
bash <(curl -sL Net.Check.Place)
;;
21)
clear
- send_stats "YABSパフォーマンステスト"
+ send_stats "yabsパフォーマンステスト"
check_swap
curl -sL yabs.sh | bash -s -- -i -5
;;
22)
clear
- send_stats "IICU/GB5 CPUパフォーマンステストスクリプト"
+ send_stats "icu/gb5 CPU パフォーマンステストスクリプト"
check_swap
bash <(curl -sL bash.icu/gb5)
;;
@@ -7229,17 +8834,25 @@ linux_test() {
curl -Lso- bench.sh | bash
;;
32)
- send_stats "SpiritySDX Fusion Monster Review"
+ send_stats "Spiritysdx フュージョンモンスター レビュー"
+ clear
+ curl -L ${gh_proxy}github.com/spiritLHLS/ecs/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ ;;
+
+ 33)
+ send_stats "ノードクオリティ融合モンスターの評価"
clear
- curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ bash <(curl -sL https://run.NodeQuality.com)
;;
+
+
0)
kejilion
;;
*)
- echo "無効な入力!"
+ echo "無効な入力です!"
;;
esac
break_end
@@ -7255,66 +8868,66 @@ linux_Oracle() {
while true; do
clear
- send_stats "Oracle Cloud Scriptコレクション"
- echo -e "Oracle Cloud Scriptコレクション"
+ send_stats "Oracle Cloudスクリプト・コレクション"
+ echo -e "Oracle Cloudスクリプト・コレクション"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}アイドルマシンアクティブスクリプトをインストールします"
- echo -e "${gl_kjlan}2. ${gl_bai}アイドルマシンアクティブスクリプトをアンインストールします"
+ echo -e "${gl_kjlan}1. ${gl_bai}アイドル状態のマシンのアクティブ スクリプトをインストールする"
+ echo -e "${gl_kjlan}2. ${gl_bai}アイドル状態のマシンからアクティブなスクリプトをアンインストールする"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}3. ${gl_bai}DDシステムスクリプトを再インストールします"
- echo -e "${gl_kjlan}4. ${gl_bai}探偵r開始スクリプト"
- echo -e "${gl_kjlan}5. ${gl_bai}ルートパスワードログインモードをオンにします"
- echo -e "${gl_kjlan}6. ${gl_bai}IPv6回復ツール"
+ echo -e "${gl_kjlan}3. ${gl_bai}DD 再インストール システム スクリプト"
+ echo -e "${gl_kjlan}4. ${gl_bai}探偵R起動スクリプト"
+ echo -e "${gl_kjlan}5. ${gl_bai}ROOTパスワードログインモードを有効にする"
+ echo -e "${gl_kjlan}6. ${gl_bai}IPV6回復ツール"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
clear
- echo "アクティブスクリプト:CPUが10-20%を占めるメモリは20%を占めています"
- read -e -p "必ずインストールしますか? (y/n):" choice
+ echo "アクティブ スクリプト: CPU 使用率 10 ~ 20% メモリ使用率 20%"
+ read -e -p "インストールしてもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
install_docker
- # デフォルト値を設定します
+ # デフォルト値を設定する
local DEFAULT_CPU_CORE=1
local DEFAULT_CPU_UTIL="10-20"
local DEFAULT_MEM_UTIL=20
local DEFAULT_SPEEDTEST_INTERVAL=120
- # ユーザーにCPUコアの数と占有率の数を入力するように促し、入力した場合はデフォルト値を使用します。
- read -e -p "CPUコアの数を入力してください[デフォルト:$DEFAULT_CPU_CORE]: " cpu_core
+ # CPU コアの数と占有率を入力するようユーザーに求めます。ユーザーが Enter キーを押すと、デフォルト値が使用されます。
+ read -e -p "CPU コアの数を入力してください [デフォルト:$DEFAULT_CPU_CORE]: " cpu_core
local cpu_core=${cpu_core:-$DEFAULT_CPU_CORE}
- read -e -p "CPU使用率の範囲(たとえば、10-20)を入力してください[デフォルト:$DEFAULT_CPU_UTIL]: " cpu_util
+ read -e -p "CPU 使用率の範囲 (例: 10 ~ 20) を入力してください [デフォルト:$DEFAULT_CPU_UTIL]: " cpu_util
local cpu_util=${cpu_util:-$DEFAULT_CPU_UTIL}
- read -e -p "メモリの使用率を入力してください[デフォルト:$DEFAULT_MEM_UTIL]: " mem_util
+ read -e -p "メモリ使用率を入力してください [デフォルト:$DEFAULT_MEM_UTIL]: " mem_util
local mem_util=${mem_util:-$DEFAULT_MEM_UTIL}
- read -e -p "SpeedTest間隔時間(秒)を入力してください[デフォルト:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
+ read -e -p "Speedtest の間隔時間 (秒) を入力してください [デフォルト:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
local speedtest_interval=${speedtest_interval:-$DEFAULT_SPEEDTEST_INTERVAL}
- # Dockerコンテナを実行します
- docker run -itd --name=lookbusy --restart=always \
+ # Dockerコンテナを実行する
+ docker run -d --name=lookbusy --restart=always \
-e TZ=Asia/Shanghai \
-e CPU_UTIL="$cpu_util" \
-e CPU_CORE="$cpu_core" \
-e MEM_UTIL="$mem_util" \
-e SPEEDTEST_INTERVAL="$speedtest_interval" \
fogforest/lookbusy
- send_stats "Oracle Cloudインストールアクティブスクリプト"
+ send_stats "Oracle Cloudインストール・アクティブ・スクリプト"
;;
[Nn])
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -7322,20 +8935,20 @@ linux_Oracle() {
clear
docker rm -f lookbusy
docker rmi fogforest/lookbusy
- send_stats "Oracle Cloudはアクティブスクリプトをアンインストールします"
+ send_stats "Oracle Cloudアンインストール・アクティブ・スクリプト"
;;
3)
clear
- echo "システムを再インストールします"
+ echo "システムを再インストールする"
echo "--------------------------------"
- echo -e "${gl_hong}知らせ:${gl_bai}再インストールは接触を失う危険であり、心配している人はそれを注意して使用する必要があります。再インストールには15分かかると予想されます。事前にデータをバックアップしてください。"
- read -e -p "必ず続行しますか? (y/n):" choice
+ echo -e "${gl_hong}知らせ:${gl_bai}再インストールすると接続が切れる可能性がありますので、不安な方はご注意ください。再インストールには 15 分程度かかることが予想されますので、事前にデータをバックアップしてください。"
+ read -e -p "続行してもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
while true; do
- read -e -p "再インストールするシステムを選択してください:1。Debian12| 2。Ubuntu20.04:" sys_choice
+ read -e -p "再インストールするシステムを選択してください: 1. Debian12 | 2.Ubuntu20.04:" sys_choice
case "$sys_choice" in
1)
@@ -7347,12 +8960,12 @@ linux_Oracle() {
break # 结束循环
;;
*)
- echo "選択の無効な、再入力してください。"
+ echo "選択が無効です。再入力してください。"
;;
esac
done
- read -e -p "再インストールされたパスワードを入力してください:" vpspasswd
+ read -e -p "再インストール後にパスワードを入力してください:" vpspasswd
install wget
bash <(wget --no-check-certificate -qO- "${gh_proxy}raw.githubusercontent.com/MoeClub/Note/master/InstallNET.sh") $xitong -v 64 -p $vpspasswd -port 22
send_stats "Oracle Cloud再インストールシステムスクリプト"
@@ -7361,32 +8974,32 @@ linux_Oracle() {
echo "キャンセル"
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
4)
clear
- echo "この機能は開発段階にあるので、お楽しみに!"
+ send_stats "探偵R起動スクリプト"
+ bash <(wget -qO- ${gh_proxy}github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh)
;;
5)
clear
add_sshpasswd
-
;;
6)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "この関数は、彼のおかげで、マスターJHBによって提供されます!"
- send_stats "IPv6修正"
+ echo "この機能は jhb によって提供されています。ありがとう!"
+ send_stats "IPv6修復"
;;
0)
kejilion
;;
*)
- echo "無効な入力!"
+ echo "無効な入力です!"
;;
esac
break_end
@@ -7398,6 +9011,9 @@ linux_Oracle() {
}
+
+
+
docker_tato() {
local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
@@ -7407,7 +9023,7 @@ docker_tato() {
if command -v docker &> /dev/null; then
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_lv}環境がインストールされています${gl_bai}容器:${gl_lv}$container_count${gl_bai}鏡:${gl_lv}$image_count${gl_bai}ネットワーク:${gl_lv}$network_count${gl_bai}ロール:${gl_lv}$volume_count${gl_bai}"
+ echo -e "${gl_lv}環境がインストールされました${gl_bai}容器:${gl_lv}$container_count${gl_bai}鏡:${gl_lv}$image_count${gl_bai}ネットワーク:${gl_lv}$network_count${gl_bai}ロール:${gl_lv}$volume_count${gl_bai}"
fi
}
@@ -7415,20 +9031,20 @@ docker_tato() {
ldnmp_tato() {
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
-local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml 2>/dev/null | tr -d '[:space:]')
if [ -n "$dbrootpasswd" ]; then
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
fi
-local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+local db_output="${gl_lv}${db_count}${gl_bai}"
if command -v docker &>/dev/null; then
if docker ps --filter "name=nginx" --filter "status=running" | grep -q nginx; then
echo -e "${gl_huang}------------------------"
- echo -e "${gl_lv}環境がインストールされています${gl_bai} $output $db_output"
+ echo -e "${gl_lv}環境がインストールされています${gl_bai}サイト:$outputデータベース:$db_output"
fi
fi
@@ -7456,31 +9072,31 @@ linux_ldnmp() {
while true; do
clear
- # send_stats "ldnmp webサイトビルディング"
- echo -e "${gl_huang}LDNMP Webサイトビルディング"
+ # send_stats "LDNMP Web サイトの構築"
+ echo -e "${gl_huang}LDNMP Web サイトの構築"
ldnmp_tato
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}1. ${gl_bai}LDNMP環境をインストールします${gl_huang}★${gl_bai} ${gl_huang}2. ${gl_bai}WordPressをインストールします${gl_huang}★${gl_bai}"
- echo -e "${gl_huang}3. ${gl_bai}Discuzフォーラムをインストールします${gl_huang}4. ${gl_bai}Kadao Cloudデスクトップをインストールします"
- echo -e "${gl_huang}5. ${gl_bai}Apple CMSフィルムとテレビ局をインストールします${gl_huang}6. ${gl_bai}ユニコーンデジタルカードネットワークをインストールします"
- echo -e "${gl_huang}7. ${gl_bai}Flarum Forum Webサイトをインストールします${gl_huang}8. ${gl_bai}Typecho Lightweight Blog Webサイトをインストールします"
- echo -e "${gl_huang}9. ${gl_bai}LinkStack共有リンクプラットフォームをインストールします${gl_huang}20. ${gl_bai}動的サイトをカスタマイズします"
+ echo -e "${gl_huang}1. ${gl_bai}LDNMP環境をインストールする${gl_huang}★${gl_bai} ${gl_huang}2. ${gl_bai}WordPressをインストールする${gl_huang}★${gl_bai}"
+ echo -e "${gl_huang}3. ${gl_bai}Discuz フォーラムをインストールする${gl_huang}4. ${gl_bai}Kedao クラウド デスクトップをインストールする"
+ echo -e "${gl_huang}5. ${gl_bai}Apple CMS ムービーおよび TV ステーションをインストールする${gl_huang}6. ${gl_bai}Unicorn デジタル カード ネットワークをインストールする"
+ echo -e "${gl_huang}7. ${gl_bai}flarumフォーラムWebサイトをインストールする${gl_huang}8. ${gl_bai}typecho 軽量ブログ Web サイトをインストールする"
+ echo -e "${gl_huang}9. ${gl_bai}LinkStack 共有リンク プラットフォームをインストールする${gl_huang}20. ${gl_bai}カスタム動的サイト"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}21. ${gl_bai}nginxのみをインストールします${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}サイトリダイレクト"
- echo -e "${gl_huang}23. ${gl_bai}サイトリバースプロキシ-IP+ポート${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}サイトリバースプロキシ - ドメイン名"
- echo -e "${gl_huang}25. ${gl_bai}Bitwardenパスワード管理プラットフォームをインストールします${gl_huang}26. ${gl_bai}HaloブログのWebサイトをインストールします"
- echo -e "${gl_huang}27. ${gl_bai}AIペイントプロンプトワードジェネレーターをインストールします${gl_huang}28. ${gl_bai}サイトの逆プロキシロードバランス"
- echo -e "${gl_huang}30. ${gl_bai}静的サイトをカスタマイズします"
+ echo -e "${gl_huang}21. ${gl_bai}nginxのみをインストールする${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}サイトリダイレクト"
+ echo -e "${gl_huang}23. ${gl_bai}サイト リバース プロキシ - IP+ポート${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}サイト リバース プロキシ ドメイン名"
+ echo -e "${gl_huang}25. ${gl_bai}Bitwarden パスワード管理プラットフォームをインストールする${gl_huang}26. ${gl_bai}Halo ブログ サイトをインストールする"
+ echo -e "${gl_huang}27. ${gl_bai}AI絵画プロンプトワードジェネレーターをインストールする${gl_huang}28. ${gl_bai}サイト リバース プロキシ負荷分散"
+ echo -e "${gl_huang}29. ${gl_bai}ストリーム 4 層プロキシ転送${gl_huang}30. ${gl_bai}カスタム静的サイト"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}31. ${gl_bai}サイトデータ管理${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}サイトデータ全体をバックアップします"
- echo -e "${gl_huang}33. ${gl_bai}タイミングのリモートバックアップ${gl_huang}34. ${gl_bai}サイトデータ全体を復元します"
+ echo -e "${gl_huang}31. ${gl_bai}サイトデータ管理${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}サイト全体のデータをバックアップする"
+ echo -e "${gl_huang}33. ${gl_bai}スケジュールされたリモートバックアップ${gl_huang}34. ${gl_bai}サイト全体のデータを復元する"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}35. ${gl_bai}LDNMP環境を保護します${gl_huang}36. ${gl_bai}LDNMP環境を最適化します"
- echo -e "${gl_huang}37. ${gl_bai}LDNMP環境を更新します${gl_huang}38. ${gl_bai}LDNMP環境をアンインストールします"
+ echo -e "${gl_huang}35. ${gl_bai}LDNMP環境を保護する${gl_huang}36. ${gl_bai}LDNMP環境の最適化"
+ echo -e "${gl_huang}37. ${gl_bai}LDNMP環境を更新する${gl_huang}38. ${gl_bai}LDNMP環境をアンインストールする"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}0. ${gl_bai}メインメニューに戻ります"
+ echo -e "${gl_huang}0. ${gl_bai}メインメニューに戻る"
echo -e "${gl_huang}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
@@ -7494,91 +9110,108 @@ linux_ldnmp() {
3)
clear
- # ディスクフォーラム
- webname="Discuz论坛"
+ # ディスカスフォーラム
+ webname="ディスカスフォーラム"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/discuz.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/main/Discuz_X3.5_SC_UTF8_20240520.zip
- unzip latest.zip
+ LATEST_URL=$(curl -s https://api.gitee.com/api/v5/repos/Discuz/DiscuzX/releases/latest | grep -o 'https://[^"]*Discuz_X[^"]*SC_UTF8[^"]*\.zip' | head -n 1)
+ wget -O latest.zip ${LATEST_URL}
+ unzip -q latest.zip
+ mv upload/* .
+ rm -rf upload readme readme.html utility.html LICENSE qqqun.png
rm latest.zip
restart_ldnmp
ldnmp_web_on
- echo "データベースアドレス:mysql"
- echo "データベース名:$dbname"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "データベース名:$dbname"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
- echo "テーブルプレフィックス:discuz_"
+ echo "テーブル接頭辞: discuz_"
;;
4)
clear
- # Kedao Cloudデスクトップ
- webname="可道云桌面"
+ # Kedao クラウド デスクトップ
+ webname="Kedao クラウド デスクトップ"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/kdy.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/1.50.02.zip
+ LATEST_VERSION=$(curl -s https://api.github.com/repos/kalcaddle/kodbox/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/${LATEST_VERSION}.zip
unzip -o latest.zip
rm latest.zip
mv /home/web/html/$yuming/kodbox* /home/web/html/$yuming/kodbox
restart_ldnmp
ldnmp_web_on
- echo "データベースアドレス:mysql"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
- echo "データベース名:$dbname"
- echo "Redisホスト:Redis"
+ echo "データベース名:$dbname"
+ echo "redisホスト: redis"
;;
5)
clear
- # Apple CMS
- webname="苹果CMS"
+ # AppleCMS
+ webname="AppleCMS"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/maccms.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7595,33 +9228,39 @@ linux_ldnmp() {
ldnmp_web_on
- echo "データベースアドレス:mysql"
- echo "データベースポート:3306"
- echo "データベース名:$dbname"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "データベースポート: 3306"
+ echo "データベース名:$dbname"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
- echo "データベースプレフィックス:mac_"
+ echo "データベース接頭辞: mac_"
echo "------------------------"
- echo "インストールが成功した後、バックグラウンドアドレスにログインします"
+ echo "インストールが成功したら、バックエンド アドレスにログインします。"
echo "https://$yuming/vip.php"
;;
6)
clear
- # 一本足のカウントカード
- webname="独脚数卡"
+ # 一本足のナンバーカード
+ webname="一本足のナンバーカード"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/dujiaoka.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7633,24 +9272,24 @@ linux_ldnmp() {
ldnmp_web_on
- echo "データベースアドレス:mysql"
- echo "データベースポート:3306"
- echo "データベース名:$dbname"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "データベースポート: 3306"
+ echo "データベース名:$dbname"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
echo ""
- echo "Redisアドレス:Redis"
- echo "Redisパスワード:デフォルトで記入されていません"
- echo "Redisポート:6379"
+ echo "redisアドレス: redis"
+ echo "redis パスワード: デフォルトでは入力されていません"
+ echo "Redis ポート: 6379"
echo ""
- echo "ウェブサイトURL:https://$yuming"
- echo "バックグラウンドログインパス: /admin"
+ echo "ウェブサイトURL:https://$yuming"
+ echo "バックエンドのログイン パス: /admin"
echo "------------------------"
- echo "ユーザー名:admin"
- echo "パスワード:管理者"
+ echo "ユーザー名: 管理者"
+ echo "パスワード: 管理者"
echo "------------------------"
- echo "ログインするときに右上隅に赤いerror0が表示される場合は、次のコマンドを使用してください。"
- echo "また、ユニコーン番号カードがとても面倒で、そのような問題があることに非常に腹を立てています!"
+ echo "ログイン時に右上隅に赤色の error0 が表示される場合は、次のコマンドを使用してください。"
+ echo "私も、なぜユニコーンナンバーカードがこんなに面倒で、こんな問題を抱えているのか、とても腹が立っています。"
echo "sed -i 's/ADMIN_HTTPS=false/ADMIN_HTTPS=true/g' /home/web/html/$yuming/dujiaoka/.env"
;;
@@ -7658,18 +9297,24 @@ linux_ldnmp() {
7)
clear
# フララムフォーラム
- webname="flarum论坛"
+ webname="フララムフォーラム"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/flarum.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec php rm -f /usr/local/etc/php/conf.d/optimized_php.ini
@@ -7690,19 +9335,23 @@ linux_ldnmp() {
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/sitemap"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/oauth"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/best-answer:*"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/upload"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/gamification"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/byobu:*"
docker exec php sh -c "cd /var/www/html/$yuming && composer require v17development/flarum-seo"
docker exec php sh -c "cd /var/www/html/$yuming && composer require clarkwinkelmann/flarum-ext-emojionearea"
+
restart_ldnmp
ldnmp_web_on
- echo "データベースアドレス:mysql"
- echo "データベース名:$dbname"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "データベース名:$dbname"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
- echo "テーブルプレフィックス:flarum_"
- echo "管理者情報は自分で設定されます"
+ echo "テーブル接頭辞: flarum_"
+ echo "管理者情報を自分で設定可能"
;;
@@ -7711,16 +9360,22 @@ linux_ldnmp() {
# typecho
webname="typecho"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/typecho.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7735,11 +9390,11 @@ linux_ldnmp() {
clear
ldnmp_web_on
- echo "データベースプレフィックス:typecho_"
- echo "データベースアドレス:mysql"
- echo "ユーザー名:$dbuse"
+ echo "データベース接頭辞: typecho_"
+ echo "データベースアドレス: mysql"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
- echo "データベース名:$dbname"
+ echo "データベース名:$dbname"
;;
@@ -7749,17 +9404,21 @@ linux_ldnmp() {
# LinkStack
webname="LinkStack"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/refs/heads/main/index_php.conf
sed -i "s|/var/www/html/yuming.com/|/var/www/html/yuming.com/linkstack|g" /home/web/conf.d/$yuming.conf
sed -i "s|yuming.com|$yuming|g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7774,27 +9433,30 @@ linux_ldnmp() {
clear
ldnmp_web_on
- echo "データベースアドレス:mysql"
- echo "データベースポート:3306"
- echo "データベース名:$dbname"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "データベースポート: 3306"
+ echo "データベース名:$dbname"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
;;
20)
clear
- webname="PHP动态站点"
+ webname="PHP動的サイト"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/index_php.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7802,10 +9464,10 @@ linux_ldnmp() {
cd $yuming
clear
- echo -e "[${gl_huang}1/6${gl_bai}] PHPソースコードをアップロードします"
+ echo -e "[${gl_huang}1/6${gl_bai}] PHPソースコードをアップロードする"
echo "-------------"
- echo "現在、zip-formatソースコードパッケージのみが許可されています。ソースコードパッケージを/home/web/html/に入れてください${yuming}ディレクトリ内"
- read -e -p "ダウンロードリンクを入力して、ソースコードパッケージをリモートでダウンロードすることもできます。 Enterを直接押してリモートダウンロードをスキップします。" url_download
+ echo "現在、zip 形式のソース コード パッケージのみをアップロードできます。ソースコードパッケージを/home/web/html/に置いてください。${yuming}ディレクトリの下"
+ read -e -p "ダウンロード リンクを入力して、ソース コード パッケージをリモートでダウンロードすることもできます。 Enter を直接押して、リモート ダウンロードをスキップします。" url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -7815,12 +9477,12 @@ linux_ldnmp() {
rm -f $(ls -t *.zip | head -n 1)
clear
- echo -e "[${gl_huang}2/6${gl_bai}] index.phpが配置されているパス"
+ echo -e "[${gl_huang}2/6${gl_bai}]index.phpが配置されているパス"
echo "-------------"
# find "$(realpath .)" -name "index.php" -print
find "$(realpath .)" -name "index.php" -print | xargs -I {} dirname {}
- read -e -p "(/home/web/html/に似たindex.phpのパスを入力してください$yuming/wordpress/): " index_lujing
+ read -e -p "(/home/web/html/ のような、index.php へのパスを入力してください)$yuming/wordpress/): " index_lujing
sed -i "s#root /var/www/html/$yuming/#root $index_lujing#g" /home/web/conf.d/$yuming.conf
sed -i "s#/home/web/#/var/www/#g" /home/web/conf.d/$yuming.conf
@@ -7828,7 +9490,7 @@ linux_ldnmp() {
clear
echo -e "[${gl_huang}3/6${gl_bai}] PHPバージョンを選択してください"
echo "-------------"
- read -e -p "1。PHPの最新バージョン| 2。Php7.4:" pho_v
+ read -e -p "1.php最新バージョン | 2.php7.4:" pho_v
case "$pho_v" in
1)
sed -i "s#php:9000#php:9000#g" /home/web/conf.d/$yuming.conf
@@ -7839,15 +9501,15 @@ linux_ldnmp() {
local PHP_Version="php74"
;;
*)
- echo "選択の無効な、再入力してください。"
+ echo "選択が無効です。再入力してください。"
;;
esac
clear
- echo -e "[${gl_huang}4/6${gl_bai}]指定された拡張機能をインストールします"
+ echo -e "[${gl_huang}4/6${gl_bai}] 指定された拡張機能をインストールします"
echo "-------------"
- echo "インストールされた拡張機能"
+ echo "インストールされている拡張機能"
docker exec php php -m
read -e -p "$(echo -e "输入需要安装的扩展名称,如 ${gl_huang}SourceGuardian imap ftp${gl_bai} 等等。直接回车将跳过安装 : ")" php_extensions
@@ -7857,25 +9519,25 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}5/6${gl_bai}]サイト構成を編集します"
+ echo -e "[${gl_huang}5/6${gl_bai}] サイト構成を編集する"
echo "-------------"
- echo "任意のキーを押して続行すると、擬似静的コンテンツなど、サイト構成を詳細に設定できます。"
+ echo "続行するには任意のキーを押してください。擬似静的コンテンツなどのサイト構成を詳細に設定できます。"
read -n 1 -s -r -p ""
install nano
nano /home/web/conf.d/$yuming.conf
clear
- echo -e "[${gl_huang}6/6${gl_bai}]データベース管理"
+ echo -e "[${gl_huang}6/6${gl_bai}] データベース管理"
echo "-------------"
- read -e -p "1.新しいサイトを構築します2。古いサイトを構築し、データベースのバックアップがあります。" use_db
+ read -e -p "1. 新しいサイトを構築します。 2. 古いサイトを構築し、データベースのバックアップを作成します。" use_db
case $use_db in
1)
echo
;;
2)
- echo "データベースのバックアップは、.GZ-endコンプレッションパッケージである必要があります。 Pagoda/1panelのバックアップデータのインポートをサポートするために、/home/directoryに入れてください。"
- read -e -p "ダウンロードリンクを入力して、バックアップデータをリモートでダウンロードすることもできます。 Enterを直接押して、リモートダウンロードをスキップします:" url_download_db
+ echo "データベースのバックアップは、.gz で終わる圧縮パッケージである必要があります。 Pagoda/1panel バックアップ データのインポートをサポートするには、/home/ ディレクトリに配置してください。"
+ read -e -p "ダウンロード リンクを入力してバックアップ データをリモートでダウンロードすることもできます。 Enter を直接押して、リモート ダウンロードをスキップします。" url_download_db
cd /home/
if [ -n "$url_download_db" ]; then
@@ -7885,7 +9547,7 @@ linux_ldnmp() {
latest_sql=$(ls -t *.sql | head -n 1)
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname < "/home/$latest_sql"
- echo "データベースインポートテーブルデータ"
+ echo "データベースにインポートされたテーブルデータ"
docker exec -i mysql mysql -u root -p"$dbrootpasswd" -e "USE $dbname; SHOW TABLES;"
rm -f *.sql
echo "データベースのインポートが完了しました"
@@ -7900,12 +9562,12 @@ linux_ldnmp() {
restart_ldnmp
ldnmp_web_on
prefix="web$(shuf -i 10-99 -n 1)_"
- echo "データベースアドレス:mysql"
- echo "データベース名:$dbname"
- echo "ユーザー名:$dbuse"
+ echo "データベースアドレス: mysql"
+ echo "データベース名:$dbname"
+ echo "ユーザー名:$dbuse"
echo "パスワード:$dbusepasswd"
- echo "テーブルプレフィックス:$prefix"
- echo "管理者ログイン情報は自分で設定されます"
+ echo "テーブルの接頭辞:$prefix"
+ echo "管理者のログイン情報は自分で設定します"
;;
@@ -7917,18 +9579,23 @@ linux_ldnmp() {
22)
clear
- webname="站点重定向"
+ webname="サイトリダイレクト"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
- read -e -p "ジャンプドメイン名を入力してください:" reverseproxy
+ read -e -p "リダイレクト ドメイン名を入力してください:" reverseproxy
nginx_install_status
+
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/rewrite.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s/baidu.com/$reverseproxy/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7943,9 +9610,10 @@ linux_ldnmp() {
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "IP+ポートは、サービスへのアクセスをブロックされています"
+ echo "IP+ポートはサービスへのアクセスをブロックされています"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
@@ -7953,19 +9621,23 @@ linux_ldnmp() {
24)
clear
- webname="反向代理-域名"
+ webname="リバースプロキシドメイン名"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
- echo -e "ドメイン名形式:${gl_huang}google.com${gl_bai}"
- read -e -p "抗ジェネレーションドメイン名を入力してください。" fandai_yuming
+ echo -e "ドメイン名の形式:${gl_huang}google.com${gl_bai}"
+ read -e -p "リバース プロキシ ドメイン名を入力してください:" fandai_yuming
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-domain.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s|fandaicom|$fandai_yuming|g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7979,22 +9651,19 @@ linux_ldnmp() {
clear
webname="Bitwarden"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
docker run -d \
--name bitwarden \
- --restart always \
+ --restart=always \
-p 3280:80 \
-v /home/web/html/$yuming/bitwarden/data:/data \
vaultwarden/server
+
duankou=3280
- reverse_proxy
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
- nginx_web_on
;;
@@ -8002,32 +9671,31 @@ linux_ldnmp() {
clear
webname="halo"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
- docker run -d --name halo --restart always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- duankou=8010
- reverse_proxy
+ docker run -d --name halo --restart=always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- nginx_web_on
+ duankou=8010
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
;;
27)
clear
- webname="AI绘画提示词生成器"
+ webname="AI絵画プロンプトワードジェネレーター"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8050,19 +9718,26 @@ linux_ldnmp() {
;;
+ 29)
+ stream_panel
+ ;;
+
30)
clear
- webname="静态站点"
+ webname="静的サイト"
send_stats "インストール$webname"
- echo "展開を開始します$webname"
+ echo "導入を開始する$webname"
add_yuming
repeat_add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8071,10 +9746,10 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}1/2${gl_bai}]静的ソースコードをアップロードします"
+ echo -e "[${gl_huang}1/2${gl_bai}] 静的ソースコードをアップロードする"
echo "-------------"
- echo "現在、zip-formatソースコードパッケージのみが許可されています。ソースコードパッケージを/home/web/html/に入れてください${yuming}ディレクトリ内"
- read -e -p "ダウンロードリンクを入力して、ソースコードパッケージをリモートでダウンロードすることもできます。 Enterを直接押してリモートダウンロードをスキップします。" url_download
+ echo "現在、zip 形式のソース コード パッケージのみをアップロードできます。ソースコードパッケージを/home/web/html/に置いてください。${yuming}ディレクトリの下"
+ read -e -p "ダウンロード リンクを入力して、ソース コード パッケージをリモートでダウンロードすることもできます。 Enter を直接押して、リモート ダウンロードをスキップします。" url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -8084,12 +9759,12 @@ linux_ldnmp() {
rm -f $(ls -t *.zip | head -n 1)
clear
- echo -e "[${gl_huang}2/2${gl_bai}] index.htmlが配置されているパス"
+ echo -e "[${gl_huang}2/2${gl_bai}]index.html が配置されているパス"
echo "-------------"
# find "$(realpath .)" -name "index.html" -print
find "$(realpath .)" -name "index.html" -print | xargs -I {} dirname {}
- read -e -p "(/home/web/html/に似たindex.htmlへのパスを入力してください$yuming/index/): " index_lujing
+ read -e -p "(/home/web/html/ のような、index.html へのパスを入力してください)$yuming/index/): " index_lujing
sed -i "s#root /var/www/html/$yuming/#root $index_lujing#g" /home/web/conf.d/$yuming.conf
sed -i "s#/home/web/#/var/www/#g" /home/web/conf.d/$yuming.conf
@@ -8114,31 +9789,29 @@ linux_ldnmp() {
32)
clear
- send_stats "LDNMP環境バックアップ"
+ send_stats "LDNMP環境のバックアップ"
local backup_filename="web_$(date +"%Y%m%d%H%M%S").tar.gz"
- echo -e "${gl_huang}バックアップ$backup_filename ...${gl_bai}"
+ echo -e "${gl_kjlan}バックアップ中$backup_filename ...${gl_bai}"
cd /home/ && tar czvf "$backup_filename" web
while true; do
clear
- echo "バックアップファイルが作成されました: /home /$backup_filename"
- read -e -p "バックアップデータをリモートサーバーに転送しますか? (y/n):" choice
+ echo "バックアップファイルが作成されました: /home/$backup_filename"
+ read -e -p "バックアップ データをリモート サーバーに転送しますか? (はい/いいえ):" choice
case "$choice" in
[Yy])
- read -e -p "リモートサーバーIPを入力してください:" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "エラー:リモートサーバーIPを入力してください。"
- continue
- fi
+ kj_ssh_read_host_port "リモートサーバーのIPを入力してください:" "ターゲット サーバーの SSH ポート [デフォルト 22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
local latest_tar=$(ls -t /home/*.tar.gz | head -1)
if [ -n "$latest_tar" ]; then
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 添加等待时间
- scp -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
- echo "このファイルは、リモートサーバーホームディレクトリに転送されました。"
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
+ echo "ファイルはリモート サーバーのホーム ディレクトリに転送されました。"
else
- echo "転送されるファイルは見つかりませんでした。"
+ echo "転送するファイルが見つかりませんでした。"
fi
break
;;
@@ -8146,7 +9819,7 @@ linux_ldnmp() {
break
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
done
@@ -8154,9 +9827,9 @@ linux_ldnmp() {
33)
clear
- send_stats "タイミングのリモートバックアップ"
- read -e -p "リモートサーバーIPを入力してください:" useip
- read -e -p "リモートサーバーのパスワードを入力してください:" usepasswd
+ send_stats "スケジュールされたリモートバックアップ"
+ read -e -p "リモート サーバーの IP を入力します。" useip
+ read -e -p "リモートサーバーのパスワードを入力してください:" usepasswd
cd ~
wget -O ${useip}_beifen.sh ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/beifen.sh > /dev/null 2>&1
@@ -8166,18 +9839,18 @@ linux_ldnmp() {
sed -i "s/123456/$usepasswd/g" ${useip}_beifen.sh
echo "------------------------"
- echo "1。毎週のバックアップ2。毎日のバックアップ"
- read -e -p "選択を入力してください:" dingshi
+ echo "1. 毎週のバックアップ 2. 毎日のバックアップ"
+ read -e -p "選択肢を入力してください:" dingshi
case $dingshi in
1)
check_crontab_installed
- read -e -p "毎週のバックアップ(0-6、0は日曜日を表す)の曜日を選択します。" weekday
+ read -e -p "毎週のバックアップの曜日を選択します (0 ~ 6、0 は日曜日を表します)。" weekday
(crontab -l ; echo "0 0 * * $weekday ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
2)
check_crontab_installed
- read -e -p "毎日のバックアップの時間を選択します(時間、0-23):" hour
+ read -e -p "毎日のバックアップ時間 (時間、0 ~ 23) を選択します。" hour
(crontab -l ; echo "0 $hour * * * ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
*)
@@ -8191,19 +9864,19 @@ linux_ldnmp() {
34)
root_use
- send_stats "LDNMP環境修復"
- echo "利用可能なサイトバックアップ"
+ send_stats "LDNMP環境の復元"
+ echo "利用可能なサイトのバックアップ"
echo "-------------------------"
ls -lt /home/*.gz | awk '{print $NF}'
echo ""
- read -e -p "入力して最新のバックアップを復元し、バックアップファイル名を入力して指定されたバックアップを復元し、0を入力して終了します。" filename
+ read -e -p "Enter キーを押して最新のバックアップを復元し、バックアップ ファイル名を入力して指定したバックアップを復元し、0 を入力して終了します。" filename
if [ "$filename" == "0" ]; then
break_end
linux_ldnmp
fi
- # ユーザーがファイル名を入力しない場合は、最新の圧縮パッケージを使用します
+ # ユーザーがファイル名を入力しない場合は、最新の圧縮パッケージが使用されます。
if [ -z "$filename" ]; then
local filename=$(ls -t /home/*.tar.gz | head -1)
fi
@@ -8213,16 +9886,15 @@ linux_ldnmp() {
docker compose down > /dev/null 2>&1
rm -rf /home/web > /dev/null 2>&1
- echo -e "${gl_huang}減圧が行われています$filename ...${gl_bai}"
+ echo -e "${gl_kjlan}解凍中$filename ...${gl_bai}"
cd /home/ && tar -xzf "$filename"
- check_port
install_dependency
install_docker
install_certbot
install_ldnmp
else
- echo "圧縮パッケージは見つかりませんでした。"
+ echo "圧縮パッケージが見つかりませんでした。"
fi
;;
@@ -8240,11 +9912,11 @@ linux_ldnmp() {
root_use
while true; do
clear
- send_stats "LDNMP環境を更新します"
- echo "LDNMP環境を更新します"
+ send_stats "LDNMP環境を更新する"
+ echo "LDNMP環境を更新する"
echo "------------------------"
ldnmp_v
- echo "コンポーネントの新しいバージョンを発見します"
+ echo "新しいバージョンのコンポーネントが見つかりました"
echo "------------------------"
check_docker_image_update nginx
if [ -n "$update_status" ]; then
@@ -8264,13 +9936,13 @@ linux_ldnmp() {
fi
echo "------------------------"
echo
- echo "1。更新nginx2。mysql3を更新します。php4を更新します。redisを更新します"
+ echo "1. nginx を更新します。 2. mysql を更新します。 3. php を更新します。 4. redis を更新します。"
echo "------------------------"
- echo "5。完全な環境を更新します"
+ echo "5. 環境全体を更新する"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
nginx_upgrade
@@ -8279,7 +9951,7 @@ linux_ldnmp() {
2)
local ldnmp_pods="mysql"
- read -e -p "入力してください${ldnmp_pods}バージョン番号(8.0 8.3 8.4 9.0など)(最新バージョンを取得するには入力):" version
+ read -e -p "入力してください${ldnmp_pods}バージョン番号 (例: 8.0 8.3 8.4 9.0) (Enter キーを押して最新バージョンを取得します):" version
local version=${version:-latest}
cd /home/web/
@@ -8290,13 +9962,13 @@ linux_ldnmp() {
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods
cp /home/web/docker-compose1.yml /home/web/docker-compose.yml
- send_stats "更新します$ldnmp_pods"
- echo "更新します${ldnmp_pods}仕上げる"
+ send_stats "更新する$ldnmp_pods"
+ echo "更新する${ldnmp_pods}仕上げる"
;;
3)
local ldnmp_pods="php"
- read -e -p "入力してください${ldnmp_pods}バージョン番号(7.4 8.0 8.1 8.2 8.3)(最新バージョンを入手するには入力):" version
+ read -e -p "入力してください${ldnmp_pods}バージョン番号 (例: 7.4 8.0 8.1 8.2 8.3) (Enter キーを押して最新バージョンを取得します):" version
local version=${version:-8.3}
cd /home/web/
cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
@@ -8329,8 +10001,8 @@ linux_ldnmp() {
docker restart $ldnmp_pods > /dev/null 2>&1
cp /home/web/docker-compose1.yml /home/web/docker-compose.yml
- send_stats "更新します$ldnmp_pods"
- echo "更新します${ldnmp_pods}仕上げる"
+ send_stats "更新する$ldnmp_pods"
+ echo "更新する${ldnmp_pods}仕上げる"
;;
4)
@@ -8340,20 +10012,18 @@ linux_ldnmp() {
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods > /dev/null 2>&1
- restart_redis
- send_stats "更新します$ldnmp_pods"
- echo "更新します${ldnmp_pods}仕上げる"
+ send_stats "更新する$ldnmp_pods"
+ echo "更新する${ldnmp_pods}仕上げる"
;;
5)
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}长时间不更新环境的用户,请慎重更新LDNMP环境,会有数据库更新失败的风险。确定更新LDNMP环境吗?(Y/N): ")" choice
case "$choice" in
[Yy])
- send_stats "LDNMP環境を完全に更新します"
+ send_stats "LDNMP環境の完全アップデート"
cd /home/web/
docker compose down --rmi all
- check_port
install_dependency
install_docker
install_certbot
@@ -8375,7 +10045,7 @@ linux_ldnmp() {
38)
root_use
- send_stats "LDNMP環境をアンインストールします"
+ send_stats "LDNMP環境をアンインストールする"
read -e -p "$(echo -e "${gl_hong}强烈建议:${gl_bai}先备份全部网站数据,再卸载LDNMP环境。确定删除所有网站数据吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -8389,7 +10059,7 @@ linux_ldnmp() {
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -8399,7 +10069,7 @@ linux_ldnmp() {
;;
*)
- echo "無効な入力!"
+ echo "無効な入力です!"
esac
break_end
@@ -8409,2266 +10079,9413 @@ linux_ldnmp() {
-linux_panel() {
- while true; do
- clear
- # send_stats「アプリマーケット」
- echo -e "アプリケーション市場"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Baotaパネルの公式バージョン${gl_kjlan}2. ${gl_bai}Aapanel International Edition"
- echo -e "${gl_kjlan}3. ${gl_bai}1パネルの新世代管理パネル${gl_kjlan}4. ${gl_bai}nginxproxymanagerビジュアルパネル"
- echo -e "${gl_kjlan}5. ${gl_bai}OpenListマルチストアファイルリストプログラム${gl_kjlan}6. ${gl_bai}UbuntuリモートデスクトップWebエディション"
- echo -e "${gl_kjlan}7. ${gl_bai}Nezha Probe VPS監視パネル${gl_kjlan}8. ${gl_bai}QBオフラインBT磁気ダウンロードパネル"
- echo -e "${gl_kjlan}9. ${gl_bai}poste.ioメールサーバープログラム${gl_kjlan}10. ${gl_bai}Rocketchatマルチプレイヤーオンラインチャットシステム"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}Zendaoプロジェクト管理ソフトウェア${gl_kjlan}12. ${gl_bai}Qinglongパネルの時限タスク管理プラットフォーム"
- echo -e "${gl_kjlan}13. ${gl_bai}CloudReveネットワークディスク${gl_huang}★${gl_bai} ${gl_kjlan}14. ${gl_bai}シンプルな写真ベッド画像管理プログラム"
- echo -e "${gl_kjlan}15. ${gl_bai}Emby Multimedia Management System${gl_kjlan}16. ${gl_bai}SpeedTest速度テストパネル"
- echo -e "${gl_kjlan}17. ${gl_bai}AdGuardhomeアドウェア${gl_kjlan}18. ${gl_bai}唯一のオフィスオンラインオフィスオフィス"
- echo -e "${gl_kjlan}19. ${gl_bai}サンダープールWAFファイアウォールパネル${gl_kjlan}20. ${gl_bai}Portainerコンテナ管理パネル"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}vscode webバージョン${gl_kjlan}22. ${gl_bai}UptimeKuma監視ツール"
- echo -e "${gl_kjlan}23. ${gl_bai}メモWebページメモ${gl_kjlan}24. ${gl_bai}webtopリモートデスクトップWebエディション${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}25. ${gl_bai}NextCloudネットワークディスク${gl_kjlan}26. ${gl_bai}QD-Todayタイミングタスク管理フレームワーク"
- echo -e "${gl_kjlan}27. ${gl_bai}Dockge Container Stack Managementパネル${gl_kjlan}28. ${gl_bai}librespeed速度テストツール"
- echo -e "${gl_kjlan}29. ${gl_bai}searxng集約検索サイト${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}フォトプリズムプライベートアルバムシステム"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}StirlingPDFツールコレクション${gl_kjlan}32. ${gl_bai}Drawio無料のオンラインチャートソフトウェア${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}サンパネルナビゲーションパネル${gl_kjlan}34. ${gl_bai}Pingvin-Shareファイル共有プラットフォーム"
- echo -e "${gl_kjlan}35. ${gl_bai}友達のミニマリストのサークル${gl_kjlan}36. ${gl_bai}Lobechataiチャット集約Webサイト"
- echo -e "${gl_kjlan}37. ${gl_bai}MyIPツールボックス${gl_huang}★${gl_bai} ${gl_kjlan}38. ${gl_bai}Xiaoya alistファミリーバケット"
- echo -e "${gl_kjlan}39. ${gl_bai}Bililive Live Broadcast Recording Tool${gl_kjlan}40. ${gl_bai}WebSH WebバージョンSSH接続ツール"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}マウス管理パネル${gl_kjlan}42. ${gl_bai}NEXTEリモート接続ツール"
- echo -e "${gl_kjlan}43. ${gl_bai}Rustdeskリモートデスク(サーバー)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${gl_bai}Rustdeskリモートデスク(リレー)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}45. ${gl_bai}Docker加速ステーション${gl_kjlan}46. ${gl_bai}GitHubアクセラレーションステーション${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}47. ${gl_bai}プロメテウス監視${gl_kjlan}48. ${gl_bai}プロメテウス(ホスト監視)"
- echo -e "${gl_kjlan}49. ${gl_bai}プロメテウス(コンテナ監視)${gl_kjlan}50. ${gl_bai}補充監視ツール"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}51. ${gl_bai}PVEチキンパネル${gl_kjlan}52. ${gl_bai}dPanelコンテナ管理パネル"
- echo -e "${gl_kjlan}53. ${gl_bai}llama3チャットAIモデル${gl_kjlan}54. ${gl_bai}AMHホストWebサイトビルディングマネジメントパネル"
- echo -e "${gl_kjlan}55. ${gl_bai}FRPイントラネット浸透(サーバー側)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${gl_bai}FRPイントラネット浸透(クライアント)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}57. ${gl_bai}deepseekチャットaiビッグモデル${gl_kjlan}58. ${gl_bai}Dify Big Model Knowledge Base${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}59. ${gl_bai}Newapi Big Model Asset Management${gl_kjlan}60. ${gl_bai}Jumpserverオープンソースバス剤マシン"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}61. ${gl_bai}オンライン翻訳サーバー${gl_kjlan}62. ${gl_bai}Ragflow Big Model Knowledge Base"
- echo -e "${gl_kjlan}63. ${gl_bai}OpenWebui自己ホストAIプラットフォーム${gl_huang}★${gl_bai} ${gl_kjlan}64. ${gl_bai}IT-Toolsツールボックス"
- echo -e "${gl_kjlan}65. ${gl_bai}N8Nオートメーションワークフロープラットフォーム${gl_huang}★${gl_bai} ${gl_kjlan}66. ${gl_bai}YT-DLPビデオダウンロードツール"
- echo -e "${gl_kjlan}67. ${gl_bai}DDNS-GOダイナミックDNS管理ツール${gl_huang}★${gl_bai} ${gl_kjlan}68. ${gl_bai}AllinsSL証明書管理プラットフォーム"
- echo -e "${gl_kjlan}69. ${gl_bai}SFTPGOファイル転送ツール${gl_kjlan}70. ${gl_bai}アストロボットチャットロボットフレームワーク"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}71. ${gl_bai}Navidromeプライベートミュージックサーバー${gl_kjlan}72. ${gl_bai}Bitwardenパスワードマネージャー${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}73. ${gl_bai}libretvプライベート映画とテレビ${gl_kjlan}74. ${gl_bai}MOONTVプライベート映画"
- echo -e "${gl_kjlan}75. ${gl_bai}メロディーミュージックエルフ${gl_kjlan}76. ${gl_bai}オンラインDOS古いゲーム"
- echo -e "${gl_kjlan}77. ${gl_bai}サンダーオフラインダウンロードツール${gl_kjlan}78. ${gl_bai}Pandawikiインテリジェントドキュメント管理システム"
- echo -e "${gl_kjlan}79. ${gl_bai}Beszelサーバーの監視"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
- case $sub_choice in
- 1)
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="宝塔面板"
- local panelurl="https://www.bt.cn/new/index.html"
+moltbot_menu() {
+ local app_id="114"
- panel_app_install() {
- if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
- }
+ send_stats "クロードボット/モルトボットの管理"
- panel_app_manage() {
- bt
- }
+ check_openclaw_update() {
+ if ! command -v npm >/dev/null 2>&1; then
+ return 1
+ fi
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ # --no-update-notifier を追加し、エラー リダイレクトが正しい場所にあることを確認します。
+ local_version=$(npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null | grep openclaw | awk '{print $NF}' | sed 's/^.*@//')
- install_panel
+ if [ -z "$local_version" ]; then
+ return 1
+ fi
+ remote_version=$(npm view openclaw version --no-update-notifier 2>/dev/null)
+ if [ -z "$remote_version" ]; then
+ return 1
+ fi
- ;;
- 2)
-
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="aapanel"
- local panelurl="https://www.aapanel.com/new/index.html"
-
- panel_app_install() {
- URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
- }
-
- panel_app_manage() {
- bt
- }
-
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
-
- install_panel
-
- ;;
- 3)
-
- local lujing="command -v 1pctl"
- local panelname="1Panel"
- local panelurl="https://1panel.cn/"
+ if [ "$local_version" != "$remote_version" ]; then
+ echo "${gl_huang}新しいバージョンが検出されました:$remote_version${gl_bai}"
+ else
+ echo "${gl_lv}現在のバージョンは最新です:$local_version${gl_bai}"
+ fi
+ }
- panel_app_install() {
- install bash
- bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
- }
- panel_app_manage() {
- 1pctl user-info
- 1pctl update password
- }
+ get_install_status() {
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "${gl_lv}インストール済み${gl_bai}"
+ else
+ echo "${gl_hui}インストールされていません${gl_bai}"
+ fi
+ }
- panel_app_uninstall() {
- 1pctl uninstall
- }
+ get_running_status() {
+ if pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
+ echo "${gl_lv}ランニング${gl_bai}"
+ else
+ echo "${gl_hui}稼働していない${gl_bai}"
+ fi
+ }
- install_panel
- ;;
- 4)
+ show_menu() {
- local docker_name="npm"
- local docker_img="jc21/nginx-proxy-manager:latest"
- local docker_port=81
- docker_rum() {
+ clear
- docker run -d \
- --name=$docker_name \
- -p ${docker_port}:81 \
- -p 80:80 \
- -p 443:443 \
- -v /home/docker/npm/data:/data \
- -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
- --restart=always \
- $docker_img
+ local install_status=$(get_install_status)
+ local running_status=$(get_running_status)
+ local update_message=$(check_openclaw_update)
+
+ echo "======================================="
+ echo -e "🦞 KEJILION による OPENCLAW 管理ツール 🦞"
+ echo -e "💡 ターミナル実行 \033[1;33mk 爪\033[0m ですぐにメニューに入ります"
+ echo -e "$install_status $running_status $update_message"
+ echo "======================================="
+ echo "1. インストール"
+ echo "2.スタート"
+ echo "3. 停止"
+ echo "--------------------"
+ echo "4. ステータスログの表示"
+ echo "5.機種変更"
+ echo "6. API管理"
+ echo "7. ロボットの接続とドッキング"
+ echo "8. プラグイン管理(インストール・削除)"
+ echo "9. スキル管理(設置・撤去)"
+ echo "10. メイン設定ファイルを編集します"
+ echo "11. 設定ウィザード"
+ echo "12. 健全性の検出と修復"
+ echo "13. WebUI へのアクセスと設定"
+ echo "14. TUIコマンドラインダイアログウィンドウ"
+ echo "15. 記憶・記憶"
+ echo "16. 権限管理"
+ echo "17. マルチエージェント管理"
+ echo "--------------------"
+ echo "18. バックアップと復元"
+ echo "19. アップデート"
+ echo "20. アンインストール"
+ echo "--------------------"
+ echo "0. 前のメニューに戻る"
+ echo "--------------------"
+ printf "オプションを入力して Enter を押してください:"
+ }
- }
+ start_gateway() {
+ openclaw gateway stop
+ openclaw gateway start
+ sleep 3
+ }
- local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
- local docker_url="官网介绍: https://nginxproxymanager.com/"
- local docker_use="echo \"初始用户名: admin@example.com\""
- local docker_passwd="echo \"初始密码: changeme\""
- local app_size="1"
- docker_app
+ install_node_and_tools() {
+ if command -v dnf &>/dev/null; then
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
+ dnf update -y
+ dnf group install -y "Development Tools" "Development Libraries"
+ dnf install -y cmake libatomic nodejs
+ fi
- ;;
+ if command -v apt &>/dev/null; then
+ curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
+ apt update -y
+ apt install build-essential python3 libatomic1 nodejs -y
+ fi
+ }
- 5)
+ sync_openclaw_api_models() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ [ ! -f "$config_file" ] && return 0
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$ENABLE_STATS" "$sh_v" <<'PY'
+import copy
+import json
+import os
+import platform
+import sys
+import time
+import urllib.request
+from datetime import datetime, timezone
+
+path = sys.argv[1]
+stats_enabled = (sys.argv[2].lower() == "true") if len(sys.argv) > 2 else True
+script_version = sys.argv[3] if len(sys.argv) > 3 else ""
+
+def send_stat(action):
+ if not stats_enabled:
+ return
+ payload = {
+ "action": action,
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
+ "country": "",
+ "os_info": platform.platform(),
+ "cpu_arch": platform.machine(),
+ "version": script_version,
+ }
+ try:
+ req = urllib.request.Request(
+ "https://api.kejilion.pro/api/log",
+ data=json.dumps(payload).encode("utf-8"),
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=3):
+ pass
+ except Exception:
+ pass
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('ℹ️ API プロバイダーが検出されず、モデルの同期がスキップされました')
+ raise SystemExit(0)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+changed = False
+fatal_errors = []
+summary = []
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+def prompt_delete_provider(name):
+ prompt = f"⚠️ {name} /models プローブは 3 回連続で失敗しました。この API プロバイダーとすべての関連モデルを削除しますか? [y/N]:"
+ try:
+ ans = input(prompt).strip().lower()
+ except EOFError:
+ return False
+ return ans in ('y', 'yes')
+
+
+def rebind_defaults_before_delete(name):
+ global changed
+
+ replacement = None
+
+ def get_replacement():
+ nonlocal replacement
+ if replacement is None:
+ candidates = collect_available_refs(exclude_provider=name)
+ replacement = candidates[0] if candidates else None
+ return replacement
+
+ primary_ref = get_primary_ref(defaults)
+ if ref_provider(primary_ref) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: デフォルトのメイン モデルはこのプロバイダーを指していますが、利用可能な代替モデルがないため、削除は中止されました。')
+ return False
+ set_primary_ref(defaults, repl)
+ changed = True
+ summary.append(f'🔁 デフォルトのプライマリ モデルは削除前に切り替えられています: {primary_ref} -> {repl}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: {fk} はプロバイダーを指しますが、使用可能な代替モデルがないため、削除は中止されました')
+ return False
+ defaults[fk] = repl
+ changed = True
+ summary.append(f'🔁 削除前に切り替えられた {fk}: {val} -> {repl}')
+
+ return True
+
+
+def delete_provider_and_refs(name):
+ global changed
+
+ if not rebind_defaults_before_delete(name):
+ return False
+
+ removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+ for r in removed_refs:
+ defaults_models.pop(r, None)
+ if removed_refs:
+ changed = True
+
+ if name in providers:
+ providers.pop(name, None)
+ changed = True
+
+ summary.append(f'🗑️ プロバイダー {name} が削除され、defaults.models の下の {len(removed_refs)} モデル参照が削除されました')
+ return True
+
+
+def fetch_remote_models_with_retry(name, base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ data = json.loads(payload)
+ return data, None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+for name, provider in list(providers.items()):
+ if not isinstance(provider, dict):
+ summary.append(f'ℹ️ {name} をスキップ: プロバイダー構造が不正です')
+ continue
+
+ api = provider.get('api', '')
+ base_url = provider.get('baseUrl')
+ api_key = provider.get('apiKey')
+ model_list = provider.get('models', [])
+
+ if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ summary.append(f'ℹ️ {name} をスキップ: なしbaseUrl/apiKey/models')
+ continue
+
+ if api not in SUPPORTED_APIS:
+ summary.append(f'🔁 {name}: 不正なプロトコル {api または "(unset)"} が見つかりました。再検出されます')
+ provider['api'] = ''
+ api = ''
+ changed = True
+
+ data, err, attempts = fetch_remote_models_with_retry(name, base_url, api_key, retries=3)
+ if err is not None:
+ summary.append(f'⚠️ {name}: /models の検出に失敗しました。{attempts} 回再試行しました ({type(err).__name__}: {err})')
+ send_stat('OpenClaw API は介入を確認しました')
+ if prompt_delete_provider(name):
+ deleted = delete_provider_and_refs(name)
+ if deleted:
+ send_stat('OpenClaw API の削除に失敗しましたプロバイダー確認')
+ summary.append(f'✅ {name}: ユーザーはプロバイダーと関連するすべてのモデル参照を削除することを確認しました')
+ else:
+ send_stat('OpenClaw API の削除に失敗しましたプロバイダーによって拒否されました')
+ summary.append(f'ℹ️ {name}: ユーザーは削除を確認しておらず、既存のプロバイダー構成を保持しています。')
+ continue
+
+ if attempts > 1:
+ summary.append(f'🔁 {name}: /models {attempts} 回の再試行後に成功しました')
+
+ if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ summary.append(f'⚠️ {name} をスキップ: /models 戻り構造が認識されません')
+ continue
+
+ remote_ids = []
+ for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+ remote_set = set(remote_ids)
+
+ if not remote_set:
+ fatal_errors.append(f'❌ {name} の上流の /models は空であるため、このプロバイダーにボトムアップ モデルを提供できません。')
+ continue
+
+ local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+ local_ids = [str(m['id']) for m in local_models]
+ local_set = set(local_ids)
+
+ template = None
+ for m in local_models:
+ template = copy.deepcopy(m)
+ break
+ if template is None:
+ summary.append(f'⚠️ {name} をスキップ: ローカル モデルに有効なテンプレート モデルがありません')
+ continue
+
+ removed_ids = [mid for mid in local_ids if mid not in remote_set]
+ added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+ kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+ new_models = kept_models[:]
+
+ for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{name} / {mid}'
+ new_models.append(nm)
+
+ if not new_models:
+ fatal_errors.append(f'❌ {name} には同期後に使用可能なモデルがなく、デフォルト モデル/フォールバック モデルは保証されません。')
+ continue
+
+ expected_refs = {model_ref(name, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+ local_refs = {model_ref(name, mid) for mid in local_ids}
+
+ first_ref = model_ref(name, str(new_models[0]['id']))
+
+ primary_ref = get_primary_ref(defaults)
+ if isinstance(primary_ref, str) and primary_ref in (local_refs - expected_refs):
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ summary.append(f'🔁 デフォルトのモデルは完全に置き換えられました: {primary_ref} -> {first_ref}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in (local_refs - expected_refs):
+ defaults[fk] = first_ref
+ changed = True
+ summary.append(f'🔁 {fk} は完全に置き換えられました: {val} -> {first_ref}')
+
+ stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/') and r not in expected_refs]
+ for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+ for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+ if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+ summary.append(f'✅ {name}: {len(added_ids)} を追加、{len(removed_ids)} を削除、現在は {len(new_models)}')
+
+ if added_ids:
+ summary.append(f'➕ 新しいモデルを追加します ({len(added_ids)}):')
+ for mid in added_ids:
+ summary.append(f' + {mid}')
+ if removed_ids:
+ summary.append(f'➖ モデルを削除します ({len(removed_ids)}):')
+ for mid in removed_ids:
+ summary.append(f' - {mid}')
+
+
+if fatal_errors:
+ for line in summary:
+ print(line)
+ for err in fatal_errors:
+ print(err)
+ print('❌ モデルの同期に失敗しました: プロバイダーがあります。同期後、使用可能なモデルが存在しないため、書き込みは中止されました。')
+ raise SystemExit(2)
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+ for line in summary:
+ print(line)
+ print('✅ OpenClaw API モデルの整合性同期が完了し、設定が書き込まれました')
+else:
+ for line in summary:
+ print(line)
+ print('ℹ️ 同期は必要ありません: 構成はすでに上流の /models と一致しています')
+PY
+ }
- local docker_name="openlist"
- local docker_img="openlistteam/openlist:latest-aria2"
- local docker_port=5244
- docker_rum() {
- docker run -d \
- --restart=always \
- -v /home/docker/openlist:/opt/openlist/data \
- -p ${docker_port}:5244 \
- -e PUID=0 \
- -e PGID=0 \
- -e UMASK=022 \
- --name="openlist" \
- openlistteam/openlist:latest-aria2
+ install_moltbot() {
+ echo "OpenClaw のインストールを開始します..."
+ send_stats "OpenClaw のインストールを開始します..."
+ install git jq
- }
+ install_node_and_tools
+ country=$(curl -s ipinfo.io/country)
+ if [[ "$country" == "CN" || "$country" == "HK" ]]; then
+ npm config set registry https://registry.npmmirror.com
+ fi
- local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
- local docker_url="官网介绍: https://github.com/OpenListTeam/OpenList"
- local docker_use="docker exec -it openlist ./openlist admin random"
- local docker_passwd=""
- local app_size="1"
- docker_app
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
- ;;
+ npm install -g openclaw@latest
+ openclaw onboard --install-daemon
+ start_gateway
+ add_app_id
+ break_end
- 6)
+ }
- local docker_name="webtop-ubuntu"
- local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
- local docker_port=3006
- docker_rum() {
+ start_bot() {
+ echo "OpenClaw を開始しています..."
+ send_stats "OpenClaw を開始しています..."
+ start_gateway
+ break_end
+ }
+ stop_bot() {
+ echo "OpenClaw を停止します..."
+ send_stats "OpenClaw を停止します..."
+ tmux kill-session -t gateway > /dev/null 2>&1
+ openclaw gateway stop
+ break_end
+ }
- docker run -d \
- --name=webtop-ubuntu \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=ubuntu-abc \
- -e PASSWORD=ubuntuABC123 \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:ubuntu-kde
+ view_logs() {
+ echo "OpenClaw ステータス ログを表示する"
+ send_stats "OpenClaw ログを表示する"
+ openclaw status
+ openclaw gateway status
+ openclaw logs
+ break_end
+ }
- }
- local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: ubuntu-abc\""
- local docker_passwd="echo \"密码: ubuntuABC123\""
- local app_size="2"
- docker_app
+ # OpenClaw API プロトコル検出ロジックが削除されました。API タイプは自動的に検出/決定されなくなりました。
+ # 注: API タイプはユーザー (models.providers..api) によって明示的に構成され、スクリプトは推論のために /responses を呼び出そうとしなくなりました。
- ;;
- 7)
- clear
- send_stats "ネザを作る"
- local docker_name="nezha-dashboard"
- local docker_port=8008
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
- clear
- echo -e "Nezhaの監視$check_docker $update_status"
- echo "オープンソース、軽量で使いやすいサーバーの監視と操作およびメンテナンスツール"
- echo "公式ウェブサイトの建設文書:https://nezha.wiki/guide/dashboard.html"
- if docker inspect "$docker_name" &>/dev/null; then
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1。使用します"
- echo "------------------------"
- echo "0。前のメニューに戻ります"
- echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ # モデル構成 JSON の構築
+ build-openclaw-provider-models-json() {
+ local provider_name="$1"
+ local model_ids="$2"
+ local models_array="["
+ local first=true
- case $choice in
- 1)
- check_disk_space 1
- install unzip jq
- install_docker
- curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- ;;
+ while read -r model_id; do
+ [ -z "$model_id" ] && continue
+ [[ $first == false ]] && models_array+=","
+ first=false
- *)
- break
- ;;
+ local context_window=1048576
+ local max_tokens=128000
+ local input_cost=0.15
+ local output_cost=0.60
- esac
- break_end
- done
- ;;
+ case "$model_id" in
+ *opus*|*pro*|*preview*|*thinking*|*sonnet*)
+ input_cost=2.00
+ output_cost=12.00
+ ;;
+ *gpt-5*|*codex*)
+ input_cost=1.25
+ output_cost=10.00
+ ;;
+ *flash*|*lite*|*haiku*|*mini*|*nano*)
+ input_cost=0.10
+ output_cost=0.40
+ ;;
+ esac
- 8)
+ models_array+=$(cat < 0 then
+ .["\($prov)/\($mid)"] //= {}
else
- echo -e "${gl_hong}ポート$port現在利用できません${gl_bai}"
- fi
- echo ""
+ .
+ end
+ )
+ )
+ ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
+ }
- if docker inspect "$docker_name" &>/dev/null; then
- yuming=$(cat /home/docker/mail.txt)
- echo "アクセスアドレス:"
- echo "https://$yuming"
- fi
+ # コア機能: すべてのモデルの取得と追加
+ add-all-models-from-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
- echo "------------------------"
- echo "1。インストール2。更新3。アンインストール"
- echo "------------------------"
- echo "0。前のメニューに戻ります"
- echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ echo "🔍 取得中$provider_name利用可能なすべてのモデル..."
- case $choice in
- 1)
- check_disk_space 2
- read -e -p "たとえば、mail.yuming.comなど、電子メールドメイン名を設定してください。" yuming
- mkdir -p /home/docker
- echo "$yuming" > /home/docker/mail.txt
- echo "------------------------"
- ip_address
- echo "これらのDNSレコードを最初に解析します"
- echo "A mail $ipv4_address"
- echo "CNAME imap $yuming"
- echo "CNAME pop $yuming"
- echo "CNAME smtp $yuming"
- echo "MX @ $yuming"
- echo "TXT @ v=spf1 mx ~all"
- echo "TXT ? ?"
- echo ""
- echo "------------------------"
- echo "任意のキーを押して続行します..."
- read -n 1 -s -r -p ""
+ local models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
- install jq
- install_docker
+ if [[ -z "$models_json" ]]; then
+ echo "❌ モデルリストを取得できません"
+ return 1
+ fi
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.io
+ local model_ids=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+')
- clear
- echo "Poste.ioがインストールされています"
- echo "------------------------"
- echo "次のアドレスを使用してposte.ioにアクセスできます。"
- echo "https://$yuming"
- echo ""
+ if [[ -z "$model_ids" ]]; then
+ echo "❌ モデルが見つかりません"
+ return 1
+ fi
- ;;
+ local model_count=$(echo "$model_ids" | wc -l)
+ echo "✅ 発見する$model_countモデル"
- 2)
- docker rm -f mailserver
- docker rmi -f analogic/poste.i
- yuming=$(cat /home/docker/mail.txt)
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.i
- clear
- echo "Poste.ioがインストールされています"
- echo "------------------------"
- echo "次のアドレスを使用してposte.ioにアクセスできます。"
- echo "https://$yuming"
- echo ""
- ;;
- 3)
- docker rm -f mailserver
- docker rmi -f analogic/poste.io
- rm /home/docker/mail.txt
- rm -rf /home/docker/mail
- echo "アプリはアンインストールされています"
- ;;
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$model_ids")
- *)
- break
- ;;
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
- esac
- break_end
- done
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 正常に追加されました$model_countモデルが到着$provider_name"
+ echo "📦 モデル参照形式:$provider_name/"
+ return 0
+ else
+ echo "❌ 構成の挿入に失敗しました"
+ return 1
+ fi
+ }
- ;;
+ # デフォルトのモデルのみを追加し、プロバイダーはそのままにします
+ add-default-model-only-to-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+ local default_model="$4"
- 10)
+ if [[ -z "$default_model" ]]; then
+ echo "❌ デフォルトのモデルを空にすることはできません"
+ return 1
+ fi
- local app_name="Rocket.Chat聊天系统"
- local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
- local app_url="官方介绍: https://www.rocket.chat/"
- local docker_name="rocketchat"
- local docker_port="3897"
- local app_size="2"
-
- docker_app_install() {
- docker run --name db -d --restart=always \
- -v /home/docker/mongo/dump:/dump \
- mongo:latest --replSet rs5 --oplogSize 256
- sleep 1
- docker exec -it db mongosh --eval "printjson(rs.initiate())"
- sleep 5
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$default_model")
- clear
- ip_address
- echo "インストール"
- check_docker_app_ip
- }
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
- docker_app_update() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat:latest
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "Rocket.chatがインストールされています"
- check_docker_app_ip
- }
+ if [[ $? -eq 0 ]]; then
+ echo "✅ プロバイダーが追加しました:$provider_name"
+ echo "✅ デフォルトのモデルにのみ書き込みます:$default_model"
+ return 0
+ else
+ echo "❌ 構成の挿入に失敗しました"
+ return 1
+ fi
+ }
- docker_app_uninstall() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat
- docker rm -f db
- docker rmi -f mongo:latest
- rm -rf /home/docker/mongo
- echo "アプリはアンインストールされています"
- }
+ add-openclaw-provider-interactive() {
+ send_stats "OpenClaw API が追加されました"
+ echo "=== OpenClaw プロバイダー (フルモデル) を対話的に追加 ==="
- docker_app_plus
- ;;
+ # 1.プロバイダ名
+ read -erp "プロバイダー名を入力してください (例: deepseek):" provider_name
+ while [[ -z "$provider_name" ]]; do
+ echo "❌ プロバイダー名を空にすることはできません"
+ read -erp "プロバイダー名を入力してください:" provider_name
+ done
+ # 2. Base URL
+ read -erp "ベース URL (例: https://api.xxx.com/v1) を入力してください:" base_url
+ while [[ -z "$base_url" ]]; do
+ echo "❌ ベース URL を空にすることはできません"
+ read -erp "ベース URL を入力してください:" base_url
+ done
+ base_url="${base_url%/}"
+ # 3. API Key
+ read -rsp "API キーを入力してください (入力は表示されません):" api_key
+ echo
+ while [[ -z "$api_key" ]]; do
+ echo "❌ API キーを空にすることはできません"
+ read -rsp "API キーを入力してください:" api_key
+ echo
+ done
- 11)
- local docker_name="zentao-server"
- local docker_img="idoop/zentao:latest"
- local docker_port=82
+ # 4. API タイプを検出/決定しなくなりました。プロトコルはユーザー自身によって選択および維持されます
+
+ # 5. モデルリストの取得
+ echo "🔍 利用可能なモデルのリストを取得中..."
+ models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -n "$models_json" ]]; then
+ available_models=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+' | sort)
+
+ if [[ -n "$available_models" ]]; then
+ model_count=$(echo "$available_models" | wc -l)
+ echo "✅ 発見する$model_count利用可能なモデル:"
+ echo "--------------------------------"
+ # すべて表示 (シリアル番号付き)
+ i=1
+ model_list=()
+ while read -r model; do
+ echo "[$i] $model"
+ model_list+=("$model")
+ ((i++))
+ done <<< "$available_models"
+ echo "--------------------------------"
+ fi
+ fi
+ # 5. デフォルトのモデルを選択します
+ echo
+ read -erp "デフォルトのモデル ID (またはシリアル番号、最初のものを使用する場合は空白のままにしておきます) を入力してください:" input_model
+
+ if [[ -z "$input_model" && -n "$available_models" ]]; then
+ default_model=$(echo "$available_models" | head -1)
+ echo "🎯 最初のモデルの使用:$default_model"
+ elif [[ "$input_model" =~ ^[0-9]+$ ]] && [ "${#model_list[@]}" -gt 0 ] && [ "$input_model" -ge 1 ] && [ "$input_model" -le "${#model_list[@]}" ]; then
+ default_model="${model_list[$((input_model-1))]}"
+ echo "🎯 選択されたモデル:$default_model"
+ else
+ default_model="$input_model"
+ fi
- docker_rum() {
+ # 6. 情報の確認
+ echo
+ echo "====== 確認 ======"
+ echo "Provider : $provider_name"
+ echo "Base URL : $base_url"
+ echo "API Key : ${api_key:0:8}****"
+ echo "デフォルトのモデル:$default_model"
+ echo "モデルの総数:$model_count"
+ echo "======================"
+
+ read -erp "他の利用可能なモデルをすべて同時に追加しますか? (y/N):" confirm
+
+ install jq
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ add-all-models-from-provider "$provider_name" "$base_url" "$api_key"
+ add_result=$?
+ finish_msg="✅完了!全て$model_countロードされたモデル"
+ else
+ add-default-model-only-to-provider "$provider_name" "$base_url" "$api_key" "$default_model"
+ add_result=$?
+ finish_msg="✅完了!プロバイダーは保持され、デフォルトのモデルのみがロードされます。$default_model"
+ fi
+ if [[ $add_result -eq 0 ]]; then
+ echo
+ echo "🔄 デフォルトのモデルを設定し、ゲートウェイを再起動します..."
+ openclaw models set "$provider_name/$default_model"
+ openclaw_sync_sessions_model "$provider_name/$default_model"
+ start_gateway
+ echo "$finish_msg"
+ echo "✅ 現在の API プロトコルの種類:$DETECTED_API"
+ fi
- docker run -d -p ${docker_port}:80 \
- -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
- -e BIND_ADDRESS="false" \
- -v /home/docker/zentao-server/:/opt/zbox/ \
- --add-host smtp.exmail.qq.com:163.177.90.125 \
- --name zentao-server \
- --restart=always \
- idoop/zentao:latest
+ break_end
+ }
- }
- local docker_describe="禅道是通用的项目管理软件"
- local docker_url="官网介绍: https://www.zentao.net/"
- local docker_use="echo \"初始用户名: admin\""
- local docker_passwd="echo \"初始密码: 123456\""
- local app_size="2"
- docker_app
+openclaw_api_manage_list() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API リスト"
- ;;
+ while IFS=$'\t' read -r rec_type idx name base_url model_count api_type latency_txt latency_level; do
+ case "$rec_type" in
+ MSG)
+ echo "$idx"
+ ;;
+ ROW)
+ local latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
- 12)
- local docker_name="qinglong"
- local docker_img="whyour/qinglong:latest"
- local docker_port=5700
+ printf '%b\n' "[$idx] ${name} | API: ${base_url}|合意:${api_type}|モデル数:${gl_huang}${model_count}${gl_bai}|遅延/ステータス:${latency_color}${latency_txt}${gl_bai}"
+ ;;
+ esac
+ done < <(python3 - "$config_file" <<-'PY'
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+
+def ping_models(base_url, api_key):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'OpenClaw-API-Manage/1.0',
+ },
+ )
+ start = time.perf_counter()
+ with urllib.request.urlopen(req, timeout=4) as resp:
+ resp.read(2048)
+ return int((time.perf_counter() - start) * 1000)
+
+
+def classify_latency(latency):
+ if latency == '利用不可':
+ return '利用不可', 'unavailable'
+ if latency == '検出されませんでした':
+ return '検出されませんでした', 'unchecked'
+ if isinstance(latency, int):
+ if latency <= 800:
+ level = 'low'
+ elif latency <= 2000:
+ level = 'medium'
+ else:
+ level = 'high'
+ return f'{latency}ms', level
+ return str(latency), 'unchecked'
+
+
+try:
+ with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+except FileNotFoundError:
+ print('MSG\tℹ️ openclaw.json が見つかりません。最初にインストール/初期化を完了してください。')
+ raise SystemExit(0)
+except Exception as e:
+ print(f'MSG\t❌ 構成の読み取りに失敗しました: {type(e).__name__}: {e}')
+ raise SystemExit(0)
+
+providers = ((obj.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or not providers:
+ print('MSG\tℹ️ 現在構成されている API プロバイダーはありません。')
+ raise SystemExit(0)
+
+print('MSG\t--- 構成された API リスト ---')
+
+for idx, name in enumerate(sorted(providers.keys()), start=1):
+ provider = providers.get(name)
+ if not isinstance(provider, dict):
+ base_url = '-'
+ model_count = 0
+ latency_raw = '利用不可'
+ else:
+ base_url = provider.get('baseUrl') or provider.get('url') or provider.get('endpoint') or '-'
+ models = provider.get('models') if isinstance(provider.get('models'), list) else []
+ model_count = sum(1 for m in models if isinstance(m, dict) and m.get('id'))
+ api = provider.get('api', '')
+ api_key = provider.get('apiKey')
+
+ latency_raw = '検出されませんでした'
+ if api in SUPPORTED_APIS:
+ if isinstance(base_url, str) and base_url != '-' and isinstance(api_key, str) and api_key:
+ try:
+ latency_raw = ping_models(base_url, api_key)
+ except Exception:
+ latency_raw = '利用不可'
+ else:
+ latency_raw = '利用不可'
+
+ latency_text, latency_level = classify_latency(latency_raw)
+ api_label = api if api in SUPPORTED_APIS else '-'
+ print(
+ 'ROW\t' + '\t'.join([
+ str(idx),
+ str(name),
+ str(base_url),
+ str(model_count),
+ str(api_label),
+ str(latency_text),
+ str(latency_level),
+ ])
+ )
+PY
+)
+}
+sync-openclaw-provider-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "プロバイダーによる OpenClaw API の同期"
- docker_rum() {
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 設定ファイルが見つかりません:$config_file"
+ break_end
+ return 1
+ fi
+ read -erp "同期する API 名 (プロバイダー) を入力し、Enter キーを押してすべてを同期してください。" provider_name
+ if [ -z "$provider_name" ]; then
+ if sync_openclaw_api_models; then
+ start_gateway
+ else
+ echo "❌ API モデルの同期に失敗しました。ゲートウェイの再起動は中止されました。プロバイダー/モデルを確認し、戻ってからもう一度お試しください。"
+ return 1
+ fi
+ break_end
+ return 0
+ fi
- docker run -d \
- -v /home/docker/qinglong/data:/ql/data \
- -p ${docker_port}:5700 \
- --name qinglong \
- --hostname qinglong \
- --restart unless-stopped \
- whyour/qinglong:latest
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" <<'PY2'
+import copy
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+target = sys.argv[2]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('❌ API プロバイダーが検出されず、同期できない')
+ raise SystemExit(2)
+
+provider = providers.get(target)
+if not isinstance(provider, dict):
+ print(f'❌ プロバイダー: {target} が見つかりません')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def fetch_remote_models_with_retry(base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ return json.loads(payload), None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+api = provider.get('api', '')
+base_url = provider.get('baseUrl')
+api_key = provider.get('apiKey')
+model_list = provider.get('models', [])
+
+if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ print(f'❌ プロバイダー {target} にbaseUrl/apiKey/models がないため、同期を実行できません')
+ raise SystemExit(3)
+
+if api not in SUPPORTED_APIS:
+ print(f'ℹ️ プロバイダー {target} は現在 api={api} ですが、スクリプトはプロトコルを検出/修正しなくなりました。手動で openai-completions または openai-responses に設定してください。')
+
+protocol_msg = None
+
+data, err, attempts = fetch_remote_models_with_retry(base_url, api_key, retries=3)
+if err is not None:
+ print(f'❌ {target}: /models の検出に失敗しました。{attempts} 回再試行しました ({type(err).__name__}: {err})')
+ raise SystemExit(4)
+
+if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ print(f'❌ {target}: /models 返された構造は認識されません')
+ raise SystemExit(4)
+
+remote_ids = []
+for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+remote_set = set(remote_ids)
+if not remote_set:
+ print(f'❌ {target}: 上流の /models は空です。同期は中止されました。')
+ raise SystemExit(5)
+
+local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+local_ids = [str(m['id']) for m in local_models]
+local_set = set(local_ids)
+
+template = copy.deepcopy(local_models[0]) if local_models else None
+if template is None:
+ print(f'❌ {target}: ローカル モデルには有効なテンプレート モデルがないため、新しいモデルを追加できません。')
+ raise SystemExit(3)
+
+removed_ids = [mid for mid in local_ids if mid not in remote_set]
+added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+new_models = kept_models[:]
+for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{target} / {mid}'
+ new_models.append(nm)
+
+if not new_models:
+ print(f'❌ {target}: 同期後に使用可能なモデルがないため、書き込みが中止されました')
+ raise SystemExit(5)
+
+expected_refs = {model_ref(target, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+local_refs = {model_ref(target, mid) for mid in local_ids}
+removed_refs = local_refs - expected_refs
+first_ref = model_ref(target, str(new_models[0]['id']))
+
+changed = False
+primary_ref = get_primary_ref(defaults)
+if isinstance(primary_ref, str) and primary_ref in removed_refs:
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ print(f'🔁 デフォルトのモデルは完全に置き換えられました: {primary_ref} -> {first_ref}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in removed_refs:
+ defaults[fk] = first_ref
+ changed = True
+ print(f'🔁 {fk} は完全に置き換えられました: {val} -> {first_ref}')
+
+stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(target + '/') and r not in expected_refs]
+for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ {target}: {len(added_ids)} を追加、{len(removed_ids)} を削除、現在の {len(new_models)}')
+
+if added_ids:
+ print(f'➕ 新しいモデルを追加します ({len(added_ids)}):')
+ for mid in added_ids:
+ print(f' + {mid}')
+if removed_ids:
+ print(f'➖ モデルを削除します ({len(removed_ids)}):')
+ for mid in removed_ids:
+ print(f' - {mid}')
+
+if changed:
+ print('✅ 指定されたプロバイダーモデルの整合性同期が完了し、設定が書き込まれました')
+else:
+ print('ℹ️ 同期は必要ありません。プロバイダーの構成はすでにアップストリームの /models と一致しています。')
+PY2
+ local rc=$?
+ case "$rc" in
+ 0)
+ echo "✅ 同期実行完了"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 同期に失敗しました: プロバイダーが存在しないか、構成されていません"
+ ;;
+ 3)
+ echo "❌ 同期に失敗しました: プロバイダーの構成が不完全であるか、タイプがサポートされていません"
+ ;;
+ 4)
+ echo "❌ 同期に失敗しました: アップストリーム /models リクエストが失敗しました"
+ ;;
+ 5)
+ echo "❌ 同期に失敗しました: 上流モデルが空であるか、同期後に使用可能なモデルがありません"
+ ;;
+ *)
+ echo "❌ 同期に失敗しました: 設定ファイルの構造またはログ出力を確認してください。"
+ ;;
+ esac
+ break_end
+}
- }
+openclaw_detect_api_protocol_by_provider() {
+ # プロトコル検出ロジックが削除されました。スクリプトは API タイプを自動的に検出/決定しなくなりました。
+ # この関数はメニュー呼び出しとの互換性のために残されていますが、書き換えは行われません。
+ echo "ℹ️ プロトコル検出がオフになっています: 手動で行ってください${HOME}/.openclaw/openclaw.json の Provider.api を openai-completions または openai-responses に設定します"
+ return 0
+}
- local docker_describe="青龙面板是一个定时任务管理平台"
- local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+fix-openclaw-provider-protocol-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw APIプロトコルの切り替え"
- ;;
- 13)
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 設定ファイルが見つかりません:$config_file"
+ break_end
+ return 1
+ fi
- local app_name="cloudreve网盘"
- local app_text="cloudreve是一个支持多家云存储的网盘系统"
- local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
- local docker_name="cloudreve"
- local docker_port="5212"
- local app_size="2"
-
- docker_app_install() {
- cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
- curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
- sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
- cd /home/docker/cloud/
- docker compose up -d
- clear
- echo "インストール"
- check_docker_app_ip
- }
+ read -erp "プロトコルを切り替えるための API 名 (プロバイダー) を入力してください:" provider_name
+ if [ -z "$provider_name" ]; then
+ echo "❌ プロバイダー名を空にすることはできません"
+ break_end
+ return 1
+ fi
+ echo "設定したい API タイプを選択してください:"
+ echo "1. openai-completions"
+ echo "2. openai-responses"
+ read -erp "選択肢を入力してください (1/2):" proto_choice
- docker_app_update() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- cd /home/docker/cloud/ && docker compose up -d
- }
+ local new_api=""
+ case "$proto_choice" in
+ 1) new_api="openai-completions" ;;
+ 2) new_api="openai-responses" ;;
+ *)
+ echo "❌ 選択が無効です"
+ break_end
+ return 1
+ ;;
+ esac
+ install python3 >/dev/null 2>&1
- docker_app_uninstall() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- rm -rf /home/docker/cloud
- echo "アプリはアンインストールされています"
- }
+ python3 - "$config_file" "$provider_name" "$new_api" <<'PY'
+import copy
+import json
+import sys
- docker_app_plus
- ;;
+path = sys.argv[1]
+name = sys.argv[2]
+new_api = sys.argv[3]
- 14)
- local docker_name="easyimage"
- local docker_img="ddsderek/easyimage:latest"
- local docker_port=85
- docker_rum() {
-
- docker run -d \
- --name easyimage \
- -p ${docker_port}:80 \
- -e TZ=Asia/Shanghai \
- -e PUID=1000 \
- -e PGID=1000 \
- -v /home/docker/easyimage/config:/app/web/config \
- -v /home/docker/easyimage/i:/app/web/i \
- --restart unless-stopped \
- ddsderek/easyimage:latest
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+if new_api not in SUPPORTED_APIS:
+ print('❌ 不正なプロトコル値')
+ raise SystemExit(3)
- }
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
- local docker_describe="简单图床是一个简单的图床程序"
- local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+work = copy.deepcopy(obj)
+providers = ((work.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or name not in providers or not isinstance(providers.get(name), dict):
+ print(f'❌ プロバイダー: {name} が見つかりません')
+ raise SystemExit(2)
- 15)
- local docker_name="emby"
- local docker_img="linuxserver/emby:latest"
- local docker_port=8096
+providers[name]['api'] = new_api
- docker_rum() {
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
- docker run -d --name=emby --restart=always \
- -v /home/docker/emby/config:/config \
- -v /home/docker/emby/share1:/mnt/share1 \
- -v /home/docker/emby/share2:/mnt/share2 \
- -v /mnt/notify:/mnt/notify \
- -p ${docker_port}:8096 \
- -e UID=1000 -e GID=100 -e GIDLIST=100 \
- linuxserver/emby:latest
+print(f'✅ プロバイダー {name} プロトコルが {new_api} に更新されました。')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 切り替えに失敗しました: プロバイダーが存在しないか、構成されていません"
+ ;;
+ 3)
+ echo "❌ 切り替えに失敗しました: プロトコル値が不正です"
+ ;;
+ *)
+ echo "❌ 切り替えに失敗しました: 設定ファイルの構造またはログ出力を確認してください。"
+ ;;
+ esac
- }
+ break_end
+}
+ delete-openclaw-provider-interactive() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ send_stats "OpenClaw API エントリの削除"
- local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
- local docker_url="官网介绍: https://emby.media/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 設定ファイルが見つかりません:$config_file"
+ break_end
+ return 1
+ fi
- 16)
- local docker_name="looking-glass"
- local docker_img="wikihostinc/looking-glass-server"
- local docker_port=89
+ read -erp "削除するAPI名(プロバイダー)を入力してください:" provider_name
+ if [ -z "$provider_name" ]; then
+ send_stats "OpenClaw API 削除 キャンセル"
+ echo "❌ プロバイダー名を空にすることはできません"
+ break_end
+ return 1
+ fi
+ python3 - "$config_file" "$provider_name" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or name not in providers:
+ print(f'❌ プロバイダー: {name} が見つかりません')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+replacement_candidates = collect_available_refs(exclude_provider=name)
+replacement = replacement_candidates[0] if replacement_candidates else None
+
+primary_ref = get_primary_ref(defaults)
+if ref_provider(primary_ref) == name:
+ if not replacement:
+ print('❌ 削除は中止されました: デフォルトのメイン モデルはプロバイダーを指しており、使用可能な代替モデルはありません')
+ raise SystemExit(3)
+ set_primary_ref(defaults, replacement)
+ print(f'🔁 デフォルトのプライマリ モデルの切り替え: {primary_ref} -> {replacement}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ if not replacement:
+ print(f'❌ 削除は中止されました: {fk} はプロバイダーを指しており、使用可能な代替モデルはありません')
+ raise SystemExit(3)
+ defaults[fk] = replacement
+ print(f'🔁 {fk} スイッチ: {val} -> {replacement}')
+
+removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+for r in removed_refs:
+ defaults_models.pop(r, None)
+
+providers.pop(name, None)
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'🗑️ 削除されたプロバイダー: {name}')
+print(f'🧹defaults.models 内の {len(removed_refs)} に関連するモデル参照をクリーンアップしました')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ send_stats "OpenClaw APIの削除確認"
+ echo "✅削除完了"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 削除に失敗しました: プロバイダーが存在しません"
+ ;;
+ 3)
+ send_stats "OpenClaw API 削除 キャンセル"
+ echo "❌ 削除に失敗しました: 代替モデルはありません。元の構成は維持されています。"
+ ;;
+ *)
+ echo "❌ 削除に失敗しました: 構成ファイルの構造またはログ出力を確認してください。"
+ ;;
+ esac
- docker_rum() {
+ break_end
+ }
- docker run -d --name looking-glass --restart always -p ${docker_port}:80 wikihostinc/looking-glass-server
-
- }
-
- local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
- local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
-
- ;;
- 17)
-
- local docker_name="adguardhome"
- local docker_img="adguard/adguardhome"
- local docker_port=3000
-
- docker_rum() {
-
- docker run -d \
- --name adguardhome \
- -v /home/docker/adguardhome/work:/opt/adguardhome/work \
- -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
- -p 53:53/tcp \
- -p 53:53/udp \
- -p ${docker_port}:3000/tcp \
- --restart always \
- adguard/adguardhome
+ openclaw_api_providers_showcase() {
+ send_stats "OpenClaw API ベンダーの推奨事項"
+ clear
+ echo ""
+ echo -e "${gl_kjlan}╔════════════════════════════════════════════════════════════╗${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_huang}🌟 APIベンダー推奨リスト${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_zi}一部の入り口にはAFFが含まれています${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}╚════════════════════════════════════════════════════════════╝${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● DeepSeek${gl_bai}"
+ echo -e " ${gl_kjlan}https://api-docs.deepseek.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● OpenRouter${gl_bai}"
+ echo -e " ${gl_kjlan}https://openrouter.ai/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Kimi${gl_bai}"
+ echo -e " ${gl_kjlan}https://platform.moonshot.cn/docs/guide/start-using-kimi-api${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● スーパーコンピューティング インターネット${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.scnet.cn/${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● ユユン インテリジェント コンピューティング${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://passport.compshare.cn/register?referral_code=4mscFZXfutfFi8swMVsPuf${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}●シリコンベースのフロー${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://cloud.siliconflow.cn/i/irWVdPic${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}●GLM${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.bigmodel.cn/glm-coding?ic=HYOTDOAJMR${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● PackyAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.packyapi.com/register?aff=wHri${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● クラウドAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://yunwu.ai/register?aff=ZuyK${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}●プラトンAI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://api.bltcy.ai/register?aff=TBzb114019${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● MiniMax${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.minimaxi.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● NVIDIA${gl_bai}"
+ echo -e " ${gl_kjlan}https://build.nvidia.com/settings/api-keys${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Ollama${gl_bai}"
+ echo -e " ${gl_kjlan}https://ollama.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}●白山雲${gl_bai}"
+ echo -e " ${gl_kjlan}https://ai.baishan.com/${gl_bai}"
+ echo ""
+ echo -e "${gl_kjlan}────────────────────────────────────────────────────────────${gl_bai}"
+ echo -e " ${gl_zi}伝説:${gl_lv}●正式な入り口${gl_bai} ${gl_huang}●AFF推奨入口${gl_bai}"
+ echo ""
+ echo -e "${gl_huang}ヒント: リンクをコピーし、ブラウザで開いてアクセスします。${gl_bai}"
+ echo ""
+ read -erp "戻るには Enter を押してください..." dummy
+ }
- }
+ openclaw_api_manage_menu() {
+ send_stats "OpenClaw APIの入り口"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw API 管理"
+ echo "======================================="
+ openclaw_api_manage_list
+ echo "---------------------------------------"
+ echo "1. APIの追加"
+ echo "2. APIプロバイダーモデルリストを同期する"
+ echo "3. API タイプの切り替え (完了/応答)"
+ echo "4. APIの削除"
+ echo "5. APIベンダーによる推奨"
+ echo "0.終了"
+ echo "---------------------------------------"
+ read -erp "選択肢を入力してください:" api_choice
+ case "$api_choice" in
+ 1)
+ add-openclaw-provider-interactive
+ ;;
+ 2)
+ sync-openclaw-provider-interactive
+ ;;
+ 3)
+ fix-openclaw-provider-protocol-interactive
+ ;;
+ 4)
+ delete-openclaw-provider-interactive
+ ;;
+ 5)
+ openclaw_api_providers_showcase
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
- local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
- local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update && apt install -y gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then
+ dnf install -y gum
+ else
+ yum install -y gum
+ fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive refresh
+ zypper --non-interactive install gum
+ fi
+ }
- 18)
- local docker_name="onlyoffice"
- local docker_img="onlyoffice/documentserver"
- local docker_port=8082
- docker_rum() {
+ change_model() {
+ send_stats "機種変更"
- docker run -d -p ${docker_port}:80 \
- --restart=always \
- --name onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
- onlyoffice/documentserver
+ local orange="#FF8C00"
+ openclaw_probe_status_line() {
+ local status_text="$1"
+ local status_color_ok='[32m'
+ local status_color_fail='[31m'
+ local status_color_reset='[0m'
+ if [ "$status_text" = "利用可能" ]; then
+ printf "%b最小检测结果:%s%b
+" "$status_color_ok" "$status_text" "$status_color_reset"
+ else
+ printf "%b最小检测结果:%s%b
+" "$status_color_fail" "$status_text" "$status_color_reset"
+ fi
+ }
+ openclaw_model_probe() {
+ local target_model="$1"
+ local probe_timeout=25
+ local tmp_payload tmp_response probe_result probe_status reply_preview reply_trimmed
+ local oc_config provider_name base_url api_key request_model
+ local first_endpoint second_endpoint
+ local first_exit first_http first_latency second_exit second_http second_latency
+ local first_reply second_reply
+
+ oc_config=$(openclaw_get_config_file)
+ [ ! -f "$oc_config" ] && {
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="openclaw 設定ファイルが見つかりません"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
}
- local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
- local docker_url="官网介绍: https://www.onlyoffice.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
-
- ;;
+ provider_name="${target_model%%/*}"
+ request_model="${target_model#*/}"
+ base_url=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].baseUrl // empty' "$oc_config" 2>/dev/null)
+ api_key=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].apiKey // empty' "$oc_config" 2>/dev/null)
+ if [ -z "$provider_name" ] || [ -z "$base_url" ] || [ -z "$api_key" ]; then
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="Provider/baseUrl/apiKey が読み込まれていません"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ fi
- 19)
- send_stats "サンダープールを構築します"
+ base_url="${base_url%/}"
+ first_endpoint="/responses"
+ second_endpoint="/chat/completions"
+
+ openclaw_extract_probe_reply() {
+ python3 - "$1" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+path = Path(sys.argv[1])
+raw = path.read_text(encoding='utf-8', errors='replace').strip()
+reply = ''
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get('choices') or []
+ if choices and isinstance(choices[0], dict):
+ message = choices[0].get('message') or {}
+ if isinstance(message, dict):
+ reply = message.get('content') or ''
+ if not reply:
+ output = data.get('output') or []
+ if isinstance(output, list):
+ texts = []
+ for item in output:
+ if not isinstance(item, dict):
+ continue
+ for content in item.get('content') or []:
+ if not isinstance(content, dict):
+ continue
+ text = content.get('text')
+ if isinstance(text, str) and text.strip():
+ texts.append(text.strip())
+ if texts:
+ break
+ if texts:
+ reply = ' '.join(texts)
+ if not reply:
+ for key in ('error', 'message', 'detail'):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ reply = value.strip()
+ break
+ if isinstance(value, dict):
+ nested = value.get('message')
+ if isinstance(nested, str) and nested.strip():
+ reply = nested.strip()
+ break
+ except Exception:
+ reply = raw
+reply = ' '.join(str(reply).split())
+print(reply)
+PYTHON_EOF
+ }
- local docker_name=safeline-mgt
- local docker_port=9443
- while true; do
- check_docker_app
- clear
- echo -e "サンダープールサービス$check_docker"
- echo "Lei Chiは、Changting Technologyによって開発されたWAFサイトファイアウォールプログラムパネルであり、自動防衛のために代理店サイトを逆転させることができます。"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1mz421t74c?t=0.1"
- if docker inspect "$docker_name" &>/dev/null; then
- check_docker_app_ip
+ openclaw_run_probe() {
+ local endpoint="$1"
+ tmp_payload=$(mktemp)
+ tmp_response=$(mktemp)
+ if [ "$endpoint" = "/responses" ]; then
+ printf '{"model":"%s","input":"hi","temperature":0,"max_output_tokens":16}' "$request_model" > "$tmp_payload"
+ else
+ printf '{"model":"%s","messages":[{"role":"user","content":"hi"}],"temperature":0,"max_tokens":16}' "$request_model" > "$tmp_payload"
fi
- echo ""
- echo "------------------------"
- echo "1。インストール2。更新3。パスワードのリセット4。アンインストール"
- echo "------------------------"
- echo "0。前のメニューに戻ります"
- echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
-
- case $choice in
- 1)
- install_docker
- check_disk_space 5
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
- clear
- echo "サンダープールWAFパネルがインストールされています"
- check_docker_app_ip
- docker exec safeline-mgt resetadmin
-
- ;;
+ probe_result=$(python3 - "$base_url" "$api_key" "$tmp_payload" "$tmp_response" "$probe_timeout" "$endpoint" <<'PYTHON_EOF'
+import sys
+import time
+import urllib.error
+import urllib.request
+
+base_url, api_key, payload_path, response_path, timeout, endpoint = sys.argv[1:7]
+timeout = int(timeout)
+url = base_url + endpoint
+payload = open(payload_path, 'rb').read()
+req = urllib.request.Request(
+ url,
+ data=payload,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'Bearer {api_key}',
+ },
+ method='POST',
+)
+start = time.time()
+body = b''
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, 'status', 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, 'code', 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode('utf-8', errors='replace')
+ exit_code = 1
+elapsed = int((time.time() - start) * 1000)
+with open(response_path, 'wb') as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYTHON_EOF
+)
+ probe_status=$?
+ reply_preview=$(openclaw_extract_probe_reply "$tmp_response")
+ rm -f "$tmp_payload" "$tmp_response"
+ return $probe_status
+ }
- 2)
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
- docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
- echo ""
- clear
- echo "サンダープールWAFパネルが更新されました"
- check_docker_app_ip
- ;;
- 3)
- docker exec safeline-mgt resetadmin
- ;;
- 4)
- cd /data/safeline
- docker compose down --rmi all
- echo "デフォルトのインストールディレクトリである場合、プロジェクトはアンインストールされました。インストールディレクトリをカスタマイズする場合は、インストールディレクトリにアクセスして自分で実行する必要があります。"
- echo "docker compose down && docker compose down --rmi all"
- ;;
- *)
- break
- ;;
+ openclaw_run_probe "$first_endpoint"
+ first_exit=${probe_result%%|*}
+ first_http=${probe_result#*|}
+ first_http=${first_http%%|*}
+ first_latency=${probe_result##*|}
+ first_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空のリターン)"
+
+ if [ "$first_exit" = "0" ] && [ "$first_http" -ge 200 ] && [ "$first_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http}"
+ OPENCLAW_PROBE_LATENCY="${first_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
- esac
- break_end
- done
+ openclaw_run_probe "$second_endpoint"
+ second_exit=${probe_result%%|*}
+ second_http=${probe_result#*|}
+ second_http=${second_http%%|*}
+ second_latency=${probe_result##*|}
+ second_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空のリターン)"
+
+ if [ "$second_exit" = "0" ] && [ "$second_http" -ge 200 ] && [ "$second_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint}-> HTTP ${first_http:-0}、スイッチ${second_endpoint} -> HTTP ${second_http}"
+ OPENCLAW_PROBE_LATENCY="${second_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
- ;;
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空のリターン)"
- 20)
- local docker_name="portainer"
- local docker_img="portainer/portainer"
- local docker_port=9050
+ OPENCLAW_PROBE_STATUS="FAIL"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0} / exit ${first_exit:-1};${second_endpoint} -> HTTP ${second_http:-0} / exit ${second_exit:-1}"
+ OPENCLAW_PROBE_LATENCY="${first_latency:-?}ms -> ${second_latency:-?}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 1
+ }
- docker_rum() {
+ clear
- docker run -d \
- --name portainer \
- -p ${docker_port}:9000 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/portainer:/data \
- --restart always \
- portainer/portainer
+ while true; do
+ local models_raw models_list default_model model_count selected_model confirm_switch
- }
+ # 構成ファイルからモデル キーを読み取ります (openclaw モデル リストを呼び出すことなく)
+ local oc_config
+ oc_config=$(openclaw_get_config_file)
+ models_raw=$(jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d')
+ if [ -z "$models_raw" ]; then
+ echo "モデルのリストを取得できませんでした: 構成ファイルに Agents.defaults.models が見つかりません。"
+ break_end
+ return 1
+ fi
- local docker_describe="portainer是一个轻量级的docker容器管理面板"
- local docker_url="官网介绍: https://www.portainer.io/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ # 簡単に見つけられるように各モデルに番号を付けます (例: "(10) or-api/...:free")。
+ models_list=$(echo "$models_raw" | awk '{print "(" NR ") " $0}')
+ model_count=$(echo "$models_list" | sed '/^\s*$/d' | wc -l | tr -d ' ')
- ;;
+ # 構成ファイルからデフォルトのモデルを読み取ります (高速)。失敗時に openclaw コマンドにフォールバックする
+ default_model=$(jq -r '.agents.defaults.model.primary // empty' "$oc_config" 2>/dev/null)
+ [ -z "$default_model" ] && default_model="(unknown)"
- 21)
- local docker_name="vscode-web"
- local docker_img="codercom/code-server"
- local docker_port=8180
+ clear
+ install_gum
+ install gum
- docker_rum() {
+ # ガムが存在しない場合は、元の手入力プロセスにダウングレードされます。
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "---モデル管理---"
+ echo "現在入手可能なモデル:"
+ jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d'
+ echo "----------------"
+ read -e -p "設定するモデル名を入力してください (例: openrouter/openai/gpt-4o) (終了するには 0 を入力します):" selected_model
- docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart always codercom/code-server
+ if [ "$selected_model" = "0" ]; then
+ echo "操作がキャンセルされました。終了しています..."
+ break
+ fi
- }
+ if [ -z "$selected_model" ]; then
+ echo "エラー: モデル名を空にすることはできません。もう一度試してください。"
+ echo ""
+ continue
+ fi
+ echo "切り替わる機種は以下の通りです。$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切り替えに失敗しました: openclaw モデル セットがエラーを返しました。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
- local docker_describe="VScode是一款强大的在线代码编写工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
- local docker_use="sleep 3"
- local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
- local app_size="1"
- docker_app
- ;;
- 22)
- local docker_name="uptime-kuma"
- local docker_img="louislam/uptime-kuma:latest"
- local docker_port=3003
+ break_end
+ return 0
+ else
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "ガムは使用できなくなり、古い入力モードに戻ります。"
+ sleep 1
+ continue
+ fi
+ gum style --foreground "$orange" --bold "モデル管理"
+ gum style --foreground "$orange" "利用可能なモデル (認証=はい):${model_count}"
+ gum style --foreground "$orange" "現在のデフォルト:${default_model}"
+ echo ""
+ gum style --faint "↑↓ 選択 / Enter でテスト / Esc で終了"
+ echo ""
+ selected_model=$(echo "$models_list" | gum filter --placeholder "モデルの検索 (cli-api/gpt-5.2 など)" --prompt "モデルを選択 >" --indicator "➜ " --prompt.foreground "$orange" --indicator.foreground "$orange" --cursor-text.foreground "$orange" --match.foreground "$orange" --header "" --height 35)
- docker_rum() {
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "操作がキャンセルされました。終了しています..."
+ break
+ fi
+ fi
- docker run -d \
- --name=uptime-kuma \
- -p ${docker_port}:3001 \
- -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
- --restart=always \
- louislam/uptime-kuma:latest
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
- }
+ echo ""
+ echo "検出モデル:$selected_model"
+ if openclaw_model_probe "$selected_model"; then
+ openclaw_probe_status_line "利用可能"
+ else
+ openclaw_probe_status_line "利用不可"
+ fi
+ echo "州:$OPENCLAW_PROBE_MESSAGE"
+ echo "遅れ:$OPENCLAW_PROBE_LATENCY"
+ echo "まとめ:$OPENCLAW_PROBE_REPLY"
+ echo ""
+ printf "このモデルに切り替えますか? [y/N、Esc でリストに戻る]:"
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ [nN]|"") confirm_switch="no" ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
- local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "モデル選択リストが返されました。"
+ sleep 1
+ continue
+ fi
- 23)
- local docker_name="memos"
- local docker_img="ghcr.io/usememos/memos:latest"
- local docker_port=5230
+ echo "切り替わる機種は以下の通りです。$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切り替えに失敗しました: openclaw モデル セットがエラーを返しました。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
- docker_rum() {
+ break_end
+ done
+ }
- docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart always ghcr.io/usememos/memos:latest
- }
+ openclaw_get_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
- local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
- local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ openclaw_get_agents_dir() {
+ local user_agents="${HOME}/.openclaw/agents"
+ local root_agents="/root/.openclaw/agents"
+ if [ -d "$user_agents" ]; then
+ echo "$user_agents"
+ elif [ "$HOME" = "/root" ] && [ -d "$root_agents" ]; then
+ echo "$root_agents"
+ else
+ echo "$user_agents"
+ fi
+ }
- 24)
- local docker_name="webtop"
- local docker_img="lscr.io/linuxserver/webtop:latest"
- local docker_port=3083
-
- docker_rum() {
-
-
- docker run -d \
- --name=webtop \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=webtop-abc \
- -e PASSWORD=webtopABC123 \
- -e LC_ALL=zh_CN.UTF-8 \
- -e DOCKER_MODS=linuxserver/mods:universal-package-install \
- -e INSTALL_PACKAGES=font-noto-cjk \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:latest
+ openclaw_sync_sessions_model() {
+ local model_ref="$1"
+ [ -z "$model_ref" ] && return 1
+
+ local agents_dir
+ agents_dir=$(openclaw_get_agents_dir)
+ [ ! -d "$agents_dir" ] && return 0
+
+ local provider="${model_ref%%/*}"
+ local model="${model_ref#*/}"
+ [ "$provider" = "$model_ref" ] && { provider=""; model="$model_ref"; }
+
+ local count=0
+ local agent_dir sessions_file backup_file
+
+ for agent_dir in "$agents_dir"/*/; do
+ [ ! -d "$agent_dir" ] && continue
+ sessions_file="$agent_dir/sessions/sessions.json"
+ [ ! -f "$sessions_file" ] && continue
+
+ backup_file="${sessions_file}.bak"
+ cp "$sessions_file" "$backup_file" 2>/dev/null || continue
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if [ -n "$provider" ]; then
+ jq --arg model "$model" --arg provider "$provider" \
+ 'to_entries | map(.value.modelOverride = $model | .value.providerOverride = $provider) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ else
+ jq --arg model "$model" \
+ 'to_entries | map(.value.modelOverride = $model | del(.value.providerOverride)) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ fi
+ fi
+ done
+ [ "$count" -gt 0 ] && echo "✅ 同期済み$countエージェントのセッション モデルは次のとおりです。$model_ref"
+ return 0
+ }
- }
+ resolve_openclaw_plugin_id() {
+ local raw_input="$1"
+ local plugin_id="$raw_input"
+ plugin_id="${plugin_id#@openclaw/}"
+ if [[ "$plugin_id" == @*/* ]]; then
+ plugin_id="${plugin_id##*/}"
+ fi
+ plugin_id="${plugin_id%%@*}"
+ echo "$plugin_id"
+ }
- local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: webtop-abc\""
- local docker_passwd="echo \"密码: webtopABC123\""
- local app_size="2"
- docker_app
- ;;
+ sync_openclaw_plugin_allowlist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
- 25)
- local docker_name="nextcloud"
- local docker_img="nextcloud:latest"
- local docker_port=8989
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+ local config_file
+ config_file=$(openclaw_get_config_file)
- docker_rum() {
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
- docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | if (.plugins.allow | index($pid)) == null then .plugins.allow += [$pid] else . end
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 同期された plugins.allow ホワイトリスト:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
- }
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+if plugin_id not in a:
+ a.append(plugin_id)
+
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 同期された plugins.allow ホワイトリスト:$plugin_id"
+ return 0
+ fi
+ fi
- local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
- local docker_url="官网介绍: https://nextcloud.com/"
- local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ echo "⚠️ プラグインはインストールされましたが、plugins.allow の同期に失敗しました。手動で確認してください:$config_file"
+ return 1
+ }
- 26)
- local docker_name="qd"
- local docker_img="qdtoday/qd:latest"
- local docker_port=8923
+ sync_openclaw_plugin_denylist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
- docker_rum() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
- docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
- }
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | .plugins.allow = (.plugins.allow | map(select(. != $pid)))
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ plugins.allow から削除されました:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
- local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
- local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 27)
- local docker_name="dockge"
- local docker_img="louislam/dockge:latest"
- local docker_port=5003
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+a = [x for x in a if x != plugin_id]
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ plugins.allow から削除されました:$plugin_id"
+ return 0
+ fi
+ fi
- docker_rum() {
+ echo "⚠️ plugins.allow の削除に失敗しました。手動で確認してください:$config_file"
+ return 1
+ }
- docker run -d --name dockge --restart unless-stopped -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
- }
- local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 28)
- local docker_name="speedtest"
- local docker_img="ghcr.io/librespeed/speedtest"
- local docker_port=8028
- docker_rum() {
- docker run -d -p ${docker_port}:8080 --name speedtest --restart always ghcr.io/librespeed/speedtest
+ install_plugin() {
+ send_stats "プラグイン管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo "プラグイン管理(インストール/削除)"
+ echo "========================================"
+ echo "現在のプラグインのリスト:"
+ openclaw plugins list
+ echo "--------------------------------------------------------"
+ echo "推奨される一般的に使用されるプラグイン ID (括弧内の ID をコピーするだけです):"
+ echo "--------------------------------------------------------"
+ echo "📱 コミュニケーションチャネル:"
+ echo "- [フェイシュ] #フェイシュ/ラーク統合"
+ echo "- [電報] #電報ボット"
+ echo "- [slack] #Slack コーポレートコミュニケーション"
+ echo " - [msteams] # Microsoft Teams"
+ echo "- [discord] #Discordコミュニティ運営"
+ echo "- [whatsapp] #WhatsApp オートメーション"
+ echo ""
+ echo "🧠 メモリと AI:"
+ echo "- [memory-core] # 基本メモリ (ファイル検索)"
+ echo "- [memory-lancedb] # メモリの強化 (ベクターデータベース)"
+ echo "- [copilot-proxy] # コパイロットインターフェイスの転送"
+ echo ""
+ echo "⚙️機能拡張:"
+ echo "- [lobster] # 承認フロー(手動確認あり)"
+ echo "- [voice-call] # 音声通話機能"
+ echo "- [nostr] # 暗号化されたプライベートチャット"
+ echo "--------------------------------------------------------"
+
+ echo "1) プラグインのインストール/有効化"
+ echo "2) プラグインの削除/無効化"
+ echo "0) 戻る"
+ read -e -p "アクションを選択してください:" plugin_action
+
+ [ "$plugin_action" = "0" ] && break
+ [ -z "$plugin_action" ] && continue
+
+ read -e -p "プラグイン ID を入力してください (スペースで区切って、終了するには 0 を入力してください):" raw_input
+ [ "$raw_input" = "0" ] && break
+ [ -z "$raw_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ for token in $raw_input; do
+ local plugin_id
+ local plugin_full
+ plugin_id=$(resolve_openclaw_plugin_id "$token")
+ plugin_full="$token"
+ [ -z "$plugin_id" ] && continue
+
+ if [ "$plugin_action" = "1" ]; then
+ echo "🔍 プラグインのステータスを確認する:$plugin_id"
+ local plugin_list
+ plugin_list=$(openclaw plugins list 2>/dev/null)
+
+ if echo "$plugin_list" | grep -qw "$plugin_id" && echo "$plugin_list" | grep "$plugin_id" | grep -q "disabled"; then
+ echo "💡プラグイン [$plugin_id] プリインストールされており、アクティベート中..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
- }
+ if [ -d "/usr/lib/node_modules/openclaw/extensions/$plugin_id" ]; then
+ echo "💡 プラグインがシステムの組み込みディレクトリに存在することがわかりました。直接有効にしてみてください..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
- local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
- local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ echo "📥 ローカルに見つかりません。ダウンロードしてインストールしてください:$plugin_full"
+ rm -rf "${HOME}/.openclaw/extensions/$plugin_id"
+ [ "$HOME" != "/root" ] && rm -rf "/root/.openclaw/extensions/$plugin_id"
+ if openclaw plugins install "$plugin_full"; then
+ echo "✅ ダウンロードに成功しました。アクティブ化しています..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "❌ インストールに失敗しました:$plugin_full"
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "🗑️ プラグインの削除/無効化:$plugin_id"
+ openclaw plugins disable "$plugin_id" >/dev/null 2>&1
+ if openclaw plugins uninstall "$plugin_id"; then
+ echo "✅ アンインストール済み:$plugin_id"
+ else
+ echo "⚠️ アンインストールに失敗しました。プリインストールされたプラグインである可能性があります。次の機能のみを無効にしてください。$plugin_id"
+ fi
+ sync_openclaw_plugin_denylist "$plugin_id" >/dev/null 2>&1
+ success_list="$success_list $plugin_id"
+ changed=true
+ fi
+ done
- 29)
- local docker_name="searxng"
- local docker_img="searxng/searxng"
- local docker_port=8029
+ echo ""
+ echo "====== 操作概要 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失敗:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️スキップ:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 変更をロードするために OpenClaw サービスを再起動しています..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
- docker_rum() {
- docker run -d \
- --name searxng \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -v "/home/docker/searxng:/etc/searxng" \
- searxng/searxng
+ install_skill() {
+ send_stats "スキル管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo "スキル管理(インストール/削除)"
+ echo "========================================"
+ echo "現在インストールされているスキル:"
+ openclaw skills list
+ echo "----------------------------------------"
+
+ # 推奨実践スキルリストを出力
+ echo "推奨される実践スキル (名前を直接コピーして入力できます):"
+ echo "github # GitHub の問題/PR/CI を管理する (gh CLI)"
+ echo "notion # Notion のページ、データベース、ブロックを操作する"
+ echo "apple-notes # macOS ネイティブのノート管理 (作成/編集/検索)"
+ echo "apple-reminders # macOS リマインダー管理 (ToDo リスト)"
+ echo "1password # 1Password キーの読み取りと挿入を自動化します"
+ echo "gog # Google Workspace (Gmail/クラウド ディスク/ドキュメント) 万能アシスタント"
+ echo "things-mac # Things 3 タスク管理の緊密な統合"
+ echo "bluebubbles # BlueBubbles で iMessage を完璧に送受信"
+ echo "ヒマラヤ # 端末メール管理(IMAP/SMTP強力ツール)"
+ echo "要約 # Web ページ/ポッドキャスト/YouTube ビデオ コンテンツのワンクリック要約"
+ echo "openhue # Philips Hue スマート照明シーンの制御"
+ echo "video-frames # ビデオフレーム抽出とショートクリップ編集 (ffmpeg ドライバー)"
+ echo "openai-whisper # ローカル音声をテキストに変換 (オフラインのプライバシー保護)"
+ echo "coding-agent # Claude Code/Codex などのプログラミング アシスタントを自動的に実行する"
+ echo "----------------------------------------"
+
+ echo "1) 取り付けスキル"
+ echo "2) スキルの削除"
+ echo "0) 戻る"
+ read -e -p "アクションを選択してください:" skill_action
+
+ [ "$skill_action" = "0" ] && break
+ [ -z "$skill_action" ] && continue
+
+ read -e -p "スキル名を入力してください (スペースで区切って、終了するには 0 を入力してください):" skill_input
+ [ "$skill_input" = "0" ] && break
+ [ -z "$skill_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ if [ "$skill_action" = "2" ]; then
+ read -e -p "二次確認: 削除はユーザー ディレクトリ ~/.openclaw/workspace/skills にのみ影響します。続けてもよろしいですか? (y/N):" confirm_del
+ if [[ ! "$confirm_del" =~ ^[Yy]$ ]]; then
+ echo "削除がキャンセルされました。"
+ break_end
+ continue
+ fi
+ fi
- }
+ for token in $skill_input; do
+ local skill_name
+ skill_name="$token"
+ [ -z "$skill_name" ] && continue
+
+ if [ "$skill_action" = "1" ]; then
+ local skill_found=false
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ echo "💡 スキル [$skill_name] がユーザーディレクトリにインストールされます。"
+ skill_found=true
+ elif [ -d "/usr/lib/node_modules/openclaw/skills/${skill_name}" ]; then
+ echo "💡 スキル [$skill_name]がシステムディレクトリにインストールされます。"
+ skill_found=true
+ fi
- local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
- local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ if [ "$skill_found" = true ]; then
+ read -e -p "スキル [$skill_name] すでにインストールされていますが、再インストールしますか? (y/N):" reinstall
+ if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
+ skipped_list="$skipped_list $skill_name"
+ continue
+ fi
+ fi
- 30)
- local docker_name="photoprism"
- local docker_img="photoprism/photoprism:latest"
- local docker_port=2342
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
-
- docker_rum() {
-
- docker run -d \
- --name photoprism \
- --restart always \
- --security-opt seccomp=unconfined \
- --security-opt apparmor=unconfined \
- -p ${docker_port}:2342 \
- -e PHOTOPRISM_UPLOAD_NSFW="true" \
- -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
- -v /home/docker/photoprism/storage:/photoprism/storage \
- -v /home/docker/photoprism/Pictures:/photoprism/originals \
- photoprism/photoprism
+ echo "スキルのインストール:$skill_name ..."
+ if npx clawhub install "$skill_name" --yes --no-input 2>/dev/null || npx clawhub install "$skill_name"; then
+ echo "✅ スキル$skill_nameインストールが成功しました。"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "❌ インストールに失敗しました:$skill_name"
+ failed_list="$failed_list $skill_name"
+ fi
+ else
+ echo "🗑️ スキルの削除:$skill_name"
+ npx clawhub uninstall "$skill_name" --yes --no-input 2>/dev/null || npx clawhub uninstall "$skill_name" >/dev/null 2>&1
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ rm -rf "${HOME}/.openclaw/workspace/skills/${skill_name}"
+ echo "✅ ユーザースキルディレクトリが削除されました:$skill_name"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "⏭️ ユーザー スキル ディレクトリが見つかりません:$skill_name"
+ skipped_list="$skipped_list $skill_name"
+ fi
+ fi
+ done
- }
+ echo ""
+ echo "====== 操作概要 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失敗:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️スキップ:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 変更をロードするために OpenClaw サービスを再起動しています..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+openclaw_json_get_bool() {
+ local expr="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r "$expr" "$config_file" 2>/dev/null || echo "false"
+ }
- local docker_describe="photoprism非常强大的私有相册系统"
- local docker_url="官网介绍: https://www.photoprism.app/"
- local docker_use="echo \"账号: admin 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ openclaw_channel_has_cfg() {
+ local channel="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r --arg c "$channel" '
+ (.channels[$c] // null) as $v
+ | if ($v | type) != "object" then
+ false
+ else
+ ([ $v
+ | to_entries[]
+ | select((.key == "enabled" or .key == "dmPolicy" or .key == "groupPolicy" or .key == "streaming") | not)
+ | .value
+ | select(. != null and . != "" and . != false)
+ ] | length) > 0
+ end
+ ' "$config_file" 2>/dev/null || echo "false"
+ }
+ openclaw_dir_has_files() {
+ local dir="$1"
+ [ -d "$dir" ] && find "$dir" -type f -print -quit 2>/dev/null | grep -q .
+ }
- 31)
- local docker_name="s-pdf"
- local docker_img="frooodle/s-pdf:latest"
- local docker_port=8020
-
- docker_rum() {
-
- docker run -d \
- --name s-pdf \
- --restart=always \
- -p ${docker_port}:8080 \
- -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
- -v /home/docker/s-pdf/extraConfigs:/configs \
- -v /home/docker/s-pdf/logs:/logs \
- -e DOCKER_ENABLE_SECURITY=false \
- frooodle/s-pdf:latest
- }
+ openclaw_plugin_local_installed() {
+ local plugin="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -s "$config_file" ] && jq -e --arg p "$plugin" '.plugins.installs[$p]' "$config_file" >/dev/null 2>&1; then
+ return 0
+ fi
- local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
- local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ # 2 つの一般的なディレクトリ名と互換性があります。
+ # - ~/.openclaw/extensions/qqbot
+ # - ~/.openclaw/extensions/openclaw-qqbot
+ # 無知な部分文字列を避け、完全一致と openclaw プレフィックス一致を優先します。
+ [ -d "${HOME}/.openclaw/extensions/${plugin}" ] \
+ || [ -d "${HOME}/.openclaw/extensions/openclaw-${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/openclaw-${plugin}" ]
+ }
- 32)
- local docker_name="drawio"
- local docker_img="jgraph/drawio"
- local docker_port=7080
+ openclaw_bot_status_text() {
+ local enabled="$1"
+ local configured="$2"
+ local connected="$3"
+ local abnormal="$4"
+ if [ "$abnormal" = "true" ]; then
+ echo "異常な"
+ elif [ "$enabled" != "true" ]; then
+ echo "有効になっていません"
+ elif [ "$connected" = "true" ]; then
+ echo "接続済み"
+ elif [ "$configured" = "true" ]; then
+ echo "設定済み"
+ else
+ echo "未設定"
+ fi
+ }
- docker_rum() {
+ openclaw_colorize_bot_status() {
+ local status="$1"
+ case "$status" in
+ 已连接) echo -e "${gl_lv}${status}${gl_bai}" ;;
+ 已配置) echo -e "${gl_huang}${status}${gl_bai}" ;;
+ 异常) echo -e "${gl_hong}${status}${gl_bai}" ;;
+ *) echo "$status" ;;
+ esac
+ }
- docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+ openclaw_print_bot_status_line() {
+ local label="$1"
+ local status="$2"
+ echo -e "- ${label}: $(openclaw_colorize_bot_status "$status")"
+ }
- }
+ openclaw_show_bot_local_status_block() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local json_ok="false"
+ if [ -s "$config_file" ] && jq empty "$config_file" >/dev/null 2>&1; then
+ json_ok="true"
+ fi
+ local tg_enabled tg_cfg tg_connected tg_abnormal tg_status
+ tg_enabled=$(openclaw_json_get_bool '.channels.telegram.enabled // .plugins.entries.telegram.enabled // false')
+ tg_cfg=$(openclaw_channel_has_cfg "telegram")
+ tg_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/telegram"; then
+ tg_connected="true"
+ fi
+ tg_abnormal="false"
+ if [ "$tg_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ tg_abnormal="true"
+ fi
+ tg_status=$(openclaw_bot_status_text "$tg_enabled" "$tg_cfg" "$tg_connected" "$tg_abnormal")
- local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
- local docker_url="官网介绍: https://www.drawio.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local feishu_enabled feishu_cfg feishu_connected feishu_abnormal feishu_status
+ feishu_enabled=$(openclaw_json_get_bool '.plugins.entries.feishu.enabled // .plugins.entries["openclaw-lark"].enabled // .channels.feishu.enabled // .channels.lark.enabled // false')
+ feishu_cfg=$(openclaw_channel_has_cfg "feishu")
+ if [ "$feishu_cfg" != "true" ]; then
+ feishu_cfg=$(openclaw_channel_has_cfg "lark")
+ fi
+ feishu_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/feishu" || openclaw_dir_has_files "${HOME}/.openclaw/lark" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-lark"; then
+ feishu_connected="true"
+ fi
+ feishu_abnormal="false"
+ if [ "$feishu_enabled" = "true" ] && ! openclaw_plugin_local_installed "feishu" && ! openclaw_plugin_local_installed "lark" && ! openclaw_plugin_local_installed "openclaw-lark"; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_connected" != "true" ] && [ "$feishu_enabled" = "true" ] && [ "$feishu_cfg" = "true" ] && { openclaw_plugin_local_installed "feishu" || openclaw_plugin_local_installed "lark" || openclaw_plugin_local_installed "openclaw-lark"; }; then
+ feishu_connected="true"
+ fi
+ feishu_status=$(openclaw_bot_status_text "$feishu_enabled" "$feishu_cfg" "$feishu_connected" "$feishu_abnormal")
+
+ local wa_enabled wa_cfg wa_connected wa_abnormal wa_status
+ wa_enabled=$(openclaw_json_get_bool '.plugins.entries.whatsapp.enabled // .channels.whatsapp.enabled // false')
+ wa_cfg=$(openclaw_channel_has_cfg "whatsapp")
+ wa_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/whatsapp"; then
+ wa_connected="true"
+ fi
+ wa_abnormal="false"
+ if [ "$wa_enabled" = "true" ] && ! openclaw_plugin_local_installed "whatsapp"; then
+ wa_abnormal="true"
+ fi
+ if [ "$wa_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wa_abnormal="true"
+ fi
+ wa_status=$(openclaw_bot_status_text "$wa_enabled" "$wa_cfg" "$wa_connected" "$wa_abnormal")
+
+ local dc_enabled dc_cfg dc_connected dc_abnormal dc_status
+ dc_enabled=$(openclaw_json_get_bool '.channels.discord.enabled // .plugins.entries.discord.enabled // false')
+ dc_cfg=$(openclaw_channel_has_cfg "discord")
+ dc_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/discord"; then
+ dc_connected="true"
+ fi
+ dc_abnormal="false"
+ if [ "$dc_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ dc_abnormal="true"
+ fi
+ dc_status=$(openclaw_bot_status_text "$dc_enabled" "$dc_cfg" "$dc_connected" "$dc_abnormal")
+
+ local slack_enabled slack_cfg slack_connected slack_abnormal slack_status
+ slack_enabled=$(openclaw_json_get_bool '.plugins.entries.slack.enabled // .channels.slack.enabled // false')
+ slack_cfg=$(openclaw_channel_has_cfg "slack")
+ slack_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/slack"; then
+ slack_connected="true"
+ fi
+ slack_abnormal="false"
+ if [ "$slack_enabled" = "true" ] && ! openclaw_plugin_local_installed "slack"; then
+ slack_abnormal="true"
+ fi
+ if [ "$slack_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ slack_abnormal="true"
+ fi
+ slack_status=$(openclaw_bot_status_text "$slack_enabled" "$slack_cfg" "$slack_connected" "$slack_abnormal")
+
+ local qq_enabled qq_cfg qq_connected qq_abnormal qq_status
+ qq_enabled=$(openclaw_json_get_bool '.plugins.entries.qqbot.enabled // .channels.qqbot.enabled // false')
+ qq_cfg=$(openclaw_channel_has_cfg "qqbot")
+ qq_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/qqbot/sessions" || openclaw_dir_has_files "${HOME}/.openclaw/qqbot/data"; then
+ qq_connected="true"
+ fi
+ qq_abnormal="false"
+ if [ "$qq_enabled" = "true" ] && ! openclaw_plugin_local_installed "qqbot"; then
+ qq_abnormal="true"
+ fi
+ if [ "$qq_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ qq_abnormal="true"
+ fi
+ qq_status=$(openclaw_bot_status_text "$qq_enabled" "$qq_cfg" "$qq_connected" "$qq_abnormal")
+
+ local wx_enabled wx_cfg wx_connected wx_abnormal wx_status
+ wx_enabled=$(openclaw_json_get_bool '.plugins.entries.weixin.enabled // .plugins.entries["openclaw-weixin"].enabled // .channels.weixin.enabled // .channels["openclaw-weixin"].enabled // false')
+ wx_cfg=$(openclaw_channel_has_cfg "weixin")
+ if [ "$wx_cfg" != "true" ]; then
+ wx_cfg=$(openclaw_channel_has_cfg "openclaw-weixin")
+ fi
+ wx_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/weixin" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-weixin"; then
+ wx_connected="true"
+ fi
+ wx_abnormal="false"
+ if [ "$wx_enabled" = "true" ] && ! openclaw_plugin_local_installed "weixin" && ! openclaw_plugin_local_installed "openclaw-weixin"; then
+ wx_abnormal="true"
+ fi
+ if [ "$wx_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wx_abnormal="true"
+ fi
+ wx_status=$(openclaw_bot_status_text "$wx_enabled" "$wx_cfg" "$wx_connected" "$wx_abnormal")
+
+ echo "ローカル ステータス (ローカル構成/キャッシュのみ、ネットワーク検出なし):"
+ openclaw_print_bot_status_line "Telegram" "$tg_status"
+ openclaw_print_bot_status_line "ヒバリ" "$feishu_status"
+ openclaw_print_bot_status_line "WhatsApp" "$wa_status"
+ openclaw_print_bot_status_line "Discord" "$dc_status"
+ openclaw_print_bot_status_line "Slack" "$slack_status"
+ openclaw_print_bot_status_line "QQ Bot" "$qq_status"
+ openclaw_print_bot_status_line "WeChat (微信)" "$wx_status"
+ }
+
+ change_tg_bot_code() {
+ send_stats "ロボットドッキング"
+ while true; do
+ clear
+ echo "========================================"
+ echo "ロボットの接続とドッキング"
+ echo "========================================"
+ openclaw_show_bot_local_status_block
+ echo "----------------------------------------"
+ echo "1. テレグラムロボットドッキング"
+ echo "2.フェイシュ(ヒバリ)ロボットドッキング"
+ echo "3. WhatsApp ボットのドッキング"
+ echo "4. QQロボットドッキング"
+ echo "5. WeChatロボットのドッキング"
+ echo "----------------------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "----------------------------------------"
+ read -e -p "選択肢を入力してください:" bot_choice
+
+ case $bot_choice in
+ 1)
+ read -e -p "TG ロボットが受信した接続コード (NYA99R2F など) を入力してください (終了するには 0 を入力します)。" code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "エラー: 接続コードを空にすることはできません。"; sleep 1; continue; fi
+ openclaw pairing approve telegram "$code"
+ break_end
+ ;;
+ 2)
+ npx -y @larksuite/openclaw-lark install
+ openclaw config set channels.feishu.streaming true
+ openclaw config set channels.feishu.requireMention true --json
+ break_end
+ ;;
+ 3)
+ read -e -p "WhatsApp で受信した接続コード (例: NYA99R2F) を入力してください (終了するには 0 を入力してください):" code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "エラー: 接続コードを空にすることはできません。"; sleep 1; continue; fi
+ openclaw pairing approve whatsapp "$code"
+ break_end
+ ;;
+ 4)
+ echo "QQ の公式ドッキング アドレス:"
+ echo "https://q.qq.com/qqbot/openclaw/login.html"
+ break_end
+ ;;
+ 5)
+ npx -y @tencent-weixin/openclaw-weixin-cli@latest install
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ openclaw_backup_root() {
+ echo "${HOME}/.openclaw/backups"
+ }
+
+ openclaw_is_interactive_terminal() {
+ [ -t 0 ] && [ -t 1 ]
+ }
+
+ openclaw_has_command() {
+ command -v "$1" >/dev/null 2>&1
+ }
+
+
+ openclaw_is_safe_relpath() {
+ local rel="$1"
+ [ -z "$rel" ] && return 1
+ [[ "$rel" = /* ]] && return 1
+ [[ "$rel" == *"//"* ]] && return 1
+ [[ "$rel" == *$'\n'* ]] && return 1
+ [[ "$rel" == *$'\r'* ]] && return 1
+ case "$rel" in
+ ../*|*/../*|*/..|..)
+ return 1
+ ;;
+ esac
+ return 0
+ }
+
+ openclaw_restore_path_allowed() {
+ local mode="$1"
+ local rel="$2"
+ case "$mode" in
+ memory)
+ case "$rel" in
+ MEMORY.md|AGENTS.md|USER.md|SOUL.md|TOOLS.md|memory/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ project)
+ case "$rel" in
+ openclaw.json|workspace/*|extensions/*|skills/*|prompts/*|tools/*|telegram/*|feishu/*|whatsapp/*|discord/*|slack/*|qqbot/*|logs/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ }
+
+ openclaw_pack_backup_archive() {
+ local backup_type="$1"
+ local export_mode="$2"
+ local payload_dir="$3"
+ local output_file="$4"
+
+ local tmp_root
+ tmp_root=$(mktemp -d) || return 1
+ local pack_dir="$tmp_root/package"
+ mkdir -p "$pack_dir"
+
+ cp -a "$payload_dir" "$pack_dir/payload"
+
+ (
+ cd "$pack_dir/payload" || exit 1
+ find . -type f | sed 's|^\./||' | sort > "$pack_dir/manifest.files"
+ : > "$pack_dir/manifest.sha256"
+ while IFS= read -r f; do
+ [ -z "$f" ] && continue
+ sha256sum "$f" >> "$pack_dir/manifest.sha256"
+ done < "$pack_dir/manifest.files"
+ ) || { rm -rf "$tmp_root"; return 1; }
+
+ cat > "$pack_dir/backup.meta" </dev/null
+ ) || { echo "❌ sha256 検証が失敗し、復元が拒否されました"; return 1; }
+
+ echo "$pkg_dir"
+ return 0
+ }
+
+ openclaw_get_all_agent_workspaces() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json, sys, os
+try:
+ with open(sys.argv[1]) as f: data = json.load(f)
+ agents = data.get("agents", {}).get("list", [])
+ results = [{"id": "main", "ws": os.path.expanduser("~/.openclaw/workspace")}]
+ for a in agents:
+ aid = a.get("id"); ws = a.get("workspace")
+ if aid and ws and aid != "main": results.append({"id": aid, "ws": os.path.expanduser(ws)})
+ print(json.dumps(results))
+except: print("[]")
+PY
+ else
+ echo '[{"id": "main", "ws": "'"${HOME}"'/.openclaw/workspace"}]'
+ fi
+ }
+
+ openclaw_memory_backup_export() {
+ send_stats "OpenClaw メモリの完全バックアップ"
+ local backup_root=$(openclaw_backup_root)
+ local ts=$(date +%Y%m%d-%H%M%S)
+ local out_file="$backup_root/openclaw-memory-full-${ts}.tar.gz"
+ mkdir -p "$backup_root"
+ local tmp_payload=$(mktemp -d) || return 1
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c "import json, sys, os, shutil;
+workspaces = json.loads(sys.argv[1]); tmp_payload = sys.argv[2]
+for item in workspaces:
+ aid = item['id']; ws = item['ws']
+ if not os.path.isdir(ws): continue
+ target_dir = os.path.join(tmp_payload, 'agents', aid)
+ os.makedirs(target_dir, exist_ok=True)
+ for f in ['MEMORY.md', 'memory']:
+ src = os.path.join(ws, f)
+ if os.path.exists(src):
+ if os.path.isfile(src): shutil.copy2(src, target_dir)
+ else: shutil.copytree(src, os.path.join(target_dir, f), dirs_exist_ok=True)
+" "$workspaces_json" "$tmp_payload"
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ バックアップメモリファイルが見つかりません"; rm -rf "$tmp_payload"; break_end; return 1
+ fi
+ if openclaw_pack_backup_archive "memory-full" "multi-agent" "$tmp_payload" "$out_file"; then
+ echo "✅ フルメモリバックアップが完了しました (マルチエージェントを含む):$out_file"; openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ フルメモリバックアップに失敗しました"
+ fi
+ rm -rf "$tmp_payload"; break_end
+ }
+
+ openclaw_memory_backup_import() {
+ send_stats "OpenClaw メモリ完全復元"
+ local archive_path=$(openclaw_read_import_path "メモリの全量を復元します (マルチエージェントをサポート)")
+ [ -z "$archive_path" ] && { echo "❌ パスが入力されていません"; break_end; return 1; }
+ local tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir=$(openclaw_prepare_import_archive "memory-full" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c 'import json, sys, os, shutil;
+workspaces = {item["id"]: item["ws"] for item in json.loads(sys.argv[1])};
+payload_dir = sys.argv[2]; agents_root = os.path.join(payload_dir, "agents")
+if os.path.isdir(agents_root):
+ for aid in os.listdir(agents_root):
+ if aid in workspaces:
+ src_agent_dir = os.path.join(agents_root, aid); dest_ws = workspaces[aid]
+ os.makedirs(dest_ws, exist_ok=True)
+ for f in os.listdir(src_agent_dir):
+ src = os.path.join(src_agent_dir, f); dest = os.path.join(dest_ws, f)
+ if os.path.isfile(src): shutil.copy2(src, dest)
+ else: shutil.copytree(src, dest, dirs_exist_ok=True)
+ print(f"✅ エージェントの記憶が復元されました: {aid}")' "$workspaces_json" "$pkg_dir/payload"
+ rm -rf "$tmp_unpack"; echo "✅ 完全なメモリ復元が完了しました"; break_end
+ }
+
+
+ openclaw_project_backup_export() {
+ send_stats "OpenClaw プロジェクトのバックアップ"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ if [ ! -d "$openclaw_root" ]; then
+ echo "❌ OpenClaw ルート ディレクトリが見つかりません:$openclaw_root"
+ break_end
+ return 1
+ fi
+
+ echo "バックアップモード:"
+ echo "1. セーフ モード (デフォルト、推奨): ワークスペース + openclaw.json + 拡張機能/スキル/プロンプト/ツール (存在する場合)"
+ echo "2. 完全モード (より多くの状態が含まれ、機密リスクが高くなります)"
+ read -e -p "バックアップ モードを選択してください (デフォルト 1):" export_mode
+ [ -z "$export_mode" ] && export_mode="1"
+
+ local mode_label="safe"
+ local tmp_payload
+ tmp_payload=$(mktemp -d) || return 1
+
+ if [ "$export_mode" = "2" ]; then
+ mode_label="full"
+ for d in workspace extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in telegram feishu whatsapp discord slack qqbot logs; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ else
+ [ -d "$openclaw_root/workspace" ] && cp -a "$openclaw_root/workspace" "$tmp_payload/"
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ fi
+
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ バックアップ可能な OpenClaw プロジェクト コンテンツが見つかりません"
+ rm -rf "$tmp_payload"
+ break_end
+ return 1
+ fi
+
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ local out_file="$backup_root/openclaw-project-${mode_label}-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ if openclaw_pack_backup_archive "openclaw-project" "$mode_label" "$tmp_payload" "$out_file"; then
+ echo "✅ OpenClaw プロジェクトのバックアップが完了しました (${mode_label}): $out_file"
+ openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ OpenClaw プロジェクトのバックアップに失敗しました"
+ fi
+
+ rm -rf "$tmp_payload"
+ break_end
+ }
+
+ openclaw_project_backup_import() {
+ send_stats "OpenClaw プロジェクトの復元"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ mkdir -p "$openclaw_root"
+
+ echo "⚠️ リスクの高い操作: プロジェクトを復元すると、OpenClaw 構成とワークスペースのコンテンツが上書きされます。"
+ echo "⚠️ 还原前将执行 manifest/sha256 校验、白名单恢复、gateway 停启与健康检查。"
+ read -e -p "続行するには、確認の単語 [リスクが高いことを承知しており、復元を続行します] を入力してください:" confirm_text
+ if [ "$confirm_text" != "高いリスクを認識しており、復旧を続けています" ]; then
+ echo "❌ 確認ワードが一致しなかったため、リストアはキャンセルされました。"
+ break_end
+ return 1
+ fi
+
+ local archive_path
+ archive_path=$(openclaw_read_import_path "OpenClaw プロジェクトのバックアップ パッケージのパスを入力してください")
+ [ -z "$archive_path" ] && { echo "❌ バックアップ パスが入力されていません"; break_end; return 1; }
+
+ local tmp_unpack
+ tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir
+ pkg_dir=$(openclaw_prepare_import_archive "openclaw-project" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+
+ local invalid=0
+ local valid_list
+ valid_list=$(mktemp)
+ while IFS= read -r rel; do
+ [ -z "$rel" ] && continue
+ if ! openclaw_is_safe_relpath "$rel" || ! openclaw_restore_path_allowed project "$rel"; then
+ echo "❌ 違法または無許可のパスが検出されました:$rel"
+ invalid=1
+ break
+ fi
+ echo "$rel" >> "$valid_list"
+ done < "$pkg_dir/manifest.files"
+
+ if [ "$invalid" -ne 0 ]; then
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "❌ 復元は中止されました: 安全でないパスが存在します"
+ break_end
+ return 1
+ fi
+
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "⏸️ 復元する前に OpenClaw ゲートウェイを停止してください..."
+ openclaw gateway stop >/dev/null 2>&1
+ fi
+
+ while IFS= read -r rel; do
+ mkdir -p "$openclaw_root/$(dirname "$rel")"
+ cp -a "$pkg_dir/payload/$rel" "$openclaw_root/$rel"
+ done < "$valid_list"
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "▶️ 復元後に OpenClaw ゲートウェイを起動します..."
+ openclaw gateway start >/dev/null 2>&1
+ sleep 2
+ echo "🩺 ゲートウェイのヘルスチェック:"
+ openclaw gateway status || true
+ fi
+
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "✅ OpenClaw プロジェクトの復元が完了しました"
+ break_end
+ }
+
+ openclaw_backup_detect_type() {
+ local file_name="$1"
+ if [[ "$file_name" == openclaw-memory-full-*.tar.gz ]]; then
+ echo "メモリバックアップファイル"
+ elif [[ "$file_name" == openclaw-project-*.tar.gz ]]; then
+ echo "プロジェクトのバックアップファイル"
+ else
+ echo "その他のバックアップファイル"
+ fi
+ }
+
+ openclaw_backup_collect_files() {
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ mapfile -t OPENCLAW_BACKUP_FILES < <(find "$backup_root" -maxdepth 1 -type f -name '*.tar.gz' -printf '%f\n' | sort -r)
+ }
+
+
+ openclaw_backup_render_file_list() {
+ local backup_root i file_name file_path file_type file_size file_time
+ local has_memory=0 has_project=0 has_other=0
+ backup_root=$(openclaw_backup_root)
+ openclaw_backup_collect_files
+
+ echo "バックアップディレクトリ:$backup_root"
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ echo "バックアップファイルはまだありません"
+ return 0
+ fi
+
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_type=$(openclaw_backup_detect_type "${OPENCLAW_BACKUP_FILES[$i]}")
+ case "$file_type" in
+ "メモリバックアップファイル") has_memory=1 ;;
+ "プロジェクトのバックアップファイル") has_project=1 ;;
+ "その他のバックアップファイル") has_other=1 ;;
+ esac
+ done
+
+ if [ "$has_memory" -eq 1 ]; then
+ echo "メモリバックアップファイル"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "メモリバックアップファイル" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_project" -eq 1 ]; then
+ echo "プロジェクトのバックアップファイル"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "プロジェクトのバックアップファイル" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_other" -eq 1 ]; then
+ echo "その他のバックアップファイル"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "その他のバックアップファイル" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+ }
+
+ openclaw_backup_file_exists_in_list() {
+ local target_file="$1"
+ local item
+ for item in "${OPENCLAW_BACKUP_FILES[@]}"; do
+ [ "$item" = "$target_file" ] && return 0
+ done
+ return 1
+ }
+
+ openclaw_backup_delete_file() {
+ send_stats "OpenClaw はバックアップ ファイルを削除します"
+ local backup_root backup_root_real user_input target_file target_path target_type
+ backup_root=$(openclaw_backup_root)
+
+ openclaw_backup_render_file_list
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ break_end
+ return 0
+ fi
+
+ read -e -p "削除するファイル名またはフルパスを入力してください (キャンセルするには 0):" user_input
+ if [ "$user_input" = "0" ]; then
+ echo "削除がキャンセルされました。"
+ break_end
+ return 0
+ fi
+ if [ -z "$user_input" ]; then
+ echo "❌ 入力を空にすることはできません。"
+ break_end
+ return 1
+ fi
+
+ backup_root_real=$(realpath -m "$backup_root")
+ if [[ "$user_input" == /* ]]; then
+ target_path=$(realpath -m "$user_input")
+ case "$target_path" in
+ "$backup_root_real"/*) ;;
+ *)
+ echo "❌ 範囲外のパス: バックアップ ルート ディレクトリ内のファイルのみを削除できます。"
+ break_end
+ return 1
+ ;;
+ esac
+ target_file=$(basename "$target_path")
+ else
+ target_file=$(basename -- "$user_input")
+ target_path="$backup_root/$target_file"
+ fi
+
+ if [ ! -f "$target_path" ]; then
+ echo "❌ 対象のファイルが存在しません:$target_path"
+ break_end
+ return 1
+ fi
+
+ if ! openclaw_backup_file_exists_in_list "$target_file"; then
+ echo "❌ ターゲットファイルは現在のバックアップリストにありません。"
+ break_end
+ return 1
+ fi
+
+ target_type=$(openclaw_backup_detect_type "$target_file")
+
+ echo "削除されようとしています: [$target_type] $target_path"
+ read -e -p "最初の確認: 「yes」と入力して確認し、続行します。" confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "削除がキャンセルされました。"
+ break_end
+ return 0
+ fi
+ read -e -p "二次確認: 削除するには「DELETE」と入力します。" confirm_step2
+ if [ "$confirm_step2" != "DELETE" ]; then
+ echo "削除がキャンセルされました。"
+ break_end
+ return 0
+ fi
+
+ if rm -f -- "$target_path"; then
+ echo "✅ 削除が成功しました:$target_file"
+ else
+ echo "❌ 削除に失敗しました:$target_file"
+ fi
+ break_end
+ }
+
+ openclaw_backup_list_files() {
+ openclaw_backup_render_file_list
+ break_end
+ }
+
+ openclaw_memory_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_memory_config_get() {
+ local key="$1"
+ local default_value="${2:-}"
+ local value
+ value=$(openclaw config get "$key" 2>/dev/null | head -n 1 | sed -e 's/^"//' -e 's/"$//')
+ if [ -z "$value" ] || [ "$value" = "null" ] || [ "$value" = "undefined" ]; then
+ echo "$default_value"
+ return 0
+ fi
+ echo "$value"
+ }
+
+ openclaw_memory_config_set() {
+ local key="$1"
+ shift
+ openclaw config set "$key" "$@" >/dev/null 2>&1
+ }
+
+ openclaw_memory_config_unset() {
+ local key="$1"
+ openclaw config unset "$key" >/dev/null 2>&1
+ }
+
+ openclaw_memory_cleanup_legacy_keys() {
+ openclaw_memory_config_unset "memory.local"
+ }
+
+ openclaw_memory_list_agents() {
+ if command -v openclaw >/dev/null 2>&1; then
+ local agents_json
+ agents_json=$(openclaw agents list --json 2>/dev/null || true)
+ if [ -n "$agents_json" ]; then
+ python3 - "$agents_json" <<'PY'
+import json, os, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ data = None
+seen = set()
+results = []
+if isinstance(data, list):
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ if not aid or aid in seen:
+ continue
+ ws = item.get('workspace') or ("~/.openclaw/workspace" if aid == 'main' else f"~/.openclaw/workspace-{aid}")
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+if results:
+ for aid, ws in results:
+ print(f"{aid}\t{ws}")
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+ [ $? -eq 0 ] && return 0
+ fi
+ fi
+ local config_path
+ config_path=$(openclaw_memory_config_file)
+ python3 - "$config_path" <<'PY'
+import json, os, sys
+config_path = sys.argv[1]
+results = [("main", os.path.expanduser("~/.openclaw/workspace"))]
+seen = {"main"}
+try:
+ if os.path.exists(config_path):
+ with open(config_path, encoding='utf-8') as f:
+ data = json.load(f)
+ agents = data.get('agents', {}).get('list', [])
+ if isinstance(agents, list):
+ for item in agents:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ ws = item.get('workspace')
+ if not aid or aid in seen:
+ continue
+ if not ws:
+ ws = f"~/.openclaw/workspace-{aid}"
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+except Exception:
+ pass
+for aid, ws in results:
+ print(f"{aid}\t{ws}")
+PY
+ }
+
+ openclaw_memory_status_value() {
+ local key="$1"
+ local agent_id="${2:-}"
+ if [ -n "$agent_id" ]; then
+ openclaw memory status --agent "$agent_id" 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ else
+ openclaw memory status 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ fi
+ }
+
+ openclaw_memory_expand_path() {
+ local raw_path="$1"
+ if [ -z "$raw_path" ]; then
+ echo ""
+ return 0
+ fi
+ raw_path=$(echo "$raw_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [[ "$raw_path" == ~* ]]; then
+ echo "${raw_path/#\~/$HOME}"
+ else
+ echo "$raw_path"
+ fi
+ }
+
+ openclaw_memory_rebuild_index_single() {
+ local agent_id="${1:-main}"
+ local store_raw store_file ts backup_file
+ store_raw=$(openclaw_memory_status_value "Store" "$agent_id")
+ store_file=$(openclaw_memory_expand_path "$store_raw")
+ if [ -z "$store_file" ] || [ ! -f "$store_file" ]; then
+ echo "⚠️ [$agent_id] インデックス ライブラリ ファイルが見つかりません。空か存在しない可能性があります。"
+ echo "生の値を保存: ${store_raw:-}"
+ echo "インデックスの再作成は引き続き実行されます。"
+ else
+ ts=$(date +%Y%m%d_%H%M%S)
+ backup_file="${store_file}.bak.${ts}"
+ if mv "$store_file" "$backup_file"; then
+ echo "✅ [$agent_id] インデックスがバックアップされました:$backup_file"
+ else
+ echo "⚠️ [$agent_id] インデックスのバックアップに失敗しました。再構築を続行します。"
+ fi
+ fi
+ openclaw memory index --agent "$agent_id" --force
+ }
+
+ openclaw_memory_rebuild_index_safe() {
+ local agent_id="${1:-main}"
+ openclaw_memory_rebuild_index_single "$agent_id"
+ openclaw gateway restart
+ echo "✅ インデックスが再構築され、ゲートウェイが自動的に再起動されました。"
+ echo ""
+ openclaw_memory_render_status
+ }
+
+ openclaw_memory_rebuild_index_all() {
+ local count=0
+ local agent_lines agent_id workspace
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id workspace; do
+ [ -z "$agent_id" ] && continue
+ openclaw_memory_rebuild_index_single "$agent_id"
+ count=$((count+1))
+ done </dev/null)
+ if [ -z "$json_output" ]; then
+ echo "メモリステータスの取得に失敗しました (openclaw メモリステータス --json 出力なし)"
+ return 1
+ fi
+ python3 - "$json_output" <<'PY'
+import json, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ print("メモリステータスの取得に失敗しました(JSON解析エラー)")
+ raise SystemExit(1)
+if not isinstance(data, list) or len(data) == 0:
+ print("エージェントのメモリ状態が検出されませんでした。")
+ raise SystemExit(0)
+first = True
+for entry in data:
+ if not isinstance(entry, dict):
+ continue
+ agent_id = entry.get("agentId", "?")
+ s = entry.get("status", {})
+ if not isinstance(s, dict):
+ s = {}
+ if not first:
+ print("")
+ first = False
+ print("Agent: %s" % agent_id)
+ backend = s.get("backend") or s.get("provider") or "-"
+ print("基本的なソリューション: %s" % backend)
+ files = s.get("files", 0)
+ chunks = s.get("chunks", 0)
+ print("含まれるもの: %s ファイル / %s ブロック" % (files, chunks))
+ dirty = s.get("dirty")
+ dirty_str = "はい" if dirty else "いいえ"
+ print("更新対象: %s" % dirty_str)
+ vec = s.get("vector", {})
+ if isinstance(vec, dict) and vec.get("enabled"):
+ vec_str = "準備ができて" if vec.get("available") else "有効 (利用不可)"
+ else:
+ vec_str = "有効になっていません"
+ print("ベクター ライブラリ: %s" % vec_str)
+ ws = s.get("workspaceDir") or "-"
+ print("ワークスペース: %s" % ws)
+ db = s.get("dbPath") or "-"
+ print("インデックス ライブラリ: %s" % db)
+ scan = entry.get("scan", {})
+ if isinstance(scan, dict):
+ issues = scan.get("issues", [])
+ if issues:
+ for issue in issues[:3]:
+ print(" ⚠️ %s" % issue)
+PY
+ }
+
+ openclaw_memory_get_backend() {
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "local" ]; then
+ echo "builtin"
+ else
+ echo "$backend"
+ fi
+ }
+
+ openclaw_memory_get_local_model_path() {
+ openclaw_memory_config_get "agents.defaults.memorySearch.local.modelPath"
+ }
+
+ openclaw_memory_local_model_status() {
+ local model_path="$1"
+ if [ -z "$model_path" ]; then
+ echo "missing"
+ return
+ fi
+ if [[ "$model_path" == hf:* ]]; then
+ echo "hf"
+ return
+ fi
+ if [ -f "$model_path" ]; then
+ echo "ok"
+ else
+ echo "missing"
+ fi
+ }
+
+ openclaw_memory_qmd_available() {
+ if command -v qmd >/dev/null 2>&1; then
+ echo "true"
+ return
+ fi
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "qmd" ]; then
+ echo "true"
+ return
+ fi
+ echo "false"
+ }
+
+ openclaw_memory_probe_url() {
+ local url="$1"
+ if ! command -v curl >/dev/null 2>&1; then
+ echo "unknown"
+ return
+ fi
+ if [ -z "$url" ]; then
+ echo "unknown"
+ return
+ fi
+ if curl -I -m 2 -s "$url" >/dev/null 2>&1; then
+ echo "ok"
+ else
+ echo "fail"
+ fi
+ }
+
+ openclaw_memory_recommend() {
+ local qmd_ok model_path model_status hf_ok mirror_ok
+ qmd_ok=$(openclaw_memory_qmd_available)
+ model_path=$(openclaw_memory_get_local_model_path)
+ model_status=$(openclaw_memory_local_model_status "$model_path")
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+
+ OPENCLAW_MEMORY_RECOMMEND_REASON=()
+ if [ "$qmd_ok" = "true" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMDが利用可能です")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMDが検出されませんでした")
+ fi
+ if [ -n "$model_path" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("ローカル モデル パス:$model_path")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("ローカル モデル パスが構成されていません")
+ fi
+ case "$model_status" in
+ ok) OPENCLAW_MEMORY_RECOMMEND_REASON+=("ローカルモデルファイルが存在します") ;;
+ hf) OPENCLAW_MEMORY_RECOMMEND_REASON+=("モデルは HF ダウンロード ソースからのものです (中国では遅い/失敗する可能性があります)") ;;
+ *) OPENCLAW_MEMORY_RECOMMEND_REASON+=("ローカル モデル ファイルが存在しないか、使用できません") ;;
+ esac
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("ハグフェイス.co にアクセスできます")
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("hf-mirror.com にアクセスできます")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("Huggingface.co / hf-mirror.com にアクセスできない可能性があります (国内/制限付きネットワークの疑い)")
+ fi
+
+ if [ "$qmd_ok" = "true" ]; then
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && { [ "$hf_ok" = "ok" ] || [ "$mirror_ok" = "ok" ]; }; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && [ "$hf_ok" = "fail" ] && [ "$mirror_ok" = "fail" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ else
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ fi
+ }
+
+
+ openclaw_memory_detect_region() {
+ OPENCLAW_MEMORY_COUNTRY="unknown"
+ OPENCLAW_MEMORY_USE_MIRROR="false"
+ if command -v curl >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_COUNTRY=$(curl -s -m 2 ipinfo.io/country | tr -d '
+' | tr -d '
+')
+ fi
+ case "$OPENCLAW_MEMORY_COUNTRY" in
+ CN|HK)
+ OPENCLAW_MEMORY_USE_MIRROR="true"
+ ;;
+ esac
+ }
+
+ openclaw_memory_select_sources() {
+ local hf_ok mirror_ok
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+ OPENCLAW_MEMORY_HF_OK="$hf_ok"
+ OPENCLAW_MEMORY_MIRROR_OK="$mirror_ok"
+ if [ "$OPENCLAW_MEMORY_USE_MIRROR" = "true" ]; then
+ if [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ elif [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://gh.kejilion.pro/"
+ else
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://"
+ fi
+ }
+
+ openclaw_memory_download_file() {
+ local url="$1"
+ local dest="$2"
+ mkdir -p "$(dirname "$dest")"
+ if command -v curl >/dev/null 2>&1; then
+ curl -L --fail --retry 2 -o "$dest" "$url"
+ return $?
+ fi
+ if command -v wget >/dev/null 2>&1; then
+ wget -O "$dest" "$url"
+ return $?
+ fi
+ echo "❌ Curl または wget が検出されず、ダウンロードできません。"
+ return 1
+ }
+
+ openclaw_memory_check_sqlite() {
+ if ! command -v sqlite3 >/dev/null 2>&1; then
+ echo "⚠️ sqlite3 が検出されない場合、QMD が正しく機能しない可能性があります。"
+ return 1
+ fi
+ local ver
+ ver=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
+ echo "✅ sqlite3 が利用可能: ${ver:-unknown}"
+ echo "ℹ️ sqlite 拡張機能のサポートは確実に検出できないため、継続されます。"
+ return 0
+ }
+
+ openclaw_memory_ensure_bun() {
+ if [ -x "$HOME/.bun/bin/bun" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ パンはすでに存在します"
+ return 0
+ fi
+ echo "⬇️バンをインストール..."
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL https://bun.sh/install | bash
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO- https://bun.sh/install | bash
+ else
+ echo "❌curl または wget が検出されないため、bun をインストールできません。"
+ return 1
+ fi
+ if [ -d "$HOME/.bun/bin" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅パンの取り付けが完了しました"
+ return 0
+ fi
+ echo "❌ バンのインストールに失敗しました"
+ return 1
+ }
+
+ openclaw_memory_ensure_qmd() {
+ local qmd_path
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -n "$qmd_path" ]; then
+ if qmd --version >/dev/null 2>&1; then
+ echo "✅ qmd はすでに存在しており、利用可能です。$qmd_path"
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ return 0
+ else
+ echo "⚠️ qmd コマンドは存在しますが、モジュールが破損しています。再インストールしてください..."
+ fi
+ fi
+ echo "⬇️ npm 経由で qmd をインストールします: @tobilu/qmd"
+ npm install -g @tobilu/qmd
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -z "$qmd_path" ]; then
+ echo "❌ qmd のインストールに失敗しました"
+ return 1
+ fi
+ if ! qmd --version >/dev/null 2>&1; then
+ echo "❌ インストール後も qmd が実行できない"
+ return 1
+ fi
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ echo "✅ qmd のインストールが完了しました:$qmd_path"
+ return 0
+ }
+
+ openclaw_memory_render_auto_summary() {
+ echo "---------------------------------------"
+ echo "✅ 環境準備完了"
+ echo "スキーム: ${OPENCLAW_MEMORY_AUTO_SCHEME:-unknown}"
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "モード: 設定の書き込みのみ (インストールされない/ダウンロードされない)"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "インデックス: 実行されました"
+ else
+ echo "インデックス: スキップされました"
+ fi
+ if [ "$OPENCLAW_MEMORY_RESTARTED" = "true" ]; then
+ echo "再起動: 実行されました"
+ else
+ echo "再起動: スキップされました"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ echo "qmd: $OPENCLAW_MEMORY_QMD_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_MODEL_PATH" ]; then
+ echo "モデル:$OPENCLAW_MEMORY_MODEL_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_COUNTRY" ]; then
+ echo "エリア:$OPENCLAW_MEMORY_COUNTRY"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_HF_BASE" ]; then
+ echo "ダウンロードソース:$OPENCLAW_MEMORY_HF_BASE"
+ fi
+ echo "最終ステータス:"
+ openclaw_memory_render_status
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_auto_confirm() {
+ local scheme_label="$1"
+ OPENCLAW_MEMORY_PREHEAT="true"
+ OPENCLAW_MEMORY_RESTARTED="false"
+ OPENCLAW_MEMORY_CONFIG_ONLY="false"
+ echo "自動デプロイメントが実行されようとしています (冗長モード)"
+ echo "対象プラン:$scheme_label"
+ echo "地域: ${OPENCLAW_MEMORY_COUNTRY:-不明}"
+ echo "ミラーソース検出:huggingface.co=${OPENCLAW_MEMORY_HF_OK:-unknown} hf-mirror.com=${OPENCLAW_MEMORY_MIRROR_OK:-unknown}"
+ echo "ダウンロード ソース: ${OPENCLAW_MEMORY_HF_BASE:-unknown}"
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_PATH" ]; then
+ echo "推定ダウンロード パス:$OPENCLAW_MEMORY_EXPECT_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_SIZE" ]; then
+ echo "考えられるトラフィック/ディスク使用量:$OPENCLAW_MEMORY_EXPECT_SIZE"
+ else
+ echo "考えられるトラフィック/ディスク使用量: 実際の状況によって異なります"
+ fi
+ echo "确认后将自动安装/下载、写入配置、构建索引并重启网关"
+ echo "詳細オプション: config を入力して構成のみを書き込みます (インストールなし、ダウンロードなし、インデックス作成なし、再起動なし)。"
+ read -e -p "続行することを確認するには「yes」と入力します (デフォルトは N):" confirm_step
+ case "$confirm_step" in
+ yes|YES)
+ OPENCLAW_MEMORY_PREHEAT="true"
+ ;;
+ config|CONFIG)
+ OPENCLAW_MEMORY_CONFIG_ONLY="true"
+ OPENCLAW_MEMORY_PREHEAT="false"
+ ;;
+ *)
+ echo "自動デプロイがキャンセルされました。"
+ return 1
+ ;;
+ esac
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "⚠️ 構成の書き込みのみを選択し、インストールやダウンロードは行いません"
+ else
+ echo "✅ インデックスが自動的に構築され、ゲートウェイが再起動されます。"
+ fi
+ return 0
+ }
+
+ openclaw_memory_auto_setup_qmd() {
+ echo "🔍 QMD環境を検出"
+ openclaw_memory_cleanup_legacy_keys
+ openclaw_memory_check_sqlite || true
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ if command -v qmd >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_QMD_PATH=$(command -v qmd)
+ else
+ OPENCLAW_MEMORY_QMD_PATH="qmd"
+ fi
+ else
+ openclaw_memory_ensure_qmd || return 1
+ fi
+ local backend
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ]; then
+ echo "✅memory.backendはすでにqmdです"
+ else
+ openclaw_memory_config_set "memory.backend" "qmd"
+ echo "✅memory.backend=qmdが設定されています"
+ fi
+ local qmd_cmd
+ qmd_cmd=$(openclaw_memory_config_get "memory.qmd.command")
+ if [ -z "$qmd_cmd" ] || [[ "$qmd_cmd" != /* ]] || [ "$qmd_cmd" != "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ openclaw_memory_config_set "memory.qmd.command" "$OPENCLAW_MEMORY_QMD_PATH"
+ echo "✅memory.qmd.commandに書き込まれます:$OPENCLAW_MEMORY_QMD_PATH"
+ else
+ echo "✅memory.qmd.commandは正しいです"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "🔥 ウォームインデックス (おそらくダウンロードモデル)"
+ openclaw_memory_prepare_workspace_all
+ local preh_agent_lines preh_agent_id preh_workspace
+ preh_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r preh_agent_id preh_workspace; do
+ [ -z "$preh_agent_id" ] && continue
+ openclaw memory index --agent "$preh_agent_id" --force
+ done </dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ OPENCLAW_MEMORY_RESTARTED="true"
+ openclaw_memory_render_auto_summary
+ return 0
+ }
+
+ openclaw_memory_auto_setup_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "メモリソリューションの自動導入"
+ echo "======================================="
+ echo "1. QMD"
+ echo "2. Local"
+ echo "3.オート(自動選択)"
+ echo "0. 前のレベルに戻ります"
+ echo "---------------------------------------"
+ read -e -p "選択肢を入力してください:" auto_choice
+ case "$auto_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_apply_scheme() {
+ local scheme="$1"
+ openclaw_memory_cleanup_legacy_keys
+ case "$scheme" in
+ qmd)
+ openclaw_memory_config_set "memory.backend" "qmd"
+ if [ $? -ne 0 ]; then
+ echo "❌ 設定の書き込みに失敗しました"
+ return 1
+ fi
+ openclaw_memory_config_set "memory.qmd.command" "qmd" >/dev/null 2>&1
+ ;;
+ local)
+ openclaw_memory_config_set "memory.backend" "builtin"
+ if [ $? -ne 0 ]; then
+ echo "❌ 設定の書き込みに失敗しました"
+ return 1
+ fi
+ openclaw_memory_config_set "agents.defaults.memorySearch.provider" "local" >/dev/null 2>&1
+ ;;
+ *)
+ echo "❌ 不明な解決策:$scheme"
+ return 1
+ esac
+ echo "✅ メモリスキーム構成が更新されました"
+ return 0
+ }
+
+ openclaw_memory_offer_restart() {
+ echo "設定は書き込まれており、OpenClaw ゲートウェイの再起動後に有効にするには、再起動する必要があります。"
+ read -e -p "今すぐ OpenClaw ゲートウェイを再起動しますか? (はい/いいえ):" restart_choice
+ if [[ "$restart_choice" =~ ^[Nn]$ ]]; then
+ echo "再起動はスキップされました。後で実行できます: openclaw ゲートウェイの再起動"
+ return 0
+ fi
+ if declare -F start_gateway >/dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ }
+
+ openclaw_memory_fix_index() {
+ local backend include_dm
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ] && ! command -v qmd >/dev/null 2>&1; then
+ echo "⚠️ 現在のスキームは QMD として検出されますが、qmd コマンドがインストールされていません。"
+ echo "ローカルに切り替えるか、bun + qmd をインストールして再試行してください。"
+ fi
+ include_dm=$(openclaw config get memory.qmd.includeDefaultMemory 2>/dev/null)
+ echo "======================================="
+ echo "インデックス修復診断"
+ echo "======================================="
+ echo "現在の includeDefaultMemory: ${include_dm:-not set}"
+ echo ""
+ if [ "$include_dm" = "false" ]; then
+ echo "⚠️ includeDefaultMemory=false が検出されました"
+ echo "これにより、デフォルトのメモリ ファイル (MEMORY.md +memory/*.md) のインデックスが作成されなくなります。"
+ echo "したがって、インデックス付きでは常に 0/N が表示されます。"
+ echo ""
+ read -e -p "true に戻してインデックスを再構築しますか? (はい/いいえ):" fix_choice
+ if [[ ! "$fix_choice" =~ ^[Nn]$ ]]; then
+ openclaw_memory_config_set "memory.qmd.includeDefaultMemory" true
+ if [ $? -ne 0 ]; then
+ echo "❌ 設定の書き込みに失敗しました"
+ break_end
+ return 1
+ fi
+ echo "✅ includeDefaultMemory=true が復元されました"
+ openclaw_memory_rebuild_index_all
+ else
+ echo "キャンセル。"
+ fi
+ else
+ echo "includeDefaultMemory 構成は正常です。"
+ echo "実行内容: 古いインデックスを削除 → すべてのエージェント インデックスを完全に再構築"
+ echo ""
+ read -e -p "実行を確認しますか? (はい/いいえ):" confirm_fix
+ if [[ ! "$confirm_fix" =~ ^[Nn]$ ]]; then
+ openclaw_memory_rebuild_index_all
+ else
+ echo "キャンセル。"
+ fi
+ fi
+ break_end
+ }
+
+ openclaw_memory_scheme_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw メモリ ソリューション"
+ echo "======================================="
+ local backend current_label
+ backend=$(openclaw_memory_get_backend)
+ case "$backend" in
+ qmd) current_label="QMD" ;;
+ builtin|local) current_label="Local" ;;
+ *) current_label="未設定" ;;
+ esac
+ echo "現在の計画:$current_label"
+ echo ""
+ echo "QMD: qmd コマンドに依存する軽量インデックス (ネットワーク制約に適しています)"
+ echo "ローカル: 埋め込みモデル ファイルに依存するローカル ベクトル取得"
+ echo "自動: 自動推奨 (可用性 + ネットワーク検出に基づく)"
+ echo "---------------------------------------"
+ echo "1. QMD の切り替え (自動展開/既にインストールされている場合はスキップ)"
+ echo "2. ローカルに切り替えます (自動展開/既にインストールされている場合はスキップ)"
+ echo "3. Auto (自動推奨と自動デプロイ)"
+ echo "0. 前のレベルに戻ります"
+ echo "---------------------------------------"
+ read -e -p "選択肢を入力してください:" scheme_choice
+ case "$scheme_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_file_collect() {
+ OPENCLAW_MEMORY_FILES=()
+ OPENCLAW_MEMORY_FILE_LABELS=()
+ local agent_lines agent_id base_dir memory_dir memory_file rel
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id base_dir; do
+ [ -z "$agent_id" ] && continue
+ memory_dir="$base_dir/memory"
+ memory_file="$base_dir/MEMORY.md"
+ if [ -f "$memory_file" ]; then
+ OPENCLAW_MEMORY_FILES+=("$memory_file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/MEMORY.md")
+ fi
+ if [ -d "$memory_dir" ]; then
+ while IFS= read -r file; do
+ [ -f "$file" ] || continue
+ rel="${file#$base_dir/}"
+ OPENCLAW_MEMORY_FILES+=("$file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/$rel")
+ done < <(find "$memory_dir" -type f -name '*.md' | sort)
+ fi
+ done </dev/null || stat -c %y "$file" | awk '{print $1" "$2}')
+ printf "%s | %s | %s | %s\\n" "$((i+1))" "$rel" "$size" "$mtime"
+ done
+ }
+
+ openclaw_memory_view_file() {
+ local file="$1"
+ [ -f "$file" ] || {
+ echo "❌ ファイルが存在しません:$file"
+ return 1
+ }
+ local total_lines
+ total_lines=$(wc -l < "$file" 2>/dev/null || echo 0)
+ local default_lines=120
+ local start_line count
+ echo "書類:$file"
+ echo "合計行数:$total_lines"
+ read -e -p "開始行を入力してください (Enter キーを押すとデフォルトで行の終わりになります)$default_linesわかりました):" start_line
+ read -e -p "表示する行数を入力してください (デフォルトでは Enter キーを押します)$default_lines): " count
+ [ -z "$count" ] && count=$default_lines
+ if [ -z "$start_line" ]; then
+ if [ "$total_lines" -le "$count" ]; then
+ start_line=1
+ else
+ start_line=$((total_lines - count + 1))
+ fi
+ fi
+ if ! [[ "$start_line" =~ ^[0-9]+$ ]] || ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ echo "❌ 有効な番号を入力してください。"
+ return 1
+ fi
+ if [ "$start_line" -lt 1 ]; then
+ start_line=1
+ fi
+ if [ "$count" -le 0 ]; then
+ echo "❌ 行数は 0 より大きくなければなりません。"
+ return 1
+ fi
+ local end_line=$((start_line + count - 1))
+ if [ "$end_line" -gt "$total_lines" ]; then
+ end_line=$total_lines
+ fi
+ if [ "$total_lines" -eq 0 ]; then
+ echo "(空のファイル)"
+ return 0
+ fi
+ echo "---------------------------------------"
+ sed -n "${start_line},${end_line}p" "$file"
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_files_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw メモリ ファイル"
+ echo "======================================="
+ openclaw_memory_file_render_list
+ echo "---------------------------------------"
+ read -e -p "表示するファイル番号を入力してください (0 を返します):" file_choice
+ if [ "$file_choice" = "0" ]; then
+ return 0
+ fi
+ if ! [[ "$file_choice" =~ ^[0-9]+$ ]]; then
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_file_collect
+ if [ ${#OPENCLAW_MEMORY_FILES[@]} -eq 0 ]; then
+ read -p "メモリ ファイルが見つかりません。Enter キーを押して戻ります..."
+ return 0
+ fi
+ local idx=$((file_choice-1))
+ if [ "$idx" -lt 0 ] || [ "$idx" -ge ${#OPENCLAW_MEMORY_FILES[@]} ]; then
+ echo "番号が無効です。もう一度お試しください。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_view_file "${OPENCLAW_MEMORY_FILES[$idx]}"
+ read -p "Enter キーを押してリストに戻ります..."
+ done
+ }
+
+
+ openclaw_memory_search_test() {
+ read -e -p "検索キーワードを入力してください:" query
+ if [ -z "$query" ]; then
+ echo "キーワードを空にすることはできません。"
+ return 1
+ fi
+ echo "記憶を検索中..."
+ openclaw memory search "$query" --max-results 5
+ }
+
+ openclaw_memory_deep_status() {
+ echo "埋め込みモデルの準備ステータスを検出しています..."
+ openclaw memory status --deep
+ }
+
+ openclaw_memory_menu() {
+ send_stats "OpenClaw のメモリ管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw のメモリ管理"
+ echo "======================================="
+ openclaw_memory_render_status
+ echo "1. メモリインデックスを更新する"
+ echo "2. メモリファイルを表示する"
+ echo "3. インデックス修復(インデックス例外)"
+ echo "4. メモリ ソリューション (QMD/ローカル/自動)"
+ echo "5. 検索テスト (インデックスが機能していることを確認します)"
+ echo "6. ディープステートの検出 (組み込みモデルのチェック)"
+ echo "0. 前のレベルに戻ります"
+ echo "---------------------------------------"
+ read -e -p "選択肢を入力してください:" memory_choice
+ case "$memory_choice" in
+ 1)
+ echo "メモリインデックスは間もなく更新されます。"
+ read -e -p "最初の確認: 「yes」と入力して続行します:" confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "キャンセル。"
+ break_end
+ continue
+ fi
+ openclaw_memory_prepare_workspace_all
+ read -e -p "2 番目の確認: 全額を使用するには、force を入力します (増分の場合は空白のままにします):" confirm_step2
+ if [ "$confirm_step2" = "force" ]; then
+ echo "⚠️ 完全な再構築はより徹底的ですが、時間がかかります。"
+ echo "推奨事項: 安全に再構築するには、再構築を入力してください (最初にインデックス データベースをバックアップします)。"
+ read -e -p "3 番目の確認: 「再構築」と入力して、安全な再構築を実行します。 Enter キーを押して通常の力で続行します。" confirm_step3
+ if [ "$confirm_step3" = "rebuild" ]; then
+ openclaw_memory_rebuild_index_all
+ else
+ local fl_agent_lines fl_agent_id fl_workspace
+ fl_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r fl_agent_id fl_workspace; do
+ [ -z "$fl_agent_id" ] && continue
+ openclaw memory index --agent "$fl_agent_id" --force
+ done </dev/null 2>&1 || {
+ echo "⚠️ 権限のバックアップが失敗しました:$backup_file"
+ return 1
+ }
+ echo "✅ 現在の権限設定がバックアップされました:$backup_file"
+ return 0
+ }
+
+ openclaw_permission_restore_backup() {
+ local config_file backup_file
+ config_file=$(openclaw_permission_config_file)
+ backup_file=$(openclaw_permission_backup_file)
+ if [ ! -s "$backup_file" ]; then
+ echo "❌ 復元可能な権限のバックアップ ファイルが見つかりません。"
+ return 1
+ fi
+ cp -f "$backup_file" "$config_file" >/dev/null 2>&1 || {
+ echo "❌ 権限の回復に失敗しました:$backup_file"
+ return 1
+ }
+ echo "✅ スイッチが復元される前の権限設定"
+ openclaw_permission_restart_gateway || true
+ return 0
+ }
+
+ openclaw_permission_restart_gateway() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ openclaw が検出されず、OpenClaw Gateway を再起動できません。"
+ return 1
+ fi
+ echo "OpenClaw ゲートウェイを再起動しています..."
+ openclaw gateway restart >/dev/null 2>&1 || {
+ openclaw gateway stop >/dev/null 2>&1
+ openclaw gateway start >/dev/null 2>&1
+ }
+ }
+
+ openclaw_permission_get_value() {
+ local path="$1"
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+
+ if openclaw_has_command openclaw; then
+ local value
+ value=$(openclaw config get "$path" 2>&1 | head -n 1)
+ if [ -n "$value" ]; then
+ if echo "$value" | grep -qi "config path not found"; then
+ echo "(unset)"
+ return 0
+ fi
+ if [ "$value" = "null" ]; then
+ echo "(unset)"
+ else
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ fi
+ return 0
+ fi
+ fi
+
+ [ -f "$config_file" ] || { echo "(unset)"; return 0; }
+
+ if openclaw_has_command jq; then
+ local jq_value
+ jq_value=$(jq -r --arg p "$path" 'getpath($p|split(".")) // "(unset)"' "$config_file" 2>/dev/null) || jq_value="(unset)"
+ [ "$jq_value" = "null" ] && jq_value="(unset)"
+ echo "$jq_value"
+ return 0
+ fi
+
+ if openclaw_has_command python3; then
+ python3 - "$config_file" "$path" <<'PY'
+import json, sys
+path = sys.argv[2]
+with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+cur = obj
+for part in path.split('.'):
+ if isinstance(cur, dict) and part in cur:
+ cur = cur[part]
+ else:
+ print('(unset)')
+ raise SystemExit(0)
+if isinstance(cur, bool):
+ print('true' if cur else 'false')
+elif cur is None:
+ print('(unset)')
+else:
+ print(json.dumps(cur, ensure_ascii=False) if isinstance(cur, (dict, list)) else str(cur))
+PY
+ return 0
+ fi
+
+ echo "(unset)"
+ return 0
+ }
+
+ openclaw_permission_unset_optional() {
+ local key="$1"
+ local probe
+ if ! openclaw_has_command openclaw; then
+ return 1
+ fi
+ if openclaw config unset "$key" >/dev/null 2>&1; then
+ return 0
+ fi
+ probe=$(openclaw config get "$key" 2>&1 | head -n 1)
+ if [ -z "$probe" ] || [ "$probe" = "null" ] || [ "$probe" = "(unset)" ] || echo "$probe" | grep -qi "config path not found"; then
+ return 0
+ fi
+ return 1
+ }
+
+ openclaw_permission_detect_mode() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ [ ! -f "$config_file" ] && { echo "不明なモード"; return; }
+
+ python3 - "$config_file" <<'PY'
+import json, sys
+
+def get_v(o, p):
+ for k in p.split('.'):
+ if isinstance(o, dict) and k in o:
+ o = o[k]
+ else:
+ return "(unset)"
+ return str(o).lower()
+
+try:
+ with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ d = json.load(f)
+ p = get_v(d, "tools.profile")
+ s = get_v(d, "tools.exec.security")
+ a = get_v(d, "tools.exec.ask")
+ e = get_v(d, "tools.elevated.enabled")
+ b = get_v(d, "commands.bash")
+ ap = get_v(d, "tools.exec.applyPatch.enabled")
+ w = get_v(d, "tools.exec.applyPatch.workspaceOnly")
+
+ if p == "coding" and s == "allowlist" and a == "on-miss" and e == "false" and b == "false" and ap == "false":
+ print("標準セーフモード")
+ elif p == "coding" and s == "allowlist" and a == "on-miss" and e == "true" and b == "true" and ap == "true" and w == "true":
+ print("拡張モードの開発")
+ elif (p == "full" or p == "(unset)") and s == "full" and a == "off" and e == "true" and b == "true" and ap == "true":
+ print("フルオープンモード")
+ else:
+ print("カスタムモード")
+except Exception:
+ print("カスタムモード")
+PY
+ }
+
+ openclaw_permission_update_exec_approvals() {
+ local sec="$1"
+ local ask="$2"
+ local fallback="$3"
+ local approvals_file="$HOME/.openclaw/exec-approvals.json"
+
+ mkdir -p "$HOME/.openclaw"
+
+ # JSON を生成し、openclaw 承認セット --stdin 経由で書き込みます (推奨)
+ # CLI がこれをサポートしていない場合は、ファイルの直接書き込みに戻ります。
+ local json_payload
+ json_payload=$(python3 -c '
+import json, sys, os
+path = sys.argv[1]
+try:
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ data = json.load(f)
+ else:
+ data = {"version": 1, "defaults": {}}
+except Exception:
+ data = {"version": 1, "defaults": {}}
+if "defaults" not in data:
+ data["defaults"] = {}
+data["defaults"]["security"] = sys.argv[2]
+data["defaults"]["ask"] = sys.argv[3]
+data["defaults"]["askFallback"] = sys.argv[4]
+data["defaults"]["autoAllowSkills"] = True
+print(json.dumps(data, indent=2))
+' "$approvals_file" "$sec" "$ask" "$fallback")
+
+ if openclaw_has_command openclaw && echo "$json_payload" | openclaw approvals set --stdin >/dev/null 2>&1; then
+ return 0
+ fi
+ # フォールバック: ファイルを直接書き込む
+ echo "$json_payload" > "$approvals_file"
+ }
+
+ openclaw_permission_render_status() {
+ echo "アプリケーション層の構成: ~/.openclaw/openclaw.json"
+ echo "ホストの承認: ~/.openclaw/exec-approvals.json"
+ echo "---------------------------------------"
+ local current_profile current_sec current_ask current_elevated
+ current_profile=$(openclaw config get tools.profile 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_sec=$(openclaw config get tools.exec.security 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_ask=$(openclaw config get tools.exec.ask 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_elevated=$(openclaw config get tools.elevated.enabled 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ # Null値をクリーンアップする
+ [ -z "$current_profile" ] || echo "$current_profile" | grep -qi "config path not found" && current_profile=""
+ [ -z "$current_sec" ] || echo "$current_sec" | grep -qi "config path not found" && current_sec=""
+ [ -z "$current_ask" ] || echo "$current_ask" | grep -qi "config path not found" && current_ask=""
+ [ -z "$current_elevated" ] || echo "$current_elevated" | grep -qi "config path not found" && current_elevated=""
+
+ local current_mode="不明 / カスタム"
+ if [ "$current_profile" = "full" ] && [ "$current_sec" = "full" ] && [ "$current_ask" = "off" ]; then
+ current_mode="\033[1;31m全開モード\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" = "true" ]; then
+ current_mode="\033[1;33m開発拡張モード\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" != "true" ]; then
+ current_mode="\033[1;32m標準セーフモード\033[0m"
+ elif [ -z "$current_profile" ] && [ -z "$current_sec" ]; then
+ current_mode="\033[1;36m公式サンドボックスが真実を語る\033[0m"
+ fi
+ echo -e "現在の包括的なセキュリティ レベル:${current_mode}"
+ echo "---------------------------------------"
+ echo -e "${gl_huang}[アプリケーション層ツールのポリシーステータス]${gl_bai}"
+ echo "プロファイル (デフォルト): ${current_profile:-(未設定)}"
+ echo "実行制限: ${current_sec:-(未設定)}"
+ echo "承認プロンプト: ${current_ask:-(unset)}"
+ echo "特権昇格スイッチ: ${current_elevated:-(unset)}"
+
+ echo -e "\n${gl_huang}[基本的な幹部の承認ステータス]${gl_bai}"
+ if openclaw_has_command openclaw; then
+ local approvals_json
+ approvals_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$approvals_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ defaults = d.get("file", {}).get("defaults", {})
+ if not defaults:
+ defaults = d.get("defaults", {})
+ sec = defaults.get("security", "(unset)")
+ ask = defaults.get("ask", "(unset)")
+ fb = defaults.get("askFallback", "(unset)")
+ auto = defaults.get("autoAllowSkills", False)
+ print("傍受戦略 (セキュリティ):" + str(sec))
+ print("即時戦略 (質問):" + str(ask))
+ print("UI カバーなし (AskFallback):" + str(fb))
+ print("自動許可スキル:" + ("on" if auto else "off"))
+ exists = d.get("exists", True)
+ if not exists:
+ print("(承認文書は存在しません。システムに組み込まれたセーフティネットを使用してください)")
+except Exception as e:
+ print("(解析に失敗しました:" + str(e) + ")")
+' "$approvals_json"
+ else
+ echo "(openclaw の承認では --json が出力されません)"
+ fi
+ elif [ -f "$HOME/.openclaw/exec-approvals.json" ]; then
+ python3 -c '
+import json, os
+path = os.path.expanduser("~/.openclaw/exec-approvals.json")
+try:
+ with open(path) as f:
+ d = json.load(f).get("defaults", {})
+ print("傍受戦略 (セキュリティ):" + str(d.get("security", "(unset)")))
+ print("即時戦略 (質問):" + str(d.get("ask", "(unset)")))
+ print("UI カバーなし (AskFallback):" + str(d.get("askFallback", "(unset)")))
+except Exception:
+ print("(設定ファイルの解析に失敗しました)")
+'
+ else
+ echo "(未構成、システムの組み込みセキュリティ ポリシーの使用が強制されます)"
+ fi
+ }
+
+ openclaw_permission_apply_standard() {
+ send_stats "OpenClaw 権限 - 標準セキュリティ モード"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "アプリケーション層ポリシーを構成しています..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled false >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval true >/dev/null 2>&1 # 拦截危险的内联代码
+ openclaw config unset commands.bash >/dev/null 2>&1 # 废弃旧版参数
+
+ echo "ホスト承認インターセプトを構成しています..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 標準の安全モードに切り替えられました (すべての危険なコマンドは UI/TG を通じて承認を求めます)${gl_bai}"
+ }
+
+ openclaw_permission_apply_developer() {
+ send_stats "OpenClaw 権限 - 開発拡張モード"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "アプリケーション層ポリシーを構成しています..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1 # 允许智能体申请提权
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "ホスト承認インターセプトを構成しています..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 開発拡張モードに切り替えました (権限昇格は許可されていますが、一般的な危険なコマンドには引き続き承認が必要です)${gl_bai}"
+ }
+
+ openclaw_permission_apply_full() {
+ send_stats "OpenClaw 権限 - 完全オープン モード"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "アプリケーション層ポリシーを構成しています..."
+ openclaw config set tools.profile full >/dev/null 2>&1
+ openclaw config set tools.exec.security full >/dev/null 2>&1
+ openclaw config set tools.exec.ask off >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "ホストの迎撃防御が崩壊中..."
+ # ここでの full と off は、基礎となるホストの実行承認システムを完全にバイパスします。
+ openclaw_permission_update_exec_approvals "full" "off" "full"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 完全オープン モードに切り替えられました (警告: すべてのホスト コマンド インターセプトの有効期限が切れており、エージェントには最高の権限が与えられています)${gl_bai}"
+ }
+
+ openclaw_permission_restore_official_defaults() {
+ send_stats "OpenClaw 権限 - 公式のデフォルトに戻す"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "クリーンなアプリケーション層の適用範囲を強化します..."
+ openclaw config unset tools.profile >/dev/null 2>&1
+ openclaw config unset tools.exec.security >/dev/null 2>&1
+ openclaw config unset tools.exec.ask >/dev/null 2>&1
+ openclaw config unset tools.elevated.enabled >/dev/null 2>&1
+ openclaw config unset tools.exec.strictInlineEval >/dev/null 2>&1
+
+ echo "クリーンなホストインターセプト構成..."
+ # CLI を介して承認設定をクリアすることを優先し、ファイルの直接削除に戻ります。
+ if echo '{"version":1,"defaults":{}}' | openclaw approvals set --stdin >/dev/null 2>&1; then
+ true
+ else
+ rm -f "$HOME/.openclaw/exec-approvals.json"
+ fi
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ OpenClaw 公式セキュリティ サンドボックス防御メカニズムに戻しました${gl_bai}"
+ }
+
+ openclaw_permission_run_audit() {
+ echo "======================================="
+ echo "公式の OpenClaw セキュリティ監査と物理的な監査を実行します。"
+ echo "======================================="
+ openclaw security audit
+ echo "---------------------------------------"
+ read -e -p "発見されたセキュリティ脆弱性を自動的に修復してみますか? (y/n):" fix_choice
+ if [[ "$fix_choice" == "y" || "$fix_choice" == "Y" || "$fix_choice" == "yes" ]]; then
+ openclaw security audit --fix
+ echo -e "${gl_lv}✅ 自動修復が完了しました。${gl_bai}"
+ fi
+ echo "戻るには任意のキーを押してください..."
+ read -n 1 -s
+ }
+
+
+ openclaw_permission_manage_allowlist() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "実行コマンドのホワイトリスト管理"
+ echo "======================================="
+ echo "現在のホワイトリスト:"
+ local allowlist_json
+ allowlist_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$allowlist_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ f = d.get("file", {})
+ agents = f.get("agents", {})
+ found = False
+ for agent_id, agent_data in agents.items():
+ al = agent_data.get("allowlist", [])
+ if al:
+ found = True
+ print("エージェント [%s]:" % agent_id)
+ for item in al:
+ print(" - %s" % item)
+ if not found:
+ print("(空、ホワイトリスト ルールが設定されていません)")
+except Exception as e:
+ print("(解析に失敗しました:" + str(e) + ")")
+' "$allowlist_json"
+ else
+ echo "(入手不可)"
+ fi
+ echo "---------------------------------------"
+ echo "1. ホワイトリストルールを追加する"
+ echo "2. ホワイトリスト ルールを削除する"
+ echo "0.戻る"
+ echo "---------------------------------------"
+ read -e -p "選択してください:" al_choice
+ case "$al_choice" in
+ 1)
+ read -e -p "解放するコマンド パスを入力します (/usr/bin/git などの glob をサポートします)。" pattern
+ [ -z "$pattern" ] && { echo "空にすることはできません"; break_end; continue; }
+ read -e -p "エージェント ID を指定します (空白のまま = すべてのエージェント *):" agent_id
+ agent_id="${agent_id:-*}"
+ openclaw approvals allowlist add --agent "$agent_id" "$pattern"
+ break_end
+ ;;
+ 2)
+ read -e -p "削除するコマンド パスを入力します。" pattern
+ [ -z "$pattern" ] && { echo "空にすることはできません"; break_end; continue; }
+ openclaw approvals allowlist remove "$pattern"
+ break_end
+ ;;
+ 0) return 0 ;;
+ *) echo "無効な選択"; sleep 1 ;;
+ esac
+ done
+ }
+
+ openclaw_permission_menu() {
+ send_stats "OpenClaw権限管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 権限管理 (デュアルレイヤー アーキテクチャの高度な適応)"
+ echo "======================================="
+ openclaw_permission_render_status
+ echo "---------------------------------------"
+ echo -e "${gl_kjlan}1.${gl_bai}標準セキュリティ モードに切り替える (毎日の推奨事項、ポップアップ カードの承認)"
+ echo -e "${gl_kjlan}2.${gl_bai}開発拡張モードに切り替える (エージェントが権限昇格を申請できるようにする)"
+ echo -e "${gl_kjlan}3.${gl_bai}全開モードに切り替えます(${gl_hong}ハイリスク!すべてのホストインターセプトを完全に削除します${gl_bai})"
+ echo -e "${gl_kjlan}4.${gl_bai}公式のデフォルトのサンドボックス防御戦略を復元する"
+ echo -e "${gl_kjlan}5.${gl_bai}基盤となるセキュリティ監査と自動修復を実行する"
+ echo -e "${gl_kjlan}6.${gl_bai}Execコマンドのホワイトリストを管理する"
+ echo -e "${gl_kjlan}0.${gl_bai}前のレベルに戻る"
+ echo "---------------------------------------"
+ read -e -p "選択肢を入力してください:" perm_choice
+ case "$perm_choice" in
+ 1)
+ echo "適用の準備: 標準セーフ モード"
+ read -e -p "確認するには「yes」と入力します。" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_standard; else echo "キャンセル"; fi
+ break_end
+ ;;
+ 2)
+ echo "アプリの準備: 拡張モードの開発"
+ read -e -p "確認するには「yes」と入力します。" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_developer; else echo "キャンセル"; fi
+ break_end
+ ;;
+ 3)
+ echo -e "${gl_hong}⚠️ フルオープンモードでは、実行承認が完全に崩壊し、高リスクコードが自動的にリリースされます。${gl_bai}"
+ read -e -p "続行することを確認するには「FULL」と入力します。" confirm
+ if [ "$confirm" = "FULL" ]; then openclaw_permission_apply_full; else echo "キャンセル"; fi
+ break_end
+ ;;
+ 4)
+ echo "すべてのカスタム オーバーライドがクリアされ、OpenClaw が最初にインストールされたときの厳密にサンドボックス化された状態に戻ります。"
+ read -e -p "確認するには「yes」と入力します。" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_restore_official_defaults; else echo "キャンセル"; fi
+ break_end
+ ;;
+ 5)
+ openclaw_permission_run_audit
+ ;;
+ 6)
+ openclaw_permission_manage_allowlist
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_multiagent_config_file() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ echo "$config_file"
+ return 0
+ fi
+ openclaw config file 2>/dev/null | tail -n 1
+ }
+
+ openclaw_multiagent_default_agent() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+value="(unset)"
+try:
+ with open(path) as f:
+ data=json.load(f)
+ defaults=data.get("agents",{}).get("defaults",{}) if isinstance(data,dict) else {}
+ value=defaults.get("agent") or None
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and (item.get("isDefault") or item.get("default")):
+ value=item.get("id")
+ break
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and item.get("id"):
+ value=item.get("id")
+ break
+except Exception:
+ value="(unset)"
+print(value or "(unset)")
+PY
+ return 0
+ fi
+ local value
+ value=$(openclaw config get agents.defaults.agent 2>&1 | head -n 1)
+ if [ -z "$value" ] || echo "$value" | grep -qi "config path not found"; then
+ value=$(openclaw agents list --json 2>/dev/null | python3 -c 'import json,sys
+try:
+ data=json.load(sys.stdin)
+ print(next((x.get("id","(unset)") for x in data if x.get("isDefault")), "(unset)"))
+except Exception:
+ print("(unset)")' 2>/dev/null)
+ fi
+ [ -z "$value" ] && value="(unset)"
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ }
+
+ openclaw_multiagent_require_openclaw() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ openclaw コマンドが検出されません。最初に OpenClaw をインストールまたは初期化してください。"
+ return 1
+ fi
+ return 0
+ }
+
+ openclaw_multiagent_agents_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents list --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # フォールバック: 構成ファイルからの読み取り
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ agents=data.get("agents",{}).get("list",[])
+ if not isinstance(agents,list):
+ agents=[]
+ print(json.dumps(agents, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_bindings_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents bindings --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # フォールバック: 構成ファイルからの読み取り
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ bindings=data.get("agents",{}).get("bindings",[])
+ if not isinstance(bindings,list):
+ bindings=[]
+ results=[]
+ for item in bindings:
+ if not isinstance(item,dict):
+ continue
+ results.append({"agentId": item.get("agentId") or item.get("agent") or "?", "description": item.get("description") or "-"})
+ print(json.dumps(results, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_sessions_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw sessions --json 2>/dev/null | grep -v '^\[')
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # フォールバック: ファイル システムからの読み取り
+ python3 <<'PY'
+import json,os
+base=os.path.expanduser("~/.openclaw/agents")
+sessions=[]
+try:
+ agent_dirs=[d for d in os.listdir(base) if os.path.isdir(os.path.join(base,d))]
+except Exception:
+ agent_dirs=[]
+for agent_id in agent_dirs:
+ path=os.path.join(base,agent_id,"sessions","sessions.json")
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path) as f:
+ data=json.load(f)
+ except Exception:
+ continue
+ if isinstance(data,dict):
+ items=data.items()
+ elif isinstance(data,list):
+ items=[(item.get("key") or "?", item) for item in data if isinstance(item,dict)]
+ else:
+ continue
+ for key,item in items:
+ if not isinstance(item,dict):
+ continue
+ model=item.get("model") or "-"
+ sessions.append({"agentId": agent_id, "key": key, "model": model})
+print(json.dumps({"path":"(filesystem)","count":len(sessions),"sessions":sessions}, ensure_ascii=False))
+PY
+ }
+
+ openclaw_multiagent_render_status() {
+ local config_file default_agent
+ config_file=$(openclaw_multiagent_config_file)
+ default_agent=$(openclaw_multiagent_default_agent)
+ echo "設定ファイル: ${config_file:-$(openclaw_permission_config_file)}"
+ echo "デフォルトのエージェント:$default_agent"
+ python3 -c '
+import json,sys
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+sess_obj=json.loads(sys.argv[3] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+print("構成されたエージェントの数: %s" % len(agents))
+print("ルート バインディングの数: %s" % len(bindings))
+print("合計セッション数: %s" % len(sessions))
+print("---------------------------------------")
+if not agents:
+ print("現在、マルチエージェントは構成されていません。")
+else:
+ for item in agents[:8]:
+ aid = item.get("id","?")
+ identity = item.get("identityName") or item.get("name") or "-"
+ emoji = item.get("identityEmoji") or ""
+ ws = item.get("workspace") or "-"
+ model = item.get("model") or "-"
+ is_default = item.get("isDefault", False)
+ bcount = item.get("bindings", 0)
+ default_tag = "[デフォルト]" if is_default else ""
+ print("- エージェント ID: \033[1;36m%s\033[0m%s]" % (aid, default_tag))
+ print("アイデンティティ名: %s %s" % (identity, emoji))
+ print("モデル: %s" % model)
+ print("作業ディレクトリ: %s" % ws)
+ print("バインディングの数: %s" % bcount)
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)" "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_list_agents() {
+ send_stats "OpenClaw マルチエージェント - エージェントのリスト"
+ python3 -c 'import json,sys; agents=json.loads(sys.argv[1] or "[]");
+if not agents: print("エージェントはまだ設定されていません。"); raise SystemExit(0)
+for idx,item in enumerate(agents,1):
+ print("%s. %s" % (idx, item.get("id","?"))); print(" workspace : %s" % item.get("workspace","-")); ident=(item.get("identityName") or "-") + ((" " + item.get("identityEmoji")) if item.get("identityEmoji") else ""); print(" identity : %s" % ident.strip()); print(" model : %s" % (item.get("model") or "-")); print(" bindings : %s" % item.get("bindings",0)); print(" default : %s" % ("yes" if item.get("isDefault") else "no"))' "$(openclaw_multiagent_agents_json)"
+ }
+
+ openclaw_multiagent_add_agent() {
+ send_stats "OpenClaw マルチエージェント - 新しいエージェント"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id workspace confirm
+ read -e -p "新しいエージェント ID を入力してください:" agent_id
+ [ -z "$agent_id" ] && echo "キャンセル済み: エージェント ID を空にすることはできません。" && return 1
+ read -e -p "ワークスペースのパス (デフォルトは ~/.openclaw/workspace-) を入力してください。${agent_id}): " workspace
+ [ -z "$workspace" ] && workspace="~/.openclaw/workspace-${agent_id}"
+ echo "エージェントが作成されます。$agent_id"
+ echo "作業ディレクトリ:$workspace"
+ read -e -p "続行することを確認するには「yes」と入力します。" confirm
+ [ "$confirm" = "yes" ] || { echo "キャンセル"; return 1; }
+ if openclaw agents add "$agent_id" --workspace "$workspace"; then
+ echo "✅ エージェントが正常に作成されました:$agent_id"
+ local name theme
+ read -e -p "エージェント ID 名を入力してください (例: コード エキスパート):" name
+ [ -z "$name" ] && name="$agent_id"
+ read -e -p "エージェントの性格テーマを入力してください (例: 厳格、効率的):" theme
+ [ -z "$theme" ] && theme="アシスタント"
+ echo "エージェント ID を構成しています..."
+ openclaw agents set-identity --agent "$agent_id" --name "$name" --theme "$theme"
+ else
+ echo "❌ エージェントの作成に失敗しました"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_delete_agent() {
+ send_stats "OpenClaw マルチエージェント - エージェントの削除"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id confirm
+ read -e -p "削除するエージェント ID を入力してください:" agent_id
+ [ -z "$agent_id" ] && echo "キャンセル済み: エージェント ID を空にすることはできません。" && return 1
+ echo "⚠️ エージェントを削除すると、その作業ディレクトリ、ルート バインディング、およびセッション ルーティングに影響する可能性があります。"
+ read -e -p "「DELETE」と入力して削除を確認します${agent_id}: " confirm
+ [ "$confirm" = "DELETE" ] || { echo "キャンセル"; return 1; }
+ if openclaw agents delete "$agent_id"; then
+ echo "✅ エージェントは正常に削除されました:$agent_id"
+ else
+ echo "❌ エージェントの削除に失敗しました"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_list_bindings() {
+ send_stats "OpenClaw マルチエージェント - ルーティング バインディングの表示"
+ python3 -c '
+import json,sys
+bindings=json.loads(sys.argv[1] or "[]")
+if not bindings:
+ print("まだルート バインディングはありません。")
+ raise SystemExit(0)
+for idx,item in enumerate(bindings,1):
+ desc = item.get("description") or "-"
+ print("%s. agent=%s | %s" % (idx, item.get("agentId","?"), desc))
+' "$(openclaw_multiagent_bindings_json)"
+ }
+
+ openclaw_multiagent_add_binding() {
+ send_stats "OpenClaw マルチエージェント - 新しいルーティング バインディング"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "エージェント ID を入力してください:" agent_id
+ read -e -p "ルート バインディング値 (telegram:ops / discord:guild-a など) を入力してください。" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "キャンセル: パラメータを空にすることはできません。" && return 1
+ echo "エージェントをバインドします [$agent_id] -> [$bind_value]"
+ read -e -p "続行することを確認するには「yes」と入力します。" confirm
+ [ "$confirm" = "yes" ] || { echo "キャンセル"; return 1; }
+ if openclaw agents bind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ ルートバインディングが正常に追加されました"
+ else
+ echo "❌ ルート バインディングの追加に失敗しました"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_remove_binding() {
+ send_stats "OpenClaw マルチエージェント - ルート バインディングの削除"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "エージェント ID を入力してください:" agent_id
+ read -e -p "削除するルート バインディング値を入力してください:" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "キャンセル: パラメータを空にすることはできません。" && return 1
+ echo "エージェントを削除します [$agent_id] ルート バインディング [$bind_value]"
+ read -e -p "続行することを確認するには「yes」と入力します。" confirm
+ [ "$confirm" = "yes" ] || { echo "キャンセル"; return 1; }
+ if openclaw agents unbind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ ルートバインディングが正常に削除されました"
+ else
+ echo "❌ ルートバインディングの削除に失敗しました"
+ return 1
+ fi
+ }
+
+
+ openclaw_multiagent_show_sessions() {
+ send_stats "OpenClaw マルチエージェント - セッションの概要"
+ python3 -c '
+import json,sys
+sess_obj=json.loads(sys.argv[1] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+if not sessions:
+ print("まだセッション データがありません。")
+ raise SystemExit(0)
+by_agent={}
+for item in sessions:
+ aid = item.get("agentId","?")
+ by_agent[aid] = by_agent.get(aid, 0) + 1
+print("セッションの概要:")
+for agent_id,count in sorted(by_agent.items()):
+ print("- %s: %s" % (agent_id, count))
+print("---------------------------------------")
+for item in sessions[:10]:
+ key = item.get("key","-")
+ model = item.get("model") or "-"
+ aid = item.get("agentId","?")
+ tokens = ""
+ it = item.get("inputTokens")
+ ot = item.get("outputTokens")
+ if it is not None:
+ tokens = " | in=%s out=%s" % (it, ot or 0)
+ print("%s | %s | %s%s" % (aid, key, model, tokens))
+' "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_health_check() {
+ send_stats "OpenClaw マルチエージェントヘルスチェック"
+ openclaw_multiagent_require_openclaw || return 1
+ local config_file
+ config_file=$(openclaw_multiagent_config_file)
+ echo "設定ファイルを確認します: ${config_file:-$(openclaw_permission_config_file)}"
+ openclaw config validate || echo "⚠️ 構成の検証に失敗しました。上記の出力を確認してください。"
+ python3 -c '
+import json,sys,os
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+print("---------------------------------------")
+if not agents:
+ print("⚠️ 設定されたエージェントが見つかりません。")
+else:
+ for item in agents:
+ ws = item.get("workspace") or ""
+ aid = item.get("id","?")
+ if ws and os.path.isdir(os.path.expanduser(ws)):
+ state = "OK"
+ elif aid == "main":
+ state = "OK"
+ else:
+ state = "MISSING"
+ model = item.get("model") or "-"
+ bcount = item.get("bindings", 0)
+ print("agent=%s workspace=%s model=%s bindings=%s [%s]" % (aid, ws or "-", model, bcount, state))
+print("ルート バインディングの数 = %s" % len(bindings))
+print("✅マルチエージェントのヘルスチェックが完了しました")
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)"
+ echo ""
+ echo "セキュリティ監査を実行します..."
+ openclaw security audit 2>/dev/null || echo "⚠️ セキュリティ監査コマンドは使用できません"
+ }
+
+
+ openclaw_multiagent_set_identity() {
+ openclaw_multiagent_require_openclaw || return 1
+ openclaw_multiagent_list_agents
+ read -e -p "ID を変更するエージェントの ID を入力します。" agent_id
+ [ -z "$agent_id" ] && { echo "ID を空にすることはできません"; return 1; }
+ echo "オプションを変更します (スキップするには空白のままにします):"
+ read -e -p "新しい名前:" new_name
+ read -e -p "新しい絵文字:" new_emoji
+ local cmd="openclaw agents set-identity --agent $agent_id"
+ [ -n "$new_name" ] && cmd="$cmd --name $new_name"
+ [ -n "$new_emoji" ] && cmd="$cmd --emoji $new_emoji"
+ echo "IDENTITY.md から ID 情報を自動的に読み取ることもできます。"
+ read -e -p "IDENTITY.md から読み取りますか? (y/n):" from_id
+ if [ "$from_id" = "y" ]; then
+ cmd="openclaw agents set-identity --agent $agent_id --from-identity"
+ fi
+ eval "$cmd"
+ }
+
+ openclaw_multiagent_cleanup_sessions() {
+ openclaw_multiagent_require_openclaw || return 1
+ echo "期限切れ/冗長セッション データを間もなくクリーンアップします..."
+ read -e -p "確認するには「yes」と入力します。" confirm
+ [ "$confirm" != "yes" ] && { echo "キャンセル"; return 0; }
+ openclaw sessions cleanup
+ }
+
+ openclaw_multiagent_menu() {
+ send_stats "OpenClaw マルチエージェント管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw マルチエージェント管理"
+ echo "======================================="
+ openclaw_multiagent_render_status
+ echo "---------------------------------------"
+ echo "1. 新しいインテリジェント エージェントを追加する"
+ echo "2. エージェントを削除する"
+ echo "3. ルーティング バインディングを表示する"
+ echo "4. 新しいルート バインディングを追加する"
+ echo "5.ルートバインディングを削除する"
+ echo "6. セッションの概要を表示する"
+ echo "7. マルチエージェントのヘルスチェックを実行する"
+ echo "8. エージェントのアイデンティティ (名前/絵文字) を変更します"
+ echo "9. 期限切れのセッションをクリーンアップする"
+ echo "0. 前のレベルに戻ります"
+ echo "---------------------------------------"
+ read -e -p "選択肢を入力してください:" multi_choice
+ case "$multi_choice" in
+ 1) openclaw_multiagent_add_agent; break_end ;;
+ 2) openclaw_multiagent_delete_agent; break_end ;;
+ 3) openclaw_multiagent_list_bindings; break_end ;;
+ 4) openclaw_multiagent_add_binding; break_end ;;
+ 5) openclaw_multiagent_remove_binding; break_end ;;
+ 6) openclaw_multiagent_show_sessions; break_end ;;
+ 7) openclaw_multiagent_health_check; break_end ;;
+ 8) openclaw_multiagent_set_identity; break_end ;;
+ 9) openclaw_multiagent_cleanup_sessions; break_end ;;
+ 0) return 0 ;;
+ *) echo "選択が無効です。もう一度お試しください。"; sleep 1 ;;
+ esac
+ done
+ }
+
+
+openclaw_backup_restore_menu() {
+
+ send_stats "OpenClaw のバックアップと復元"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw のバックアップと復元"
+ echo "======================================="
+ openclaw_backup_render_file_list
+ echo "---------------------------------------"
+ echo "1. メモリ全体をバックアップする"
+ echo "2. メモリ全体を復元する"
+ echo "3. OpenClaw プロジェクトをバックアップします (デフォルトのセーフ モード)"
+ echo "4. OpenClaw プロジェクトの復元 (高度/高リスク)"
+ echo "5. バックアップファイルを削除する"
+ echo "0. 前のレベルに戻ります"
+ echo "---------------------------------------"
+ read -e -p "選択肢を入力してください:" backup_choice
+
+ case "$backup_choice" in
+ 1) openclaw_memory_backup_export ;;
+ 2) openclaw_memory_backup_import ;;
+ 3) openclaw_project_backup_export ;;
+ 4) openclaw_project_backup_import ;;
+ 5) openclaw_backup_delete_file ;;
+ 0) return 0 ;;
+ *)
+ echo "選択が無効です。もう一度お試しください。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ update_moltbot() {
+ echo "OpenClaw を更新..."
+ send_stats "OpenClaw を更新..."
+ install_node_and_tools
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+ npm install -g openclaw@latest
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ start_gateway
+ hash -r
+ add_app_id
+ echo "アップデート完了"
+ break_end
+ }
+
+
+ uninstall_moltbot() {
+ echo "OpenClaw をアンインストールします..."
+ send_stats "OpenClaw をアンインストールします..."
+ openclaw uninstall
+ npm uninstall -g openclaw
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ rm -rf "$HOME/.openclaw"
+ [ "$HOME" != "/root" ] && [ -d /root/.openclaw ] && echo "⚠️ ルートディレクトリに /root/.openclaw がまだ存在していることが検出されます。掃除が必要な場合は手動で行ってください。"
+ hash -r
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "アンインストールが完了しました"
+ break_end
+ }
+
+ nano_openclaw_json() {
+ send_stats "OpenClaw 設定ファイルを編集する"
+ install nano
+ nano "$(openclaw_get_config_file)"
+ start_gateway
+ }
+
+
+
+
+
+
+ openclaw_find_webui_domain() {
+ local conf domain_list
+
+ domain_list=$(
+ grep -R "18789" /home/web/conf.d/*.conf 2>/dev/null \
+ | awk -F: '{print $1}' \
+ | sort -u \
+ | while read conf; do
+ basename "$conf" .conf
+ done
+ )
+
+ if [ -n "$domain_list" ]; then
+ echo "$domain_list"
+ fi
+ }
+
+
+
+ openclaw_show_webui_addr() {
+ local local_ip token domains
+
+ echo "=================================="
+ echo "OpenClaw WebUI アクセス アドレス"
+ local_ip="127.0.0.1"
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+ echo
+ echo "ローカルアドレス:"
+ echo "http://${local_ip}:18789/#token=${token}"
+
+ domains=$(openclaw_find_webui_domain)
+ if [ -n "$domains" ]; then
+ echo "ドメイン名アドレス:"
+ echo "$domains" | while read d; do
+ echo "https://${d}/#token=${token}"
+ done
+ fi
+
+ echo "=================================="
+ }
+
+
+
+ # ドメイン名を追加します(指定した関数を呼び出します)
+ openclaw_domain_webui() {
+ add_yuming
+ ldnmp_Proxy ${yuming} 127.0.0.1 18789
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+
+ clear
+ echo "訪問先住所:"
+ echo "https://${yuming}/#token=$token"
+ echo "まず URL にアクセスしてデバイス ID をトリガーし、Enter キーを押してペアリングを続行します。"
+ read
+ echo -e "${gl_kjlan}デバイスリストをロード中...${gl_bai}"
+ # ドメイン名を allowedOrigins に自動的に追加する
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ new_origin="https://${yuming}"
+ # jq を使用して JSON を安全に変更し、構造が存在し、ドメイン名が繰り返し追加されないようにします。
+ if command -v jq >/dev/null 2>&1; then
+ tmp_json=$(mktemp)
+ jq 'if .gateway.controlUi == null then .gateway.controlUi = {"allowedOrigins": ["http://127.0.0.1"]} else . end | if (.gateway.controlUi.allowedOrigins | contains([$origin]) | not) then .gateway.controlUi.allowedOrigins += [$origin] else . end' --arg origin "$new_origin" "$config_file" > "$tmp_json" && mv "$tmp_json" "$config_file"
+ echo -e "${gl_kjlan}ドメイン名は${yuming}allowedOrigins 構成を追加する${gl_bai}"
+ openclaw gateway restart >/dev/null 2>&1
+ fi
+ fi
+
+ openclaw devices list
+
+ read -e -p "Request_Key を入力してください:" Request_Key
+
+ [ -z "$Request_Key" ] && {
+ echo "Request_Key を空にすることはできません"
+ return 1
+ }
+
+ openclaw devices approve "$Request_Key"
+
+ }
+
+ # ドメイン名の削除
+ openclaw_remove_domain() {
+ echo "ドメイン名の形式 example.com (https:// なし)"
+ web_del
+ }
+
+ # メインメニュー
+ openclaw_webui_menu() {
+
+ send_stats "WebUI へのアクセスと設定"
+ while true; do
+ clear
+ openclaw_show_webui_addr
+ echo
+ echo "1. ドメイン名アクセスを追加する"
+ echo "2. ドメイン名アクセスの削除"
+ echo "0.終了"
+ echo
+ read -e -p "選択してください:" choice
+
+ case "$choice" in
+ 1)
+ openclaw_domain_webui
+ echo
+ read -p "Enter を押してメニューに戻ります..."
+ ;;
+ 2)
+ openclaw_remove_domain
+ read -p "Enter を押してメニューに戻ります..."
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "無効なオプション"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ # メインループ
+ while true; do
+ show_menu
+ read choice
+ case $choice in
+ 1) install_moltbot ;;
+ 2) start_bot ;;
+ 3) stop_bot ;;
+ 4) view_logs ;;
+ 5) change_model ;;
+ 6) openclaw_api_manage_menu ;;
+ 7) change_tg_bot_code ;;
+ 8) install_plugin ;;
+ 9) install_skill ;;
+ 10) nano_openclaw_json ;;
+ 11) send_stats "初期設定ウィザード"
+ openclaw onboard --install-daemon
+ break_end
+ ;;
+ 12) send_stats "健全性の検出と修復"
+ openclaw doctor --fix
+ break_end
+ ;;
+ 13) openclaw_webui_menu ;;
+ 14) send_stats "TUI コマンドラインでの会話"
+ openclaw tui
+ break_end
+ ;;
+ 15) openclaw_memory_menu ;;
+ 16) openclaw_permission_menu ;;
+ 17) openclaw_multiagent_menu ;;
+ 18) openclaw_backup_restore_menu ;;
+ 19) update_moltbot ;;
+ 20) uninstall_moltbot ;;
+ *) break ;;
+ esac
+ done
+
+}
+
+
+
+
+linux_panel() {
+
+local sub_choice="$1"
+
+clear
+cd ~
+install git
+echo -e "${gl_kjlan}アプリケーションリストは更新中です。お待ちください...${gl_bai}"
+if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+fi
+
+while true; do
+
+ if [ -z "$sub_choice" ]; then
+ clear
+ echo -e "アプリケーション市場"
+ echo -e "${gl_kjlan}-------------------------"
+
+ local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+
+ # ループで色を設定する
+ for i in {1..150}; do
+ if echo "$app_numbers" | grep -q "^$i$"; then
+ declare "color$i=${gl_lv}"
+ else
+ declare "color$i=${gl_bai}"
+ fi
+ done
+
+ echo -e "${gl_kjlan}1. ${color1}パゴダパネル正式版${gl_kjlan}2. ${color2}aaPanel パゴダ国際版"
+ echo -e "${gl_kjlan}3. ${color3}1Panel 新世代管理パネル${gl_kjlan}4. ${color4}NginxProxyManager 視覚化パネル"
+ echo -e "${gl_kjlan}5. ${color5}OpenList マルチストア ファイル リスト プログラム${gl_kjlan}6. ${color6}Ubuntu リモート デスクトップ Web エディション"
+ echo -e "${gl_kjlan}7. ${color7}Nezha Probe VPS 監視パネル${gl_kjlan}8. ${color8}QBオフラインBT磁気ダウンロードパネル"
+ echo -e "${gl_kjlan}9. ${color9}Poste.io メール サーバー プログラム${gl_kjlan}10. ${color10}RocketChat 複数人オンライン チャット システム"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}11. ${color11}ZenTao プロジェクト管理ソフトウェア${gl_kjlan}12. ${color12}Qinglong パネルのスケジュールされたタスク管理プラットフォーム"
+ echo -e "${gl_kjlan}13. ${color13}Cloudreve ネットワークディスク${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}シンプルなピクチャーベッド画像管理プログラム"
+ echo -e "${gl_kjlan}15. ${color15}emby マルチメディア管理システム${gl_kjlan}16. ${color16}Speedtest スピードテストパネル"
+ echo -e "${gl_kjlan}17. ${color17}AdGuardHome はアドウェアを削除します${gl_kjlan}18. ${color18}Onlyofficeオンラインオフィス OFFICE"
+ echo -e "${gl_kjlan}19. ${color19}Leichi WAF ファイアウォール パネル${gl_kjlan}20. ${color20}ポーターコンテナ管理パネル"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}21. ${color21}VScode Web バージョン${gl_kjlan}22. ${color22}UptimeKuma監視ツール"
+ echo -e "${gl_kjlan}23. ${color23}メモウェブメモ${gl_kjlan}24. ${color24}Webtop リモート デスクトップ Web バージョン${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}25. ${color25}Nextcloud ネットワーク ディスク${gl_kjlan}26. ${color26}QD-Today スケジュールされたタスク管理フレームワーク"
+ echo -e "${gl_kjlan}27. ${color27}Dockge コンテナ スタック管理パネル${gl_kjlan}28. ${color28}LibreSpeed 速度テストツール"
+ echo -e "${gl_kjlan}29. ${color29}searxng 集約検索ステーション${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrismプライベートアルバムシステム"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}31. ${color31}StirlingPDF ツール コレクション${gl_kjlan}32. ${color32}無料のオンライングラフ作成ソフトウェアdrawio${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${color33}Sun-Panel ナビゲーション パネル${gl_kjlan}34. ${color34}Pingvin-Share ファイル共有プラットフォーム"
+ echo -e "${gl_kjlan}35. ${color35}ミニマリストの友達の輪${gl_kjlan}36. ${color36}LobeChatAIチャットアグリゲーションサイト"
+ echo -e "${gl_kjlan}37. ${color37}MyIP ツールボックス${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}Xiaoya alistファミリーバケット"
+ echo -e "${gl_kjlan}39. ${color39}Bililive ライブ配信録画ツール${gl_kjlan}40. ${color40}webssh Web版 SSH接続ツール"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}41. ${color41}マウス管理パネル${gl_kjlan}42. ${color42}Nexterm リモート接続ツール"
+ echo -e "${gl_kjlan}43. ${color43}RustDesk リモート デスクトップ (サーバー)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk リモート デスクトップ (リレー)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}45. ${color45}Docker アクセラレーション ステーション${gl_kjlan}46. ${color46}GitHub アクセラレーション ステーション${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}47. ${color47}プロメテウスの監視${gl_kjlan}48. ${color48}Prometheus (ホスト監視)"
+ echo -e "${gl_kjlan}49. ${color49}Prometheus (コンテナ監視)${gl_kjlan}50. ${color50}補充監視ツール"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}51. ${color51}PVEオープンチックパネル${gl_kjlan}52. ${color52}DPanel コンテナ管理パネル"
+ echo -e "${gl_kjlan}53. ${color53}llama3チャットAI大型モデル${gl_kjlan}54. ${color54}AMH ホスト Web サイト構築管理パネル"
+ echo -e "${gl_kjlan}55. ${color55}FRPイントラネット普及(サーバー)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRPイントラネット普及(クライアント)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}57. ${color57}ディープシークチャットAI大型モデル${gl_kjlan}58. ${color58}Dify 大規模モデルのナレッジ ベース${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}59. ${color59}NewAPI 大型モデル資産管理${gl_kjlan}60. ${color60}JumpServer オープンソース要塞マシン"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}61. ${color61}オンライン翻訳サーバー${gl_kjlan}62. ${color62}RAGFlow 大規模モデルのナレッジ ベース"
+ echo -e "${gl_kjlan}63. ${color63}OpenWebUI セルフホスト型 AI プラットフォーム${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}ITツールツールボックス"
+ echo -e "${gl_kjlan}65. ${color65}n8n自動ワークフロープラットフォーム${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp ビデオ ダウンロード ツール"
+ echo -e "${gl_kjlan}67. ${color67}ddns-go ダイナミック DNS 管理ツール${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL 証明書管理プラットフォーム"
+ echo -e "${gl_kjlan}69. ${color69}SFTPGo ファイル転送ツール${gl_kjlan}70. ${color70}AstrBot チャットボット フレームワーク"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}71. ${color71}Navidrome プライベート ミュージック サーバー${gl_kjlan}72. ${color72}bitwarden パスワードマネージャー${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}73. ${color73}LibreTV プライベートムービー${gl_kjlan}74. ${color74}MoonTV のプライベート ムービー"
+ echo -e "${gl_kjlan}75. ${color75}メロディー音楽の魔法使い${gl_kjlan}76. ${color76}オンライン DOS 古いゲーム"
+ echo -e "${gl_kjlan}77. ${color77}Thunder オフライン ダウンロード ツール${gl_kjlan}78. ${color78}PandaWiki インテリジェント文書管理システム"
+ echo -e "${gl_kjlan}79. ${color79}Beszel サーバーの監視${gl_kjlan}80. ${color80}リンクワーデンのブックマーク管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}81. ${color81}JitsiMeet ビデオ会議${gl_kjlan}82. ${color82}gpt-load 高性能 AI 透過プロキシ"
+ echo -e "${gl_kjlan}83. ${color83}komariサーバー監視ツール${gl_kjlan}84. ${color84}Wallos の個人財務管理ツール"
+ echo -e "${gl_kjlan}85. ${color85}イミッチ・ピクチャー・ビデオ・マネージャー${gl_kjlan}86. ${color86}ジェリーフィンメディア管理システム"
+ echo -e "${gl_kjlan}87. ${color87}SyncTV は一緒に映画を見るための素晴らしいツールです${gl_kjlan}88. ${color88}Owncast の自己ホスト型ライブ ストリーミング プラットフォーム"
+ echo -e "${gl_kjlan}89. ${color89}FileCodeBox ファイルエクスプレス${gl_kjlan}90. ${color90}マトリックス分散型チャットプロトコル"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}91. ${color91}gitea プライベート コード リポジトリ${gl_kjlan}92. ${color92}FileBrowser ファイルマネージャー"
+ echo -e "${gl_kjlan}93. ${color93}Dufs のミニマリスト静的ファイル サーバー${gl_kjlan}94. ${color94}Gopeed高速ダウンロードツール"
+ echo -e "${gl_kjlan}95. ${color95}ペーパーレス文書管理プラットフォーム${gl_kjlan}96. ${color96}2FAuth セルフホスト型 2 段階認証システム"
+ echo -e "${gl_kjlan}97. ${color97}WireGuard ネットワーキング (サーバー)${gl_kjlan}98. ${color98}WireGuard ネットワーキング (クライアント)"
+ echo -e "${gl_kjlan}99. ${color99}DSM Synology 仮想マシン${gl_kjlan}100. ${color100}Syncthing ピアツーピア ファイル同期ツール"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}101. ${color101}AI動画生成ツール${gl_kjlan}102. ${color102}VoceChat 複数人オンライン チャット システム"
+ echo -e "${gl_kjlan}103. ${color103}Umami ウェブサイト統計ツール${gl_kjlan}104. ${color104}ストリーム 4 層プロキシ転送ツール"
+ echo -e "${gl_kjlan}105. ${color105}思源ノート${gl_kjlan}106. ${color106}Drawnix オープンソース ホワイトボード ツール"
+ echo -e "${gl_kjlan}107. ${color107}PanSou ネットワークディスク検索${gl_kjlan}108. ${color108}LangBot チャットボット"
+ echo -e "${gl_kjlan}109. ${color109}ZFileオンラインネットワークディスク${gl_kjlan}110. ${color110}カラオケのブックマーク管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}111. ${color111}マルチフォーマットファイル変換ツール${gl_kjlan}112. ${color112}Lucky 大規模イントラネット侵入ツール"
+ echo -e "${gl_kjlan}113. ${color113}Firefoxブラウザ${gl_kjlan}114. ${color114}OpenClaw ボット管理ツール${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}115. ${color115}ヘルメスロボット管理ツール${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}サードパーティ製アプリケーションのリスト"
+ echo -e "${gl_kjlan}あなたのアプリをここに表示したいですか?開発者ガイドを確認してください。${gl_huang}https://dev.kejilion.sh/${gl_bai}"
+
+ for f in "$HOME"/apps/*.conf; do
+ [ -e "$f" ] || continue
+ local base_name=$(basename "$f" .conf)
+ # アプリの説明を取得する
+ local app_text=$(grep "app_text=" "$f" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
+
+ # インストールステータスを確認します (appno.txt の ID と一致します)。
+ # ここでは、appno.txtに記録されているのはbase_name(つまりファイル名)であると仮定します。
+ if echo "$app_numbers" | grep -q "^$base_name$"; then
+ # インストールされている場合: showbase_name - description [インストール済み] (緑色)
+ echo -e "${gl_kjlan}$base_name${gl_bai} - ${gl_lv}$app_text[インストール済み]${gl_bai}"
+ else
+ # インストールされていない場合:通常通り表示
+ echo -e "${gl_kjlan}$base_name${gl_bai} - $app_text"
+ fi
+ done
+
+
+
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}b. ${gl_bai}すべてのアプリケーション データをバックアップする${gl_kjlan}r. ${gl_bai}すべてのアプリデータを復元する"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "選択肢を入力してください:" sub_choice
+ fi
+
+ case $sub_choice in
+ 1|bt|baota)
+ local app_id="1"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="パゴダパネル"
+ local panelurl="https://www.bt.cn/new/index.html"
+
+ panel_app_install() {
+ if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+
+
+ ;;
+ 2|aapanel)
+
+
+ local app_id="2"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="aapanel"
+ local panelurl="https://www.aapanel.com/new/index.html"
+
+ panel_app_install() {
+ URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+ ;;
+ 3|1p|1panel)
+
+ local app_id="3"
+ local lujing="command -v 1pctl"
+ local panelname="1Panel"
+ local panelurl="https://1panel.cn/"
+
+ panel_app_install() {
+ install bash
+ bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
+ }
+
+ panel_app_manage() {
+ 1pctl user-info
+ 1pctl update password
+ }
+
+ panel_app_uninstall() {
+ 1pctl uninstall
+ }
+
+ install_panel
+
+ ;;
+ 4|npm)
+
+ local app_id="4"
+ local docker_name="npm"
+ local docker_img="jc21/nginx-proxy-manager:latest"
+ local docker_port=81
+
+ docker_rum() {
+
+ docker run -d \
+ --name=$docker_name \
+ -p ${docker_port}:81 \
+ -p 80:80 \
+ -p 443:443 \
+ -v /home/docker/npm/data:/data \
+ -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
+ --restart=always \
+ $docker_img
+
+
+ }
+
+ local docker_describe="ドメイン名アクセスの追加をサポートしていない Nginx リバース プロキシ ツール パネル。"
+ local docker_url="公式サイト紹介:https://nginxproxymanager.com/"
+ local docker_use="echo \"初期ユーザー名: admin@example.com\""
+ local docker_passwd="echo \"初期パスワード:changeme\""
+ local app_size="1"
+
+ docker_app
+
+ ;;
+
+ 5|openlist)
+
+ local app_id="5"
+ local docker_name="openlist"
+ local docker_img="openlistteam/openlist:latest-aria2"
+ local docker_port=5244
+
+ docker_rum() {
+
+ mkdir -p /home/docker/openlist
+ chmod -R 777 /home/docker/openlist
+
+ docker run -d \
+ --restart=always \
+ -v /home/docker/openlist:/opt/openlist/data \
+ -p ${docker_port}:5244 \
+ -e PUID=0 \
+ -e PGID=0 \
+ -e UMASK=022 \
+ --name="openlist" \
+ openlistteam/openlist:latest-aria2
+
+ }
+
+
+ local docker_describe="gin と Solidjs を利用した、複数のストレージ、Web ブラウジング、WebDAV をサポートするファイル一覧プログラム"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/OpenListTeam/OpenList"
+ local docker_use="docker exec openlist ./openlist admin random"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 6|webtop-ubuntu)
+
+ local app_id="6"
+ local docker_name="webtop-ubuntu"
+ local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
+ local docker_port=3006
+
+ docker_rum() {
+
+ read -e -p "ログインユーザー名を設定します:" admin
+ read -e -p "ログインユーザーのパスワードを設定します。" admin_password
+ docker run -d \
+ --name=webtop-ubuntu \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:ubuntu-kde
+
+
+ }
+
+
+ local docker_describe="webtop は Ubuntu ベースのコンテナです。 IP にアクセスできない場合は、アクセス用のドメイン名を追加してください。"
+ local docker_url="公式Webサイトの紹介:https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+ 7|nezha)
+ clear
+ send_stats "ネザを構築する"
+
+ local app_id="7"
+ local docker_name="nezha-dashboard"
+ local docker_port=8008
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+ clear
+ echo -e "ネザモニタリング$check_docker $update_status"
+ echo "オープンソースの軽量で使いやすいサーバー監視および運用保守ツール"
+ echo "公式 Web サイト構築ドキュメント: https://nezha.wiki/guide/dashboard.html"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 使用する"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択内容を入力してください:" choice
+
+ case $choice in
+ 1)
+ check_disk_space 1
+ install unzip jq
+ install_docker
+ curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+ ;;
+
+ 8|qb|QB)
+
+ local app_id="8"
+ local docker_name="qbittorrent"
+ local docker_img="lscr.io/linuxserver/qbittorrent:latest"
+ local docker_port=8081
+
+ docker_rum() {
+
+ docker run -d \
+ --name=qbittorrent \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e WEBUI_PORT=${docker_port} \
+ -e TORRENTING_PORT=56881 \
+ -p ${docker_port}:${docker_port} \
+ -p 56881:56881 \
+ -p 56881:56881/udp \
+ -v /home/docker/qbittorrent/config:/config \
+ -v /home/docker/qbittorrent/downloads:/downloads \
+ --restart=always \
+ lscr.io/linuxserver/qbittorrent:latest
+
+ }
+
+ local docker_describe="qbittorrent オフライン BT 磁気ダウンロード サービス"
+ local docker_url="公式サイト紹介:https://hub.docker.com/r/linuxserver/qbittorrent"
+ local docker_use="sleep 3"
+ local docker_passwd="docker logs qbittorrent"
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 9|mail)
+ send_stats "郵便局を建てる"
+ clear
+ install telnet
+ local app_id="9"
+ local docker_name=“mailserver”
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+
+ clear
+ echo -e "郵便サービス$check_docker $update_status"
+ echo "poste.io はオープンソースのメール サーバー ソリューションです。"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+
+ echo ""
+ echo "ポート検出"
+ port=25
+ timeout=3
+ if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
+ echo -e "${gl_lv}ポート$port現在利用可能${gl_bai}"
+ else
+ echo -e "${gl_hong}ポート$port現在利用不可${gl_bai}"
+ fi
+ echo ""
+
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ yuming=$(cat /home/docker/mail.txt)
+ echo "訪問先住所:"
+ echo "https://$yuming"
+ fi
+
+ echo "------------------------"
+ echo "1. インストール 2. アップデート 3. アンインストール"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択内容を入力してください:" choice
+
+ case $choice in
+ 1)
+ setup_docker_dir
+ check_disk_space 2 /home/docker
+ read -e -p "電子メールのドメイン名を設定してください (例: mail.yuming.com)。" yuming
+ mkdir -p /home/docker
+ echo "$yuming" > /home/docker/mail.txt
+ echo "------------------------"
+ ip_address
+ echo "まずこれらの DNS レコードを解析します"
+ echo "A mail $ipv4_address"
+ echo "CNAME imap $yuming"
+ echo "CNAME pop $yuming"
+ echo "CNAME smtp $yuming"
+ echo "MX @ $yuming"
+ echo "TXT @ v=spf1 mx ~all"
+ echo "TXT ? ?"
+ echo ""
+ echo "------------------------"
+ echo "続行するには任意のキーを押してください..."
+ read -n 1 -s -r -p ""
+
+ install jq
+ install_docker
+
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.io
+
+
+ add_app_id
+
+ clear
+ echo "poste.ioがインストールされました"
+ echo "------------------------"
+ echo "次のアドレスを使用して poste.io にアクセスできます。"
+ echo "https://$yuming"
+ echo ""
+
+ ;;
+
+ 2)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.i
+ yuming=$(cat /home/docker/mail.txt)
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.i
+
+
+ add_app_id
+
+ clear
+ echo "poste.ioがインストールされました"
+ echo "------------------------"
+ echo "次のアドレスを使用して poste.io にアクセスできます。"
+ echo "https://$yuming"
+ echo ""
+ ;;
+ 3)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.io
+ rm /home/docker/mail.txt
+ rm -rf /home/docker/mail
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "アプリがアンインストールされました"
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 10|rocketchat)
+
+ local app_id="10"
+ local app_name="Rocket.Chat チャット システム"
+ local app_text="Rocket.Chat は、リアルタイム チャット、音声およびビデオ通話、ファイル共有、その他の機能をサポートするオープンソースのチーム コミュニケーション プラットフォームです。"
+ local app_url="公式紹介:https://www.rocket.chat/"
+ local docker_name="rocketchat"
+ local docker_port="3897"
+ local app_size="2"
+
+ docker_app_install() {
+ docker run --name db -d --restart=always \
+ -v /home/docker/mongo/dump:/dump \
+ mongo:latest --replSet rs5 --oplogSize 256
+ sleep 1
+ docker exec db mongosh --eval "printjson(rs.initiate())"
+ sleep 5
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+
+ clear
+ ip_address
+ echo "インストール完了"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat:latest
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ clear
+ ip_address
+ echo "rocket.chat がインストールされました"
+ check_docker_app_ip
+ }
+
+ docker_app_uninstall() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat
+ docker rm -f db
+ docker rmi -f mongo:latest
+ rm -rf /home/docker/mongo
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 11|zentao)
+ local app_id="11"
+ local docker_name="zentao-server"
+ local docker_img="idoop/zentao:latest"
+ local docker_port=82
+
+
+ docker_rum() {
+
+
+ docker run -d -p ${docker_port}:80 \
+ -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
+ -e BIND_ADDRESS="false" \
+ -v /home/docker/zentao-server/:/opt/zbox/ \
+ --add-host smtp.exmail.qq.com:163.177.90.125 \
+ --name zentao-server \
+ --restart=always \
+ idoop/zentao:latest
+
+
+ }
+
+ local docker_describe="ZenTao はユニバーサルなプロジェクト管理ソフトウェアです"
+ local docker_url="公式サイト紹介:https://www.zentao.net/"
+ local docker_use="echo \"初期ユーザー名: admin\""
+ local docker_passwd="echo 「初期パスワード: 123456」"
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 12|qinglong)
+ local app_id="12"
+ local docker_name="qinglong"
+ local docker_img="whyour/qinglong:latest"
+ local docker_port=5700
+
+ docker_rum() {
+
+
+ docker run -d \
+ -v /home/docker/qinglong/data:/ql/data \
+ -p ${docker_port}:5700 \
+ --name qinglong \
+ --hostname qinglong \
+ --restart=always \
+ whyour/qinglong:latest
+
+
+ }
+
+ local docker_describe="Qinglong Panel はスケジュールされたタスク管理プラットフォームです"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/whyour/qinglong"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 13|cloudreve)
+
+ local app_id="13"
+ local app_name="クラウドリーブネットワークディスク"
+ local app_text="Cloudreve は、複数のクラウド ストレージをサポートするネットワーク ディスク システムです。"
+ local app_url="ビデオ紹介: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
+ local docker_name="cloudreve"
+ local docker_port="5212"
+ local app_size="2"
+
+ docker_app_install() {
+ cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
+ curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
+ sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
+ cd /home/docker/cloud/
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ cd /home/docker/cloud/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ rm -rf /home/docker/cloud
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+ ;;
+
+ 14|easyimage)
+ local app_id="14"
+ local docker_name="easyimage"
+ local docker_img="ddsderek/easyimage:latest"
+ local docker_port=8014
+ docker_rum() {
+
+ docker run -d \
+ --name easyimage \
+ -p ${docker_port}:80 \
+ -e TZ=Asia/Shanghai \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -v /home/docker/easyimage/config:/app/web/config \
+ -v /home/docker/easyimage/i:/app/web/i \
+ --restart=always \
+ ddsderek/easyimage:latest
+
+ }
+
+ local docker_describe="シンプルドローイングベッドはシンプルなドローイングベッドプログラムです"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/icret/EasyImages2.0"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 15|emby)
+ local app_id="15"
+ local docker_name="emby"
+ local docker_img="linuxserver/emby:latest"
+ local docker_port=8015
+
+ docker_rum() {
+
+ docker run -d --name=emby --restart=always \
+ -v /home/docker/emby/config:/config \
+ -v /home/docker/emby/share1:/mnt/share1 \
+ -v /home/docker/emby/share2:/mnt/share2 \
+ -v /mnt/notify:/mnt/notify \
+ -p ${docker_port}:8096 \
+ -e UID=1000 -e GID=100 -e GIDLIST=100 \
+ linuxserver/emby:latest
+
+ }
+
+
+ local docker_describe="emby は、サーバー上でビデオとオーディオを整理し、クライアント デバイスにオーディオとビデオをストリーミングするために使用できるマスター/スレーブ アーキテクチャのメディア サーバー ソフトウェアです。"
+ local docker_url="公式サイト紹介:https://emby.media/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 16|looking)
+ local app_id="16"
+ local docker_name="looking-glass"
+ local docker_img="wikihostinc/looking-glass-server"
+ local docker_port=8016
+
+
+ docker_rum() {
+
+ docker run -d --name looking-glass --restart=always -p ${docker_port}:80 wikihostinc/looking-glass-server
+
+ }
+
+ local docker_describe="Speedtest 速度測定パネルは、複数のテスト機能を備えた VPS ネットワーク速度テスト ツールで、VPS のインバウンドおよびアウトバウンドのトラフィックをリアルタイムで監視することもできます。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/wikihost-opensource/als"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 17|adguardhome)
+
+ local app_id="17"
+ local docker_name="adguardhome"
+ local docker_img="adguard/adguardhome"
+ local docker_port=8017
+
+ docker_rum() {
+
+ docker run -d \
+ --name adguardhome \
+ -v /home/docker/adguardhome/work:/opt/adguardhome/work \
+ -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
+ -p 53:53/tcp \
+ -p 53:53/udp \
+ -p ${docker_port}:3000/tcp \
+ --restart=always \
+ adguard/adguardhome
+
+
+ }
+
+
+ local docker_describe="AdGuardHome は、将来的には単なる DNS サーバー以上のネットワーク全体の広告ブロックおよび追跡防止ソフトウェアです。"
+ local docker_url="公式サイト紹介:https://hub.docker.com/r/adguard/adguardhome"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 18|onlyoffice)
+
+ local app_id="18"
+ local docker_name="onlyoffice"
+ local docker_img="onlyoffice/documentserver"
+ local docker_port=8018
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:80 \
+ --restart=always \
+ --name onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
+ onlyoffice/documentserver
+
+
+ }
+
+ local docker_describe="Onlyoffice はオープンソースのオンライン オフィス ツールで、非常に強力です。"
+ local docker_url="公式サイト紹介:https://www.onlyoffice.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 19|safeline)
+ send_stats "雷のプールを作る"
+
+ local app_id="19"
+ local docker_name=safeline-mgt
+ local docker_port=9443
+ while true; do
+ check_docker_app
+ clear
+ echo -e "サンダープールサービス$check_docker"
+ echo "Leichi は、Changting Technology によって開発された WAF サイト ファイアウォール プログラム パネルで、自動防御のためにサイトを反転できます。"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ check_docker_app_ip
+ fi
+ echo ""
+
+ echo "------------------------"
+ echo "1. インストール 2. アップデート 3. パスワードのリセット 4. アンインストール"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択内容を入力してください:" choice
+
+ case $choice in
+ 1)
+ install_docker
+ check_disk_space 5
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
+
+ add_app_id
+ clear
+ echo "Leichi WAFパネルを導入しました"
+ check_docker_app_ip
+ docker exec safeline-mgt resetadmin
+
+ ;;
+
+ 2)
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
+ docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
+ echo ""
+
+ add_app_id
+ clear
+ echo "Leichi WAF パネルが更新されました"
+ check_docker_app_ip
+ ;;
+ 3)
+ docker exec safeline-mgt resetadmin
+ ;;
+ 4)
+ cd /data/safeline
+ docker compose down --rmi all
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "デフォルトのインストール ディレクトリにいる場合、プロジェクトはすでにアンインストールされています。インストール ディレクトリをカスタマイズする場合は、インストール ディレクトリに移動して自分で実行する必要があります。"
+ echo "docker compose down && docker compose down --rmi all"
+ ;;
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 20|portainer)
+ local app_id="20"
+ local docker_name="portainer"
+ local docker_img="portainer/portainer"
+ local docker_port=8020
+
+ docker_rum() {
+
+ docker run -d \
+ --name portainer \
+ -p ${docker_port}:9000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/portainer:/data \
+ --restart=always \
+ portainer/portainer
+
+ }
+
+
+ local docker_describe="portainer は軽量の Docker コンテナ管理パネルです"
+ local docker_url="公式サイト紹介:https://www.porttainer.io/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 21|vscode)
+ local app_id="21"
+ local docker_name="vscode-web"
+ local docker_img="codercom/code-server"
+ local docker_port=8021
+
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart=always codercom/code-server
+
+ }
+
+
+ local docker_describe="VScode は強力なオンライン コード作成ツールです"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/coder/code-server"
+ local docker_use="sleep 3"
+ local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 22|uptime-kuma)
+ local app_id="22"
+ local docker_name="uptime-kuma"
+ local docker_img="louislam/uptime-kuma:latest"
+ local docker_port=8022
+
+
+ docker_rum() {
+
+ docker run -d \
+ --name=uptime-kuma \
+ -p ${docker_port}:3001 \
+ -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
+ --restart=always \
+ louislam/uptime-kuma:latest
+
+ }
+
+
+ local docker_describe="Uptime Kuma 使いやすいセルフホスト型監視ツール"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/louislam/uptime-kuma"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 23|memos)
+ local app_id="23"
+ local docker_name="memos"
+ local docker_img="neosmemo/memos:stable"
+ local docker_port=8023
+
+ docker_rum() {
+
+ docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart=always neosmemo/memos:stable
+
+ }
+
+ local docker_describe="Memos は軽量の自己ホスト型メモ センターです"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/usememos/memos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 24|webtop)
+ local app_id="24"
+ local docker_name="webtop"
+ local docker_img="lscr.io/linuxserver/webtop:latest"
+ local docker_port=8024
+
+ docker_rum() {
+
+ read -e -p "ログインユーザー名を設定します:" admin
+ read -e -p "ログインユーザーのパスワードを設定します。" admin_password
+ docker run -d \
+ --name=webtop \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -e LC_ALL=zh_CN.UTF-8 \
+ -e DOCKER_MODS=linuxserver/mods:universal-package-install \
+ -e INSTALL_PACKAGES=font-noto-cjk \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:latest
+
+ }
+
+
+ local docker_describe="Webtop は、Alpine コンテナの中国語版に基づいています。 IP にアクセスできない場合は、アクセス用のドメイン名を追加してください。"
+ local docker_url="公式Webサイトの紹介:https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 25|nextcloud)
+ local app_id="25"
+ local docker_name="nextcloud"
+ local docker_img="nextcloud:latest"
+ local docker_port=8025
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+
+ }
+
+ local docker_describe="400,000 を超える導入実績を持つ Nextcloud は、ダウンロードできる最も人気のあるローカル コンテンツ コラボレーション プラットフォームです。"
+ local docker_url="公式サイト紹介:https://nextcloud.com/"
+ local docker_use="echo \"アカウント: nextcloud パスワード:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 26|qd)
+ local app_id="26"
+ local docker_name="qd"
+ local docker_img="qdtoday/qd:latest"
+ local docker_port=8026
+
+ docker_rum() {
+
+ docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+
+ }
+
+ local docker_describe="QD-Today は、HTTP リクエストのスケジュールされたタスク自動実行フレームワークです。"
+ local docker_url="公式サイト紹介:https://qd-today.github.io/qd/zh_CN/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 27|dockge)
+ local app_id="27"
+ local docker_name="dockge"
+ local docker_img="louislam/dockge:latest"
+ local docker_port=8027
+
+ docker_rum() {
+
+ docker run -d --name dockge --restart=always -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+
+ }
+
+ local docker_describe="dockge は、視覚的な docker-compose コンテナー管理パネルです。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/louislam/dockge"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 28|speedtest)
+ local app_id="28"
+ local docker_name="speedtest"
+ local docker_img="ghcr.io/librespeed/speedtest"
+ local docker_port=8028
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 --name speedtest --restart=always ghcr.io/librespeed/speedtest
+
+ }
+
+ local docker_describe="librespeed は、JavaScript で実装され、すぐに使用できる軽量の速度テスト ツールです。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/librespeed/speedtest"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 29|searxng)
+ local app_id="29"
+ local docker_name="searxng"
+ local docker_img="searxng/searxng"
+ local docker_port=8029
+
+ docker_rum() {
+
+ docker run -d \
+ --name searxng \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v "/home/docker/searxng:/etc/searxng" \
+ searxng/searxng
+
+ }
+
+ local docker_describe="searxng は非公開の検索エンジン サイトです。"
+ local docker_url="公式サイト紹介:https://hub.docker.com/r/alandoyle/searxng"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 30|photoprism)
+ local app_id="30"
+ local docker_name="photoprism"
+ local docker_img="photoprism/photoprism:latest"
+ local docker_port=8030
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d \
+ --name photoprism \
+ --restart=always \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ -p ${docker_port}:2342 \
+ -e PHOTOPRISM_UPLOAD_NSFW="true" \
+ -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
+ -v /home/docker/photoprism/storage:/photoprism/storage \
+ -v /home/docker/photoprism/Pictures:/photoprism/originals \
+ photoprism/photoprism
+
+ }
+
+
+ local docker_describe="Photoprism は非常に強力なプライベート フォト アルバム システムです"
+ local docker_url="公式サイト紹介:https://www.photoprism.app/"
+ local docker_use="echo \"アカウント: admin パスワード:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 31|s-pdf)
+ local app_id="31"
+ local docker_name="s-pdf"
+ local docker_img="frooodle/s-pdf:latest"
+ local docker_port=8031
+
+ docker_rum() {
+
+ docker run -d \
+ --name s-pdf \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
+ -v /home/docker/s-pdf/extraConfigs:/configs \
+ -v /home/docker/s-pdf/logs:/logs \
+ -e DOCKER_ENABLE_SECURITY=false \
+ frooodle/s-pdf:latest
+ }
+
+ local docker_describe="これは、Docker を使用してローカルでホストされる強力な Web ベースの PDF 操作ツールで、分割マージ、変換、再編成、画像の追加、回転、圧縮など、PDF ファイルに対してさまざまな操作を実行できます。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 32|drawio)
+ local app_id="32"
+ local docker_name="drawio"
+ local docker_img="jgraph/drawio"
+ local docker_port=8032
+
+ docker_rum() {
+
+ docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+
+ }
+
+
+ local docker_describe="これは強力なグラフ作成ソフトウェアです。マインド マップ、トポロジ図、フローチャートを描画できます。"
+ local docker_url="公式サイト紹介:https://www.drawio.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 33|sun-panel)
+ local app_id="33"
+ local docker_name="sun-panel"
+ local docker_img="hslr/sun-panel"
+ local docker_port=8033
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:3002 \
+ -v /home/docker/sun-panel/conf:/app/conf \
+ -v /home/docker/sun-panel/uploads:/app/uploads \
+ -v /home/docker/sun-panel/database:/app/database \
+ --name sun-panel \
+ hslr/sun-panel
+
+ }
+
+ local docker_describe="Sun-Panel サーバー、NAS ナビゲーション パネル、ホームページ、ブラウザー ホームページ"
+ local docker_url="公式サイト紹介:https://doc.sun-panel.top/zh_cn/"
+ local docker_use="echo 「アカウント: admin@sun.cc パスワード: 12345678」"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 34|pingvin-share)
+ local app_id="34"
+ local docker_name="pingvin-share"
+ local docker_img="stonith404/pingvin-share"
+ local docker_port=8034
+
+ docker_rum() {
+
+ docker run -d \
+ --name pingvin-share \
+ --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/pingvin-share/data:/opt/app/backend/data \
+ stonith404/pingvin-share
+ }
+
+ local docker_describe="Pingvin Share は自己構築可能なファイル共有プラットフォームであり、WeTransfer の代替品です"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/stonith404/pingvin-share"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 35|moments)
+ local app_id="35"
+ local docker_name="moments"
+ local docker_img="kingwrcy/moments:latest"
+ local docker_port=8035
+
+ docker_rum() {
+
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/moments/data:/app/data \
+ -v /etc/localtime:/etc/localtime:ro \
+ -v /etc/timezone:/etc/timezone:ro \
+ --name moments \
+ kingwrcy/moments:latest
+ }
+
+
+ local docker_describe="ミニマリストの瞬間、模倣性の高いWeChatの瞬間、あなたの素晴らしい人生を記録してください"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
+ local docker_use="echo 「アカウント: admin パスワード: a123456」"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+
+ 36|lobe-chat)
+ local app_id="36"
+ local docker_name="lobe-chat"
+ local docker_img="lobehub/lobe-chat:latest"
+ local docker_port=8036
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:3210 \
+ --name lobe-chat \
+ --restart=always \
+ lobehub/lobe-chat
+ }
+
+ local docker_describe="LobeChat は、市場で主流の AI 大型モデル、ChatGPT/Claude/Gemini/Groq/Ollama を集約しています。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/lobehub/lobe-chat"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 37|myip)
+ local app_id="37"
+ local docker_name="myip"
+ local docker_img="jason5ng32/myip:latest"
+ local docker_port=8037
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+
+ }
+
+
+ local docker_describe="独自の IP 情報と接続性を表示し、Web パネルを使用して表示できる多機能 IP ツールボックスです。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 38|xiaoya)
+ send_stats "シャオヤファミリーバケツ"
+ clear
+ install_docker
+ check_disk_space 1
+ bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
+ ;;
+
+ 39|bililive)
+
+ if [ ! -d /home/docker/bililive-go/ ]; then
+ mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
+ wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
+ fi
+
+ local app_id="39"
+ local docker_name="bililive-go"
+ local docker_img="chigusa/bililive-go"
+ local docker_port=8039
+
+ docker_rum() {
+
+ docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+
+ }
+
+ local docker_describe="Bililive-go は、複数のライブ ブロードキャスト プラットフォームをサポートするライブ ブロードキャスト録画ツールです"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/hr3lxphr6j/bililive-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 40|webssh)
+ local app_id="40"
+ local docker_name="webssh"
+ local docker_img="jrohy/webssh"
+ local docker_port=8040
+ docker_rum() {
+ docker run -d -p ${docker_port}:5032 --restart=always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
+ }
+
+ local docker_describe="シンプルなオンライン SSH 接続ツールと SFTP ツール"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/Jrohy/webssh"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 41|haozi|acepanel)
+
+ local app_id="41"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AcePanelオリジナルマウスパネル"
+ local panelurl="正式な住所:${gh_proxy}github.com/acepanel/panel"
+
+ panel_app_install() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+ }
+
+ panel_app_manage() {
+ acepanel help
+ }
+
+ panel_app_uninstall() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+
+ }
+
+ install_panel
+
+ ;;
+
+
+ 42|nexterm)
+ local app_id="42"
+ local docker_name="nexterm"
+ local docker_img="germannewsmaker/nexterm:latest"
+ local docker_port=8042
+
+ docker_rum() {
+
+ ENCRYPTION_KEY=$(openssl rand -hex 32)
+ docker run -d \
+ --name nexterm \
+ -e ENCRYPTION_KEY=${ENCRYPTION_KEY} \
+ -p ${docker_port}:6989 \
+ -v /home/docker/nexterm:/app/data \
+ --restart=always \
+ germannewsmaker/nexterm:latest
+
+ }
+
+ local docker_describe="nexterm は、強力なオンライン SSH/VNC/RDP 接続ツールです。"
+ local docker_url="公式サイト紹介:${gh_proxy}github.com/gnmyt/Nexterm"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 43|hbbs)
+ local app_id="43"
+ local docker_name="hbbs"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbs
+
+ }
+
+
+ local docker_describe="Rustdesk のオープンソース リモート デスクトップ (サーバー) は、自社の Sunflower プライベート サーバーに似ています。"
+ local docker_url="公式サイト紹介:https://rustdesk.com/zh-cn/"
+ local docker_use="docker logs hbbs"
+ local docker_passwd="echo \"リモート デスクトップ クライアントで使用される IP とキーを記録します。オプション 44 に進んでリレーをインストールしてください!\""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 44|hbbr)
+ local app_id="44"
+ local docker_name="hbbr"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbr
+
+ }
+
+ local docker_describe="Rustdesk のオープンソース リモート デスクトップ (リレー) は、独自の Sunflower プライベート サーバーに似ています。"
+ local docker_url="公式サイト紹介:https://rustdesk.com/zh-cn/"
+ local docker_use="echo \"公式 Web サイトにアクセスして、リモート デスクトップ クライアントをダウンロードします: https://rustdesk.com/zh-cn/\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 45|registry)
+ local app_id="45"
+ local docker_name="registry"
+ local docker_img="registry:2"
+ local docker_port=8045
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name registry \
+ -v /home/docker/registry:/var/lib/registry \
+ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
+ --restart=always \
+ registry:2
+
+ }
+
+ local docker_describe="Docker Registry は、Docker イメージを保存および配布するためのサービスです。"
+ local docker_url="公式サイト紹介:https://hub.docker.com/_/registry"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 46|ghproxy)
+ local app_id="46"
+ local docker_name="ghproxy"
+ local docker_img="wjqserver/ghproxy:latest"
+ local docker_port=8046
+
+ docker_rum() {
+
+ docker run -d --name ghproxy --restart=always -p ${docker_port}:8080 -v /home/docker/ghproxy/config:/data/ghproxy/config wjqserver/ghproxy:latest
+
+ }
+
+ local docker_describe="Go を使用して実装された GHProxy は、一部の領域で Github リポジトリのプルを高速化するために使用されます。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/WJQSERVER-STUDIO/ghproxy"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 47|prometheus|grafana)
+
+ local app_id="47"
+ local app_name="プロメテウスの監視"
+ local app_text="Prometheus+Grafana エンタープライズレベルの監視システム"
+ local app_url="公式サイト紹介:https://prometheus.io"
+ local docker_name="grafana"
+ local docker_port="8047"
+ local app_size="2"
+
+ docker_app_install() {
+ prometheus_install
+ clear
+ ip_address
+ echo "インストール完了"
+ check_docker_app_ip
+ echo "初期のユーザー名とパスワードは次のとおりです: admin"
+ }
+
+ docker_app_update() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+
+ rm -rf /home/docker/monitoring
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+ ;;
+
+ 48|node-exporter)
+ local app_id="48"
+ local docker_name="node-exporter"
+ local docker_img="prom/node-exporter"
+ local docker_port=8048
+
+ docker_rum() {
+
+ docker run -d \
+ --name=node-exporter \
+ -p ${docker_port}:9100 \
+ --restart=always \
+ prom/node-exporter
+
+
+ }
+
+ local docker_describe="これは Prometheus ホスト データ収集コンポーネントです。監視対象ホストにデプロイしてください。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/prometheus/node_exporter"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 49|cadvisor)
+ local app_id="49"
+ local docker_name="cadvisor"
+ local docker_img="gcr.io/cadvisor/cadvisor:latest"
+ local docker_port=8049
+
+ docker_rum() {
+
+ docker run -d \
+ --name=cadvisor \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ --volume=/:/rootfs:ro \
+ --volume=/var/run:/var/run:rw \
+ --volume=/sys:/sys:ro \
+ --volume=/var/lib/docker/:/var/lib/docker:ro \
+ gcr.io/cadvisor/cadvisor:latest \
+ -housekeeping_interval=10s \
+ -docker_only=true
+
+ }
+
+ local docker_describe="これは Prometheus コンテナのデータ収集コンポーネントです。監視対象ホストにデプロイしてください。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/google/cadvisor"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 50|changedetection)
+ local app_id="50"
+ local docker_name="changedetection"
+ local docker_img="dgtlmoon/changedetection.io:latest"
+ local docker_port=8050
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:5000 \
+ -v /home/docker/datastore:/datastore \
+ --name changedetection dgtlmoon/changedetection.io:latest
+
+ }
+
+ local docker_describe="これは、Web サイトの変更の検出、補充の監視、通知を行うための小さなツールです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/dgtlmoon/changedetection.io"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 51|pve)
+ clear
+ send_stats "PVE オープンひよこ"
+ check_disk_space 1
+ curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
+ ;;
+
+
+ 52|dpanel)
+ local app_id="52"
+ local docker_name="dpanel"
+ local docker_img="dpanel/dpanel:lite"
+ local docker_port=8052
+
+ docker_rum() {
+
+ docker run -d --name dpanel --restart=always \
+ -p ${docker_port}:8080 -e APP_NAME=dpanel \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/dpanel:/dpanel \
+ dpanel/dpanel:lite
+
+ }
+
+ local docker_describe="Docker ビジュアル パネル システムは、完全な Docker 管理機能を提供します。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/donknap/dpanel"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 53|llama3)
+ local app_id="53"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI は、新しい llama3 ラージ言語モデルに接続されているラージ言語モデル Web ページ フレームワークです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run llama3.2:1b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+ 54|amh)
+
+ local app_id="54"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AMHパネル"
+ local panelurl="公式アドレス:https://amh.sh/index.htm?amh"
+
+ panel_app_install() {
+ cd ~
+ wget https://dl.amh.sh/amh.sh && bash amh.sh
+ }
+
+ panel_app_manage() {
+ panel_app_install
+ }
+
+ panel_app_uninstall() {
+ panel_app_install
+ }
+
+ install_panel
+ ;;
+
+
+ 55|frps)
+ frps_panel
+ ;;
+
+ 56|frpc)
+ frpc_panel
+ ;;
+
+ 57|deepseek)
+ local app_id="57"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI は、新しい DeepSeek R1 大規模言語モデルに接続されている大規模言語モデル Web ページ フレームワークです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+ 58|dify)
+ local app_id="58"
+ local app_name="Difyナレッジベース"
+ local app_text="これは、オープンソースの大規模言語モデル (LLM) アプリケーション開発プラットフォームです。 AI生成用の自己ホスト型トレーニングデータ"
+ local app_url="公式ウェブサイト:https://docs.dify.ai/zh-hans"
+ local docker_name="docker-nginx-1"
+ local docker_port="8058"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
+ sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+
+ docker compose up -d
+
+ chown -R 1001:1001 /home/docker/dify/docker/volumes/app/storage
+ chmod -R 755 /home/docker/dify/docker/volumes/app/storage
+ docker compose down
+ docker compose up -d
+
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ cd /home/docker/dify/
+ git pull ${gh_proxy}github.com/langgenius/dify.git main > /dev/null 2>&1
+ sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
+ cd /home/docker/dify/docker/ && docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/dify
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+ ;;
+
+ 59|new-api)
+ local app_id="59"
+ local app_name="NewAPI"
+ local app_text="新世代の大型モデルゲートウェイとAI資産管理システム"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/Calcium-Ion/new-api"
+ local docker_name="new-api"
+ local docker_port="8059"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ cd /home/docker/new-api/
+
+ git pull ${gh_proxy}github.com/Calcium-Ion/new-api.git main > /dev/null 2>&1
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ rm -rf /home/docker/new-api
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 60|jms)
+
+ local app_id="60"
+ local app_name="JumpServer オープンソース要塞マシン"
+ local app_text="これは、オープンソースの特権アクセス管理 (PAM) ツールです。このプログラムはポート 80 を占有しているため、アクセス用のドメイン名の追加はサポートされていません。"
+ local app_url="公式紹介:${gh_https_url}github.com/jumpserver/jumpserver"
+ local docker_name="jms_web"
+ local docker_port="80"
+ local app_size="2"
+
+ docker_app_install() {
+ curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ echo "初期ユーザー名: admin"
+ echo "初期パスワード:ChangeMe"
+ }
+
+
+ docker_app_update() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh upgrade
+ echo "アプリが更新されました"
+ }
+
+
+ docker_app_uninstall() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh uninstall
+ cd /opt
+ rm -rf jumpserver-installer*/
+ rm -rf jumpserver
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+ ;;
+
+ 61|libretranslate)
+ local app_id="61"
+ local docker_name="libretranslate"
+ local docker_img="libretranslate/libretranslate:latest"
+ local docker_port=8061
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name libretranslate \
+ libretranslate/libretranslate \
+ --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+
+ }
+
+ local docker_describe="無料のオープンソース機械翻訳 API は完全に自己ホストされており、その翻訳エンジンはオープンソースの Argos Translate ライブラリを利用しています。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/LibreTranslate/LibreTranslate"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+
+ 62|ragflow)
+ local app_id="62"
+ local app_name="RAGFlow ナレッジベース"
+ local app_text="ドキュメントの深い理解に基づくオープンソース RAG (Retrieval Augmented Generation) エンジン"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/infiniflow/ragflow"
+ local docker_name="ragflow-server"
+ local docker_port="8062"
+ local app_size="8"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ cd /home/docker/ragflow/
+ git pull ${gh_proxy}github.com/infiniflow/ragflow.git main > /dev/null 2>&1
+ cd /home/docker/ragflow/docker/
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/ragflow
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 63|open-webui)
+ local app_id="63"
+ local docker_name="open-webui"
+ local docker_img="ghcr.io/open-webui/open-webui:main"
+ local docker_port=8063
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart=always ghcr.io/open-webui/open-webui:main
+
+ }
+
+ local docker_describe="OpenWebUI は大規模な言語モデルの Web ページ フレームワークであり、公式の簡易バージョンではすべての主要モデルへの API アクセスがサポートされています。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 64|it-tools)
+ local app_id="64"
+ local docker_name="it-tools"
+ local docker_img="corentinth/it-tools:latest"
+ local docker_port=8064
+
+ docker_rum() {
+ docker run -d --name it-tools --restart=always -p ${docker_port}:80 corentinth/it-tools:latest
+ }
+
+ local docker_describe="開発者と IT ワーカーにとって非常に便利なツール"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/CorentinTh/it-tools"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 65|n8n)
+ local app_id="65"
+ local docker_name="n8n"
+ local docker_img="docker.n8n.io/n8nio/n8n"
+ local docker_port=8065
+
+ docker_rum() {
+
+ add_yuming
+ mkdir -p /home/docker/n8n
+ chmod -R 777 /home/docker/n8n
+
+ docker run -d --name n8n \
+ --restart=always \
+ -p ${docker_port}:5678 \
+ -v /home/docker/n8n:/home/node/.n8n \
+ -e N8N_HOST=${yuming} \
+ -e N8N_PORT=5678 \
+ -e N8N_PROTOCOL=https \
+ -e WEBHOOK_URL=https://${yuming}/ \
+ docker.n8n.io/n8nio/n8n
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
+
+ }
+
+ local docker_describe="強力な自動ワークフロー プラットフォームです"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/n8n-io/n8n"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 66|yt)
+ yt_menu_pro
+ ;;
+
+
+ 67|ddns)
+ local app_id="67"
+ local docker_name="ddns-go"
+ local docker_img="jeessy/ddns-go"
+ local docker_port=8067
+
+ docker_rum() {
+ docker run -d \
+ --name ddns-go \
+ --restart=always \
+ -p ${docker_port}:9876 \
+ -v /home/docker/ddns-go:/root \
+ jeessy/ddns-go
+
+ }
- 33)
- local docker_name="sun-panel"
- local docker_img="hslr/sun-panel"
- local docker_port=3009
+ local docker_describe="パブリック IP (IPv4/IPv6) を主要な DNS サービス プロバイダーにリアルタイムで自動的に更新し、動的なドメイン名解決を実現します。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/jeessy2/ddns-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_rum() {
+ 68|allinssl)
+ local app_id="68"
+ local docker_name="allinssl"
+ local docker_img="allinssl/allinssl:latest"
+ local docker_port=8068
- docker run -d --restart=always -p ${docker_port}:3002 \
- -v /home/docker/sun-panel/conf:/app/conf \
- -v /home/docker/sun-panel/uploads:/app/uploads \
- -v /home/docker/sun-panel/database:/app/database \
- --name sun-panel \
- hslr/sun-panel
+ docker_rum() {
+ docker run -d --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
+ }
- }
+ local docker_describe="オープンソースの無料 SSL 証明書自動化管理プラットフォーム"
+ local docker_url="公式サイト紹介:https://allinssl.com"
+ local docker_use="echo \"セキュリティの入り口: /allinssl\""
+ local docker_passwd="echo \"ユーザー名: allinssl パスワード: allinssldocker\""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
- local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
- local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 34)
- local docker_name="pingvin-share"
- local docker_img="stonith404/pingvin-share"
- local docker_port=3060
-
- docker_rum() {
-
- docker run -d \
- --name pingvin-share \
- --restart always \
- -p ${docker_port}:3000 \
- -v /home/docker/pingvin-share/data:/opt/app/backend/data \
- stonith404/pingvin-share
- }
+ 69|sftpgo)
+ local app_id="69"
+ local docker_name="sftpgo"
+ local docker_img="drakkan/sftpgo:latest"
+ local docker_port=8069
- local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
- local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
+ mkdir -p /home/docker/sftpgo/data
+ mkdir -p /home/docker/sftpgo/config
+ chown -R 1000:1000 /home/docker/sftpgo
- 35)
- local docker_name="moments"
- local docker_img="kingwrcy/moments:latest"
- local docker_port=8035
-
- docker_rum() {
-
- docker run -d --restart unless-stopped \
- -p ${docker_port}:3000 \
- -v /home/docker/moments/data:/app/data \
- -v /etc/localtime:/etc/localtime:ro \
- -v /etc/timezone:/etc/timezone:ro \
- --name moments \
- kingwrcy/moments:latest
- }
+ docker run -d \
+ --name sftpgo \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -p 22022:2022 \
+ --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
+ --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
+ drakkan/sftpgo:latest
+ }
- local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
- local docker_url="公式ウェブサイトの紹介:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
- local docker_use="echo \"账号: admin 密码: a123456\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="オープンソースでいつでもどこでも無料の SFTP FTP WebDAV ファイル転送ツール"
+ local docker_url="公式サイト紹介:https://sftpgo.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+ 70|astrbot)
+ local app_id="70"
+ local docker_name="astrbot"
+ local docker_img="soulter/astrbot:latest"
+ local docker_port=8070
- 36)
- local docker_name="lobe-chat"
- local docker_img="lobehub/lobe-chat:latest"
- local docker_port=8036
+ docker_rum() {
- docker_rum() {
+ mkdir -p /home/docker/astrbot/data
- docker run -d -p ${docker_port}:3210 \
- --name lobe-chat \
- --restart=always \
- lobehub/lobe-chat
- }
+ docker run -d \
+ -p ${docker_port}:6185 \
+ -p 6195:6195 \
+ -p 6196:6196 \
+ -p 6199:6199 \
+ -p 11451:11451 \
+ -v /home/docker/astrbot/data:/AstrBot/data \
+ --restart=always \
+ --name astrbot \
+ soulter/astrbot:latest
- local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
- local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ }
- 37)
- local docker_name="myip"
- local docker_img="jason5ng32/myip:latest"
- local docker_port=8037
+ local docker_describe="オープンソース AI チャットボット フレームワーク。WeChat、QQ、TG による大規模 AI モデルへのアクセスをサポート"
+ local docker_url="公式サイト紹介:https://astrbot.app/"
+ local docker_use="echo \"ユーザー名: astrbot パスワード: astrbot\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_rum() {
- docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+ 71|navidrome)
+ local app_id="71"
+ local docker_name="navidrome"
+ local docker_img="deluan/navidrome:latest"
+ local docker_port=8071
- }
+ docker_rum() {
+
+ docker run -d \
+ --name navidrome \
+ --restart=always \
+ --user $(id -u):$(id -g) \
+ -v /home/docker/navidrome/music:/music \
+ -v /home/docker/navidrome/data:/data \
+ -p ${docker_port}:4533 \
+ -e ND_LOGLEVEL=info \
+ deluan/navidrome:latest
+
+ }
+
+ local docker_describe="軽量で高性能な音楽ストリーミングサーバーです"
+ local docker_url="公式サイト紹介:https://www.navidrome.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 72|bitwarden)
+
+ local app_id="72"
+ local docker_name="bitwarden"
+ local docker_img="vaultwarden/server"
+ local docker_port=8072
+
+ docker_rum() {
+
+ docker run -d \
+ --name bitwarden \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/bitwarden/data:/data \
+ vaultwarden/server
+
+ }
+
+ local docker_describe="データを管理できるパスワード マネージャー"
+ local docker_url="公式サイト紹介:https://bitwarden.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+
+ ;;
+
+
+
+ 73|libretv)
+
+ local app_id="73"
+ local docker_name="libretv"
+ local docker_img="bestzwei/libretv:latest"
+ local docker_port=8073
+
+ docker_rum() {
+
+ read -e -p "LibreTV のログイン パスワードを設定します。" app_passwd
+
+ docker run -d \
+ --name libretv \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -e PASSWORD=${app_passwd} \
+ bestzwei/libretv:latest
+
+ }
+
+ local docker_describe="無料のオンラインビデオ検索および視聴プラットフォーム"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/LibreSpark/LibreTV"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 74|moontv)
+
+ local app_id="74"
+
+ local app_name="MoonTV プライベート映画とテレビ"
+ local app_text="無料のオンラインビデオ検索および視聴プラットフォーム"
+ local app_url="ビデオの紹介:${gh_https_url}github.com/MoonTechLab/LunaTV"
+ local docker_name="moontv-core"
+ local docker_port="8074"
+ local app_size="2"
+
+ docker_app_install() {
+ read -e -p "ログインユーザー名を設定します:" admin
+ read -e -p "ログインユーザーのパスワードを設定します。" admin_password
+ read -e -p "認証コードを入力してください:" shouquanma
+
+
+ mkdir -p /home/docker/moontv
+ mkdir -p /home/docker/moontv/config
+ mkdir -p /home/docker/moontv/data
+ cd /home/docker/moontv
+
+ curl -o /home/docker/moontv/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/moontv-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin_password|${admin_password}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin|${admin}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|shouquanma|${shouquanma}|g" /home/docker/moontv/docker-compose.yml
+ cd /home/docker/moontv/
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ cd /home/docker/moontv/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ rm -rf /home/docker/moontv
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 75|melody)
+
+ local app_id="75"
+ local docker_name="melody"
+ local docker_img="foamzou/melody:latest"
+ local docker_port=8075
+
+ docker_rum() {
+
+ docker run -d \
+ --name melody \
+ --restart=always \
+ -p ${docker_port}:5566 \
+ -v /home/docker/melody/.profile:/app/backend/.profile \
+ foamzou/melody:latest
+
+
+ }
+
+ local docker_describe="音楽をより適切に管理できるように設計された音楽ウィザード。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/foamzou/melody"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+
+ ;;
+
+
+ 76|dosgame)
+
+ local app_id="76"
+ local docker_name="dosgame"
+ local docker_img="oldiy/dosgame-web-docker:latest"
+ local docker_port=8076
+
+ docker_rum() {
+ docker run -d \
+ --name dosgame \
+ --restart=always \
+ -p ${docker_port}:262 \
+ oldiy/dosgame-web-docker:latest
+
+ }
+
+ local docker_describe="中華DOSゲームまとめサイトです"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/rwv/chinese-dos-games"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+
+ 77|xunlei)
+
+ local app_id="77"
+ local docker_name="xunlei"
+ local docker_img="cnk3x/xunlei"
+ local docker_port=8077
+
+ docker_rum() {
+
+ read -e -p "ログインユーザー名を設定します:" app_use
+ read -e -p "ログインパスワードを設定します:" app_passwd
+
+ docker run -d \
+ --name xunlei \
+ --restart=always \
+ --privileged \
+ -e XL_DASHBOARD_USERNAME=${app_use} \
+ -e XL_DASHBOARD_PASSWORD=${app_passwd} \
+ -v /home/docker/xunlei/data:/xunlei/data \
+ -v /home/docker/xunlei/downloads:/xunlei/downloads \
+ -p ${docker_port}:2345 \
+ cnk3x/xunlei
+
+ }
+
+ local docker_describe="Xunlei、オフライン高速 BT 磁気ダウンロード ツール"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/cnk3x/xunlei"
+ local docker_use="echo \"携帯電話で Xunlei にログインし、招待コードを入力してください。招待コード: Xunlei Niutong\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 78|PandaWiki)
+
+ local app_id="78"
+ local app_name="PandaWiki"
+ local app_text="PandaWiki は、AI 大型モデルによって駆動されるオープンソースのインテリジェントな文書管理システムです。ポートの展開をカスタマイズしないことを強くお勧めします。"
+ local app_url="公式紹介:${gh_https_url}github.com/chaitin/PandaWiki"
+ local docker_name="panda-wiki-nginx"
+ local docker_port="2443"
+ local app_size="2"
+
+ docker_app_install() {
+ bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
+ }
+
+ docker_app_update() {
+ docker_app_install
+ }
+
+
+ docker_app_uninstall() {
+ docker_app_install
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 79|beszel)
+
+ local app_id="79"
+ local docker_name="beszel"
+ local docker_img="henrygd/beszel"
+ local docker_port=8079
+
+ docker_rum() {
+
+ mkdir -p /home/docker/beszel && \
+ docker run -d \
+ --name beszel \
+ --restart=always \
+ -v /home/docker/beszel:/beszel_data \
+ -p ${docker_port}:8090 \
+ henrygd/beszel
+
+ }
+
+ local docker_describe="Beszel は軽量で使いやすいサーバー監視です"
+ local docker_url="公式サイト紹介:https://beszel.dev/zh/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 80|linkwarden)
+
+ local app_id="80"
+ local app_name="リンクワーデンのブックマーク管理"
+ local app_text="タグ付け、検索、チーム コラボレーションをサポートする、オープン ソースの自己ホスト型ブックマーク管理プラットフォーム。"
+ local app_url="公式サイト:https://linkwarden.app/"
+ local docker_name="linkwarden-linkwarden-1"
+ local docker_port="8080"
+ local app_size="3"
+
+ docker_app_install() {
+ install git openssl
+ mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
+
+ # 公式の docker-compose および env ファイルをダウンロードする
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
+
+ # ランダムなキーとパスワードを生成する
+ local ADMIN_EMAIL="admin@example.com"
+ local ADMIN_PASSWORD=$(openssl rand -hex 8)
+
+ sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
+ sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
+ sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
+ sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
+
+ # 管理者アカウント情報を追加する
+ echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
+ echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
+
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ # コンテナの起動
+ docker compose up -d
+
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
+
+ # 元の変数を保持する
+ source .env
+ mv .env.new .env
+ echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
+ echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
+ echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
+ echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
+ echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
+ echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ rm -rf /home/docker/linkwarden
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+
+ 81|jitsi)
+ local app_id="81"
+ local app_name="JitsiMeet ビデオ会議"
+ local app_text="複数人でのオンライン会議、画面共有、暗号化通信をサポートするオープンソースの安全なビデオ会議ソリューション。"
+ local app_url="公式サイト:https://jitsi.org/"
+ local docker_name="jitsi"
+ local docker_app_service="jitsi-web-1"
+ local docker_port="8081"
+ local app_size="3"
+
+ docker_app_install() {
+
+ add_yuming
+ mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
+ rm -rf docker-jitsi-meet-* jitsi.zip
+ local jitsi_zip_url=$(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep -m1 '"zipball_url"' | cut -d\" -f4)
+ if [ -z "$jitsi_zip_url" ]; then
+ echo "Failed to get Jitsi Meet latest download URL"
+ return 1
+ fi
+ wget -O jitsi.zip "$jitsi_zip_url"
+ unzip -q jitsi.zip
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ cp env.example .env
+ ./gen-passwords.sh
+ mkdir -p /home/docker/jitsi/config/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
+ sed -i "s|^CONFIG=.*|CONFIG=/home/docker/jitsi/config|" .env
+ grep -q '^COMPOSE_PROJECT_NAME=' .env && sed -i "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=jitsi|" .env || echo "COMPOSE_PROJECT_NAME=jitsi" >> .env
+ sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
+ sed -i "s|^#\?PUBLIC_URL=.*|PUBLIC_URL=https://$yuming|" .env
+ grep -q '^PUBLIC_URL=' .env || echo "PUBLIC_URL=https://$yuming" >> .env
+ grep -q '^DISABLE_HTTPS=' .env && sed -i "s|^DISABLE_HTTPS=.*|DISABLE_HTTPS=1|" .env || echo "DISABLE_HTTPS=1" >> .env
+ grep -q '^ENABLE_HTTP_REDIRECT=' .env && sed -i "s|^ENABLE_HTTP_REDIRECT=.*|ENABLE_HTTP_REDIRECT=0|" .env || echo "ENABLE_HTTP_REDIRECT=0" >> .env
+ sed -i "/'\${HTTPS_PORT}:443'/d" docker-compose.yml
+ docker compose up -d
+ local jitsi_web_container=$(docker compose ps -q web)
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ [ -n "$jitsi_web_container" ] && block_container_port "$jitsi_web_container" "$ipv4_address"
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose pull
+ docker compose up -d
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose down --rmi all
+ rm -rf /home/docker/jitsi
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+
+ 82|gpt-load)
+
+ local app_id="82"
+ local docker_name="gpt-load"
+ local docker_img="tbphp/gpt-load:latest"
+ local docker_port=8082
+
+ docker_rum() {
+
+ read -e -p "設定${docker_name}ログイン キー (sk- で始まる文字と数字の組み合わせ) 例: sk-159kejilionyyds163:" app_passwd
+
+ mkdir -p /home/docker/gpt-load && \
+ docker run -d --name gpt-load \
+ -p ${docker_port}:3001 \
+ -e AUTH_KEY=${app_passwd} \
+ -v "/home/docker/gpt-load/data":/app/data \
+ tbphp/gpt-load:latest
+
+ }
+
+ local docker_describe="高性能AIインターフェース透過プロキシサービス"
+ local docker_url="公式サイト紹介:https://www.gpt-load.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 83|komari)
+
+ local app_id="83"
+ local docker_name="komari"
+ local docker_img="ghcr.io/komari-monitor/komari:latest"
+ local docker_port=8083
+
+ docker_rum() {
+
+ mkdir -p /home/docker/komari && \
+ docker run -d \
+ --name komari \
+ -p ${docker_port}:25774 \
+ -v /home/docker/komari:/app/data \
+ -e ADMIN_USERNAME=admin \
+ -e ADMIN_PASSWORD=1212156 \
+ -e TZ=Asia/Shanghai \
+ --restart=always \
+ ghcr.io/komari-monitor/komari:latest
+
+ }
+
+ local docker_describe="軽量のセルフホスト型サーバー監視ツール"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/komari-monitor/komari/tree/main"
+ local docker_use="echo \"デフォルトのアカウント: admin デフォルトのパスワード: 1212156\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 84|wallos)
+
+ local app_id="84"
+ local docker_name="wallos"
+ local docker_img="bellamy/wallos:latest"
+ local docker_port=8084
+
+ docker_rum() {
+
+ mkdir -p /home/docker/wallos && \
+ docker run -d --name wallos \
+ -v /home/docker/wallos/db:/var/www/html/db \
+ -v /home/docker/wallos/logos:/var/www/html/images/uploads/logos \
+ -e TZ=UTC \
+ -p ${docker_port}:80 \
+ --restart=always \
+ bellamy/wallos:latest
+
+ }
+
+ local docker_describe="財務管理用のオープンソースの個人サブスクリプション トラッカー"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/ellite/Wallos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 85|immich)
+
+ local app_id="85"
+ local app_name="イミッチ・ピクチャー・ビデオ・マネージャー"
+ local app_text="高性能の自己ホスト型写真およびビデオ管理ソリューション。"
+ local app_url="公式サイト紹介:${gh_https_url}github.com/immich-app/immich"
+ local docker_name="immich_server"
+ local docker_port="8085"
+ local app_size="3"
+
+ docker_app_install() {
+ install git openssl wget
+ mkdir -p /home/docker/${docker_name} && cd /home/docker/${docker_name}
+
+ wget -O docker-compose.yml ${gh_proxy}github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+ wget -O .env ${gh_proxy}github.com/immich-app/immich/releases/latest/download/example.env
+ sed -i "s/2283:2283/${docker_port}:2283/g" /home/docker/${docker_name}/docker-compose.yml
+
+ docker compose up -d
+
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ rm -rf /home/docker/${docker_name}
+ echo "アプリがアンインストールされました"
+ }
+
+ docker_app_plus
+
+
+ ;;
+
+
+ 86|jellyfin)
+
+ local app_id="86"
+ local docker_name="jellyfin"
+ local docker_img="jellyfin/jellyfin"
+ local docker_port=8086
+
+ docker_rum() {
+
+ mkdir -p /home/docker/jellyfin/media
+ chmod -R 777 /home/docker/jellyfin
+
+ docker run -d \
+ --name jellyfin \
+ --user root \
+ --volume /home/docker/jellyfin/config:/config \
+ --volume /home/docker/jellyfin/cache:/cache \
+ --mount type=bind,source=/home/docker/jellyfin/media,target=/media \
+ -p ${docker_port}:8096 \
+ -p 7359:7359/udp \
+ --restart=always \
+ jellyfin/jellyfin
+
+
+ }
+
+ local docker_describe="オープンソースのメディアサーバーソフトウェアです"
+ local docker_url="公式サイト紹介:https://jellyfin.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 87|synctv)
+
+ local app_id="87"
+ local docker_name="synctv"
+ local docker_img="synctvorg/synctv"
+ local docker_port=8087
+
+ docker_rum() {
+
+ docker run -d \
+ --name synctv \
+ -v /home/docker/synctv:/root/.synctv \
+ -p ${docker_port}:8080 \
+ --restart=always \
+ synctvorg/synctv
+
+ }
+
+ local docker_describe="リモートで映画や生放送を一緒に見るプログラム。同時視聴、ライブブロードキャスト、チャットなどの機能を提供します"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/synctv-org/synctv"
+ local docker_use="echo \"初期アカウントとパスワード: root。ログイン後、時間内にログイン パスワードを変更してください\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 88|owncast)
+
+ local app_id="88"
+ local docker_name="owncast"
+ local docker_img="owncast/owncast:latest"
+ local docker_port=8088
+
+ docker_rum() {
+
+ docker run -d \
+ --name owncast \
+ -p ${docker_port}:8080 \
+ -p 1935:1935 \
+ -v /home/docker/owncast/data:/app/data \
+ --restart=always \
+ owncast/owncast:latest
+
+
+ }
+
+ local docker_describe="オープンソース、無料の自社構築ライブ ブロードキャスト プラットフォーム"
+ local docker_url="公式サイト紹介:https://owncast.online"
+ local docker_use="echo \"管理者ページにアクセスするには、アクセス アドレスの後に /admin を続けます\""
+ local docker_passwd="echo \"初期アカウント: admin 初期パスワード: abc123 ログイン後、時間内にログイン パスワードを変更してください\""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 89|file-code-box)
+
+ local app_id="89"
+ local docker_name="file-code-box"
+ local docker_img="lanol/filecodebox:latest"
+ local docker_port=8089
+
+ docker_rum() {
+
+ docker run -d \
+ --name file-code-box \
+ -p ${docker_port}:12345 \
+ -v /home/docker/file-code-box/data:/app/data \
+ --restart=always \
+ lanol/filecodebox:latest
+
+ }
+
+ local docker_describe="匿名のパスワードでテキストやファイルを共有し、速達のようにファイルを受け取ります"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/vastsa/FileCodeBox"
+ local docker_use="echo \"管理者ページにアクセスするには、アクセス アドレスの後に /#/admin を続けます\""
+ local docker_passwd="echo \"管理者パスワード: FileCodeBox2023\""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+
+ 90|matrix)
+
+ local app_id="90"
+ local docker_name="matrix"
+ local docker_img="matrixdotorg/synapse:latest"
+ local docker_port=8090
+
+ docker_rum() {
+
+ add_yuming
+
+ if [ ! -d /home/docker/matrix/data ]; then
+ docker run --rm \
+ -v /home/docker/matrix/data:/data \
+ -e SYNAPSE_SERVER_NAME=${yuming} \
+ -e SYNAPSE_REPORT_STATS=yes \
+ --name matrix \
+ matrixdotorg/synapse:latest generate
+ fi
+
+ docker run -d \
+ --name matrix \
+ -v /home/docker/matrix/data:/data \
+ -p ${docker_port}:8008 \
+ --restart=always \
+ matrixdotorg/synapse:latest
+
+ echo "初期ユーザーまたは管理者を作成します。以下のユーザー名とパスワード、および管理者であるかどうかを設定してください。"
+ docker exec -it matrix register_new_matrix_user \
+ http://localhost:8008 \
+ -c /data/homeserver.yaml
+
+ sed -i '/^enable_registration:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration: true' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^enable_registration_without_verification:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration_without_verification: true' /home/docker/matrix/data/homeserver.yaml
+ docker restart matrix
- local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
- local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 38)
- send_stats "Xiaoyaファミリーバケット"
- clear
- install_docker
- check_disk_space 1
- bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
- ;;
+ }
- 39)
+ local docker_describe="Matrix は分散型チャット プロトコルです"
+ local docker_url="公式サイト紹介:https://matrix.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- if [ ! -d /home/docker/bililive-go/ ]; then
- mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
- wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
- fi
+ ;;
- local docker_name="bililive-go"
- local docker_img="chigusa/bililive-go"
- local docker_port=8039
- docker_rum() {
- docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+ 91|gitea)
- }
+ local app_id="91"
- local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_name="gitea プライベート コード リポジトリ"
+ local app_text="GitHub に近いエクスペリエンスを提供する、無料の新世代コード ホスティング プラットフォーム。"
+ local app_url="ビデオの紹介:${gh_https_url}github.com/go-gitea/gitea"
+ local docker_name="gitea"
+ local docker_port="8091"
+ local app_size="2"
- 40)
- local docker_name="webssh"
- local docker_img="jrohy/webssh"
- local docker_port=8040
- docker_rum() {
- docker run -d -p ${docker_port}:5032 --restart always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
- }
+ docker_app_install() {
- local docker_describe="简易在线ssh连接工具和sftp工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/gitea
+ mkdir -p /home/docker/gitea/gitea
+ mkdir -p /home/docker/gitea/data
+ mkdir -p /home/docker/gitea/postgres
+ cd /home/docker/gitea
- 41)
+ curl -o /home/docker/gitea/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/gitea-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/gitea/docker-compose.yml
+ cd /home/docker/gitea/
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="耗子面板"
- local panelurl="官方地址: ${gh_proxy}github.com/TheTNB/panel"
- panel_app_install() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
- cd ~
- }
+ docker_app_update() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ cd /home/docker/gitea/ && docker compose up -d
+ }
- panel_app_manage() {
- panel-cli
- }
- panel_app_uninstall() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
- cd ~
- }
+ docker_app_uninstall() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ rm -rf /home/docker/gitea
+ echo "アプリがアンインストールされました"
+ }
- install_panel
+ docker_app_plus
- ;;
+ ;;
- 42)
- local docker_name="nexterm"
- local docker_img="germannewsmaker/nexterm:latest"
- local docker_port=8042
- docker_rum() {
- docker run -d \
- --name nexterm \
- -p ${docker_port}:6989 \
- -v /home/docker/nexterm:/app/data \
- --restart unless-stopped \
- germannewsmaker/nexterm:latest
+ 92|filebrowser)
- }
+ local app_id="92"
+ local docker_name="filebrowser"
+ local docker_img="hurlenko/filebrowser"
+ local docker_port=8092
- local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
- local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 43)
- local docker_name="hbbs"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker run -d \
+ --name filebrowser \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/filebrowser/data:/data \
+ -v /home/docker/filebrowser/config:/config \
+ -e FB_BASEURL=/filebrowser \
+ hurlenko/filebrowser
- docker_rum() {
+ }
- docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbs
+ local docker_describe="Webベースのファイルマネージャーです"
+ local docker_url="公式サイト紹介:https://filebrowser.org/"
+ local docker_use="docker logs filebrowser"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
+ 93|dufs)
- local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="docker logs hbbs"
- local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
- local app_size="1"
- docker_app
- ;;
+ local app_id="93"
+ local docker_name="dufs"
+ local docker_img="sigoden/dufs"
+ local docker_port=8093
- 44)
- local docker_name="hbbr"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker_rum() {
- docker_rum() {
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}:/data \
+ -p ${docker_port}:5000 \
+ ${docker_img} /data -A
- docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbr
+ }
- }
+ local docker_describe="シンプルな静的ファイル サーバー、アップロードとダウンロードをサポート"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/sigoden/dufs"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 45)
- local docker_name="registry"
- local docker_img="registry:2"
- local docker_port=8045
+ 94|gopeed)
- docker_rum() {
+ local app_id="94"
+ local docker_name="gopeed"
+ local docker_img="liwei2633/gopeed"
+ local docker_port=8094
- docker run -d \
- -p ${docker_port}:5000 \
- --name registry \
- -v /home/docker/registry:/var/lib/registry \
- -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
- --restart always \
- registry:2
+ docker_rum() {
- }
+ read -e -p "ログインユーザー名を設定します:" app_use
+ read -e -p "ログインパスワードを設定します:" app_passwd
- local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
- local docker_url="官网介绍: https://hub.docker.com/_/registry"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}/downloads:/app/Downloads \
+ -v /home/docker/${docker_name}/storage:/app/storage \
+ -p ${docker_port}:9999 \
+ ${docker_img} -u ${app_use} -p ${app_passwd}
- 46)
- local docker_name="ghproxy"
- local docker_img="wjqserver/ghproxy:latest"
- local docker_port=8046
+ }
- docker_rum() {
+ local docker_describe="複数のプロトコルをサポートする分散型高速ダウンロード ツール"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/GopeedLab/gopeed"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d --name ghproxy --restart always -p ${docker_port}:8080 wjqserver/ghproxy:latest
+ ;;
- }
- local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
- local docker_url="官网介绍: https://github.com/WJQSERVER-STUDIO/ghproxy"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 47)
+ 95|paperless)
+ local app_id="95"
+ local app_name="ペーパーレス文書管理プラットフォーム"
+ local app_text="オープンソースの電子文書管理システムであり、その主な目的は紙文書をデジタル化して管理することです。"
+ local app_url="ビデオ紹介: https://docs.paperless-ngx.com/"
+ local docker_name="paperless-webserver-1"
+ local docker_port="8095"
+ local app_size="2"
- local app_name="普罗米修斯监控"
- local app_text="Prometheus+Grafana企业级监控系统"
- local app_url="官网介绍: https://prometheus.io"
- local docker_name="grafana"
- local docker_port="8047"
- local app_size="2"
+ docker_app_install() {
- docker_app_install() {
- prometheus_install
- clear
- ip_address
- echo "インストール"
- check_docker_app_ip
- echo "最初のユーザー名とパスワードは次のとおりです"
- }
+ mkdir -p /home/docker/paperless
+ mkdir -p /home/docker/paperless/export
+ mkdir -p /home/docker/paperless/consume
+ cd /home/docker/paperless
- docker_app_update() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
- docker_app_install
- }
+ curl -o /home/docker/paperless/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/docker-compose.postgres-tika.yml
+ curl -o /home/docker/paperless/docker-compose.env ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/.env
- docker_app_uninstall() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/paperless/docker-compose.yml
+ cd /home/docker/paperless
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- rm -rf /home/docker/monitoring
- echo "アプリはアンインストールされています"
- }
- docker_app_plus
- ;;
+ docker_app_update() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ docker_app_install
+ }
- 48)
- local docker_name="node-exporter"
- local docker_img="prom/node-exporter"
- local docker_port=8048
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ rm -rf /home/docker/paperless
+ echo "アプリがアンインストールされました"
+ }
- docker run -d \
- --name=node-exporter \
- -p ${docker_port}:9100 \
- --restart unless-stopped \
- prom/node-exporter
+ docker_app_plus
+ ;;
- }
- local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/prometheus/node_exporter"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
-
- 49)
- local docker_name="cadvisor"
- local docker_img="gcr.io/cadvisor/cadvisor:latest"
- local docker_port=8049
-
- docker_rum() {
-
- docker run -d \
- --name=cadvisor \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- --volume=/:/rootfs:ro \
- --volume=/var/run:/var/run:rw \
- --volume=/sys:/sys:ro \
- --volume=/var/lib/docker/:/var/lib/docker:ro \
- gcr.io/cadvisor/cadvisor:latest \
- -housekeeping_interval=10s \
- -docker_only=true
- }
+ 96|2fauth)
- local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/google/cadvisor"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="96"
+ local app_name="2FAuth セルフホスト型 2 段階認証システム"
+ local app_text="セルフホスト型の 2 要素認証 (2FA) アカウント管理および検証コード生成ツール。"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/Bubka/2FAuth"
+ local docker_name="2fauth"
+ local docker_port="8096"
+ local app_size="1"
- 50)
- local docker_name="changedetection"
- local docker_img="dgtlmoon/changedetection.io:latest"
- local docker_port=8050
+ docker_app_install() {
- docker_rum() {
+ add_yuming
- docker run -d --restart always -p ${docker_port}:5000 \
- -v /home/docker/datastore:/datastore \
- --name changedetection dgtlmoon/changedetection.io:latest
+ mkdir -p /home/docker/2fauth
+ mkdir -p /home/docker/2fauth/data
+ chmod -R 777 /home/docker/2fauth/
+ cd /home/docker/2fauth
- }
+ curl -o /home/docker/2fauth/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/2fauth-docker-compose.yml
- local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
- local docker_url="官网介绍: https://github.com/dgtlmoon/changedetection.io"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/2fauth/docker-compose.yml
+ sed -i "s/yuming.com/${yuming}/g" /home/docker/2fauth/docker-compose.yml
+ cd /home/docker/2fauth
+ docker compose up -d
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 51)
clear
- send_stats "PVEチキン"
- check_disk_space 1
- curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
- ;;
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- 52)
- local docker_name="dpanel"
- local docker_img="dpanel/dpanel:lite"
- local docker_port=8052
+ docker_app_update() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ docker_app_install
+ }
- docker_rum() {
- docker run -it -d --name dpanel --restart=always \
- -p ${docker_port}:8080 -e APP_NAME=dpanel \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/dpanel:/dpanel \
- dpanel/dpanel:lite
+ docker_app_uninstall() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ rm -rf /home/docker/2fauth
+ echo "アプリがアンインストールされました"
+ }
- }
+ docker_app_plus
- local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
- local docker_url="官网介绍: https://github.com/donknap/dpanel"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 53)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
- docker_rum() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ 97|wgs)
- }
+ local app_id="97"
+ local docker_name="wireguard"
+ local docker_img="lscr.io/linuxserver/wireguard:latest"
+ local docker_port=8097
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run llama3.2:1b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_rum() {
- 54)
+ read -e -p "ネットワーク内のクライアントの数を入力してください (デフォルトは 5):" COUNT
+ COUNT=${COUNT:-5}
+ read -e -p "WireGuard ネットワーク セグメントを入力してください (デフォルトは 10.13.13.0):" NETWORK
+ NETWORK=${NETWORK:-10.13.13.0}
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="AMH面板"
- local panelurl="官方地址: https://amh.sh/index.htm?amh"
+ PEERS=$(seq -f "wg%02g" 1 "$COUNT" | paste -sd,)
- panel_app_install() {
- cd ~
- wget https://dl.amh.sh/amh.sh && bash amh.sh
- }
+ ip link delete wg0 &>/dev/null
- panel_app_manage() {
- panel_app_install
- }
+ ip_address
+ docker run -d \
+ --name=wireguard \
+ --network host \
+ --cap-add=NET_ADMIN \
+ --cap-add=SYS_MODULE \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SERVERURL=${ipv4_address} \
+ -e SERVERPORT=51820 \
+ -e PEERS=${PEERS} \
+ -e INTERNAL_SUBNET=${NETWORK} \
+ -e ALLOWEDIPS=${NETWORK}/24 \
+ -e PERSISTENTKEEPALIVE_PEERS=all \
+ -e LOG_CONFS=true \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules \
+ --restart=always \
+ lscr.io/linuxserver/wireguard:latest
- panel_app_uninstall() {
- panel_app_install
- }
- install_panel
- ;;
+ sleep 3
+ docker exec wireguard sh -c "
+ f='/config/wg_confs/wg0.conf'
+ sed -i 's/51820/${docker_port}/g' \$f
+ "
- 55)
- frps_panel
- ;;
+ docker exec wireguard sh -c "
+ for d in /config/peer_*; do
+ sed -i 's/51820/${docker_port}/g' \$d/*.conf
+ done
+ "
- 56)
- frpc_panel
- ;;
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ sed -i "/^DNS/d" "$d"/*.conf
+ done
+ '
- 57)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ for f in "$d"/*.conf; do
+ grep -q "^PersistentKeepalive" "$f" || \
+ sed -i "/^AllowedIPs/ a PersistentKeepalive = 25" "$f"
+ done
+ done
+ '
+
+ docker exec wireguard bash -c '
+ for d in /config/peer_*; do
+ cd "$d" || continue
+ conf_file=$(ls *.conf)
+ base_name="${conf_file%.conf}"
+ qrencode -o "$base_name.png" < "$conf_file"
+ done
+ '
- docker_rum() {
+ docker restart wireguard
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ sleep 2
+ echo
+ echo -e "${gl_huang}すべてのクライアント QR コード構成:${gl_bai}"
+ docker exec wireguard bash -c 'for i in $(ls /config | grep peer_ | sed "s/peer_//"); do echo "--- $i ---"; /app/show-peer $i; done'
+ sleep 2
+ echo
+ echo -e "${gl_huang}すべてのクライアント構成コード:${gl_bai}"
+ docker exec wireguard sh -c 'for d in /config/peer_*; do echo "# $(basename $d) "; cat $d/*.conf; echo; done'
+ sleep 2
+ echo -e "${gl_lv}${COUNT}各クライアントのすべての出力を構成します。利用方法は以下の通りです。${gl_bai}"
+ echo -e "${gl_lv}1. 携帯電話に wg APP をダウンロードし、上の QR コードをスキャンして、すぐにインターネットに接続します。${gl_bai}"
+ echo -e "${gl_lv}2. Windows 用クライアントをダウンロードし、ネットワークに接続するための構成コードをコピーします。${gl_bai}"
+ echo -e "${gl_lv}3. スクリプトを使用して Linux に WG クライアントを展開し、構成コードをコピーしてネットワークに接続します。${gl_bai}"
+ echo -e "${gl_lv}公式クライアントのダウンロード方法:https://www.wireguard.com/install/${gl_bai}"
+ break_end
- }
+ }
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ local docker_describe="最新の高性能仮想プライベート ネットワーク ツール"
+ local docker_url="公式サイト紹介:https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 58)
- local app_name="Dify知识库"
- local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
- local app_url="官方网站: https://docs.dify.ai/zh-hans"
- local docker_name="docker-nginx-1"
- local docker_port="8058"
- local app_size="3"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
- # sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+ 98|wgc)
- docker compose up -d
- clear
- echo "インストール"
- check_docker_app_ip
- }
+ local app_id="98"
+ local docker_name="wireguardc"
+ local docker_img="kjlion/wireguard:alpine"
+ local docker_port=51820
- docker_app_update() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- cd /home/docker/dify/
- git pull origin main
- sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- cd /home/docker/dify/docker/ && docker compose up -d
- }
+ docker_rum() {
- docker_app_uninstall() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- rm -rf /home/docker/dify
- echo "アプリはアンインストールされています"
- }
+ mkdir -p /home/docker/wireguard/config/
- docker_app_plus
+ local CONFIG_FILE="/home/docker/wireguard/config/wg0.conf"
- ;;
+ # ディレクトリが存在しない場合は作成する
+ mkdir -p "$(dirname "$CONFIG_FILE")"
- 59)
- local app_name="New API"
- local app_text="新一代大模型网关与AI资产管理系统"
- local app_url="官方网站: https://github.com/Calcium-Ion/new-api"
- local docker_name="new-api"
- local docker_port="8059"
- local app_size="3"
+ echo "クライアント構成を貼り付け、Enter キーを 2 回押して保存してください。"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+ # 変数を初期化する
+ input=""
+ empty_line_count=0
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ # ユーザー入力を 1 行ずつ読み取ります
+ while IFS= read -r line; do
+ if [[ -z "$line" ]]; then
+ ((empty_line_count++))
+ if [[ $empty_line_count -ge 2 ]]; then
+ break
+ fi
+ else
+ empty_line_count=0
+ input+="$line"$'\n'
+ fi
+ done
+ # 設定ファイルの書き込み
+ echo "$input" > "$CONFIG_FILE"
- docker compose up -d
- clear
- echo "インストール"
- check_docker_app_ip
- }
+ echo "クライアント設定の保存場所$CONFIG_FILE"
- docker_app_update() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- cd /home/docker/new-api/
- git pull origin main
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ ip link delete wg0 &>/dev/null
- docker compose up -d
- clear
- echo "インストール"
- check_docker_app_ip
+ docker run -d \
+ --name wireguardc \
+ --network host \
+ --cap-add NET_ADMIN \
+ --cap-add SYS_MODULE \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules:ro \
+ --restart=always \
+ kjlion/wireguard:alpine
- }
+ sleep 3
- docker_app_uninstall() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- rm -rf /home/docker/new-api
- echo "アプリはアンインストールされています"
- }
+ docker logs wireguardc
- docker_app_plus
+ break_end
- ;;
+ }
+ local docker_describe="最新の高性能仮想プライベート ネットワーク ツール"
+ local docker_url="公式サイト紹介:https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 60)
+ ;;
- local app_name="JumpServer开源堡垒机"
- local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
- local app_url="官方介绍: https://github.com/jumpserver/jumpserver"
- local docker_name="jms_web"
- local docker_port="80"
- local app_size="2"
- docker_app_install() {
- curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
- clear
- echo "インストール"
- check_docker_app_ip
- echo "初期ユーザー名:admin"
- echo "最初のパスワード:changeme"
- }
+ 99|dsm)
+ local app_id="99"
- docker_app_update() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh upgrade
- echo "アプリが更新されました"
- }
+ local app_name="DSM Synology 仮想マシン"
+ local app_text="Dockerコンテナ内の仮想DSM"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/vdsm/virtual-dsm"
+ local docker_name="dsm"
+ local docker_port="8099"
+ local app_size="16"
+ docker_app_install() {
- docker_app_uninstall() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh uninstall
- cd /opt
- rm -rf jumpserver-installer*/
- rm -rf jumpserver
- echo "アプリはアンインストールされています"
- }
+ read -e -p "CPU コアの数を設定します (デフォルトは 2)。" CPU_CORES
+ local CPU_CORES=${CPU_CORES:-2}
- docker_app_plus
- ;;
+ read -e -p "メモリ サイズを設定します (デフォルトは 4G):" RAM_SIZE
+ local RAM_SIZE=${RAM_SIZE:-4}
- 61)
- local docker_name="libretranslate"
- local docker_img="libretranslate/libretranslate:latest"
- local docker_port=8061
+ mkdir -p /home/docker/dsm
+ mkdir -p /home/docker/dsm/dev
+ chmod -R 777 /home/docker/dsm/
+ cd /home/docker/dsm
- docker_rum() {
+ curl -o /home/docker/dsm/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/dsm-docker-compose.yml
- docker run -d \
- -p ${docker_port}:5000 \
- --name libretranslate \
- libretranslate/libretranslate \
- --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+ sed -i "s/5000:5000/${docker_port}:5000/g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|CPU_CORES: "2"|CPU_CORES: "${CPU_CORES}"|g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|RAM_SIZE: "2G"|RAM_SIZE: "${RAM_SIZE}G"|g" /home/docker/dsm/docker-compose.yml
+ cd /home/docker/dsm
+ docker compose up -d
- }
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
- local docker_url="官网介绍: https://github.com/LibreTranslate/LibreTranslate"
- local docker_use=""
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_app_update() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ docker_app_install
+ }
- 62)
- local app_name="RAGFlow知识库"
- local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
- local app_url="官方网站: https://github.com/infiniflow/ragflow"
- local docker_name="ragflow-server"
- local docker_port="8062"
- local app_size="8"
+ docker_app_uninstall() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ rm -rf /home/docker/dsm
+ echo "アプリがアンインストールされました"
+ }
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- clear
- echo "インストール"
- check_docker_app_ip
- }
+ docker_app_plus
- docker_app_update() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- cd /home/docker/ragflow/
- git pull origin main
- cd /home/docker/ragflow/docker/
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- }
+ ;;
- docker_app_uninstall() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- rm -rf /home/docker/ragflow
- echo "アプリはアンインストールされています"
- }
- docker_app_plus
- ;;
+ 100|syncthing)
+ local app_id="100"
+ local docker_name="syncthing"
+ local docker_img="syncthing/syncthing:latest"
+ local docker_port=8100
- 63)
- local docker_name="open-webui"
- local docker_img="ghcr.io/open-webui/open-webui:main"
- local docker_port=8063
+ docker_rum() {
+ docker run -d \
+ --name=syncthing \
+ --hostname=my-syncthing \
+ --restart=always \
+ -p ${docker_port}:8384 \
+ -p 22000:22000/tcp \
+ -p 22000:22000/udp \
+ -p 21027:21027/udp \
+ -v /home/docker/syncthing:/var/syncthing \
+ syncthing/syncthing:latest
+ }
- docker_rum() {
+ local docker_describe="オープンソースのピアツーピア ファイル同期ツール。Dropbox や Resilio Sync に似ていますが、完全に分散化されています。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/syncthing/syncthing"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+ ;;
- }
- local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use=""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ 101|moneyprinterturbo)
+ local app_id="101"
+ local app_name="AI動画生成ツール"
+ local app_text="MoneyPrinterTurbo は、AI ラージモデルを使用して高解像度のショートビデオを合成するツールです"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/harry0703/MoneyPrinterTurbo"
+ local docker_name="moneyprinterturbo"
+ local docker_port="8101"
+ local app_size="3"
- 64)
- local docker_name="it-tools"
- local docker_img="corentinth/it-tools:latest"
- local docker_port=8064
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git && cd MoneyPrinterTurbo/
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
- docker_rum() {
- docker run -d --name it-tools --restart unless-stopped -p ${docker_port}:80 corentinth/it-tools:latest
- }
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
- local docker_url="官网介绍: https://github.com/CorentinTh/it-tools"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_update() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ cd /home/docker/MoneyPrinterTurbo/
+ git pull ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose up -d
+ }
- 65)
- local docker_name="n8n"
- local docker_img="docker.n8n.io/n8nio/n8n"
- local docker_port=8065
+ docker_app_uninstall() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ rm -rf /home/docker/MoneyPrinterTurbo
+ echo "アプリがアンインストールされました"
+ }
- docker_rum() {
+ docker_app_plus
- add_yuming
- mkdir -p /home/docker/n8n
- chmod -R 777 /home/docker/n8n
-
- docker run -d --name n8n \
- --restart always \
- -p ${docker_port}:5678 \
- -v /home/docker/n8n:/home/node/.n8n \
- -e N8N_HOST=${yuming} \
- -e N8N_PORT=5678 \
- -e N8N_PROTOCOL=https \
- -e N8N_WEBHOOK_URL=https://${yuming}/ \
- docker.n8n.io/n8nio/n8n
+ ;;
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
- }
- local docker_describe="是一款功能强大的自动化工作流平台"
- local docker_url="官网介绍: https://github.com/n8n-io/n8n"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 102|vocechat)
- 66)
- yt_menu_pro
- ;;
+ local app_id="102"
+ local docker_name="vocechat-server"
+ local docker_img="privoce/vocechat-server:latest"
+ local docker_port=8102
+ docker_rum() {
- 67)
- local docker_name="ddns-go"
- local docker_img="jeessy/ddns-go"
- local docker_port=8067
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ --name vocechat-server \
+ -v /home/docker/vocechat/data:/home/vocechat-server/data \
+ privoce/vocechat-server:latest
- docker_rum() {
- docker run -d \
- --name ddns-go \
- --restart=always \
- -p ${docker_port}:9876 \
- -v /home/docker/ddns-go:/root \
- jeessy/ddns-go
+ }
- }
+ local docker_describe="独自導入をサポートするパーソナルクラウドソーシャルメディアチャットサービスです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/Privoce/vocechat-web"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
- local docker_url="官网介绍: https://github.com/jeessy2/ddns-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 68)
- local docker_name="allinssl"
- local docker_img="allinssl/allinssl:latest"
- local docker_port=8068
- docker_rum() {
- docker run -itd --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
- }
+ 103|umami)
+ local app_id="103"
+ local app_name="Umami ウェブサイト統計ツール"
+ local app_text="Google Analytics に似た、オープンソースの軽量でプライバシーに配慮した Web サイト分析ツール。"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/umami-software/umami"
+ local docker_name="umami-umami-1"
+ local docker_port="8103"
+ local app_size="1"
- local docker_describe="开源免费的 SSL 证书自动化管理平台"
- local docker_url="官网介绍: https://allinssl.com"
- local docker_use="echo \"安全入口: /allinssl\""
- local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
- local app_size="1"
- docker_app
- ;;
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/umami-software/umami.git && cd umami
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/umami/docker-compose.yml
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ echo "初期ユーザー名: admin"
+ echo "初期パスワード:umami"
+ }
- 69)
- local docker_name="sftpgo"
- local docker_img="drakkan/sftpgo:latest"
- local docker_port=8069
+ docker_app_update() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ cd /home/docker/umami/
+ git pull ${gh_proxy}github.com/umami-software/umami.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/umami/docker-compose.yml
+ cd /home/docker/umami/ && docker compose up -d
+ }
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ rm -rf /home/docker/umami
+ echo "アプリがアンインストールされました"
+ }
- mkdir -p /home/docker/sftpgo/data
- mkdir -p /home/docker/sftpgo/config
- chown -R 1000:1000 /home/docker/sftpgo
+ docker_app_plus
- docker run -d \
- --name sftpgo \
- --restart=always \
- -p ${docker_port}:8080 \
- -p 22022:2022 \
- --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
- --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
- drakkan/sftpgo:latest
+ ;;
- }
+ 104|nginx-stream)
+ stream_panel
+ ;;
- local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
- local docker_url="官网介绍: https://sftpgo.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 105|siyuan)
- 70)
- local docker_name="astrbot"
- local docker_img="soulter/astrbot:latest"
- local docker_port=8070
+ local app_id="105"
+ local docker_name="siyuan"
+ local docker_img="b3log/siyuan"
+ local docker_port=8105
- docker_rum() {
+ docker_rum() {
- mkdir -p /home/docker/astrbot/data
+ read -e -p "ログインパスワードを設定します:" app_passwd
- sudo docker run -d \
- -p ${docker_port}:6185 \
- -p 6195:6195 \
- -p 6196:6196 \
- -p 6199:6199 \
- -p 11451:11451 \
- -v /home/docker/astrbot/data:/AstrBot/data \
- --restart unless-stopped \
- --name astrbot \
- soulter/astrbot:latest
+ docker run -d \
+ --name siyuan \
+ --restart=always \
+ -v /home/docker/siyuan/workspace:/siyuan/workspace \
+ -p ${docker_port}:6806 \
+ -e PUID=1001 \
+ -e PGID=1002 \
+ b3log/siyuan \
+ --workspace=/siyuan/workspace/ \
+ --accessAuthCode="${app_passwd}"
- }
+ }
- local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
- local docker_url="官网介绍: https://astrbot.app/"
- local docker_use="echo \"用户名: astrbot 密码: astrbot\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="Siyuan Notes はプライバシーを最優先したナレッジ管理システムです"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/siyuan-note/siyuan"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 71)
- local docker_name="navidrome"
- local docker_img="deluan/navidrome:latest"
- local docker_port=8071
- docker_rum() {
+ 106|drawnix)
- docker run -d \
- --name navidrome \
- --restart=unless-stopped \
- --user $(id -u):$(id -g) \
- -v /home/docker/navidrome/music:/music \
- -v /home/docker/navidrome/data:/data \
- -p ${docker_port}:4533 \
- -e ND_LOGLEVEL=info \
- deluan/navidrome:latest
+ local app_id="106"
+ local docker_name="drawnix"
+ local docker_img="pubuzhixing/drawnix"
+ local docker_port=8106
- }
+ docker_rum() {
- local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
- local docker_url="官网介绍: https://www.navidrome.org/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --restart=always \
+ --name drawnix \
+ -p ${docker_port}:80 \
+ pubuzhixing/drawnix
+ }
- 72)
+ local docker_describe="これは、マインド マップ、フローチャートなどを統合する強力なオープンソース ホワイトボード ツールです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/plait-board/drawnix"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="bitwarden"
- local docker_img="vaultwarden/server"
- local docker_port=8072
+ ;;
- docker_rum() {
- docker run -d \
- --name bitwarden \
- --restart always \
- -p ${docker_port}:80 \
- -v /home/docker/bitwarden/data:/data \
- vaultwarden/server
+ 107|pansou)
- }
+ local app_id="107"
+ local docker_name="pansou"
+ local docker_img="ghcr.io/fish2018/pansou-web"
+ local docker_port=8107
- local docker_describe="一个你可以控制数据的密码管理器"
- local docker_url="官网介绍: https://bitwarden.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
+ docker run -d \
+ --name pansou \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/pansou/data:/app/data \
+ -v /home/docker/pansou/logs:/app/logs \
+ -e ENABLED_PLUGINS="hunhepan,jikepan,panwiki,pansearch,panta,qupansou,
+susu,thepiratebay,wanou,xuexizhinan,panyq,zhizhen,labi,muou,ouge,shandian,
+duoduo,huban,cyg,erxiao,miaoso,fox4k,pianku,clmao,wuji,cldi,xiaozhang,
+libvio,leijing,xb6v,xys,ddys,hdmoli,yuhuage,u3c3,javdb,clxiong,jutoushe,
+sdso,xiaoji,xdyh,haisou,bixin,djgou,nyaa,xinjuc,aikanzy,qupanshe,xdpan,
+discourse,yunsou,ahhhhfs,nsgame,gying" \
+ ghcr.io/fish2018/pansou-web
- ;;
+ }
+ local docker_describe="PanSou は、高パフォーマンスのネットワーク ディスク リソース検索 API サービスです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/fish2018/pansou"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 73)
- local docker_name="libretv"
- local docker_img="bestzwei/libretv:latest"
- local docker_port=8073
- docker_rum() {
- read -e -p "libretvログインパスワードを設定します。" app_passwd
+ 108|langbot)
+ local app_id="108"
+ local app_name="LangBot チャットボット"
+ local app_text="これは、オープンソースの大規模言語モデルのネイティブ インスタント メッセージング ロボット開発プラットフォームです。"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/langbot-app/LangBot"
+ local docker_name="langbot_plugin_runtime"
+ local docker_port="8108"
+ local app_size="1"
- docker run -d \
- --name libretv \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -e PASSWORD=${app_passwd} \
- bestzwei/libretv:latest
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langbot-app/LangBot && cd LangBot/docker
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
- }
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/LibreSpark/LibreTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_update() {
+ cd /home/docker/LangBot/docker && docker compose down --rmi all
+ cd /home/docker/LangBot/
+ git pull ${gh_proxy}github.com/langbot-app/LangBot main > /dev/null 2>&1
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ cd /home/docker/LangBot/docker/ && docker compose up -d
+ }
- ;;
+ docker_app_uninstall() {
+ cd /home/docker/LangBot/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/LangBot
+ echo "アプリがアンインストールされました"
+ }
+ docker_app_plus
- 74)
+ ;;
- local docker_name="moontv"
- local docker_img="ghcr.io/senshinya/moontv:latest"
- local docker_port=8074
- docker_rum() {
+ 109|zfile)
- read -e -p "MOONTVログインパスワードを設定します。" app_passwd
+ local app_id="109"
+ local docker_name="zfile"
+ local docker_img="zhaojun1998/zfile:latest"
+ local docker_port=8109
- docker run -d \
- --name moontv \
- --restart unless-stopped \
- -p ${docker_port}:3000 \
- -e PASSWORD=${app_passwd} \
- ghcr.io/senshinya/moontv:latest
+ docker_rum() {
- }
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/senshinya/MoonTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d --name=zfile --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/zfile/db:/root/.zfile-v4/db \
+ -v /home/docker/zfile/logs:/root/.zfile-v4/logs \
+ -v /home/docker/zfile/file:/data/file \
+ -v /home/docker/zfile/application.properties:/root/.zfile-v4/application.properties \
+ zhaojun1998/zfile:latest
- ;;
+ }
- 75)
+ local docker_describe="これは、個人または小規模チームに適したオンライン ネットワーク ディスク プログラムです。"
+ local docker_url="公式サイト紹介:${gh_https_url}github.com/zfile-dev/zfile"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="melody"
- local docker_img="foamzou/melody:latest"
- local docker_port=8075
+ ;;
- docker_rum() {
- docker run -d \
- --name melody \
- --restart unless-stopped \
- -p ${docker_port}:5566 \
- -v /home/docker/melody/.profile:/app/backend/.profile \
- foamzou/melody:latest
+ 110|karakeep)
+ local app_id="110"
+ local app_name="karakeepのブックマーク管理"
+ local app_text="は、データ蓄積者向けに設計された人工知能機能を備えた自己ホスト型ブックマーク アプリです。"
+ local app_url="公式ウェブサイト:${gh_https_url}github.com/karakeep-app/karakeep"
+ local docker_name="docker-web-1"
+ local docker_port="8110"
+ local app_size="1"
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/karakeep-app/karakeep.git && cd karakeep/docker && cp .env.sample .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
- }
+ docker compose up -d
+ clear
+ echo "インストール完了"
+ check_docker_app_ip
+ }
- local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
- local docker_url="官网介绍: https://github.com/foamzou/melody"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_update() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ cd /home/docker/karakeep/
+ git pull ${gh_proxy}github.com/karakeep-app/karakeep.git main > /dev/null 2>&1
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ cd /home/docker/karakeep/docker/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/karakeep
+ echo "アプリがアンインストールされました"
+ }
- ;;
+ docker_app_plus
+ ;;
- 76)
- local docker_name="dosgame"
- local docker_img="oldiy/dosgame-web-docker:latest"
- local docker_port=8076
- docker_rum() {
- docker run -d \
- --name dosgame \
- --restart unless-stopped \
- -p ${docker_port}:262 \
- oldiy/dosgame-web-docker:latest
+ 111|convertx)
- }
+ local app_id="111"
+ local docker_name="convertx"
+ local docker_img="ghcr.io/c4illin/convertx:latest"
+ local docker_port=8111
- local docker_describe="是一个中文DOS游戏合集网站"
- local docker_url="官网介绍: https://github.com/rwv/chinese-dos-games"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ docker_rum() {
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/convertx:/app/data \
+ ${docker_img}
- ;;
+ }
- 77)
+ local docker_describe="これは、強力なマルチフォーマット ファイル変換ツール (ドキュメント、画像、オーディオ、ビデオなどをサポート) です。ドメイン名アクセスを追加することを強くお勧めします。"
+ local docker_url="プロジェクトアドレス:${gh_https_url}github.com/c4illin/ConvertX"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- local docker_name="xunlei"
- local docker_img="cnk3x/xunlei"
- local docker_port=8077
+ ;;
- docker_rum() {
- read -e -p "設定${docker_name}ログインユーザー名:" app_use
- read -e -p "設定${docker_name}ログインパスワード:" app_passwd
+ 112|lucky)
- docker run -d \
- --name xunlei \
- --restart unless-stopped \
- --privileged \
- -e XL_DASHBOARD_USERNAME=${app_use} \
- -e XL_DASHBOARD_PASSWORD=${app_passwd} \
- -v /home/docker/xunlei/data:/xunlei/data \
- -v /home/docker/xunlei/downloads:/xunlei/downloads \
- -p ${docker_port}:2345 \
- cnk3x/xunlei
+ local app_id="112"
+ local docker_name="lucky"
+ local docker_img="gdy666/lucky:v2"
+ # Lucky はホスト ネットワーク モードを使用するため、ここでのポートは記録と説明の参照のみを目的としており、実際にはアプリケーション自体によって制御されます (デフォルトは 16601)。
+ local docker_port=8112
- }
+ docker_rum() {
- local docker_describe="迅雷你的离线高速BT磁力下载工具"
- local docker_url="官网介绍: https://github.com/cnk3x/xunlei"
- local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d --name=${docker_name} --restart=always \
+ --network host \
+ -v /home/docker/lucky/conf:/app/conf \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ ${docker_img}
- ;;
+ echo "Lucky が初期化されるのを待っています..."
+ sleep 10
+ docker exec lucky /app/lucky -rSetHttpAdminPort ${docker_port}
+ }
+ local docker_describe="Lucky は、DDNS、リバース プロキシ、WOL、その他の機能をサポートする大規模なイントラネット ペネトレーションおよびポート フォワーディング管理ツールです。"
+ local docker_url="プロジェクトアドレス:${gh_https_url}github.com/gdy666/lucky"
+ local docker_use="echo \"デフォルトのアカウントパスワード: 666\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 78)
+ ;;
- local app_name="PandaWiki"
- local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
- local app_url="官方介绍: https://github.com/chaitin/PandaWiki"
- local docker_name="panda-wiki-nginx"
- local docker_port="2443"
- local app_size="2"
- docker_app_install() {
- bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
- }
+ 113|firefox)
- docker_app_update() {
- docker_app_install
- }
+ local app_id="113"
+ local docker_name="firefox"
+ local docker_img="jlesage/firefox:latest"
+ local docker_port=8113
+ docker_rum() {
- docker_app_uninstall() {
- docker_app_install
- }
+ read -e -p "ログインパスワードを設定します:" admin_password
- docker_app_plus
- ;;
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:5800 \
+ -v /home/docker/firefox:/config:rw \
+ -e ENABLE_CJK_FONT=1 \
+ -e WEB_AUDIO=1 \
+ -e VNC_PASSWORD="${admin_password}" \
+ ${docker_img}
+ }
+ local docker_describe="これは、Web ページを介したデスクトップ ブラウザ インターフェイスへの直接アクセスをサポートする、Docker で実行される Firefox ブラウザです。"
+ local docker_url="プロジェクトアドレス:${gh_https_url}github.com/jlesage/docker-firefox"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 79)
+ 114|Moltbot|ClawdBot|moltbot|clawdbot|openclaw|OpenClaw)
+ moltbot_menu
+ ;;
- local docker_name="beszel"
- local docker_img="henrygd/beszel"
- local docker_port=8079
+ 115|hermes)
+ bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/hermes_manager.sh)
+ ;;
- docker_rum() {
+ b)
+ clear
+ send_stats "すべてのアプリケーションのバックアップ"
- mkdir -p /home/docker/beszel && \
- docker run -d \
- --name beszel \
- --restart=unless-stopped \
- -v /home/docker/beszel:/beszel_data \
- -p ${docker_port}:8090 \
- henrygd/beszel
+ local backup_filename="app_$(date +"%Y%m%d%H%M%S").tar.gz"
+ echo -e "${gl_kjlan}バックアップ中$backup_filename ...${gl_bai}"
+ cd / && tar czvf "$backup_filename" home
- }
+ while true; do
+ clear
+ echo "バックアップファイルが作成されました: /$backup_filename"
+ read -e -p "バックアップ データをリモート サーバーに転送しますか? (はい/いいえ):" choice
+ case "$choice" in
+ [Yy])
+ kj_ssh_read_host_port "リモートサーバーのIPを入力してください:" "ターゲット サーバーの SSH ポート [デフォルト 22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
+ local latest_tar=$(ls -t /app*.tar.gz | head -1)
+ if [ -n "$latest_tar" ]; then
+ ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
+ sleep 2 # 添加等待时间
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/"
+ echo "ファイルはリモート サーバー/ルート ディレクトリに転送されます。"
+ else
+ echo "転送するファイルが見つかりませんでした。"
+ fi
+ break
+ ;;
+ *)
+ echo "注: 現在のバックアップには Docker プロジェクトのみが含まれており、Pagoda や 1panel などの Web サイト構築パネルのデータ バックアップは含まれていません。"
+ break
+ ;;
+ esac
+ done
- local docker_describe="Beszel轻量易用的服务器监控"
- local docker_url="官网介绍: https://beszel.dev/zh/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ ;;
- ;;
+ r)
+ root_use
+ send_stats "すべてのアプリを復元する"
+ echo "利用可能なアプリケーションのバックアップ"
+ echo "-------------------------"
+ ls -lt /app*.gz | awk '{print $NF}'
+ echo ""
+ read -e -p "Enter キーを押して最新のバックアップを復元し、バックアップ ファイル名を入力して指定したバックアップを復元し、0 を入力して終了します。" filename
+ if [ "$filename" == "0" ]; then
+ break_end
+ linux_panel
+ fi
+
+ # ユーザーがファイル名を入力しない場合は、最新の圧縮パッケージが使用されます。
+ if [ -z "$filename" ]; then
+ local filename=$(ls -t /app*.tar.gz | head -1)
+ fi
+
+ if [ -n "$filename" ]; then
+ echo -e "${gl_kjlan}解凍中$filename ...${gl_bai}"
+ cd / && tar -xzf "$filename"
+ echo "アプリケーションデータが復元されました。現在、アプリケーションを復元するには、手動で指定されたアプリケーションメニューに入り、アプリケーションを更新してください。"
+ else
+ echo "圧縮パッケージが見つかりませんでした。"
+ fi
- 0)
- kejilion
- ;;
- *)
- echo "無効な入力!"
- ;;
- esac
- break_end
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ cd ~
+ install git
+ if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+ else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+ fi
+ local custom_app="$HOME/apps/${sub_choice}.conf"
+ if [ -f "$custom_app" ]; then
+ . "$custom_app"
+ else
+ echo -e "${gl_hong}エラー: 番号が見つかりません${sub_choice}アプリケーション構成${gl_bai}"
+ fi
+ ;;
+ esac
+ break_end
+ sub_choice=""
- done
+done
}
+
linux_work() {
while true; do
clear
send_stats "バックエンドワークスペース"
echo -e "バックエンドワークスペース"
- echo -e "このシステムは、バックエンドで実行できるワークスペースを提供し、長期タスクを実行するために使用できます。"
- echo -e "SSHを切断したとしても、ワークスペースのタスクは中断されず、バックグラウンドのタスクが居住します。"
- echo -e "${gl_huang}ヒント:${gl_bai}ワークスペースに入った後、Ctrl+Bを使用してDを押してワークスペースを終了します!"
+ echo -e "システムは、バックグラウンドで永続的に実行できるワークスペースを提供し、長期的なタスクを実行するために使用できます。"
+ echo -e "SSH を切断しても、ワークスペース内のタスクは中断されず、タスクはバックグラウンドで残ります。"
+ echo -e "${gl_huang}ヒント:${gl_bai}ワークスペースに入ったら、Ctrl+b を使用し、d を単独で押してワークスペースを終了します。"
echo -e "${gl_kjlan}------------------------"
- echo "現在既存のワークスペースのリスト"
+ echo "現在存在するワークスペースのリスト"
echo -e "${gl_kjlan}------------------------"
tmux list-sessions
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}ワークスペース番号1"
- echo -e "${gl_kjlan}2. ${gl_bai}ワークスペースNo. 2"
- echo -e "${gl_kjlan}3. ${gl_bai}ワークスペース番号3"
- echo -e "${gl_kjlan}4. ${gl_bai}ワークスペースNo. 4"
- echo -e "${gl_kjlan}5. ${gl_bai}ワークスペースNo. 5"
- echo -e "${gl_kjlan}6. ${gl_bai}ワークスペースNo. 6"
- echo -e "${gl_kjlan}7. ${gl_bai}ワークスペースNo. 7"
- echo -e "${gl_kjlan}8. ${gl_bai}ワークスペースNo. 8"
- echo -e "${gl_kjlan}9. ${gl_bai}ワークスペースNo. 9"
- echo -e "${gl_kjlan}10. ${gl_bai}ワークスペースNo. 10"
+ echo -e "${gl_kjlan}1. ${gl_bai}作業エリア1"
+ echo -e "${gl_kjlan}2. ${gl_bai}作業エリア 2"
+ echo -e "${gl_kjlan}3. ${gl_bai}作業エリア 3"
+ echo -e "${gl_kjlan}4. ${gl_bai}作業エリア 4"
+ echo -e "${gl_kjlan}5. ${gl_bai}ワークスペースNo.5"
+ echo -e "${gl_kjlan}6. ${gl_bai}作業エリア6"
+ echo -e "${gl_kjlan}7. ${gl_bai}作業エリア 7"
+ echo -e "${gl_kjlan}8. ${gl_bai}作業エリア8"
+ echo -e "${gl_kjlan}9. ${gl_bai}ワークスペースNo.9"
+ echo -e "${gl_kjlan}10. ${gl_bai}ワークスペース10"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}SSH常駐モード${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}22. ${gl_bai}ワークスペースを作成/入力します"
- echo -e "${gl_kjlan}23. ${gl_bai}バックグラウンドワークスペースにコマンドを注入します"
- echo -e "${gl_kjlan}24. ${gl_bai}指定されたワークスペースを削除します"
+ echo -e "${gl_kjlan}22. ${gl_bai}ワークスペースの作成/入力"
+ echo -e "${gl_kjlan}23. ${gl_bai}バックグラウンドワークスペースにコマンドを挿入する"
+ echo -e "${gl_kjlan}24. ${gl_bai}指定したワークスペースを削除します"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
@@ -10676,7 +19493,7 @@ linux_work() {
clear
install tmux
local SESSION_NAME="work1"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
@@ -10684,134 +19501,624 @@ linux_work() {
clear
install tmux
local SESSION_NAME="work2"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
3)
clear
install tmux
local SESSION_NAME="work3"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
4)
clear
install tmux
local SESSION_NAME="work4"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
5)
clear
install tmux
local SESSION_NAME="work5"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
6)
clear
install tmux
local SESSION_NAME="work6"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
7)
clear
install tmux
local SESSION_NAME="work7"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
8)
clear
install tmux
local SESSION_NAME="work8"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
9)
clear
install tmux
local SESSION_NAME="work9"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
tmux_run
;;
10)
clear
install tmux
local SESSION_NAME="work10"
- send_stats "ワークスペースを開始します$SESSION_NAME"
+ send_stats "ワークスペースの開始$SESSION_NAME"
+ tmux_run
+ ;;
+
+ 21)
+ while true; do
+ clear
+ if grep -q 'tmux attach-session -t sshd || tmux new-session -s sshd' ~/.bashrc; then
+ local tmux_sshd_status="${gl_lv}オンにする${gl_bai}"
+ else
+ local tmux_sshd_status="${gl_hui}閉鎖${gl_bai}"
+ fi
+ send_stats "SSH常駐モード"
+ echo -e "SSH常駐モード${tmux_sshd_status}"
+ echo "SSH 接続を開いた後、直接常駐モードに入り、前の動作状態に直接戻ります。"
+ echo "------------------------"
+ echo "1. オン 2. オフ"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択肢を入力してください:" gongzuoqu_del
+ case "$gongzuoqu_del" in
+ 1)
+ install tmux
+ local SESSION_NAME="sshd"
+ send_stats "ワークスペースの開始$SESSION_NAME"
+ grep -q "tmux attach-session -t sshd" ~/.bashrc || echo -e "\n# 自動的に tmux セッションに入ります\nif [[ -z \"\$TMUX\" ]]; then\n tmux attach-session -t sshd || tmux new-session -s sshd\nfi" >> ~/.bashrc
+ source ~/.bashrc
+ tmux_run
+ ;;
+ 2)
+ sed -i '/# 自動的に tmux セッションに入ります/,+4d' ~/.bashrc
+ tmux kill-window -t sshd
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+ ;;
+
+ 22)
+ read -e -p "作成または入力したワークスペースの名前を入力してください (1001 kj001 work1 など)。" SESSION_NAME
tmux_run
+ send_stats "カスタムワークスペース"
+ ;;
+
+
+ 23)
+ read -e -p "バックグラウンドで実行するコマンドを入力してください。たとえば、curl -fsSL https://get.docker.com |し:" tmuxd
+ tmux_run_d
+ send_stats "バックグラウンドワークスペースにコマンドを挿入する"
+ ;;
+
+ 24)
+ read -e -p "削除するワークスペースの名前を入力してください:" gongzuoqu_name
+ tmux kill-window -t $gongzuoqu_name
+ send_stats "ワークスペースの削除"
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ echo "無効な入力です!"
;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+# インテリジェントスイッチングミラーソース機能
+switch_mirror() {
+ # オプションのパラメータ、デフォルトは false
+ local upgrade_software=${1:-false}
+ local clean_cache=${2:-false}
+
+ # ユーザーの国を取得する
+ local country
+ country=$(curl -s ipinfo.io/country)
+
+ echo "検出された国:$country"
+
+ if [ "$country" = "CN" ]; then
+ echo "国内のミラーソースを使用..."
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.huaweicloud.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ echo "海外のミラーソースを使用..."
+ if [ -f /etc/os-release ] && grep -qi "oracle" /etc/os-release; then
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.xtom.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --use-official-source true \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ fi
+ fi
+}
+
+
+fail2ban_panel() {
+ root_use
+ send_stats "SSH防御"
+ while true; do
+
+ check_f2b_status
+ echo -e "SSH防御プログラム$check_f2b_status"
+ echo "failed2ban はブルート フォース クラッキングを防ぐ SSH ツールです"
+ echo "公式サイト紹介:${gh_proxy}github.com/fail2ban/fail2ban"
+ echo "------------------------"
+ echo "1. 防御プログラムをインストールする"
+ echo "------------------------"
+ echo "2. SSH インターセプト記録の表示"
+ echo "3. リアルタイムログ監視"
+ echo "------------------------"
+ echo "4. 基本パラメータ設定(禁止期間/時間枠/リトライ回数)"
+ echo "5. 設定ファイル(nano)の編集"
+ echo "------------------------"
+ echo "9. 防御プログラムをアンインストールする"
+ echo "------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "------------------------"
+ read -e -p "選択肢を入力してください:" sub_choice
+ case $sub_choice in
+ 1)
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ break_end
+ ;;
+ 2)
+ echo "------------------------"
+ f2b_sshd
+ echo "------------------------"
+ break_end
+ ;;
+ 3)
+ tail -f /var/log/fail2ban.log
+ break
+ ;;
+ 4)
+ send_stats "SSH防御基本パラメータ設定"
+ f2b_basic_config
+ break_end
+ ;;
+ 5)
+ send_stats "SSH 防御構成ファイルの編集"
+ f2b_edit_config
+ break_end
+ ;;
+ 9)
+ remove fail2ban
+ rm -rf /etc/fail2ban
+ echo "Fail2Ban 防御プログラムがアンインストールされました"
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+}
+
+
+
+
+
+net_menu() {
+
+ send_stats "ネットワークカード管理ツール"
+ show_nics() {
+ echo "================ 現在のネットワークカード情報 ================"
+ printf "%-18s %-12s %-20s %-26s\n" "ネットワークカード名" "州" "IPアドレス" "MACアドレス"
+ echo "------------------------------------------------"
+ for nic in $(ls /sys/class/net); do
+ state=$(cat /sys/class/net/$nic/operstate 2>/dev/null)
+ ipaddr=$(ip -4 addr show $nic | awk '/inet /{print $2}' | head -n1)
+ mac=$(cat /sys/class/net/$nic/address 2>/dev/null)
+ printf "%-15s %-10s %-18s %-20s\n" "$nic" "$state" "${ipaddr:-无}" "$mac"
+ done
+ echo "================================================"
+ }
+
+ while true; do
+ clear
+ show_nics
+ echo
+ echo "=========== ネットワークカード管理メニュー ==========="
+ echo "1.ネットワークカードを有効にする"
+ echo "2.ネットワークカードを無効にする"
+ echo "3. ネットワークカードの詳細を表示する"
+ echo "4. ネットワークカード情報を更新する"
+ echo "0. 前のメニューに戻る"
+ echo "===================================="
+ read -erp "アクションを選択してください:" choice
+
+ case $choice in
+ 1)
+ send_stats "ネットワークカードを有効にする"
+ read -erp "有効にするネットワーク カードの名前を入力してください:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" up && echo "✔ ネットワークカード$nic有効"
+ else
+ echo "✘ ネットワークカードが存在しません"
+ fi
+ read -erp "Enter を押して続行します..."
+ ;;
+ 2)
+ send_stats "ネットワークカードを無効にする"
+ read -erp "無効にするネットワーク カードの名前を入力してください:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" down && echo "✔ ネットワークカード$nic無効"
+ else
+ echo "✘ ネットワークカードが存在しません"
+ fi
+ read -erp "Enter を押して続行します..."
+ ;;
+ 3)
+ send_stats "ネットワークカードの詳細を表示する"
+ read -erp "表示したいネットワーク カードの名前を入力してください:" nic
+ if ip link show "$nic" &>/dev/null; then
+ echo "========== $nic詳細 =========="
+ ip addr show "$nic"
+ ethtool "$nic" 2>/dev/null | head -n 10
+ else
+ echo "✘ ネットワークカードが存在しません"
+ fi
+ read -erp "Enter を押して続行します..."
+ ;;
+ 4)
+ send_stats "ネットワークカード情報を更新する"
+ continue
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+log_menu() {
+ send_stats "システムログ管理ツール"
+
+ show_log_overview() {
+ echo "============= システムログの概要 ============="
+ echo "ホスト名: $(ホスト名)"
+ echo "システム時間: $(日付)"
+ echo
+ echo "[/var/logディレクトリ占有]"
+ du -sh /var/log 2>/dev/null
+ echo
+ echo "【日記帳の職業】"
+ journalctl --disk-usage 2>/dev/null
+ echo "========================================"
+ }
+
+ while true; do
+ clear
+ show_log_overview
+ echo
+ echo "=========== システムログ管理メニュー ==========="
+ echo "1. 最新のシステムログ(ジャーナル)を表示する"
+ echo "2. 指定したサービスログを表示します。"
+ echo "3. ログイン/セキュリティログの表示"
+ echo "4. リアルタイム追跡ログ"
+ echo "5. 古いジャーナル ログをクリーンアップする"
+ echo "0. 前のメニューに戻る"
+ echo "======================================="
+ read -erp "アクションを選択してください:" choice
+
+ case $choice in
+ 1)
+ send_stats "最近のログを表示する"
+ read -erp "最新のログ行を表示しますか? [デフォルト 100]:" lines
+ lines=${lines:-100}
+ journalctl -n "$lines" --no-pager
+ read -erp "Enter を押して続行します..."
+ ;;
+ 2)
+ send_stats "指定したサービスログを表示する"
+ read -erp "サービス名 (sshd、nginx など) を入力してください:" svc
+ if systemctl list-unit-files | grep -q "^$svc"; then
+ journalctl -u "$svc" -n 100 --no-pager
+ else
+ echo "✘ サービスが存在しないか、ログがありません"
+ fi
+ read -erp "Enter を押して続行します..."
+ ;;
+ 3)
+ send_stats "ログイン/セキュリティログの表示"
+ echo "====== 最近のログインログ ======"
+ last -n 10
+ echo
+ echo "====== 認証ログ ======"
+ if [ -f /var/log/secure ]; then
+ tail -n 20 /var/log/secure
+ elif [ -f /var/log/auth.log ]; then
+ tail -n 20 /var/log/auth.log
+ else
+ echo "セキュリティログファイルが見つかりません"
+ fi
+ read -erp "Enter を押して続行します..."
+ ;;
+ 4)
+ send_stats "リアルタイム追跡ログ"
+ echo "1) システムログ"
+ echo "2) サービスログの指定"
+ read -erp "追跡タイプを選択します:" t
+ if [ "$t" = "1" ]; then
+ journalctl -f
+ elif [ "$t" = "2" ]; then
+ read -erp "サービス名を入力してください:" svc
+ journalctl -u "$svc" -f
+ else
+ echo "無効な選択"
+ fi
+ ;;
+ 5)
+ send_stats "古いジャーナル ログをクリーンアップする"
+ echo "⚠️ ジャーナルをクリーンアップします (安全な方法)"
+ echo "1) 過去 7 日間を保存します"
+ echo "2) 過去 3 日間を保存します"
+ echo "3) 最大ログ サイズを 500M に制限する"
+ read -erp "クリーニング方法を選択してください:" c
+ case $c in
+ 1) journalctl --vacuum-time=7d ;;
+ 2) journalctl --vacuum-time=3d ;;
+ 3) journalctl --vacuum-size=500M ;;
+ *) echo "無効なオプション" ;;
+ esac
+ echo "✔ ジャーナルログのクリーニングが完了しました"
+ sleep 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+env_menu() {
+
+ BASHRC="$HOME/.bashrc"
+ PROFILE="$HOME/.profile"
+
+ send_stats "システム変数管理ツール"
+
+ show_env_vars() {
+ clear
+ send_stats "現在有効な環境変数"
+ echo "========== 現在有効な環境変数(抜粋) =========="
+ printf "%-20s %s\n" "変数名" "価値"
+ echo "-----------------------------------------------"
+ for v in USER HOME SHELL LANG PWD; do
+ printf "%-20s %s\n" "$v" "${!v}"
+ done
+
+ echo
+ echo "PATH:"
+ echo "$PATH" | tr ':' '\n' | nl -ba
+
+ echo
+ echo "========== 設定ファイルに定義されている変数 (解析) =========="
+
+ parse_file_vars() {
+ local file="$1"
+ [ -f "$file" ] || return
+
+ echo
+ echo ">>> ソースファイル:$file"
+ echo "-----------------------------------------------"
+
+ # 抽出エクスポート VAR=xxx または VAR=xxx
+ grep -Ev '^\s*#|^\s*$' "$file" \
+ | grep -E '^(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' \
+ | while read -r line; do
+ var=$(echo "$line" | sed -E 's/^(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*).*/\2/')
+ val=$(echo "$line" | sed -E 's/^[^=]+=//')
+ printf "%-20s %s\n" "$var" "$val"
+ done
+ }
+
+ parse_file_vars "$HOME/.bashrc"
+ parse_file_vars "$HOME/.profile"
+
+ echo
+ echo "==============================================="
+ read -erp "Enter を押して続行します..."
+ }
+
+
+ view_file() {
+ local file="$1"
+ send_stats "変数ファイルを表示する$file"
+ clear
+ if [ -f "$file" ]; then
+ echo "========== ファイルを表示:$file =========="
+ cat -n "$file"
+ echo "===================================="
+ else
+ echo "ファイルが存在しません:$file"
+ fi
+ read -erp "Enter を押して続行します..."
+ }
+
+ edit_file() {
+ local file="$1"
+ send_stats "変数ファイルを編集する$file"
+ install nano
+ nano "$file"
+ }
+
+ source_files() {
+ echo "環境変数を再ロードしています..."
+ send_stats "環境変数の再読み込み"
+ source "$BASHRC"
+ source "$PROFILE"
+ echo "✔ 環境変数がリロードされました"
+ read -erp "Enter を押して続行します..."
+ }
+
+ while true; do
+ clear
+ echo "=========== システム環境変数の管理 =========="
+ echo "現在のユーザー:$USER"
+ echo "--------------------------------------"
+ echo "1. 現在一般的に使用されている環境変数を確認します。"
+ echo "2. ~/.bashrc を表示する"
+ echo "3. ~/.profile を表示する"
+ echo "4. ~/.bashrc を編集します"
+ echo "5. ~/.profile を編集する"
+ echo "6. 環境変数 (ソース) をリロードします。"
+ echo "--------------------------------------"
+ echo "0. 前のメニューに戻る"
+ echo "--------------------------------------"
+ read -erp "アクションを選択してください:" choice
+
+ case "$choice" in
+ 1)
+ show_env_vars
+ ;;
+ 2)
+ view_file "$BASHRC"
+ ;;
+ 3)
+ view_file "$PROFILE"
+ ;;
+ 4)
+ edit_file "$BASHRC"
+ ;;
+ 5)
+ edit_file "$PROFILE"
+ ;;
+ 6)
+ source_files
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "無効なオプション"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
+
+create_user_with_sshkey() {
+ local new_username="$1"
+ local is_sudo="${2:-false}"
+ local sshkey_vl
+
+ if [[ -z "$new_username" ]]; then
+ echo "使用法: create_user_with_sshkey <ユーザー名>"
+ return 1
+ fi
- 21)
- while true; do
- clear
- if grep -q 'tmux attach-session -t sshd || tmux new-session -s sshd' ~/.bashrc; then
- local tmux_sshd_status="${gl_lv}开启${gl_bai}"
- else
- local tmux_sshd_status="${gl_hui}关闭${gl_bai}"
- fi
- send_stats "SSH常駐モード"
- echo -e "SSH常駐モード${tmux_sshd_status}"
- echo "SSH接続が有効になった後、レジデントモードに直接入力し、以前の作業状態に戻ります。"
- echo "------------------------"
- echo "1。2をオンにします。オフにします"
- echo "------------------------"
- echo "0。前のメニューに戻ります"
- echo "------------------------"
- read -e -p "選択を入力してください:" gongzuoqu_del
- case "$gongzuoqu_del" in
- 1)
- install tmux
- local SESSION_NAME="sshd"
- send_stats "ワークスペースを開始します$SESSION_NAME"
- grep -q "tmux attach-session -t sshd" ~/.bashrc || echo -e "\ n#tmuxセッション\ nif [[-z \"\$TMUX\" ]]; then\n tmux attach-session -t sshd || tmux new-session -s sshd\nfi" >> ~/.bashrc
- source ~/.bashrc
- tmux_run
- ;;
- 2)
- sed -i '/# 自动进入 tmux 会话/,+4d' ~/.bashrc
- tmux kill-window -t sshd
- ;;
- *)
- break
- ;;
- esac
- done
- ;;
+ # ユーザーの作成
+ useradd -m -s /bin/bash "$new_username" || return 1
- 22)
- read -e -p "1001 KJ001 Work1など、作成または入力したワークスペースの名前を入力してください。" SESSION_NAME
- tmux_run
- send_stats "カスタムワークスペース"
- ;;
+ echo "公開鍵をインポートする例:"
+ echo " - URL: ${gh_https_url}github.com/torvalds.keys"
+ echo "- 直接貼り付けます: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+ read -e -p "輸入してください${new_username}公開鍵:" sshkey_vl
+ case "$sshkey_vl" in
+ http://*|https://*)
+ send_stats "URLからSSH公開キーをインポート"
+ fetch_remote_ssh_keys "$sshkey_vl" "/home/$new_username"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa*)
+ send_stats "公開鍵を直接インポートする"
+ import_sshkey "$sshkey_vl" "/home/$new_username"
+ ;;
+ *)
+ echo "エラー: 不明なパラメータ '$sshkey_vl'"
+ return 1
+ ;;
+ esac
- 23)
- read -e -p "次のようなバックグラウンドで実行するコマンドを入力してください:curl -fssl https://get.docker.com SH:" tmuxd
- tmux_run_d
- send_stats "バックグラウンドワークスペースにコマンドを注入します"
- ;;
- 24)
- read -e -p "削除するワークスペースの名前を入力してください:" gongzuoqu_name
- tmux kill-window -t $gongzuoqu_name
- send_stats "ワークスペースを削除します"
- ;;
+ # 権限を修正する
+ chown -R "$new_username:$new_username" "/home/$new_username/.ssh"
- 0)
- kejilion
- ;;
- *)
- echo "無効な入力!"
- ;;
- esac
- break_end
+ install sudo
- done
+ # sudo パスワード不要
+ if [[ "$is_sudo" == "true" ]]; then
+ cat >"/etc/sudoers.d/$new_username" <> /etc/ssh/sshd_config
+ passwd -l "$new_username" &>/dev/null
+ restart_ssh
+ echo "ユーザー$new_username作成完了"
}
@@ -10825,57 +20132,66 @@ linux_work() {
+
+
+
linux_Settings() {
while true; do
clear
- # send_stats「システムツール」
+ # send_stats 「システムツール」
echo -e "システムツール"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}スクリプトの起動ショートカットキーを設定します${gl_kjlan}2. ${gl_bai}ログインパスワードを変更します"
- echo -e "${gl_kjlan}3. ${gl_bai}ルートパスワードログインモード${gl_kjlan}4. ${gl_bai}指定されたバージョンのPythonをインストールします"
- echo -e "${gl_kjlan}5. ${gl_bai}すべてのポートを開きます${gl_kjlan}6. ${gl_bai}SSH接続ポートを変更します"
- echo -e "${gl_kjlan}7. ${gl_bai}DNSアドレスを最適化します${gl_kjlan}8. ${gl_bai}ワンクリック再インストールシステム${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}9. ${gl_bai}ルートアカウントを無効にして新しいアカウントを作成します${gl_kjlan}10. ${gl_bai}優先順位IPv4/IPv6を切り替えます"
+ echo -e "${gl_kjlan}1. ${gl_bai}スクリプト起動のショートカットキーを設定する${gl_kjlan}2. ${gl_bai}ログインパスワードを変更する"
+ echo -e "${gl_kjlan}3. ${gl_bai}ユーザーパスワードログインモード${gl_kjlan}4. ${gl_bai}指定されたバージョンの Python をインストールします"
+ echo -e "${gl_kjlan}5. ${gl_bai}すべてのポートを開く${gl_kjlan}6. ${gl_bai}SSH接続ポートの変更"
+ echo -e "${gl_kjlan}7. ${gl_bai}DNSアドレスを最適化する${gl_kjlan}8. ${gl_bai}ワンクリックでシステムを再インストールします${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}9. ${gl_bai}ROOTアカウントを無効にして新しいアカウントを作成する${gl_kjlan}10. ${gl_bai}スイッチ優先度 ipv4/ipv6"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}ポートの職業ステータスを確認してください${gl_kjlan}12. ${gl_bai}仮想メモリサイズを変更します"
- echo -e "${gl_kjlan}13. ${gl_bai}ユーザー管理${gl_kjlan}14. ${gl_bai}ユーザー/パスワードジェネレーター"
- echo -e "${gl_kjlan}15. ${gl_bai}システムタイムゾーンの調整${gl_kjlan}16. ${gl_bai}BBR3加速度をセットアップします"
- echo -e "${gl_kjlan}17. ${gl_bai}ファイアウォール上級マネージャー${gl_kjlan}18. ${gl_bai}ホスト名を変更します"
- echo -e "${gl_kjlan}19. ${gl_bai}システムの更新ソースを切り替えます${gl_kjlan}20. ${gl_bai}タイミングタスク管理"
+ echo -e "${gl_kjlan}11. ${gl_bai}ポートの占有状況を確認する${gl_kjlan}12. ${gl_bai}仮想メモリのサイズを変更する"
+ echo -e "${gl_kjlan}13. ${gl_bai}ユーザー管理${gl_kjlan}14. ${gl_bai}ユーザー/パスワード生成器"
+ echo -e "${gl_kjlan}15. ${gl_bai}システムのタイムゾーン調整${gl_kjlan}16. ${gl_bai}BBR3アクセラレーションの設定"
+ echo -e "${gl_kjlan}17. ${gl_bai}ファイアウォール アドバンスト マネージャー${gl_kjlan}18. ${gl_bai}ホスト名の変更"
+ echo -e "${gl_kjlan}19. ${gl_bai}システムアップデート元の切り替え${gl_kjlan}20. ${gl_bai}スケジュールされたタスクの管理"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}ネイティブホストの解析${gl_kjlan}22. ${gl_bai}SSH防衛プログラム"
- echo -e "${gl_kjlan}23. ${gl_bai}電流制限の自動シャットダウン${gl_kjlan}24. ${gl_bai}ルート秘密キーログインモード"
- echo -e "${gl_kjlan}25. ${gl_bai}TGボットシステムの監視と早期警告${gl_kjlan}26. ${gl_bai}opensshの高リスクの脆弱性(xiuyuan)を修正"
- echo -e "${gl_kjlan}27. ${gl_bai}Red Hat Linuxカーネルのアップグレード${gl_kjlan}28. ${gl_bai}Linuxシステムにおけるカーネルパラメーターの最適化${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}21. ${gl_bai}ネイティブホスト解像度${gl_kjlan}22. ${gl_bai}SSH防御プログラム"
+ echo -e "${gl_kjlan}23. ${gl_bai}電流制限自動シャットダウン${gl_kjlan}24. ${gl_bai}ユーザーキーログインモード"
+ echo -e "${gl_kjlan}25. ${gl_bai}TG-bot システムの監視と早期警告${gl_kjlan}26. ${gl_bai}OpenSSH の高リスク脆弱性を修正"
+ echo -e "${gl_kjlan}27. ${gl_bai}Red Hat Linux カーネルのアップグレード${gl_kjlan}28. ${gl_bai}Linuxシステムのカーネルパラメータの最適化${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}29. ${gl_bai}ウイルススキャンツール${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}ファイルマネージャー"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}システム言語を切り替えます${gl_kjlan}32. ${gl_bai}コマンドラインの美化ツール${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}システムリサイクルビンをセットアップします${gl_kjlan}34. ${gl_bai}システムのバックアップと回復"
+ echo -e "${gl_kjlan}31. ${gl_bai}システム言語を切り替える${gl_kjlan}32. ${gl_bai}コマンドライン美化ツール${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}システムのごみ箱をセットアップする${gl_kjlan}34. ${gl_bai}システムのバックアップとリカバリ"
echo -e "${gl_kjlan}35. ${gl_bai}SSHリモート接続ツール${gl_kjlan}36. ${gl_bai}ハードディスクパーティション管理ツール"
- echo -e "${gl_kjlan}37. ${gl_bai}コマンドラインの履歴${gl_kjlan}38. ${gl_bai}RSYNCリモート同期ツール"
+ echo -e "${gl_kjlan}37. ${gl_bai}コマンドラインの履歴${gl_kjlan}38. ${gl_bai}rsync リモート同期ツール"
+ echo -e "${gl_kjlan}39. ${gl_bai}コマンドのお気に入り${gl_huang}★${gl_bai} ${gl_kjlan}40. ${gl_bai}ネットワークカード管理ツール"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}41. ${gl_bai}システムログ管理ツール${gl_huang}★${gl_bai} ${gl_kjlan}42. ${gl_bai}システム変数管理ツール"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}メッセージボード${gl_kjlan}66. ${gl_bai}ワンストップシステムの最適化${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}61. ${gl_bai}掲示板${gl_kjlan}66. ${gl_bai}ワンストップのシステムチューニング${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}99. ${gl_bai}サーバーを再起動します${gl_kjlan}100. ${gl_bai}プライバシーとセキュリティ"
- echo -e "${gl_kjlan}101. ${gl_bai}Kコマンドの高度な使用${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}テックライオンスクリプトをアンインストールします"
+ echo -e "${gl_kjlan}101. ${gl_bai}k コマンドの高度な使用法${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}Tech Lion スクリプトをアンインストールする"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻ります"
+ echo -e "${gl_kjlan}0. ${gl_bai}メインメニューに戻る"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
while true; do
clear
- read -e -p "ショートカットキーを入力してください(出口に0を入力してください):" kuaijiejian
+ read -e -p "ショートカット キーを入力してください (終了するには 0 を入力してください):" kuaijiejian
if [ "$kuaijiejian" == "0" ]; then
break_end
linux_Settings
fi
find /usr/local/bin/ -type l -exec bash -c 'test "$(readlink -f {})" = "/usr/local/bin/k" && rm -f {}' \;
- ln -s /usr/local/bin/k /usr/local/bin/$kuaijiejian
- echo "ショートカットキーが設定されています"
- send_stats "スクリプトのショートカットキーが設定されています"
+ if [ "$kuaijiejian" != "k" ]; then
+ ln -sf /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ fi
+ ln -sf /usr/local/bin/k /usr/bin/$kuaijiejian > /dev/null 2>&1
+ echo "ショートカットキーが設定されている"
+ send_stats "スクリプトのショートカットキーが設定されました"
break_end
linux_Settings
done
@@ -10883,34 +20199,33 @@ linux_Settings() {
2)
clear
- send_stats "ログインパスワードを設定します"
- echo "ログインパスワードを設定します"
+ send_stats "ログインパスワードを設定する"
+ echo "ログインパスワードを設定する"
passwd
;;
3)
- root_use
- send_stats "ルートパスワードモード"
+ clear
add_sshpasswd
;;
4)
root_use
- send_stats "Pyバージョン管理"
- echo "Pythonバージョン管理"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1pm42157ck?t=0.1"
+ send_stats "pyのバージョン管理"
+ echo "Pythonのバージョン管理"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1Pm42157cK?t=0.1"
echo "---------------------------------------"
- echo "この機能は、Pythonが正式にサポートするバージョンをシームレスにインストールします!"
+ echo "この機能を使用すると、Python で公式にサポートされているバージョンをシームレスにインストールできます。"
local VERSION=$(python3 -V 2>&1 | awk '{print $2}')
- echo -e "現在のPythonバージョン番号:${gl_huang}$VERSION${gl_bai}"
+ echo -e "現在のPythonのバージョン番号:${gl_huang}$VERSION${gl_bai}"
echo "------------"
- echo "推奨バージョン:3.12 3.11 3.10 3.9 3.8 2.7"
- echo "クエリの詳細:https://www.python.org/downloads/"
+ echo "推奨バージョン: 3.12 3.11 3.10 3.9 3.8 2.7"
+ echo "他のバージョンを確認する: https://www.python.org/downloads/"
echo "------------"
- read -e -p "インストールするPythonバージョン番号を入力します(Enter 0からExit):" py_new_v
+ read -e -p "インストールする Python のバージョン番号を入力します (終了するには 0 を入力します)。" py_new_v
if [[ "$py_new_v" == "0" ]]; then
- send_stats "スクリプトPy管理"
+ send_stats "スクリプト PY 管理"
break_end
linux_Settings
fi
@@ -10943,7 +20258,7 @@ linux_Settings() {
apk update && apk add git
apk add --no-cache bash gcc musl-dev libffi-dev openssl-dev bzip2-dev zlib-dev readline-dev sqlite-dev libc6-compat linux-headers make xz-dev build-base ncurses-dev
else
- echo "不明なパッケージマネージャー!"
+ echo "不明なパッケージマネージャーです!"
return
fi
@@ -10972,14 +20287,14 @@ EOF
rm -rf $(pyenv root)/cache/*
local VERSION=$(python -V 2>&1 | awk '{print $2}')
- echo -e "現在のPythonバージョン番号:${gl_huang}$VERSION${gl_bai}"
- send_stats "スイッチスクリプトPyバージョン"
+ echo -e "現在のPythonのバージョン番号:${gl_huang}$VERSION${gl_bai}"
+ send_stats "スクリプトPYバージョン切り替え"
;;
5)
root_use
- send_stats "オープンポート"
+ send_stats "ポートを開く"
iptables_open
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
echo "すべてのポートが開いています"
@@ -10987,40 +20302,40 @@ EOF
;;
6)
root_use
- send_stats "SSHポートを変更します"
+ send_stats "SSHポートを変更する"
while true; do
clear
- sed -i 's/#Port/Port/' /etc/ssh/sshd_config
+ sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- # 現在のSSHポート番号をお読みください
+ # 現在の SSH ポート番号を読み取ります
local current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
- # 現在のSSHポート番号を印刷します
- echo -e "現在のSSHポート番号は次のとおりです。${gl_huang}$current_port ${gl_bai}"
+ # 現在の SSH ポート番号を出力する
+ echo -e "現在の SSH ポート番号は次のとおりです。${gl_huang}$current_port ${gl_bai}"
echo "------------------------"
- echo "ポート番号が1〜65535の範囲の数字(0を入力して終了)"
+ echo "ポート番号の範囲は 1 ~ 65535 です (終了するには 0 を入力します)。"
- # ユーザーに新しいSSHポート番号を入力するように促します
- read -e -p "新しいSSHポート番号を入力してください:" new_port
+ # 新しい SSH ポート番号の入力をユーザーに求める
+ read -e -p "新しい SSH ポート番号を入力してください:" new_port
- # ポート番号が有効な範囲内にあるかどうかを判断します
+ # ポート番号が有効な範囲内であるかどうかを確認します。
if [[ $new_port =~ ^[0-9]+$ ]]; then # 检查输入是否为数字
if [[ $new_port -ge 1 && $new_port -le 65535 ]]; then
send_stats "SSHポートが変更されました"
- new_ssh_port
+ new_ssh_port $new_port
elif [[ $new_port -eq 0 ]]; then
- send_stats "SSHポート変更を終了します"
+ send_stats "SSHポート変更の終了"
break
else
- echo "ポート番号は無効です。1〜65535の数字を入力してください。"
- send_stats "無効なSSHポート入力"
+ echo "ポート番号が無効です。 1 ~ 65535 の数字を入力してください。"
+ send_stats "無効な SSH ポートが入力されました"
break_end
fi
else
- echo "入力が無効です。番号を入力してください。"
- send_stats "無効なSSHポート入力"
+ echo "入力が無効です。数値を入力してください。"
+ send_stats "無効な SSH ポートが入力されました"
break_end
fi
done
@@ -11039,63 +20354,61 @@ EOF
;;
9)
root_use
- send_stats "新しいユーザーはルートを無効にします"
- read -e -p "新しいユーザー名を入力してください(出口に0を入力してください):" new_username
+ send_stats "新規ユーザーの root を無効にする"
+ read -e -p "新しいユーザー名を入力してください (終了するには 0 を入力してください):" new_username
if [ "$new_username" == "0" ]; then
break_end
linux_Settings
fi
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ create_user_with_sshkey $new_username true
- passwd -l root
+ ssh-keygen -l -f /home/$new_username/.ssh/authorized_keys &>/dev/null && {
+ passwd -l root &>/dev/null
+ sed -i 's/^[[:space:]]*#\?[[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
+ }
- echo "操作が完了しました。"
;;
10)
root_use
- send_stats "V4/V6の優先度を設定します"
+ send_stats "v4/v6 の優先順位を設定する"
while true; do
clear
- echo "V4/V6の優先度を設定します"
+ echo "v4/v6 の優先順位を設定する"
echo "------------------------"
- local ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
- if [ "$ipv6_disabled" -eq 1 ]; then
- echo -e "現在のネットワーク優先設定:${gl_huang}IPv4${gl_bai}優先度"
+
+ if grep -Eq '^\s*precedence\s+::ffff:0:0/96\s+100\s*$' /etc/gai.conf 2>/dev/null; then
+ echo -e "現在のネットワーク優先設定:${gl_huang}IPv4${gl_bai}優先度"
else
- echo -e "現在のネットワーク優先設定:${gl_huang}IPv6${gl_bai}優先度"
+ echo -e "現在のネットワーク優先設定:${gl_huang}IPv6${gl_bai}優先度"
fi
+
echo ""
echo "------------------------"
- echo "1。IPv4優先度2。IPv6優先度3。IPv6修理ツール"
+ echo "1. IPv4 が先 2. IPv6 が先 3. IPv6 修復ツール"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "優先ネットワークを選択します。" choice
+ read -e -p "優先ネットワークを選択してください:" choice
case $choice in
1)
- sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
- echo "IPv4の優先度に切り替えました"
- send_stats "IPv4の優先度に切り替えました"
+ prefer_ipv4
;;
2)
- sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
- echo "IPv6の優先度に切り替えました"
- send_stats "IPv6の優先度に切り替えました"
+ rm -f /etc/gai.conf
+ echo "IPv6優先に切り替えました"
+ send_stats "IPv6優先に切り替えました"
;;
3)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "この関数は、彼のおかげで、マスターJHBによって提供されます!"
- send_stats "IPv6修正"
+ echo "この機能は jhb によって提供されています。ありがとう!"
+ send_stats "IPv6修復"
;;
*)
@@ -11113,43 +20426,43 @@ EOF
12)
root_use
- send_stats "仮想メモリを設定します"
+ send_stats "仮想メモリを設定する"
while true; do
clear
- echo "仮想メモリを設定します"
+ echo "仮想メモリを設定する"
local swap_used=$(free -m | awk 'NR==3{print $3}')
local swap_total=$(free -m | awk 'NR==3{print $2}')
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- echo -e "現在の仮想メモリ:${gl_huang}$swap_info${gl_bai}"
+ echo -e "現在の仮想メモリ:${gl_huang}$swap_info${gl_bai}"
echo "------------------------"
- echo "1。1024Mの割り当て2。2048m3を割り当てます。4096m4。カスタムサイズを割り当てます"
+ echo "1. 1024M の割り当て 2. 2048M の割り当て 3. 4096M の割り当て 4. カスタム サイズ"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" choice
+ read -e -p "選択肢を入力してください:" choice
case "$choice" in
1)
- send_stats "1G仮想メモリが設定されています"
+ send_stats "1Gの仮想メモリが設定されています"
add_swap 1024
;;
2)
- send_stats "2G仮想メモリが設定されています"
+ send_stats "2Gの仮想メモリが設定されています"
add_swap 2048
;;
3)
- send_stats "4G仮想メモリが設定されています"
+ send_stats "4G仮想メモリが設定されました"
add_swap 4096
;;
4)
- read -e -p "仮想メモリサイズ(ユニットM)を入力してください:" new_swap
+ read -e -p "仮想メモリ サイズ (単位 M) を入力してください:" new_swap
add_swap "$new_swap"
- send_stats "カスタム仮想メモリが設定されています"
+ send_stats "カスタム仮想メモリセット"
;;
*)
@@ -11165,10 +20478,15 @@ EOF
send_stats "ユーザー管理"
echo "ユーザーリスト"
echo "----------------------------------------------------------------------------"
- printf "%-24s %-34s %-20s %-10s\n" "用户名" "用户权限" "用户组" "sudo权限"
+ printf "%-24s %-34s %-20s %-10s\n" "ユーザー名" "ユーザー権限" "ユーザーグループ" "sudo 権限"
while IFS=: read -r username _ userid groupid _ _ homedir shell; do
local groups=$(groups "$username" | cut -d : -f 2)
- local sudo_status=$(sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL : ALL)' && echo "Yes" || echo "No")
+ local sudo_status
+ if sudo -n -lU "$username" 2>/dev/null | grep -q "(ALL) \(NOPASSWD: \)\?ALL"; then
+ sudo_status="Yes"
+ else
+ sudo_status="No"
+ fi
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$groups" "$sudo_status"
done < /etc/passwd
@@ -11176,56 +20494,48 @@ EOF
echo ""
echo "アカウント操作"
echo "------------------------"
- echo "1.通常のアカウントを作成する2。プレミアムアカウントを作成します"
+ echo "1. 通常ユーザーを作成します。 2. 上級ユーザーを作成します。"
echo "------------------------"
- echo "3.最高の権限を与える4。最高の権限をキャンセルします"
+ echo "3. 最高の権限を付与する 4. 最高の権限を削除する"
echo "------------------------"
- echo "5.アカウントを削除します"
+ echo "5. アカウントを削除する"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- # ユーザーに新しいユーザー名を入力するように求めます
- read -e -p "新しいユーザー名を入力してください:" new_username
-
- # 新しいユーザーを作成し、パスワードを設定します
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
+ # ユーザーに新しいユーザー名の入力を求める
+ read -e -p "新しいユーザー名を入力してください:" new_username
+ create_user_with_sshkey $new_username false
- echo "操作が完了しました。"
;;
2)
- # ユーザーに新しいユーザー名を入力するように求めます
- read -e -p "新しいユーザー名を入力してください:" new_username
-
- # 新しいユーザーを作成し、パスワードを設定します
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- # 新規ユーザーのsudo許可を付与します
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- echo "操作が完了しました。"
+ # ユーザーに新しいユーザー名の入力を求める
+ read -e -p "新しいユーザー名を入力してください:" new_username
+ create_user_with_sshkey $new_username true
;;
3)
- read -e -p "ユーザー名を入力してください:" username
- # 新規ユーザーのsudo許可を付与します
- echo "$username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ read -e -p "ユーザー名を入力してください:" username
+ install sudo
+ cat >"/etc/sudoers.d/$username" </dev/null | xargs rm -f
+ fi
+ sed -i "/^$username\s*ALL=(ALL)/d" /etc/sudoers
;;
5)
- read -e -p "削除するにはユーザー名を入力してください:" username
- # ユーザーとそのホームディレクトリを削除します
+ read -e -p "削除するユーザー名を入力してください:" username
userdel -r "$username"
;;
@@ -11233,26 +20543,27 @@ EOF
break # 跳出循环,退出菜单
;;
esac
+
done
;;
14)
clear
- send_stats "ユーザー情報ジェネレーター"
- echo "ランダムユーザー名"
+ send_stats "ユーザー情報ジェネレータ"
+ echo "ランダムなユーザー名"
echo "------------------------"
for i in {1..5}; do
username="user$(< /dev/urandom tr -dc _a-z0-9 | head -c6)"
- echo "ランダムユーザー名$i: $username"
+ echo "ランダムなユーザー名$i: $username"
done
echo ""
- echo "ランダム名"
+ echo "ランダムな名前"
echo "------------------------"
local first_names=("John" "Jane" "Michael" "Emily" "David" "Sophia" "William" "Olivia" "James" "Emma" "Ava" "Liam" "Mia" "Noah" "Isabella")
local last_names=("Smith" "Johnson" "Brown" "Davis" "Wilson" "Miller" "Jones" "Garcia" "Martinez" "Williams" "Lee" "Gonzalez" "Rodriguez" "Hernandez")
- # 5つのランダムユーザー名を生成します
+ # 5 つのランダムなユーザー名を生成する
for i in {1..5}; do
local first_name_index=$((RANDOM % ${#first_names[@]}))
local last_name_index=$((RANDOM % ${#last_names[@]}))
@@ -11261,15 +20572,15 @@ EOF
done
echo ""
- echo "ランダムuuid"
+ echo "ランダムな UUID"
echo "------------------------"
for i in {1..5}; do
uuid=$(cat /proc/sys/kernel/random/uuid)
- echo "ランダムuuid$i: $uuid"
+ echo "ランダムな UUID$i: $uuid"
done
echo ""
- echo "16ビットランダムパスワード"
+ echo "16桁のランダムなパスワード"
echo "------------------------"
for i in {1..5}; do
local password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
@@ -11277,7 +20588,7 @@ EOF
done
echo ""
- echo "32ビットランダムパスワード"
+ echo "32ビットのランダムなパスワード"
echo "------------------------"
for i in {1..5}; do
local password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32)
@@ -11289,46 +20600,46 @@ EOF
15)
root_use
- send_stats "タイムゾーンを変更します"
+ send_stats "タイムゾーンを変更する"
while true; do
clear
- echo "システム時間情報"
+ echo "システム時刻情報"
- # 現在のシステムタイムゾーンを取得します
+ # 現在のシステムのタイムゾーンを取得する
local timezone=$(current_timezone)
- # 現在のシステム時間を取得します
+ # 現在のシステム時刻を取得する
local current_time=$(date +"%Y-%m-%d %H:%M:%S")
- # タイムゾーンと時間を表示します
- echo "現在のシステムタイムゾーン:$timezone"
- echo "現在のシステム時間:$current_time"
+ # タイムゾーンと時間を表示する
+ echo "現在のシステムのタイムゾーン:$timezone"
+ echo "現在のシステム時間:$current_time"
echo ""
- echo "タイムゾーンの切り替え"
+ echo "タイムゾーンスイッチ"
echo "------------------------"
echo "アジア"
- echo "1。中国の上海時間2。中国の香港時間"
- echo "3。日本の東京時間4。韓国のソウル時間"
- echo "5。シンガポール時間6。インドのコルカタ時間"
- echo "7。アラブ首長国連邦のドバイ時間8。オーストラリアのシドニー時間"
- echo "9。タイのバンコクでの時間"
+ echo "1. 中国上海時間 2. 中国香港時間"
+ echo "3. 東京、日本時間 4. ソウル、韓国時間"
+ echo "5. シンガポール時間 6. インド、コルカタ時間"
+ echo "7. アラブ首長国連邦、ドバイ時間 8. オーストラリア、シドニー時間"
+ echo "9. タイ・バンコク時間"
echo "------------------------"
echo "ヨーロッパ"
- echo "11。英国のロンドン時間12。パリの時間フランスの時間"
- echo "13。ベルリン時代、ドイツ14。モスクワ・タイム、ロシア"
- echo "15。オランダのユトレヒト時間16。スペインでのマドリード時間"
+ echo "11. ロンドン、イギリス時間 12. パリ、フランス時間"
+ echo "13. ベルリン、ドイツ時間 14. モスクワ、ロシア時間"
+ echo "15. ユトラハト時間、オランダ 16. マドリッド時間、スペイン"
echo "------------------------"
echo "アメリカ"
- echo "21。WesternTime22。東部時間"
- echo "23。カナダ時間24。メキシコの時間"
- echo "25。ブラジル時間26。アルゼンチン時間"
+ echo "21. 米国西部時間 22. 米国東部時間"
+ echo "23. カナダ時間 24. メキシコ時間"
+ echo "25. ブラジル時間 26. アルゼンチン時間"
echo "------------------------"
- echo "31。UTCグローバル標準時間"
+ echo "31. UTC 世界標準時"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
@@ -11371,21 +20682,21 @@ EOF
18)
root_use
- send_stats "ホスト名を変更します"
+ send_stats "ホスト名の変更"
while true; do
clear
local current_hostname=$(uname -n)
- echo -e "現在のホスト名:${gl_huang}$current_hostname${gl_bai}"
+ echo -e "現在のホスト名:${gl_huang}$current_hostname${gl_bai}"
echo "------------------------"
- read -e -p "新しいホスト名を入力してください(出口に0を入力してください):" new_hostname
+ read -e -p "新しいホスト名を入力してください (終了するには 0 を入力してください):" new_hostname
if [ -n "$new_hostname" ] && [ "$new_hostname" != "0" ]; then
if [ -f /etc/alpine-release ]; then
# Alpine
echo "$new_hostname" > /etc/hostname
hostname "$new_hostname"
else
- # Debian、Ubuntu、Centosなどの他のシステム。
+ # Debian、Ubuntu、CentOS などのその他のシステム
hostnamectl set-hostname "$new_hostname"
sed -i "s/$current_hostname/$new_hostname/g" /etc/hostname
systemctl restart systemd-hostnamed
@@ -11403,11 +20714,11 @@ EOF
echo "::1 $new_hostname localhost localhost.localdomain ipv6-localhost ipv6-loopback" >> /etc/hosts
fi
- echo "ホスト名は次のように変更されています。$new_hostname"
+ echo "ホスト名は次のように変更されました。$new_hostname"
send_stats "ホスト名が変更されました"
sleep 1
else
- echo "終了すると、ホスト名は変更されていません。"
+ echo "ホスト名を変更せずに終了しました。"
break
fi
done
@@ -11415,20 +20726,20 @@ EOF
19)
root_use
- send_stats "システムの更新ソースを変更します"
+ send_stats "システムアップデートソースを変更する"
clear
- echo "更新ソース領域を選択します"
- echo "LinuxMirrorsに接続して、システム更新ソースを切り替えます"
+ echo "更新元リージョンの選択"
+ echo "LinuxMirror にアクセスしてシステム アップデート ソースを切り替える"
echo "------------------------"
- echo "1。中国本土[デフォルト]2。中国本土[教育ネットワーク]3。海外地域"
+ echo "1. 中国本土 [デフォルト] 2. 中国本土 [教育ネットワーク] 3. 海外地域 4. 更新ソースのインテリジェントな切り替え"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "あなたの選択を入力してください:" choice
+ read -e -p "選択内容を入力してください:" choice
case $choice in
1)
- send_stats "中国本土のデフォルトソース"
+ send_stats "中国本土のデフォルトのソース"
bash <(curl -sSL https://linuxmirrors.cn/main.sh)
;;
2)
@@ -11436,9 +20747,14 @@ EOF
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --edu
;;
3)
- send_stats "海外起源"
+ send_stats "海外情報源"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --abroad
;;
+ 4)
+ send_stats "アップデートソースのインテリジェントな切り替え"
+ switch_mirror false false
+ ;;
+
*)
echo "キャンセル"
;;
@@ -11448,62 +20764,62 @@ EOF
;;
20)
- send_stats "タイミングタスク管理"
+ send_stats "スケジュールされたタスクの管理"
while true; do
clear
check_crontab_installed
clear
- echo "タイミングされたタスクリスト"
+ echo "スケジュールされたタスクのリスト"
crontab -l
echo ""
- echo "動作します"
+ echo "操作する"
echo "------------------------"
- echo "1.タイミングタスクの追加2。タイミングタスクを削除する3。タイミングタスクの編集"
+ echo "1. スケジュールされたタスクを追加します。 2. スケジュールされたタスクを削除します。 3. スケジュールされたタスクを編集します。"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
- read -e -p "新しいタスクについては、実行コマンドを入力してください。" newquest
+ read -e -p "新しいタスクの実行コマンドを入力してください:" newquest
echo "------------------------"
- echo "1。毎月のタスク2。毎週のタスク"
- echo "3。毎日のタスク4。時間ごとのタスク"
+ echo "1. 月次タスク 2. 週次タスク"
+ echo "3. 毎日のタスク 4. 時間ごとのタスク"
echo "------------------------"
- read -e -p "選択を入力してください:" dingshi
+ read -e -p "選択肢を入力してください:" dingshi
case $dingshi in
1)
- read -e -p "毎月何日を選択してタスクを実行しますか? (1-30):" day
+ read -e -p "タスクを実行する日は月の何日ですか? (1-30):" day
(crontab -l ; echo "0 0 $day * * $newquest") | crontab - > /dev/null 2>&1
;;
2)
- read -e -p "タスクを実行するためにどの週を選択しますか? (0-6、0は日曜日を表します):" weekday
+ read -e -p "タスクを実行する曜日を選択しますか? (0 ~ 6、0 は日曜日を表します):" weekday
(crontab -l ; echo "0 0 * * $weekday $newquest") | crontab - > /dev/null 2>&1
;;
3)
- read -e -p "毎日タスクを実行する時間を選択しますか? (時間、0-23):" hour
+ read -e -p "毎日、そのタスクを実行する時刻を選択しますか? (時、0-23):" hour
(crontab -l ; echo "0 $hour * * * $newquest") | crontab - > /dev/null 2>&1
;;
4)
- read -e -p "タスクを実行するために時間の何時間を入力しますか? (分、0-60):" minute
+ read -e -p "タスクを実行する時間を入力してください。 (分、0 ~ 60):" minute
(crontab -l ; echo "$minute * * * * $newquest") | crontab - > /dev/null 2>&1
;;
*)
break # 跳出
;;
esac
- send_stats "時限タスクを追加します"
+ send_stats "スケジュールされたタスクを追加する"
;;
2)
- read -e -p "削除する必要があるキーワードを入力してください。" kquest
+ read -e -p "削除するタスクのキーワードを入力してください:" kquest
crontab -l | grep -v "$kquest" | crontab -
- send_stats "タイミングタスクを削除します"
+ send_stats "スケジュールされたタスクを削除する"
;;
3)
crontab -e
- send_stats "タイミングタスクを編集します"
+ send_stats "スケジュールされたタスクを編集する"
;;
*)
break # 跳出循环,退出菜单
@@ -11515,32 +20831,32 @@ EOF
21)
root_use
- send_stats "ローカルホストの解析"
+ send_stats "ローカルホストの解決"
while true; do
clear
- echo "ネイティブホストのペルシングリスト"
- echo "ここに解析の一致を追加すると、動的な解析はもはや使用されなくなります"
+ echo "ネイティブホスト解決リスト"
+ echo "ここに解析一致を追加すると、動的解析は使用されなくなります"
cat /etc/hosts
echo ""
- echo "動作します"
+ echo "操作する"
echo "------------------------"
- echo "1.新しい解析2を追加します。解析アドレスを削除します"
+ echo "1. 新しい解決策を追加 2. 解決策アドレスを削除"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" host_dns
+ read -e -p "選択肢を入力してください:" host_dns
case $host_dns in
1)
- read -e -p "新しい解析レコード形式を入力してください:110.25.5.33 Kejilion.pro:" addhost
+ read -e -p "新しい解析レコード形式を入力してください: 110.25.5.33 kejilion.pro:" addhost
echo "$addhost" >> /etc/hosts
- send_stats "ローカルホストの解析が追加されました"
+ send_stats "ローカルホスト解像度が追加されました"
;;
2)
- read -e -p "削除する必要があるコンテンツの解析のキーワードを入力してください。" delhost
+ read -e -p "削除する必要がある解析済みコンテンツのキーワードを入力してください:" delhost
sed -i "/$delhost/d" /etc/hosts
- send_stats "ローカルホストの解析と削除"
+ send_stats "ローカルホストの解決と削除"
;;
*)
break # 跳出循环,退出菜单
@@ -11550,63 +20866,7 @@ EOF
;;
22)
- root_use
- send_stats "SSH防御"
- while true; do
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
- clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "SSH防衛プログラム$check_docker"
- echo "Fail2banは、ブルートフォースを防ぐためのSSHツールです"
- echo "公式ウェブサイトの紹介:${gh_proxy}github.com/fail2ban/fail2ban"
- echo "------------------------"
- echo "1.防衛プログラムをインストールします"
- echo "------------------------"
- echo "2。SSH傍受記録を表示します"
- echo "3。リアルタイムログ監視"
- echo "------------------------"
- echo "9.防衛プログラムをアンインストールします"
- echo "------------------------"
- echo "0。前のメニューに戻ります"
- echo "------------------------"
- read -e -p "選択を入力してください:" sub_choice
- case $sub_choice in
- 1)
- install_docker
- f2b_install_sshd
-
- cd ~
- f2b_status
- break_end
- ;;
- 2)
- echo "------------------------"
- f2b_sshd
- echo "------------------------"
- break_end
- ;;
- 3)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
- break
- ;;
- 9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
- echo "Fail2Ban防衛プログラムがアンインストールされています"
- ;;
- *)
- break
- ;;
- esac
- fi
- done
+ fail2ban_panel
;;
@@ -11616,43 +20876,43 @@ EOF
while true; do
clear
echo "電流制限シャットダウン機能"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1mc411j7qd?t=0.1"
+ echo "ビデオ紹介: https://www.bilibili.com/video/BV1mC411j7Qd?t=0.1"
echo "------------------------------------------------"
- echo "現在のトラフィックの使用、サーバートラフィックの計算の再起動がクリアされます!"
+ echo "現在のトラフィック使用量は、サーバーが再起動されるとクリアされます。"
output_status
- echo -e "${gl_kjlan}合計受信:${gl_bai}$rx"
- echo -e "${gl_kjlan}合計送信:${gl_bai}$tx"
+ echo -e "${gl_kjlan}受け取った合計:${gl_bai}$rx"
+ echo -e "${gl_kjlan}送信合計:${gl_bai}$tx"
- # limiting_shut_down.shファイルが存在するかどうかを確認してください
+ # Limiting_Shut_down.sh ファイルが存在するかどうかを確認します
if [ -f ~/Limiting_Shut_down.sh ]; then
- # threshold_gbの値を取得します
+ # しきい値_gbの値を取得する
local rx_threshold_gb=$(grep -oP 'rx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
local tx_threshold_gb=$(grep -oP 'tx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
- echo -e "${gl_lv}現在のセットエントリステーション電流制限しきい値は次のとおりです。${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
- echo -e "${gl_lv}現在のアウトバウンド電流制限しきい値は次のとおりです。${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
+ echo -e "${gl_lv}現在設定されている受信トラフィック制限のしきい値は次のとおりです。${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
+ echo -e "${gl_lv}現在設定されている送信トラフィック制限のしきい値は次のとおりです。${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
else
- echo -e "${gl_hui}現在の制限シャットダウン機能は有効になりません${gl_bai}"
+ echo -e "${gl_hui}電流制限シャットダウン機能は現在有効になっていません。${gl_bai}"
fi
echo
echo "------------------------------------------------"
- echo "システムは、実際のトラフィックが毎分でしきい値に達するかどうかを検出し、サーバーが到着した後にサーバーが自動的にシャットダウンされます!"
+ echo "システムは実際のトラフィックがしきい値に達したかどうかを毎分検出し、しきい値に達するとサーバーを自動的にシャットダウンします。"
echo "------------------------"
- echo "1。現在の制限シャットダウン関数をオンにします2。現在の制限シャットダウン機能を無効にします"
+ echo "1. 電流制限シャットダウン機能を有効にする 2. 電流制限シャットダウン機能を無効にする"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" Limiting
+ read -e -p "選択肢を入力してください:" Limiting
case "$Limiting" in
1)
- # 新しい仮想メモリサイズを入力します
- echo "実際のサーバーに100gのトラフィックがある場合、しきい値を95gに設定し、事前に電源をシャットダウンして、トラフィックエラーやオーバーフローを回避できます。"
- read -e -p "着信トラフィックのしきい値を入力してください(ユニットはG、デフォルトは100gです):" rx_threshold_gb
+ # 新しい仮想メモリ サイズを入力してください
+ echo "実際のサーバーのトラフィックが 100G しかない場合は、しきい値を 95G に設定し、事前にシャットダウンして、トラフィック エラーやオーバーフローを回避できます。"
+ read -e -p "受信トラフィックのしきい値を入力してください (単位は G、デフォルトは 100G):" rx_threshold_gb
rx_threshold_gb=${rx_threshold_gb:-100}
- read -e -p "アウトバウンドトラフィックのしきい値を入力してください(ユニットはG、デフォルトは100gです):" tx_threshold_gb
+ read -e -p "送信トラフィックのしきい値を入力してください (単位は G、デフォルトは 100G):" tx_threshold_gb
tx_threshold_gb=${tx_threshold_gb:-100}
- read -e -p "トラフィックリセット日を入力してください(デフォルトのリセットは、毎月1日目にリセットされます):" cz_day
+ read -e -p "トラフィックのリセット日を入力してください (デフォルトは毎月 1 日にリセットされます)。" cz_day
cz_day=${cz_day:-1}
cd ~
@@ -11665,15 +20925,15 @@ EOF
(crontab -l ; echo "* * * * * ~/Limiting_Shut_down.sh") | crontab - > /dev/null 2>&1
crontab -l | grep -v 'reboot' | crontab -
(crontab -l ; echo "0 1 $cz_day * * reboot") | crontab - > /dev/null 2>&1
- echo "現在の制限シャットダウンが設定されています"
- send_stats "現在の制限シャットダウンが設定されています"
+ echo "電流制限シャットダウンが設定されています"
+ send_stats "電流制限シャットダウンが設定されています"
;;
2)
check_crontab_installed
crontab -l | grep -v '~/Limiting_Shut_down.sh' | crontab -
crontab -l | grep -v 'reboot' | crontab -
rm ~/Limiting_Shut_down.sh
- echo "現在の制限シャットダウン関数はオフになっています"
+ echo "電流制限シャットダウン機能がオフになる"
;;
*)
break
@@ -11684,69 +20944,23 @@ EOF
24)
-
- root_use
- send_stats "秘密キーログイン"
- while true; do
- clear
- echo "ルート秘密キーログインモード"
- echo "ビデオの紹介:https://www.bilibili.com/video/bv1q4421x78n?t=209.4"
- echo "------------------------------------------------"
- echo "キーペアが生成され、SSHログインのより安全な方法"
- echo "------------------------"
- echo "1.新しいキーを生成する2。既存のキーをインポートする3。ネイティブキーを表示します"
- echo "------------------------"
- echo "0。前のメニューに戻ります"
- echo "------------------------"
- read -e -p "選択を入力してください:" host_dns
-
- case $host_dns in
- 1)
- send_stats "新しいキーを生成します"
- add_sshkey
- break_end
-
- ;;
- 2)
- send_stats "既存の公開キーをインポートします"
- import_sshkey
- break_end
-
- ;;
- 3)
- send_stats "地元の秘密の鍵を表示します"
- echo "------------------------"
- echo "公開鍵情報"
- cat ~/.ssh/authorized_keys
- echo "------------------------"
- echo "秘密のキー情報"
- cat ~/.ssh/sshkey
- echo "------------------------"
- break_end
-
- ;;
- *)
- break # 跳出循环,退出菜单
- ;;
- esac
- done
-
+ sshkey_panel
;;
25)
root_use
- send_stats "電報警告"
- echo "TG-BOTモニタリングと早期警告機能"
- echo "ビデオの紹介:https://youtu.be/vll-eb3z_ty"
+ send_stats "電報警報"
+ echo "TG-bot監視・早期警告機能"
+ echo "動画紹介:https://youtu.be/vLL-eb3Z_TY"
echo "------------------------------------------------"
- echo "ネイティブCPU、メモリ、ハードディスク、トラフィック、およびSSHログインのリアルタイム監視と早期警告を実現するために、TG Robot APIとユーザーIDを構成する必要があります。"
- echo "しきい値に達した後、ユーザーはユーザーに送信されます"
- echo -e "${gl_hui}- トラフィックに関しては、サーバーの再起動が再計算されます -${gl_bai}"
- read -e -p "必ず続行しますか? (y/n):" choice
+ echo "ローカル CPU、メモリ、ハードディスク、トラフィック、SSH ログインのリアルタイム監視とアラートを実現するには、tg robot API とアラートを受信するユーザー ID を設定する必要があります。"
+ echo "しきい値に達すると、警告メッセージがユーザーに送信されます。"
+ echo -e "${gl_hui}- 通信量についてはサーバーを再起動すると再計算されます -${gl_bai}"
+ read -e -p "続行してもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
- send_stats "電報警告が有効になっています"
+ send_stats "テレグラム警告が有効になっています"
cd ~
install nano tmux bc jq
check_crontab_installed
@@ -11768,7 +20982,7 @@ EOF
sed -i "4i$(grep '^CHAT_ID=' ~/TG-check-notify.sh)" TG-SSH-check-notify.sh
chmod +x ~/TG-SSH-check-notify.sh
- # 〜/.Profileファイルに追加します
+ # ~/.profile ファイルに追加
if ! grep -q 'bash ~/TG-SSH-check-notify.sh' ~/.profile > /dev/null 2>&1; then
echo 'bash ~/TG-SSH-check-notify.sh' >> ~/.profile
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -11779,21 +20993,21 @@ EOF
source ~/.profile
clear
- echo "TG-BOT早期警告システムが開始されました"
- echo -e "${gl_hui}他のマシンのルートディレクトリにTG-Check-notify.sh警告ファイルを配置して、直接使用することもできます。${gl_bai}"
+ echo "TG-bot早期警戒システムが作動しました"
+ echo -e "${gl_hui}TG-check-notify.sh 警告ファイルを他のマシンのルート ディレクトリに置き、それを直接使用することもできます。${gl_bai}"
;;
[Nn])
echo "キャンセル"
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
26)
root_use
- send_stats "SSHの高リスクの脆弱性を修正します"
+ send_stats "高リスクの SSH 脆弱性を修正する"
cd ~
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/upgrade_openssh9.8p1.sh
chmod +x ~/upgrade_openssh9.8p1.sh
@@ -11853,98 +21067,117 @@ EOF
;;
+ 39)
+ clear
+ linux_fav
+ ;;
+
+ 40)
+ clear
+ net_menu
+ ;;
+
41)
+ clear
+ log_menu
+ ;;
+
+ 42)
+ clear
+ env_menu
+ ;;
+
+
+ 61)
clear
- send_stats "メッセージボード"
- echo "テクノロジーライオンメッセージボードは公式コミュニティに移動されました!公式コミュニティにメッセージを残してください!"
- echo "https://bbs.kejilion.pro/"
+ send_stats "掲示板"
+ echo "Technology Lion の公式掲示板をご覧ください。脚本についてのアイデアがあれば、メッセージを残して交換してください。"
+ echo "https://board.kejilion.pro"
+ echo "公開パスワード: kejilion.sh"
;;
66)
root_use
send_stats "ワンストップチューニング"
- echo "ワンストップシステムの最適化"
+ echo "ワンストップのシステムチューニング"
echo "------------------------------------------------"
- echo "以下が操作され、最適化されます"
- echo "1.システムを最新の状態に更新します"
- echo "2。システムジャンクファイルをクリーンアップします"
- echo -e "3.仮想メモリを設定します${gl_huang}1G${gl_bai}"
- echo -e "4. SSHポート番号をに設定します${gl_huang}5522${gl_bai}"
- echo -e "5.すべてのポートを開きます"
- echo -e "6。電源を入れます${gl_huang}BBR${gl_bai}加速します"
- echo -e "7.タイムゾーンをに設定します${gl_huang}上海${gl_bai}"
- echo -e "8。DNSアドレスを自動的に最適化します${gl_huang}海外:1.1.1.1 8.8.8.8国内:223.5.5.5${gl_bai}"
- echo -e "9.基本ツールをインストールします${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
- echo -e "10。Linuxシステムのカーネルパラメーター最適化に切り替えます${gl_huang}バランスの取れた最適化モード${gl_bai}"
+ echo "将对以下内容进行操作与优化"
+ echo "1. システムアップデートソースを最適化し、システムを最新の状態にアップデートします。"
+ echo "2. システムジャンクファイルをクリーンアップする"
+ echo -e "3. 仮想メモリを設定する${gl_huang}1G${gl_bai}"
+ echo -e "4. SSH ポート番号を次のように設定します。${gl_huang}5522${gl_bai}"
+ echo -e "5. SSH ブルート フォース クラッキングを防ぐために、fail2ban を開始します。"
+ echo -e "6.すべてのポートを開きます"
+ echo -e "7.電源を入れます${gl_huang}BBR${gl_bai}加速する"
+ echo -e "8. タイムゾーンを次のように設定します。${gl_huang}上海${gl_bai}"
+ echo -e "9. DNS アドレスを自動的に最適化する${gl_huang}海外:1.1.1.1 8.8.8.8 国内:223.5.5.5${gl_bai}"
+ echo -e "10. ネットワークを次のように設定します。${gl_huang}IPv4優先度${gl_bai}"
+ echo -e "11. 基本ツールのインストール${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "12. Linuxシステムのカーネルパラメータの最適化${gl_huang}ネットワーク環境に応じて自動チューニング${gl_bai}"
echo "------------------------------------------------"
- read -e -p "ワンクリックメンテナンスは必ずありますか? (y/n):" choice
+ read -e -p "ワンクリックメンテナンスを実行してもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
clear
- send_stats "ワンストップチューニングスタート"
+ send_stats "ワンストップチューニングが始まります"
echo "------------------------------------------------"
+ switch_mirror false false
linux_update
- echo -e "[${gl_lv}OK${gl_bai}] 1/10。システムを最新の状態に更新します"
+ echo -e "[${gl_lv}OK${gl_bai}】1/12。システムを最新のものにアップデートする"
echo "------------------------------------------------"
linux_clean
- echo -e "[${gl_lv}OK${gl_bai}] 2/10。システムジャンクファイルをクリーンアップします"
+ echo -e "[${gl_lv}OK${gl_bai}】2/12。システムのジャンクファイルをクリーンアップする"
echo "------------------------------------------------"
add_swap 1024
- echo -e "[${gl_lv}OK${gl_bai}] 3/10。仮想メモリを設定します${gl_huang}1G${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}】3/12。仮想メモリを設定する${gl_huang}1G${gl_bai}"
echo "------------------------------------------------"
- local new_port=5522
- new_ssh_port
- echo -e "[${gl_lv}OK${gl_bai}] 4/10。 SSHポート番号をに設定します${gl_huang}5522${gl_bai}"
+ new_ssh_port 5522
+ echo -e "[${gl_lv}OK${gl_bai}】4/12。 SSH ポート番号を次のように設定します。${gl_huang}5522${gl_bai}"
+ echo "------------------------------------------------"
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ echo -e "[${gl_lv}OK${gl_bai}】5/12。 SSH ブルート フォース クラッキングを防ぐために、fail2ban を開始します。"
+
echo "------------------------------------------------"
- echo -e "[${gl_lv}OK${gl_bai}] 5/10。すべてのポートを開きます"
+ echo -e "[${gl_lv}OK${gl_bai}】6/12。すべてのポートを開く"
echo "------------------------------------------------"
bbr_on
- echo -e "[${gl_lv}OK${gl_bai}] 6/10。開ける${gl_huang}BBR${gl_bai}加速します"
+ echo -e "[${gl_lv}OK${gl_bai}】7/12。開ける${gl_huang}BBR${gl_bai}加速する"
echo "------------------------------------------------"
set_timedate Asia/Shanghai
- echo -e "[${gl_lv}OK${gl_bai}] 7/10。タイムゾーンをに設定します${gl_huang}上海${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}】8/12。タイムゾーンを次のように設定します${gl_huang}上海${gl_bai}"
echo "------------------------------------------------"
- local country=$(curl -s ipinfo.io/country)
- if [ "$country" = "CN" ]; then
- local dns1_ipv4="223.5.5.5"
- local dns2_ipv4="183.60.83.19"
- local dns1_ipv6="2400:3200::1"
- local dns2_ipv6="2400:da00::6666"
- else
- local dns1_ipv4="1.1.1.1"
- local dns2_ipv4="8.8.8.8"
- local dns1_ipv6="2606:4700:4700::1111"
- local dns2_ipv6="2001:4860:4860::8888"
- fi
-
- set_dns
- echo -e "[${gl_lv}OK${gl_bai}] 8/10。 DNSアドレスを自動的に最適化します${gl_huang}${gl_bai}"
+ auto_optimize_dns
+ echo -e "[${gl_lv}OK${gl_bai}】9/12。 DNSアドレスを自動的に最適化する${gl_huang}${gl_bai}"
+ echo "------------------------------------------------"
+ prefer_ipv4
+ echo -e "[${gl_lv}OK${gl_bai}】10/12。ネットワークを次のように設定します${gl_huang}IPv4優先度${gl_bai}}"
echo "------------------------------------------------"
install_docker
install wget sudo tar unzip socat btop nano vim
- echo -e "[${gl_lv}OK${gl_bai}] 9/10。基本ツールをインストールします${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}】11/12。基本的なツールをインストールする${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
echo "------------------------------------------------"
- echo "------------------------------------------------"
- optimize_balanced
- echo -e "[${gl_lv}OK${gl_bai}] 10/10。 Linuxシステムのカーネルパラメーターの最適化"
- echo -e "${gl_lv}ワンストップシステムのチューニングが完了しました${gl_bai}"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ echo -e "[${gl_lv}OK${gl_bai}】12/12。 Linuxシステムのカーネルパラメータの最適化"
+ echo -e "${gl_lv}ワンストップでのシステムチューニングが完了${gl_bai}"
;;
[Nn])
echo "キャンセル"
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
@@ -11961,39 +21194,39 @@ EOF
while true; do
clear
if grep -q '^ENABLE_STATS="true"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_lv}正在采集数据${gl_bai}"
+ local status_message="${gl_lv}データの収集${gl_bai}"
elif grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_hui}采集已关闭${gl_bai}"
+ local status_message="${gl_hui}コレクションは終了しました${gl_bai}"
else
- local status_message="无法确定的状态"
+ local status_message="不確実なステータス"
fi
echo "プライバシーとセキュリティ"
- echo "スクリプトは、ユーザー機能に関するデータを収集し、スクリプトエクスペリエンスを最適化し、より楽しく便利な機能を作成します。"
- echo "スクリプトバージョン番号、使用時間、システムバージョン、CPUアーキテクチャ、マシンの国、および使用される関数の名前を収集します。"
+ echo "スクリプトはユーザーの機能使用に関するデータを収集し、スクリプト エクスペリエンスを最適化し、より楽しくて便利な機能を作成します。"
+ echo "スクリプトのバージョン番号、使用時間、システムバージョン、CPUアーキテクチャ、マシンの国、使用された機能の名前が収集されます。"
echo "------------------------------------------------"
- echo -e "現在のステータス:$status_message"
+ echo -e "現在のステータス:$status_message"
echo "--------------------"
- echo "1。コレクションをオンにします"
- echo "2。コレクションを閉じます"
+ echo "1.収集を開始する"
+ echo "2. コレクションを閉じる"
echo "--------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "--------------------"
- read -e -p "選択を入力してください:" sub_choice
+ read -e -p "選択肢を入力してください:" sub_choice
case $sub_choice in
1)
cd ~
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' ~/kejilion.sh
- echo "コレクションが有効になっています"
- send_stats "プライバシーとセキュリティコレクションが有効になっています"
+ echo "収集が開始されました"
+ send_stats "プライバシーとセキュリティの収集がオンになっています"
;;
2)
cd ~
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
- echo "コレクションは閉じた"
- send_stats "プライバシーとセキュリティは収集のために閉鎖されています"
+ echo "コレクションは終了しました"
+ send_stats "プライバシーとセキュリティの収集がオフになっています"
;;
*)
break
@@ -12009,11 +21242,11 @@ EOF
102)
clear
- send_stats "テックライオンスクリプトをアンインストールします"
- echo "テックライオンスクリプトをアンインストールします"
+ send_stats "Tech Lion スクリプトをアンインストールする"
+ echo "Tech Lion スクリプトをアンインストールする"
echo "------------------------------------------------"
- echo "Kejilionスクリプトを完全にアンインストールし、他の機能には影響しません"
- read -e -p "必ず続行しますか? (y/n):" choice
+ echo "kejilion スクリプトは、他の機能に影響を与えることなく完全にアンインストールされます。"
+ read -e -p "続行してもよろしいですか? (はい/いいえ):" choice
case "$choice" in
[Yy])
@@ -12021,7 +21254,7 @@ EOF
(crontab -l | grep -v "kejilion.sh") | crontab -
rm -f /usr/local/bin/k
rm ~/kejilion.sh
- echo "スクリプトはアンインストールされています、さようなら!"
+ echo "スクリプトはアンインストールされました、さようなら!"
break_end
clear
exit
@@ -12030,7 +21263,7 @@ EOF
echo "キャンセル"
;;
*)
- echo "無効な選択、yまたはnを入力してください。"
+ echo "選択が無効です。Y または N を入力してください。"
;;
esac
;;
@@ -12040,7 +21273,7 @@ EOF
;;
*)
- echo "無効な入力!"
+ echo "無効な入力です!"
;;
esac
break_end
@@ -12068,174 +21301,161 @@ linux_file() {
echo "------------------------"
ls --color=auto -x
echo "------------------------"
- echo "1.ディレクトリ2を入力します。ディレクトリを作成3。ディレクトリアクセス許可を変更します。4。ディレクトリの名前を変更します"
- echo "5.ディレクトリを削除6。前のメニューディレクトリに戻ります"
+ echo "1. ディレクトリを入力します。 2. ディレクトリを作成します。 3. ディレクトリのアクセス許可を変更します。 4. ディレクトリの名前を変更します。"
+ echo "5. ディレクトリを削除します。 6. 前のメニュー ディレクトリに戻ります。"
echo "------------------------"
- echo "11。ファイルを作成する12。ファイル13を編集します。ファイル許可を変更14。ファイルの名前を変更します"
- echo "15.ファイルを削除します"
+ echo "11. ファイルの作成 12. ファイルの編集 13. ファイル権限の変更 14. ファイル名の変更"
+ echo "15. ファイルの削除"
echo "------------------------"
- echo "21。ファイルディレクトリの圧縮22。UNZIPファイルディレクトリ23。ファイルディレクトリの移動24。ファイルディレクトリをコピーする"
- echo "25。ファイルを別のサーバーに渡します"
+ echo "21. ファイル ディレクトリの圧縮 22. ファイル ディレクトリの解凍 23. ファイル ディレクトリの移動 24. ファイル ディレクトリのコピー"
+ echo "25. 他のサーバーにファイルを転送する"
echo "------------------------"
- echo "0。前のメニューに戻ります"
+ echo "0. 前のメニューに戻る"
echo "------------------------"
- read -e -p "選択を入力してください:" Limiting
+ read -e -p "選択肢を入力してください:" Limiting
case "$Limiting" in
1) # 进入目录
- read -e -p "ディレクトリ名を入力してください:" dirname
- cd "$dirname" 2>/dev/null || echo "ディレクトリを入力できません"
- send_stats "ディレクトリに移動します"
+ read -e -p "ディレクトリ名を入力してください:" dirname
+ cd "$dirname" 2>/dev/null || echo "ディレクトリに入れません"
+ send_stats "ディレクトリを入力してください"
;;
2) # 创建目录
- read -e -p "作成するにはディレクトリ名を入力してください。" dirname
- mkdir -p "$dirname" && echo "作成されたディレクトリ" || echo "作成に失敗しました"
- send_stats "ディレクトリを作成します"
+ read -e -p "作成するディレクトリ名を入力してください:" dirname
+ mkdir -p "$dirname" && echo "ディレクトリが作成されました" || echo "作成に失敗しました"
+ send_stats "ディレクトリの作成"
;;
3) # 修改目录权限
- read -e -p "ディレクトリ名を入力してください:" dirname
- read -e -p "許可を入力してください(755など):" perm
- chmod "$perm" "$dirname" && echo "許可が変更されました" || echo "変更に失敗しました"
- send_stats "ディレクトリ権限を変更します"
+ read -e -p "ディレクトリ名を入力してください:" dirname
+ read -e -p "権限を入力してください (例: 755):" perm
+ chmod "$perm" "$dirname" && echo "権限が変更されました" || echo "変更に失敗しました"
+ send_stats "ディレクトリの権限を変更する"
;;
4) # 重命名目录
- read -e -p "現在のディレクトリ名を入力してください:" current_name
- read -e -p "新しいディレクトリ名を入力してください:" new_name
- mv "$current_name" "$new_name" && echo "ディレクトリの名前が変更されました" || echo "名前変更に失敗しました"
- send_stats "ディレクトリの名前を変更します"
+ read -e -p "現在のディレクトリ名を入力してください:" current_name
+ read -e -p "新しいディレクトリ名を入力してください:" new_name
+ mv "$current_name" "$new_name" && echo "ディレクトリの名前が変更されました" || echo "名前の変更に失敗しました"
+ send_stats "ディレクトリの名前を変更する"
;;
5) # 删除目录
- read -e -p "削除するには、ディレクトリ名を入力してください。" dirname
- rm -rf "$dirname" && echo "ディレクトリが削除されました" || echo "削除が失敗しました"
- send_stats "ディレクトリを削除します"
+ read -e -p "削除するディレクトリ名を入力してください:" dirname
+ rm -rf "$dirname" && echo "ディレクトリが削除されました" || echo "削除に失敗しました"
+ send_stats "ディレクトリを削除する"
;;
6) # 返回上一级选单目录
cd ..
- send_stats "前のメニューディレクトリに戻ります"
+ send_stats "前のメニュー ディレクトリに戻る"
;;
11) # 创建文件
- read -e -p "作成するにはファイル名を入力してください。" filename
- touch "$filename" && echo "作成されたファイル" || echo "作成に失敗しました"
- send_stats "ファイルを作成します"
+ read -e -p "作成するファイル名を入力してください:" filename
+ touch "$filename" && echo "ファイルが作成されました" || echo "作成に失敗しました"
+ send_stats "ファイルの作成"
;;
12) # 编辑文件
- read -e -p "編集するにはファイル名を入力してください:" filename
+ read -e -p "編集するファイル名を入力してください:" filename
install nano
nano "$filename"
- send_stats "ファイルを編集します"
+ send_stats "ファイルを編集する"
;;
13) # 修改文件权限
- read -e -p "ファイル名を入力してください:" filename
- read -e -p "許可を入力してください(755など):" perm
- chmod "$perm" "$filename" && echo "許可が変更されました" || echo "変更に失敗しました"
- send_stats "ファイル権限を変更します"
+ read -e -p "ファイル名を入力してください:" filename
+ read -e -p "権限を入力してください (例: 755):" perm
+ chmod "$perm" "$filename" && echo "権限が変更されました" || echo "変更に失敗しました"
+ send_stats "ファイル権限を変更する"
;;
14) # 重命名文件
- read -e -p "現在のファイル名を入力してください:" current_name
- read -e -p "新しいファイル名を入力してください:" new_name
- mv "$current_name" "$new_name" && echo "名前の変更" || echo "名前変更に失敗しました"
- send_stats "ファイルの名前を変更します"
+ read -e -p "現在のファイル名を入力してください:" current_name
+ read -e -p "新しいファイル名を入力してください:" new_name
+ mv "$current_name" "$new_name" && echo "ファイル名が変更されました" || echo "名前の変更に失敗しました"
+ send_stats "ファイル名の変更"
;;
15) # 删除文件
- read -e -p "削除するには、ファイル名を入力してください。" filename
- rm -f "$filename" && echo "削除されたファイル" || echo "削除が失敗しました"
- send_stats "ファイルを削除します"
+ read -e -p "削除するファイル名を入力してください:" filename
+ rm -f "$filename" && echo "ファイルが削除されました" || echo "削除に失敗しました"
+ send_stats "ファイルの削除"
;;
21) # 压缩文件/目录
- read -e -p "圧縮するには、ファイル/ディレクトリ名を入力してください。" name
+ read -e -p "圧縮するファイル/ディレクトリ名を入力してください:" name
install tar
- tar -czvf "$name.tar.gz" "$name" && echo "圧縮$name.tar.gz" || echo "圧縮に失敗しました"
+ tar -czvf "$name.tar.gz" "$name" && echo "に圧縮$name.tar.gz" || echo "圧縮に失敗しました"
send_stats "圧縮ファイル/ディレクトリ"
;;
22) # 解压文件/目录
- read -e -p "ファイル名(.tar.gz)を入力してください:" filename
+ read -e -p "抽出するファイル名 (.tar.gz) を入力してください:" filename
install tar
- tar -xzvf "$filename" && echo "減圧$filename" || echo "減圧が失敗しました"
- send_stats "ファイル/ディレクトリを解凍します"
+ tar -xzvf "$filename" && echo "解凍された$filename" || echo "解凍に失敗しました"
+ send_stats "ファイル/ディレクトリを解凍する"
;;
23) # 移动文件或目录
- read -e -p "移動するには、ファイルまたはディレクトリパスを入力してください。" src_path
+ read -e -p "移動するファイルまたはディレクトリのパスを入力してください:" src_path
if [ ! -e "$src_path" ]; then
- echo "エラー:ファイルまたはディレクトリは存在しません。"
- send_stats "ファイルまたはディレクトリの移動に失敗しました:ファイルまたはディレクトリは存在しません"
+ echo "エラー: ファイルまたはディレクトリが存在しません。"
+ send_stats "ファイルまたはディレクトリの移動に失敗しました: ファイルまたはディレクトリが存在しません"
continue
fi
- read -e -p "ターゲットパス(新しいファイル名またはディレクトリ名を含む)を入力してください。" dest_path
+ read -e -p "宛先パス (新しいファイルまたはディレクトリ名を含む) を入力してください:" dest_path
if [ -z "$dest_path" ]; then
- echo "エラー:ターゲットパスを入力してください。"
- send_stats "ファイルまたはディレクトリの移動に失敗しました:宛先パスが指定されていません"
+ echo "エラー: 宛先パスを入力してください。"
+ send_stats "ファイルまたはディレクトリの移動に失敗しました: 宛先パスが指定されていません"
continue
fi
- mv "$src_path" "$dest_path" && echo "ファイルまたはディレクトリが移動されました$dest_path" || echo "ファイルやディレクトリの移動に失敗しました"
- send_stats "ファイルまたはディレクトリを移動します"
+ mv "$src_path" "$dest_path" && echo "ファイルまたはディレクトリの移動先$dest_path" || echo "ファイルまたはディレクトリの移動に失敗しました"
+ send_stats "ファイルまたはディレクトリを移動する"
;;
24) # 复制文件目录
- read -e -p "コピーするには、ファイルまたはディレクトリパスを入力してください。" src_path
+ read -e -p "コピーするファイルまたはディレクトリのパスを入力してください:" src_path
if [ ! -e "$src_path" ]; then
- echo "エラー:ファイルまたはディレクトリは存在しません。"
- send_stats "ファイルまたはディレクトリのコピーに失敗しました:ファイルまたはディレクトリが存在しません"
+ echo "エラー: ファイルまたはディレクトリが存在しません。"
+ send_stats "ファイルまたはディレクトリのコピーに失敗しました: ファイルまたはディレクトリが存在しません"
continue
fi
- read -e -p "ターゲットパス(新しいファイル名またはディレクトリ名を含む)を入力してください。" dest_path
+ read -e -p "宛先パス (新しいファイルまたはディレクトリ名を含む) を入力してください:" dest_path
if [ -z "$dest_path" ]; then
- echo "エラー:ターゲットパスを入力してください。"
- send_stats "ファイルまたはディレクトリのコピーに失敗しました:宛先パスが指定されていない"
+ echo "エラー: 宛先パスを入力してください。"
+ send_stats "ファイルまたはディレクトリのコピーに失敗しました: 宛先パスが指定されていません"
continue
fi
- # -Rオプションを使用して、ディレクトリを再帰的にコピーします
- cp -r "$src_path" "$dest_path" && echo "ファイルまたはディレクトリがコピーされています$dest_path" || echo "ファイルまたはディレクトリのコピーに失敗しました"
- send_stats "ファイルまたはディレクトリをコピーします"
+ # -r オプションを使用してディレクトリを再帰的にコピーします
+ cp -r "$src_path" "$dest_path" && echo "コピー先のファイルまたはディレクトリ$dest_path" || echo "ファイルまたはディレクトリのコピーに失敗しました"
+ send_stats "ファイルまたはディレクトリをコピーする"
;;
25) # 传送文件至远端服务器
- read -e -p "転送されるファイルパスを入力してください。" file_to_transfer
+ read -e -p "転送するファイル パスを入力してください:" file_to_transfer
if [ ! -f "$file_to_transfer" ]; then
- echo "エラー:ファイルは存在しません。"
- send_stats "ファイルの転送に失敗しました:ファイルは存在しません"
+ echo "エラー: ファイルが存在しません。"
+ send_stats "ファイルの転送に失敗しました: ファイルが存在しません"
continue
fi
- read -e -p "リモートサーバーIPを入力してください:" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "エラー:リモートサーバーIPを入力してください。"
- send_stats "ファイル転送に失敗しました:リモートサーバーIPは入力されませんでした"
- continue
- fi
-
- read -e -p "リモートサーバーのユーザー名(デフォルトルート)を入力してください。" remote_user
- remote_user=${remote_user:-root}
-
- read -e -p "リモートサーバーのパスワードを入力してください:" -s remote_password
- echo
- if [ -z "$remote_password" ]; then
- echo "エラー:リモートサーバーのパスワードを入力してください。"
- send_stats "ファイル転送の失敗:リモートサーバーパスワードが入力されていません"
- continue
- fi
+ kj_ssh_read_host_user_port "リモートサーバーのIPを入力してください:" "リモート サーバーのユーザー名 (デフォルトの root) を入力してください:" "ログイン ポートを入力してください (デフォルトは 22):" "root" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local remote_user="$KJ_SSH_USER"
+ local remote_port="$KJ_SSH_PORT"
- read -e -p "ログインポートを入力してください(デフォルト22):" remote_port
- remote_port=${remote_port:-22}
+ kj_ssh_read_password "リモートサーバーのパスワードを入力してください:"
+ local remote_password="$KJ_SSH_PASSWORD"
- # 既知のホストの古いエントリをクリアします
+ # 既知のホストの古いエントリをクリアする
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 等待时间
- # SCPを使用してファイルを転送します
+ # scpを使用してファイルを転送する
scp -P "$remote_port" -o StrictHostKeyChecking=no "$file_to_transfer" "$remote_user@$remote_ip:/home/" </dev/null
+
+ # 一時ファイルにダウンロードし、確認してから置き換えます
+ local tmp_file=$(mktemp ~/kejilion_tmp.XXXXXX)
+ if curl -sS --max-time 60 --fail -o "$tmp_file" "$download_url" && \
+ [ -s "$tmp_file" ] && \
+ head -1 "$tmp_file" | grep -q '^#!/bin/bash'; then
+ chmod +x "$tmp_file"
+ mv -f "$tmp_file" ~/kejilion.sh
+ canshu_v6
+ CheckFirstRun_true
+ yinsiyuanquan2
+ cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
+ echo -e "${gl_lv}スクリプトが最新バージョンに更新されました。${gl_huang}v$sh_v_new${gl_bai}"
+ send_stats "スクリプトは最新です$sh_v_new"
else
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh
+ rm -f "$tmp_file"
+ # バックアップを復元する
+ if [ -f ~/kejilion.sh.bak ]; then
+ mv -f ~/kejilion.sh.bak ~/kejilion.sh
+ fi
+ echo -e "${gl_hong}アップデートに失敗しました!ダウンロード中にエラーが発生したか、ファイルの検証に失敗しました。オリジナルのバージョンが復元されました。${gl_bai}"
+ send_stats "スクリプトの更新に失敗しました"
fi
- canshu_v6
- CheckFirstRun_true
- yinsiyuanquan2
- cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
- echo -e "${gl_lv}スクリプトは最新バージョンに更新されました!${gl_huang}v$sh_v_new${gl_bai}"
- send_stats "スクリプトは最新です$sh_v_new"
break_end
~/kejilion.sh
exit
;;
2)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
local ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
+ local cron_proxy cron_sed_cmd
if [ "$country" = "CN" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ~/kejilion.sh"
elif [ -n "$ipv6_address" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ~/kejilion.sh"
else
- SH_Update_task="curl -sS -O https://raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh"
+ cron_proxy="https://"
+ cron_sed_cmd=""
+ fi
+
+ # 堅牢な自動更新コマンドを作成します: 一時ファイルにダウンロード → 検証 → バックアップ → 置換 → ローカル設定の復元 → 導入
+ SH_Update_task="cd ~ && tmp=\$(mktemp ~/kejilion_tmp.XXXXXX) && curl -sS --max-time 60 --fail -o \"\$tmp\" ${cron_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && [ -s \"\$tmp\" ] && head -1 \"\$tmp\" | grep -q '^#!/bin/bash' && cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null && chmod +x \"\$tmp\" && mv -f \"\$tmp\" ~/kejilion.sh"
+ # 追加設定の回復
+ if [ -n "$cron_sed_cmd" ]; then
+ SH_Update_task="$SH_Update_task && $cron_sed_cmd"
fi
+ # 古いスクリプトからpermission_grantedおよびENABLE_STATS設定を復元します
+ SH_Update_task="$SH_Update_task && grep -q 'permission_granted=\"true\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/permission_granted=\"false\"/permission_granted=\"true\"/' ~/kejilion.sh; grep -q 'ENABLE_STATS=\"false\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/ENABLE_STATS=\"true\"/ENABLE_STATS=\"false\"/' ~/kejilion.sh"
+ # /usr/local/bin/k および /usr/bin/k にデプロイします
+ SH_Update_task="$SH_Update_task; cp -f ~/kejilion.sh /usr/local/bin/k 2>/dev/null; ln -sf /usr/local/bin/k /usr/bin/k 2>/dev/null"
+ # ダウンロードが失敗した場合に一時ファイルを消去する
+ SH_Update_task="$SH_Update_task || rm -f \"\$tmp\" 2>/dev/null"
+
check_crontab_installed
(crontab -l | grep -v "kejilion.sh") | crontab -
- # (crontab -l 2>/dev/null; echo "0 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- echo -e "${gl_lv}自動更新が有効になり、スクリプトは毎日午前2時に自動的に更新されます!${gl_bai}"
- send_stats "自動スクリプトの更新をオンにします"
+ (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c '$SH_Update_task'") | crontab -
+ echo -e "${gl_lv}自動更新がオンになっており、スクリプトは毎日午前 2 時に自動的に更新されます。${gl_bai}"
+ send_stats "スクリプトの自動更新を有効にする"
break_end
;;
3)
clear
(crontab -l | grep -v "kejilion.sh") | crontab -
- echo -e "${gl_lv}自動更新は閉じられています${gl_bai}"
- send_stats "スクリプト自動更新を閉じます"
+ echo -e "${gl_lv}自動更新はオフになっています${gl_bai}"
+ send_stats "スクリプトの自動更新をオフにする"
break_end
;;
*)
@@ -12566,41 +21886,40 @@ echo -e "${gl_kjlan}"
echo "╦╔═╔═╗ ╦╦╦ ╦╔═╗╔╗╔ ╔═╗╦ ╦"
echo "╠╩╗║╣ ║║║ ║║ ║║║║ ╚═╗╠═╣"
echo "╩ ╩╚═╝╚╝╩╩═╝╩╚═╝╝╚╝o╚═╝╩ ╩"
-echo -e "テクノロジーライオンスクリプトツールボックスv$sh_v"
-echo -e "コマンドライン入力${gl_huang}k${gl_kjlan}スクリプトをすばやく開始します${gl_bai}"
+echo -e "テクノロジー ライオン スクリプト ツールボックス v$sh_v"
+echo -e "コマンドライン入力${gl_huang}k${gl_kjlan}クイックスタートスクリプト${gl_bai}"
echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}1. ${gl_bai}システム情報クエリ"
-echo -e "${gl_kjlan}2. ${gl_bai}システムの更新"
+echo -e "${gl_kjlan}1. ${gl_bai}システム情報の問い合わせ"
+echo -e "${gl_kjlan}2. ${gl_bai}システムアップデート"
echo -e "${gl_kjlan}3. ${gl_bai}システムのクリーンアップ"
echo -e "${gl_kjlan}4. ${gl_bai}基本的なツール"
echo -e "${gl_kjlan}5. ${gl_bai}BBR管理"
echo -e "${gl_kjlan}6. ${gl_bai}Docker管理"
echo -e "${gl_kjlan}7. ${gl_bai}ワープ管理"
-echo -e "${gl_kjlan}8. ${gl_bai}テストスクリプトコレクション"
-echo -e "${gl_kjlan}9. ${gl_bai}Oracle Cloud Scriptコレクション"
-echo -e "${gl_huang}10. ${gl_bai}LDNMP Webサイトビルディング"
+echo -e "${gl_kjlan}8. ${gl_bai}テストスクリプト集"
+echo -e "${gl_kjlan}9. ${gl_bai}Oracle Cloudスクリプト・コレクション"
+echo -e "${gl_huang}10. ${gl_bai}LDNMP Web サイトの構築"
echo -e "${gl_kjlan}11. ${gl_bai}アプリケーション市場"
echo -e "${gl_kjlan}12. ${gl_bai}バックエンドワークスペース"
echo -e "${gl_kjlan}13. ${gl_bai}システムツール"
-echo -e "${gl_kjlan}14. ${gl_bai}サーバークラスター制御"
-echo -e "${gl_kjlan}15. ${gl_bai}広告列"
-echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}p. ${gl_bai}Phantom Beast Palu Serverオープニングスクリプト"
+echo -e "${gl_kjlan}14. ${gl_bai}サーバークラスタ制御"
+echo -e "${gl_kjlan}15. ${gl_bai}広告コラム"
+echo -e "${gl_kjlan}16. ${gl_bai}ゲームサーバー起動スクリプト集"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}00. ${gl_bai}スクリプトの更新"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}0. ${gl_bai}スクリプトを終了します"
echo -e "${gl_kjlan}------------------------${gl_bai}"
-read -e -p "選択を入力してください:" choice
+read -e -p "選択肢を入力してください:" choice
case $choice in
- 1) linux_ps ;;
- 2) clear ; send_stats "システムの更新" ; linux_update ;;
+ 1) linux_info ;;
+ 2) clear ; send_stats "システムアップデート" ; linux_update ;;
3) clear ; send_stats "システムのクリーンアップ" ; linux_clean ;;
4) linux_tools ;;
5) linux_bbr ;;
6) linux_docker ;;
- 7) clear ; send_stats "ワープ管理" ; install wget
+ 7) clear ; send_stats "反り管理" ; install wget
wget -N https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh ; bash menu.sh [option] [lisence/url/token]
;;
8) linux_test ;;
@@ -12611,13 +21930,10 @@ case $choice in
13) linux_Settings ;;
14) linux_cluster ;;
15) kejilion_Affiliates ;;
- p) send_stats "Phantom Beast Palu Serverオープニングスクリプト" ; cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
- exit
- ;;
+ 16) games_server_tools ;;
00) kejilion_update ;;
0) clear ; exit ;;
- *) echo "無効な入力!" ;;
+ *) echo "無効な入力です!" ;;
esac
break_end
done
@@ -12625,67 +21941,76 @@ done
k_info() {
-send_stats "Kコマンド参照ユースケース"
+send_stats "k コマンドのリファレンス例"
echo "-------------------"
-echo "ビデオの紹介:https://www.bilibili.com/video/bv1ib421e7it?t=0.1"
-echo "以下は、Kコマンドリファレンスユースケースです。"
+echo "ビデオ紹介: https://www.bilibili.com/video/BV1ib421E7it?t=0.1"
+echo "以下は、k コマンドの参考使用例です。"
echo "スクリプトkを開始します"
-echo "ソフトウェアパッケージkをインストールしますnano wgetをインストールします| k nano wgetを追加| K nano wgetをインストールします"
-echo "パッケージのアンインストールk nano wgetを削除| k del nano wget | K UNINSTALLNANOWGET | K UNINSTALLNANO WGET"
-echo "システムKアップデートを更新| Kアップデート"
-echo "クリーンシステムガベージkクリーン| kきれい"
-echo "システムパネルk dd |を再インストールしますk再インストール"
-echo "BBR3コントロールパネルK BBR3 | K BBRV3"
-echo "カーネルチューニングパネルk nhyh | Kカーネル最適化"
-echo "仮想メモリkスワップ2048を設定します"
-echo "仮想タイムゾーンKタイムアジア/上海|を設定しますKタイムゾーンアジア/上海"
-echo "システムリサイクルビンKトラッシュ| K HSZ | Kリサイクルビン"
-echo "システムバックアップ関数Kバックアップ| k bf | Kバックアップ"
-echo "SSHリモート接続ツールK SSH | Kリモート接続"
-echo "rsyncリモート同期ツールk rsync | Kリモート同期"
-echo "ハードディスク管理ツールKディスク| Kハードディスク管理"
-echo "イントラネット浸透(サーバー側)K FRP"
-echo "イントラネット浸透(クライアント)K FRPC"
-echo "ソフトウェアStart K Start SSHD | k sshdを開始します"
-echo "ソフトウェアSTOP K STOP SSHD | k stop sshd"
-echo "ソフトウェア再起動k再起動sshd | k再起動sshd"
-echo "ソフトウェアステータスビューKステータスSSHD | KステータスSSHD"
-echo "ソフトウェアブートk dockerを有効にする| K AutoStart Docke | Kスタートアップドッカー"
-echo "ドメイン名証明書アプリケーションK SSL"
-echo "ドメイン名証明書の有効期限クエリK SSL PS"
-echo "Docker Environment Installation K Dockerインストール| K Dockerのインストール"
-echo "Docker Container Management K Docker PS | K Dockerコンテナ"
-echo "Docker Image Management K Docker IMG | K Docker画像"
-echo "LDNMPサイト管理k Web"
-echo "LDNMPキャッシュクリーンアップK Webキャッシュ"
-echo "WordPress k wp | k wordpress | k wp xxx.comをインストールします"
-echo "リバースプロキシk fd | k rp | k抗ジェネレーション| k fd xxx.comをインストールする"
-echo "ロードバランスkロードバランス| kロードバランシングをインストールします"
-echo "ファイアウォールパネルk fhq | kファイアウォール"
-echo "オープンポートK DKDK 8080 | Kオープンポート8080"
-echo "ポートK GBDK 7800を閉じる| kポート7800を閉じます"
-echo "IP K FXIP 127.0.0.0/8 | KリリースIP 127.0.0.0/8をリリースします"
-echo "ブロックIP K ZZIP 177.5.25.36 | KブロックIP 177.5.25.36"
-
+echo "パッケージをインストールします k install nano wget | k ナノ wget を追加 | nano wgetをインストールします"
+echo "パッケージをアンインストールします。 k 削除 nano wget | kデルナノwget | k nano wget をアンインストールする | nano wgetをアンインストールします"
+echo "システム k 更新を更新します。 kアップデート"
+echo "クリーン系ジャンククリーン |きれいだ"
+echo "システムパネルを再度取り付けます。 k再インストール"
+echo "BBR3 コントロール パネル K BBR3 | k bbrv3"
+echo "カーネル チューニング パネルk カーネルの最適化"
+echo "仮想メモリ k スワップを設定 2048"
+echo "仮想タイムゾーンを設定します k 時間 アジア/上海 | k タイムゾーン アジア/上海"
+echo "システムごみ箱のゴミ箱 | k hz | k ごみ箱"
+echo "システムバックアップ機能 kバックアップ | k bf | k バックアップ"
+echo "ssh リモート接続ツール k ssh | kリモート接続"
+echo "rsync リモート同期ツール k rsync | k リモート同期"
+echo "ハードディスク管理ツール k ディスク | k ハードディスクの管理"
+echo "イントラネット普及率 (サーバー) k frps"
+echo "イントラネット浸透率 (クライアント) k frpc"
+echo "ソフトウェア起動 k start sshd | sshdを起動します"
+echo "ソフトウェア停止 k 停止 sshd | k ストップ sshd"
+echo "ソフトウェア再起動 k 再起動 sshd | k sshdを再起動します"
+echo "ソフトウェアのステータスを確認します。 k ステータス sshd | kステータスsshd"
+echo "k ドッカーを有効にする | k 自動開始ドッカー | k ソフトウェアの起動時に Docker を有効にする"
+echo "ドメイン名証明書アプリケーション k ssl"
+echo "ドメイン名証明書の有効期限のクエリ k ssl ps"
+echo "docker 管理プレーン k docker"
+echo "docker 環境のインストール k docker install |k docker インストール"
+echo "docker コンテナ管理 k docker ps |k docker コンテナ"
+echo "docker イメージ管理 k docker img |k docker image"
+echo "LDNMP サイト管理 k Web"
+echo "LDNMP キャッシュのクリーニング k Web キャッシュ"
+echo "WordPress をインストールします。 kワードプレス | k wp xxx.com"
+echo "リバース プロキシ k fd |k rp |k リバース プロキシ |k fd xxx.com をインストールします。"
+echo "ロード バランシングのインストール k ロード バランシング |k ロード バランシング"
+echo "L4 ロード バランシング k ストリーム |k L4 ロード バランシングをインストールする"
+echo "ファイアウォール パネル k fhq |k ファイアウォール"
+echo "ポートを開く k dkdk 8080 |k ポートを開く 8080"
+echo "ポート k gbdk 7800 を閉じる |k ポート 7800 を閉じる"
+echo "リリース IP k fxip 127.0.0.0/8 |k リリース IP 127.0.0.0/8"
+echo "ブロック IP k zzip 177.5.25.36 |k ブロック IP 177.5.25.36"
+echo "コマンド お気に入り k お気に入り | k コマンドのお気に入り"
+echo "アプリケーションマーケット管理kアプリ"
+echo "申請番号の迅速な管理 k app 26 | kアプリ1パネル | k アプリ npm"
+echo "フェイル 2 バン管理 k フェイル 2 バン | k f2b"
+echo "システム情報を表示 k info"
+echo "ROOT キー管理 k sshkey"
+echo "SSH 公開キーのインポート (URL) k sshkey "
+echo "SSH 公開キーのインポート (GitHub) k sshkey github "
}
if [ "$#" -eq 0 ]; then
- # パラメーターがない場合は、インタラクティブロジックを実行します
+ # 引数なしで対話型ロジックを実行します
kejilion_sh
else
- # パラメーターがある場合は、対応する関数を実行します
+ # パラメータがある場合は、対応する関数を実行します
case $1 in
install|add|安装)
shift
- send_stats "ソフトウェアをインストールします"
+ send_stats "ソフトウェアのインストール"
install "$@"
;;
remove|del|uninstall|卸载)
shift
- send_stats "ソフトウェアをアンインストールします"
+ send_stats "ソフトウェアのアンインストール"
remove "$@"
;;
update|更新)
@@ -12719,7 +22044,7 @@ else
rsync_run)
shift
- send_stats "タイム付きRSYNC同期"
+ send_stats "スケジュールされたrsync同期"
run_task "$@"
;;
@@ -12738,9 +22063,10 @@ else
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "IP+ポートは、サービスへのアクセスをブロックされています"
+ echo "IP+ポートはサービスへのアクセスをブロックされています"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
;;
@@ -12749,15 +22075,20 @@ else
ldnmp_Proxy_backend
;;
+
+ stream|L4负载均衡)
+ ldnmp_Proxy_backend_stream
+ ;;
+
swap)
shift
- send_stats "仮想メモリをすばやく設定します"
+ send_stats "仮想メモリをすばやくセットアップする"
add_swap "$@"
;;
time|时区)
shift
- send_stats "タイムゾーンをすばやく設定します"
+ send_stats "タイムゾーンを素早く設定"
set_timedate "$@"
;;
@@ -12799,14 +22130,18 @@ else
iptables_panel
;;
+ 命令收藏夹|fav)
+ linux_fav
+ ;;
+
status|状态)
shift
- send_stats "ソフトウェアステータスビュー"
+ send_stats "ソフトウェアのステータスを確認する"
status "$@"
;;
start|启动)
shift
- send_stats "ソフトウェアスタートアップ"
+ send_stats "ソフトウェアの起動"
start "$@"
;;
stop|停止)
@@ -12822,14 +22157,14 @@ else
enable|autostart|开机启动)
shift
- send_stats "ソフトウェアが起動します"
+ send_stats "起動時にソフトウェアが自動的に起動します"
enable "$@"
;;
ssl)
shift
if [ "$1" = "ps" ]; then
- send_stats "証明書のステータスを確認してください"
+ send_stats "証明書ステータスの表示"
ssl_ps
elif [ -z "$1" ]; then
add_ssl
@@ -12846,19 +22181,19 @@ else
shift
case $1 in
install|安装)
- send_stats "Dockerをすばやくインストールします"
+ send_stats "Dockerを素早くインストールする"
install_docker
;;
ps|容器)
- send_stats "クイックコンテナ管理"
+ send_stats "迅速なコンテナ管理"
docker_ps
;;
img|镜像)
- send_stats "クイックミラー管理"
+ send_stats "素早い画像管理"
docker_image
;;
*)
- k_info
+ linux_docker
;;
esac
;;
@@ -12878,6 +22213,59 @@ else
fi
;;
+
+ app)
+ shift
+ send_stats "申し込む$@"
+ linux_panel "$@"
+ ;;
+
+ claw|oc|OpenClaw)
+ moltbot_menu
+ ;;
+
+ info)
+ linux_info
+ ;;
+
+ fail2ban|f2b)
+ fail2ban_panel
+ ;;
+
+
+ sshkey)
+
+ shift
+ case "$1" in
+ "" )
+ # sshkey → インタラクティブメニュー
+ send_stats "SSHKey 対話型メニュー"
+ sshkey_panel
+ ;;
+ github )
+ shift
+ send_stats "GitHub から SSH 公開キーをインポートする"
+ fetch_github_ssh_keys "$1"
+ ;;
+ http://*|https://* )
+ send_stats "URLからSSH公開キーをインポート"
+ fetch_remote_ssh_keys "$1"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa* )
+ send_stats "公開鍵を直接インポートする"
+ import_sshkey "$1"
+ ;;
+ * )
+ echo "エラー: 不明なパラメータ '$1'"
+ echo "使用法:"
+ echo "k sshkey は対話型メニューに入ります"
+ echo "k sshkey \"\" SSH 公開キーを直接インポートします"
+ echo "k sshkey URL から SSH 公開キーをインポートします"
+ echo "k sshkey github GitHub から SSH 公開キーをインポートします"
+ ;;
+ esac
+
+ ;;
*)
k_info
;;
diff --git a/kejilion.sh b/kejilion.sh
index 78bf8ceca..cf5f3af28 100644
--- a/kejilion.sh
+++ b/kejilion.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-sh_v="4.0.5"
+sh_v="4.5.2"
gl_hui='\e[37m'
@@ -29,6 +29,8 @@ else
gh_proxy="https://"
fi
+gh_https_url="https://"
+
}
quanju_canshu
@@ -45,6 +47,8 @@ run_command() {
canshu_v6() {
if grep -q '^canshu="V6"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
+ elif grep -q '^canshu="V6"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
fi
}
@@ -52,6 +56,8 @@ canshu_v6() {
CheckFirstRun_true() {
if grep -q '^permission_granted="true"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
+ elif grep -q '^permission_granted="true"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
fi
}
@@ -86,6 +92,8 @@ yinsiyuanquan2() {
if grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
+elif grep -q '^ENABLE_STATS="false"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
fi
}
@@ -102,6 +110,7 @@ sed -i '/^alias k=/d' ~/.profile > /dev/null 2>&1
sed -i '/^alias k=/d' ~/.bash_profile > /dev/null 2>&1
cp -f ./kejilion.sh ~/kejilion.sh > /dev/null 2>&1
cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
@@ -118,7 +127,7 @@ UserLicenseAgreement() {
echo "首次使用脚本,请先阅读并同意用户许可协议。"
echo "用户许可协议: https://blog.kejilion.pro/user-license-agreement/"
echo -e "----------------------"
- read -r -p "是否同意以上条款?(y/n): " user_input
+ read -e -p "是否同意以上条款?(y/n): " user_input
if [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]; then
@@ -154,7 +163,7 @@ public_ip=$(get_public_ip)
isp_info=$(curl -s --max-time 3 http://ipinfo.io/org)
-if echo "$isp_info" | grep -Eiq 'china|mobile|unicom|telecom'; then
+if echo "$isp_info" | grep -Eiq 'CHINANET|mobile|unicom|telecom'; then
ipv4_address=$(get_local_ip)
else
ipv4_address="$public_ip"
@@ -176,7 +185,7 @@ install() {
for package in "$@"; do
if ! command -v "$package" &>/dev/null; then
- echo -e "${gl_huang}正在安装 $package...${gl_bai}"
+ echo -e "${gl_kjlan}正在安装 $package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
dnf install -y epel-release
@@ -213,12 +222,15 @@ install() {
check_disk_space() {
+ local required_gb=$1
+ local path=${2:-/}
- required_gb=$1
- required_space_mb=$((required_gb * 1024))
- available_space_mb=$(df -m / | awk 'NR==2 {print $4}')
+ mkdir -p "$path"
- if [ $available_space_mb -lt $required_space_mb ]; then
+ local required_space_mb=$((required_gb * 1024))
+ local available_space_mb=$(df -m "$path" | awk 'NR==2 {print $4}')
+
+ if [ "$available_space_mb" -lt "$required_space_mb" ]; then
echo -e "${gl_huang}提示: ${gl_bai}磁盘空间不足!"
echo "当前可用空间: $((available_space_mb/1024))G"
echo "最小需求空间: ${required_gb}G"
@@ -230,8 +242,15 @@ check_disk_space() {
}
+
install_dependency() {
+ switch_mirror false false
+ check_port
+ check_swap
+ prefer_ipv4
+ auto_optimize_dns
install wget unzip tar jq grep
+
}
remove() {
@@ -241,7 +260,7 @@ remove() {
fi
for package in "$@"; do
- echo -e "${gl_huang}正在卸载 $package...${gl_bai}"
+ echo -e "${gl_kjlan}正在卸载 $package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf remove -y "$package"
elif command -v yum &>/dev/null; then
@@ -349,22 +368,22 @@ kejilion() {
-check_port() {
- install lsof
+stop_containers_or_kill_process() {
+ local port=$1
+ local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
- stop_containers_or_kill_process() {
- local port=$1
- local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
+ if [ -n "$containers" ]; then
+ docker stop $containers
+ else
+ install lsof
+ for pid in $(lsof -t -i:$port); do
+ kill -9 $pid
+ done
+ fi
+}
- if [ -n "$containers" ]; then
- docker stop $containers
- else
- for pid in $(lsof -t -i:$port); do
- kill -9 $pid
- done
- fi
- }
+check_port() {
stop_containers_or_kill_process 80
stop_containers_or_kill_process 443
}
@@ -377,23 +396,23 @@ if [ "$country" = "CN" ]; then
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
- "https://docker-0.unsee.tech",
- "https://docker.1panel.live",
- "https://registry.dockermirror.com",
- "https://docker.imgdb.de",
- "https://docker.m.daocloud.io",
- "https://hub.firefly.store",
- "https://hub.littlediary.cn",
+ "https://docker.1ms.run",
+ "https://docker.m.ixdev.cn",
"https://hub.rat.dev",
- "https://dhub.kubesre.xyz",
- "https://cjie.eu.org",
- "https://docker.1panelproxy.com",
+ "https://dockerproxy.net",
+ "https://docker-registry.nmqu.com",
+ "https://docker.amingg.com",
"https://docker.hlmirror.com",
- "https://hub.fast360.xyz",
- "https://dockerpull.cn",
- "https://cr.laoyou.ip-ddns.com",
- "https://docker.melikeme.cn",
- "https://docker.kejilion.pro"
+ "https://hub1.nat.tf",
+ "https://hub2.nat.tf",
+ "https://hub3.nat.tf",
+ "https://docker.m.daocloud.io",
+ "https://docker.kejilion.pro",
+ "https://docker.367231.xyz",
+ "https://hub.1panel.dev",
+ "https://dockerproxy.cool",
+ "https://docker.apiba.cn",
+ "https://proxy.vvvv.ee"
]
}
EOF
@@ -407,80 +426,40 @@ restart docker
}
-install_add_docker_guanfang() {
+
+linuxmirrors_install_docker() {
+
local country=$(curl -s ipinfo.io/country)
if [ "$country" = "CN" ]; then
- cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/install && chmod +x install
- sh install --mirror Aliyun
- rm -f install
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source mirrors.huaweicloud.com/docker-ce \
+ --source-registry docker.1ms.run \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
else
- curl -fsSL https://get.docker.com | sh
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source download.docker.com \
+ --source-registry registry.hub.docker.com \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
fi
-install_add_docker_cn
+install_add_docker_cn
}
install_add_docker() {
- echo -e "${gl_huang}正在安装docker环境...${gl_bai}"
- if [ -f /etc/os-release ] && grep -q "Fedora" /etc/os-release; then
- install_add_docker_guanfang
- elif command -v dnf &>/dev/null; then
- dnf update -y
- dnf install -y yum-utils device-mapper-persistent-data lvm2
- rm -f /etc/yum.repos.d/docker*.repo > /dev/null
- country=$(curl -s ipinfo.io/country)
- arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo | tee /etc/yum.repos.d/docker-ce.repo > /dev/null
- else
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo > /dev/null
- fi
- dnf install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
- elif [ -f /etc/os-release ] && grep -q "Kali" /etc/os-release; then
- apt update
- apt upgrade -y
- apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
- rm -f /usr/share/keyrings/docker-archive-keyring.gpg
- local country=$(curl -s ipinfo.io/country)
- local arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- else
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- fi
- apt update
- apt install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
-
- elif command -v apt &>/dev/null || command -v yum &>/dev/null; then
- install_add_docker_guanfang
+ echo -e "${gl_kjlan}正在安装docker环境...${gl_bai}"
+ if command -v apt &>/dev/null || command -v yum &>/dev/null || command -v dnf &>/dev/null; then
+ linuxmirrors_install_docker
else
install docker docker-compose
install_add_docker_cn
@@ -657,7 +636,7 @@ while true; do
send_stats "拉取镜像"
read -e -p "请输入镜像名(多个镜像名请用空格分隔): " imagenames
for name in $imagenames; do
- echo -e "${gl_huang}正在获取镜像: $name${gl_bai}"
+ echo -e "${gl_kjlan}正在获取镜像: $name${gl_bai}"
docker pull $name
done
;;
@@ -665,7 +644,7 @@ while true; do
send_stats "更新镜像"
read -e -p "请输入镜像名(多个镜像名请用空格分隔): " imagenames
for name in $imagenames; do
- echo -e "${gl_huang}正在更新镜像: $name${gl_bai}"
+ echo -e "${gl_kjlan}正在更新镜像: $name${gl_bai}"
docker pull $name
done
;;
@@ -1025,91 +1004,84 @@ disable_ddos_defense() {
# 管理国家IP规则的函数
manage_country_rules() {
local action="$1"
- local country_code="$2"
- local ipset_name="${country_code,,}_block"
- local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
+ shift # 去掉第一个参数,剩下的全是国家代码
install ipset
- case "$action" in
- block)
- # 如果 ipset 不存在则创建
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ for country_code in "$@"; do
+ local ipset_name="${country_code,,}_block"
+ local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
- # 下载 IP 区域文件
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "错误:下载 $country_code 的 IP 区域文件失败"
- exit 1
- fi
+ case "$action" in
+ block)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
- # 将 IP 添加到 ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "错误:下载 $country_code 的 IP 区域文件失败"
+ continue
+ fi
- # 使用 iptables 阻止 IP
- iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- iptables -I OUTPUT -m set --match-set "$ipset_name" dst -j DROP
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- echo "已成功阻止 $country_code 的 IP 地址"
- rm "${country_code,,}.zone"
- ;;
+ iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- allow)
- # 为允许的国家创建 ipset(如果不存在)
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ echo "已成功阻止 $country_code 的 IP 地址"
+ rm "${country_code,,}.zone"
+ ;;
- # 下载 IP 区域文件
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "错误:下载 $country_code 的 IP 区域文件失败"
- exit 1
- fi
+ allow)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
+
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "错误:下载 $country_code 的 IP 区域文件失败"
+ continue
+ fi
- # 删除现有的国家规则
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
- ipset flush "$ipset_name"
+ ipset flush "$ipset_name"
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- # 将 IP 添加到 ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
- # 仅允许指定国家的 IP
- iptables -P INPUT DROP
- iptables -P OUTPUT DROP
- iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- iptables -A OUTPUT -m set --match-set "$ipset_name" dst -j ACCEPT
+ iptables -P INPUT DROP
+ iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- echo "已成功仅允许 $country_code 的 IP 地址"
- rm "${country_code,,}.zone"
- ;;
+ echo "已成功允许 $country_code 的 IP 地址"
+ rm "${country_code,,}.zone"
+ ;;
- unblock)
- # 删除国家的 iptables 规则
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
+ unblock)
+ iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- # 销毁 ipset
- if ipset list "$ipset_name" &> /dev/null; then
- ipset destroy "$ipset_name"
- fi
+ if ipset list "$ipset_name" &> /dev/null; then
+ ipset destroy "$ipset_name"
+ fi
- echo "已成功解除 $country_code 的 IP 地址限制"
- ;;
+ echo "已成功解除 $country_code 的 IP 地址限制"
+ ;;
- *)
- ;;
- esac
+ *)
+ echo "用法: manage_country_rules {block|allow|unblock} "
+ ;;
+ esac
+ done
}
+
+
+
+
+
+
iptables_panel() {
root_use
install iptables
@@ -1223,18 +1195,18 @@ iptables_panel() {
;;
15)
- read -e -p "请输入阻止的国家代码(如 CN, US, JP): " country_code
+ read -e -p "请输入阻止的国家代码(多个国家代码可用空格隔开如 CN US JP): " country_code
manage_country_rules block $country_code
send_stats "允许国家 $country_code 的IP"
;;
16)
- read -e -p "请输入允许的国家代码(如 CN, US, JP): " country_code
+ read -e -p "请输入允许的国家代码(多个国家代码可用空格隔开如 CN US JP): " country_code
manage_country_rules allow $country_code
send_stats "阻止国家 $country_code 的IP"
;;
17)
- read -e -p "请输入清除的国家代码(如 CN, US, JP): " country_code
+ read -e -p "请输入清除的国家代码(多个国家代码可用空格隔开如 CN US JP): " country_code
manage_country_rules unblock $country_code
send_stats "清除国家 $country_code 的IP"
;;
@@ -1252,8 +1224,6 @@ iptables_panel() {
-
-
add_swap() {
local new_swap=$1 # 获取传入的参数
@@ -1342,11 +1312,9 @@ ldnmp_v() {
install_ldnmp_conf() {
# 创建必要的目录和文件
- cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/redis web/log/nginx && touch web/docker-compose.yml
+ cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/stream.d web/redis web/log/nginx web/letsencrypt && touch web/docker-compose.yml
wget -O /home/web/nginx.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default10.conf
- wget -O /home/web/redis/valkey.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/valkey.conf
-
default_server_ssl
@@ -1362,31 +1330,69 @@ install_ldnmp_conf() {
}
+update_docker_compose_with_db_creds() {
+ cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
+ if ! grep -q "letsencrypt" /home/web/docker-compose.yml; then
+ wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
-install_ldnmp() {
+ dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+
+ sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
+ fi
+
+ if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
+ sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ fi
- check_swap
+}
- cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
- if ! grep -q "network_mode" /home/web/docker-compose.yml; then
- wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
- dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
- fi
- if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
- sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- fi
+auto_optimize_dns() {
+ # 获取国家代码(如 CN、US 等)
+ local country=$(curl -s ipinfo.io/country)
+
+ # 根据国家设置 DNS
+ if [ "$country" = "CN" ]; then
+ local dns1_ipv4="223.5.5.5"
+ local dns2_ipv4="183.60.83.19"
+ local dns1_ipv6="2400:3200::1"
+ local dns2_ipv6="2400:da00::6666"
+ else
+ local dns1_ipv4="1.1.1.1"
+ local dns2_ipv4="8.8.8.8"
+ local dns1_ipv6="2606:4700:4700::1111"
+ local dns2_ipv6="2001:4860:4860::8888"
+ fi
+
+ set_dns
+
+
+}
+
+
+prefer_ipv4() {
+grep -q '^precedence ::ffff:0:0/96 100' /etc/gai.conf 2>/dev/null \
+ || echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
+echo "已切换为 IPv4 优先"
+send_stats "已切换为 IPv4 优先"
+}
+
+
+
+
+install_ldnmp() {
+
+ update_docker_compose_with_db_creds
cd /home/web && docker compose up -d
sleep 1
@@ -1395,9 +1401,17 @@ install_ldnmp() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- restart_ldnmp
+
+ # mysql调优
+ wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
+ docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
+ rm -rf /home/custom_mysql_config.cnf
+
+ restart_ldnmp
+ sleep 2
+
clear
echo "LDNMP环境安装完毕"
echo "------------------------"
@@ -1422,13 +1436,12 @@ install_certbot() {
install_ssltls() {
docker stop nginx > /dev/null 2>&1
- check_port > /dev/null 2>&1
cd ~
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ ! -f "$file_path" ]; then
local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
- local ipv6_pattern='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))))$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
if [[ ($yuming =~ $ipv4_pattern || $yuming =~ $ipv6_pattern) ]]; then
mkdir -p /etc/letsencrypt/live/$yuming/
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -1438,7 +1451,7 @@ install_ssltls() {
openssl req -x509 -key /etc/letsencrypt/live/$yuming/privkey.pem -out /etc/letsencrypt/live/$yuming/fullchain.pem -days 5475 -subj "/C=US/ST=State/L=City/O=Organization/OU=Organizational Unit/CN=Common Name"
fi
else
- docker run -it --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
fi
fi
mkdir -p /home/web/certs/
@@ -1475,7 +1488,7 @@ if [ -z "$yuming" ]; then
fi
install_docker
install_certbot
-docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
install_ssltls_text
@@ -1534,12 +1547,69 @@ certs_status() {
echo -e "4. 防火墙限制 ➠ 检查80/443端口是否开放,确保验证可访问"
echo -e "5. 申请次数超限 ➠ Let's Encrypt有每周限额(5次/域名/周)"
echo -e "6. 国内备案限制 ➠ 中国大陆环境请确认域名是否备案"
- break_end
- clear
- echo "请再次尝试部署 $webname"
- add_yuming
- install_ssltls
- certs_status
+ echo "------------------------"
+ echo "1. 重新申请 2. 导入已有证书 0. 退出"
+ echo "------------------------"
+ read -e -p "请输入你的选择: " sub_choice
+ case $sub_choice in
+ 1)
+ send_stats "重新申请"
+ echo "请再次尝试部署 $webname"
+ add_yuming
+ install_ssltls
+ certs_status
+
+ ;;
+ 2)
+ send_stats "导入已有证书"
+
+ # 定义文件路径
+ local cert_file="/home/web/certs/${yuming}_cert.pem"
+ local key_file="/home/web/certs/${yuming}_key.pem"
+
+ mkdir -p /home/web/certs
+
+ # 1. 输入证书 (ECC 和 RSA 证书开头都是 BEGIN CERTIFICATE)
+ echo "请粘贴 证书 (CRT/PEM) 内容 (按两次回车结束):"
+ local cert_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$cert_content" == *"-----BEGIN"* ]] && break
+ cert_content+="${line}"$'\n'
+ done
+
+ # 2. 输入私钥 (兼容 RSA, ECC, PKCS#8)
+ echo "请粘贴 证书私钥 (Private Key) 内容 (按两次回车结束):"
+ local key_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$key_content" == *"-----BEGIN"* ]] && break
+ key_content+="${line}"$'\n'
+ done
+
+ # 3. 智能校验
+ # 只要包含 "BEGIN CERTIFICATE" 和 "PRIVATE KEY" 即可通过
+ if [[ "$cert_content" == *"-----BEGIN CERTIFICATE-----"* && "$key_content" == *"PRIVATE KEY-----"* ]]; then
+ echo -n "$cert_content" > "$cert_file"
+ echo -n "$key_content" > "$key_file"
+
+ chmod 644 "$cert_file"
+ chmod 600 "$key_file"
+
+ # 识别当前证书类型并显示
+ if [[ "$key_content" == *"EC PRIVATE KEY"* ]]; then
+ echo "检测到 ECC 证书已成功保存。"
+ else
+ echo "检测到 RSA 证书已成功保存。"
+ fi
+ auth_method="ssl_imported"
+ else
+ echo "错误:无效的证书或私钥格式!"
+ certs_status
+ fi
+ ;;
+ *)
+ exit
+ ;;
+ esac
fi
}
@@ -1561,6 +1631,40 @@ add_yuming() {
}
+check_ip_and_get_access_port() {
+ local yuming="$1"
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ read -e -p "请输入访问/监听端口,回车默认使用 80: " access_port
+ access_port=${access_port:-80}
+ fi
+}
+
+
+
+update_nginx_listen_port() {
+ local yuming="$1"
+ local access_port="$2"
+ local conf="/home/web/conf.d/${yuming}.conf"
+
+ # 如果 access_port 为空,则跳过
+ [ -z "$access_port" ] && return 0
+
+ # 删除所有 listen 行
+ sed -i '/^[[:space:]]*listen[[:space:]]\+/d' "$conf"
+
+ # 在 server { 后插入新的 listen
+ sed -i "/server {/a\\
+ listen ${access_port};\\
+ listen [::]:${access_port};
+" "$conf"
+}
+
+
+
add_db() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbname="${dbname}"
@@ -1571,28 +1675,8 @@ add_db() {
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "CREATE DATABASE $dbname; GRANT ALL PRIVILEGES ON $dbname.* TO \"$dbuse\"@\"%\";"
}
-reverse_proxy() {
- ip_address
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
- sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$ipv4_address/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$duankou|g" /home/web/conf.d/$yuming.conf
- nginx_http_on
- docker exec nginx nginx -s reload
-}
-
-
-restart_redis() {
- rm -rf /home/web/redis/*
- docker exec redis redis-cli FLUSHALL > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory 1gb > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory-policy allkeys-lru > /dev/null 2>&1
-}
-
-
restart_ldnmp() {
- restart_redis
docker exec nginx chown -R nginx:nginx /var/www/html > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/proxy > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/fastcgi > /dev/null 2>&1
@@ -1600,7 +1684,8 @@ restart_ldnmp() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi > /dev/null 2>&1
docker exec php chown -R www-data:www-data /var/www/html > /dev/null 2>&1
docker exec php74 chown -R www-data:www-data /var/www/html > /dev/null 2>&1
- cd /home/web && docker compose restart nginx php php74
+ cd /home/web && docker compose restart
+
}
@@ -1635,7 +1720,7 @@ phpmyadmin_upgrade() {
cd /home/web/
docker rm -f $ldnmp_pods > /dev/null 2>&1
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
- curl -sS -O https://raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
docker compose -f docker-compose.phpmyadmin.yml up -d
clear
ip_address
@@ -1694,7 +1779,6 @@ web_cache() {
send_stats "清理站点缓存"
cf_purge_cache
cd /home/web && docker compose restart
- restart_redis
}
@@ -1776,7 +1860,7 @@ check_waf_status() {
check_cf_mode() {
- if [ -f "/path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf" ]; then
+ if [ -f "/etc/fail2ban/action.d/cloudflare-docker.conf" ]; then
CFmessage=" cf模式已开启"
else
CFmessage=""
@@ -1847,6 +1931,43 @@ patch_wp_debug() {
}
+
+
+patch_wp_url() {
+ local HOME_URL="$1"
+ local SITE_URL="$2"
+ local TARGET_DIR="/home/web/html"
+
+ find "$TARGET_DIR" -type f -name "wp-config-sample.php" | while read -r FILE; do
+ # 删除旧定义
+ sed -i "/define(['\"]WP_HOME['\"].*/d" "$FILE"
+ sed -i "/define(['\"]WP_SITEURL['\"].*/d" "$FILE"
+
+ # 生成插入内容
+ INSERT="
+define('WP_HOME', '$HOME_URL');
+define('WP_SITEURL', '$SITE_URL');
+"
+
+ # 插入到 “Happy publishing” 之前
+ awk -v insert="$INSERT" '
+ /Happy publishing/ {
+ print insert
+ }
+ { print }
+ ' "$FILE" > "$FILE.tmp" && mv -f "$FILE.tmp" "$FILE"
+
+ echo "[+] Updated WP_HOME and WP_SITEURL in $FILE"
+ done
+}
+
+
+
+
+
+
+
+
nginx_br() {
local mode=$1
@@ -1990,19 +2111,11 @@ nginx_gzip() {
web_security() {
send_stats "LDNMP环境防御"
while true; do
+ check_f2b_status
check_waf_status
check_cf_mode
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "服务器网站防御程序 ${check_docker}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
+ echo -e "服务器网站防御程序 ${check_f2b_status}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
echo "------------------------"
echo "1. 安装防御程序"
echo "------------------------"
@@ -2024,11 +2137,16 @@ web_security() {
case $sub_choice in
1)
f2b_install_sshd
- cd /path/to/fail2ban/config/fail2ban/filter.d
+ cd /etc/fail2ban/filter.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/fail2ban-nginx-cc.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-418.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-deny.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-unauthorized.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-bad-request.conf
+
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- sed -i "/cloudflare/d" /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ sed -i "/cloudflare/d" /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
;;
5)
@@ -2042,56 +2160,57 @@ web_security() {
local xxx="fail2ban-nginx-cc"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-418"
+ local xxx="nginx-418"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-bad-request"
+ local xxx="nginx-bad-request"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-badbots"
+ local xxx="nginx-badbots"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-botsearch"
+ local xxx="nginx-botsearch"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-deny"
+ local xxx="nginx-deny"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-http-auth"
+ local xxx="nginx-http-auth"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-unauthorized"
+ local xxx="nginx-unauthorized"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-php-url-fopen"
+ local xxx="php-url-fopen"
f2b_status_xxx
echo "------------------------"
;;
7)
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
;;
8)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
+ tail -f /var/log/fail2ban.log
;;
9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
+ remove fail2ban
+ rm -rf /etc/fail2ban
crontab -l | grep -v "CF-Under-Attack.sh" | crontab - 2>/dev/null
echo "Fail2Ban防御程序已卸载"
+ break
;;
11)
install nano
- nano /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ nano /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
break
;;
12)
- docker exec -it fail2ban fail2ban-client unban --all
+ fail2ban-client unban --all
;;
21)
@@ -2104,14 +2223,14 @@ web_security() {
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default11.conf
docker exec nginx nginx -s reload
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- cd /path/to/fail2ban/config/fail2ban/action.d
+ cd /etc/fail2ban/action.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/cloudflare-docker.conf
- sed -i "s/kejilion@outlook.com/$cfuser/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
- sed -i "s/APIKEY00000/$cftoken/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/kejilion@outlook.com/$cfuser/g" /etc/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/APIKEY00000/$cftoken/g" /etc/fail2ban/action.d/cloudflare-docker.conf
f2b_status
echo "已配置cloudflare模式,可在cf后台,站点-安全性-事件中查看拦截记录"
@@ -2176,26 +2295,23 @@ web_security() {
break
;;
esac
- fi
break_end
done
}
-check_nginx_mode() {
+check_ldnmp_mode() {
-CONFIG_FILE="/home/web/nginx.conf"
+ local MYSQL_CONTAINER="mysql"
+ local MYSQL_CONF="/etc/mysql/conf.d/custom_mysql_config.cnf"
-# 获取当前的 worker_processes 设置值
-current_value=$(grep -E '^\s*worker_processes\s+[0-9]+;' "$CONFIG_FILE" | awk '{print $2}' | tr -d ';')
-
-# 根据值设置模式信息
-if [ "$current_value" = "8" ]; then
- mode_info=" 高性能模式"
-else
- mode_info=" 标准模式"
-fi
+ # 检查 MySQL 配置文件中是否包含 4096M
+ if docker exec "$MYSQL_CONTAINER" grep -q "4096M" "$MYSQL_CONF" 2>/dev/null; then
+ mode_info=" 高性能模式"
+ else
+ mode_info=" 标准模式"
+ fi
@@ -2204,7 +2320,7 @@ fi
check_nginx_compression() {
- CONFIG_FILE="/home/web/nginx.conf"
+ local CONFIG_FILE="/home/web/nginx.conf"
# 检查 zstd 是否开启且未被注释(整行以 zstd on; 开头)
if grep -qE '^\s*zstd\s+on;' "$CONFIG_FILE"; then
@@ -2233,7 +2349,7 @@ check_nginx_compression() {
web_optimization() {
while true; do
- check_nginx_mode
+ check_ldnmp_mode
check_nginx_compression
clear
send_stats "优化LDNMP环境"
@@ -2252,9 +2368,11 @@ web_optimization() {
1)
send_stats "站点标准模式"
- # nginx调优
- sed -i 's/worker_connections.*/worker_connections 10240;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 4;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((1024 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
+
# php调优
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2282,7 +2400,6 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_balanced
@@ -2293,8 +2410,10 @@ web_optimization() {
send_stats "站点高性能模式"
# nginx调优
- sed -i 's/worker_connections.*/worker_connections 20480;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 8;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((2048 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
# php调优
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2321,7 +2440,6 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_web_server
echo "LDNMP环境已设置成 高性能模式"
@@ -2372,7 +2490,7 @@ web_optimization() {
check_docker_app() {
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name" ; then
check_docker="${gl_lv}已安装${gl_bai}"
else
check_docker="${gl_hui}未安装${gl_bai}"
@@ -2383,7 +2501,7 @@ check_docker_app() {
# check_docker_app() {
-# if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+# if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
# check_docker="${gl_lv}已安装${gl_bai}"
# else
# check_docker="${gl_hui}未安装${gl_bai}"
@@ -2423,54 +2541,61 @@ done
check_docker_image_update() {
-
local container_name=$1
+ update_status=""
- local country=$(curl -s ipinfo.io/country)
- if [[ "$country" == "CN" ]]; then
- update_status=""
- return
- fi
+ # 1. 区域检查
+ local country=$(curl -s --max-time 2 ipinfo.io/country)
+ [[ "$country" == "CN" ]] && return
- # 获取容器的创建时间和镜像名称
+ # 2. 获取本地镜像信息
local container_info=$(docker inspect --format='{{.Created}},{{.Config.Image}}' "$container_name" 2>/dev/null)
- local container_created=$(echo "$container_info" | cut -d',' -f1)
- local image_name=$(echo "$container_info" | cut -d',' -f2)
+ [[ -z "$container_info" ]] && return
- # 提取镜像仓库和标签
- local image_repo=${image_name%%:*}
- local image_tag=${image_name##*:}
-
- # 默认标签为 latest
- [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
-
- # 添加对官方镜像的支持
- [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
-
- # 从 Docker Hub API 获取镜像发布时间
- local hub_info=$(curl -s "https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag")
- local last_updated=$(echo "$hub_info" | jq -r '.last_updated' 2>/dev/null)
+ local container_created=$(echo "$container_info" | cut -d',' -f1)
+ local full_image_name=$(echo "$container_info" | cut -d',' -f2)
+ local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
+
+ # 3. 智能路由判断
+ if [[ "$full_image_name" == ghcr.io* ]]; then
+ # --- 场景 A: 镜像在 GitHub (ghcr.io) ---
+ # 提取仓库路径,例如 ghcr.io/onexru/oneimg -> onexru/oneimg
+ local repo_path=$(echo "$full_image_name" | sed 's/ghcr.io\///' | cut -d':' -f1)
+ # 注意:ghcr.io 的 API 比较复杂,通常最快的方法是查 GitHub Repo 的 Release
+ local api_url="https://api.github.com/repos/$repo_path/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
+
+ elif [[ "$full_image_name" == *"oneimg"* ]]; then
+ # --- 场景 B: 特殊指定 (即便在 Docker Hub,也想通过 GitHub Release 判断) ---
+ local api_url="https://api.github.com/repos/onexru/oneimg/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
- # 验证获取的时间
- if [[ -n "$last_updated" && "$last_updated" != "null" ]]; then
- local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
- local last_updated_ts=$(date -d "$last_updated" +%s 2>/dev/null)
+ else
+ # --- 场景 C: 标准 Docker Hub ---
+ local image_repo=${full_image_name%%:*}
+ local image_tag=${full_image_name##*:}
+ [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+
+ local api_url="https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag"
+ local remote_date=$(curl -s "$api_url" | jq -r '.last_updated' 2>/dev/null)
+ fi
- # 比较时间戳
- if [[ $container_created_ts -lt $last_updated_ts ]]; then
+ # 4. 时间戳对比
+ if [[ -n "$remote_date" && "$remote_date" != "null" ]]; then
+ local remote_ts=$(date -d "$remote_date" +%s 2>/dev/null)
+ if [[ $container_created_ts -lt $remote_ts ]]; then
update_status="${gl_huang}发现新版本!${gl_bai}"
- else
- update_status=""
fi
- else
- update_status=""
fi
-
}
+
+
+
block_container_port() {
local container_name_or_id=$1
local allowed_ip=$2
@@ -2705,16 +2830,33 @@ clear_host_port_rules() {
setup_docker_dir() {
- mkdir -p /home/docker/ 2>/dev/null
+ mkdir -p /home /home/docker 2>/dev/null
+
if [ -d "/vol1/1000/" ] && [ ! -d "/vol1/1000/docker" ]; then
cp -f /home/docker /home/docker1 2>/dev/null
rm -rf /home/docker 2>/dev/null
mkdir -p /vol1/1000/docker 2>/dev/null
ln -s /vol1/1000/docker /home/docker 2>/dev/null
fi
+
+ if [ -d "/volume1/" ] && [ ! -d "/volume1/docker" ]; then
+ cp -f /home/docker /home/docker1 2>/dev/null
+ rm -rf /home/docker 2>/dev/null
+ mkdir -p /volume1/docker 2>/dev/null
+ ln -s /volume1/docker /home/docker 2>/dev/null
+ fi
+
+
}
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "${app_id}" /home/docker/appno.txt || echo "${app_id}" >> /home/docker/appno.txt
+
+}
+
docker_app() {
@@ -2723,11 +2865,11 @@ send_stats "${docker_name}管理"
while true; do
clear
check_docker_app
- check_docker_image_update $docker_name
+ check_docker_image_update "${docker_app_service:-$docker_name}"
echo -e "$docker_name $check_docker $update_status"
echo "$docker_describe"
echo "$docker_url"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
@@ -2748,18 +2890,27 @@ while true; do
read -e -p "请输入你的选择: " choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+ while true; do
+ read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}错误: ${gl_bai}端口 $app_port 已被占用,请更换一个端口"
+ send_stats "应用端口已被占用"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
install jq
install_docker
docker_rum
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
clear
echo "$docker_name 已经安装完成"
@@ -2773,8 +2924,8 @@ while true; do
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
docker_rum
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
clear
echo "$docker_name 已经安装完成"
@@ -2789,8 +2940,8 @@ while true; do
docker rmi -f "$docker_img"
rm -rf "/home/docker/$docker_name"
rm -f /home/docker/${docker_name}_port.conf
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "应用已卸载"
send_stats "卸载$docker_name"
;;
@@ -2840,9 +2991,10 @@ docker_app_plus() {
echo -e "$app_name $check_docker $update_status"
echo "$app_text"
echo "$app_url"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_check_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
- local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
+ local docker_port=$(docker port "$docker_check_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
fi
@@ -2861,36 +3013,53 @@ docker_app_plus() {
read -e -p "输入你的选择: " choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+
+ while true; do
+ read -e -p "输入应用对外服务端口,回车默认使用${docker_port}端口: " app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}错误: ${gl_bai}端口 $app_port 已被占用,请更换一个端口"
+ send_stats "应用端口已被占用"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
+
install jq
install_docker
docker_app_install
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
+ send_stats "$app_name 安装"
;;
+
2)
docker_app_update
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ add_app_id
+ send_stats "$app_name 更新"
;;
+
3)
docker_app_uninstall
rm -f /home/docker/${docker_name}_port.conf
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "$app_name 卸载"
;;
+
5)
echo "${docker_name}域名访问设置"
send_stats "${docker_name}域名访问设置"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
+
;;
6)
echo "域名格式 example.com 不带https://"
@@ -2902,7 +3071,8 @@ docker_app_plus() {
;;
8)
send_stats "阻止IP访问 ${docker_name}"
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
;;
*)
break
@@ -2940,7 +3110,7 @@ docker network create $NETWORK_NAME
docker run -d \
--name=node-exporter \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
prom/node-exporter
# Run Prometheus container
@@ -2949,7 +3119,7 @@ docker run -d \
-v $PROMETHEUS_DIR/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $PROMETHEUS_DIR/data:/prometheus \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
--user 0:0 \
prom/prometheus:latest
@@ -2959,7 +3129,7 @@ docker run -d \
-p ${docker_port}:3000 \
-v $GRAFANA_DIR:/var/lib/grafana \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
grafana/grafana:latest
}
@@ -3005,55 +3175,44 @@ tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
f2b_status() {
- docker exec -it fail2ban fail2ban-client reload
+ fail2ban-client reload
sleep 3
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
}
f2b_status_xxx() {
- docker exec -it fail2ban fail2ban-client status $xxx
+ fail2ban-client status $xxx
}
-f2b_install_sshd() {
-
- docker run -d \
- --name=fail2ban \
- --net=host \
- --cap-add=NET_ADMIN \
- --cap-add=NET_RAW \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e VERBOSITY=-vv \
- -v /path/to/fail2ban/config:/config \
- -v /var/log:/var/log:ro \
- -v /home/web/log/nginx/:/remotelogs/nginx:ro \
- --restart unless-stopped \
- lscr.io/linuxserver/fail2ban:latest
-
- sleep 3
- if grep -q 'Alpine' /etc/issue; then
- cd /path/to/fail2ban/config/fail2ban/filter.d
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd.conf
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd-ddos.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-ssh.conf
- elif command -v dnf &>/dev/null; then
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+check_f2b_status() {
+ if command -v fail2ban-client >/dev/null 2>&1; then
+ check_f2b_status="${gl_lv}已安装${gl_bai}"
else
- install rsyslog
- systemctl start rsyslog
- systemctl enable rsyslog
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/linux-ssh.conf
- systemctl restart rsyslog
+ check_f2b_status="${gl_hui}未安装${gl_bai}"
fi
-
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf
}
-f2b_sshd() {
+f2b_install_sshd() {
+
+ docker rm -f fail2ban >/dev/null 2>&1
+ install fail2ban
+ start fail2ban
+ enable fail2ban
+
+ if command -v dnf &>/dev/null; then
+ cd /etc/fail2ban/jail.d/
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+ fi
+
+ if command -v apt &>/dev/null; then
+ install rsyslog
+ systemctl start rsyslog
+ systemctl enable rsyslog
+ fi
+
+}
+
+f2b_sshd() {
if grep -q 'Alpine' /etc/issue; then
xxx=alpine-sshd
f2b_status_xxx
@@ -3063,6 +3222,80 @@ f2b_sshd() {
fi
}
+# 基础参数配置:封禁时长(bantime)、时间窗口(findtime)、重试次数(maxretry)
+# 说明:
+# - 优先写入 /etc/fail2ban/jail.d/sshd.local(覆盖默认 jail 配置,升级不易丢)
+# - 若是 Alpine 且 jail 名称不同,依然写 sshd.local;Fail2Ban 会按 jail 名称匹配
+f2b_basic_config() {
+ root_use
+ install nano
+
+ if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo -e "${gl_hui}未检测到 fail2ban-client,请先安装 fail2ban。${gl_bai}"
+ return
+ fi
+
+ local jail_name="sshd"
+ if grep -qi 'Alpine' /etc/issue 2>/dev/null; then
+ # Alpine 默认 jail 通常为 sshd;仅当检测到自定义 alpine-sshd 规则时才切换
+ if [ -f /etc/fail2ban/filter.d/alpine-sshd.conf ] || [ -f /etc/fail2ban/jail.d/alpine-ssh.conf ] || [ -f /etc/fail2ban/jail.d/alpine-sshd.local ]; then
+ jail_name="alpine-sshd"
+ fi
+ fi
+
+ echo "即将配置 SSH jail:$jail_name"
+ read -e -p "封禁时长 bantime (秒/分钟/小时,如 3600 或 1h) [默认 1h]: " bantime
+ read -e -p "时间窗口 findtime (秒/分钟/小时,如 600 或 10m) [默认 10m]: " findtime
+ read -e -p "重试次数 maxretry (整数) [默认 5]: " maxretry
+
+ bantime=${bantime:-1h}
+ findtime=${findtime:-10m}
+ maxretry=${maxretry:-5}
+
+ mkdir -p /etc/fail2ban/jail.d
+ cat > /etc/fail2ban/jail.d/sshd.local <> /etc/fail2ban/jail.d/sshd.local
+ fi
+ fi
+
+ echo -e "${gl_lv}已写入配置${gl_bai}: /etc/fail2ban/jail.d/sshd.local"
+ fail2ban-client reload >/dev/null 2>&1 || true
+ sleep 2
+ fail2ban-client status $jail_name || true
+}
+
+# 直接打开主配置/覆盖配置编辑(nano)
+# 优先编辑 /etc/fail2ban/jail.d/sshd.local(更安全),若不存在则创建
+f2b_edit_config() {
+ root_use
+ install nano
+
+ if [ ! -d /etc/fail2ban ]; then
+ echo -e "${gl_hui}/etc/fail2ban 不存在,请先安装 fail2ban。${gl_bai}"
+ return
+ fi
+
+ mkdir -p /etc/fail2ban/jail.d
+ local cfg="/etc/fail2ban/jail.d/sshd.local"
+ [ -f "$cfg" ] || printf "[sshd]\n# bantime/findtime/maxretry\n" > "$cfg"
+
+ nano "$cfg"
+ echo -e "${gl_lv}已保存${gl_bai},正在 reload fail2ban..."
+ fail2ban-client reload >/dev/null 2>&1 || true
+}
@@ -3133,8 +3366,7 @@ send_stats "安装LDNMP环境"
root_use
clear
echo -e "${gl_huang}LDNMP环境未安装,开始安装LDNMP环境...${gl_bai}"
-check_disk_space 3
-check_port
+check_disk_space 3 /home
install_dependency
install_docker
install_certbot
@@ -3150,8 +3382,6 @@ send_stats "安装nginx环境"
root_use
clear
echo -e "${gl_huang}nginx未安装,开始安装nginx环境...${gl_bai}"
-check_disk_space 1
-check_port
install_dependency
install_docker
install_certbot
@@ -3201,10 +3431,21 @@ ldnmp_web_on() {
}
nginx_web_on() {
- clear
- echo "您的 $webname 搭建好了!"
- echo "https://$yuming"
+ clear
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ echo "您的 $webname 搭建好了!"
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ mv /home/web/conf.d/"$yuming".conf /home/web/conf.d/"${yuming}_${access_port}".conf
+ echo "http://$yuming:$access_port"
+ elif grep -q '^[[:space:]]*#.*if (\$scheme = http)' "/home/web/conf.d/"$yuming".conf"; then
+ echo "http://$yuming"
+ else
+ echo "https://$yuming"
+ fi
}
@@ -3221,40 +3462,40 @@ ldnmp_wp() {
fi
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/wordpress.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
nginx_http_on
+
cd /home/web/html
mkdir $yuming
cd $yuming
wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/refs/heads/main/wp-latest.zip
- # wget -O latest.zip https://cn.wordpress.org/latest-zh_CN.zip
- # wget -O latest.zip https://wordpress.org/latest.zip
unzip latest.zip
rm latest.zip
- echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
+ echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379'); define('WP_REDIS_MAXTTL', 86400); define('WP_CACHE_KEY_SALT', '${yuming}_');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|database_name_here|$dbname|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|username_here|$dbuse|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|password_here|$dbusepasswd|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|localhost|mysql|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
+ patch_wp_url "https://$yuming" "https://$yuming"
cp /home/web/html/$yuming/wordpress/wp-config-sample.php /home/web/html/$yuming/wordpress/wp-config.php
+
restart_ldnmp
nginx_web_on
-# echo "数据库名: $dbname"
-# echo "用户名: $dbuse"
-# echo "密码: $dbusepasswd"
-# echo "数据库地址: mysql"
-# echo "表前缀: wp_"
}
+
ldnmp_Proxy() {
clear
webname="反向代理-IP+端口"
@@ -3267,21 +3508,43 @@ ldnmp_Proxy() {
if [ -z "$yuming" ]; then
add_yuming
fi
+
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy" ]; then
- read -e -p "请输入你的反代IP: " reverseproxy
+ read -e -p "请输入你的反代IP (回车默认本机IP 127.0.0.1): " reverseproxy
+ reverseproxy=${reverseproxy:-127.0.0.1}
fi
if [ -z "$port" ]; then
read -e -p "请输入你的反代端口: " port
fi
nginx_install_status
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
+ wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/backend_$backend/g" /home/web/conf.d/"$yuming".conf
+
+
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$reverseproxy/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$port|g" /home/web/conf.d/$yuming.conf
+
+ reverseproxy_port="$reverseproxy:$port"
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i '/remote_addr/d' /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3292,8 +3555,6 @@ ldnmp_Proxy() {
ldnmp_Proxy_backend() {
clear
webname="反向代理-负载均衡"
- yuming="${1:-}"
- reverseproxy_port="${2:-}"
send_stats "安装$webname"
echo "开始部署 $webname"
@@ -3301,14 +3562,17 @@ ldnmp_Proxy_backend() {
add_yuming
fi
- # 获取用户输入的多个IP:端口(用空格分隔)
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy_port" ]; then
read -e -p "请输入你的多个反代IP+端口用空格隔开(例如 127.0.0.1:3000 127.0.0.1:3002): " reverseproxy_port
fi
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
@@ -3318,15 +3582,16 @@ ldnmp_Proxy_backend() {
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- # 动态生成 upstream 配置
upstream_servers=""
for server in $reverseproxy_port; do
upstream_servers="$upstream_servers server $server;\n"
done
- # 替换模板中的占位符
sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3334,6 +3599,200 @@ ldnmp_Proxy_backend() {
+
+
+
+list_stream_services() {
+
+ STREAM_DIR="/home/web/stream.d"
+ printf "%-25s %-18s %-25s %-20s\n" "服务名" "通信类型" "本机地址" "后端地址"
+
+ if [ -z "$(ls -A "$STREAM_DIR")" ]; then
+ return
+ fi
+
+ for conf in "$STREAM_DIR"/*; do
+ # 服务名取文件名
+ service_name=$(basename "$conf" .conf)
+
+ # 获取 upstream 块中的 server 后端 IP:端口
+ backend=$(grep -Po '(?<=server )[^;]+' "$conf" | head -n1)
+
+ # 获取 listen 端口
+ listen_port=$(grep -Po '(?<=listen )[^;]+' "$conf" | head -n1)
+
+ # 默认本地 IP
+ ip_address
+ local_ip="$ipv4_address"
+
+ # 获取通信类型,优先从文件名后缀或内容判断
+ if grep -qi 'udp;' "$conf"; then
+ proto="udp"
+ else
+ proto="tcp"
+ fi
+
+ # 拼接监听 IP:端口
+ local_addr="$local_ip:$listen_port"
+
+ printf "%-22s %-14s %-21s %-20s\n" "$service_name" "$proto" "$local_addr" "$backend"
+ done
+}
+
+
+
+
+
+
+
+
+
+stream_panel() {
+ send_stats "Stream四层代理"
+ local app_id="104"
+ local docker_name="nginx"
+
+ while true; do
+ clear
+ check_docker_app
+ check_docker_image_update $docker_name
+ echo -e "Stream四层代理转发工具 $check_docker $update_status"
+ echo "NGINX Stream 是 NGINX 的 TCP/UDP 代理模块,用于实现高性能的 传输层流量转发和负载均衡。"
+ echo "------------------------"
+ if [ -d "/home/web/stream.d" ]; then
+ list_stream_services
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 安装 2. 更新 3. 卸载"
+ echo "------------------------"
+ echo "4. 添加转发服务 5. 修改转发服务 6. 删除转发服务"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+ case $choice in
+ 1)
+ nginx_install_status
+ add_app_id
+ send_stats "安装Stream四层代理"
+ ;;
+ 2)
+ update_docker_compose_with_db_creds
+ nginx_upgrade
+ add_app_id
+ send_stats "更新Stream四层代理"
+ ;;
+ 3)
+ read -e -p "确定要删除 nginx 容器吗?这可能会影响网站功能!(y/N): " confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ docker rm -f nginx
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "更新Stream四层代理"
+ echo "nginx 容器已删除。"
+ else
+ echo "操作已取消。"
+ fi
+
+ ;;
+
+ 4)
+ ldnmp_Proxy_backend_stream
+ add_app_id
+ send_stats "添加四层代理"
+ ;;
+ 5)
+ send_stats "编辑转发配置"
+ read -e -p "请输入你要编辑的服务名: " stream_name
+ install nano
+ nano /home/web/stream.d/$stream_name.conf
+ docker restart nginx
+ send_stats "修改四层代理"
+ ;;
+ 6)
+ send_stats "删除转发配置"
+ read -e -p "请输入你要删除的服务名: " stream_name
+ rm /home/web/stream.d/$stream_name.conf > /dev/null 2>&1
+ docker restart nginx
+ send_stats "删除四层代理"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ break_end
+ done
+}
+
+
+
+ldnmp_Proxy_backend_stream() {
+ clear
+ webname="Stream四层代理-负载均衡"
+
+ send_stats "安装$webname"
+ echo "开始部署 $webname"
+
+ # 获取代理名称
+ read -erp "请输入代理转发名称 (如 mysql_proxy): " proxy_name
+ if [ -z "$proxy_name" ]; then
+ echo "名称不能为空"; return 1
+ fi
+
+ # 获取监听端口
+ read -erp "请输入本机监听端口 (如 3306): " listen_port
+ if ! [[ "$listen_port" =~ ^[0-9]+$ ]]; then
+ echo "端口必须是数字"; return 1
+ fi
+
+ echo "请选择协议类型:"
+ echo "1. TCP 2. UDP"
+ read -erp "请输入序号 [1-2]: " proto_choice
+
+ case "$proto_choice" in
+ 1) proto="tcp"; listen_suffix="" ;;
+ 2) proto="udp"; listen_suffix=" udp" ;;
+ *) echo "无效选择"; return 1 ;;
+ esac
+
+ read -e -p "请输入你的一个或者多个后端IP+端口用空格隔开(例如 10.13.0.2:3306 10.13.0.3:3306): " reverseproxy_port
+
+ nginx_install_status
+ cd /home && mkdir -p web/stream.d
+ grep -q '^[[:space:]]*stream[[:space:]]*{' /home/web/nginx.conf || echo -e '\nstream {\n include /etc/nginx/stream.d/*.conf;\n}' | tee -a /home/web/nginx.conf
+ wget -O /home/web/stream.d/$proxy_name.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend-stream.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/${proxy_name}_${backend}/g" /home/web/stream.d/"$proxy_name".conf
+ sed -i "s|listen 80|listen $listen_port $listen_suffix|g" /home/web/stream.d/$proxy_name.conf
+ sed -i "s|listen \[::\]:|listen [::]:${listen_port} ${listen_suffix}|g" "/home/web/stream.d/${proxy_name}.conf"
+
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 动态添加/$upstream_servers/g" /home/web/stream.d/$proxy_name.conf
+
+ docker exec nginx nginx -s reload
+ clear
+ echo "您的 $webname 搭建好了!"
+ echo "------------------------"
+ echo "访问地址:"
+ ip_address
+ if [ -n "$ipv4_address" ]; then
+ echo "$ipv4_address:${listen_port}"
+ fi
+ if [ -n "$ipv6_address" ]; then
+ echo "$ipv6_address:${listen_port}"
+ fi
+ echo ""
+}
+
+
+
+
+
find_container_by_host_port() {
port="$1"
docker_name=$(docker ps --format '{{.ID}} {{.Names}}' | while read id name; do
@@ -3351,11 +3810,11 @@ ldnmp_web_status() {
root_use
while true; do
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
- local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+ local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
- local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+ local db_output="${gl_lv}${db_count}${gl_bai}"
clear
send_stats "LDNMP站点管理"
@@ -3363,8 +3822,7 @@ ldnmp_web_status() {
echo "------------------------"
ldnmp_v
- # ls -t /home/web/conf.d | sed 's/\.[^.]*$//'
- echo -e "${output} 证书到期时间"
+ echo -e "站点: ${output} 证书到期时间"
echo -e "------------------------"
for cert_file in /home/web/certs/*_cert.pem; do
local domain=$(basename "$cert_file" | sed 's/_cert.pem//')
@@ -3375,9 +3833,28 @@ ldnmp_web_status() {
fi
done
+ for conf_file in /home/web/conf.d/*_*.conf; do
+ [ -e "$conf_file" ] || continue
+ basename "$conf_file" .conf
+ done
+
+ for conf_file in /home/web/conf.d/*.conf; do
+ [ -e "$conf_file" ] || continue
+
+ filename=$(basename "$conf_file")
+
+ if [ "$filename" = "map.conf" ] || [ "$filename" = "default.conf" ]; then
+ continue
+ fi
+
+ if ! grep -q "ssl_certificate" "$conf_file"; then
+ basename "$conf_file" .conf
+ fi
+ done
+
echo "------------------------"
echo ""
- echo -e "${db_output}"
+ echo -e "数据库: ${db_output}"
echo -e "------------------------"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys"
@@ -3391,11 +3868,11 @@ ldnmp_web_status() {
echo ""
echo "操作"
echo "------------------------"
- echo "1. 申请/更新域名证书 2. 更换站点域名"
+ echo "1. 申请/更新域名证书 2. 克隆站点域名"
echo "3. 清理站点缓存 4. 创建关联站点"
echo "5. 查看访问日志 6. 查看错误日志"
echo "7. 编辑全局配置 8. 编辑站点配置"
- echo "9. 管理站点数据库 10. 查看站点分析报告"
+ echo "9. 管理站点数据库 10. 查看站点分析报告"
echo "------------------------"
echo "20. 删除指定站点数据"
echo "------------------------"
@@ -3407,30 +3884,26 @@ ldnmp_web_status() {
send_stats "申请域名证书"
read -e -p "请输入你的域名: " yuming
install_certbot
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
;;
2)
- send_stats "更换站点域名"
- echo -e "${gl_hong}强烈建议: ${gl_bai}先备份好全站数据再更换站点域名!"
+ send_stats "克隆站点域名"
read -e -p "请输入旧域名: " oddyuming
read -e -p "请输入新域名: " yuming
install_certbot
install_ssltls
certs_status
- # mysql替换
- add_db
+ add_db
local odd_dbname=$(echo "$oddyuming" | sed -e 's/[^A-Za-z0-9]/_/g')
local odd_dbname="${odd_dbname}"
docker exec mysql mysqldump -u root -p"$dbrootpasswd" $odd_dbname | docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname
- docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE $odd_dbname;"
-
local tables=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -D $dbname -e "SHOW TABLES;" | awk '{ if (NR>1) print $1 }')
for table in $tables; do
@@ -3441,18 +3914,15 @@ ldnmp_web_status() {
done
# 网站目录替换
- mv /home/web/html/$oddyuming /home/web/html/$yuming
+ cp -r /home/web/html/$oddyuming /home/web/html/$yuming
find /home/web/html/$yuming -type f -exec sed -i "s/$odd_dbname/$dbname/g" {} +
find /home/web/html/$yuming -type f -exec sed -i "s/$oddyuming/$yuming/g" {} +
- mv /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
+ cp /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
sed -i "s/$oddyuming/$yuming/g" /home/web/conf.d/$yuming.conf
- rm /home/web/certs/${oddyuming}_key.pem
- rm /home/web/certs/${oddyuming}_cert.pem
-
- docker exec nginx nginx -s reload
+ cd /home/web && docker compose restart
;;
@@ -3513,7 +3983,7 @@ ldnmp_web_status() {
20)
web_del
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
;;
*)
@@ -3558,21 +4028,21 @@ while true; do
install wget
iptables_open
panel_app_install
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
send_stats "${panelname}安装"
;;
2)
panel_app_manage
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
send_stats "${panelname}控制"
;;
3)
panel_app_uninstall
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
send_stats "${panelname}卸载"
;;
*)
@@ -3871,6 +4341,7 @@ frps_main_ports() {
frps_panel() {
send_stats "FRP服务端"
+ local app_id="55"
local docker_name="frps"
local docker_port=8056
while true; do
@@ -3879,7 +4350,7 @@ frps_panel() {
check_docker_image_update $docker_name
echo -e "FRP服务端 $check_frp $update_status"
echo "构建FRP内网穿透服务环境,将无公网IP的设备暴露到互联网"
- echo "官网介绍: https://github.com/fatedier/frp/"
+ echo "官网介绍: ${gh_https_url}github.com/fatedier/frp/"
echo "视频教学: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
if [ -d "/home/frp/" ]; then
check_docker_app_ip
@@ -3901,8 +4372,8 @@ frps_panel() {
install jq grep ss
install_docker
generate_frps_config
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP服务端已经安装完成"
;;
2)
@@ -3911,8 +4382,8 @@ frps_panel() {
docker rm -f frps && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frps.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frps.toml /home/frp/frps.toml
donlond_frp frps
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP服务端已经更新完成"
;;
3)
@@ -3922,8 +4393,8 @@ frps_panel() {
rm -rf /home/frp
close_port 8055 8056
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "应用已卸载"
;;
5)
@@ -3968,6 +4439,7 @@ frps_panel() {
frpc_panel() {
send_stats "FRP客户端"
+ local app_id="56"
local docker_name="frpc"
local docker_port=8055
while true; do
@@ -3976,7 +4448,7 @@ frpc_panel() {
check_docker_image_update $docker_name
echo -e "FRP客户端 $check_frp $update_status"
echo "与服务端对接,对接后可创建内网穿透服务到互联网访问"
- echo "官网介绍: https://github.com/fatedier/frp/"
+ echo "官网介绍: ${gh_https_url}github.com/fatedier/frp/"
echo "视频教学: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
echo "------------------------"
if [ -d "/home/frp/" ]; then
@@ -3997,8 +4469,8 @@ frpc_panel() {
install jq grep ss
install_docker
configure_frpc
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP客户端已经安装完成"
;;
2)
@@ -4007,8 +4479,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
donlond_frp frpc
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+
+ add_app_id
echo "FRP客户端已经更新完成"
;;
@@ -4018,8 +4490,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine
rm -rf /home/frp
close_port 8055
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "应用已卸载"
;;
@@ -4050,6 +4522,7 @@ frpc_panel() {
yt_menu_pro() {
+ local app_id="66"
local VIDEO_DIR="/home/yt-dlp"
local URL_FILE="$VIDEO_DIR/urls.txt"
local ARCHIVE_FILE="$VIDEO_DIR/archive.txt"
@@ -4068,7 +4541,7 @@ yt_menu_pro() {
send_stats "yt-dlp 下载工具"
echo -e "yt-dlp $YTDLP_STATUS"
echo -e "yt-dlp 是一个功能强大的视频下载工具,支持 YouTube、Bilibili、Twitter 等数千站点。"
- echo -e "官网地址:https://github.com/yt-dlp/yt-dlp"
+ echo -e "官网地址:${gh_https_url}github.com/yt-dlp/yt-dlp"
echo "-------------------------"
echo "已下载视频列表:"
ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(暂无)"
@@ -4087,26 +4560,26 @@ yt_menu_pro() {
send_stats "正在安装 yt-dlp..."
echo "正在安装 yt-dlp..."
install ffmpeg
- sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
- sudo chmod a+rx /usr/local/bin/yt-dlp
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ curl -L ${gh_https_url}github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ chmod a+rx /usr/local/bin/yt-dlp
+
+ add_app_id
echo "安装完成。按任意键继续..."
read ;;
2)
send_stats "正在更新 yt-dlp..."
echo "正在更新 yt-dlp..."
- sudo yt-dlp -U
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ yt-dlp -U
+
+ add_app_id
echo "更新完成。按任意键继续..."
read ;;
3)
send_stats "正在卸载 yt-dlp..."
echo "正在卸载 yt-dlp..."
- sudo rm -f /usr/local/bin/yt-dlp
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
+ rm -f /usr/local/bin/yt-dlp
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "卸载完成。按任意键继续..."
read ;;
5)
@@ -4203,7 +4676,7 @@ fix_dpkg() {
linux_update() {
- echo -e "${gl_huang}正在系统更新...${gl_bai}"
+ echo -e "${gl_kjlan}正在系统更新...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
elif command -v yum &>/dev/null; then
@@ -4230,7 +4703,7 @@ linux_update() {
linux_clean() {
- echo -e "${gl_huang}正在系统清理...${gl_bai}"
+ echo -e "${gl_kjlan}正在系统清理...${gl_bai}"
if command -v dnf &>/dev/null; then
rpm --rebuilddb
dnf autoremove -y
@@ -4309,10 +4782,17 @@ linux_clean() {
bbr_on() {
-cat > /etc/sysctl.conf << EOF
-net.ipv4.tcp_congestion_control=bbr
-EOF
-sysctl -p
+# 统一写入到 sysctl.d 以防与内核调优模块打架
+local CONF="/etc/sysctl.d/99-kejilion-bbr.conf"
+mkdir -p /etc/sysctl.d
+echo "net.core.default_qdisc=fq" > "$CONF"
+echo "net.ipv4.tcp_congestion_control=bbr" >> "$CONF"
+
+# 清理可能导致冲突的旧版 sysctl.conf 残留
+sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf 2>/dev/null
+
+sysctl -p "$CONF" >/dev/null 2>&1 || sysctl --system >/dev/null 2>&1
}
@@ -4321,8 +4801,8 @@ set_dns() {
ip_address
-rm /etc/resolv.conf
-touch /etc/resolv.conf
+chattr -i /etc/resolv.conf
+> /etc/resolv.conf
if [ -n "$ipv4_address" ]; then
echo "nameserver $dns1_ipv4" >> /etc/resolv.conf
@@ -4334,6 +4814,13 @@ if [ -n "$ipv6_address" ]; then
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
fi
+if [ ! -s /etc/resolv.conf ]; then
+ echo "nameserver 223.5.5.5" >> /etc/resolv.conf
+ echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+fi
+
+chattr +i /etc/resolv.conf
+
}
@@ -4378,7 +4865,9 @@ while true; do
;;
3)
install nano
+ chattr -i /etc/resolv.conf
nano /etc/resolv.conf
+ chattr +i /etc/resolv.conf
send_stats "手动编辑DNS配置"
;;
*)
@@ -4402,39 +4891,32 @@ correct_ssh_config() {
local sshd_config="/etc/ssh/sshd_config"
- # 如果找到 PasswordAuthentication 设置为 yes
- if grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
- # 如果找到 PubkeyAuthentication 设置为 yes
- if grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
+ if grep -Eq "^\s*PasswordAuthentication\s+no" "$sshd_config"; then
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
-e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' "$sshd_config"
+ else
+ sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin yes/' \
+ -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication yes/' \
+ -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' "$sshd_config"
fi
- # 如果 PasswordAuthentication 和 PubkeyAuthentication 都没有匹配,则设置默认值
- if ! grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config" && ! grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
-
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
}
new_ssh_port() {
- # 备份 SSH 配置文件
+ local new_port=$1
+
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
open_port $new_port
@@ -4448,21 +4930,7 @@ new_ssh_port() {
-add_sshkey() {
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f /root/.ssh/sshkey -N ""
- cat ~/.ssh/sshkey.pub >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
-
- ip_address
- echo -e "私钥信息已生成,务必复制保存,可保存成 ${gl_huang}${ipv4_address}_ssh.key${gl_bai} 文件,用于以后的SSH登录"
-
- echo "--------------------------------"
- cat ~/.ssh/sshkey
- echo "--------------------------------"
+sshkey_on() {
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
@@ -4470,61 +4938,335 @@ add_sshkey() {
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
- echo -e "${gl_lv}ROOT私钥登录已开启,已关闭ROOT密码登录,重连将会生效${gl_bai}"
+ echo -e "${gl_lv}用户密钥登录模式已开启,已关闭密码登录模式,重连将会生效${gl_bai}"
}
-import_sshkey() {
- read -e -p "请输入您的SSH公钥内容(通常以 'ssh-rsa' 或 'ssh-ed25519' 开头): " public_key
+add_sshkey() {
+ chmod 700 "${HOME}"
+ mkdir -p "${HOME}/.ssh"
+ chmod 700 "${HOME}/.ssh"
+ touch "${HOME}/.ssh/authorized_keys"
- if [[ -z "$public_key" ]]; then
- echo -e "${gl_hong}错误:未输入公钥内容。${gl_bai}"
- return 1
- fi
+ ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f "${HOME}/.ssh/sshkey" -N ""
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- echo "$public_key" >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
+ cat "${HOME}/.ssh/sshkey.pub" >> "${HOME}/.ssh/authorized_keys"
+ chmod 600 "${HOME}/.ssh/authorized_keys"
- sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
- -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
- -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
- -e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
+ ip_address
+ echo -e "私钥信息已生成,务必复制保存,可保存成 ${gl_huang}${ipv4_address}_ssh.key${gl_bai} 文件,用于以后的SSH登录"
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
- restart_ssh
- echo -e "${gl_lv}公钥已成功导入,ROOT私钥登录已开启,已关闭ROOT密码登录,重连将会生效${gl_bai}"
+ echo "--------------------------------"
+ cat "${HOME}/.ssh/sshkey"
+ echo "--------------------------------"
+ sshkey_on
}
-add_sshpasswd() {
-echo "设置你的ROOT密码"
-passwd
-sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
-sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
-rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
-restart_ssh
-echo -e "${gl_lv}ROOT登录设置完毕!${gl_bai}"
+import_sshkey() {
-}
+ local public_key="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local auth_keys="${ssh_dir}/authorized_keys"
+ if [[ -z "$public_key" ]]; then
+ read -e -p "请输入您的SSH公钥内容(通常以 'ssh-rsa' 或 'ssh-ed25519' 开头): " public_key
+ fi
-root_use() {
-clear
-[ "$EUID" -ne 0 ] && echo -e "${gl_huang}提示: ${gl_bai}该功能需要root用户才能运行!" && break_end && kejilion
+ if [[ -z "$public_key" ]]; then
+ echo -e "${gl_hong}错误:未输入公钥内容。${gl_bai}"
+ return 1
+ fi
+
+ if [[ ! "$public_key" =~ ^ssh-(rsa|ed25519|ecdsa) ]]; then
+ echo -e "${gl_hong}错误:看起来不像合法的 SSH 公钥。${gl_bai}"
+ return 1
+ fi
+
+ if grep -Fxq "$public_key" "$auth_keys" 2>/dev/null; then
+ echo "该公钥已存在,无需重复添加"
+ return 0
+ fi
+
+ mkdir -p "$ssh_dir"
+ chmod 700 "$ssh_dir"
+ touch "$auth_keys"
+ echo "$public_key" >> "$auth_keys"
+ chmod 600 "$auth_keys"
+
+ sshkey_on
+}
+
+
+
+fetch_remote_ssh_keys() {
+
+ local keys_url="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local authorized_keys="${ssh_dir}/authorized_keys"
+ local temp_file
+
+ if [[ -z "${keys_url}" ]]; then
+ read -e -p "请输入您的远端公钥URL: " keys_url
+ fi
+
+ echo "此脚本将从远程 URL 拉取 SSH 公钥,并添加到 ${authorized_keys}"
+ echo ""
+ echo "远程公钥地址:"
+ echo " ${keys_url}"
+ echo ""
+
+ # 创建临时文件
+ temp_file=$(mktemp)
+
+ # 下载公钥
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 10 "${keys_url}" -o "${temp_file}" || {
+ echo "错误:无法从 URL 下载公钥(网络问题或地址无效)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q --timeout=10 -O "${temp_file}" "${keys_url}" || {
+ echo "错误:无法从 URL 下载公钥(网络问题或地址无效)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ else
+ echo "错误:系统中未找到 curl 或 wget,无法下载公钥" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ # 检查内容是否有效
+ if [[ ! -s "${temp_file}" ]]; then
+ echo "错误:下载到的文件为空,URL 可能不包含任何公钥" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ mkdir -p "${ssh_dir}"
+ chmod 700 "${ssh_dir}"
+ touch "${authorized_keys}"
+ chmod 600 "${authorized_keys}"
+
+ # 备份原有 authorized_keys
+ if [[ -f "${authorized_keys}" ]]; then
+ cp "${authorized_keys}" "${authorized_keys}.bak.$(date +%Y%m%d-%H%M%S)"
+ echo "已备份原有 authorized_keys 文件"
+ fi
+
+ # 追加公钥(避免重复)
+ local added=0
+ while IFS= read -r line; do
+ [[ -z "${line}" || "${line}" =~ ^# ]] && continue
+
+ if ! grep -Fxq "${line}" "${authorized_keys}" 2>/dev/null; then
+ echo "${line}" >> "${authorized_keys}"
+ ((added++))
+ fi
+ done < "${temp_file}"
+
+ rm -f "${temp_file}"
+
+ echo ""
+ if (( added > 0 )); then
+ echo "成功添加 ${added} 条新的公钥到 ${authorized_keys}"
+ sshkey_on
+ else
+ echo "没有新的公钥需要添加(可能已全部存在)"
+ fi
+
+ echo ""
+}
+
+
+
+
+fetch_github_ssh_keys() {
+
+ local username="$1"
+ local base_dir="${2:-$HOME}"
+
+ echo "操作前,请确保您已在 GitHub 账户中添加了 SSH 公钥:"
+ echo " 1. 登录 ${gh_https_url}github.com/settings/keys"
+ echo " 2. 点击 New SSH key 或 Add SSH key"
+ echo " 3. Title 可随意填写(例如:Home Laptop 2026)"
+ echo " 4. 将本地公钥内容(通常是 ~/.ssh/id_ed25519.pub 或 id_rsa.pub 的全部内容)粘贴到 Key 字段"
+ echo " 5. 点击 Add SSH key 完成添加"
+ echo ""
+ echo "添加完成后,GitHub 会公开提供您的所有公钥,地址为:"
+ echo " ${gh_https_url}github.com/您的用户名.keys"
+ echo ""
+
+
+ if [[ -z "${username}" ]]; then
+ read -e -p "请输入您的 GitHub 用户名(username,不含 @): " username
+ fi
+
+ if [[ -z "${username}" ]]; then
+ echo "错误:GitHub 用户名不能为空" >&2
+ return 1
+ fi
+
+ keys_url="${gh_https_url}github.com/${username}.keys"
+
+ fetch_remote_ssh_keys "${keys_url}" "${base_dir}"
+
+}
+
+
+sshkey_panel() {
+ root_use
+ send_stats "用户密钥登录"
+ while true; do
+ clear
+ local REAL_STATUS=$(grep -i "^PubkeyAuthentication" /etc/ssh/sshd_config | tr '[:upper:]' '[:lower:]')
+ if [[ "$REAL_STATUS" =~ "yes" ]]; then
+ IS_KEY_ENABLED="${gl_lv}已启用${gl_bai}"
+ else
+ IS_KEY_ENABLED="${gl_hui}未启用${gl_bai}"
+ fi
+ echo -e "用户密钥登录模式 ${IS_KEY_ENABLED}"
+ echo "进阶玩法: https://blog.kejilion.pro/ssh-key"
+ echo "------------------------------------------------"
+ echo "将会生成密钥对,更安全的方式SSH登录"
+ echo "------------------------"
+ echo "1. 生成新密钥对 2. 手动输入已有公钥"
+ echo "3. 从GitHub导入已有公钥 4. 从URL导入已有公钥"
+ echo "5. 编辑公钥文件 6. 查看本机密钥"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "请输入你的选择: " host_dns
+ case $host_dns in
+ 1)
+ send_stats "生成新密钥"
+ add_sshkey
+ break_end
+ ;;
+ 2)
+ send_stats "导入已有公钥"
+ import_sshkey
+ break_end
+ ;;
+ 3)
+ send_stats "导入GitHub远端公钥"
+ fetch_github_ssh_keys
+ break_end
+ ;;
+ 4)
+ send_stats "导入URL远端公钥"
+ read -e -p "请输入您的远端公钥URL: " keys_url
+ fetch_remote_ssh_keys "${keys_url}"
+ break_end
+ ;;
+
+ 5)
+ send_stats "编辑公钥文件"
+ install nano
+ nano ${HOME}/.ssh/authorized_keys
+ break_end
+ ;;
+
+ 6)
+ send_stats "查看本机密钥"
+ echo "------------------------"
+ echo "公钥信息"
+ cat ${HOME}/.ssh/authorized_keys
+ echo "------------------------"
+ echo "私钥信息"
+ cat ${HOME}/.ssh/sshkey
+ echo "------------------------"
+ break_end
+ ;;
+ *)
+ break # 跳出循环,退出菜单
+ ;;
+ esac
+ done
+
+
+}
+
+
+
+
+
+
+add_sshpasswd() {
+
+ root_use
+ send_stats "设置密码登录模式"
+ echo "设置密码登录模式"
+
+ local target_user="$1"
+
+ # 如果没有通过参数传入,则交互输入
+ if [[ -z "$target_user" ]]; then
+ read -e -p "请输入要修改密码的用户名(默认 root): " target_user
+ fi
+
+ # 回车不输入,默认 root
+ target_user=${target_user:-root}
+
+ # 校验用户是否存在
+ if ! id "$target_user" >/dev/null 2>&1; then
+ echo "错误:用户 $target_user 不存在"
+ return 1
+ fi
+
+ passwd "$target_user"
+
+ if [[ "$target_user" == "root" ]]; then
+ sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
+ fi
+
+ sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
+
+ restart_ssh
+
+ echo -e "${gl_lv}密码设置完毕,已更改为密码登录模式!${gl_bai}"
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+root_use() {
+clear
+[ "$EUID" -ne 0 ] && echo -e "${gl_huang}提示: ${gl_bai}该功能需要root用户才能运行!" && break_end && kejilion
}
+
+
+
+
+
+
+
+
+
dd_xitong() {
send_stats "重装系统"
dd_xitong_MollyLau() {
@@ -4538,6 +5280,7 @@ dd_xitong() {
dd_xitong_1() {
echo -e "重装后初始用户名: ${gl_huang}root${gl_bai} 初始密码: ${gl_huang}LeitboGi0ro${gl_bai} 初始端口: ${gl_huang}22${gl_bai}"
+ echo -e "${gl_huang}重装后请及时修改初始密码,防止暴力入侵。命令行输入passwd修改密码${gl_bai}"
echo -e "按任意键继续..."
read -n 1 -s -r -p ""
install wget
@@ -4571,18 +5314,20 @@ dd_xitong() {
echo "重装系统"
echo "--------------------------------"
echo -e "${gl_hong}注意: ${gl_bai}重装有风险失联,不放心者慎用。重装预计花费15分钟,请提前备份数据。"
- echo -e "${gl_hui}感谢MollyLau大佬和bin456789大佬的脚本支持!${gl_bai} "
+ echo -e "${gl_hui}感谢bin456789大佬和leitbogioro大佬的脚本支持!${gl_bai} "
+ echo -e "${gl_hui}bin456789项目地址: ${gh_https_url}github.com/bin456789/reinstall${gl_bai}"
+ echo -e "${gl_hui}leitbogioro项目地址: ${gh_https_url}github.com/leitbogioro/Tools${gl_bai}"
echo "------------------------"
- echo "1. Debian 12 2. Debian 11"
- echo "3. Debian 10 4. Debian 9"
+ echo "1. Debian 13 2. Debian 12"
+ echo "3. Debian 11 4. Debian 10"
echo "------------------------"
- echo "11. Ubuntu 24.04 12. Ubuntu 22.04"
- echo "13. Ubuntu 20.04 14. Ubuntu 18.04"
+ echo "11. Ubuntu 26.04 12. Ubuntu 24.04"
+ echo "13. Ubuntu 22.04 14. Ubuntu 20.04"
echo "------------------------"
echo "21. Rocky Linux 10 22. Rocky Linux 9"
echo "23. Alma Linux 10 24. Alma Linux 9"
echo "25. oracle Linux 10 26. oracle Linux 9"
- echo "27. Fedora Linux 42 28. Fedora Linux 41"
+ echo "27. Fedora Linux 44 28. Fedora Linux 43"
echo "29. CentOS 10 30. CentOS 9"
echo "------------------------"
echo "31. Alpine Linux 32. Arch Linux"
@@ -4590,72 +5335,74 @@ dd_xitong() {
echo "35. openSUSE Tumbleweed 36. fnos飞牛公测版"
echo "------------------------"
echo "41. Windows 11 42. Windows 10"
- echo "43. Windows 7 44. Windows Server 2022"
- echo "45. Windows Server 2019 46. Windows Server 2016"
+ echo "43. Windows 7 44. Windows Server 2025"
+ echo "45. Windows Server 2022 46. Windows Server 2019"
echo "47. Windows 11 ARM"
echo "------------------------"
echo "0. 返回上一级选单"
echo "------------------------"
read -e -p "请选择要重装的系统: " sys_choice
case "$sys_choice" in
+
+
1)
- send_stats "重装debian 12"
- dd_xitong_1
- bash InstallNET.sh -debian 12
+ send_stats "重装debian 13"
+ dd_xitong_3
+ bash reinstall.sh debian 13
reboot
exit
;;
+
2)
- send_stats "重装debian 11"
- dd_xitong_1
- bash InstallNET.sh -debian 11
+ send_stats "重装debian 12"
+ dd_xitong_3
+ bash reinstall.sh debian 12
reboot
exit
;;
3)
- send_stats "重装debian 10"
- dd_xitong_1
- bash InstallNET.sh -debian 10
+ send_stats "重装debian 11"
+ dd_xitong_3
+ bash reinstall.sh debian 11
reboot
exit
;;
4)
- send_stats "重装debian 9"
- dd_xitong_1
- bash InstallNET.sh -debian 9
+ send_stats "重装debian 10"
+ dd_xitong_3
+ bash reinstall.sh debian 10
reboot
exit
;;
11)
- send_stats "重装ubuntu 24.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 24.04
+ send_stats "重装ubuntu 26.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 26.04
reboot
exit
;;
12)
- send_stats "重装ubuntu 22.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 22.04
+ send_stats "重装ubuntu 24.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 24.04
reboot
exit
;;
13)
- send_stats "重装ubuntu 20.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 20.04
+ send_stats "重装ubuntu 22.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 22.04
reboot
exit
;;
14)
- send_stats "重装ubuntu 18.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 18.04
+ send_stats "重装ubuntu 20.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 20.04
reboot
exit
;;
-
21)
send_stats "重装rockylinux10"
dd_xitong_3
@@ -4705,17 +5452,17 @@ dd_xitong() {
;;
27)
- send_stats "重装fedora42"
+ send_stats "重装fedora44"
dd_xitong_3
- bash reinstall.sh fedora
+ bash reinstall.sh fedora 44
reboot
exit
;;
28)
- send_stats "重装fedora41"
+ send_stats "重装fedora43"
dd_xitong_3
- bash reinstall.sh fedora 41
+ bash reinstall.sh fedora 43
reboot
exit
;;
@@ -4784,7 +5531,6 @@ dd_xitong() {
exit
;;
-
41)
send_stats "重装windows11"
dd_xitong_2
@@ -4792,6 +5538,7 @@ dd_xitong() {
reboot
exit
;;
+
42)
dd_xitong_2
send_stats "重装windows10"
@@ -4799,32 +5546,35 @@ dd_xitong() {
reboot
exit
;;
+
43)
send_stats "重装windows7"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://archive.org/download/en_windows_7_professional_with_sp1_x64_dvd_u_676939_201906/en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso" --image-name='windows 7 professional'
reboot
exit
;;
44)
- send_stats "重装windows server 22"
+ send_stats "重装windows server 25"
dd_xitong_2
- bash InstallNET.sh -windows 2022 -lang "cn"
+ bash InstallNET.sh -windows 2025 -lang "cn"
reboot
exit
;;
+
45)
- send_stats "重装windows server 19"
+ send_stats "重装windows server 22"
dd_xitong_2
- bash InstallNET.sh -windows 2019 -lang "cn"
+ bash InstallNET.sh -windows 2022 -lang "cn"
reboot
exit
;;
+
46)
- send_stats "重装windows server 16"
+ send_stats "重装windows server 19"
dd_xitong_2
- bash InstallNET.sh -windows 2016 -lang "cn"
+ bash InstallNET.sh -windows 2019 -lang "cn"
reboot
exit
;;
@@ -4849,6 +5599,146 @@ bbrv3() {
root_use
send_stats "bbrv3管理"
+ xanmod_add_repo() {
+ local keyring="/usr/share/keyrings/xanmod-archive-keyring.gpg"
+ local list_file="/etc/apt/sources.list.d/xanmod-release.list"
+ local key_url="https://dl.xanmod.org/archive.key"
+ local fallback_key_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key"
+ local os_codename=""
+
+ if command -v lsb_release >/dev/null 2>&1; then
+ os_codename=$(lsb_release -sc)
+ elif [ -r /etc/os-release ]; then
+ os_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
+ fi
+
+ # 兼容官方已移除的老系统代号(回退使用 releases 尝试旧包库)
+ if ! echo "bookworm trixie forky sid noble plucky questing resolute faye gigi wilma xia zara zena" | grep -qw "$os_codename"; then
+ os_codename="releases"
+ fi
+
+ # 官方已彻底移除对 jammy, focal, bullseye 等老系统的 apt 支持
+ if echo "jammy focal bullseye buster" | grep -qw "$os_codename" || [ "$os_codename" = "releases" ]; then
+ echo -e "${gl_hong}XanMod 官方已停止对当前系统($os_codename)的 APT 源支持,请升级至 Debian12 / Ubuntu24 或更高版本。${gl_bai}"
+ return 1
+ fi
+
+ if [ -z "$os_codename" ]; then
+ echo "无法获取系统代号,无法配置XanMod源"
+ return 1
+ fi
+
+ install wget gnupg ca-certificates
+ mkdir -p /usr/share/keyrings /etc/apt/sources.list.d
+ if ! wget -qO - "$key_url" | gpg --dearmor -o "$keyring" --yes; then
+ echo "官方密钥下载失败,尝试备用下载源..."
+ wget -qO - "$fallback_key_url" | gpg --dearmor -o "$keyring" --yes || return 1
+ fi
+ chmod 644 "$keyring"
+ echo "deb [signed-by=$keyring] http://deb.xanmod.org $os_codename main" > "$list_file"
+ }
+
+ xanmod_detect_psabi_level() {
+ local psabi_output=""
+ psabi_output=$(awk 'BEGIN {
+ while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
+ if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
+ if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
+ if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
+ if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
+ if (level > 0) { print level; exit }
+ exit 1
+ }' /proc/cpuinfo 2>/dev/null) || return 1
+ printf '%s' "$psabi_output" | tr -dc '0-9' | head -c 1
+ }
+
+ xanmod_package_available() {
+ local package="$1"
+ apt-cache policy "$package" 2>/dev/null | grep -q 'Candidate: [^ ]'
+ }
+
+ xanmod_detect_package() {
+ local psabi_level=""
+ local level=""
+ local package=""
+ local prefix_list="linux-xanmod linux-xanmod-lts"
+
+ psabi_level=$(xanmod_detect_psabi_level) || return 1
+ [ -n "$psabi_level" ] || return 1
+ [ "$psabi_level" -gt 3 ] && psabi_level=3
+
+ apt update -y >/dev/null 2>&1
+
+ for prefix in $prefix_list; do
+ level="$psabi_level"
+ while [ "$level" -ge 1 ]; do
+ package="${prefix}-x64v${level}"
+ if xanmod_package_available "$package"; then
+ if [ "$level" != "$psabi_level" ] || [ "$prefix" = "linux-xanmod-lts" ]; then
+ echo "已自动匹配合适安装包: $package" >&2
+ fi
+ printf '%s\n' "$package"
+ return 0
+ fi
+ level=$((level - 1))
+ done
+ done
+
+ echo "软件源中未找到适配此CPU的XanMod内核包" >&2
+ return 1
+ }
+
+ xanmod_installed() {
+ dpkg-query -W -f='${Package}\n' 'linux-*xanmod*' 2>/dev/null | grep -q '^linux-.*xanmod'
+ }
+
+ xanmod_install_or_update() {
+ local action="$1"
+ local package=""
+
+ check_disk_space 3
+ check_swap
+ xanmod_add_repo || {
+ echo "XanMod官方仓库配置失败,请稍后重试"
+ return 1
+ }
+
+ package=$(xanmod_detect_package) || {
+ echo "无法识别当前CPU或找不到匹配内核包,已取消安装"
+ return 1
+ }
+
+ apt update -y
+ if [ "$action" = "update" ]; then
+ apt install -y --only-upgrade "$package" || apt install -y "$package" || {
+ echo "XanMod内核更新失败,请检查软件源或稍后重试"
+ return 1
+ }
+ else
+ apt install -y "$package" || {
+ echo "XanMod内核安装失败,请检查软件源或稍后重试"
+ return 1
+ }
+ fi
+
+ bbr_on || {
+ echo "BBR3参数写入失败,请检查系统配置"
+ return 1
+ }
+ echo "XanMod BBRv3内核处理完成。重启后生效"
+ server_reboot
+ }
+
+ xanmod_uninstall() {
+ apt purge -y 'linux-*xanmod*'
+ apt autoremove -y
+ update-grub 2>/dev/null || true
+ rm -f /etc/apt/sources.list.d/xanmod-release.list
+ rm -f /usr/share/keyrings/xanmod-archive-keyring.gpg
+ echo "XanMod内核已卸载。重启后生效"
+ server_reboot
+ }
+
local cpu_arch=$(uname -m)
if [ "$cpu_arch" = "aarch64" ]; then
bash <(curl -sL jhb.ovh/jb/bbrv3arm.sh)
@@ -4856,7 +5746,20 @@ bbrv3() {
linux_Settings
fi
- if dpkg -l | grep -q 'linux-xanmod'; then
+ if [ -r /etc/os-release ]; then
+ . /etc/os-release
+ if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
+ echo "当前环境不支持,仅支持Debian和Ubuntu系统"
+ break_end
+ linux_Settings
+ fi
+ else
+ echo "无法确定操作系统类型"
+ break_end
+ linux_Settings
+ fi
+
+ if xanmod_installed; then
while true; do
clear
local kernel_version=$(uname -r)
@@ -4874,38 +5777,14 @@ bbrv3() {
case $sub_choice in
1)
- apt purge -y 'linux-*xanmod1*'
- update-grub
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 步骤3:添加存储库
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- echo "XanMod内核已更新。重启后生效"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
-
- server_reboot
-
- ;;
+ xanmod_install_or_update update
+ ;;
2)
- apt purge -y 'linux-*xanmod1*'
- update-grub
- echo "XanMod内核已卸载。重启后生效"
- server_reboot
- ;;
-
+ xanmod_uninstall
+ ;;
*)
- break # 跳出循环,退出菜单
- ;;
+ break
+ ;;
esac
done
@@ -4917,48 +5796,12 @@ bbrv3() {
echo "------------------------------------------------"
echo "仅支持Debian/Ubuntu"
echo "请备份数据,将为你升级Linux内核开启BBR3"
- echo "VPS是512M内存的,请提前添加1G虚拟内存,防止因内存不足失联!"
echo "------------------------------------------------"
read -e -p "确定继续吗?(Y/N): " choice
case "$choice" in
[Yy])
- check_disk_space 3
- if [ -r /etc/os-release ]; then
- . /etc/os-release
- if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
- echo "当前环境不支持,仅支持Debian和Ubuntu系统"
- break_end
- linux_Settings
- fi
- else
- echo "无法确定操作系统类型"
- break_end
- linux_Settings
- fi
-
- check_swap
- install wget gnupg
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 步骤3:添加存储库
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- bbr_on
-
- echo "XanMod内核安装并BBR3启用成功。重启后生效"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
- server_reboot
-
+ xanmod_install_or_update install
;;
[Nn])
echo "已取消"
@@ -4971,7 +5814,6 @@ bbrv3() {
}
-
elrepo_install() {
# 导入 ELRepo GPG 公钥
echo "导入 ELRepo GPG 公钥..."
@@ -5087,7 +5929,7 @@ elrepo() {
clamav_freshclam() {
- echo -e "${gl_huang}正在更新病毒库...${gl_bai}"
+ echo -e "${gl_kjlan}正在更新病毒库...${gl_bai}"
docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
@@ -5101,7 +5943,7 @@ clamav_scan() {
return
fi
- echo -e "${gl_huang}正在扫描目录$@... ${gl_bai}"
+ echo -e "${gl_kjlan}正在扫描目录$@... ${gl_bai}"
# 构建 mount 参数
local MOUNT_PARAMS=""
@@ -5119,7 +5961,7 @@ clamav_scan() {
> /home/docker/clamav/log/scan.log > /dev/null 2>&1
# 执行 Docker 命令
- docker run -it --rm \
+ docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
$MOUNT_PARAMS \
@@ -5189,171 +6031,348 @@ clamav() {
}
+# ============================================================================
+# Linux 内核调优模块(重构版)
+# 统一核心函数 + 场景差异化参数 + 持久化到配置文件 + 硬件自适应
+# 替换原 optimize_high_performance / optimize_balanced / optimize_web_server / restore_defaults
+# ============================================================================
+# 获取内存大小(MB)
+_get_mem_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
+}
-# 高性能模式优化函数
-optimize_high_performance() {
- echo -e "${gl_lv}切换到${tiaoyou_moshi}...${gl_bai}"
-
- echo -e "${gl_lv}优化文件描述符...${gl_bai}"
- ulimit -n 65535
+# 统一内核调优核心函数
+# 参数: $1 = 模式名称, $2 = 场景 (high/balanced/web/stream/game)
+_kernel_optimize_core() {
+ local mode_name="$1"
+ local scene="${2:-high}"
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
+ local MEM_MB=$(_get_mem_mb)
+
+ echo -e "${gl_lv}切换到${mode_name}...${gl_bai}"
+
+ # ── 根据场景设定参数 ──
+ local SWAPPINESS DIRTY_RATIO DIRTY_BG_RATIO OVERCOMMIT MIN_FREE_KB VFS_PRESSURE
+ local RMEM_MAX WMEM_MAX TCP_RMEM TCP_WMEM
+ local SOMAXCONN BACKLOG SYN_BACKLOG
+ local PORT_RANGE SCHED_AUTOGROUP THP NUMA FIN_TIMEOUT
+ local KEEPALIVE_TIME KEEPALIVE_INTVL KEEPALIVE_PROBES
+
+ case "$scene" in
+ high|stream|game)
+ # 高性能/直播/游戏:激进参数
+ SWAPPINESS=10
+ DIRTY_RATIO=15
+ DIRTY_BG_RATIO=5
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=67108864
+ WMEM_MAX=67108864
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ SOMAXCONN=8192
+ BACKLOG=250000
+ SYN_BACKLOG=8192
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=10
+ KEEPALIVE_TIME=300
+ KEEPALIVE_INTVL=30
+ KEEPALIVE_PROBES=5
+ ;;
+ web)
+ # 网站服务器:高并发优先
+ SWAPPINESS=10
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=33554432
+ WMEM_MAX=33554432
+ TCP_RMEM="4096 131072 33554432"
+ TCP_WMEM="4096 131072 33554432"
+ SOMAXCONN=16384
+ BACKLOG=10000
+ SYN_BACKLOG=16384
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=15
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ balanced)
+ # 均衡模式:适度优化
+ SWAPPINESS=30
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=0
+ VFS_PRESSURE=75
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ SOMAXCONN=4096
+ BACKLOG=5000
+ SYN_BACKLOG=4096
+ PORT_RANGE="1024 49151"
+ SCHED_AUTOGROUP=1
+ THP="always"
+ NUMA=1
+ FIN_TIMEOUT=30
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ esac
- echo -e "${gl_lv}优化虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=15 2>/dev/null
- sysctl -w vm.dirty_background_ratio=5 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # ── 根据内存大小自适应调整 ──
+ if [ "$MEM_MB" -ge 16384 ]; then
+ MIN_FREE_KB=131072
+ [ "$scene" != "balanced" ] && SWAPPINESS=5
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ MIN_FREE_KB=32768
+ # 小内存缩小缓冲区
+ if [ "$scene" != "balanced" ]; then
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ fi
+ else
+ MIN_FREE_KB=16384
+ SWAPPINESS=30
+ OVERCOMMIT=0
+ RMEM_MAX=4194304
+ WMEM_MAX=4194304
+ TCP_RMEM="4096 32768 4194304"
+ TCP_WMEM="4096 32768 4194304"
+ SOMAXCONN=1024
+ BACKLOG=1000
+ fi
- echo -e "${gl_lv}优化网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=250000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # ── 直播场景额外:UDP 缓冲区加大 ──
+ local STREAM_EXTRA=""
+ if [ "$scene" = "stream" ]; then
+ STREAM_EXTRA="
+# 直播推流 UDP 优化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
- echo -e "${gl_lv}优化缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # ── 游戏服场景额外:低延迟优先 ──
+ local GAME_EXTRA=""
+ if [ "$scene" = "game" ]; then
+ GAME_EXTRA="
+# 游戏服低延迟优化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384
+net.ipv4.tcp_slow_start_after_idle = 0"
+ fi
- echo -e "${gl_lv}优化CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # ── 加载 BBR 模块 ──
+ local CC="bbr"
+ local QDISC="fq"
+ local KVER
+ KVER=$(uname -r | grep -oP '^\d+\.\d+')
+ if printf '%s\n%s' "4.9" "$KVER" | sort -V -C; then
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ fi
+ if ! sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
+ else
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
- echo -e "${gl_lv}其他优化...${gl_bai}"
- # 禁用透明大页面,减少延迟
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # ── 备份已有配置 ──
+ [ -f "$CONF" ] && cp "$CONF" "${CONF}.bak.$(date +%s)"
+
+ # ── 写入配置文件(持久化) ──
+ echo -e "${gl_lv}写入优化配置...${gl_bai}"
+ cat > "$CONF" << SYSCTL
+# kejilion 内核调优配置
+# 模式: $mode_name | 场景: $scene
+# 内存: ${MEM_MB}MB | 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
+
+# ── TCP 拥塞控制 ──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $CC
+
+# ── TCP 缓冲区 ──
+net.core.rmem_max = $RMEM_MAX
+net.core.wmem_max = $WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+
+# ── 连接队列 ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $BACKLOG
+net.ipv4.tcp_max_syn_backlog = $SYN_BACKLOG
+
+# ── TCP 连接优化 ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = $FIN_TIMEOUT
+net.ipv4.tcp_keepalive_time = $KEEPALIVE_TIME
+net.ipv4.tcp_keepalive_intvl = $KEEPALIVE_INTVL
+net.ipv4.tcp_keepalive_probes = $KEEPALIVE_PROBES
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── 端口与内存 ──
+net.ipv4.ip_local_port_range = $PORT_RANGE
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── 虚拟内存 ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = $DIRTY_RATIO
+vm.dirty_background_ratio = $DIRTY_BG_RATIO
+vm.overcommit_memory = $OVERCOMMIT
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = $VFS_PRESSURE
+
+# ── CPU/内核调度 ──
+kernel.sched_autogroup_enabled = $SCHED_AUTOGROUP
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = $NUMA" || echo "# numa_balancing 不支持")
+
+# ── 安全防护 ──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── 文件描述符 ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── 连接跟踪 ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+echo "net.netfilter.nf_conntrack_max = $((SOMAXCONN * 32))"
+echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+echo "# conntrack 未启用"
+fi)
+$STREAM_EXTRA
+$GAME_EXTRA
+SYSCTL
+
+ # ── 应用配置(逐行,跳过不支持的参数) ──
+ echo -e "${gl_lv}应用优化参数...${gl_bai}"
+ local applied=0 skipped=0
+ while IFS= read -r line; do
+ # 跳过注释和空行
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
+ [[ -z "${line// /}" ]] && continue
+ if sysctl -w "$line" >/dev/null 2>&1; then
+ applied=$((applied + 1))
+ else
+ skipped=$((skipped + 1))
+ fi
+ done < "$CONF"
+ echo -e "${gl_lv}已应用 ${applied} 项参数${skipped:+,跳过 ${skipped} 项不支持的参数}${gl_bai}"
+ # ── 透明大页面 ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo "$THP" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
+ fi
-}
+ # ── 文件描述符限制 ──
+ if ! grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
-# 均衡模式优化函数
-optimize_balanced() {
- echo -e "${gl_lv}切换到均衡模式...${gl_bai}"
-
- echo -e "${gl_lv}优化文件描述符...${gl_bai}"
- ulimit -n 32768
-
- echo -e "${gl_lv}优化虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=30 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=32768 2>/dev/null
-
- echo -e "${gl_lv}优化网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=8388608 2>/dev/null
- sysctl -w net.core.wmem_max=8388608 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=125000 2>/dev/null
- sysctl -w net.core.somaxconn=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 32768 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 49151' 2>/dev/null
-
- echo -e "${gl_lv}优化缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=75 2>/dev/null
-
- echo -e "${gl_lv}优化CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}其他优化...${gl_bai}"
- # 还原透明大页面
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # 还原 NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# kejilion-optimize
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ fi
+ # ── BBR 持久化 ──
+ if [ "$CC" = "bbr" ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ # 清理旧的 sysctl.conf 里的 bbr 配置(避免冲突)
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+ fi
+ echo -e "${gl_lv}${mode_name} 优化完成!配置已持久化到 ${CONF}${gl_bai}"
+ echo -e "${gl_lv}内存: ${MEM_MB}MB | 拥塞算法: ${CC} | 队列: ${QDISC}${gl_bai}"
}
-# 还原默认设置函数
-restore_defaults() {
- echo -e "${gl_lv}还原到默认设置...${gl_bai}"
-
- echo -e "${gl_lv}还原文件描述符...${gl_bai}"
- ulimit -n 1024
-
- echo -e "${gl_lv}还原虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=60 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=16384 2>/dev/null
-
- echo -e "${gl_lv}还原网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=212992 2>/dev/null
- sysctl -w net.core.wmem_max=212992 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=1000 2>/dev/null
- sysctl -w net.core.somaxconn=128 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=0 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='32768 60999' 2>/dev/null
-
- echo -e "${gl_lv}还原缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=100 2>/dev/null
-
- echo -e "${gl_lv}还原CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}还原其他优化...${gl_bai}"
- # 还原透明大页面
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # 还原 NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# ── 各模式入口函数(保持原有调用接口不变) ──
+optimize_high_performance() {
+ _kernel_optimize_core "${tiaoyou_moshi:-高性能优化模式}" "high"
}
+optimize_balanced() {
+ _kernel_optimize_core "均衡优化模式" "balanced"
+}
-
-# 网站搭建优化函数
optimize_web_server() {
- echo -e "${gl_lv}切换到网站搭建优化模式...${gl_bai}"
+ _kernel_optimize_core "网站搭建优化模式" "web"
+}
+
+# ── 还原默认设置(完全清理) ──
+restore_defaults() {
+ echo -e "${gl_lv}还原到默认设置...${gl_bai}"
- echo -e "${gl_lv}优化文件描述符...${gl_bai}"
- ulimit -n 65535
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
- echo -e "${gl_lv}优化虚拟内存...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # 删除优化配置文件(含外链自动调优配置)
+ rm -f "$CONF"
+ rm -f /etc/sysctl.d/99-network-optimize.conf
- echo -e "${gl_lv}优化网络设置...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=5000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # 清理 sysctl.conf 里可能残留的 bbr 配置
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
- echo -e "${gl_lv}优化缓存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # 重新加载系统默认配置
+ sysctl --system 2>/dev/null | tail -1
- echo -e "${gl_lv}优化CPU设置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # 还原透明大页面
+ [ -f /sys/kernel/mm/transparent_hugepage/enabled ] && \
+ echo always > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
- echo -e "${gl_lv}其他优化...${gl_bai}"
- # 禁用透明大页面,减少延迟
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # 清理文件描述符配置
+ if grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# kejilion-optimize/,+4d' /etc/security/limits.conf
+ fi
+ # 清理 BBR 持久化
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+ echo -e "${gl_lv}系统已还原到默认设置${gl_bai}"
}
@@ -5362,18 +6381,26 @@ Kernel_optimize() {
while true; do
clear
send_stats "Linux内核调优管理"
+ local current_mode=$(grep "^# 模式:" /etc/sysctl.d/99-kejilion-optimize.conf 2>/dev/null | sed 's/# 模式: //' | awk -F'|' '{print $1}' | xargs)
+ [ -z "$current_mode" ] && [ -f /etc/sysctl.d/99-network-optimize.conf ] && current_mode="自动调优模式"
echo "Linux系统内核参数优化"
+ if [ -n "$current_mode" ]; then
+ echo -e "当前模式: ${gl_lv}${current_mode}${gl_bai}"
+ else
+ echo -e "当前模式: ${gl_hui}未优化${gl_bai}"
+ fi
echo "视频介绍: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
echo "------------------------------------------------"
echo "提供多种系统参数调优模式,用户可以根据自身使用场景进行选择切换。"
echo -e "${gl_huang}提示: ${gl_bai}生产环境请谨慎使用!"
- echo "--------------------"
- echo "1. 高性能优化模式: 最大化系统性能,优化文件描述符、虚拟内存、网络设置、缓存管理和CPU设置。"
- echo "2. 均衡优化模式: 在性能与资源消耗之间取得平衡,适合日常使用。"
- echo "3. 网站优化模式: 针对网站服务器进行优化,提高并发连接处理能力、响应速度和整体性能。"
- echo "4. 直播优化模式: 针对直播推流的特殊需求进行优化,减少延迟,提高传输性能。"
- echo "5. 游戏服优化模式: 针对游戏服务器进行优化,提高并发处理能力和响应速度。"
- echo "6. 还原默认设置: 将系统设置还原为默认配置。"
+ echo -e "--------------------"
+ echo -e "1. 高性能优化模式: 最大化系统性能,激进的内存和网络参数。"
+ echo -e "2. 均衡优化模式: 在性能与资源消耗之间取得平衡,适合日常使用。"
+ echo -e "3. 网站优化模式: 针对网站服务器优化,超高并发连接队列。"
+ echo -e "4. 直播优化模式: 针对直播推流优化,UDP 缓冲区加大,减少延迟。"
+ echo -e "5. 游戏服优化模式: 针对游戏服务器优化,低延迟优先。"
+ echo -e "6. 还原默认设置: 将系统设置还原为默认配置。"
+ echo -e "7. 自动调优: 根据测试数据自动调优内核参数。${gl_huang}★${gl_bai}"
echo "--------------------"
echo "0. 返回上一级选单"
echo "--------------------"
@@ -5401,23 +6428,30 @@ Kernel_optimize() {
4)
cd ~
clear
- local tiaoyou_moshi="直播优化模式"
- optimize_high_performance
+ _kernel_optimize_core "直播优化模式" "stream"
send_stats "直播推流优化"
;;
5)
cd ~
clear
- local tiaoyou_moshi="游戏服优化模式"
- optimize_high_performance
+ _kernel_optimize_core "游戏服优化模式" "game"
send_stats "游戏服优化"
;;
6)
cd ~
clear
restore_defaults
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh -o /tmp/network-optimize.sh && source /tmp/network-optimize.sh && restore_network_defaults
send_stats "还原默认设置"
;;
+
+ 7)
+ cd ~
+ clear
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ send_stats "内核自动调优"
+ ;;
+
*)
break
;;
@@ -5430,6 +6464,8 @@ Kernel_optimize() {
+
+
update_locale() {
local lang=$1
local locale_file=$2
@@ -5654,7 +6690,10 @@ linux_trash() {
done
}
-
+linux_fav() {
+send_stats "命令收藏夹"
+bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+}
# 创建备份
create_backup() {
@@ -5666,7 +6705,7 @@ create_backup() {
echo " - 备份单个目录: /var/www"
echo " - 备份多个目录: /etc /home /var/log"
echo " - 直接回车将使用默认目录 (/etc /usr /home)"
- read -r -p "请输入要备份的目录(多个目录用空格分隔,直接回车则使用默认目录):" input
+ read -e -p "请输入要备份的目录(多个目录用空格分隔,直接回车则使用默认目录):" input
# 如果用户没有输入目录,则使用默认目录
if [ -z "$input" ]; then
@@ -5800,30 +6839,95 @@ linux_backup() {
-# 显示连接列表
-list_connections() {
- echo "已保存的连接:"
- echo "------------------------"
- cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
- echo "------------------------"
+# SSH 输入标准化函数
+kj_ssh_validate_host() {
+ local host="$1"
+ [[ -n "$host" && ! "$host" =~ [[:space:]] && "$host" =~ ^[A-Za-z0-9._:-]+$ ]]
}
+kj_ssh_validate_port() {
+ local port="$1"
+ [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
+}
-# 添加新连接
-add_connection() {
- send_stats "添加新连接"
- echo "创建新连接示例:"
- echo " - 连接名称: my_server"
- echo " - IP地址: 192.168.1.100"
- echo " - 用户名: root"
- echo " - 端口: 22"
- echo "------------------------"
- read -e -p "请输入连接名称: " name
- read -e -p "请输入IP地址: " ip
- read -e -p "请输入用户名 (默认: root): " user
- local user=${user:-root} # 如果用户未输入,则使用默认值 root
- read -e -p "请输入端口号 (默认: 22): " port
- local port=${port:-22} # 如果用户未输入,则使用默认值 22
+kj_ssh_validate_user() {
+ local user="$1"
+ [[ -n "$user" && "$user" =~ ^[A-Za-z_][A-Za-z0-9._-]*$ ]]
+}
+
+kj_ssh_read_host_port() {
+ local host_prompt="$1"
+ local port_prompt="$2"
+ local default_port="${3:-22}"
+
+ while true; do
+ read -e -p "$host_prompt" KJ_SSH_HOST
+ if kj_ssh_validate_host "$KJ_SSH_HOST"; then
+ break
+ fi
+ echo "错误: 请输入有效的服务器地址。"
+ done
+
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ break
+ fi
+ echo "错误: 端口必须是 1-65535 之间的数字。"
+ done
+}
+
+kj_ssh_read_host_user_port() {
+ local host_prompt="$1"
+ local user_prompt="$2"
+ local port_prompt="$3"
+ local default_user="${4:-root}"
+ local default_port="${5:-22}"
+
+ kj_ssh_read_host_port "$host_prompt" "$port_prompt" "$default_port"
+
+ while true; do
+ read -e -p "$user_prompt" KJ_SSH_USER
+ KJ_SSH_USER=${KJ_SSH_USER:-$default_user}
+ if kj_ssh_validate_user "$KJ_SSH_USER"; then
+ break
+ fi
+ echo "错误: 用户名格式不正确。"
+ done
+}
+
+kj_ssh_parse_remote() {
+ local remote_raw="$1"
+ local default_user="${2:-root}"
+ local remote_user remote_host
+
+ if [[ "$remote_raw" == *@* ]]; then
+ remote_user="${remote_raw%@*}"
+ remote_host="${remote_raw#*@}"
+ else
+ remote_user="$default_user"
+ remote_host="$remote_raw"
+ fi
+
+ if ! kj_ssh_validate_user "$remote_user"; then
+ echo "错误: SSH 用户名格式不正确。"
+ return 1
+ fi
+
+ if ! kj_ssh_validate_host "$remote_host"; then
+ echo "错误: SSH 主机地址格式不正确。"
+ return 1
+ fi
+
+ KJ_SSH_USER="$remote_user"
+ KJ_SSH_HOST="$remote_host"
+ KJ_SSH_REMOTE="$remote_user@$remote_host"
+}
+
+kj_ssh_read_auth() {
+ local key_file="$1"
+ local password_or_key=""
echo "请选择身份验证方式:"
echo "1. 密码"
@@ -5833,37 +6937,92 @@ add_connection() {
case $auth_choice in
1)
read -s -p "请输入密码: " password_or_key
- echo # 换行
+ echo
+ if [ -z "$password_or_key" ]; then
+ echo "错误: 密码不能为空。"
+ return 1
+ fi
+ KJ_SSH_AUTH_METHOD="password"
+ KJ_SSH_AUTH_SECRET="$password_or_key"
;;
2)
echo "请粘贴密钥内容 (粘贴完成后按两次回车):"
- local password_or_key=""
while IFS= read -r line; do
- # 如果输入为空行且密钥内容已经包含了开头,则结束输入
if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
break
fi
- # 如果是第一行或已经开始输入密钥内容,则继续添加
if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- local password_or_key+="${line}"$'\n'
+ password_or_key+="${line}"$'\n'
fi
done
- # 检查是否是密钥内容
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/$name.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- local password_or_key="$key_file"
+ if [[ "$password_or_key" != *"-----BEGIN"* || "$password_or_key" != *"PRIVATE KEY-----"* ]]; then
+ echo "无效的密钥内容!"
+ return 1
fi
+
+ mkdir -p "$(dirname "$key_file")"
+ echo -n "$password_or_key" > "$key_file"
+ chmod 600 "$key_file"
+ KJ_SSH_AUTH_METHOD="key"
+ KJ_SSH_AUTH_SECRET="$key_file"
;;
*)
echo "无效的选择!"
- return
+ return 1
;;
esac
+}
+
+kj_ssh_read_password() {
+ local prompt="${1:-请输入密码: }"
+ while true; do
+ read -e -s -p "$prompt" KJ_SSH_PASSWORD
+ echo
+ [ -n "$KJ_SSH_PASSWORD" ] && break
+ echo "错误: 密码不能为空。"
+ done
+}
+
+kj_ssh_read_port() {
+ local port_prompt="$1"
+ local default_port="${2:-22}"
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ return 0
+ fi
+ echo "错误: 端口必须是 1-65535 之间的数字。"
+ done
+}
+
+# 显示连接列表
+list_connections() {
+ echo "已保存的连接:"
+ echo "------------------------"
+ cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
+ echo "------------------------"
+}
+
+
+# 添加新连接
+add_connection() {
+ send_stats "添加新连接"
+ echo "创建新连接示例:"
+ echo " - 连接名称: my_server"
+ echo " - IP地址: 192.168.1.100"
+ echo " - 用户名: root"
+ echo " - 端口: 22"
+ echo "------------------------"
+ read -e -p "请输入连接名称: " name
+
+ kj_ssh_read_host_user_port "请输入IP地址: " "请输入用户名 (默认: root): " "请输入端口号 (默认: 22): " "root" "22"
+ if ! kj_ssh_read_auth "$KEY_DIR/$name.key"; then
+ return
+ fi
- echo "$name|$ip|$user|$port|$password_or_key" >> "$CONFIG_FILE"
+ echo "$name|$KJ_SSH_HOST|$KJ_SSH_USER|$KJ_SSH_PORT|$KJ_SSH_AUTH_SECRET" >> "$CONFIG_FILE"
echo "连接已保存!"
}
@@ -5988,38 +7147,66 @@ list_partitions() {
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT | grep -v "sr\|loop"
}
-# 挂载分区
+
+# 持久化挂载分区
mount_partition() {
send_stats "挂载分区"
read -e -p "请输入要挂载的分区名称(例如 sda1): " PARTITION
+ DEVICE="/dev/$PARTITION"
+ MOUNT_POINT="/mnt/$PARTITION"
+
# 检查分区是否存在
- if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
+ if ! lsblk -no NAME | grep -qw "$PARTITION"; then
echo "分区不存在!"
- return
+ return 1
fi
- # 检查分区是否已经挂载
- if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
+ # 检查是否已挂载
+ if mount | grep -qw "$DEVICE"; then
echo "分区已经挂载!"
- return
+ return 1
+ fi
+
+ # 获取 UUID
+ UUID=$(blkid -s UUID -o value "$DEVICE")
+ if [ -z "$UUID" ]; then
+ echo "无法获取 UUID!"
+ return 1
+ fi
+
+ # 获取文件系统类型
+ FSTYPE=$(blkid -s TYPE -o value "$DEVICE")
+ if [ -z "$FSTYPE" ]; then
+ echo "无法获取文件系统类型!"
+ return 1
fi
# 创建挂载点
- MOUNT_POINT="/mnt/$PARTITION"
mkdir -p "$MOUNT_POINT"
- # 挂载分区
- mount "/dev/$PARTITION" "$MOUNT_POINT"
-
- if [ $? -eq 0 ]; then
- echo "分区挂载成功: $MOUNT_POINT"
- else
+ # 挂载
+ if ! mount "$DEVICE" "$MOUNT_POINT"; then
echo "分区挂载失败!"
rmdir "$MOUNT_POINT"
+ return 1
+ fi
+
+ echo "分区已成功挂载到 $MOUNT_POINT"
+
+ # 检查 /etc/fstab 是否已经存在 UUID 或挂载点
+ if grep -qE "UUID=$UUID|[[:space:]]$MOUNT_POINT[[:space:]]" /etc/fstab; then
+ echo "/etc/fstab 中已存在该分区记录,跳过写入"
+ return 0
fi
+
+ # 写入 /etc/fstab
+ echo "UUID=$UUID $MOUNT_POINT $FSTYPE defaults,nofail 0 2" >> /etc/fstab
+
+ echo "已写入 /etc/fstab,实现持久化挂载"
}
+
# 卸载分区
unmount_partition() {
send_stats "卸载分区"
@@ -6168,52 +7355,23 @@ add_task() {
read -e -p "请输入任务名称: " name
read -e -p "请输入本地目录: " local_path
read -e -p "请输入远程目录: " remote_path
- read -e -p "请输入远程用户@IP: " remote
- read -e -p "请输入 SSH 端口 (默认 22): " port
- port=${port:-22}
- echo "请选择身份验证方式:"
- echo "1. 密码"
- echo "2. 密钥"
- read -e -p "请选择 (1/2): " auth_choice
+ while true; do
+ read -e -p "请输入远程用户@IP: " remote
+ if kj_ssh_parse_remote "$remote" "root"; then
+ remote="$KJ_SSH_REMOTE"
+ break
+ fi
+ done
- case $auth_choice in
- 1)
- read -s -p "请输入密码: " password_or_key
- echo # 换行
- auth_method="password"
- ;;
- 2)
- echo "请粘贴密钥内容 (粘贴完成后按两次回车):"
- local password_or_key=""
- while IFS= read -r line; do
- # 如果输入为空行且密钥内容已经包含了开头,则结束输入
- if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
- break
- fi
- # 如果是第一行或已经开始输入密钥内容,则继续添加
- if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- password_or_key+="${line}"$'\n'
- fi
- done
+ kj_ssh_read_port "请输入 SSH 端口 (默认 22): " "22"
+ port="$KJ_SSH_PORT"
- # 检查是否是密钥内容
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/${name}_sync.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- password_or_key="$key_file"
- auth_method="key"
- else
- echo "无效的密钥内容!"
- return
- fi
- ;;
- *)
- echo "无效的选择!"
- return
- ;;
- esac
+ if ! kj_ssh_read_auth "$KEY_DIR/${name}_sync.key"; then
+ return
+ fi
+ auth_method="$KJ_SSH_AUTH_METHOD"
+ password_or_key="$KJ_SSH_AUTH_SECRET"
echo "请选择同步模式:"
echo "1. 标准模式 (-avz)"
@@ -6232,6 +7390,7 @@ add_task() {
echo "任务已保存!"
}
+
# 删除任务
delete_task() {
send_stats "删除同步任务"
@@ -6439,9 +7598,12 @@ rsync_manager() {
-linux_ps() {
+linux_info() {
+
+
clear
+ echo -e "${gl_kjlan}正在查询系统信息……${gl_bai}"
send_stats "系统信息查询"
ip_address
@@ -6490,43 +7652,46 @@ linux_ps() {
local timezone=$(current_timezone)
+ local tcp_count=$(ss -t | wc -l)
+ local udp_count=$(ss -u | wc -l)
- echo ""
+ clear
echo -e "系统信息查询"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}主机名: ${gl_bai}$hostname"
- echo -e "${gl_kjlan}系统版本: ${gl_bai}$os_info"
- echo -e "${gl_kjlan}Linux版本: ${gl_bai}$kernel_version"
+ echo -e "${gl_kjlan}主机名: ${gl_bai}$hostname"
+ echo -e "${gl_kjlan}系统版本: ${gl_bai}$os_info"
+ echo -e "${gl_kjlan}Linux版本: ${gl_bai}$kernel_version"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU架构: ${gl_bai}$cpu_arch"
- echo -e "${gl_kjlan}CPU型号: ${gl_bai}$cpu_info"
- echo -e "${gl_kjlan}CPU核心数: ${gl_bai}$cpu_cores"
- echo -e "${gl_kjlan}CPU频率: ${gl_bai}$cpu_freq"
+ echo -e "${gl_kjlan}CPU架构: ${gl_bai}$cpu_arch"
+ echo -e "${gl_kjlan}CPU型号: ${gl_bai}$cpu_info"
+ echo -e "${gl_kjlan}CPU核心数: ${gl_bai}$cpu_cores"
+ echo -e "${gl_kjlan}CPU频率: ${gl_bai}$cpu_freq"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU占用: ${gl_bai}$cpu_usage_percent%"
- echo -e "${gl_kjlan}系统负载: ${gl_bai}$load"
- echo -e "${gl_kjlan}物理内存: ${gl_bai}$mem_info"
- echo -e "${gl_kjlan}虚拟内存: ${gl_bai}$swap_info"
- echo -e "${gl_kjlan}硬盘占用: ${gl_bai}$disk_info"
+ echo -e "${gl_kjlan}CPU占用: ${gl_bai}$cpu_usage_percent%"
+ echo -e "${gl_kjlan}系统负载: ${gl_bai}$load"
+ echo -e "${gl_kjlan}TCP|UDP连接数: ${gl_bai}$tcp_count|$udp_count"
+ echo -e "${gl_kjlan}物理内存: ${gl_bai}$mem_info"
+ echo -e "${gl_kjlan}虚拟内存: ${gl_bai}$swap_info"
+ echo -e "${gl_kjlan}硬盘占用: ${gl_bai}$disk_info"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}总接收: ${gl_bai}$rx"
- echo -e "${gl_kjlan}总发送: ${gl_bai}$tx"
+ echo -e "${gl_kjlan}总接收: ${gl_bai}$rx"
+ echo -e "${gl_kjlan}总发送: ${gl_bai}$tx"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}网络算法: ${gl_bai}$congestion_algorithm $queue_algorithm"
+ echo -e "${gl_kjlan}网络算法: ${gl_bai}$congestion_algorithm $queue_algorithm"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}运营商: ${gl_bai}$isp_info"
+ echo -e "${gl_kjlan}运营商: ${gl_bai}$isp_info"
if [ -n "$ipv4_address" ]; then
- echo -e "${gl_kjlan}IPv4地址: ${gl_bai}$ipv4_address"
+ echo -e "${gl_kjlan}IPv4地址: ${gl_bai}$ipv4_address"
fi
if [ -n "$ipv6_address" ]; then
- echo -e "${gl_kjlan}IPv6地址: ${gl_bai}$ipv6_address"
+ echo -e "${gl_kjlan}IPv6地址: ${gl_bai}$ipv6_address"
fi
- echo -e "${gl_kjlan}DNS地址: ${gl_bai}$dns_addresses"
- echo -e "${gl_kjlan}地理位置: ${gl_bai}$country $city"
- echo -e "${gl_kjlan}系统时间: ${gl_bai}$timezone $current_time"
+ echo -e "${gl_kjlan}DNS地址: ${gl_bai}$dns_addresses"
+ echo -e "${gl_kjlan}地理位置: ${gl_bai}$country $city"
+ echo -e "${gl_kjlan}系统时间: ${gl_bai}$timezone $current_time"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}运行时长: ${gl_bai}$runtime"
+ echo -e "${gl_kjlan}运行时长: ${gl_bai}$runtime"
echo
@@ -6541,6 +7706,58 @@ linux_tools() {
clear
# send_stats "基础工具"
echo -e "基础工具"
+
+ tools=(
+ curl wget sudo socat htop iftop unzip tar tmux ffmpeg
+ btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders
+ vim nano git
+ )
+
+ if command -v apt >/dev/null 2>&1; then
+ PM="apt"
+ elif command -v dnf >/dev/null 2>&1; then
+ PM="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PM="yum"
+ elif command -v pacman >/dev/null 2>&1; then
+ PM="pacman"
+ elif command -v apk >/dev/null 2>&1; then
+ PM="apk"
+ elif command -v zypper >/dev/null 2>&1; then
+ PM="zypper"
+ elif command -v opkg >/dev/null 2>&1; then
+ PM="opkg"
+ elif command -v pkg >/dev/null 2>&1; then
+ PM="pkg"
+ else
+ echo "❌ 未识别的包管理器"
+ exit 1
+ fi
+
+ echo "📦 使用包管理器: $PM"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+
+ for ((i=0; i<${#tools[@]}; i+=2)); do
+ # 左列
+ if command -v "${tools[i]}" >/dev/null 2>&1; then
+ left=$(printf "✅ %-12s 已安装" "${tools[i]}")
+ else
+ left=$(printf "❌ %-12s 未安装" "${tools[i]}")
+ fi
+
+ # 右列(防止数组越界)
+ if [[ -n "${tools[i+1]}" ]]; then
+ if command -v "${tools[i+1]}" >/dev/null 2>&1; then
+ right=$(printf "✅ %-12s 已安装" "${tools[i+1]}")
+ else
+ right=$(printf "❌ %-12s 未安装" "${tools[i+1]}")
+ fi
+ printf "%-42s %s\n" "$left" "$right"
+ else
+ printf "%s\n" "$left"
+ fi
+ done
+
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}curl 下载工具 ${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}wget 下载工具 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}3. ${gl_bai}sudo 超级管理权限工具 ${gl_kjlan}4. ${gl_bai}socat 通信连接工具"
@@ -6551,7 +7768,7 @@ linux_tools() {
echo -e "${gl_kjlan}11. ${gl_bai}btop 现代化监控工具 ${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}ranger 文件管理工具"
echo -e "${gl_kjlan}13. ${gl_bai}ncdu 磁盘占用查看工具 ${gl_kjlan}14. ${gl_bai}fzf 全局搜索工具"
echo -e "${gl_kjlan}15. ${gl_bai}vim 文本编辑器 ${gl_kjlan}16. ${gl_bai}nano 文本编辑器 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}17. ${gl_bai}git 版本控制系统"
+ echo -e "${gl_kjlan}17. ${gl_bai}git 版本控制系统 ${gl_kjlan}18. ${gl_bai}opencode AI编程助手 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}黑客帝国屏保 ${gl_kjlan}22. ${gl_bai}跑火车屏保"
echo -e "${gl_kjlan}26. ${gl_bai}俄罗斯方块小游戏 ${gl_kjlan}27. ${gl_bai}贪吃蛇小游戏"
@@ -6710,6 +7927,17 @@ linux_tools() {
send_stats "安装git"
;;
+ 18)
+ clear
+ cd ~
+ curl -fsSL https://opencode.ai/install | bash
+ source ~/.bashrc
+ source ~/.profile
+ opencode
+ send_stats "安装opencode"
+ ;;
+
+
21)
clear
install cmatrix
@@ -6738,6 +7966,7 @@ linux_tools() {
nsnake
send_stats "安装nsnake"
;;
+
28)
clear
install ninvaders
@@ -6763,6 +7992,8 @@ linux_tools() {
clear
send_stats "全部卸载"
remove htop iftop tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
+ opencode uninstall
+ rm -rf ~/.opencode
;;
41)
@@ -6820,7 +8051,7 @@ linux_bbr() {
send_stats "alpine开启bbr3"
;;
2)
- sed -i '/net.ipv4.tcp_congestion_control=bbr/d' /etc/sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control=/d' /etc/sysctl.conf
sysctl -p
server_reboot
;;
@@ -6844,15 +8075,336 @@ linux_bbr() {
-linux_docker() {
+docker_ssh_migration() {
- while true; do
- clear
- # send_stats "docker管理"
- echo -e "Docker管理"
- docker_tato
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}安装更新Docker环境 ${gl_huang}★${gl_bai}"
+ is_compose_container() {
+ local container=$1
+ docker inspect "$container" | jq -e '.[0].Config.Labels["com.docker.compose.project"]' >/dev/null 2>&1
+ }
+
+ list_backups() {
+ local BACKUP_ROOT="/tmp"
+ echo -e "${gl_kjlan}当前备份列表:${gl_bai}"
+ ls -1dt ${BACKUP_ROOT}/docker_backup_* 2>/dev/null || echo "无备份"
+ }
+
+
+
+ # ----------------------------
+ # 备份
+ # ----------------------------
+ backup_docker() {
+ send_stats "Docker备份"
+
+ echo -e "${gl_kjlan}正在备份 Docker 容器...${gl_bai}"
+ docker ps --format '{{.Names}}'
+ read -e -p "请输入要备份的容器名(多个空格分隔,回车备份全部运行中容器): " containers
+
+ install tar jq gzip
+ install_docker
+
+ local BACKUP_ROOT="/tmp"
+ local DATE_STR=$(date +%Y%m%d_%H%M%S)
+ local TARGET_CONTAINERS=()
+ if [ -z "$containers" ]; then
+ mapfile -t TARGET_CONTAINERS < <(docker ps --format '{{.Names}}')
+ else
+ read -ra TARGET_CONTAINERS <<< "$containers"
+ fi
+ [[ ${#TARGET_CONTAINERS[@]} -eq 0 ]] && { echo -e "${gl_hong}没有找到容器${gl_bai}"; return; }
+
+ local BACKUP_DIR="${BACKUP_ROOT}/docker_backup_${DATE_STR}"
+ mkdir -p "$BACKUP_DIR"
+
+ local RESTORE_SCRIPT="${BACKUP_DIR}/docker_restore.sh"
+ echo "#!/bin/bash" > "$RESTORE_SCRIPT"
+ echo "set -e" >> "$RESTORE_SCRIPT"
+ echo "# 自动生成的还原脚本" >> "$RESTORE_SCRIPT"
+
+ # 记录已打包过的 Compose 项目路径,避免重复打包
+ declare -A PACKED_COMPOSE_PATHS=()
+
+ for c in "${TARGET_CONTAINERS[@]}"; do
+ echo -e "${gl_lv}备份容器: $c${gl_bai}"
+ local inspect_file="${BACKUP_DIR}/${c}_inspect.json"
+ docker inspect "$c" > "$inspect_file"
+
+ if is_compose_container "$c"; then
+ echo -e "${gl_kjlan}检测到 $c 是 docker-compose 容器${gl_bai}"
+ local project_dir=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty')
+ local project_name=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty')
+
+ if [ -z "$project_dir" ]; then
+ read -e -p "未检测到 compose 目录,请手动输入路径: " project_dir
+ fi
+
+ # 如果该 Compose 项目已经打包过,跳过
+ if [[ -n "${PACKED_COMPOSE_PATHS[$project_dir]}" ]]; then
+ echo -e "${gl_huang}Compose 项目 [$project_name] 已备份过,跳过重复打包...${gl_bai}"
+ continue
+ fi
+
+ if [ -f "$project_dir/docker-compose.yml" ]; then
+ echo "compose" > "${BACKUP_DIR}/backup_type_${project_name}"
+ echo "$project_dir" > "${BACKUP_DIR}/compose_path_${project_name}.txt"
+ tar -czf "${BACKUP_DIR}/compose_project_${project_name}.tar.gz" -C "$project_dir" .
+ echo "# docker-compose 恢复: $project_name" >> "$RESTORE_SCRIPT"
+ echo "cd \"$project_dir\" && docker compose up -d" >> "$RESTORE_SCRIPT"
+ PACKED_COMPOSE_PATHS["$project_dir"]=1
+ echo -e "${gl_lv}Compose 项目 [$project_name] 已打包: ${project_dir}${gl_bai}"
+ else
+ echo -e "${gl_hong}未找到 docker-compose.yml,跳过此容器...${gl_bai}"
+ fi
+ else
+ # 普通容器备份卷
+ local VOL_PATHS
+ VOL_PATHS=$(docker inspect "$c" --format '{{range .Mounts}}{{.Source}} {{end}}')
+ for path in $VOL_PATHS; do
+ echo "打包卷: $path"
+ tar -czpf "${BACKUP_DIR}/${c}_$(basename $path).tar.gz" -C / "$(echo $path | sed 's/^\///')"
+ done
+
+ # 端口
+ local PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[] | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$inspect_file" 2>/dev/null)
+ for p in "${PORTS[@]}"; do PORT_ARGS+="-p $p "; done
+
+ # 环境变量
+ local ENV_VARS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[] | @sh' "$inspect_file")
+ for e in "${ENVS[@]}"; do ENV_VARS+="-e $e "; done
+
+ # 卷映射
+ local VOL_ARGS=""
+ for path in $VOL_PATHS; do VOL_ARGS+="-v $path:$path "; done
+
+ # 镜像
+ local IMAGE
+ IMAGE=$(jq -r '.[0].Config.Image' "$inspect_file")
+
+ echo -e "\n# 还原容器: $c" >> "$RESTORE_SCRIPT"
+ echo "docker run -d --name $c $PORT_ARGS $VOL_ARGS $ENV_VARS $IMAGE" >> "$RESTORE_SCRIPT"
+ fi
+ done
+
+
+ # 备份 /home/docker 下的所有文件(不含子目录)
+ if [ -d "/home/docker" ]; then
+ echo -e "${gl_kjlan}备份 /home/docker 下的文件...${gl_bai}"
+ find /home/docker -maxdepth 1 -type f | tar -czf "${BACKUP_DIR}/home_docker_files.tar.gz" -T -
+ echo -e "${gl_lv}/home/docker 下的文件已打包到: ${BACKUP_DIR}/home_docker_files.tar.gz${gl_bai}"
+ fi
+
+ chmod +x "$RESTORE_SCRIPT"
+ echo -e "${gl_lv}备份完成: ${BACKUP_DIR}${gl_bai}"
+ echo -e "${gl_lv}可用还原脚本: ${RESTORE_SCRIPT}${gl_bai}"
+
+
+ }
+
+ # ----------------------------
+ # 还原
+ # ----------------------------
+ restore_docker() {
+
+ send_stats "Docker还原"
+ read -e -p "请输入要还原的备份目录: " BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}备份目录不存在${gl_bai}"; return; }
+
+ echo -e "${gl_kjlan}开始执行还原操作...${gl_bai}"
+
+ install tar jq gzip
+ install_docker
+
+ # --------- 优先还原 Compose 项目 ---------
+ for f in "$BACKUP_DIR"/backup_type_*; do
+ [[ ! -f "$f" ]] && continue
+ if grep -q "compose" "$f"; then
+ project_name=$(basename "$f" | sed 's/backup_type_//')
+ path_file="$BACKUP_DIR/compose_path_${project_name}.txt"
+ [[ -f "$path_file" ]] && original_path=$(cat "$path_file") || original_path=""
+ [[ -z "$original_path" ]] && read -e -p "未找到原始路径,请输入还原目录路径: " original_path
+
+ # 检查该 compose 项目的容器是否已经在运行
+ running_count=$(docker ps --filter "label=com.docker.compose.project=$project_name" --format '{{.Names}}' | wc -l)
+ if [[ "$running_count" -gt 0 ]]; then
+ echo -e "${gl_huang}Compose 项目 [$project_name] 已有容器在运行,跳过还原...${gl_bai}"
+ continue
+ fi
+
+ read -e -p "确认还原 Compose 项目 [$project_name] 到路径 [$original_path] ? (y/n): " confirm
+ [[ "$confirm" != "y" ]] && read -e -p "请输入新的还原路径: " original_path
+
+ mkdir -p "$original_path"
+ tar -xzf "$BACKUP_DIR/compose_project_${project_name}.tar.gz" -C "$original_path"
+ echo -e "${gl_lv}Compose 项目 [$project_name] 已解压到: $original_path${gl_bai}"
+
+ cd "$original_path" || return
+ docker compose down || true
+ docker compose up -d
+ echo -e "${gl_lv}Compose 项目 [$project_name] 还原完成!${gl_bai}"
+ fi
+ done
+
+ # --------- 继续还原普通容器 ---------
+ echo -e "${gl_kjlan}检查并还原普通 Docker 容器...${gl_bai}"
+ local has_container=false
+ for json in "$BACKUP_DIR"/*_inspect.json; do
+ [[ ! -f "$json" ]] && continue
+ has_container=true
+ container=$(basename "$json" | sed 's/_inspect.json//')
+ echo -e "${gl_lv}处理容器: $container${gl_bai}"
+
+ # 检查容器是否已经存在且正在运行
+ if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] 已在运行,跳过还原...${gl_bai}"
+ continue
+ fi
+
+ IMAGE=$(jq -r '.[0].Config.Image' "$json")
+ [[ -z "$IMAGE" || "$IMAGE" == "null" ]] && { echo -e "${gl_hong}未找到镜像信息,跳过: $container${gl_bai}"; continue; }
+
+ # 端口映射
+ PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$json")
+ for p in "${PORTS[@]}"; do
+ [[ -n "$p" ]] && PORT_ARGS="$PORT_ARGS -p $p"
+ done
+
+ # 环境变量
+ ENV_ARGS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[]' "$json")
+ for e in "${ENVS[@]}"; do
+ ENV_ARGS="$ENV_ARGS -e \"$e\""
+ done
+
+ # 卷映射 + 卷数据恢复
+ VOL_ARGS=""
+ mapfile -t VOLS < <(jq -r '.[0].Mounts[] | "\(.Source):\(.Destination)"' "$json")
+ for v in "${VOLS[@]}"; do
+ VOL_SRC=$(echo "$v" | cut -d':' -f1)
+ VOL_DST=$(echo "$v" | cut -d':' -f2)
+ mkdir -p "$VOL_SRC"
+ VOL_ARGS="$VOL_ARGS -v $VOL_SRC:$VOL_DST"
+
+ VOL_FILE="$BACKUP_DIR/${container}_$(basename $VOL_SRC).tar.gz"
+ if [[ -f "$VOL_FILE" ]]; then
+ echo "恢复卷数据: $VOL_SRC"
+ tar -xzf "$VOL_FILE" -C /
+ fi
+ done
+
+ # 删除已存在但未运行的容器
+ if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] 存在但未运行,删除旧容器...${gl_bai}"
+ docker rm -f "$container"
+ fi
+
+ # 启动容器
+ echo "执行还原命令: docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ eval "docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ done
+
+ [[ "$has_container" == false ]] && echo -e "${gl_huang}未找到普通容器的备份信息${gl_bai}"
+
+ # 还原 /home/docker 下的文件
+ if [ -f "$BACKUP_DIR/home_docker_files.tar.gz" ]; then
+ echo -e "${gl_kjlan}正在还原 /home/docker 下的文件...${gl_bai}"
+ mkdir -p /home/docker
+ tar -xzf "$BACKUP_DIR/home_docker_files.tar.gz" -C /
+ echo -e "${gl_lv}/home/docker 下的文件已还原完成${gl_bai}"
+ else
+ echo -e "${gl_huang}未找到 /home/docker 下文件的备份,跳过...${gl_bai}"
+ fi
+
+
+ }
+
+
+ # ----------------------------
+ # 迁移
+ # ----------------------------
+ migrate_docker() {
+ send_stats "Docker迁移"
+ install jq
+ read -e -p "请输入要迁移的备份目录: " BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}备份目录不存在${gl_bai}"; return; }
+
+ kj_ssh_read_host_user_port "目标服务器IP: " "目标服务器SSH用户名 [默认root]: " "目标服务器SSH端口 [默认22]: " "root" "22"
+ local TARGET_IP="$KJ_SSH_HOST"
+ local TARGET_USER="$KJ_SSH_USER"
+ local TARGET_PORT="$KJ_SSH_PORT"
+
+ local LATEST_TAR="$BACKUP_DIR"
+
+ echo -e "${gl_huang}传输备份中...${gl_bai}"
+ if [[ -z "$TARGET_PASS" ]]; then
+ # 使用密钥登录
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no -r "$LATEST_TAR" "$TARGET_USER@$TARGET_IP:/tmp/"
+ fi
+
+ }
+
+ # ----------------------------
+ # 删除备份
+ # ----------------------------
+ delete_backup() {
+ send_stats "Docker备份文件删除"
+ read -e -p "请输入要删除的备份目录: " BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}备份目录不存在${gl_bai}"; return; }
+ rm -rf "$BACKUP_DIR"
+ echo -e "${gl_lv}已删除备份: ${BACKUP_DIR}${gl_bai}"
+ }
+
+ # ----------------------------
+ # 主菜单
+ # ----------------------------
+ main_menu() {
+ send_stats "Docker备份迁移还原"
+ while true; do
+ clear
+ echo "------------------------"
+ echo -e "Docker备份/迁移/还原工具"
+ echo "------------------------"
+ list_backups
+ echo -e ""
+ echo "------------------------"
+ echo -e "1. 备份docker项目"
+ echo -e "2. 迁移docker项目"
+ echo -e "3. 还原docker项目"
+ echo -e "4. 删除docker项目的备份文件"
+ echo "------------------------"
+ echo -e "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "请选择: " choice
+ case $choice in
+ 1) backup_docker ;;
+ 2) migrate_docker ;;
+ 3) restore_docker ;;
+ 4) delete_backup ;;
+ 0) return ;;
+ *) echo -e "${gl_hong}无效选项${gl_bai}" ;;
+ esac
+ break_end
+ done
+ }
+
+ main_menu
+}
+
+
+
+
+
+linux_docker() {
+
+ while true; do
+ clear
+ # send_stats "docker管理"
+ echo -e "Docker管理"
+ docker_tato
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}安装更新Docker环境 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}2. ${gl_bai}查看Docker全局状态 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
@@ -6869,6 +8421,7 @@ linux_docker() {
echo -e "${gl_kjlan}11. ${gl_bai}开启Docker-ipv6访问"
echo -e "${gl_kjlan}12. ${gl_bai}关闭Docker-ipv6访问"
echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}19. ${gl_bai}备份/迁移/还原Docker环境"
echo -e "${gl_kjlan}20. ${gl_bai}卸载Docker环境"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
@@ -7076,6 +8629,9 @@ linux_docker() {
restart docker
;;
+
+
+
11)
clear
send_stats "Docker v6 开"
@@ -7088,6 +8644,11 @@ linux_docker() {
docker_ipv6_off
;;
+ 19)
+ docker_ssh_migration
+ ;;
+
+
20)
clear
send_stats "Docker卸载"
@@ -7147,6 +8708,7 @@ linux_test() {
echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 三网线路测试"
echo -e "${gl_kjlan}17. ${gl_bai}i-abc 多功能测速脚本"
echo -e "${gl_kjlan}18. ${gl_bai}NetQuality 网络质量体检脚本 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}19. ${gl_bai}TcpQuality TCP重传探测脚本 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}硬件性能测试"
@@ -7157,6 +8719,7 @@ linux_test() {
echo -e "${gl_kjlan}综合性测试"
echo -e "${gl_kjlan}31. ${gl_bai}bench 性能测试"
echo -e "${gl_kjlan}32. ${gl_bai}spiritysdx 融合怪测评 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}nodequality 融合怪测评 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
echo -e "${gl_kjlan}------------------------${gl_bai}"
@@ -7253,6 +8816,12 @@ linux_test() {
bash <(curl -sL Net.Check.Place)
;;
+ 19)
+ clear
+ send_stats "TcpQuality TCP重传探测脚本"
+ bash <(curl -sL https://raw.githubusercontent.com/ibsgss/TcpQuality/main/runTcpQuality.sh)
+ ;;
+
21)
clear
send_stats "yabs性能测试"
@@ -7274,9 +8843,17 @@ linux_test() {
32)
send_stats "spiritysdx融合怪测评"
clear
- curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ curl -L ${gh_proxy}github.com/spiritLHLS/ecs/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
;;
+ 33)
+ send_stats "nodequality融合怪测评"
+ clear
+ bash <(curl -sL https://run.NodeQuality.com)
+ ;;
+
+
+
0)
kejilion
@@ -7343,7 +8920,7 @@ linux_Oracle() {
local speedtest_interval=${speedtest_interval:-$DEFAULT_SPEEDTEST_INTERVAL}
# 运行Docker容器
- docker run -itd --name=lookbusy --restart=always \
+ docker run -d --name=lookbusy --restart=always \
-e TZ=Asia/Shanghai \
-e CPU_UTIL="$cpu_util" \
-e CPU_CORE="$cpu_core" \
@@ -7411,12 +8988,12 @@ linux_Oracle() {
4)
clear
- echo "该功能处于开发阶段,敬请期待!"
+ send_stats "R探长开机脚本"
+ bash <(wget -qO- ${gh_proxy}github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh)
;;
5)
clear
add_sshpasswd
-
;;
6)
clear
@@ -7441,6 +9018,9 @@ linux_Oracle() {
}
+
+
+
docker_tato() {
local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
@@ -7458,20 +9038,20 @@ docker_tato() {
ldnmp_tato() {
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
-local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml 2>/dev/null | tr -d '[:space:]')
if [ -n "$dbrootpasswd" ]; then
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
fi
-local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+local db_output="${gl_lv}${db_count}${gl_bai}"
if command -v docker &>/dev/null; then
if docker ps --filter "name=nginx" --filter "status=running" | grep -q nginx; then
echo -e "${gl_huang}------------------------"
- echo -e "${gl_lv}环境已安装${gl_bai} $output $db_output"
+ echo -e "${gl_lv}环境已安装${gl_bai} 站点: $output 数据库: $db_output"
fi
fi
@@ -7513,7 +9093,7 @@ linux_ldnmp() {
echo -e "${gl_huang}23. ${gl_bai}站点反向代理-IP+端口 ${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}站点反向代理-域名"
echo -e "${gl_huang}25. ${gl_bai}安装Bitwarden密码管理平台 ${gl_huang}26. ${gl_bai}安装Halo博客网站"
echo -e "${gl_huang}27. ${gl_bai}安装AI绘画提示词生成器 ${gl_huang}28. ${gl_bai}站点反向代理-负载均衡"
- echo -e "${gl_huang}30. ${gl_bai}自定义静态站点"
+ echo -e "${gl_huang}29. ${gl_bai}Stream四层代理转发 ${gl_huang}30. ${gl_bai}自定义静态站点"
echo -e "${gl_huang}------------------------"
echo -e "${gl_huang}31. ${gl_bai}站点数据管理 ${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}备份全站数据"
echo -e "${gl_huang}33. ${gl_bai}定时远程备份 ${gl_huang}34. ${gl_bai}还原全站数据"
@@ -7544,19 +9124,27 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/discuz.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/main/Discuz_X3.5_SC_UTF8_20240520.zip
- unzip latest.zip
+ LATEST_URL=$(curl -s https://api.gitee.com/api/v5/repos/Discuz/DiscuzX/releases/latest | grep -o 'https://[^"]*Discuz_X[^"]*SC_UTF8[^"]*\.zip' | head -n 1)
+ wget -O latest.zip ${LATEST_URL}
+ unzip -q latest.zip
+ mv upload/* .
+ rm -rf upload readme readme.html utility.html LICENSE qqqun.png
rm latest.zip
restart_ldnmp
@@ -7581,18 +9169,22 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/kdy.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/1.50.02.zip
+ LATEST_VERSION=$(curl -s https://api.github.com/repos/kalcaddle/kodbox/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/${LATEST_VERSION}.zip
unzip -o latest.zip
rm latest.zip
mv /home/web/html/$yuming/kodbox* /home/web/html/$yuming/kodbox
@@ -7616,12 +9208,17 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/maccms.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7659,12 +9256,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/dujiaoka.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7707,12 +9310,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/flarum.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec php rm -f /usr/local/etc/php/conf.d/optimized_php.ini
@@ -7733,9 +9342,13 @@ linux_ldnmp() {
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/sitemap"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/oauth"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/best-answer:*"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/upload"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/gamification"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/byobu:*"
docker exec php sh -c "cd /var/www/html/$yuming && composer require v17development/flarum-seo"
docker exec php sh -c "cd /var/www/html/$yuming && composer require clarkwinkelmann/flarum-ext-emojionearea"
+
restart_ldnmp
@@ -7758,12 +9371,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/typecho.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7796,13 +9415,17 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/refs/heads/main/index_php.conf
sed -i "s|/var/www/html/yuming.com/|/var/www/html/yuming.com/linkstack|g" /home/web/conf.d/$yuming.conf
sed -i "s|yuming.com|$yuming|g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7832,12 +9455,15 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/index_php.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7966,12 +9592,17 @@ linux_ldnmp() {
add_yuming
read -e -p "请输入跳转域名: " reverseproxy
nginx_install_status
+
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/rewrite.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s/baidu.com/$reverseproxy/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7989,6 +9620,7 @@ linux_ldnmp() {
echo "已阻止IP+端口访问该服务"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
@@ -8003,12 +9635,16 @@ linux_ldnmp() {
echo -e "域名格式: ${gl_huang}google.com${gl_bai}"
read -e -p "请输入你的反代域名: " fandai_yuming
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-domain.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s|fandaicom|$fandai_yuming|g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -8024,20 +9660,17 @@ linux_ldnmp() {
send_stats "安装$webname"
echo "开始部署 $webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
docker run -d \
--name bitwarden \
- --restart always \
+ --restart=always \
-p 3280:80 \
-v /home/web/html/$yuming/bitwarden/data:/data \
vaultwarden/server
+
duankou=3280
- reverse_proxy
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
- nginx_web_on
;;
@@ -8047,15 +9680,11 @@ linux_ldnmp() {
send_stats "安装$webname"
echo "开始部署 $webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
- docker run -d --name halo --restart always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- duankou=8010
- reverse_proxy
+ docker run -d --name halo --restart=always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- nginx_web_on
+ duankou=8010
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
;;
@@ -8066,11 +9695,14 @@ linux_ldnmp() {
echo "开始部署 $webname"
add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8093,6 +9725,10 @@ linux_ldnmp() {
;;
+ 29)
+ stream_panel
+ ;;
+
30)
clear
webname="静态站点"
@@ -8101,11 +9737,14 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8160,7 +9799,7 @@ linux_ldnmp() {
send_stats "LDNMP环境备份"
local backup_filename="web_$(date +"%Y%m%d%H%M%S").tar.gz"
- echo -e "${gl_huang}正在备份 $backup_filename ...${gl_bai}"
+ echo -e "${gl_kjlan}正在备份 $backup_filename ...${gl_bai}"
cd /home/ && tar czvf "$backup_filename" web
while true; do
@@ -8169,16 +9808,14 @@ linux_ldnmp() {
read -e -p "要传送备份数据到远程服务器吗?(Y/N): " choice
case "$choice" in
[Yy])
- read -e -p "请输入远端服务器IP: " remote_ip
- if [ -z "$remote_ip" ]; then
- echo "错误: 请输入远端服务器IP。"
- continue
- fi
+ kj_ssh_read_host_port "请输入远端服务器IP: " "目标服务器SSH端口 [默认22]: " "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
local latest_tar=$(ls -t /home/*.tar.gz | head -1)
if [ -n "$latest_tar" ]; then
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 添加等待时间
- scp -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
echo "文件已传送至远程服务器home目录。"
else
echo "未找到要传送的文件。"
@@ -8256,10 +9893,9 @@ linux_ldnmp() {
docker compose down > /dev/null 2>&1
rm -rf /home/web > /dev/null 2>&1
- echo -e "${gl_huang}正在解压 $filename ...${gl_bai}"
+ echo -e "${gl_kjlan}正在解压 $filename ...${gl_bai}"
cd /home/ && tar -xzf "$filename"
- check_port
install_dependency
install_docker
install_certbot
@@ -8383,7 +10019,6 @@ linux_ldnmp() {
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods > /dev/null 2>&1
- restart_redis
send_stats "更新$ldnmp_pods"
echo "更新${ldnmp_pods}完成"
@@ -8396,7 +10031,6 @@ linux_ldnmp() {
cd /home/web/
docker compose down --rmi all
- check_port
install_dependency
install_docker
install_certbot
@@ -8452,2386 +10086,9380 @@ linux_ldnmp() {
-linux_panel() {
+moltbot_menu() {
+ local app_id="114"
+ send_stats "clawdbot/moltbot管理"
- while true; do
- clear
- echo -e "应用市场"
- echo -e "${gl_kjlan}------------------------"
+ check_openclaw_update() {
+ if ! command -v npm >/dev/null 2>&1; then
+ return 1
+ fi
- local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+ # 加上 --no-update-notifier,并确保错误重定向位置正确
+ local_version=$(npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null | grep openclaw | awk '{print $NF}' | sed 's/^.*@//')
- # 用循环设置颜色
- for i in {1..100}; do
- if echo "$app_numbers" | grep -q "^$i$"; then
- declare "color$i=${gl_lv}"
- else
- declare "color$i=${gl_bai}"
- fi
- done
+ if [ -z "$local_version" ]; then
+ return 1
+ fi
- echo -e "${gl_kjlan}1. ${color1}宝塔面板官方版 ${gl_kjlan}2. ${color2}aaPanel宝塔国际版"
- echo -e "${gl_kjlan}3. ${color3}1Panel新一代管理面板 ${gl_kjlan}4. ${color4}NginxProxyManager可视化面板"
- echo -e "${gl_kjlan}5. ${color5}OpenList多存储文件列表程序 ${gl_kjlan}6. ${color6}Ubuntu远程桌面网页版"
- echo -e "${gl_kjlan}7. ${color7}哪吒探针VPS监控面板 ${gl_kjlan}8. ${color8}QB离线BT磁力下载面板"
- echo -e "${gl_kjlan}9. ${color9}Poste.io邮件服务器程序 ${gl_kjlan}10. ${color10}RocketChat多人在线聊天系统"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${color11}禅道项目管理软件 ${gl_kjlan}12. ${color12}青龙面板定时任务管理平台"
- echo -e "${gl_kjlan}13. ${color13}Cloudreve网盘 ${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}简单图床图片管理程序"
- echo -e "${gl_kjlan}15. ${color15}emby多媒体管理系统 ${gl_kjlan}16. ${color16}Speedtest测速面板"
- echo -e "${gl_kjlan}17. ${color17}AdGuardHome去广告软件 ${gl_kjlan}18. ${color18}onlyoffice在线办公OFFICE"
- echo -e "${gl_kjlan}19. ${color19}雷池WAF防火墙面板 ${gl_kjlan}20. ${color20}portainer容器管理面板"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${color21}VScode网页版 ${gl_kjlan}22. ${color22}UptimeKuma监控工具"
- echo -e "${gl_kjlan}23. ${color23}Memos网页备忘录 ${gl_kjlan}24. ${color24}Webtop远程桌面网页版 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}25. ${color25}Nextcloud网盘 ${gl_kjlan}26. ${color26}QD-Today定时任务管理框架"
- echo -e "${gl_kjlan}27. ${color27}Dockge容器堆栈管理面板 ${gl_kjlan}28. ${color28}LibreSpeed测速工具"
- echo -e "${gl_kjlan}29. ${color29}searxng聚合搜索站 ${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism私有相册系统"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${color31}StirlingPDF工具大全 ${gl_kjlan}32. ${color32}drawio免费的在线图表软件 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${color33}Sun-Panel导航面板 ${gl_kjlan}34. ${color34}Pingvin-Share文件分享平台"
- echo -e "${gl_kjlan}35. ${color35}极简朋友圈 ${gl_kjlan}36. ${color36}LobeChatAI聊天聚合网站"
- echo -e "${gl_kjlan}37. ${color37}MyIP工具箱 ${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}小雅alist全家桶"
- echo -e "${gl_kjlan}39. ${color39}Bililive直播录制工具 ${gl_kjlan}40. ${color40}webssh网页版SSH连接工具"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${color41}耗子管理面板 ${gl_kjlan}42. ${color42}Nexterm远程连接工具"
- echo -e "${gl_kjlan}43. ${color43}RustDesk远程桌面(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk远程桌面(中继端) ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}45. ${color45}Docker加速站 ${gl_kjlan}46. ${color46}GitHub加速站 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}47. ${color47}普罗米修斯监控 ${gl_kjlan}48. ${color48}普罗米修斯(主机监控)"
- echo -e "${gl_kjlan}49. ${color49}普罗米修斯(容器监控) ${gl_kjlan}50. ${color50}补货监控工具"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}51. ${color51}PVE开小鸡面板 ${gl_kjlan}52. ${color52}DPanel容器管理面板"
- echo -e "${gl_kjlan}53. ${color53}llama3聊天AI大模型 ${gl_kjlan}54. ${color54}AMH主机建站管理面板"
- echo -e "${gl_kjlan}55. ${color55}FRP内网穿透(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP内网穿透(客户端) ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}57. ${color57}Deepseek聊天AI大模型 ${gl_kjlan}58. ${color58}Dify大模型知识库 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}59. ${color59}NewAPI大模型资产管理 ${gl_kjlan}60. ${color60}JumpServer开源堡垒机"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}61. ${color61}在线翻译服务器 ${gl_kjlan}62. ${color62}RAGFlow大模型知识库"
- echo -e "${gl_kjlan}63. ${color63}OpenWebUI自托管AI平台 ${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools工具箱"
- echo -e "${gl_kjlan}65. ${color65}n8n自动化工作流平台 ${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp视频下载工具"
- echo -e "${gl_kjlan}67. ${color67}ddns-go动态DNS管理工具 ${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL证书管理平台"
- echo -e "${gl_kjlan}69. ${color69}SFTPGo文件传输工具 ${gl_kjlan}70. ${color70}AstrBot聊天机器人框架"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}71. ${color71}Navidrome私有音乐服务器 ${gl_kjlan}72. ${color72}bitwarden密码管理器 ${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}73. ${color73}LibreTV私有影视 ${gl_kjlan}74. ${color74}MoonTV私有影视"
- echo -e "${gl_kjlan}75. ${color75}Melody音乐精灵 ${gl_kjlan}76. ${color76}在线DOS老游戏"
- echo -e "${gl_kjlan}77. ${color77}迅雷离线下载工具 ${gl_kjlan}78. ${color78}PandaWiki智能文档管理系统"
- echo -e "${gl_kjlan}79. ${color79}Beszel服务器监控 ${gl_kjlan}80. ${color80}linkwarden书签管理"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}81. ${color81}JitsiMeet视频会议"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "请输入你的选择: " sub_choice
+ remote_version=$(npm view openclaw version --no-update-notifier 2>/dev/null)
- case $sub_choice in
- 1)
+ if [ -z "$remote_version" ]; then
+ return 1
+ fi
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="宝塔面板"
- local panelurl="https://www.bt.cn/new/index.html"
+ if [ "$local_version" != "$remote_version" ]; then
+ echo "${gl_huang}检测到新版本:$remote_version${gl_bai}"
+ else
+ echo "${gl_lv}当前版本已是最新:$local_version${gl_bai}"
+ fi
+ }
- panel_app_install() {
- if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
- }
- panel_app_manage() {
- bt
- }
+ get_install_status() {
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "${gl_lv}已安装${gl_bai}"
+ else
+ echo "${gl_hui}未安装${gl_bai}"
+ fi
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ get_running_status() {
+ if pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
+ echo "${gl_lv}运行中${gl_bai}"
+ else
+ echo "${gl_hui}未运行${gl_bai}"
+ fi
+ }
- install_panel
+ show_menu() {
- ;;
- 2)
+ clear
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="aapanel"
- local panelurl="https://www.aapanel.com/new/index.html"
+ local install_status=$(get_install_status)
+ local running_status=$(get_running_status)
+ local update_message=$(check_openclaw_update)
+
+ echo "======================================="
+ echo -e "🦞 OPENCLAW 管理工具 by KEJILION 🦞"
+ echo -e "💡 终端执行 \033[1;33mk claw\033[0m 快速进入菜单"
+ echo -e "$install_status $running_status $update_message"
+ echo "======================================="
+ echo "1. 安装"
+ echo "2. 启动"
+ echo "3. 停止"
+ echo "--------------------"
+ echo "4. 状态日志查看"
+ echo "5. 换模型"
+ echo "6. API管理"
+ echo "7. 机器人连接对接"
+ echo "8. 插件管理(安装/删除)"
+ echo "9. 技能管理(安装/删除)"
+ echo "10. 编辑主配置文件"
+ echo "11. 配置向导"
+ echo "12. 健康检测与修复"
+ echo "13. WebUI访问与设置"
+ echo "14. TUI命令行对话窗口"
+ echo "15. 记忆/Memory"
+ echo "16. 权限管理"
+ echo "17. 多智能体管理"
+ echo "--------------------"
+ echo "18. 备份与还原"
+ echo "19. 更新"
+ echo "20. 卸载"
+ echo "--------------------"
+ echo "0. 返回上一级选单"
+ echo "--------------------"
+ printf "请输入选项并回车: "
+ }
- panel_app_install() {
- URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
- }
- panel_app_manage() {
- bt
- }
+ start_gateway() {
+ openclaw gateway stop
+ openclaw gateway start
+ sleep 3
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
- install_panel
+ install_node_and_tools() {
+ if command -v dnf &>/dev/null; then
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
+ dnf update -y
+ dnf group install -y "Development Tools" "Development Libraries"
+ dnf install -y cmake libatomic nodejs
+ fi
- ;;
- 3)
+ if command -v apt &>/dev/null; then
+ curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
+ apt update -y
+ apt install build-essential python3 libatomic1 nodejs -y
+ fi
+ }
- local lujing="command -v 1pctl"
- local panelname="1Panel"
- local panelurl="https://1panel.cn/"
+ sync_openclaw_api_models() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ [ ! -f "$config_file" ] && return 0
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$ENABLE_STATS" "$sh_v" <<'PY'
+import copy
+import json
+import os
+import platform
+import sys
+import time
+import urllib.request
+from datetime import datetime, timezone
+
+path = sys.argv[1]
+stats_enabled = (sys.argv[2].lower() == "true") if len(sys.argv) > 2 else True
+script_version = sys.argv[3] if len(sys.argv) > 3 else ""
+
+def send_stat(action):
+ if not stats_enabled:
+ return
+ payload = {
+ "action": action,
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
+ "country": "",
+ "os_info": platform.platform(),
+ "cpu_arch": platform.machine(),
+ "version": script_version,
+ }
+ try:
+ req = urllib.request.Request(
+ "https://api.kejilion.pro/api/log",
+ data=json.dumps(payload).encode("utf-8"),
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=3):
+ pass
+ except Exception:
+ pass
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('ℹ️ 未检测到 API providers,跳过模型同步')
+ raise SystemExit(0)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+changed = False
+fatal_errors = []
+summary = []
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+def prompt_delete_provider(name):
+ prompt = f"⚠️ {name} /models 探测连续失败 3 次。是否删除该 API 供应商及其全部相关模型?[y/N]: "
+ try:
+ ans = input(prompt).strip().lower()
+ except EOFError:
+ return False
+ return ans in ('y', 'yes')
+
+
+def rebind_defaults_before_delete(name):
+ global changed
+
+ replacement = None
+
+ def get_replacement():
+ nonlocal replacement
+ if replacement is None:
+ candidates = collect_available_refs(exclude_provider=name)
+ replacement = candidates[0] if candidates else None
+ return replacement
+
+ primary_ref = get_primary_ref(defaults)
+ if ref_provider(primary_ref) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: 默认主模型指向该 provider,但无可用替代模型,已中止删除')
+ return False
+ set_primary_ref(defaults, repl)
+ changed = True
+ summary.append(f'🔁 删除前已切换默认主模型: {primary_ref} -> {repl}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: {fk} 指向该 provider,但无可用替代模型,已中止删除')
+ return False
+ defaults[fk] = repl
+ changed = True
+ summary.append(f'🔁 删除前已切换 {fk}: {val} -> {repl}')
+
+ return True
+
+
+def delete_provider_and_refs(name):
+ global changed
+
+ if not rebind_defaults_before_delete(name):
+ return False
+
+ removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+ for r in removed_refs:
+ defaults_models.pop(r, None)
+ if removed_refs:
+ changed = True
+
+ if name in providers:
+ providers.pop(name, None)
+ changed = True
+
+ summary.append(f'🗑️ 已删除 provider {name},并移除 defaults.models 下 {len(removed_refs)} 个模型引用')
+ return True
+
+
+def fetch_remote_models_with_retry(name, base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ data = json.loads(payload)
+ return data, None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+for name, provider in list(providers.items()):
+ if not isinstance(provider, dict):
+ summary.append(f'ℹ️ 跳过 {name}: provider 结构非法')
+ continue
+
+ api = provider.get('api', '')
+ base_url = provider.get('baseUrl')
+ api_key = provider.get('apiKey')
+ model_list = provider.get('models', [])
+
+ if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ summary.append(f'ℹ️ 跳过 {name}: 无 baseUrl/apiKey/models')
+ continue
+
+ if api not in SUPPORTED_APIS:
+ summary.append(f'🔁 {name}: 发现非法协议 {api or "(unset)"},将重新探测')
+ provider['api'] = ''
+ api = ''
+ changed = True
+
+ data, err, attempts = fetch_remote_models_with_retry(name, base_url, api_key, retries=3)
+ if err is not None:
+ summary.append(f'⚠️ {name}: /models 探测失败,已重试 {attempts} 次 ({type(err).__name__}: {err})')
+ send_stat('OpenClaw API确认介入')
+ if prompt_delete_provider(name):
+ deleted = delete_provider_and_refs(name)
+ if deleted:
+ send_stat('OpenClaw API删失败Provider-确认')
+ summary.append(f'✅ {name}: 用户已确认删除该 provider 及全部相关模型引用')
+ else:
+ send_stat('OpenClaw API删失败Provider-拒绝')
+ summary.append(f'ℹ️ {name}: 用户未确认删除,保留现有 provider 配置')
+ continue
+
+ if attempts > 1:
+ summary.append(f'🔁 {name}: /models 第 {attempts} 次重试后成功')
+
+ if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ summary.append(f'⚠️ 跳过 {name}: /models 返回结构不可识别')
+ continue
+
+ remote_ids = []
+ for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+ remote_set = set(remote_ids)
+
+ if not remote_set:
+ fatal_errors.append(f'❌ {name} 上游 /models 为空,无法为该 provider 提供兜底模型')
+ continue
+
+ local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+ local_ids = [str(m['id']) for m in local_models]
+ local_set = set(local_ids)
+
+ template = None
+ for m in local_models:
+ template = copy.deepcopy(m)
+ break
+ if template is None:
+ summary.append(f'⚠️ 跳过 {name}: 本地 models 无有效模板模型')
+ continue
+
+ removed_ids = [mid for mid in local_ids if mid not in remote_set]
+ added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+ kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+ new_models = kept_models[:]
+
+ for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{name} / {mid}'
+ new_models.append(nm)
+
+ if not new_models:
+ fatal_errors.append(f'❌ {name} 同步后无可用模型,无法保障默认模型/回退模型兜底')
+ continue
+
+ expected_refs = {model_ref(name, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+ local_refs = {model_ref(name, mid) for mid in local_ids}
+
+ first_ref = model_ref(name, str(new_models[0]['id']))
+
+ primary_ref = get_primary_ref(defaults)
+ if isinstance(primary_ref, str) and primary_ref in (local_refs - expected_refs):
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ summary.append(f'🔁 默认模型已兜底替换: {primary_ref} -> {first_ref}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in (local_refs - expected_refs):
+ defaults[fk] = first_ref
+ changed = True
+ summary.append(f'🔁 {fk} 已兜底替换: {val} -> {first_ref}')
+
+ stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/') and r not in expected_refs]
+ for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+ for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+ if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+ summary.append(f'✅ {name}: 新增 {len(added_ids)} 个,删除 {len(removed_ids)} 个,当前 {len(new_models)} 个')
+
+ if added_ids:
+ summary.append(f'➕ 新增模型({len(added_ids)}):')
+ for mid in added_ids:
+ summary.append(f' + {mid}')
+ if removed_ids:
+ summary.append(f'➖ 删除模型({len(removed_ids)}):')
+ for mid in removed_ids:
+ summary.append(f' - {mid}')
+
+
+if fatal_errors:
+ for line in summary:
+ print(line)
+ for err in fatal_errors:
+ print(err)
+ print('❌ 模型同步失败:存在 provider 同步后无可用模型,已中止写入')
+ raise SystemExit(2)
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+ for line in summary:
+ print(line)
+ print('✅ OpenClaw API 模型一致性同步完成并已写入配置')
+else:
+ for line in summary:
+ print(line)
+ print('ℹ️ 无需同步:配置已与上游 /models 保持一致')
+PY
+ }
- panel_app_install() {
- install bash
- bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
- }
- panel_app_manage() {
- 1pctl user-info
- 1pctl update password
- }
- panel_app_uninstall() {
- 1pctl uninstall
- }
+ install_moltbot() {
+ echo "开始安装 OpenClaw..."
+ send_stats "开始安装 OpenClaw..."
+ install git jq
- install_panel
+ install_node_and_tools
- ;;
- 4)
+ country=$(curl -s ipinfo.io/country)
+ if [[ "$country" == "CN" || "$country" == "HK" ]]; then
+ npm config set registry https://registry.npmmirror.com
+ fi
- local docker_name="npm"
- local docker_img="jc21/nginx-proxy-manager:latest"
- local docker_port=81
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
- docker_rum() {
+ npm install -g openclaw@latest
+ openclaw onboard --install-daemon
+ start_gateway
+ add_app_id
+ break_end
- docker run -d \
- --name=$docker_name \
- -p ${docker_port}:81 \
- -p 80:80 \
- -p 443:443 \
- -v /home/docker/npm/data:/data \
- -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
- --restart=always \
- $docker_img
+ }
- }
+ start_bot() {
+ echo "启动 OpenClaw..."
+ send_stats "启动 OpenClaw..."
+ start_gateway
+ break_end
+ }
- local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
- local docker_url="官网介绍: https://nginxproxymanager.com/"
- local docker_use="echo \"初始用户名: admin@example.com\""
- local docker_passwd="echo \"初始密码: changeme\""
- local app_size="1"
+ stop_bot() {
+ echo "停止 OpenClaw..."
+ send_stats "停止 OpenClaw..."
+ tmux kill-session -t gateway > /dev/null 2>&1
+ openclaw gateway stop
+ break_end
+ }
- docker_app
+ view_logs() {
+ echo "查看 OpenClaw 状态日志"
+ send_stats "查看 OpenClaw 日志"
+ openclaw status
+ openclaw gateway status
+ openclaw logs
+ break_end
+ }
- ;;
- 5)
- local docker_name="openlist"
- local docker_img="openlistteam/openlist:latest-aria2"
- local docker_port=5244
- docker_rum() {
- docker run -d \
- --restart=always \
- -v /home/docker/openlist:/opt/openlist/data \
- -p ${docker_port}:5244 \
- -e PUID=0 \
- -e PGID=0 \
- -e UMASK=022 \
- --name="openlist" \
- openlistteam/openlist:latest-aria2
+ # OpenClaw API 协议探测逻辑已移除:不再自动探测/判定 API 类型。
+ # 说明:API 类型由用户显式配置(models.providers..api),脚本不再尝试调用 /responses 做推断。
+ # 构造模型配置 JSON
+ build-openclaw-provider-models-json() {
+ local provider_name="$1"
+ local model_ids="$2"
+ local models_array="["
+ local first=true
+
+ while read -r model_id; do
+ [ -z "$model_id" ] && continue
+ [[ $first == false ]] && models_array+=","
+ first=false
+
+ local context_window=1048576
+ local max_tokens=128000
+ local input_cost=0.15
+ local output_cost=0.60
+
+ case "$model_id" in
+ *opus*|*pro*|*preview*|*thinking*|*sonnet*)
+ input_cost=2.00
+ output_cost=12.00
+ ;;
+ *gpt-5*|*codex*)
+ input_cost=1.25
+ output_cost=10.00
+ ;;
+ *flash*|*lite*|*haiku*|*mini*|*nano*)
+ input_cost=0.10
+ output_cost=0.40
+ ;;
+ esac
+
+ models_array+=$(cat < 0 then
+ .["\($prov)/\($mid)"] //= {}
+ else
+ .
+ end
+ )
+ )
+ ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
+ }
+
+ # 核心函数:获取并添加所有模型
+ add-all-models-from-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+
+ echo "🔍 正在获取 $provider_name 的所有可用模型..."
+
+ local models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -z "$models_json" ]]; then
+ echo "❌ 无法获取模型列表"
+ return 1
+ fi
+
+ local model_ids=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+')
+
+ if [[ -z "$model_ids" ]]; then
+ echo "❌ 未找到任何模型"
+ return 1
+ fi
+
+ local model_count=$(echo "$model_ids" | wc -l)
+ echo "✅ 发现 $model_count 个模型"
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$model_ids")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 成功添加 $model_count 个模型到 $provider_name"
+ echo "📦 模型引用格式: $provider_name/"
+ return 0
+ else
+ echo "❌ 配置注入失败"
+ return 1
+ fi
+ }
+
+ # 仅添加默认模型并保留 provider
+ add-default-model-only-to-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+ local default_model="$4"
+
+ if [[ -z "$default_model" ]]; then
+ echo "❌ 默认模型不能为空"
+ return 1
+ fi
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$default_model")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 已添加 provider:$provider_name"
+ echo "✅ 仅写入默认模型:$default_model"
+ return 0
+ else
+ echo "❌ 配置注入失败"
+ return 1
+ fi
+ }
+
+ add-openclaw-provider-interactive() {
+ send_stats "OpenClaw API添加"
+ echo "=== 交互式添加 OpenClaw Provider (全量模型) ==="
+
+ # 1. Provider 名称
+ read -erp "请输入 Provider 名称 (如: deepseek): " provider_name
+ while [[ -z "$provider_name" ]]; do
+ echo "❌ Provider 名称不能为空"
+ read -erp "请输入 Provider 名称: " provider_name
+ done
+
+ # 2. Base URL
+ read -erp "请输入 Base URL (如: https://api.xxx.com/v1): " base_url
+ while [[ -z "$base_url" ]]; do
+ echo "❌ Base URL 不能为空"
+ read -erp "请输入 Base URL: " base_url
+ done
+ base_url="${base_url%/}"
+
+ # 3. API Key
+ read -rsp "请输入 API Key (输入不显示): " api_key
+ echo
+ while [[ -z "$api_key" ]]; do
+ echo "❌ API Key 不能为空"
+ read -rsp "请输入 API Key: " api_key
+ echo
+ done
+
+ # 4. 不再探测/判断 API 类型;协议由用户自行选择与维护
+
+ # 5. 获取模型列表
+ echo "🔍 正在获取可用模型列表..."
+ models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -n "$models_json" ]]; then
+ available_models=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+' | sort)
+
+ if [[ -n "$available_models" ]]; then
+ model_count=$(echo "$available_models" | wc -l)
+ echo "✅ 发现 $model_count 个可用模型:"
+ echo "--------------------------------"
+ # 全部显示,带序号
+ i=1
+ model_list=()
+ while read -r model; do
+ echo "[$i] $model"
+ model_list+=("$model")
+ ((i++))
+ done <<< "$available_models"
+ echo "--------------------------------"
+ fi
+ fi
+
+ # 5. 选择默认模型
+ echo
+ read -erp "请输入默认 Model ID (或序号,留空则使用第一个): " input_model
+
+ if [[ -z "$input_model" && -n "$available_models" ]]; then
+ default_model=$(echo "$available_models" | head -1)
+ echo "🎯 使用第一个模型: $default_model"
+ elif [[ "$input_model" =~ ^[0-9]+$ ]] && [ "${#model_list[@]}" -gt 0 ] && [ "$input_model" -ge 1 ] && [ "$input_model" -le "${#model_list[@]}" ]; then
+ default_model="${model_list[$((input_model-1))]}"
+ echo "🎯 已选择模型: $default_model"
+ else
+ default_model="$input_model"
+ fi
+
+ # 6. 确认信息
+ echo
+ echo "====== 确认信息 ======"
+ echo "Provider : $provider_name"
+ echo "Base URL : $base_url"
+ echo "API Key : ${api_key:0:8}****"
+ echo "默认模型 : $default_model"
+ echo "模型总数 : $model_count"
+ echo "======================"
+
+ read -erp "是否同时添加其他所有可用模型?(y/N): " confirm
+
+ install jq
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ add-all-models-from-provider "$provider_name" "$base_url" "$api_key"
+ add_result=$?
+ finish_msg="✅ 完成!所有 $model_count 个模型已加载"
+ else
+ add-default-model-only-to-provider "$provider_name" "$base_url" "$api_key" "$default_model"
+ add_result=$?
+ finish_msg="✅ 完成!已保留 provider,并仅加载默认模型:$default_model"
+ fi
+
+ if [[ $add_result -eq 0 ]]; then
+ echo
+ echo "🔄 设置默认模型并重启网关..."
+ openclaw models set "$provider_name/$default_model"
+ openclaw_sync_sessions_model "$provider_name/$default_model"
+ start_gateway
+ echo "$finish_msg"
+ echo "✅ 当前 API 协议类型: $DETECTED_API"
+ fi
+
+ break_end
+ }
+
+
+
+openclaw_api_manage_list() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API列表"
+
+ while IFS=$'\t' read -r rec_type idx name base_url model_count api_type latency_txt latency_level; do
+ case "$rec_type" in
+ MSG)
+ echo "$idx"
+ ;;
+ ROW)
+ local latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
+
+ printf '%b\n' "[$idx] ${name} | API: ${base_url} | 协议: ${api_type} | 模型数量: ${gl_huang}${model_count}${gl_bai} | 延迟/状态: ${latency_color}${latency_txt}${gl_bai}"
+ ;;
+ esac
+ done < <(python3 - "$config_file" <<-'PY'
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+
+def ping_models(base_url, api_key):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'OpenClaw-API-Manage/1.0',
+ },
+ )
+ start = time.perf_counter()
+ with urllib.request.urlopen(req, timeout=4) as resp:
+ resp.read(2048)
+ return int((time.perf_counter() - start) * 1000)
+
+
+def classify_latency(latency):
+ if latency == '不可用':
+ return '不可用', 'unavailable'
+ if latency == '未检测':
+ return '未检测', 'unchecked'
+ if isinstance(latency, int):
+ if latency <= 800:
+ level = 'low'
+ elif latency <= 2000:
+ level = 'medium'
+ else:
+ level = 'high'
+ return f'{latency}ms', level
+ return str(latency), 'unchecked'
+
+
+try:
+ with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+except FileNotFoundError:
+ print('MSG\tℹ️ 未找到 openclaw.json,请先完成安装/初始化。')
+ raise SystemExit(0)
+except Exception as e:
+ print(f'MSG\t❌ 读取配置失败: {type(e).__name__}: {e}')
+ raise SystemExit(0)
+
+providers = ((obj.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or not providers:
+ print('MSG\tℹ️ 当前未配置任何 API provider。')
+ raise SystemExit(0)
+
+print('MSG\t--- 已配置 API 列表 ---')
+
+for idx, name in enumerate(sorted(providers.keys()), start=1):
+ provider = providers.get(name)
+ if not isinstance(provider, dict):
+ base_url = '-'
+ model_count = 0
+ latency_raw = '不可用'
+ else:
+ base_url = provider.get('baseUrl') or provider.get('url') or provider.get('endpoint') or '-'
+ models = provider.get('models') if isinstance(provider.get('models'), list) else []
+ model_count = sum(1 for m in models if isinstance(m, dict) and m.get('id'))
+ api = provider.get('api', '')
+ api_key = provider.get('apiKey')
+
+ latency_raw = '未检测'
+ if api in SUPPORTED_APIS:
+ if isinstance(base_url, str) and base_url != '-' and isinstance(api_key, str) and api_key:
+ try:
+ latency_raw = ping_models(base_url, api_key)
+ except Exception:
+ latency_raw = '不可用'
+ else:
+ latency_raw = '不可用'
+
+ latency_text, latency_level = classify_latency(latency_raw)
+ api_label = api if api in SUPPORTED_APIS else '-'
+ print(
+ 'ROW\t' + '\t'.join([
+ str(idx),
+ str(name),
+ str(base_url),
+ str(model_count),
+ str(api_label),
+ str(latency_text),
+ str(latency_level),
+ ])
+ )
+PY
+)
+}
+sync-openclaw-provider-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API按Provider同步"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到配置文件: $config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "请输入要同步的 API 名称(provider),直接回车同步全部: " provider_name
+ if [ -z "$provider_name" ]; then
+ if sync_openclaw_api_models; then
+ start_gateway
+ else
+ echo "❌ API 模型同步失败,已中止重启网关。请检查 provider /models 返回后重试。"
+ return 1
+ fi
+ break_end
+ return 0
+ fi
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" <<'PY2'
+import copy
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+target = sys.argv[2]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('❌ 未检测到 API providers,无法同步')
+ raise SystemExit(2)
+
+provider = providers.get(target)
+if not isinstance(provider, dict):
+ print(f'❌ 未找到 provider: {target}')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def fetch_remote_models_with_retry(base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ return json.loads(payload), None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+api = provider.get('api', '')
+base_url = provider.get('baseUrl')
+api_key = provider.get('apiKey')
+model_list = provider.get('models', [])
+
+if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ print(f'❌ provider {target} 缺少 baseUrl/apiKey/models,无法执行同步')
+ raise SystemExit(3)
+
+if api not in SUPPORTED_APIS:
+ print(f'ℹ️ provider {target} 当前 api={api},但脚本已不再探测/纠正协议;请手动设置为 openai-completions 或 openai-responses')
+
+protocol_msg = None
+
+data, err, attempts = fetch_remote_models_with_retry(base_url, api_key, retries=3)
+if err is not None:
+ print(f'❌ {target}: /models 探测失败,已重试 {attempts} 次 ({type(err).__name__}: {err})')
+ raise SystemExit(4)
+
+if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ print(f'❌ {target}: /models 返回结构不可识别')
+ raise SystemExit(4)
+
+remote_ids = []
+for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+remote_set = set(remote_ids)
+if not remote_set:
+ print(f'❌ {target}: 上游 /models 为空,已中止同步')
+ raise SystemExit(5)
+
+local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+local_ids = [str(m['id']) for m in local_models]
+local_set = set(local_ids)
+
+template = copy.deepcopy(local_models[0]) if local_models else None
+if template is None:
+ print(f'❌ {target}: 本地 models 无有效模板模型,无法补全新增模型')
+ raise SystemExit(3)
+
+removed_ids = [mid for mid in local_ids if mid not in remote_set]
+added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+new_models = kept_models[:]
+for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{target} / {mid}'
+ new_models.append(nm)
+
+if not new_models:
+ print(f'❌ {target}: 同步后无可用模型,已中止写入')
+ raise SystemExit(5)
+
+expected_refs = {model_ref(target, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+local_refs = {model_ref(target, mid) for mid in local_ids}
+removed_refs = local_refs - expected_refs
+first_ref = model_ref(target, str(new_models[0]['id']))
+
+changed = False
+primary_ref = get_primary_ref(defaults)
+if isinstance(primary_ref, str) and primary_ref in removed_refs:
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ print(f'🔁 默认模型已兜底替换: {primary_ref} -> {first_ref}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in removed_refs:
+ defaults[fk] = first_ref
+ changed = True
+ print(f'🔁 {fk} 已兜底替换: {val} -> {first_ref}')
+
+stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(target + '/') and r not in expected_refs]
+for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ {target}: 新增 {len(added_ids)} 个,删除 {len(removed_ids)} 个,当前 {len(new_models)} 个')
+
+if added_ids:
+ print(f'➕ 新增模型({len(added_ids)}):')
+ for mid in added_ids:
+ print(f' + {mid}')
+if removed_ids:
+ print(f'➖ 删除模型({len(removed_ids)}):')
+ for mid in removed_ids:
+ print(f' - {mid}')
+
+if changed:
+ print('✅ 指定 provider 模型一致性同步完成并已写入配置')
+else:
+ print('ℹ️ 无需同步:该 provider 配置已与上游 /models 保持一致')
+PY2
+ local rc=$?
+ case "$rc" in
+ 0)
+ echo "✅ 同步执行完成"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 同步失败:provider 不存在或未配置"
+ ;;
+ 3)
+ echo "❌ 同步失败:provider 配置不完整或类型不支持"
+ ;;
+ 4)
+ echo "❌ 同步失败:上游 /models 请求失败"
+ ;;
+ 5)
+ echo "❌ 同步失败:上游模型为空或同步后无可用模型"
+ ;;
+ *)
+ echo "❌ 同步失败:请检查配置文件结构或日志输出"
+ ;;
+ esac
+
+ break_end
+}
+
+openclaw_detect_api_protocol_by_provider() {
+ # 协议探测逻辑已移除:脚本不再自动探测/判定 API 类型。
+ # 保留函数以兼容菜单调用,但不做任何改写。
+ echo "ℹ️ 已关闭协议探测:请手动在 ${HOME}/.openclaw/openclaw.json 中设置 provider.api 为 openai-completions 或 openai-responses"
+ return 0
+}
+
+fix-openclaw-provider-protocol-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API协议切换"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到配置文件: $config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "请输入要切换协议的 API 名称(provider): " provider_name
+ if [ -z "$provider_name" ]; then
+ echo "❌ provider 名称不能为空"
+ break_end
+ return 1
+ fi
+
+ echo "请选择要设置的 API 类型:"
+ echo "1. openai-completions"
+ echo "2. openai-responses"
+ read -erp "请输入你的选择 (1/2): " proto_choice
+
+ local new_api=""
+ case "$proto_choice" in
+ 1) new_api="openai-completions" ;;
+ 2) new_api="openai-responses" ;;
+ *)
+ echo "❌ 无效选择"
+ break_end
+ return 1
+ ;;
+ esac
+
+ install python3 >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" "$new_api" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+new_api = sys.argv[3]
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+if new_api not in SUPPORTED_APIS:
+ print('❌ 非法协议值')
+ raise SystemExit(3)
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+providers = ((work.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or name not in providers or not isinstance(providers.get(name), dict):
+ print(f'❌ 未找到 provider: {name}')
+ raise SystemExit(2)
+
+providers[name]['api'] = new_api
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ 已更新 provider {name} 协议为: {new_api}')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 切换失败:provider 不存在或未配置"
+ ;;
+ 3)
+ echo "❌ 切换失败:协议值非法"
+ ;;
+ *)
+ echo "❌ 切换失败:请检查配置文件结构或日志输出"
+ ;;
+ esac
+
+ break_end
+}
+
+ delete-openclaw-provider-interactive() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ send_stats "OpenClaw API删除入口"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到配置文件: $config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "请输入要删除的 API 名称(provider): " provider_name
+ if [ -z "$provider_name" ]; then
+ send_stats "OpenClaw API删除取消"
+ echo "❌ provider 名称不能为空"
+ break_end
+ return 1
+ fi
+
+ python3 - "$config_file" "$provider_name" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or name not in providers:
+ print(f'❌ 未找到 provider: {name}')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+replacement_candidates = collect_available_refs(exclude_provider=name)
+replacement = replacement_candidates[0] if replacement_candidates else None
+
+primary_ref = get_primary_ref(defaults)
+if ref_provider(primary_ref) == name:
+ if not replacement:
+ print('❌ 删除中止:默认主模型指向该 provider,且无可用替代模型')
+ raise SystemExit(3)
+ set_primary_ref(defaults, replacement)
+ print(f'🔁 默认主模型切换: {primary_ref} -> {replacement}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ if not replacement:
+ print(f'❌ 删除中止:{fk} 指向该 provider,且无可用替代模型')
+ raise SystemExit(3)
+ defaults[fk] = replacement
+ print(f'🔁 {fk} 切换: {val} -> {replacement}')
+
+removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+for r in removed_refs:
+ defaults_models.pop(r, None)
+
+providers.pop(name, None)
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'🗑️ 已删除 provider: {name}')
+print(f'🧹 已清理 defaults.models 中 {len(removed_refs)} 个关联模型引用')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ send_stats "OpenClaw API删除确认"
+ echo "✅ 删除完成"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 删除失败:provider 不存在"
+ ;;
+ 3)
+ send_stats "OpenClaw API删除取消"
+ echo "❌ 删除失败:无可用替代模型,已保持原配置"
+ ;;
+ *)
+ echo "❌ 删除失败:请检查配置文件结构或日志输出"
+ ;;
+ esac
+
+ break_end
+ }
+
+ openclaw_api_providers_showcase() {
+ send_stats "OpenClaw API厂商推荐"
+
+ clear
+ echo ""
+ echo -e "${gl_kjlan}╔════════════════════════════════════════════════════════════╗${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_huang}🌟 API 厂商推荐列表${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_zi}部分入口含 AFF${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}╚════════════════════════════════════════════════════════════╝${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● DeepSeek${gl_bai}"
+ echo -e " ${gl_kjlan}https://api-docs.deepseek.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● OpenRouter${gl_bai}"
+ echo -e " ${gl_kjlan}https://openrouter.ai/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Kimi${gl_bai}"
+ echo -e " ${gl_kjlan}https://platform.moonshot.cn/docs/guide/start-using-kimi-api${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 超算互联网${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.scnet.cn/${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 优云智算${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://passport.compshare.cn/register?referral_code=4mscFZXfutfFi8swMVsPuf${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 硅基流动${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://cloud.siliconflow.cn/i/irWVdPic${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 智谱 GLM${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.bigmodel.cn/glm-coding?ic=HYOTDOAJMR${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● PackyAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.packyapi.com/register?aff=wHri${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 云雾 API${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://yunwu.ai/register?aff=ZuyK${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 柏拉图AI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://api.bltcy.ai/register?aff=TBzb114019${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● MiniMax${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.minimaxi.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● NVIDIA${gl_bai}"
+ echo -e " ${gl_kjlan}https://build.nvidia.com/settings/api-keys${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Ollama${gl_bai}"
+ echo -e " ${gl_kjlan}https://ollama.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 白山云${gl_bai}"
+ echo -e " ${gl_kjlan}https://ai.baishan.com/${gl_bai}"
+ echo ""
+ echo -e "${gl_kjlan}────────────────────────────────────────────────────────────${gl_bai}"
+ echo -e " ${gl_zi}图例:${gl_lv}● 官方入口${gl_bai} ${gl_huang}● AFF 推荐入口${gl_bai}"
+ echo ""
+ echo -e "${gl_huang}提示:复制链接到浏览器打开即可访问${gl_bai}"
+ echo ""
+ read -erp "按回车键返回..." dummy
+ }
+
+ openclaw_api_manage_menu() {
+ send_stats "OpenClaw API入口"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw API 管理"
+ echo "======================================="
+ openclaw_api_manage_list
+ echo "---------------------------------------"
+ echo "1. 添加API"
+ echo "2. 同步API供应商模型列表"
+ echo "3. 切换 API 类型(completions / responses)"
+ echo "4. 删除API"
+ echo "5. API 厂商推荐"
+ echo "0. 退出"
+ echo "---------------------------------------"
+ read -erp "请输入你的选择: " api_choice
+
+ case "$api_choice" in
+ 1)
+ add-openclaw-provider-interactive
+ ;;
+ 2)
+ sync-openclaw-provider-interactive
+ ;;
+ 3)
+ fix-openclaw-provider-protocol-interactive
+ ;;
+ 4)
+ delete-openclaw-provider-interactive
+ ;;
+ 5)
+ openclaw_api_providers_showcase
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update && apt install -y gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then
+ dnf install -y gum
+ else
+ yum install -y gum
+ fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive refresh
+ zypper --non-interactive install gum
+ fi
+ }
+
+
+
+ change_model() {
+ send_stats "换模型"
+
+ local orange="#FF8C00"
+
+ openclaw_probe_status_line() {
+ local status_text="$1"
+ local status_color_ok='[32m'
+ local status_color_fail='[31m'
+ local status_color_reset='[0m'
+ if [ "$status_text" = "可用" ]; then
+ printf "%b最小检测结果:%s%b
+" "$status_color_ok" "$status_text" "$status_color_reset"
+ else
+ printf "%b最小检测结果:%s%b
+" "$status_color_fail" "$status_text" "$status_color_reset"
+ fi
+ }
+
+ openclaw_model_probe() {
+ local target_model="$1"
+ local probe_timeout=25
+ local tmp_payload tmp_response probe_result probe_status reply_preview reply_trimmed
+ local oc_config provider_name base_url api_key request_model
+ local first_endpoint second_endpoint
+ local first_exit first_http first_latency second_exit second_http second_latency
+ local first_reply second_reply
+
+ oc_config=$(openclaw_get_config_file)
+ [ ! -f "$oc_config" ] && {
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="未找到 openclaw 配置文件"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ }
+
+ provider_name="${target_model%%/*}"
+ request_model="${target_model#*/}"
+ base_url=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].baseUrl // empty' "$oc_config" 2>/dev/null)
+ api_key=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].apiKey // empty' "$oc_config" 2>/dev/null)
+ if [ -z "$provider_name" ] || [ -z "$base_url" ] || [ -z "$api_key" ]; then
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="未读取到 provider/baseUrl/apiKey"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ fi
+
+ base_url="${base_url%/}"
+ first_endpoint="/responses"
+ second_endpoint="/chat/completions"
+
+ openclaw_extract_probe_reply() {
+ python3 - "$1" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+path = Path(sys.argv[1])
+raw = path.read_text(encoding='utf-8', errors='replace').strip()
+reply = ''
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get('choices') or []
+ if choices and isinstance(choices[0], dict):
+ message = choices[0].get('message') or {}
+ if isinstance(message, dict):
+ reply = message.get('content') or ''
+ if not reply:
+ output = data.get('output') or []
+ if isinstance(output, list):
+ texts = []
+ for item in output:
+ if not isinstance(item, dict):
+ continue
+ for content in item.get('content') or []:
+ if not isinstance(content, dict):
+ continue
+ text = content.get('text')
+ if isinstance(text, str) and text.strip():
+ texts.append(text.strip())
+ if texts:
+ break
+ if texts:
+ reply = ' '.join(texts)
+ if not reply:
+ for key in ('error', 'message', 'detail'):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ reply = value.strip()
+ break
+ if isinstance(value, dict):
+ nested = value.get('message')
+ if isinstance(nested, str) and nested.strip():
+ reply = nested.strip()
+ break
+ except Exception:
+ reply = raw
+reply = ' '.join(str(reply).split())
+print(reply)
+PYTHON_EOF
+ }
+
+ openclaw_run_probe() {
+ local endpoint="$1"
+ tmp_payload=$(mktemp)
+ tmp_response=$(mktemp)
+ if [ "$endpoint" = "/responses" ]; then
+ printf '{"model":"%s","input":"hi","temperature":0,"max_output_tokens":16}' "$request_model" > "$tmp_payload"
+ else
+ printf '{"model":"%s","messages":[{"role":"user","content":"hi"}],"temperature":0,"max_tokens":16}' "$request_model" > "$tmp_payload"
+ fi
+
+ probe_result=$(python3 - "$base_url" "$api_key" "$tmp_payload" "$tmp_response" "$probe_timeout" "$endpoint" <<'PYTHON_EOF'
+import sys
+import time
+import urllib.error
+import urllib.request
+
+base_url, api_key, payload_path, response_path, timeout, endpoint = sys.argv[1:7]
+timeout = int(timeout)
+url = base_url + endpoint
+payload = open(payload_path, 'rb').read()
+req = urllib.request.Request(
+ url,
+ data=payload,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'Bearer {api_key}',
+ },
+ method='POST',
+)
+start = time.time()
+body = b''
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, 'status', 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, 'code', 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode('utf-8', errors='replace')
+ exit_code = 1
+elapsed = int((time.time() - start) * 1000)
+with open(response_path, 'wb') as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYTHON_EOF
+)
+ probe_status=$?
+ reply_preview=$(openclaw_extract_probe_reply "$tmp_response")
+ rm -f "$tmp_payload" "$tmp_response"
+ return $probe_status
+ }
+
+ openclaw_run_probe "$first_endpoint"
+ first_exit=${probe_result%%|*}
+ first_http=${probe_result#*|}
+ first_http=${first_http%%|*}
+ first_latency=${probe_result##*|}
+ first_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ if [ "$first_exit" = "0" ] && [ "$first_http" -ge 200 ] && [ "$first_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http}"
+ OPENCLAW_PROBE_LATENCY="${first_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ openclaw_run_probe "$second_endpoint"
+ second_exit=${probe_result%%|*}
+ second_http=${probe_result#*|}
+ second_http=${second_http%%|*}
+ second_latency=${probe_result##*|}
+ second_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ if [ "$second_exit" = "0" ] && [ "$second_http" -ge 200 ] && [ "$second_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0},切换 ${second_endpoint} -> HTTP ${second_http}"
+ OPENCLAW_PROBE_LATENCY="${second_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ OPENCLAW_PROBE_STATUS="FAIL"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0} / exit ${first_exit:-1};${second_endpoint} -> HTTP ${second_http:-0} / exit ${second_exit:-1}"
+ OPENCLAW_PROBE_LATENCY="${first_latency:-?}ms -> ${second_latency:-?}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 1
+ }
+
+ clear
+
+ while true; do
+ local models_raw models_list default_model model_count selected_model confirm_switch
+
+ # 从配置文件读取模型键(不调用 openclaw models list)
+ local oc_config
+ oc_config=$(openclaw_get_config_file)
+
+ models_raw=$(jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d')
+ if [ -z "$models_raw" ]; then
+ echo "获取模型列表失败:配置文件中未找到 agents.defaults.models。"
+ break_end
+ return 1
+ fi
+
+ # 为每个模型加编号,便于快速定位(例如:"(10) or-api/...:free")
+ models_list=$(echo "$models_raw" | awk '{print "(" NR ") " $0}')
+ model_count=$(echo "$models_list" | sed '/^\s*$/d' | wc -l | tr -d ' ')
+
+ # 从配置文件读取默认模型(更快);失败再回退到 openclaw 命令
+ default_model=$(jq -r '.agents.defaults.model.primary // empty' "$oc_config" 2>/dev/null)
+ [ -z "$default_model" ] && default_model="(unknown)"
+
+ clear
+
+ install_gum
+ install gum
+
+ # 若 gum 不存在,降级为原始手动输入流程
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "--- 模型管理 ---"
+ echo "当前可用模型:"
+ jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d'
+ echo "----------------"
+ read -e -p "请输入要设置的模型名称 (例如 openrouter/openai/gpt-4o)(输入 0 退出): " selected_model
+
+ if [ "$selected_model" = "0" ]; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+
+ if [ -z "$selected_model" ]; then
+ echo "错误:模型名称不能为空。请重试。"
+ echo ""
+ continue
+ fi
+
+ echo "正在切换模型为: $selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切换失败:openclaw models set 返回错误。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ return 0
+ else
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "gum 不可用,返回旧版输入模式。"
+ sleep 1
+ continue
+ fi
+ gum style --foreground "$orange" --bold "模型管理"
+ gum style --foreground "$orange" "可用模型(Auth=yes):${model_count}"
+ gum style --foreground "$orange" "当前默认:${default_model}"
+ echo ""
+ gum style --faint "↑↓ 选择 / Enter 测试 / Esc 退出"
+ echo ""
+
+ selected_model=$(echo "$models_list" | gum filter --placeholder "搜索模型(如 cli-api/gpt-5.2)" --prompt "选择模型 > " --indicator "➜ " --prompt.foreground "$orange" --indicator.foreground "$orange" --cursor-text.foreground "$orange" --match.foreground "$orange" --header "" --height 35)
+
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+ fi
+
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
+
+ echo ""
+ echo "正在检测模型: $selected_model"
+ if openclaw_model_probe "$selected_model"; then
+ openclaw_probe_status_line "可用"
+ else
+ openclaw_probe_status_line "不可用"
+ fi
+ echo "状态:$OPENCLAW_PROBE_MESSAGE"
+ echo "延迟:$OPENCLAW_PROBE_LATENCY"
+ echo "摘要:$OPENCLAW_PROBE_REPLY"
+ echo ""
+
+ printf "是否切换到该模型?[y/N,Esc 返回列表]: "
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ [nN]|"") confirm_switch="no" ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
+
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "已返回模型选择列表。"
+ sleep 1
+ continue
+ fi
+
+ echo "正在切换模型为: $selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切换失败:openclaw models set 返回错误。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ done
+ }
+
+
+ openclaw_get_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_get_agents_dir() {
+ local user_agents="${HOME}/.openclaw/agents"
+ local root_agents="/root/.openclaw/agents"
+ if [ -d "$user_agents" ]; then
+ echo "$user_agents"
+ elif [ "$HOME" = "/root" ] && [ -d "$root_agents" ]; then
+ echo "$root_agents"
+ else
+ echo "$user_agents"
+ fi
+ }
+
+ openclaw_sync_sessions_model() {
+ local model_ref="$1"
+ [ -z "$model_ref" ] && return 1
+
+ local agents_dir
+ agents_dir=$(openclaw_get_agents_dir)
+ [ ! -d "$agents_dir" ] && return 0
+
+ local provider="${model_ref%%/*}"
+ local model="${model_ref#*/}"
+ [ "$provider" = "$model_ref" ] && { provider=""; model="$model_ref"; }
+
+ local count=0
+ local agent_dir sessions_file backup_file
+
+ for agent_dir in "$agents_dir"/*/; do
+ [ ! -d "$agent_dir" ] && continue
+ sessions_file="$agent_dir/sessions/sessions.json"
+ [ ! -f "$sessions_file" ] && continue
+
+ backup_file="${sessions_file}.bak"
+ cp "$sessions_file" "$backup_file" 2>/dev/null || continue
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if [ -n "$provider" ]; then
+ jq --arg model "$model" --arg provider "$provider" \
+ 'to_entries | map(.value.modelOverride = $model | .value.providerOverride = $provider) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ else
+ jq --arg model "$model" \
+ 'to_entries | map(.value.modelOverride = $model | del(.value.providerOverride)) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ fi
+ fi
+ done
+
+ [ "$count" -gt 0 ] && echo "✅ 已同步 $count 个 agent 的会话模型为 $model_ref"
+ return 0
+ }
+
+ resolve_openclaw_plugin_id() {
+ local raw_input="$1"
+ local plugin_id="$raw_input"
+
+ plugin_id="${plugin_id#@openclaw/}"
+ if [[ "$plugin_id" == @*/* ]]; then
+ plugin_id="${plugin_id##*/}"
+ fi
+ plugin_id="${plugin_id%%@*}"
+ echo "$plugin_id"
+ }
+
+ sync_openclaw_plugin_allowlist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | if (.plugins.allow | index($pid)) == null then .plugins.allow += [$pid] else . end
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 已同步 plugins.allow 白名单: $plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+if plugin_id not in a:
+ a.append(plugin_id)
+
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 已同步 plugins.allow 白名单: $plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ 已安装插件,但同步 plugins.allow 失败,请手动检查: $config_file"
+ return 1
+ }
+
+ sync_openclaw_plugin_denylist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | .plugins.allow = (.plugins.allow | map(select(. != $pid)))
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 已从 plugins.allow 移除: $plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+a = [x for x in a if x != plugin_id]
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 已从 plugins.allow 移除: $plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ plugins.allow 移除失败,请手动检查: $config_file"
+ return 1
+ }
+
+
+
+
+
+
+ install_plugin() {
+ send_stats "插件管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo " 插件管理 (安装/删除) "
+ echo "========================================"
+ echo "当前插件列表:"
+ openclaw plugins list
+ echo "--------------------------------------------------------"
+ echo "推荐的常用插件 ID (直接复制括号内的 ID 即可):"
+ echo "--------------------------------------------------------"
+ echo "📱 通讯渠道:"
+ echo " - [feishu] # 飞书/Lark 集成"
+ echo " - [telegram] # Telegram 机器人"
+ echo " - [slack] # Slack 企业通讯"
+ echo " - [msteams] # Microsoft Teams"
+ echo " - [discord] # Discord 社区管理"
+ echo " - [whatsapp] # WhatsApp 自动化"
+ echo ""
+ echo "🧠 记忆与 AI:"
+ echo " - [memory-core] # 基础记忆 (文件检索)"
+ echo " - [memory-lancedb] # 增强记忆 (向量数据库)"
+ echo " - [copilot-proxy] # Copilot 接口转发"
+ echo ""
+ echo "⚙️ 功能扩展:"
+ echo " - [lobster] # 审批流 (带人工确认)"
+ echo " - [voice-call] # 语音通话能力"
+ echo " - [nostr] # 加密隐私聊天"
+ echo "--------------------------------------------------------"
+
+ echo "1) 安装/启用插件"
+ echo "2) 删除/禁用插件"
+ echo "0) 返回"
+ read -e -p "请选择操作:" plugin_action
+
+ [ "$plugin_action" = "0" ] && break
+ [ -z "$plugin_action" ] && continue
+
+ read -e -p "请输入插件 ID(空格分隔,输入 0 退出): " raw_input
+ [ "$raw_input" = "0" ] && break
+ [ -z "$raw_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ for token in $raw_input; do
+ local plugin_id
+ local plugin_full
+ plugin_id=$(resolve_openclaw_plugin_id "$token")
+ plugin_full="$token"
+ [ -z "$plugin_id" ] && continue
+
+ if [ "$plugin_action" = "1" ]; then
+ echo "🔍 正在检查插件状态: $plugin_id"
+ local plugin_list
+ plugin_list=$(openclaw plugins list 2>/dev/null)
+
+ if echo "$plugin_list" | grep -qw "$plugin_id" && echo "$plugin_list" | grep "$plugin_id" | grep -q "disabled"; then
+ echo "💡 插件 [$plugin_id] 已预装,正在激活..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ if [ -d "/usr/lib/node_modules/openclaw/extensions/$plugin_id" ]; then
+ echo "💡 发现系统内置目录存在该插件,尝试直接启用..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ echo "📥 本地未发现,尝试下载安装: $plugin_full"
+ rm -rf "${HOME}/.openclaw/extensions/$plugin_id"
+ [ "$HOME" != "/root" ] && rm -rf "/root/.openclaw/extensions/$plugin_id"
+ if openclaw plugins install "$plugin_full"; then
+ echo "✅ 下载成功,正在启用..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "❌ 安装失败:$plugin_full"
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "🗑️ 正在删除/禁用插件: $plugin_id"
+ openclaw plugins disable "$plugin_id" >/dev/null 2>&1
+ if openclaw plugins uninstall "$plugin_id"; then
+ echo "✅ 已卸载: $plugin_id"
+ else
+ echo "⚠️ 卸载失败,可能为预装插件,仅禁用: $plugin_id"
+ fi
+ sync_openclaw_plugin_denylist "$plugin_id" >/dev/null 2>&1
+ success_list="$success_list $plugin_id"
+ changed=true
+ fi
+ done
+
+ echo ""
+ echo "====== 操作汇总 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失败:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 跳过:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 正在重启 OpenClaw 服务以加载变更..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+
+ install_skill() {
+ send_stats "技能管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo " 技能管理 (安装/删除) "
+ echo "========================================"
+ echo "当前已安装技能:"
+ openclaw skills list
+ echo "----------------------------------------"
+
+ # 输出推荐的实用技能列表
+ echo "推荐的实用技能(可直接复制名称输入):"
+ echo "github # 管理 GitHub Issues/PR/CI (gh CLI)"
+ echo "notion # 操作 Notion 页面、数据库和块"
+ echo "apple-notes # macOS 原生笔记管理 (创建/编辑/搜索)"
+ echo "apple-reminders # macOS 提醒事项管理 (待办清单)"
+ echo "1password # 自动化读取和注入 1Password 密钥"
+ echo "gog # Google Workspace (Gmail/云盘/文档) 全能助手"
+ echo "things-mac # 深度整合 Things 3 任务管理"
+ echo "bluebubbles # 通过 BlueBubbles 完美收发 iMessage"
+ echo "himalaya # 终端邮件管理 (IMAP/SMTP 强力工具)"
+ echo "summarize # 网页/播客/YouTube 视频内容一键总结"
+ echo "openhue # 控制 Philips Hue 智能灯光场景"
+ echo "video-frames # 视频抽帧与短片剪辑 (ffmpeg 驱动)"
+ echo "openai-whisper # 本地音频转文字 (离线隐私保护)"
+ echo "coding-agent # 自动运行 Claude Code/Codex 等编程助手"
+ echo "----------------------------------------"
+
+ echo "1) 安装技能"
+ echo "2) 删除技能"
+ echo "0) 返回"
+ read -e -p "请选择操作:" skill_action
+
+ [ "$skill_action" = "0" ] && break
+ [ -z "$skill_action" ] && continue
+
+ read -e -p "请输入技能名称(空格分隔,输入 0 退出): " skill_input
+ [ "$skill_input" = "0" ] && break
+ [ -z "$skill_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ if [ "$skill_action" = "2" ]; then
+ read -e -p "二次确认:删除仅影响用户目录 ~/.openclaw/workspace/skills,确认继续?(y/N): " confirm_del
+ if [[ ! "$confirm_del" =~ ^[Yy]$ ]]; then
+ echo "已取消删除。"
+ break_end
+ continue
+ fi
+ fi
+
+ for token in $skill_input; do
+ local skill_name
+ skill_name="$token"
+ [ -z "$skill_name" ] && continue
+
+ if [ "$skill_action" = "1" ]; then
+ local skill_found=false
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ echo "💡 技能 [$skill_name] 已在用户目录安装。"
+ skill_found=true
+ elif [ -d "/usr/lib/node_modules/openclaw/skills/${skill_name}" ]; then
+ echo "💡 技能 [$skill_name] 已在系统目录安装。"
+ skill_found=true
+ fi
+
+ if [ "$skill_found" = true ]; then
+ read -e -p "技能 [$skill_name] 已安装,是否重新安装?(y/N): " reinstall
+ if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
+ skipped_list="$skipped_list $skill_name"
+ continue
+ fi
+ fi
+
+ echo "正在安装技能:$skill_name ..."
+ if npx clawhub install "$skill_name" --yes --no-input 2>/dev/null || npx clawhub install "$skill_name"; then
+ echo "✅ 技能 $skill_name 安装成功。"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "❌ 安装失败:$skill_name"
+ failed_list="$failed_list $skill_name"
+ fi
+ else
+ echo "🗑️ 正在删除技能: $skill_name"
+ npx clawhub uninstall "$skill_name" --yes --no-input 2>/dev/null || npx clawhub uninstall "$skill_name" >/dev/null 2>&1
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ rm -rf "${HOME}/.openclaw/workspace/skills/${skill_name}"
+ echo "✅ 已删除用户技能目录: $skill_name"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "⏭️ 未发现用户技能目录: $skill_name"
+ skipped_list="$skipped_list $skill_name"
+ fi
+ fi
+ done
+
+ echo ""
+ echo "====== 操作汇总 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失败:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 跳过:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 正在重启 OpenClaw 服务以加载变更..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+openclaw_json_get_bool() {
+ local expr="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r "$expr" "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_channel_has_cfg() {
+ local channel="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r --arg c "$channel" '
+ (.channels[$c] // null) as $v
+ | if ($v | type) != "object" then
+ false
+ else
+ ([ $v
+ | to_entries[]
+ | select((.key == "enabled" or .key == "dmPolicy" or .key == "groupPolicy" or .key == "streaming") | not)
+ | .value
+ | select(. != null and . != "" and . != false)
+ ] | length) > 0
+ end
+ ' "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_dir_has_files() {
+ local dir="$1"
+ [ -d "$dir" ] && find "$dir" -type f -print -quit 2>/dev/null | grep -q .
+ }
+
+ openclaw_plugin_local_installed() {
+ local plugin="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -s "$config_file" ] && jq -e --arg p "$plugin" '.plugins.installs[$p]' "$config_file" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ # 兼容两种常见目录命名:
+ # - ~/.openclaw/extensions/qqbot
+ # - ~/.openclaw/extensions/openclaw-qqbot
+ # 避免无脑 substring,优先精确匹配与 openclaw- 前缀匹配。
+ [ -d "${HOME}/.openclaw/extensions/${plugin}" ] \
+ || [ -d "${HOME}/.openclaw/extensions/openclaw-${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/openclaw-${plugin}" ]
+ }
+
+ openclaw_bot_status_text() {
+ local enabled="$1"
+ local configured="$2"
+ local connected="$3"
+ local abnormal="$4"
+ if [ "$abnormal" = "true" ]; then
+ echo "异常"
+ elif [ "$enabled" != "true" ]; then
+ echo "未启用"
+ elif [ "$connected" = "true" ]; then
+ echo "已连接"
+ elif [ "$configured" = "true" ]; then
+ echo "已配置"
+ else
+ echo "未配置"
+ fi
+ }
+
+ openclaw_colorize_bot_status() {
+ local status="$1"
+ case "$status" in
+ 已连接) echo -e "${gl_lv}${status}${gl_bai}" ;;
+ 已配置) echo -e "${gl_huang}${status}${gl_bai}" ;;
+ 异常) echo -e "${gl_hong}${status}${gl_bai}" ;;
+ *) echo "$status" ;;
+ esac
+ }
+
+ openclaw_print_bot_status_line() {
+ local label="$1"
+ local status="$2"
+ echo -e "- ${label}: $(openclaw_colorize_bot_status "$status")"
+ }
+
+ openclaw_show_bot_local_status_block() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local json_ok="false"
+ if [ -s "$config_file" ] && jq empty "$config_file" >/dev/null 2>&1; then
+ json_ok="true"
+ fi
+
+ local tg_enabled tg_cfg tg_connected tg_abnormal tg_status
+ tg_enabled=$(openclaw_json_get_bool '.channels.telegram.enabled // .plugins.entries.telegram.enabled // false')
+ tg_cfg=$(openclaw_channel_has_cfg "telegram")
+ tg_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/telegram"; then
+ tg_connected="true"
+ fi
+ tg_abnormal="false"
+ if [ "$tg_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ tg_abnormal="true"
+ fi
+ tg_status=$(openclaw_bot_status_text "$tg_enabled" "$tg_cfg" "$tg_connected" "$tg_abnormal")
+
+ local feishu_enabled feishu_cfg feishu_connected feishu_abnormal feishu_status
+ feishu_enabled=$(openclaw_json_get_bool '.plugins.entries.feishu.enabled // .plugins.entries["openclaw-lark"].enabled // .channels.feishu.enabled // .channels.lark.enabled // false')
+ feishu_cfg=$(openclaw_channel_has_cfg "feishu")
+ if [ "$feishu_cfg" != "true" ]; then
+ feishu_cfg=$(openclaw_channel_has_cfg "lark")
+ fi
+ feishu_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/feishu" || openclaw_dir_has_files "${HOME}/.openclaw/lark" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-lark"; then
+ feishu_connected="true"
+ fi
+ feishu_abnormal="false"
+ if [ "$feishu_enabled" = "true" ] && ! openclaw_plugin_local_installed "feishu" && ! openclaw_plugin_local_installed "lark" && ! openclaw_plugin_local_installed "openclaw-lark"; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_connected" != "true" ] && [ "$feishu_enabled" = "true" ] && [ "$feishu_cfg" = "true" ] && { openclaw_plugin_local_installed "feishu" || openclaw_plugin_local_installed "lark" || openclaw_plugin_local_installed "openclaw-lark"; }; then
+ feishu_connected="true"
+ fi
+ feishu_status=$(openclaw_bot_status_text "$feishu_enabled" "$feishu_cfg" "$feishu_connected" "$feishu_abnormal")
+
+ local wa_enabled wa_cfg wa_connected wa_abnormal wa_status
+ wa_enabled=$(openclaw_json_get_bool '.plugins.entries.whatsapp.enabled // .channels.whatsapp.enabled // false')
+ wa_cfg=$(openclaw_channel_has_cfg "whatsapp")
+ wa_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/whatsapp"; then
+ wa_connected="true"
+ fi
+ wa_abnormal="false"
+ if [ "$wa_enabled" = "true" ] && ! openclaw_plugin_local_installed "whatsapp"; then
+ wa_abnormal="true"
+ fi
+ if [ "$wa_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wa_abnormal="true"
+ fi
+ wa_status=$(openclaw_bot_status_text "$wa_enabled" "$wa_cfg" "$wa_connected" "$wa_abnormal")
+
+ local dc_enabled dc_cfg dc_connected dc_abnormal dc_status
+ dc_enabled=$(openclaw_json_get_bool '.channels.discord.enabled // .plugins.entries.discord.enabled // false')
+ dc_cfg=$(openclaw_channel_has_cfg "discord")
+ dc_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/discord"; then
+ dc_connected="true"
+ fi
+ dc_abnormal="false"
+ if [ "$dc_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ dc_abnormal="true"
+ fi
+ dc_status=$(openclaw_bot_status_text "$dc_enabled" "$dc_cfg" "$dc_connected" "$dc_abnormal")
+
+ local slack_enabled slack_cfg slack_connected slack_abnormal slack_status
+ slack_enabled=$(openclaw_json_get_bool '.plugins.entries.slack.enabled // .channels.slack.enabled // false')
+ slack_cfg=$(openclaw_channel_has_cfg "slack")
+ slack_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/slack"; then
+ slack_connected="true"
+ fi
+ slack_abnormal="false"
+ if [ "$slack_enabled" = "true" ] && ! openclaw_plugin_local_installed "slack"; then
+ slack_abnormal="true"
+ fi
+ if [ "$slack_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ slack_abnormal="true"
+ fi
+ slack_status=$(openclaw_bot_status_text "$slack_enabled" "$slack_cfg" "$slack_connected" "$slack_abnormal")
+
+ local qq_enabled qq_cfg qq_connected qq_abnormal qq_status
+ qq_enabled=$(openclaw_json_get_bool '.plugins.entries.qqbot.enabled // .channels.qqbot.enabled // false')
+ qq_cfg=$(openclaw_channel_has_cfg "qqbot")
+ qq_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/qqbot/sessions" || openclaw_dir_has_files "${HOME}/.openclaw/qqbot/data"; then
+ qq_connected="true"
+ fi
+ qq_abnormal="false"
+ if [ "$qq_enabled" = "true" ] && ! openclaw_plugin_local_installed "qqbot"; then
+ qq_abnormal="true"
+ fi
+ if [ "$qq_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ qq_abnormal="true"
+ fi
+ qq_status=$(openclaw_bot_status_text "$qq_enabled" "$qq_cfg" "$qq_connected" "$qq_abnormal")
+
+ local wx_enabled wx_cfg wx_connected wx_abnormal wx_status
+ wx_enabled=$(openclaw_json_get_bool '.plugins.entries.weixin.enabled // .plugins.entries["openclaw-weixin"].enabled // .channels.weixin.enabled // .channels["openclaw-weixin"].enabled // false')
+ wx_cfg=$(openclaw_channel_has_cfg "weixin")
+ if [ "$wx_cfg" != "true" ]; then
+ wx_cfg=$(openclaw_channel_has_cfg "openclaw-weixin")
+ fi
+ wx_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/weixin" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-weixin"; then
+ wx_connected="true"
+ fi
+ wx_abnormal="false"
+ if [ "$wx_enabled" = "true" ] && ! openclaw_plugin_local_installed "weixin" && ! openclaw_plugin_local_installed "openclaw-weixin"; then
+ wx_abnormal="true"
+ fi
+ if [ "$wx_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wx_abnormal="true"
+ fi
+ wx_status=$(openclaw_bot_status_text "$wx_enabled" "$wx_cfg" "$wx_connected" "$wx_abnormal")
+
+ echo "本地状态(仅本机配置/缓存,不做网络探测):"
+ openclaw_print_bot_status_line "Telegram" "$tg_status"
+ openclaw_print_bot_status_line "飞书(Lark)" "$feishu_status"
+ openclaw_print_bot_status_line "WhatsApp" "$wa_status"
+ openclaw_print_bot_status_line "Discord" "$dc_status"
+ openclaw_print_bot_status_line "Slack" "$slack_status"
+ openclaw_print_bot_status_line "QQ Bot" "$qq_status"
+ openclaw_print_bot_status_line "微信 (Weixin)" "$wx_status"
+ }
+
+ change_tg_bot_code() {
+ send_stats "机器人对接"
+ while true; do
+ clear
+ echo "========================================"
+ echo " 机器人连接对接 "
+ echo "========================================"
+ openclaw_show_bot_local_status_block
+ echo "----------------------------------------"
+ echo "1. Telegram 机器人对接"
+ echo "2. 飞书 (Lark) 机器人对接"
+ echo "3. WhatsApp 机器人对接"
+ echo "4. QQ 机器人对接"
+ echo "5. 微信机器人对接"
+ echo "----------------------------------------"
+ echo "0. 返回上一级选单"
+ echo "----------------------------------------"
+ read -e -p "请输入你的选择: " bot_choice
+
+ case $bot_choice in
+ 1)
+ read -e -p "请输入TG机器人收到的连接码 (例如 NYA99R2F)(输入 0 退出): " code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "错误:连接码不能为空。"; sleep 1; continue; fi
+ openclaw pairing approve telegram "$code"
+ break_end
+ ;;
+ 2)
+ npx -y @larksuite/openclaw-lark install
+ openclaw config set channels.feishu.streaming true
+ openclaw config set channels.feishu.requireMention true --json
+ break_end
+ ;;
+ 3)
+ read -e -p "请输入WhatsApp收到的连接码 (例如 NYA99R2F)(输入 0 退出): " code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "错误:连接码不能为空。"; sleep 1; continue; fi
+ openclaw pairing approve whatsapp "$code"
+ break_end
+ ;;
+ 4)
+ echo "QQ 官方对接地址:"
+ echo "https://q.qq.com/qqbot/openclaw/login.html"
+ break_end
+ ;;
+ 5)
+ npx -y @tencent-weixin/openclaw-weixin-cli@latest install
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ openclaw_backup_root() {
+ echo "${HOME}/.openclaw/backups"
+ }
+
+ openclaw_is_interactive_terminal() {
+ [ -t 0 ] && [ -t 1 ]
+ }
+
+ openclaw_has_command() {
+ command -v "$1" >/dev/null 2>&1
+ }
+
+
+ openclaw_is_safe_relpath() {
+ local rel="$1"
+ [ -z "$rel" ] && return 1
+ [[ "$rel" = /* ]] && return 1
+ [[ "$rel" == *"//"* ]] && return 1
+ [[ "$rel" == *$'\n'* ]] && return 1
+ [[ "$rel" == *$'\r'* ]] && return 1
+ case "$rel" in
+ ../*|*/../*|*/..|..)
+ return 1
+ ;;
+ esac
+ return 0
+ }
+
+ openclaw_restore_path_allowed() {
+ local mode="$1"
+ local rel="$2"
+ case "$mode" in
+ memory)
+ case "$rel" in
+ MEMORY.md|AGENTS.md|USER.md|SOUL.md|TOOLS.md|memory/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ project)
+ case "$rel" in
+ openclaw.json|workspace/*|extensions/*|skills/*|prompts/*|tools/*|telegram/*|feishu/*|whatsapp/*|discord/*|slack/*|qqbot/*|logs/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ }
+
+ openclaw_pack_backup_archive() {
+ local backup_type="$1"
+ local export_mode="$2"
+ local payload_dir="$3"
+ local output_file="$4"
+
+ local tmp_root
+ tmp_root=$(mktemp -d) || return 1
+ local pack_dir="$tmp_root/package"
+ mkdir -p "$pack_dir"
+
+ cp -a "$payload_dir" "$pack_dir/payload"
+
+ (
+ cd "$pack_dir/payload" || exit 1
+ find . -type f | sed 's|^\./||' | sort > "$pack_dir/manifest.files"
+ : > "$pack_dir/manifest.sha256"
+ while IFS= read -r f; do
+ [ -z "$f" ] && continue
+ sha256sum "$f" >> "$pack_dir/manifest.sha256"
+ done < "$pack_dir/manifest.files"
+ ) || { rm -rf "$tmp_root"; return 1; }
+
+ cat > "$pack_dir/backup.meta" </dev/null
+ ) || { echo "❌ sha256 校验失败,拒绝还原"; return 1; }
+
+ echo "$pkg_dir"
+ return 0
+ }
+
+ openclaw_get_all_agent_workspaces() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json, sys, os
+try:
+ with open(sys.argv[1]) as f: data = json.load(f)
+ agents = data.get("agents", {}).get("list", [])
+ results = [{"id": "main", "ws": os.path.expanduser("~/.openclaw/workspace")}]
+ for a in agents:
+ aid = a.get("id"); ws = a.get("workspace")
+ if aid and ws and aid != "main": results.append({"id": aid, "ws": os.path.expanduser(ws)})
+ print(json.dumps(results))
+except: print("[]")
+PY
+ else
+ echo '[{"id": "main", "ws": "'"${HOME}"'/.openclaw/workspace"}]'
+ fi
+ }
+
+ openclaw_memory_backup_export() {
+ send_stats "OpenClaw记忆全量备份"
+ local backup_root=$(openclaw_backup_root)
+ local ts=$(date +%Y%m%d-%H%M%S)
+ local out_file="$backup_root/openclaw-memory-full-${ts}.tar.gz"
+ mkdir -p "$backup_root"
+ local tmp_payload=$(mktemp -d) || return 1
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c "import json, sys, os, shutil;
+workspaces = json.loads(sys.argv[1]); tmp_payload = sys.argv[2]
+for item in workspaces:
+ aid = item['id']; ws = item['ws']
+ if not os.path.isdir(ws): continue
+ target_dir = os.path.join(tmp_payload, 'agents', aid)
+ os.makedirs(target_dir, exist_ok=True)
+ for f in ['MEMORY.md', 'memory']:
+ src = os.path.join(ws, f)
+ if os.path.exists(src):
+ if os.path.isfile(src): shutil.copy2(src, target_dir)
+ else: shutil.copytree(src, os.path.join(target_dir, f), dirs_exist_ok=True)
+" "$workspaces_json" "$tmp_payload"
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 未找到可备份的记忆文件"; rm -rf "$tmp_payload"; break_end; return 1
+ fi
+ if openclaw_pack_backup_archive "memory-full" "multi-agent" "$tmp_payload" "$out_file"; then
+ echo "✅ 记忆全量备份完成 (含多智能体): $out_file"; openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ 记忆全量备份失败"
+ fi
+ rm -rf "$tmp_payload"; break_end
+ }
+
+ openclaw_memory_backup_import() {
+ send_stats "OpenClaw记忆全量还原"
+ local archive_path=$(openclaw_read_import_path "还原记忆全量 (支持多智能体)")
+ [ -z "$archive_path" ] && { echo "❌ 未输入路径"; break_end; return 1; }
+ local tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir=$(openclaw_prepare_import_archive "memory-full" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c 'import json, sys, os, shutil;
+workspaces = {item["id"]: item["ws"] for item in json.loads(sys.argv[1])};
+payload_dir = sys.argv[2]; agents_root = os.path.join(payload_dir, "agents")
+if os.path.isdir(agents_root):
+ for aid in os.listdir(agents_root):
+ if aid in workspaces:
+ src_agent_dir = os.path.join(agents_root, aid); dest_ws = workspaces[aid]
+ os.makedirs(dest_ws, exist_ok=True)
+ for f in os.listdir(src_agent_dir):
+ src = os.path.join(src_agent_dir, f); dest = os.path.join(dest_ws, f)
+ if os.path.isfile(src): shutil.copy2(src, dest)
+ else: shutil.copytree(src, dest, dirs_exist_ok=True)
+ print(f"✅ 已还原智能体记忆: {aid}")' "$workspaces_json" "$pkg_dir/payload"
+ rm -rf "$tmp_unpack"; echo "✅ 记忆全量还原完成"; break_end
+ }
+
+
+ openclaw_project_backup_export() {
+ send_stats "OpenClaw项目备份"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ if [ ! -d "$openclaw_root" ]; then
+ echo "❌ 未找到 OpenClaw 根目录: $openclaw_root"
+ break_end
+ return 1
+ fi
+
+ echo "备份模式:"
+ echo "1. 安全模式(默认,推荐):workspace + openclaw.json + extensions/skills/prompts/tools(如存在)"
+ echo "2. 完整模式(含更多状态,敏感风险更高)"
+ read -e -p "请选择备份模式(默认 1): " export_mode
+ [ -z "$export_mode" ] && export_mode="1"
+
+ local mode_label="safe"
+ local tmp_payload
+ tmp_payload=$(mktemp -d) || return 1
+
+ if [ "$export_mode" = "2" ]; then
+ mode_label="full"
+ for d in workspace extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in telegram feishu whatsapp discord slack qqbot logs; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ else
+ [ -d "$openclaw_root/workspace" ] && cp -a "$openclaw_root/workspace" "$tmp_payload/"
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ fi
+
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 未找到可备份的 OpenClaw 项目内容"
+ rm -rf "$tmp_payload"
+ break_end
+ return 1
+ fi
+
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ local out_file="$backup_root/openclaw-project-${mode_label}-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ if openclaw_pack_backup_archive "openclaw-project" "$mode_label" "$tmp_payload" "$out_file"; then
+ echo "✅ OpenClaw 项目备份完成 (${mode_label}): $out_file"
+ openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ OpenClaw 项目备份失败"
+ fi
+
+ rm -rf "$tmp_payload"
+ break_end
+ }
+
+ openclaw_project_backup_import() {
+ send_stats "OpenClaw项目还原"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ mkdir -p "$openclaw_root"
+
+ echo "⚠️ 高风险操作:项目还原会覆盖 OpenClaw 配置与工作区内容。"
+ echo "⚠️ 还原前将执行 manifest/sha256 校验、白名单恢复、gateway 停启与健康检查。"
+ read -e -p "请输入确认词【我已知晓高风险并继续还原】后继续: " confirm_text
+ if [ "$confirm_text" != "我已知晓高风险并继续还原" ]; then
+ echo "❌ 确认词不匹配,已取消还原"
+ break_end
+ return 1
+ fi
+
+ local archive_path
+ archive_path=$(openclaw_read_import_path "请输入 OpenClaw 项目备份包路径")
+ [ -z "$archive_path" ] && { echo "❌ 未输入备份路径"; break_end; return 1; }
+
+ local tmp_unpack
+ tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir
+ pkg_dir=$(openclaw_prepare_import_archive "openclaw-project" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+
+ local invalid=0
+ local valid_list
+ valid_list=$(mktemp)
+ while IFS= read -r rel; do
+ [ -z "$rel" ] && continue
+ if ! openclaw_is_safe_relpath "$rel" || ! openclaw_restore_path_allowed project "$rel"; then
+ echo "❌ 检测到非法或越权路径: $rel"
+ invalid=1
+ break
+ fi
+ echo "$rel" >> "$valid_list"
+ done < "$pkg_dir/manifest.files"
+
+ if [ "$invalid" -ne 0 ]; then
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "❌ 还原中止:存在不安全路径"
+ break_end
+ return 1
+ fi
+
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "⏸️ 还原前停止 OpenClaw gateway..."
+ openclaw gateway stop >/dev/null 2>&1
+ fi
+
+ while IFS= read -r rel; do
+ mkdir -p "$openclaw_root/$(dirname "$rel")"
+ cp -a "$pkg_dir/payload/$rel" "$openclaw_root/$rel"
+ done < "$valid_list"
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "▶️ 还原后启动 OpenClaw gateway..."
+ openclaw gateway start >/dev/null 2>&1
+ sleep 2
+ echo "🩺 gateway 健康检查:"
+ openclaw gateway status || true
+ fi
+
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "✅ OpenClaw 项目还原完成"
+ break_end
+ }
+
+ openclaw_backup_detect_type() {
+ local file_name="$1"
+ if [[ "$file_name" == openclaw-memory-full-*.tar.gz ]]; then
+ echo "记忆备份文件"
+ elif [[ "$file_name" == openclaw-project-*.tar.gz ]]; then
+ echo "项目备份文件"
+ else
+ echo "其他备份文件"
+ fi
+ }
+
+ openclaw_backup_collect_files() {
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ mapfile -t OPENCLAW_BACKUP_FILES < <(find "$backup_root" -maxdepth 1 -type f -name '*.tar.gz' -printf '%f\n' | sort -r)
+ }
+
+
+ openclaw_backup_render_file_list() {
+ local backup_root i file_name file_path file_type file_size file_time
+ local has_memory=0 has_project=0 has_other=0
+ backup_root=$(openclaw_backup_root)
+ openclaw_backup_collect_files
+
+ echo "备份目录: $backup_root"
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ echo "暂无备份文件"
+ return 0
+ fi
+
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_type=$(openclaw_backup_detect_type "${OPENCLAW_BACKUP_FILES[$i]}")
+ case "$file_type" in
+ "记忆备份文件") has_memory=1 ;;
+ "项目备份文件") has_project=1 ;;
+ "其他备份文件") has_other=1 ;;
+ esac
+ done
+
+ if [ "$has_memory" -eq 1 ]; then
+ echo "记忆备份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "记忆备份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_project" -eq 1 ]; then
+ echo "项目备份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "项目备份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_other" -eq 1 ]; then
+ echo "其他备份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "其他备份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+ }
+
+ openclaw_backup_file_exists_in_list() {
+ local target_file="$1"
+ local item
+ for item in "${OPENCLAW_BACKUP_FILES[@]}"; do
+ [ "$item" = "$target_file" ] && return 0
+ done
+ return 1
+ }
+
+ openclaw_backup_delete_file() {
+ send_stats "OpenClaw删除备份文件"
+ local backup_root backup_root_real user_input target_file target_path target_type
+ backup_root=$(openclaw_backup_root)
+
+ openclaw_backup_render_file_list
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ break_end
+ return 0
+ fi
+
+ read -e -p "请输入要删除的文件名或完整路径(0 取消): " user_input
+ if [ "$user_input" = "0" ]; then
+ echo "已取消删除。"
+ break_end
+ return 0
+ fi
+ if [ -z "$user_input" ]; then
+ echo "❌ 输入不能为空。"
+ break_end
+ return 1
+ fi
+
+ backup_root_real=$(realpath -m "$backup_root")
+ if [[ "$user_input" == /* ]]; then
+ target_path=$(realpath -m "$user_input")
+ case "$target_path" in
+ "$backup_root_real"/*) ;;
+ *)
+ echo "❌ 路径越界:仅允许删除备份根目录内的文件。"
+ break_end
+ return 1
+ ;;
+ esac
+ target_file=$(basename "$target_path")
+ else
+ target_file=$(basename -- "$user_input")
+ target_path="$backup_root/$target_file"
+ fi
+
+ if [ ! -f "$target_path" ]; then
+ echo "❌ 目标文件不存在: $target_path"
+ break_end
+ return 1
+ fi
+
+ if ! openclaw_backup_file_exists_in_list "$target_file"; then
+ echo "❌ 目标文件不在当前备份列表中。"
+ break_end
+ return 1
+ fi
+
+ target_type=$(openclaw_backup_detect_type "$target_file")
+
+ echo "即将删除: [$target_type] $target_path"
+ read -e -p "第一次确认:输入 yes 确认继续: " confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "已取消删除。"
+ break_end
+ return 0
+ fi
+ read -e -p "二次确认:输入 DELETE 执行删除: " confirm_step2
+ if [ "$confirm_step2" != "DELETE" ]; then
+ echo "已取消删除。"
+ break_end
+ return 0
+ fi
+
+ if rm -f -- "$target_path"; then
+ echo "✅ 删除成功: $target_file"
+ else
+ echo "❌ 删除失败: $target_file"
+ fi
+ break_end
+ }
+
+ openclaw_backup_list_files() {
+ openclaw_backup_render_file_list
+ break_end
+ }
+
+ openclaw_memory_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_memory_config_get() {
+ local key="$1"
+ local default_value="${2:-}"
+ local value
+ value=$(openclaw config get "$key" 2>/dev/null | head -n 1 | sed -e 's/^"//' -e 's/"$//')
+ if [ -z "$value" ] || [ "$value" = "null" ] || [ "$value" = "undefined" ]; then
+ echo "$default_value"
+ return 0
+ fi
+ echo "$value"
+ }
+
+ openclaw_memory_config_set() {
+ local key="$1"
+ shift
+ openclaw config set "$key" "$@" >/dev/null 2>&1
+ }
+
+ openclaw_memory_config_unset() {
+ local key="$1"
+ openclaw config unset "$key" >/dev/null 2>&1
+ }
+
+ openclaw_memory_cleanup_legacy_keys() {
+ openclaw_memory_config_unset "memory.local"
+ }
+
+ openclaw_memory_list_agents() {
+ if command -v openclaw >/dev/null 2>&1; then
+ local agents_json
+ agents_json=$(openclaw agents list --json 2>/dev/null || true)
+ if [ -n "$agents_json" ]; then
+ python3 - "$agents_json" <<'PY'
+import json, os, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ data = None
+seen = set()
+results = []
+if isinstance(data, list):
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ if not aid or aid in seen:
+ continue
+ ws = item.get('workspace') or ("~/.openclaw/workspace" if aid == 'main' else f"~/.openclaw/workspace-{aid}")
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+if results:
+ for aid, ws in results:
+ print(f"{aid}\t{ws}")
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+ [ $? -eq 0 ] && return 0
+ fi
+ fi
+ local config_path
+ config_path=$(openclaw_memory_config_file)
+ python3 - "$config_path" <<'PY'
+import json, os, sys
+config_path = sys.argv[1]
+results = [("main", os.path.expanduser("~/.openclaw/workspace"))]
+seen = {"main"}
+try:
+ if os.path.exists(config_path):
+ with open(config_path, encoding='utf-8') as f:
+ data = json.load(f)
+ agents = data.get('agents', {}).get('list', [])
+ if isinstance(agents, list):
+ for item in agents:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ ws = item.get('workspace')
+ if not aid or aid in seen:
+ continue
+ if not ws:
+ ws = f"~/.openclaw/workspace-{aid}"
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+except Exception:
+ pass
+for aid, ws in results:
+ print(f"{aid}\t{ws}")
+PY
+ }
+
+ openclaw_memory_status_value() {
+ local key="$1"
+ local agent_id="${2:-}"
+ if [ -n "$agent_id" ]; then
+ openclaw memory status --agent "$agent_id" 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ else
+ openclaw memory status 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ fi
+ }
+
+ openclaw_memory_expand_path() {
+ local raw_path="$1"
+ if [ -z "$raw_path" ]; then
+ echo ""
+ return 0
+ fi
+ raw_path=$(echo "$raw_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [[ "$raw_path" == ~* ]]; then
+ echo "${raw_path/#\~/$HOME}"
+ else
+ echo "$raw_path"
+ fi
+ }
+
+ openclaw_memory_rebuild_index_single() {
+ local agent_id="${1:-main}"
+ local store_raw store_file ts backup_file
+ store_raw=$(openclaw_memory_status_value "Store" "$agent_id")
+ store_file=$(openclaw_memory_expand_path "$store_raw")
+ if [ -z "$store_file" ] || [ ! -f "$store_file" ]; then
+ echo "⚠️ [$agent_id] 未找到索引库文件,可能为空或不存在。"
+ echo " Store 原始值: ${store_raw:-<空>}"
+ echo " 仍将执行重建索引。"
+ else
+ ts=$(date +%Y%m%d_%H%M%S)
+ backup_file="${store_file}.bak.${ts}"
+ if mv "$store_file" "$backup_file"; then
+ echo "✅ [$agent_id] 已备份索引: $backup_file"
+ else
+ echo "⚠️ [$agent_id] 索引备份失败,继续重建。"
+ fi
+ fi
+ openclaw memory index --agent "$agent_id" --force
+ }
+
+ openclaw_memory_rebuild_index_safe() {
+ local agent_id="${1:-main}"
+ openclaw_memory_rebuild_index_single "$agent_id"
+ openclaw gateway restart
+ echo "✅ 索引已重建并自动重启网关"
+ echo ""
+ openclaw_memory_render_status
+ }
+
+ openclaw_memory_rebuild_index_all() {
+ local count=0
+ local agent_lines agent_id workspace
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id workspace; do
+ [ -z "$agent_id" ] && continue
+ openclaw_memory_rebuild_index_single "$agent_id"
+ count=$((count+1))
+ done </dev/null)
+ if [ -z "$json_output" ]; then
+ echo "获取记忆状态失败(openclaw memory status --json 无输出)"
+ return 1
+ fi
+ python3 - "$json_output" <<'PY'
+import json, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ print("获取记忆状态失败(JSON 解析错误)")
+ raise SystemExit(1)
+if not isinstance(data, list) or len(data) == 0:
+ print("未检测到任何智能体记忆状态。")
+ raise SystemExit(0)
+first = True
+for entry in data:
+ if not isinstance(entry, dict):
+ continue
+ agent_id = entry.get("agentId", "?")
+ s = entry.get("status", {})
+ if not isinstance(s, dict):
+ s = {}
+ if not first:
+ print("")
+ first = False
+ print("Agent: %s" % agent_id)
+ backend = s.get("backend") or s.get("provider") or "-"
+ print(" 底层方案: %s" % backend)
+ files = s.get("files", 0)
+ chunks = s.get("chunks", 0)
+ print(" 已收录: %s 文件 / %s 块" % (files, chunks))
+ dirty = s.get("dirty")
+ dirty_str = "是" if dirty else "否"
+ print(" 待刷新: %s" % dirty_str)
+ vec = s.get("vector", {})
+ if isinstance(vec, dict) and vec.get("enabled"):
+ vec_str = "就绪" if vec.get("available") else "已启用(不可用)"
+ else:
+ vec_str = "未启用"
+ print(" 向量库: %s" % vec_str)
+ ws = s.get("workspaceDir") or "-"
+ print(" 工作区: %s" % ws)
+ db = s.get("dbPath") or "-"
+ print(" 索引库: %s" % db)
+ scan = entry.get("scan", {})
+ if isinstance(scan, dict):
+ issues = scan.get("issues", [])
+ if issues:
+ for issue in issues[:3]:
+ print(" ⚠️ %s" % issue)
+PY
+ }
+
+ openclaw_memory_get_backend() {
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "local" ]; then
+ echo "builtin"
+ else
+ echo "$backend"
+ fi
+ }
+
+ openclaw_memory_get_local_model_path() {
+ openclaw_memory_config_get "agents.defaults.memorySearch.local.modelPath"
+ }
+
+ openclaw_memory_local_model_status() {
+ local model_path="$1"
+ if [ -z "$model_path" ]; then
+ echo "missing"
+ return
+ fi
+ if [[ "$model_path" == hf:* ]]; then
+ echo "hf"
+ return
+ fi
+ if [ -f "$model_path" ]; then
+ echo "ok"
+ else
+ echo "missing"
+ fi
+ }
+
+ openclaw_memory_qmd_available() {
+ if command -v qmd >/dev/null 2>&1; then
+ echo "true"
+ return
+ fi
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "qmd" ]; then
+ echo "true"
+ return
+ fi
+ echo "false"
+ }
+
+ openclaw_memory_probe_url() {
+ local url="$1"
+ if ! command -v curl >/dev/null 2>&1; then
+ echo "unknown"
+ return
+ fi
+ if [ -z "$url" ]; then
+ echo "unknown"
+ return
+ fi
+ if curl -I -m 2 -s "$url" >/dev/null 2>&1; then
+ echo "ok"
+ else
+ echo "fail"
+ fi
+ }
+
+ openclaw_memory_recommend() {
+ local qmd_ok model_path model_status hf_ok mirror_ok
+ qmd_ok=$(openclaw_memory_qmd_available)
+ model_path=$(openclaw_memory_get_local_model_path)
+ model_status=$(openclaw_memory_local_model_status "$model_path")
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+
+ OPENCLAW_MEMORY_RECOMMEND_REASON=()
+ if [ "$qmd_ok" = "true" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD 可用")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("未检测到 QMD")
+ fi
+ if [ -n "$model_path" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型路径: $model_path")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("未配置本地模型路径")
+ fi
+ case "$model_status" in
+ ok) OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型文件存在") ;;
+ hf) OPENCLAW_MEMORY_RECOMMEND_REASON+=("模型来自 HF 下载源(国内可能慢/失败)") ;;
+ *) OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型文件不存在或不可用") ;;
+ esac
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co 可访问")
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("hf-mirror.com 可访问")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co / hf-mirror.com 可能不可达(疑似国内/受限网络)")
+ fi
+
+ if [ "$qmd_ok" = "true" ]; then
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && { [ "$hf_ok" = "ok" ] || [ "$mirror_ok" = "ok" ]; }; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && [ "$hf_ok" = "fail" ] && [ "$mirror_ok" = "fail" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ else
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ fi
+ }
+
+
+ openclaw_memory_detect_region() {
+ OPENCLAW_MEMORY_COUNTRY="unknown"
+ OPENCLAW_MEMORY_USE_MIRROR="false"
+ if command -v curl >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_COUNTRY=$(curl -s -m 2 ipinfo.io/country | tr -d '
+' | tr -d '
+')
+ fi
+ case "$OPENCLAW_MEMORY_COUNTRY" in
+ CN|HK)
+ OPENCLAW_MEMORY_USE_MIRROR="true"
+ ;;
+ esac
+ }
+
+ openclaw_memory_select_sources() {
+ local hf_ok mirror_ok
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+ OPENCLAW_MEMORY_HF_OK="$hf_ok"
+ OPENCLAW_MEMORY_MIRROR_OK="$mirror_ok"
+ if [ "$OPENCLAW_MEMORY_USE_MIRROR" = "true" ]; then
+ if [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ elif [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://gh.kejilion.pro/"
+ else
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://"
+ fi
+ }
+
+ openclaw_memory_download_file() {
+ local url="$1"
+ local dest="$2"
+ mkdir -p "$(dirname "$dest")"
+ if command -v curl >/dev/null 2>&1; then
+ curl -L --fail --retry 2 -o "$dest" "$url"
+ return $?
+ fi
+ if command -v wget >/dev/null 2>&1; then
+ wget -O "$dest" "$url"
+ return $?
+ fi
+ echo "❌ 未检测到 curl 或 wget,无法下载。"
+ return 1
+ }
+
+ openclaw_memory_check_sqlite() {
+ if ! command -v sqlite3 >/dev/null 2>&1; then
+ echo "⚠️ 未检测到 sqlite3,QMD 可能无法正常运行。"
+ return 1
+ fi
+ local ver
+ ver=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
+ echo "✅ sqlite3 可用: ${ver:-unknown}"
+ echo "ℹ️ sqlite 扩展支持无法可靠检测,将继续。"
+ return 0
+ }
+
+ openclaw_memory_ensure_bun() {
+ if [ -x "$HOME/.bun/bin/bun" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun 已存在"
+ return 0
+ fi
+ echo "⬇️ 安装 bun..."
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL https://bun.sh/install | bash
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO- https://bun.sh/install | bash
+ else
+ echo "❌ 未检测到 curl 或 wget,无法安装 bun。"
+ return 1
+ fi
+ if [ -d "$HOME/.bun/bin" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun 安装完成"
+ return 0
+ fi
+ echo "❌ bun 安装失败"
+ return 1
+ }
+
+ openclaw_memory_ensure_qmd() {
+ local qmd_path
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -n "$qmd_path" ]; then
+ if qmd --version >/dev/null 2>&1; then
+ echo "✅ qmd 已存在且可用: $qmd_path"
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ return 0
+ else
+ echo "⚠️ qmd 命令存在但模块损坏,重新安装..."
+ fi
+ fi
+ echo "⬇️ 通过 npm 安装 qmd: @tobilu/qmd"
+ npm install -g @tobilu/qmd
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -z "$qmd_path" ]; then
+ echo "❌ qmd 安装失败"
+ return 1
+ fi
+ if ! qmd --version >/dev/null 2>&1; then
+ echo "❌ qmd 安装后仍无法运行"
+ return 1
+ fi
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ echo "✅ qmd 安装完成: $qmd_path"
+ return 0
+ }
+
+ openclaw_memory_render_auto_summary() {
+ echo "---------------------------------------"
+ echo "✅ 环境就绪"
+ echo "方案: ${OPENCLAW_MEMORY_AUTO_SCHEME:-unknown}"
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "模式: 仅写配置(未安装/未下载)"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "索引: 已执行"
+ else
+ echo "索引: 已跳过"
+ fi
+ if [ "$OPENCLAW_MEMORY_RESTARTED" = "true" ]; then
+ echo "重启: 已执行"
+ else
+ echo "重启: 已跳过"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ echo "qmd: $OPENCLAW_MEMORY_QMD_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_MODEL_PATH" ]; then
+ echo "模型: $OPENCLAW_MEMORY_MODEL_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_COUNTRY" ]; then
+ echo "地区: $OPENCLAW_MEMORY_COUNTRY"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_HF_BASE" ]; then
+ echo "下载源: $OPENCLAW_MEMORY_HF_BASE"
+ fi
+ echo "最终状态:"
+ openclaw_memory_render_status
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_auto_confirm() {
+ local scheme_label="$1"
+ OPENCLAW_MEMORY_PREHEAT="true"
+ OPENCLAW_MEMORY_RESTARTED="false"
+ OPENCLAW_MEMORY_CONFIG_ONLY="false"
+ echo "即将执行自动部署(详细模式)"
+ echo "目标方案: $scheme_label"
+ echo "地区: ${OPENCLAW_MEMORY_COUNTRY:-unknown}"
+ echo "镜像源探测: huggingface.co=${OPENCLAW_MEMORY_HF_OK:-unknown} hf-mirror.com=${OPENCLAW_MEMORY_MIRROR_OK:-unknown}"
+ echo "下载源: ${OPENCLAW_MEMORY_HF_BASE:-unknown}"
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_PATH" ]; then
+ echo "预计下载路径: $OPENCLAW_MEMORY_EXPECT_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_SIZE" ]; then
+ echo "可能流量/磁盘占用: $OPENCLAW_MEMORY_EXPECT_SIZE"
+ else
+ echo "可能流量/磁盘占用: 视实际情况而定"
+ fi
+ echo "确认后将自动安装/下载、写入配置、构建索引并重启网关"
+ echo "高级选项: 输入 config 仅写配置(不安装不下载、不索引、不重启)"
+ read -e -p "输入 yes 确认继续(默认 N): " confirm_step
+ case "$confirm_step" in
+ yes|YES)
+ OPENCLAW_MEMORY_PREHEAT="true"
+ ;;
+ config|CONFIG)
+ OPENCLAW_MEMORY_CONFIG_ONLY="true"
+ OPENCLAW_MEMORY_PREHEAT="false"
+ ;;
+ *)
+ echo "已取消自动部署。"
+ return 1
+ ;;
+ esac
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "⚠️ 已选择仅写配置,不安装不下载"
+ else
+ echo "✅ 将自动构建索引并重启网关"
+ fi
+ return 0
+ }
+
+ openclaw_memory_auto_setup_qmd() {
+ echo "🔍 检测 QMD 环境"
+ openclaw_memory_cleanup_legacy_keys
+ openclaw_memory_check_sqlite || true
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ if command -v qmd >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_QMD_PATH=$(command -v qmd)
+ else
+ OPENCLAW_MEMORY_QMD_PATH="qmd"
+ fi
+ else
+ openclaw_memory_ensure_qmd || return 1
+ fi
+ local backend
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ]; then
+ echo "✅ memory.backend 已是 qmd"
+ else
+ openclaw_memory_config_set "memory.backend" "qmd"
+ echo "✅ 已设置 memory.backend=qmd"
+ fi
+ local qmd_cmd
+ qmd_cmd=$(openclaw_memory_config_get "memory.qmd.command")
+ if [ -z "$qmd_cmd" ] || [[ "$qmd_cmd" != /* ]] || [ "$qmd_cmd" != "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ openclaw_memory_config_set "memory.qmd.command" "$OPENCLAW_MEMORY_QMD_PATH"
+ echo "✅ 已写入 memory.qmd.command: $OPENCLAW_MEMORY_QMD_PATH"
+ else
+ echo "✅ memory.qmd.command 已正确"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "🔥 预热索引(可能下载模型)"
+ openclaw_memory_prepare_workspace_all
+ local preh_agent_lines preh_agent_id preh_workspace
+ preh_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r preh_agent_id preh_workspace; do
+ [ -z "$preh_agent_id" ] && continue
+ openclaw memory index --agent "$preh_agent_id" --force
+ done </dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ OPENCLAW_MEMORY_RESTARTED="true"
+ openclaw_memory_render_auto_summary
+ return 0
+ }
+
+ openclaw_memory_auto_setup_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "记忆方案自动部署"
+ echo "======================================="
+ echo "1. QMD"
+ echo "2. Local"
+ echo "3. Auto(自动选择)"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " auto_choice
+ case "$auto_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_apply_scheme() {
+ local scheme="$1"
+ openclaw_memory_cleanup_legacy_keys
+ case "$scheme" in
+ qmd)
+ openclaw_memory_config_set "memory.backend" "qmd"
+ if [ $? -ne 0 ]; then
+ echo "❌ 写入配置失败"
+ return 1
+ fi
+ openclaw_memory_config_set "memory.qmd.command" "qmd" >/dev/null 2>&1
+ ;;
+ local)
+ openclaw_memory_config_set "memory.backend" "builtin"
+ if [ $? -ne 0 ]; then
+ echo "❌ 写入配置失败"
+ return 1
+ fi
+ openclaw_memory_config_set "agents.defaults.memorySearch.provider" "local" >/dev/null 2>&1
+ ;;
+ *)
+ echo "❌ 未知方案: $scheme"
+ return 1
+ esac
+ echo "✅ 已更新记忆方案配置"
+ return 0
+ }
+
+ openclaw_memory_offer_restart() {
+ echo "配置已写入,需要重启 OpenClaw 网关后生效。"
+ read -e -p "是否立即重启 OpenClaw 网关?(Y/n): " restart_choice
+ if [[ "$restart_choice" =~ ^[Nn]$ ]]; then
+ echo "已跳过重启,可稍后执行: openclaw gateway restart"
+ return 0
+ fi
+ if declare -F start_gateway >/dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ }
+
+ openclaw_memory_fix_index() {
+ local backend include_dm
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ] && ! command -v qmd >/dev/null 2>&1; then
+ echo "⚠️ 检测到当前方案为 QMD,但未安装 qmd 命令。"
+ echo " 可切换 Local,或安装 bun + qmd 后再试。"
+ fi
+ include_dm=$(openclaw config get memory.qmd.includeDefaultMemory 2>/dev/null)
+ echo "======================================="
+ echo "索引修复诊断"
+ echo "======================================="
+ echo "当前 includeDefaultMemory: ${include_dm:-未设置}"
+ echo ""
+ if [ "$include_dm" = "false" ]; then
+ echo "⚠️ 检测到 includeDefaultMemory=false"
+ echo " 这会导致默认记忆文件(MEMORY.md + memory/*.md)不被索引"
+ echo " 所以 Indexed 会一直显示 0/N"
+ echo ""
+ read -e -p "是否恢复为 true 并重建索引?(Y/n): " fix_choice
+ if [[ ! "$fix_choice" =~ ^[Nn]$ ]]; then
+ openclaw_memory_config_set "memory.qmd.includeDefaultMemory" true
+ if [ $? -ne 0 ]; then
+ echo "❌ 写入配置失败"
+ break_end
+ return 1
+ fi
+ echo "✅ 已恢复 includeDefaultMemory=true"
+ openclaw_memory_rebuild_index_all
+ else
+ echo "已取消。"
+ fi
+ else
+ echo "includeDefaultMemory 配置正常。"
+ echo "将执行:清理旧索引 → 全量重建所有智能体索引"
+ echo ""
+ read -e -p "确认执行?(Y/n): " confirm_fix
+ if [[ ! "$confirm_fix" =~ ^[Nn]$ ]]; then
+ openclaw_memory_rebuild_index_all
+ else
+ echo "已取消。"
+ fi
+ fi
+ break_end
+ }
+
+ openclaw_memory_scheme_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 记忆方案"
+ echo "======================================="
+ local backend current_label
+ backend=$(openclaw_memory_get_backend)
+ case "$backend" in
+ qmd) current_label="QMD" ;;
+ builtin|local) current_label="Local" ;;
+ *) current_label="未配置" ;;
+ esac
+ echo "当前方案: $current_label"
+ echo ""
+ echo "QMD : 轻量索引,依赖 qmd 命令(适合网络受限)"
+ echo "Local: 本地向量检索,依赖 embedding 模型文件"
+ echo "Auto : 自动推荐(基于可用性 + 网络探测)"
+ echo "---------------------------------------"
+ echo "1. 切换 QMD(自动部署/已装则跳过)"
+ echo "2. 切换 Local(自动部署/已装则跳过)"
+ echo "3. Auto(自动推荐并自动部署)"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " scheme_choice
+ case "$scheme_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_file_collect() {
+ OPENCLAW_MEMORY_FILES=()
+ OPENCLAW_MEMORY_FILE_LABELS=()
+ local agent_lines agent_id base_dir memory_dir memory_file rel
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id base_dir; do
+ [ -z "$agent_id" ] && continue
+ memory_dir="$base_dir/memory"
+ memory_file="$base_dir/MEMORY.md"
+ if [ -f "$memory_file" ]; then
+ OPENCLAW_MEMORY_FILES+=("$memory_file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/MEMORY.md")
+ fi
+ if [ -d "$memory_dir" ]; then
+ while IFS= read -r file; do
+ [ -f "$file" ] || continue
+ rel="${file#$base_dir/}"
+ OPENCLAW_MEMORY_FILES+=("$file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/$rel")
+ done < <(find "$memory_dir" -type f -name '*.md' | sort)
+ fi
+ done </dev/null || stat -c %y "$file" | awk '{print $1" "$2}')
+ printf "%s | %s | %s | %s\\n" "$((i+1))" "$rel" "$size" "$mtime"
+ done
+ }
+
+ openclaw_memory_view_file() {
+ local file="$1"
+ [ -f "$file" ] || {
+ echo "❌ 文件不存在: $file"
+ return 1
+ }
+ local total_lines
+ total_lines=$(wc -l < "$file" 2>/dev/null || echo 0)
+ local default_lines=120
+ local start_line count
+ echo "文件: $file"
+ echo "总行数: $total_lines"
+ read -e -p "请输入起始行(回车默认末尾 $default_lines 行): " start_line
+ read -e -p "请输入显示行数(回车默认 $default_lines): " count
+ [ -z "$count" ] && count=$default_lines
+ if [ -z "$start_line" ]; then
+ if [ "$total_lines" -le "$count" ]; then
+ start_line=1
+ else
+ start_line=$((total_lines - count + 1))
+ fi
+ fi
+ if ! [[ "$start_line" =~ ^[0-9]+$ ]] || ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ echo "❌ 请输入有效的数字。"
+ return 1
+ fi
+ if [ "$start_line" -lt 1 ]; then
+ start_line=1
+ fi
+ if [ "$count" -le 0 ]; then
+ echo "❌ 行数必须大于 0。"
+ return 1
+ fi
+ local end_line=$((start_line + count - 1))
+ if [ "$end_line" -gt "$total_lines" ]; then
+ end_line=$total_lines
+ fi
+ if [ "$total_lines" -eq 0 ]; then
+ echo "(空文件)"
+ return 0
+ fi
+ echo "---------------------------------------"
+ sed -n "${start_line},${end_line}p" "$file"
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_files_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 记忆文件"
+ echo "======================================="
+ openclaw_memory_file_render_list
+ echo "---------------------------------------"
+ read -e -p "请输入文件编号查看(0 返回): " file_choice
+ if [ "$file_choice" = "0" ]; then
+ return 0
+ fi
+ if ! [[ "$file_choice" =~ ^[0-9]+$ ]]; then
+ echo "无效的选择,请重试。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_file_collect
+ if [ ${#OPENCLAW_MEMORY_FILES[@]} -eq 0 ]; then
+ read -p "未找到记忆文件,按回车返回..."
+ return 0
+ fi
+ local idx=$((file_choice-1))
+ if [ "$idx" -lt 0 ] || [ "$idx" -ge ${#OPENCLAW_MEMORY_FILES[@]} ]; then
+ echo "无效的编号,请重试。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_view_file "${OPENCLAW_MEMORY_FILES[$idx]}"
+ read -p "按回车返回列表..."
+ done
+ }
+
+
+ openclaw_memory_search_test() {
+ read -e -p "输入搜索关键词: " query
+ if [ -z "$query" ]; then
+ echo "关键词不能为空。"
+ return 1
+ fi
+ echo "正在搜索记忆..."
+ openclaw memory search "$query" --max-results 5
+ }
+
+ openclaw_memory_deep_status() {
+ echo "正在探测嵌入模型就绪状态..."
+ openclaw memory status --deep
+ }
+
+ openclaw_memory_menu() {
+ send_stats "OpenClaw记忆管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 记忆管理"
+ echo "======================================="
+ openclaw_memory_render_status
+ echo "1. 更新记忆索引"
+ echo "2. 查看记忆文件"
+ echo "3. 索引修复(Indexed 异常)"
+ echo "4. 记忆方案(QMD/Local/Auto)"
+ echo "5. 搜索测试(验证索引是否工作)"
+ echo "6. 深度状态探测(检查嵌入模型)"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " memory_choice
+ case "$memory_choice" in
+ 1)
+ echo "即将更新记忆索引。"
+ read -e -p "第一次确认:输入 yes 继续: " confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "已取消。"
+ break_end
+ continue
+ fi
+ openclaw_memory_prepare_workspace_all
+ read -e -p "二次确认:输入 force 使用全量(留空为增量): " confirm_step2
+ if [ "$confirm_step2" = "force" ]; then
+ echo "⚠️ 全量重建更彻底,但耗时更长。"
+ echo "推荐:输入 rebuild 进行安全重建(先备份索引库)。"
+ read -e -p "第三次确认:输入 rebuild 执行安全重建;直接回车继续普通 force: " confirm_step3
+ if [ "$confirm_step3" = "rebuild" ]; then
+ openclaw_memory_rebuild_index_all
+ else
+ local fl_agent_lines fl_agent_id fl_workspace
+ fl_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r fl_agent_id fl_workspace; do
+ [ -z "$fl_agent_id" ] && continue
+ openclaw memory index --agent "$fl_agent_id" --force
+ done </dev/null 2>&1 || {
+ echo "⚠️ 权限备份失败:$backup_file"
+ return 1
+ }
+ echo "✅ 已备份当前权限配置: $backup_file"
+ return 0
+ }
+
+ openclaw_permission_restore_backup() {
+ local config_file backup_file
+ config_file=$(openclaw_permission_config_file)
+ backup_file=$(openclaw_permission_backup_file)
+ if [ ! -s "$backup_file" ]; then
+ echo "❌ 未找到可恢复的权限备份文件。"
+ return 1
+ fi
+ cp -f "$backup_file" "$config_file" >/dev/null 2>&1 || {
+ echo "❌ 权限恢复失败:$backup_file"
+ return 1
+ }
+ echo "✅ 已恢复切换前权限配置"
+ openclaw_permission_restart_gateway || true
+ return 0
+ }
+
+ openclaw_permission_restart_gateway() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ 未检测到 openclaw,无法重启 OpenClaw Gateway。"
+ return 1
+ fi
+ echo "正在重启 OpenClaw Gateway..."
+ openclaw gateway restart >/dev/null 2>&1 || {
+ openclaw gateway stop >/dev/null 2>&1
+ openclaw gateway start >/dev/null 2>&1
+ }
+ }
+
+ openclaw_permission_get_value() {
+ local path="$1"
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+
+ if openclaw_has_command openclaw; then
+ local value
+ value=$(openclaw config get "$path" 2>&1 | head -n 1)
+ if [ -n "$value" ]; then
+ if echo "$value" | grep -qi "config path not found"; then
+ echo "(unset)"
+ return 0
+ fi
+ if [ "$value" = "null" ]; then
+ echo "(unset)"
+ else
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ fi
+ return 0
+ fi
+ fi
+
+ [ -f "$config_file" ] || { echo "(unset)"; return 0; }
+
+ if openclaw_has_command jq; then
+ local jq_value
+ jq_value=$(jq -r --arg p "$path" 'getpath($p|split(".")) // "(unset)"' "$config_file" 2>/dev/null) || jq_value="(unset)"
+ [ "$jq_value" = "null" ] && jq_value="(unset)"
+ echo "$jq_value"
+ return 0
+ fi
+
+ if openclaw_has_command python3; then
+ python3 - "$config_file" "$path" <<'PY'
+import json, sys
+path = sys.argv[2]
+with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+cur = obj
+for part in path.split('.'):
+ if isinstance(cur, dict) and part in cur:
+ cur = cur[part]
+ else:
+ print('(unset)')
+ raise SystemExit(0)
+if isinstance(cur, bool):
+ print('true' if cur else 'false')
+elif cur is None:
+ print('(unset)')
+else:
+ print(json.dumps(cur, ensure_ascii=False) if isinstance(cur, (dict, list)) else str(cur))
+PY
+ return 0
+ fi
+
+ echo "(unset)"
+ return 0
+ }
+
+ openclaw_permission_unset_optional() {
+ local key="$1"
+ local probe
+ if ! openclaw_has_command openclaw; then
+ return 1
+ fi
+ if openclaw config unset "$key" >/dev/null 2>&1; then
+ return 0
+ fi
+ probe=$(openclaw config get "$key" 2>&1 | head -n 1)
+ if [ -z "$probe" ] || [ "$probe" = "null" ] || [ "$probe" = "(unset)" ] || echo "$probe" | grep -qi "config path not found"; then
+ return 0
+ fi
+ return 1
+ }
+
+ openclaw_permission_detect_mode() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ [ ! -f "$config_file" ] && { echo "未知模式"; return; }
+
+ python3 - "$config_file" <<'PY'
+import json, sys
+
+def get_v(o, p):
+ for k in p.split('.'):
+ if isinstance(o, dict) and k in o:
+ o = o[k]
+ else:
+ return "(unset)"
+ return str(o).lower()
+
+try:
+ with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ d = json.load(f)
+ p = get_v(d, "tools.profile")
+ s = get_v(d, "tools.exec.security")
+ a = get_v(d, "tools.exec.ask")
+ e = get_v(d, "tools.elevated.enabled")
+ b = get_v(d, "commands.bash")
+ ap = get_v(d, "tools.exec.applyPatch.enabled")
+ w = get_v(d, "tools.exec.applyPatch.workspaceOnly")
+
+ if p == "coding" and s == "allowlist" and a == "on-miss" and e == "false" and b == "false" and ap == "false":
+ print("标准安全模式")
+ elif p == "coding" and s == "allowlist" and a == "on-miss" and e == "true" and b == "true" and ap == "true" and w == "true":
+ print("开发增强模式")
+ elif (p == "full" or p == "(unset)") and s == "full" and a == "off" and e == "true" and b == "true" and ap == "true":
+ print("完全开放模式")
+ else:
+ print("自定义模式")
+except Exception:
+ print("自定义模式")
+PY
+ }
+
+ openclaw_permission_update_exec_approvals() {
+ local sec="$1"
+ local ask="$2"
+ local fallback="$3"
+ local approvals_file="$HOME/.openclaw/exec-approvals.json"
+
+ mkdir -p "$HOME/.openclaw"
+
+ # 生成 JSON 并通过 openclaw approvals set --stdin 写入(优先)
+ # 若 CLI 不支持则回退直接写文件
+ local json_payload
+ json_payload=$(python3 -c '
+import json, sys, os
+path = sys.argv[1]
+try:
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ data = json.load(f)
+ else:
+ data = {"version": 1, "defaults": {}}
+except Exception:
+ data = {"version": 1, "defaults": {}}
+if "defaults" not in data:
+ data["defaults"] = {}
+data["defaults"]["security"] = sys.argv[2]
+data["defaults"]["ask"] = sys.argv[3]
+data["defaults"]["askFallback"] = sys.argv[4]
+data["defaults"]["autoAllowSkills"] = True
+print(json.dumps(data, indent=2))
+' "$approvals_file" "$sec" "$ask" "$fallback")
+
+ if openclaw_has_command openclaw && echo "$json_payload" | openclaw approvals set --stdin >/dev/null 2>&1; then
+ return 0
+ fi
+ # 回退:直接写文件
+ echo "$json_payload" > "$approvals_file"
+ }
+
+ openclaw_permission_render_status() {
+ echo "应用层配置: ~/.openclaw/openclaw.json"
+ echo "宿主机审批: ~/.openclaw/exec-approvals.json"
+ echo "---------------------------------------"
+ local current_profile current_sec current_ask current_elevated
+ current_profile=$(openclaw config get tools.profile 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_sec=$(openclaw config get tools.exec.security 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_ask=$(openclaw config get tools.exec.ask 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_elevated=$(openclaw config get tools.elevated.enabled 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ # 清理空值
+ [ -z "$current_profile" ] || echo "$current_profile" | grep -qi "config path not found" && current_profile=""
+ [ -z "$current_sec" ] || echo "$current_sec" | grep -qi "config path not found" && current_sec=""
+ [ -z "$current_ask" ] || echo "$current_ask" | grep -qi "config path not found" && current_ask=""
+ [ -z "$current_elevated" ] || echo "$current_elevated" | grep -qi "config path not found" && current_elevated=""
+
+ local current_mode="未知 / 自定义"
+ if [ "$current_profile" = "full" ] && [ "$current_sec" = "full" ] && [ "$current_ask" = "off" ]; then
+ current_mode="\033[1;31m完全开放模式\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" = "true" ]; then
+ current_mode="\033[1;33m开发增强模式\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" != "true" ]; then
+ current_mode="\033[1;32m标准安全模式\033[0m"
+ elif [ -z "$current_profile" ] && [ -z "$current_sec" ]; then
+ current_mode="\033[1;36m官方沙盒兜底\033[0m"
+ fi
+ echo -e " 当前综合安全等级: ${current_mode}"
+ echo "---------------------------------------"
+ echo -e "${gl_huang}[应用层 Tool Policy 状态]${gl_bai}"
+ echo " Profile (预设): ${current_profile:-(unset)}"
+ echo " Exec 限制: ${current_sec:-(unset)}"
+ echo " 审批提示: ${current_ask:-(unset)}"
+ echo " 提权开关: ${current_elevated:-(unset)}"
+
+ echo -e "\n${gl_huang}[底层 Exec Approvals 状态]${gl_bai}"
+ if openclaw_has_command openclaw; then
+ local approvals_json
+ approvals_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$approvals_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ defaults = d.get("file", {}).get("defaults", {})
+ if not defaults:
+ defaults = d.get("defaults", {})
+ sec = defaults.get("security", "(unset)")
+ ask = defaults.get("ask", "(unset)")
+ fb = defaults.get("askFallback", "(unset)")
+ auto = defaults.get("autoAllowSkills", False)
+ print(" 拦截策略 (Security): " + str(sec))
+ print(" 提示策略 (Ask): " + str(ask))
+ print(" 无UI兜底 (AskFallback): " + str(fb))
+ print(" 自动放行技能 (autoAllowSkills): " + ("on" if auto else "off"))
+ exists = d.get("exists", True)
+ if not exists:
+ print(" (审批文件不存在,使用系统内置安全兜底)")
+except Exception as e:
+ print(" (解析失败: " + str(e) + ")")
+' "$approvals_json"
+ else
+ echo " (openclaw approvals get --json 无输出)"
+ fi
+ elif [ -f "$HOME/.openclaw/exec-approvals.json" ]; then
+ python3 -c '
+import json, os
+path = os.path.expanduser("~/.openclaw/exec-approvals.json")
+try:
+ with open(path) as f:
+ d = json.load(f).get("defaults", {})
+ print(" 拦截策略 (Security): " + str(d.get("security", "(unset)")))
+ print(" 提示策略 (Ask): " + str(d.get("ask", "(unset)")))
+ print(" 无UI兜底 (AskFallback): " + str(d.get("askFallback", "(unset)")))
+except Exception:
+ print(" (配置文件解析失败)")
+'
+ else
+ echo " (未配置,强制使用系统内置安全兜底策略)"
+ fi
+ }
+
+ openclaw_permission_apply_standard() {
+ send_stats "OpenClaw权限-标准安全模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在配置应用层策略..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled false >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval true >/dev/null 2>&1 # 拦截危险的内联代码
+ openclaw config unset commands.bash >/dev/null 2>&1 # 废弃旧版参数
+
+ echo "正在配置宿主机审批拦截..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切换为标准安全模式 (所有危险命令将通过UI/TG请求你的审批)${gl_bai}"
+ }
+
+ openclaw_permission_apply_developer() {
+ send_stats "OpenClaw权限-开发增强模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在配置应用层策略..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1 # 允许智能体申请提权
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "正在配置宿主机审批拦截..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切换为开发增强模式 (允许提权,但常规危险命令依然需要审批)${gl_bai}"
+ }
+
+ openclaw_permission_apply_full() {
+ send_stats "OpenClaw权限-完全开放模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在配置应用层策略..."
+ openclaw config set tools.profile full >/dev/null 2>&1
+ openclaw config set tools.exec.security full >/dev/null 2>&1
+ openclaw config set tools.exec.ask off >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "正在瓦解宿主机拦截防御..."
+ # 这里的 full 和 off 将彻底绕过底层宿主机的 exec 审批系统
+ openclaw_permission_update_exec_approvals "full" "off" "full"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切换为完全开放模式 (警告:所有宿主机命令拦截已失效,智能体具有最高权限)${gl_bai}"
+ }
+
+ openclaw_permission_restore_official_defaults() {
+ send_stats "OpenClaw权限-恢复官方默认"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "清理应用层强制覆盖..."
+ openclaw config unset tools.profile >/dev/null 2>&1
+ openclaw config unset tools.exec.security >/dev/null 2>&1
+ openclaw config unset tools.exec.ask >/dev/null 2>&1
+ openclaw config unset tools.elevated.enabled >/dev/null 2>&1
+ openclaw config unset tools.exec.strictInlineEval >/dev/null 2>&1
+
+ echo "清理宿主机拦截配置..."
+ # 优先通过 CLI 清空审批配置,回退直接删文件
+ if echo '{"version":1,"defaults":{}}' | openclaw approvals set --stdin >/dev/null 2>&1; then
+ true
+ else
+ rm -f "$HOME/.openclaw/exec-approvals.json"
+ fi
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已恢复到 OpenClaw 官方安全沙盒防御机制${gl_bai}"
+ }
+
+ openclaw_permission_run_audit() {
+ echo "======================================="
+ echo "运行 OpenClaw 官方安全审计与体检..."
+ echo "======================================="
+ openclaw security audit
+ echo "---------------------------------------"
+ read -e -p "是否尝试自动修复发现的安全隐患?(y/n): " fix_choice
+ if [[ "$fix_choice" == "y" || "$fix_choice" == "Y" || "$fix_choice" == "yes" ]]; then
+ openclaw security audit --fix
+ echo -e "${gl_lv}✅ 自动修复完成。${gl_bai}"
+ fi
+ echo "按任意键返回..."
+ read -n 1 -s
+ }
+
+
+ openclaw_permission_manage_allowlist() {
+ while true; do
+ clear
+ echo "======================================="
+ echo " Exec 命令白名单管理"
+ echo "======================================="
+ echo "当前白名单:"
+ local allowlist_json
+ allowlist_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$allowlist_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ f = d.get("file", {})
+ agents = f.get("agents", {})
+ found = False
+ for agent_id, agent_data in agents.items():
+ al = agent_data.get("allowlist", [])
+ if al:
+ found = True
+ print(" 智能体 [%s]:" % agent_id)
+ for item in al:
+ print(" - %s" % item)
+ if not found:
+ print(" (空,未配置任何白名单规则)")
+except Exception as e:
+ print(" (解析失败: " + str(e) + ")")
+' "$allowlist_json"
+ else
+ echo " (无法获取)"
+ fi
+ echo "---------------------------------------"
+ echo "1. 添加白名单规则"
+ echo "2. 移除白名单规则"
+ echo "0. 返回"
+ echo "---------------------------------------"
+ read -e -p "请选择: " al_choice
+ case "$al_choice" in
+ 1)
+ read -e -p "输入要放行的命令路径 (支持 glob,如 /usr/bin/git): " pattern
+ [ -z "$pattern" ] && { echo "不能为空"; break_end; continue; }
+ read -e -p "指定智能体ID (留空=所有智能体 *): " agent_id
+ agent_id="${agent_id:-*}"
+ openclaw approvals allowlist add --agent "$agent_id" "$pattern"
+ break_end
+ ;;
+ 2)
+ read -e -p "输入要移除的命令路径: " pattern
+ [ -z "$pattern" ] && { echo "不能为空"; break_end; continue; }
+ openclaw approvals allowlist remove "$pattern"
+ break_end
+ ;;
+ 0) return 0 ;;
+ *) echo "无效选择"; sleep 1 ;;
+ esac
+ done
+ }
+
+ openclaw_permission_menu() {
+ send_stats "OpenClaw权限管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo " OpenClaw 权限管理 (双层架构深度适配)"
+ echo "======================================="
+ openclaw_permission_render_status
+ echo "---------------------------------------"
+ echo -e "${gl_kjlan}1.${gl_bai} 切换为标准安全模式(日常推荐,弹卡片审批)"
+ echo -e "${gl_kjlan}2.${gl_bai} 切换为开发增强模式(允许智能体申请提权)"
+ echo -e "${gl_kjlan}3.${gl_bai} 切换为完全开放模式(${gl_hong}高风险!彻底解除所有宿主机拦截${gl_bai})"
+ echo -e "${gl_kjlan}4.${gl_bai} 恢复官方默认沙盒防御策略"
+ echo -e "${gl_kjlan}5.${gl_bai} 运行底层安全审计与自动修复"
+ echo -e "${gl_kjlan}6.${gl_bai} 管理 Exec 命令白名单"
+ echo -e "${gl_kjlan}0.${gl_bai} 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " perm_choice
+ case "$perm_choice" in
+ 1)
+ echo "准备应用:标准安全模式"
+ read -e -p "输入 yes 确认: " confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_standard; else echo "已取消"; fi
+ break_end
+ ;;
+ 2)
+ echo "准备应用:开发增强模式"
+ read -e -p "输入 yes 确认: " confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_developer; else echo "已取消"; fi
+ break_end
+ ;;
+ 3)
+ echo -e "${gl_hong}⚠️ 完全开放模式会彻底瓦解 exec 审批并自动放行高危代码。${gl_bai}"
+ read -e -p "输入 FULL 确认继续: " confirm
+ if [ "$confirm" = "FULL" ]; then openclaw_permission_apply_full; else echo "已取消"; fi
+ break_end
+ ;;
+ 4)
+ echo "将清除所有定制覆盖,恢复 OpenClaw 刚安装时的严格沙盒状态。"
+ read -e -p "输入 yes 确认: " confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_restore_official_defaults; else echo "已取消"; fi
+ break_end
+ ;;
+ 5)
+ openclaw_permission_run_audit
+ ;;
+ 6)
+ openclaw_permission_manage_allowlist
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_multiagent_config_file() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ echo "$config_file"
+ return 0
+ fi
+ openclaw config file 2>/dev/null | tail -n 1
+ }
+
+ openclaw_multiagent_default_agent() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+value="(unset)"
+try:
+ with open(path) as f:
+ data=json.load(f)
+ defaults=data.get("agents",{}).get("defaults",{}) if isinstance(data,dict) else {}
+ value=defaults.get("agent") or None
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and (item.get("isDefault") or item.get("default")):
+ value=item.get("id")
+ break
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and item.get("id"):
+ value=item.get("id")
+ break
+except Exception:
+ value="(unset)"
+print(value or "(unset)")
+PY
+ return 0
+ fi
+ local value
+ value=$(openclaw config get agents.defaults.agent 2>&1 | head -n 1)
+ if [ -z "$value" ] || echo "$value" | grep -qi "config path not found"; then
+ value=$(openclaw agents list --json 2>/dev/null | python3 -c 'import json,sys
+try:
+ data=json.load(sys.stdin)
+ print(next((x.get("id","(unset)") for x in data if x.get("isDefault")), "(unset)"))
+except Exception:
+ print("(unset)")' 2>/dev/null)
+ fi
+ [ -z "$value" ] && value="(unset)"
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ }
+
+ openclaw_multiagent_require_openclaw() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ 未检测到 openclaw 命令,请先安装或初始化 OpenClaw。"
+ return 1
+ fi
+ return 0
+ }
+
+ openclaw_multiagent_agents_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents list --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:从配置文件读取
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ agents=data.get("agents",{}).get("list",[])
+ if not isinstance(agents,list):
+ agents=[]
+ print(json.dumps(agents, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_bindings_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents bindings --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:从配置文件读取
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ bindings=data.get("agents",{}).get("bindings",[])
+ if not isinstance(bindings,list):
+ bindings=[]
+ results=[]
+ for item in bindings:
+ if not isinstance(item,dict):
+ continue
+ results.append({"agentId": item.get("agentId") or item.get("agent") or "?", "description": item.get("description") or "-"})
+ print(json.dumps(results, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_sessions_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw sessions --json 2>/dev/null | grep -v '^\[')
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:从文件系统读取
+ python3 <<'PY'
+import json,os
+base=os.path.expanduser("~/.openclaw/agents")
+sessions=[]
+try:
+ agent_dirs=[d for d in os.listdir(base) if os.path.isdir(os.path.join(base,d))]
+except Exception:
+ agent_dirs=[]
+for agent_id in agent_dirs:
+ path=os.path.join(base,agent_id,"sessions","sessions.json")
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path) as f:
+ data=json.load(f)
+ except Exception:
+ continue
+ if isinstance(data,dict):
+ items=data.items()
+ elif isinstance(data,list):
+ items=[(item.get("key") or "?", item) for item in data if isinstance(item,dict)]
+ else:
+ continue
+ for key,item in items:
+ if not isinstance(item,dict):
+ continue
+ model=item.get("model") or "-"
+ sessions.append({"agentId": agent_id, "key": key, "model": model})
+print(json.dumps({"path":"(filesystem)","count":len(sessions),"sessions":sessions}, ensure_ascii=False))
+PY
+ }
+
+ openclaw_multiagent_render_status() {
+ local config_file default_agent
+ config_file=$(openclaw_multiagent_config_file)
+ default_agent=$(openclaw_multiagent_default_agent)
+ echo "配置文件: ${config_file:-$(openclaw_permission_config_file)}"
+ echo "默认智能体: $default_agent"
+ python3 -c '
+import json,sys
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+sess_obj=json.loads(sys.argv[3] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+print("已配置智能体数: %s" % len(agents))
+print("路由绑定数: %s" % len(bindings))
+print("会话总数: %s" % len(sessions))
+print("---------------------------------------")
+if not agents:
+ print("当前未配置任何多智能体。")
+else:
+ for item in agents[:8]:
+ aid = item.get("id","?")
+ identity = item.get("identityName") or item.get("name") or "-"
+ emoji = item.get("identityEmoji") or ""
+ ws = item.get("workspace") or "-"
+ model = item.get("model") or "-"
+ is_default = item.get("isDefault", False)
+ bcount = item.get("bindings", 0)
+ default_tag = " [默认]" if is_default else ""
+ print("- 智能体ID: \033[1;36m%s\033[0m%s" % (aid, default_tag))
+ print(" 身份名称: %s %s" % (identity, emoji))
+ print(" 模型: %s" % model)
+ print(" 工作目录: %s" % ws)
+ print(" 绑定数: %s" % bcount)
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)" "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_list_agents() {
+ send_stats "OpenClaw多智能体-列出Agent"
+ python3 -c 'import json,sys; agents=json.loads(sys.argv[1] or "[]");
+if not agents: print("暂无已配置 Agent。"); raise SystemExit(0)
+for idx,item in enumerate(agents,1):
+ print("%s. %s" % (idx, item.get("id","?"))); print(" workspace : %s" % item.get("workspace","-")); ident=(item.get("identityName") or "-") + ((" " + item.get("identityEmoji")) if item.get("identityEmoji") else ""); print(" identity : %s" % ident.strip()); print(" model : %s" % (item.get("model") or "-")); print(" bindings : %s" % item.get("bindings",0)); print(" default : %s" % ("yes" if item.get("isDefault") else "no"))' "$(openclaw_multiagent_agents_json)"
+ }
+
+ openclaw_multiagent_add_agent() {
+ send_stats "OpenClaw多智能体-新增Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id workspace confirm
+ read -e -p "请输入新的 Agent ID: " agent_id
+ [ -z "$agent_id" ] && echo "已取消:Agent ID 不能为空。" && return 1
+ read -e -p "请输入 workspace 路径(默认 ~/.openclaw/workspace-${agent_id}): " workspace
+ [ -z "$workspace" ] && workspace="~/.openclaw/workspace-${agent_id}"
+ echo "将创建智能体: $agent_id"
+ echo "工作目录: $workspace"
+ read -e -p "输入 yes 确认继续: " confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents add "$agent_id" --workspace "$workspace"; then
+ echo "✅ 智能体创建成功: $agent_id"
+ local name theme
+ read -e -p "请输入智能体身份名称 (如: 代码专家): " name
+ [ -z "$name" ] && name="$agent_id"
+ read -e -p "请输入智能体性格主题 (如: 严谨、高效): " theme
+ [ -z "$theme" ] && theme="助手"
+ echo "正在配置智能体身份..."
+ openclaw agents set-identity --agent "$agent_id" --name "$name" --theme "$theme"
+ else
+ echo "❌ 智能体创建失败"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_delete_agent() {
+ send_stats "OpenClaw多智能体-删除Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id confirm
+ read -e -p "请输入要删除的 Agent ID: " agent_id
+ [ -z "$agent_id" ] && echo "已取消:Agent ID 不能为空。" && return 1
+ echo "⚠️ 删除智能体可能影响其工作目录、路由绑定与会话路由。"
+ read -e -p "输入 DELETE 确认删除 ${agent_id}: " confirm
+ [ "$confirm" = "DELETE" ] || { echo "已取消"; return 1; }
+ if openclaw agents delete "$agent_id"; then
+ echo "✅ 智能体删除成功: $agent_id"
+ else
+ echo "❌ 智能体删除失败"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_list_bindings() {
+ send_stats "OpenClaw多智能体-查看路由绑定"
+ python3 -c '
+import json,sys
+bindings=json.loads(sys.argv[1] or "[]")
+if not bindings:
+ print("暂无路由绑定。")
+ raise SystemExit(0)
+for idx,item in enumerate(bindings,1):
+ desc = item.get("description") or "-"
+ print("%s. agent=%s | %s" % (idx, item.get("agentId","?"), desc))
+' "$(openclaw_multiagent_bindings_json)"
+ }
+
+ openclaw_multiagent_add_binding() {
+ send_stats "OpenClaw多智能体-新增路由绑定"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "请输入智能体 ID: " agent_id
+ read -e -p "请输入路由绑定值(如 telegram:ops / discord:guild-a): " bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "已取消:参数不能为空。" && return 1
+ echo "将绑定智能体 [$agent_id] -> [$bind_value]"
+ read -e -p "输入 yes 确认继续: " confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents bind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 路由绑定添加成功"
+ else
+ echo "❌ 路由绑定添加失败"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_remove_binding() {
+ send_stats "OpenClaw多智能体-移除路由绑定"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "请输入智能体 ID: " agent_id
+ read -e -p "请输入要移除的路由绑定值: " bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "已取消:参数不能为空。" && return 1
+ echo "将移除智能体 [$agent_id] 的路由绑定 [$bind_value]"
+ read -e -p "输入 yes 确认继续: " confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents unbind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 路由绑定移除成功"
+ else
+ echo "❌ 路由绑定移除失败"
+ return 1
+ fi
+ }
+
+
+ openclaw_multiagent_show_sessions() {
+ send_stats "OpenClaw多智能体-会话概况"
+ python3 -c '
+import json,sys
+sess_obj=json.loads(sys.argv[1] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+if not sessions:
+ print("暂无 session 数据。")
+ raise SystemExit(0)
+by_agent={}
+for item in sessions:
+ aid = item.get("agentId","?")
+ by_agent[aid] = by_agent.get(aid, 0) + 1
+print("会话汇总:")
+for agent_id,count in sorted(by_agent.items()):
+ print("- %s: %s" % (agent_id, count))
+print("---------------------------------------")
+for item in sessions[:10]:
+ key = item.get("key","-")
+ model = item.get("model") or "-"
+ aid = item.get("agentId","?")
+ tokens = ""
+ it = item.get("inputTokens")
+ ot = item.get("outputTokens")
+ if it is not None:
+ tokens = " | in=%s out=%s" % (it, ot or 0)
+ print("%s | %s | %s%s" % (aid, key, model, tokens))
+' "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_health_check() {
+ send_stats "OpenClaw多智能体-健康检查"
+ openclaw_multiagent_require_openclaw || return 1
+ local config_file
+ config_file=$(openclaw_multiagent_config_file)
+ echo "检查配置文件: ${config_file:-$(openclaw_permission_config_file)}"
+ openclaw config validate || echo "⚠️ 配置校验未通过,请检查上方输出。"
+ python3 -c '
+import json,sys,os
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+print("---------------------------------------")
+if not agents:
+ print("⚠️ 未发现已配置智能体。")
+else:
+ for item in agents:
+ ws = item.get("workspace") or ""
+ aid = item.get("id","?")
+ if ws and os.path.isdir(os.path.expanduser(ws)):
+ state = "OK"
+ elif aid == "main":
+ state = "OK"
+ else:
+ state = "MISSING"
+ model = item.get("model") or "-"
+ bcount = item.get("bindings", 0)
+ print("agent=%s workspace=%s model=%s bindings=%s [%s]" % (aid, ws or "-", model, bcount, state))
+print("路由绑定数=%s" % len(bindings))
+print("✅ 多智能体健康检查完成")
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)"
+ echo ""
+ echo "运行安全审计..."
+ openclaw security audit 2>/dev/null || echo "⚠️ 安全审计命令不可用"
+ }
+
+
+ openclaw_multiagent_set_identity() {
+ openclaw_multiagent_require_openclaw || return 1
+ openclaw_multiagent_list_agents
+ read -e -p "输入要修改身份的智能体ID: " agent_id
+ [ -z "$agent_id" ] && { echo "ID 不能为空"; return 1; }
+ echo "修改选项(留空跳过):"
+ read -e -p " 新名称: " new_name
+ read -e -p " 新 Emoji: " new_emoji
+ local cmd="openclaw agents set-identity --agent $agent_id"
+ [ -n "$new_name" ] && cmd="$cmd --name $new_name"
+ [ -n "$new_emoji" ] && cmd="$cmd --emoji $new_emoji"
+ echo "也可以从 IDENTITY.md 自动读取身份信息。"
+ read -e -p "是否从 IDENTITY.md 读取?(y/n): " from_id
+ if [ "$from_id" = "y" ]; then
+ cmd="openclaw agents set-identity --agent $agent_id --from-identity"
+ fi
+ eval "$cmd"
+ }
+
+ openclaw_multiagent_cleanup_sessions() {
+ openclaw_multiagent_require_openclaw || return 1
+ echo "即将清理过期/冗余会话数据..."
+ read -e -p "输入 yes 确认: " confirm
+ [ "$confirm" != "yes" ] && { echo "已取消"; return 0; }
+ openclaw sessions cleanup
+ }
+
+ openclaw_multiagent_menu() {
+ send_stats "OpenClaw多智能体管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 多智能体管理"
+ echo "======================================="
+ openclaw_multiagent_render_status
+ echo "---------------------------------------"
+ echo "1. 新增智能体"
+ echo "2. 删除智能体"
+ echo "3. 查看路由绑定"
+ echo "4. 新增路由绑定"
+ echo "5. 移除路由绑定"
+ echo "6. 查看会话概况"
+ echo "7. 运行多智能体健康检查"
+ echo "8. 修改智能体身份(名称/Emoji)"
+ echo "9. 清理过期会话"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " multi_choice
+ case "$multi_choice" in
+ 1) openclaw_multiagent_add_agent; break_end ;;
+ 2) openclaw_multiagent_delete_agent; break_end ;;
+ 3) openclaw_multiagent_list_bindings; break_end ;;
+ 4) openclaw_multiagent_add_binding; break_end ;;
+ 5) openclaw_multiagent_remove_binding; break_end ;;
+ 6) openclaw_multiagent_show_sessions; break_end ;;
+ 7) openclaw_multiagent_health_check; break_end ;;
+ 8) openclaw_multiagent_set_identity; break_end ;;
+ 9) openclaw_multiagent_cleanup_sessions; break_end ;;
+ 0) return 0 ;;
+ *) echo "无效的选择,请重试。"; sleep 1 ;;
+ esac
+ done
+ }
+
+
+openclaw_backup_restore_menu() {
+
+ send_stats "OpenClaw备份与还原"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 备份与还原"
+ echo "======================================="
+ openclaw_backup_render_file_list
+ echo "---------------------------------------"
+ echo "1. 备份记忆全量"
+ echo "2. 还原记忆全量"
+ echo "3. 备份 OpenClaw 项目(默认安全模式)"
+ echo "4. 还原 OpenClaw 项目(高级/高风险)"
+ echo "5. 删除备份文件"
+ echo "0. 返回上一级"
+ echo "---------------------------------------"
+ read -e -p "请输入你的选择: " backup_choice
+
+ case "$backup_choice" in
+ 1) openclaw_memory_backup_export ;;
+ 2) openclaw_memory_backup_import ;;
+ 3) openclaw_project_backup_export ;;
+ 4) openclaw_project_backup_import ;;
+ 5) openclaw_backup_delete_file ;;
+ 0) return 0 ;;
+ *)
+ echo "无效的选择,请重试。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ update_moltbot() {
+ echo "更新 OpenClaw..."
+ send_stats "更新 OpenClaw..."
+ install_node_and_tools
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+ npm install -g openclaw@latest
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ start_gateway
+ hash -r
+ add_app_id
+ echo "更新完成"
+ break_end
+ }
+
+
+ uninstall_moltbot() {
+ echo "卸载 OpenClaw..."
+ send_stats "卸载 OpenClaw..."
+ openclaw uninstall
+ npm uninstall -g openclaw
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ rm -rf "$HOME/.openclaw"
+ [ "$HOME" != "/root" ] && [ -d /root/.openclaw ] && echo "⚠️ 检测到 root 目录下仍存在 /root/.openclaw,如需清理请手动处理"
+ hash -r
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "卸载完成"
+ break_end
+ }
+
+ nano_openclaw_json() {
+ send_stats "编辑 OpenClaw 配置文件"
+ install nano
+ nano "$(openclaw_get_config_file)"
+ start_gateway
+ }
+
+
+
+
+
+
+ openclaw_find_webui_domain() {
+ local conf domain_list
+
+ domain_list=$(
+ grep -R "18789" /home/web/conf.d/*.conf 2>/dev/null \
+ | awk -F: '{print $1}' \
+ | sort -u \
+ | while read conf; do
+ basename "$conf" .conf
+ done
+ )
+
+ if [ -n "$domain_list" ]; then
+ echo "$domain_list"
+ fi
+ }
+
+
+
+ openclaw_show_webui_addr() {
+ local local_ip token domains
+
+ echo "=================================="
+ echo "OpenClaw WebUI 访问地址"
+ local_ip="127.0.0.1"
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+ echo
+ echo "本机地址:"
+ echo "http://${local_ip}:18789/#token=${token}"
+
+ domains=$(openclaw_find_webui_domain)
+ if [ -n "$domains" ]; then
+ echo "域名地址:"
+ echo "$domains" | while read d; do
+ echo "https://${d}/#token=${token}"
+ done
+ fi
+
+ echo "=================================="
+ }
+
+
+
+ # 添加域名(调用你给的函数)
+ openclaw_domain_webui() {
+ add_yuming
+ ldnmp_Proxy ${yuming} 127.0.0.1 18789
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+
+ clear
+ echo "访问地址:"
+ echo "https://${yuming}/#token=$token"
+ echo "先访问URL触发设备ID,然后回车下一步进行配对。"
+ read
+ echo -e "${gl_kjlan}正在加载设备列表……${gl_bai}"
+ # 自动添加域名到 allowedOrigins
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ new_origin="https://${yuming}"
+ # 使用 jq 安全修改 JSON,确保结构存在且不重复添加域名
+ if command -v jq >/dev/null 2>&1; then
+ tmp_json=$(mktemp)
+ jq 'if .gateway.controlUi == null then .gateway.controlUi = {"allowedOrigins": ["http://127.0.0.1"]} else . end | if (.gateway.controlUi.allowedOrigins | contains([$origin]) | not) then .gateway.controlUi.allowedOrigins += [$origin] else . end' --arg origin "$new_origin" "$config_file" > "$tmp_json" && mv "$tmp_json" "$config_file"
+ echo -e "${gl_kjlan}已将域名 ${yuming} 加入 allowedOrigins 配置${gl_bai}"
+ openclaw gateway restart >/dev/null 2>&1
+ fi
+ fi
+
+ openclaw devices list
+
+ read -e -p "请输入 Request_Key: " Request_Key
+
+ [ -z "$Request_Key" ] && {
+ echo "Request_Key 不能为空"
+ return 1
+ }
+
+ openclaw devices approve "$Request_Key"
+
+ }
+
+ # 删除域名
+ openclaw_remove_domain() {
+ echo "域名格式 example.com 不带https://"
+ web_del
+ }
+
+ # 主菜单
+ openclaw_webui_menu() {
+
+ send_stats "WebUI访问与设置"
+ while true; do
+ clear
+ openclaw_show_webui_addr
+ echo
+ echo "1. 添加域名访问"
+ echo "2. 删除域名访问"
+ echo "0. 退出"
+ echo
+ read -e -p "请选择: " choice
+
+ case "$choice" in
+ 1)
+ openclaw_domain_webui
+ echo
+ read -p "按回车返回菜单..."
+ ;;
+ 2)
+ openclaw_remove_domain
+ read -p "按回车返回菜单..."
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "无效选项"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ # 主循环
+ while true; do
+ show_menu
+ read choice
+ case $choice in
+ 1) install_moltbot ;;
+ 2) start_bot ;;
+ 3) stop_bot ;;
+ 4) view_logs ;;
+ 5) change_model ;;
+ 6) openclaw_api_manage_menu ;;
+ 7) change_tg_bot_code ;;
+ 8) install_plugin ;;
+ 9) install_skill ;;
+ 10) nano_openclaw_json ;;
+ 11) send_stats "初始化配置向导"
+ openclaw onboard --install-daemon
+ break_end
+ ;;
+ 12) send_stats "健康检测与修复"
+ openclaw doctor --fix
+ break_end
+ ;;
+ 13) openclaw_webui_menu ;;
+ 14) send_stats "TUI命令行对话"
+ openclaw tui
+ break_end
+ ;;
+ 15) openclaw_memory_menu ;;
+ 16) openclaw_permission_menu ;;
+ 17) openclaw_multiagent_menu ;;
+ 18) openclaw_backup_restore_menu ;;
+ 19) update_moltbot ;;
+ 20) uninstall_moltbot ;;
+ *) break ;;
+ esac
+ done
+
+}
+
+
+
+
+linux_panel() {
+
+local sub_choice="$1"
+
+clear
+cd ~
+install git
+echo -e "${gl_kjlan}正在更新应用列表请稍等……${gl_bai}"
+if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+fi
+
+while true; do
+
+ if [ -z "$sub_choice" ]; then
+ clear
+ echo -e "应用市场"
+ echo -e "${gl_kjlan}-------------------------"
+
+ local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+
+ # 用循环设置颜色
+ for i in {1..150}; do
+ if echo "$app_numbers" | grep -q "^$i$"; then
+ declare "color$i=${gl_lv}"
+ else
+ declare "color$i=${gl_bai}"
+ fi
+ done
+
+ echo -e "${gl_kjlan}1. ${color1}宝塔面板官方版 ${gl_kjlan}2. ${color2}aaPanel宝塔国际版"
+ echo -e "${gl_kjlan}3. ${color3}1Panel新一代管理面板 ${gl_kjlan}4. ${color4}NginxProxyManager可视化面板"
+ echo -e "${gl_kjlan}5. ${color5}OpenList多存储文件列表程序 ${gl_kjlan}6. ${color6}Ubuntu远程桌面网页版"
+ echo -e "${gl_kjlan}7. ${color7}哪吒探针VPS监控面板 ${gl_kjlan}8. ${color8}QB离线BT磁力下载面板"
+ echo -e "${gl_kjlan}9. ${color9}Poste.io邮件服务器程序 ${gl_kjlan}10. ${color10}RocketChat多人在线聊天系统"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}11. ${color11}禅道项目管理软件 ${gl_kjlan}12. ${color12}青龙面板定时任务管理平台"
+ echo -e "${gl_kjlan}13. ${color13}Cloudreve网盘 ${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}简单图床图片管理程序"
+ echo -e "${gl_kjlan}15. ${color15}emby多媒体管理系统 ${gl_kjlan}16. ${color16}Speedtest测速面板"
+ echo -e "${gl_kjlan}17. ${color17}AdGuardHome去广告软件 ${gl_kjlan}18. ${color18}onlyoffice在线办公OFFICE"
+ echo -e "${gl_kjlan}19. ${color19}雷池WAF防火墙面板 ${gl_kjlan}20. ${color20}portainer容器管理面板"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}21. ${color21}VScode网页版 ${gl_kjlan}22. ${color22}UptimeKuma监控工具"
+ echo -e "${gl_kjlan}23. ${color23}Memos网页备忘录 ${gl_kjlan}24. ${color24}Webtop远程桌面网页版 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}25. ${color25}Nextcloud网盘 ${gl_kjlan}26. ${color26}QD-Today定时任务管理框架"
+ echo -e "${gl_kjlan}27. ${color27}Dockge容器堆栈管理面板 ${gl_kjlan}28. ${color28}LibreSpeed测速工具"
+ echo -e "${gl_kjlan}29. ${color29}searxng聚合搜索站 ${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism私有相册系统"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}31. ${color31}StirlingPDF工具大全 ${gl_kjlan}32. ${color32}drawio免费的在线图表软件 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${color33}Sun-Panel导航面板 ${gl_kjlan}34. ${color34}Pingvin-Share文件分享平台"
+ echo -e "${gl_kjlan}35. ${color35}极简朋友圈 ${gl_kjlan}36. ${color36}LobeChatAI聊天聚合网站"
+ echo -e "${gl_kjlan}37. ${color37}MyIP工具箱 ${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}小雅alist全家桶"
+ echo -e "${gl_kjlan}39. ${color39}Bililive直播录制工具 ${gl_kjlan}40. ${color40}webssh网页版SSH连接工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}41. ${color41}耗子管理面板 ${gl_kjlan}42. ${color42}Nexterm远程连接工具"
+ echo -e "${gl_kjlan}43. ${color43}RustDesk远程桌面(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk远程桌面(中继端) ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}45. ${color45}Docker加速站 ${gl_kjlan}46. ${color46}GitHub加速站 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}47. ${color47}普罗米修斯监控 ${gl_kjlan}48. ${color48}普罗米修斯(主机监控)"
+ echo -e "${gl_kjlan}49. ${color49}普罗米修斯(容器监控) ${gl_kjlan}50. ${color50}补货监控工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}51. ${color51}PVE开小鸡面板 ${gl_kjlan}52. ${color52}DPanel容器管理面板"
+ echo -e "${gl_kjlan}53. ${color53}llama3聊天AI大模型 ${gl_kjlan}54. ${color54}AMH主机建站管理面板"
+ echo -e "${gl_kjlan}55. ${color55}FRP内网穿透(服务端) ${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP内网穿透(客户端) ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}57. ${color57}Deepseek聊天AI大模型 ${gl_kjlan}58. ${color58}Dify大模型知识库 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}59. ${color59}NewAPI大模型资产管理 ${gl_kjlan}60. ${color60}JumpServer开源堡垒机"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}61. ${color61}在线翻译服务器 ${gl_kjlan}62. ${color62}RAGFlow大模型知识库"
+ echo -e "${gl_kjlan}63. ${color63}OpenWebUI自托管AI平台 ${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools工具箱"
+ echo -e "${gl_kjlan}65. ${color65}n8n自动化工作流平台 ${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp视频下载工具"
+ echo -e "${gl_kjlan}67. ${color67}ddns-go动态DNS管理工具 ${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL证书管理平台"
+ echo -e "${gl_kjlan}69. ${color69}SFTPGo文件传输工具 ${gl_kjlan}70. ${color70}AstrBot聊天机器人框架"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}71. ${color71}Navidrome私有音乐服务器 ${gl_kjlan}72. ${color72}bitwarden密码管理器 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}73. ${color73}LibreTV私有影视 ${gl_kjlan}74. ${color74}MoonTV私有影视"
+ echo -e "${gl_kjlan}75. ${color75}Melody音乐精灵 ${gl_kjlan}76. ${color76}在线DOS老游戏"
+ echo -e "${gl_kjlan}77. ${color77}迅雷离线下载工具 ${gl_kjlan}78. ${color78}PandaWiki智能文档管理系统"
+ echo -e "${gl_kjlan}79. ${color79}Beszel服务器监控 ${gl_kjlan}80. ${color80}linkwarden书签管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}81. ${color81}JitsiMeet视频会议 ${gl_kjlan}82. ${color82}gpt-load高性能AI透明代理"
+ echo -e "${gl_kjlan}83. ${color83}komari服务器监控工具 ${gl_kjlan}84. ${color84}Wallos个人财务管理工具"
+ echo -e "${gl_kjlan}85. ${color85}immich图片视频管理器 ${gl_kjlan}86. ${color86}jellyfin媒体管理系统"
+ echo -e "${gl_kjlan}87. ${color87}SyncTV一起看片神器 ${gl_kjlan}88. ${color88}Owncast自托管直播平台"
+ echo -e "${gl_kjlan}89. ${color89}FileCodeBox文件快递 ${gl_kjlan}90. ${color90}matrix去中心化聊天协议"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}91. ${color91}gitea私有代码仓库 ${gl_kjlan}92. ${color92}FileBrowser文件管理器"
+ echo -e "${gl_kjlan}93. ${color93}Dufs极简静态文件服务器 ${gl_kjlan}94. ${color94}Gopeed高速下载工具"
+ echo -e "${gl_kjlan}95. ${color95}paperless文档管理平台 ${gl_kjlan}96. ${color96}2FAuth自托管二步验证器"
+ echo -e "${gl_kjlan}97. ${color97}WireGuard组网(服务端) ${gl_kjlan}98. ${color98}WireGuard组网(客户端)"
+ echo -e "${gl_kjlan}99. ${color99}DSM群晖虚拟机 ${gl_kjlan}100. ${color100}Syncthing点对点文件同步工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}101. ${color101}AI视频生成工具 ${gl_kjlan}102. ${color102}VoceChat多人在线聊天系统"
+ echo -e "${gl_kjlan}103. ${color103}Umami网站统计工具 ${gl_kjlan}104. ${color104}Stream四层代理转发工具"
+ echo -e "${gl_kjlan}105. ${color105}思源笔记 ${gl_kjlan}106. ${color106}Drawnix开源白板工具"
+ echo -e "${gl_kjlan}107. ${color107}PanSou网盘搜索 ${gl_kjlan}108. ${color108}LangBot聊天机器人"
+ echo -e "${gl_kjlan}109. ${color109}ZFile在线网盘 ${gl_kjlan}110. ${color110}Karakeep书签管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}111. ${color111}多格式文件转换工具 ${gl_kjlan}112. ${color112}Lucky大内网穿透工具"
+ echo -e "${gl_kjlan}113. ${color113}Firefox浏览器 ${gl_kjlan}114. ${color114}OpenClaw机器人管理工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}115. ${color115}Hermes机器人管理工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}第三方应用列表"
+ echo -e "${gl_kjlan}想要让你的应用出现在这里?查看开发者指南: ${gl_huang}https://dev.kejilion.sh/${gl_bai}"
+
+ for f in "$HOME"/apps/*.conf; do
+ [ -e "$f" ] || continue
+ local base_name=$(basename "$f" .conf)
+ # 获取应用描述
+ local app_text=$(grep "app_text=" "$f" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
+
+ # 检查安装状态 (匹配 appno.txt 中的 ID)
+ # 这里假设 appno.txt 中记录的是 base_name (即文件名)
+ if echo "$app_numbers" | grep -q "^$base_name$"; then
+ # 如果已安装:显示 base_name - 描述 [已安装] (绿色)
+ echo -e "${gl_kjlan}$base_name${gl_bai} - ${gl_lv}$app_text [已安装]${gl_bai}"
+ else
+ # 如果未安装:正常显示
+ echo -e "${gl_kjlan}$base_name${gl_bai} - $app_text"
+ fi
+ done
+
+
+
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}b. ${gl_bai}备份全部应用数据 ${gl_kjlan}r. ${gl_bai}还原全部应用数据"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "请输入你的选择: " sub_choice
+ fi
+
+ case $sub_choice in
+ 1|bt|baota)
+ local app_id="1"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="宝塔面板"
+ local panelurl="https://www.bt.cn/new/index.html"
+
+ panel_app_install() {
+ if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+
+
+ ;;
+ 2|aapanel)
+
+
+ local app_id="2"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="aapanel"
+ local panelurl="https://www.aapanel.com/new/index.html"
+
+ panel_app_install() {
+ URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+ ;;
+ 3|1p|1panel)
+
+ local app_id="3"
+ local lujing="command -v 1pctl"
+ local panelname="1Panel"
+ local panelurl="https://1panel.cn/"
+
+ panel_app_install() {
+ install bash
+ bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
+ }
+
+ panel_app_manage() {
+ 1pctl user-info
+ 1pctl update password
+ }
+
+ panel_app_uninstall() {
+ 1pctl uninstall
+ }
+
+ install_panel
+
+ ;;
+ 4|npm)
+
+ local app_id="4"
+ local docker_name="npm"
+ local docker_img="jc21/nginx-proxy-manager:latest"
+ local docker_port=81
+
+ docker_rum() {
+
+ docker run -d \
+ --name=$docker_name \
+ -p ${docker_port}:81 \
+ -p 80:80 \
+ -p 443:443 \
+ -v /home/docker/npm/data:/data \
+ -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
+ --restart=always \
+ $docker_img
+
+
+ }
+
+ local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
+ local docker_url="官网介绍: https://nginxproxymanager.com/"
+ local docker_use="echo \"初始用户名: admin@example.com\""
+ local docker_passwd="echo \"初始密码: changeme\""
+ local app_size="1"
+
+ docker_app
+
+ ;;
+
+ 5|openlist)
+
+ local app_id="5"
+ local docker_name="openlist"
+ local docker_img="openlistteam/openlist:latest-aria2"
+ local docker_port=5244
+
+ docker_rum() {
+
+ mkdir -p /home/docker/openlist
+ chmod -R 777 /home/docker/openlist
+
+ docker run -d \
+ --restart=always \
+ -v /home/docker/openlist:/opt/openlist/data \
+ -p ${docker_port}:5244 \
+ -e PUID=0 \
+ -e PGID=0 \
+ -e UMASK=022 \
+ --name="openlist" \
+ openlistteam/openlist:latest-aria2
+
+ }
+
+
+ local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
+ local docker_url="官网介绍: ${gh_https_url}github.com/OpenListTeam/OpenList"
+ local docker_use="docker exec openlist ./openlist admin random"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 6|webtop-ubuntu)
+
+ local app_id="6"
+ local docker_name="webtop-ubuntu"
+ local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
+ local docker_port=3006
+
+ docker_rum() {
+
+ read -e -p "设置登录用户名: " admin
+ read -e -p "设置登录用户密码: " admin_password
+ docker run -d \
+ --name=webtop-ubuntu \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:ubuntu-kde
+
+
+ }
+
+
+ local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
+ local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+ 7|nezha)
+ clear
+ send_stats "搭建哪吒"
+
+ local app_id="7"
+ local docker_name="nezha-dashboard"
+ local docker_port=8008
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+ clear
+ echo -e "哪吒监控 $check_docker $update_status"
+ echo "开源、轻量、易用的服务器监控与运维工具"
+ echo "官网搭建文档: https://nezha.wiki/guide/dashboard.html"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 使用"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+
+ case $choice in
+ 1)
+ check_disk_space 1
+ install unzip jq
+ install_docker
+ curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+ ;;
+
+ 8|qb|QB)
+
+ local app_id="8"
+ local docker_name="qbittorrent"
+ local docker_img="lscr.io/linuxserver/qbittorrent:latest"
+ local docker_port=8081
+
+ docker_rum() {
+
+ docker run -d \
+ --name=qbittorrent \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e WEBUI_PORT=${docker_port} \
+ -e TORRENTING_PORT=56881 \
+ -p ${docker_port}:${docker_port} \
+ -p 56881:56881 \
+ -p 56881:56881/udp \
+ -v /home/docker/qbittorrent/config:/config \
+ -v /home/docker/qbittorrent/downloads:/downloads \
+ --restart=always \
+ lscr.io/linuxserver/qbittorrent:latest
+
+ }
+
+ local docker_describe="qbittorrent离线BT磁力下载服务"
+ local docker_url="官网介绍: https://hub.docker.com/r/linuxserver/qbittorrent"
+ local docker_use="sleep 3"
+ local docker_passwd="docker logs qbittorrent"
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 9|mail)
+ send_stats "搭建邮局"
+ clear
+ install telnet
+ local app_id="9"
+ local docker_name=“mailserver”
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+
+ clear
+ echo -e "邮局服务 $check_docker $update_status"
+ echo "poste.io 是一个开源的邮件服务器解决方案,"
+ echo "视频介绍: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+
+ echo ""
+ echo "端口检测"
+ port=25
+ timeout=3
+ if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
+ echo -e "${gl_lv}端口 $port 当前可用${gl_bai}"
+ else
+ echo -e "${gl_hong}端口 $port 当前不可用${gl_bai}"
+ fi
+ echo ""
+
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ yuming=$(cat /home/docker/mail.txt)
+ echo "访问地址: "
+ echo "https://$yuming"
+ fi
+
+ echo "------------------------"
+ echo "1. 安装 2. 更新 3. 卸载"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+
+ case $choice in
+ 1)
+ setup_docker_dir
+ check_disk_space 2 /home/docker
+ read -e -p "请设置邮箱域名 例如 mail.yuming.com : " yuming
+ mkdir -p /home/docker
+ echo "$yuming" > /home/docker/mail.txt
+ echo "------------------------"
+ ip_address
+ echo "先解析这些DNS记录"
+ echo "A mail $ipv4_address"
+ echo "CNAME imap $yuming"
+ echo "CNAME pop $yuming"
+ echo "CNAME smtp $yuming"
+ echo "MX @ $yuming"
+ echo "TXT @ v=spf1 mx ~all"
+ echo "TXT ? ?"
+ echo ""
+ echo "------------------------"
+ echo "按任意键继续..."
+ read -n 1 -s -r -p ""
+
+ install jq
+ install_docker
+
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.io
+
+
+ add_app_id
+
+ clear
+ echo "poste.io已经安装完成"
+ echo "------------------------"
+ echo "您可以使用以下地址访问poste.io:"
+ echo "https://$yuming"
+ echo ""
+
+ ;;
+
+ 2)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.i
+ yuming=$(cat /home/docker/mail.txt)
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.i
+
+
+ add_app_id
+
+ clear
+ echo "poste.io已经安装完成"
+ echo "------------------------"
+ echo "您可以使用以下地址访问poste.io:"
+ echo "https://$yuming"
+ echo ""
+ ;;
+ 3)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.io
+ rm /home/docker/mail.txt
+ rm -rf /home/docker/mail
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "应用已卸载"
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 10|rocketchat)
+
+ local app_id="10"
+ local app_name="Rocket.Chat聊天系统"
+ local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
+ local app_url="官方介绍: https://www.rocket.chat/"
+ local docker_name="rocketchat"
+ local docker_port="3897"
+ local app_size="2"
+
+ docker_app_install() {
+ docker run --name db -d --restart=always \
+ -v /home/docker/mongo/dump:/dump \
+ mongo:latest --replSet rs5 --oplogSize 256
+ sleep 1
+ docker exec db mongosh --eval "printjson(rs.initiate())"
+ sleep 5
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+
+ clear
+ ip_address
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat:latest
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ clear
+ ip_address
+ echo "rocket.chat已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_uninstall() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat
+ docker rm -f db
+ docker rmi -f mongo:latest
+ rm -rf /home/docker/mongo
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 11|zentao)
+ local app_id="11"
+ local docker_name="zentao-server"
+ local docker_img="idoop/zentao:latest"
+ local docker_port=82
+
+
+ docker_rum() {
+
+
+ docker run -d -p ${docker_port}:80 \
+ -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
+ -e BIND_ADDRESS="false" \
+ -v /home/docker/zentao-server/:/opt/zbox/ \
+ --add-host smtp.exmail.qq.com:163.177.90.125 \
+ --name zentao-server \
+ --restart=always \
+ idoop/zentao:latest
+
+
+ }
+
+ local docker_describe="禅道是通用的项目管理软件"
+ local docker_url="官网介绍: https://www.zentao.net/"
+ local docker_use="echo \"初始用户名: admin\""
+ local docker_passwd="echo \"初始密码: 123456\""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 12|qinglong)
+ local app_id="12"
+ local docker_name="qinglong"
+ local docker_img="whyour/qinglong:latest"
+ local docker_port=5700
+
+ docker_rum() {
+
+
+ docker run -d \
+ -v /home/docker/qinglong/data:/ql/data \
+ -p ${docker_port}:5700 \
+ --name qinglong \
+ --hostname qinglong \
+ --restart=always \
+ whyour/qinglong:latest
+
+
+ }
+
+ local docker_describe="青龙面板是一个定时任务管理平台"
+ local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 13|cloudreve)
+
+ local app_id="13"
+ local app_name="cloudreve网盘"
+ local app_text="cloudreve是一个支持多家云存储的网盘系统"
+ local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
+ local docker_name="cloudreve"
+ local docker_port="5212"
+ local app_size="2"
+
+ docker_app_install() {
+ cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
+ curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
+ sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
+ cd /home/docker/cloud/
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ cd /home/docker/cloud/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ rm -rf /home/docker/cloud
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+ 14|easyimage)
+ local app_id="14"
+ local docker_name="easyimage"
+ local docker_img="ddsderek/easyimage:latest"
+ local docker_port=8014
+ docker_rum() {
+
+ docker run -d \
+ --name easyimage \
+ -p ${docker_port}:80 \
+ -e TZ=Asia/Shanghai \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -v /home/docker/easyimage/config:/app/web/config \
+ -v /home/docker/easyimage/i:/app/web/i \
+ --restart=always \
+ ddsderek/easyimage:latest
+
+ }
+
+ local docker_describe="简单图床是一个简单的图床程序"
+ local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 15|emby)
+ local app_id="15"
+ local docker_name="emby"
+ local docker_img="linuxserver/emby:latest"
+ local docker_port=8015
+
+ docker_rum() {
+
+ docker run -d --name=emby --restart=always \
+ -v /home/docker/emby/config:/config \
+ -v /home/docker/emby/share1:/mnt/share1 \
+ -v /home/docker/emby/share2:/mnt/share2 \
+ -v /mnt/notify:/mnt/notify \
+ -p ${docker_port}:8096 \
+ -e UID=1000 -e GID=100 -e GIDLIST=100 \
+ linuxserver/emby:latest
+
+ }
+
+
+ local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
+ local docker_url="官网介绍: https://emby.media/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 16|looking)
+ local app_id="16"
+ local docker_name="looking-glass"
+ local docker_img="wikihostinc/looking-glass-server"
+ local docker_port=8016
+
+
+ docker_rum() {
+
+ docker run -d --name looking-glass --restart=always -p ${docker_port}:80 wikihostinc/looking-glass-server
+
+ }
+
+ local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
+ local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 17|adguardhome)
+
+ local app_id="17"
+ local docker_name="adguardhome"
+ local docker_img="adguard/adguardhome"
+ local docker_port=8017
+
+ docker_rum() {
+
+ docker run -d \
+ --name adguardhome \
+ -v /home/docker/adguardhome/work:/opt/adguardhome/work \
+ -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
+ -p 53:53/tcp \
+ -p 53:53/udp \
+ -p ${docker_port}:3000/tcp \
+ --restart=always \
+ adguard/adguardhome
+
+
+ }
+
+
+ local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
+ local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 18|onlyoffice)
+
+ local app_id="18"
+ local docker_name="onlyoffice"
+ local docker_img="onlyoffice/documentserver"
+ local docker_port=8018
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:80 \
+ --restart=always \
+ --name onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
+ onlyoffice/documentserver
+
+
+ }
+
+ local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
+ local docker_url="官网介绍: https://www.onlyoffice.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 19|safeline)
+ send_stats "搭建雷池"
+
+ local app_id="19"
+ local docker_name=safeline-mgt
+ local docker_port=9443
+ while true; do
+ check_docker_app
+ clear
+ echo -e "雷池服务 $check_docker"
+ echo "雷池是长亭科技开发的WAF站点防火墙程序面板,可以反代站点进行自动化防御"
+ echo "视频介绍: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ check_docker_app_ip
+ fi
+ echo ""
+
+ echo "------------------------"
+ echo "1. 安装 2. 更新 3. 重置密码 4. 卸载"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "输入你的选择: " choice
+
+ case $choice in
+ 1)
+ install_docker
+ check_disk_space 5
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
+
+ add_app_id
+ clear
+ echo "雷池WAF面板已经安装完成"
+ check_docker_app_ip
+ docker exec safeline-mgt resetadmin
+
+ ;;
+
+ 2)
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
+ docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
+ echo ""
+
+ add_app_id
+ clear
+ echo "雷池WAF面板已经更新完成"
+ check_docker_app_ip
+ ;;
+ 3)
+ docker exec safeline-mgt resetadmin
+ ;;
+ 4)
+ cd /data/safeline
+ docker compose down --rmi all
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "如果你是默认安装目录那现在项目已经卸载。如果你是自定义安装目录你需要到安装目录下自行执行:"
+ echo "docker compose down && docker compose down --rmi all"
+ ;;
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 20|portainer)
+ local app_id="20"
+ local docker_name="portainer"
+ local docker_img="portainer/portainer"
+ local docker_port=8020
+
+ docker_rum() {
+
+ docker run -d \
+ --name portainer \
+ -p ${docker_port}:9000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/portainer:/data \
+ --restart=always \
+ portainer/portainer
+
+ }
+
+
+ local docker_describe="portainer是一个轻量级的docker容器管理面板"
+ local docker_url="官网介绍: https://www.portainer.io/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 21|vscode)
+ local app_id="21"
+ local docker_name="vscode-web"
+ local docker_img="codercom/code-server"
+ local docker_port=8021
+
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart=always codercom/code-server
+
+ }
+
+
+ local docker_describe="VScode是一款强大的在线代码编写工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
+ local docker_use="sleep 3"
+ local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 22|uptime-kuma)
+ local app_id="22"
+ local docker_name="uptime-kuma"
+ local docker_img="louislam/uptime-kuma:latest"
+ local docker_port=8022
+
+
+ docker_rum() {
+
+ docker run -d \
+ --name=uptime-kuma \
+ -p ${docker_port}:3001 \
+ -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
+ --restart=always \
+ louislam/uptime-kuma:latest
+
+ }
+
+
+ local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 23|memos)
+ local app_id="23"
+ local docker_name="memos"
+ local docker_img="neosmemo/memos:stable"
+ local docker_port=8023
+
+ docker_rum() {
+
+ docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart=always neosmemo/memos:stable
+
+ }
+
+ local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
+ local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 24|webtop)
+ local app_id="24"
+ local docker_name="webtop"
+ local docker_img="lscr.io/linuxserver/webtop:latest"
+ local docker_port=8024
+
+ docker_rum() {
+
+ read -e -p "设置登录用户名: " admin
+ read -e -p "设置登录用户密码: " admin_password
+ docker run -d \
+ --name=webtop \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -e LC_ALL=zh_CN.UTF-8 \
+ -e DOCKER_MODS=linuxserver/mods:universal-package-install \
+ -e INSTALL_PACKAGES=font-noto-cjk \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:latest
+
+ }
+
+
+ local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
+ local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 25|nextcloud)
+ local app_id="25"
+ local docker_name="nextcloud"
+ local docker_img="nextcloud:latest"
+ local docker_port=8025
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+
+ }
+
+ local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
+ local docker_url="官网介绍: https://nextcloud.com/"
+ local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 26|qd)
+ local app_id="26"
+ local docker_name="qd"
+ local docker_img="qdtoday/qd:latest"
+ local docker_port=8026
+
+ docker_rum() {
+
+ docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+
+ }
+
+ local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
+ local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 27|dockge)
+ local app_id="27"
+ local docker_name="dockge"
+ local docker_img="louislam/dockge:latest"
+ local docker_port=8027
+
+ docker_rum() {
+
+ docker run -d --name dockge --restart=always -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+
+ }
+
+ local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
+ local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 28|speedtest)
+ local app_id="28"
+ local docker_name="speedtest"
+ local docker_img="ghcr.io/librespeed/speedtest"
+ local docker_port=8028
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 --name speedtest --restart=always ghcr.io/librespeed/speedtest
+
+ }
+
+ local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
+ local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 29|searxng)
+ local app_id="29"
+ local docker_name="searxng"
+ local docker_img="searxng/searxng"
+ local docker_port=8029
+
+ docker_rum() {
+
+ docker run -d \
+ --name searxng \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v "/home/docker/searxng:/etc/searxng" \
+ searxng/searxng
+
+ }
+
+ local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
+ local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 30|photoprism)
+ local app_id="30"
+ local docker_name="photoprism"
+ local docker_img="photoprism/photoprism:latest"
+ local docker_port=8030
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d \
+ --name photoprism \
+ --restart=always \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ -p ${docker_port}:2342 \
+ -e PHOTOPRISM_UPLOAD_NSFW="true" \
+ -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
+ -v /home/docker/photoprism/storage:/photoprism/storage \
+ -v /home/docker/photoprism/Pictures:/photoprism/originals \
+ photoprism/photoprism
+
+ }
+
+
+ local docker_describe="photoprism非常强大的私有相册系统"
+ local docker_url="官网介绍: https://www.photoprism.app/"
+ local docker_use="echo \"账号: admin 密码: $rootpasswd\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 31|s-pdf)
+ local app_id="31"
+ local docker_name="s-pdf"
+ local docker_img="frooodle/s-pdf:latest"
+ local docker_port=8031
+
+ docker_rum() {
+
+ docker run -d \
+ --name s-pdf \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
+ -v /home/docker/s-pdf/extraConfigs:/configs \
+ -v /home/docker/s-pdf/logs:/logs \
+ -e DOCKER_ENABLE_SECURITY=false \
+ frooodle/s-pdf:latest
+ }
+
+ local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
+ local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 32|drawio)
+ local app_id="32"
+ local docker_name="drawio"
+ local docker_img="jgraph/drawio"
+ local docker_port=8032
+
+ docker_rum() {
+
+ docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+
+ }
+
+
+ local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
+ local docker_url="官网介绍: https://www.drawio.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 33|sun-panel)
+ local app_id="33"
+ local docker_name="sun-panel"
+ local docker_img="hslr/sun-panel"
+ local docker_port=8033
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:3002 \
+ -v /home/docker/sun-panel/conf:/app/conf \
+ -v /home/docker/sun-panel/uploads:/app/uploads \
+ -v /home/docker/sun-panel/database:/app/database \
+ --name sun-panel \
+ hslr/sun-panel
+
+ }
+
+ local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
+ local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
+ local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 34|pingvin-share)
+ local app_id="34"
+ local docker_name="pingvin-share"
+ local docker_img="stonith404/pingvin-share"
+ local docker_port=8034
+
+ docker_rum() {
+
+ docker run -d \
+ --name pingvin-share \
+ --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/pingvin-share/data:/opt/app/backend/data \
+ stonith404/pingvin-share
+ }
+
+ local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
+ local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 35|moments)
+ local app_id="35"
+ local docker_name="moments"
+ local docker_img="kingwrcy/moments:latest"
+ local docker_port=8035
+
+ docker_rum() {
+
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/moments/data:/app/data \
+ -v /etc/localtime:/etc/localtime:ro \
+ -v /etc/timezone:/etc/timezone:ro \
+ --name moments \
+ kingwrcy/moments:latest
+ }
+
+
+ local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
+ local docker_url="官网介绍: ${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
+ local docker_use="echo \"账号: admin 密码: a123456\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+
+ 36|lobe-chat)
+ local app_id="36"
+ local docker_name="lobe-chat"
+ local docker_img="lobehub/lobe-chat:latest"
+ local docker_port=8036
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:3210 \
+ --name lobe-chat \
+ --restart=always \
+ lobehub/lobe-chat
+ }
+
+ local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
+ local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 37|myip)
+ local app_id="37"
+ local docker_name="myip"
+ local docker_img="jason5ng32/myip:latest"
+ local docker_port=8037
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+
+ }
+
+
+ local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
+ local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 38|xiaoya)
+ send_stats "小雅全家桶"
+ clear
+ install_docker
+ check_disk_space 1
+ bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
+ ;;
+
+ 39|bililive)
+
+ if [ ! -d /home/docker/bililive-go/ ]; then
+ mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
+ wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
+ fi
+
+ local app_id="39"
+ local docker_name="bililive-go"
+ local docker_img="chigusa/bililive-go"
+ local docker_port=8039
+
+ docker_rum() {
+
+ docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+
+ }
+
+ local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 40|webssh)
+ local app_id="40"
+ local docker_name="webssh"
+ local docker_img="jrohy/webssh"
+ local docker_port=8040
+ docker_rum() {
+ docker run -d -p ${docker_port}:5032 --restart=always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
+ }
+
+ local docker_describe="简易在线ssh连接工具和sftp工具"
+ local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 41|haozi|acepanel)
+
+ local app_id="41"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AcePanel 原耗子面板"
+ local panelurl="官方地址: ${gh_proxy}github.com/acepanel/panel"
+
+ panel_app_install() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+ }
+
+ panel_app_manage() {
+ acepanel help
+ }
+
+ panel_app_uninstall() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+
+ }
+
+ install_panel
+
+ ;;
+
+
+ 42|nexterm)
+ local app_id="42"
+ local docker_name="nexterm"
+ local docker_img="germannewsmaker/nexterm:latest"
+ local docker_port=8042
+
+ docker_rum() {
+
+ ENCRYPTION_KEY=$(openssl rand -hex 32)
+ docker run -d \
+ --name nexterm \
+ -e ENCRYPTION_KEY=${ENCRYPTION_KEY} \
+ -p ${docker_port}:6989 \
+ -v /home/docker/nexterm:/app/data \
+ --restart=always \
+ germannewsmaker/nexterm:latest
+
+ }
+
+ local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
+ local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 43|hbbs)
+ local app_id="43"
+ local docker_name="hbbs"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbs
+
+ }
+
+
+ local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
+ local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
+ local docker_use="docker logs hbbs"
+ local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 44|hbbr)
+ local app_id="44"
+ local docker_name="hbbr"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbr
+
+ }
+
+ local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
+ local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
+ local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 45|registry)
+ local app_id="45"
+ local docker_name="registry"
+ local docker_img="registry:2"
+ local docker_port=8045
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name registry \
+ -v /home/docker/registry:/var/lib/registry \
+ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
+ --restart=always \
+ registry:2
+
+ }
+
+ local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
+ local docker_url="官网介绍: https://hub.docker.com/_/registry"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 46|ghproxy)
+ local app_id="46"
+ local docker_name="ghproxy"
+ local docker_img="wjqserver/ghproxy:latest"
+ local docker_port=8046
+
+ docker_rum() {
+
+ docker run -d --name ghproxy --restart=always -p ${docker_port}:8080 -v /home/docker/ghproxy/config:/data/ghproxy/config wjqserver/ghproxy:latest
+
+ }
+
+ local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/WJQSERVER-STUDIO/ghproxy"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 47|prometheus|grafana)
+
+ local app_id="47"
+ local app_name="普罗米修斯监控"
+ local app_text="Prometheus+Grafana企业级监控系统"
+ local app_url="官网介绍: https://prometheus.io"
+ local docker_name="grafana"
+ local docker_port="8047"
+ local app_size="2"
+
+ docker_app_install() {
+ prometheus_install
+ clear
+ ip_address
+ echo "已经安装完成"
+ check_docker_app_ip
+ echo "初始用户名密码均为: admin"
+ }
+
+ docker_app_update() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+
+ rm -rf /home/docker/monitoring
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+ 48|node-exporter)
+ local app_id="48"
+ local docker_name="node-exporter"
+ local docker_img="prom/node-exporter"
+ local docker_port=8048
+
+ docker_rum() {
+
+ docker run -d \
+ --name=node-exporter \
+ -p ${docker_port}:9100 \
+ --restart=always \
+ prom/node-exporter
+
+
+ }
+
+ local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/prometheus/node_exporter"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 49|cadvisor)
+ local app_id="49"
+ local docker_name="cadvisor"
+ local docker_img="gcr.io/cadvisor/cadvisor:latest"
+ local docker_port=8049
+
+ docker_rum() {
+
+ docker run -d \
+ --name=cadvisor \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ --volume=/:/rootfs:ro \
+ --volume=/var/run:/var/run:rw \
+ --volume=/sys:/sys:ro \
+ --volume=/var/lib/docker/:/var/lib/docker:ro \
+ gcr.io/cadvisor/cadvisor:latest \
+ -housekeeping_interval=10s \
+ -docker_only=true
+
+ }
+
+ local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/google/cadvisor"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 50|changedetection)
+ local app_id="50"
+ local docker_name="changedetection"
+ local docker_img="dgtlmoon/changedetection.io:latest"
+ local docker_port=8050
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:5000 \
+ -v /home/docker/datastore:/datastore \
+ --name changedetection dgtlmoon/changedetection.io:latest
+
+ }
+
+ local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/dgtlmoon/changedetection.io"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 51|pve)
+ clear
+ send_stats "PVE开小鸡"
+ check_disk_space 1
+ curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
+ ;;
+
+
+ 52|dpanel)
+ local app_id="52"
+ local docker_name="dpanel"
+ local docker_img="dpanel/dpanel:lite"
+ local docker_port=8052
+
+ docker_rum() {
+
+ docker run -d --name dpanel --restart=always \
+ -p ${docker_port}:8080 -e APP_NAME=dpanel \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/dpanel:/dpanel \
+ dpanel/dpanel:lite
+
+ }
+
+ local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/donknap/dpanel"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 53|llama3)
+ local app_id="53"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
+ local docker_url="官网介绍: ${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run llama3.2:1b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+ 54|amh)
+
+ local app_id="54"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AMH面板"
+ local panelurl="官方地址: https://amh.sh/index.htm?amh"
+
+ panel_app_install() {
+ cd ~
+ wget https://dl.amh.sh/amh.sh && bash amh.sh
+ }
+
+ panel_app_manage() {
+ panel_app_install
+ }
+
+ panel_app_uninstall() {
+ panel_app_install
+ }
+
+ install_panel
+ ;;
+
+
+ 55|frps)
+ frps_panel
+ ;;
+
+ 56|frpc)
+ frpc_panel
+ ;;
+
+ 57|deepseek)
+ local app_id="57"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
+ local docker_url="官网介绍: ${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+ 58|dify)
+ local app_id="58"
+ local app_name="Dify知识库"
+ local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
+ local app_url="官方网站: https://docs.dify.ai/zh-hans"
+ local docker_name="docker-nginx-1"
+ local docker_port="8058"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
+ sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+
+ docker compose up -d
+
+ chown -R 1001:1001 /home/docker/dify/docker/volumes/app/storage
+ chmod -R 755 /home/docker/dify/docker/volumes/app/storage
+ docker compose down
+ docker compose up -d
+
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ cd /home/docker/dify/
+ git pull ${gh_proxy}github.com/langgenius/dify.git main > /dev/null 2>&1
+ sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
+ cd /home/docker/dify/docker/ && docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/dify
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+
+ ;;
+
+ 59|new-api)
+ local app_id="59"
+ local app_name="NewAPI"
+ local app_text="新一代大模型网关与AI资产管理系统"
+ local app_url="官方网站: ${gh_https_url}github.com/Calcium-Ion/new-api"
+ local docker_name="new-api"
+ local docker_port="8059"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ cd /home/docker/new-api/
+
+ git pull ${gh_proxy}github.com/Calcium-Ion/new-api.git main > /dev/null 2>&1
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ rm -rf /home/docker/new-api
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 60|jms)
+
+ local app_id="60"
+ local app_name="JumpServer开源堡垒机"
+ local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
+ local app_url="官方介绍: ${gh_https_url}github.com/jumpserver/jumpserver"
+ local docker_name="jms_web"
+ local docker_port="80"
+ local app_size="2"
+
+ docker_app_install() {
+ curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ echo "初始用户名: admin"
+ echo "初始密码: ChangeMe"
+ }
+
+
+ docker_app_update() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh upgrade
+ echo "应用已更新"
+ }
+
+
+ docker_app_uninstall() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh uninstall
+ cd /opt
+ rm -rf jumpserver-installer*/
+ rm -rf jumpserver
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
+
+ 61|libretranslate)
+ local app_id="61"
+ local docker_name="libretranslate"
+ local docker_img="libretranslate/libretranslate:latest"
+ local docker_port=8061
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name libretranslate \
+ libretranslate/libretranslate \
+ --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+
+ }
+
+ local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/LibreTranslate/LibreTranslate"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+
+ 62|ragflow)
+ local app_id="62"
+ local app_name="RAGFlow知识库"
+ local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
+ local app_url="官方网站: ${gh_https_url}github.com/infiniflow/ragflow"
+ local docker_name="ragflow-server"
+ local docker_port="8062"
+ local app_size="8"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ cd /home/docker/ragflow/
+ git pull ${gh_proxy}github.com/infiniflow/ragflow.git main > /dev/null 2>&1
+ cd /home/docker/ragflow/docker/
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/ragflow
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 63|open-webui)
+ local app_id="63"
+ local docker_name="open-webui"
+ local docker_img="ghcr.io/open-webui/open-webui:main"
+ local docker_port=8063
+ docker_rum() {
- local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
- local docker_url="官网介绍: https://github.com/OpenListTeam/OpenList"
- local docker_use="docker exec -it openlist ./openlist admin random"
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart=always ghcr.io/open-webui/open-webui:main
- ;;
+ }
- 6)
+ local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
+ local docker_url="官网介绍: ${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
- local docker_name="webtop-ubuntu"
- local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
- local docker_port=3006
+ 64|it-tools)
+ local app_id="64"
+ local docker_name="it-tools"
+ local docker_img="corentinth/it-tools:latest"
+ local docker_port=8064
- docker_rum() {
+ docker_rum() {
+ docker run -d --name it-tools --restart=always -p ${docker_port}:80 corentinth/it-tools:latest
+ }
+ local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/CorentinTh/it-tools"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker run -d \
- --name=webtop-ubuntu \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=ubuntu-abc \
- -e PASSWORD=ubuntuABC123 \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:ubuntu-kde
+ 65|n8n)
+ local app_id="65"
+ local docker_name="n8n"
+ local docker_img="docker.n8n.io/n8nio/n8n"
+ local docker_port=8065
- }
+ docker_rum() {
+ add_yuming
+ mkdir -p /home/docker/n8n
+ chmod -R 777 /home/docker/n8n
+
+ docker run -d --name n8n \
+ --restart=always \
+ -p ${docker_port}:5678 \
+ -v /home/docker/n8n:/home/node/.n8n \
+ -e N8N_HOST=${yuming} \
+ -e N8N_PORT=5678 \
+ -e N8N_PROTOCOL=https \
+ -e WEBHOOK_URL=https://${yuming}/ \
+ docker.n8n.io/n8nio/n8n
- local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: ubuntu-abc\""
- local docker_passwd="echo \"密码: ubuntuABC123\""
- local app_size="2"
- docker_app
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
+ }
- ;;
- 7)
- clear
- send_stats "搭建哪吒"
- local docker_name="nezha-dashboard"
- local docker_port=8008
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
- clear
- echo -e "哪吒监控 $check_docker $update_status"
- echo "开源、轻量、易用的服务器监控与运维工具"
- echo "官网搭建文档: https://nezha.wiki/guide/dashboard.html"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. 使用"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "输入你的选择: " choice
+ local docker_describe="是一款功能强大的自动化工作流平台"
+ local docker_url="官网介绍: ${gh_https_url}github.com/n8n-io/n8n"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- case $choice in
- 1)
- check_disk_space 1
- install unzip jq
- install_docker
- curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- ;;
+ 66|yt)
+ yt_menu_pro
+ ;;
- *)
- break
- ;;
- esac
- break_end
- done
- ;;
+ 67|ddns)
+ local app_id="67"
+ local docker_name="ddns-go"
+ local docker_img="jeessy/ddns-go"
+ local docker_port=8067
- 8)
+ docker_rum() {
+ docker run -d \
+ --name ddns-go \
+ --restart=always \
+ -p ${docker_port}:9876 \
+ -v /home/docker/ddns-go:/root \
+ jeessy/ddns-go
- local docker_name="qbittorrent"
- local docker_img="lscr.io/linuxserver/qbittorrent:latest"
- local docker_port=8081
-
- docker_rum() {
-
- docker run -d \
- --name=qbittorrent \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e WEBUI_PORT=${docker_port} \
- -e TORRENTING_PORT=56881 \
- -p ${docker_port}:${docker_port} \
- -p 56881:56881 \
- -p 56881:56881/udp \
- -v /home/docker/qbittorrent/config:/config \
- -v /home/docker/qbittorrent/downloads:/downloads \
- --restart unless-stopped \
- lscr.io/linuxserver/qbittorrent:latest
+ }
- }
+ local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/jeessy2/ddns-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="qbittorrent离线BT磁力下载服务"
- local docker_url="官网介绍: https://hub.docker.com/r/linuxserver/qbittorrent"
- local docker_use="sleep 3"
- local docker_passwd="docker logs qbittorrent"
- local app_size="1"
- docker_app
+ 68|allinssl)
+ local app_id="68"
+ local docker_name="allinssl"
+ local docker_img="allinssl/allinssl:latest"
+ local docker_port=8068
- ;;
+ docker_rum() {
+ docker run -d --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
+ }
- 9)
- send_stats "搭建邮局"
- clear
- install telnet
- local docker_name=“mailserver”
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
+ local docker_describe="开源免费的 SSL 证书自动化管理平台"
+ local docker_url="官网介绍: https://allinssl.com"
+ local docker_use="echo \"安全入口: /allinssl\""
+ local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
+ local app_size="1"
+ docker_app
+ ;;
- clear
- echo -e "邮局服务 $check_docker $update_status"
- echo "poste.io 是一个开源的邮件服务器解决方案,"
- echo "视频介绍: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
- echo ""
- echo "端口检测"
- port=25
- timeout=3
- if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
- echo -e "${gl_lv}端口 $port 当前可用${gl_bai}"
- else
- echo -e "${gl_hong}端口 $port 当前不可用${gl_bai}"
- fi
- echo ""
+ 69|sftpgo)
+ local app_id="69"
+ local docker_name="sftpgo"
+ local docker_img="drakkan/sftpgo:latest"
+ local docker_port=8069
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
- yuming=$(cat /home/docker/mail.txt)
- echo "访问地址: "
- echo "https://$yuming"
- fi
+ docker_rum() {
- echo "------------------------"
- echo "1. 安装 2. 更新 3. 卸载"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "输入你的选择: " choice
+ mkdir -p /home/docker/sftpgo/data
+ mkdir -p /home/docker/sftpgo/config
+ chown -R 1000:1000 /home/docker/sftpgo
- case $choice in
- 1)
- check_disk_space 2
- read -e -p "请设置邮箱域名 例如 mail.yuming.com : " yuming
- mkdir -p /home/docker
- echo "$yuming" > /home/docker/mail.txt
- echo "------------------------"
- ip_address
- echo "先解析这些DNS记录"
- echo "A mail $ipv4_address"
- echo "CNAME imap $yuming"
- echo "CNAME pop $yuming"
- echo "CNAME smtp $yuming"
- echo "MX @ $yuming"
- echo "TXT @ v=spf1 mx ~all"
- echo "TXT ? ?"
- echo ""
- echo "------------------------"
- echo "按任意键继续..."
- read -n 1 -s -r -p ""
+ docker run -d \
+ --name sftpgo \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -p 22022:2022 \
+ --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
+ --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
+ drakkan/sftpgo:latest
- install jq
- install_docker
+ }
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.io
+ local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
+ local docker_url="官网介绍: https://sftpgo.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
- clear
- echo "poste.io已经安装完成"
- echo "------------------------"
- echo "您可以使用以下地址访问poste.io:"
- echo "https://$yuming"
- echo ""
+ 70|astrbot)
+ local app_id="70"
+ local docker_name="astrbot"
+ local docker_img="soulter/astrbot:latest"
+ local docker_port=8070
- ;;
+ docker_rum() {
- 2)
- docker rm -f mailserver
- docker rmi -f analogic/poste.i
- yuming=$(cat /home/docker/mail.txt)
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.i
-
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
+ mkdir -p /home/docker/astrbot/data
- clear
- echo "poste.io已经安装完成"
- echo "------------------------"
- echo "您可以使用以下地址访问poste.io:"
- echo "https://$yuming"
- echo ""
- ;;
- 3)
- docker rm -f mailserver
- docker rmi -f analogic/poste.io
- rm /home/docker/mail.txt
- rm -rf /home/docker/mail
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
- echo "应用已卸载"
- ;;
+ docker run -d \
+ -p ${docker_port}:6185 \
+ -p 6195:6195 \
+ -p 6196:6196 \
+ -p 6199:6199 \
+ -p 11451:11451 \
+ -v /home/docker/astrbot/data:/AstrBot/data \
+ --restart=always \
+ --name astrbot \
+ soulter/astrbot:latest
- *)
- break
- ;;
+ }
- esac
- break_end
- done
+ local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
+ local docker_url="官网介绍: https://astrbot.app/"
+ local docker_use="echo \"用户名: astrbot 密码: astrbot\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- ;;
- 10)
+ 71|navidrome)
+ local app_id="71"
+ local docker_name="navidrome"
+ local docker_img="deluan/navidrome:latest"
+ local docker_port=8071
- local app_name="Rocket.Chat聊天系统"
- local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
- local app_url="官方介绍: https://www.rocket.chat/"
- local docker_name="rocketchat"
- local docker_port="3897"
- local app_size="2"
-
- docker_app_install() {
- docker run --name db -d --restart=always \
- -v /home/docker/mongo/dump:/dump \
- mongo:latest --replSet rs5 --oplogSize 256
- sleep 1
- docker exec -it db mongosh --eval "printjson(rs.initiate())"
- sleep 5
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ docker_rum() {
- clear
- ip_address
- echo "已经安装完成"
- check_docker_app_ip
- }
+ docker run -d \
+ --name navidrome \
+ --restart=always \
+ --user $(id -u):$(id -g) \
+ -v /home/docker/navidrome/music:/music \
+ -v /home/docker/navidrome/data:/data \
+ -p ${docker_port}:4533 \
+ -e ND_LOGLEVEL=info \
+ deluan/navidrome:latest
- docker_app_update() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat:latest
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "rocket.chat已经安装完成"
- check_docker_app_ip
- }
+ }
- docker_app_uninstall() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat
- docker rm -f db
- docker rmi -f mongo:latest
- rm -rf /home/docker/mongo
- echo "应用已卸载"
- }
+ local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
+ local docker_url="官网介绍: https://www.navidrome.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_app_plus
- ;;
+ 72|bitwarden)
+ local app_id="72"
+ local docker_name="bitwarden"
+ local docker_img="vaultwarden/server"
+ local docker_port=8072
- 11)
- local docker_name="zentao-server"
- local docker_img="idoop/zentao:latest"
- local docker_port=82
+ docker_rum() {
+ docker run -d \
+ --name bitwarden \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/bitwarden/data:/data \
+ vaultwarden/server
- docker_rum() {
+ }
+ local docker_describe="一个你可以控制数据的密码管理器"
+ local docker_url="官网介绍: https://bitwarden.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:80 \
- -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
- -e BIND_ADDRESS="false" \
- -v /home/docker/zentao-server/:/opt/zbox/ \
- --add-host smtp.exmail.qq.com:163.177.90.125 \
- --name zentao-server \
- --restart=always \
- idoop/zentao:latest
+ ;;
- }
- local docker_describe="禅道是通用的项目管理软件"
- local docker_url="官网介绍: https://www.zentao.net/"
- local docker_use="echo \"初始用户名: admin\""
- local docker_passwd="echo \"初始密码: 123456\""
- local app_size="2"
- docker_app
- ;;
+ 73|libretv)
- 12)
- local docker_name="qinglong"
- local docker_img="whyour/qinglong:latest"
- local docker_port=5700
+ local app_id="73"
+ local docker_name="libretv"
+ local docker_img="bestzwei/libretv:latest"
+ local docker_port=8073
- docker_rum() {
+ docker_rum() {
+ read -e -p "设置LibreTV的登录密码: " app_passwd
- docker run -d \
- -v /home/docker/qinglong/data:/ql/data \
- -p ${docker_port}:5700 \
- --name qinglong \
- --hostname qinglong \
- --restart unless-stopped \
- whyour/qinglong:latest
+ docker run -d \
+ --name libretv \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -e PASSWORD=${app_passwd} \
+ bestzwei/libretv:latest
+ }
- }
+ local docker_describe="免费在线视频搜索与观看平台"
+ local docker_url="官网介绍: ${gh_https_url}github.com/LibreSpark/LibreTV"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="青龙面板是一个定时任务管理平台"
- local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ ;;
- ;;
- 13)
- local app_name="cloudreve网盘"
- local app_text="cloudreve是一个支持多家云存储的网盘系统"
- local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
- local docker_name="cloudreve"
- local docker_port="5212"
- local app_size="2"
-
- docker_app_install() {
- cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
- curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
- sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
- cd /home/docker/cloud/
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
+ 74|moontv)
- docker_app_update() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- cd /home/docker/cloud/ && docker compose up -d
- }
+ local app_id="74"
+ local app_name="moontv私有影视"
+ local app_text="免费在线视频搜索与观看平台"
+ local app_url="视频介绍: ${gh_https_url}github.com/MoonTechLab/LunaTV"
+ local docker_name="moontv-core"
+ local docker_port="8074"
+ local app_size="2"
- docker_app_uninstall() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- rm -rf /home/docker/cloud
- echo "应用已卸载"
- }
+ docker_app_install() {
+ read -e -p "设置登录用户名: " admin
+ read -e -p "设置登录用户密码: " admin_password
+ read -e -p "输入授权码: " shouquanma
- docker_app_plus
- ;;
- 14)
- local docker_name="easyimage"
- local docker_img="ddsderek/easyimage:latest"
- local docker_port=85
- docker_rum() {
-
- docker run -d \
- --name easyimage \
- -p ${docker_port}:80 \
- -e TZ=Asia/Shanghai \
- -e PUID=1000 \
- -e PGID=1000 \
- -v /home/docker/easyimage/config:/app/web/config \
- -v /home/docker/easyimage/i:/app/web/i \
- --restart unless-stopped \
- ddsderek/easyimage:latest
+ mkdir -p /home/docker/moontv
+ mkdir -p /home/docker/moontv/config
+ mkdir -p /home/docker/moontv/data
+ cd /home/docker/moontv
- }
+ curl -o /home/docker/moontv/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/moontv-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin_password|${admin_password}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin|${admin}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|shouquanma|${shouquanma}|g" /home/docker/moontv/docker-compose.yml
+ cd /home/docker/moontv/
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- local docker_describe="简单图床是一个简单的图床程序"
- local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 15)
- local docker_name="emby"
- local docker_img="linuxserver/emby:latest"
- local docker_port=8096
+ docker_app_update() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ cd /home/docker/moontv/ && docker compose up -d
+ }
- docker_rum() {
- docker run -d --name=emby --restart=always \
- -v /home/docker/emby/config:/config \
- -v /home/docker/emby/share1:/mnt/share1 \
- -v /home/docker/emby/share2:/mnt/share2 \
- -v /mnt/notify:/mnt/notify \
- -p ${docker_port}:8096 \
- -e UID=1000 -e GID=100 -e GIDLIST=100 \
- linuxserver/emby:latest
+ docker_app_uninstall() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ rm -rf /home/docker/moontv
+ echo "应用已卸载"
+ }
- }
+ docker_app_plus
+ ;;
- local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
- local docker_url="官网介绍: https://emby.media/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 16)
- local docker_name="looking-glass"
- local docker_img="wikihostinc/looking-glass-server"
- local docker_port=89
+ 75|melody)
+ local app_id="75"
+ local docker_name="melody"
+ local docker_img="foamzou/melody:latest"
+ local docker_port=8075
- docker_rum() {
+ docker_rum() {
- docker run -d --name looking-glass --restart always -p ${docker_port}:80 wikihostinc/looking-glass-server
+ docker run -d \
+ --name melody \
+ --restart=always \
+ -p ${docker_port}:5566 \
+ -v /home/docker/melody/.profile:/app/backend/.profile \
+ foamzou/melody:latest
- }
- local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
- local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ }
- ;;
- 17)
+ local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/foamzou/melody"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="adguardhome"
- local docker_img="adguard/adguardhome"
- local docker_port=3000
- docker_rum() {
+ ;;
- docker run -d \
- --name adguardhome \
- -v /home/docker/adguardhome/work:/opt/adguardhome/work \
- -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
- -p 53:53/tcp \
- -p 53:53/udp \
- -p ${docker_port}:3000/tcp \
- --restart always \
- adguard/adguardhome
+ 76|dosgame)
- }
+ local app_id="76"
+ local docker_name="dosgame"
+ local docker_img="oldiy/dosgame-web-docker:latest"
+ local docker_port=8076
+ docker_rum() {
+ docker run -d \
+ --name dosgame \
+ --restart=always \
+ -p ${docker_port}:262 \
+ oldiy/dosgame-web-docker:latest
- local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
- local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ }
- ;;
+ local docker_describe="是一个中文DOS游戏合集网站"
+ local docker_url="官网介绍: ${gh_https_url}github.com/rwv/chinese-dos-games"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- 18)
+ ;;
- local docker_name="onlyoffice"
- local docker_img="onlyoffice/documentserver"
- local docker_port=8082
+ 77|xunlei)
- docker_rum() {
+ local app_id="77"
+ local docker_name="xunlei"
+ local docker_img="cnk3x/xunlei"
+ local docker_port=8077
- docker run -d -p ${docker_port}:80 \
- --restart=always \
- --name onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
- onlyoffice/documentserver
+ docker_rum() {
+ read -e -p "设置登录用户名: " app_use
+ read -e -p "设置登录密码: " app_passwd
- }
+ docker run -d \
+ --name xunlei \
+ --restart=always \
+ --privileged \
+ -e XL_DASHBOARD_USERNAME=${app_use} \
+ -e XL_DASHBOARD_PASSWORD=${app_passwd} \
+ -v /home/docker/xunlei/data:/xunlei/data \
+ -v /home/docker/xunlei/downloads:/xunlei/downloads \
+ -p ${docker_port}:2345 \
+ cnk3x/xunlei
- local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
- local docker_url="官网介绍: https://www.onlyoffice.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ }
- ;;
+ local docker_describe="迅雷你的离线高速BT磁力下载工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/cnk3x/xunlei"
+ local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 19)
- send_stats "搭建雷池"
+ ;;
- local docker_name=safeline-mgt
- local docker_port=9443
- while true; do
- check_docker_app
- clear
- echo -e "雷池服务 $check_docker"
- echo "雷池是长亭科技开发的WAF站点防火墙程序面板,可以反代站点进行自动化防御"
- echo "视频介绍: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
- if docker ps -a --format '{{.Names}}' | grep -q "$docker_name"; then
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. 安装 2. 更新 3. 重置密码 4. 卸载"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "输入你的选择: " choice
- case $choice in
- 1)
- install_docker
- check_disk_space 5
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
- clear
- echo "雷池WAF面板已经安装完成"
- check_docker_app_ip
- docker exec safeline-mgt resetadmin
+ 78|PandaWiki)
- ;;
+ local app_id="78"
+ local app_name="PandaWiki"
+ local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
+ local app_url="官方介绍: ${gh_https_url}github.com/chaitin/PandaWiki"
+ local docker_name="panda-wiki-nginx"
+ local docker_port="2443"
+ local app_size="2"
- 2)
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
- docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
- echo ""
- local app_no=$sub_choice
- grep -qxF "${app_no}" /home/docker/appno.txt || echo "${app_no}" >> /home/docker/appno.txt
- clear
- echo "雷池WAF面板已经更新完成"
- check_docker_app_ip
- ;;
- 3)
- docker exec safeline-mgt resetadmin
- ;;
- 4)
- cd /data/safeline
- docker compose down --rmi all
- local app_no=$sub_choice
- sed -i "/\b${app_no}\b/d" /home/docker/appno.txt
- echo "如果你是默认安装目录那现在项目已经卸载。如果你是自定义安装目录你需要到安装目录下自行执行:"
- echo "docker compose down && docker compose down --rmi all"
- ;;
- *)
- break
- ;;
+ docker_app_install() {
+ bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
+ }
- esac
- break_end
- done
+ docker_app_update() {
+ docker_app_install
+ }
- ;;
- 20)
- local docker_name="portainer"
- local docker_img="portainer/portainer"
- local docker_port=9050
+ docker_app_uninstall() {
+ docker_app_install
+ }
- docker_rum() {
+ docker_app_plus
+ ;;
- docker run -d \
- --name portainer \
- -p ${docker_port}:9000 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/portainer:/data \
- --restart always \
- portainer/portainer
- }
+ 79|beszel)
- local docker_describe="portainer是一个轻量级的docker容器管理面板"
- local docker_url="官网介绍: https://www.portainer.io/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="79"
+ local docker_name="beszel"
+ local docker_img="henrygd/beszel"
+ local docker_port=8079
- ;;
+ docker_rum() {
- 21)
- local docker_name="vscode-web"
- local docker_img="codercom/code-server"
- local docker_port=8180
+ mkdir -p /home/docker/beszel && \
+ docker run -d \
+ --name beszel \
+ --restart=always \
+ -v /home/docker/beszel:/beszel_data \
+ -p ${docker_port}:8090 \
+ henrygd/beszel
+ }
- docker_rum() {
+ local docker_describe="Beszel轻量易用的服务器监控"
+ local docker_url="官网介绍: https://beszel.dev/zh/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart always codercom/code-server
+ ;;
- }
+ 80|linkwarden)
- local docker_describe="VScode是一款强大的在线代码编写工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
- local docker_use="sleep 3"
- local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
- local app_size="1"
- docker_app
- ;;
- 22)
- local docker_name="uptime-kuma"
- local docker_img="louislam/uptime-kuma:latest"
- local docker_port=3003
+ local app_id="80"
+ local app_name="linkwarden书签管理"
+ local app_text="一个开源的自托管书签管理平台,支持标签、搜索和团队协作。"
+ local app_url="官方网站: https://linkwarden.app/"
+ local docker_name="linkwarden-linkwarden-1"
+ local docker_port="8080"
+ local app_size="3"
+ docker_app_install() {
+ install git openssl
+ mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
- docker_rum() {
+ # 下载官方 docker-compose 和 env 文件
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
- docker run -d \
- --name=uptime-kuma \
- -p ${docker_port}:3001 \
- -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
- --restart=always \
- louislam/uptime-kuma:latest
+ # 生成随机密钥与密码
+ local ADMIN_EMAIL="admin@example.com"
+ local ADMIN_PASSWORD=$(openssl rand -hex 8)
- }
+ sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
+ sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
+ sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
+ sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
+ # 追加管理员账号信息
+ echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
+ echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
- local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
- 23)
- local docker_name="memos"
- local docker_img="ghcr.io/usememos/memos:latest"
- local docker_port=5230
+ # 启动容器
+ docker compose up -d
- docker_rum() {
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
+
+ # 保留原本的变量
+ source .env
+ mv .env.new .env
+ echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
+ echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
+ echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
+ echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
+ echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
+ echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ rm -rf /home/docker/linkwarden
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
- docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart always ghcr.io/usememos/memos:latest
+ ;;
- }
- local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
- local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 24)
- local docker_name="webtop"
- local docker_img="lscr.io/linuxserver/webtop:latest"
- local docker_port=3083
-
- docker_rum() {
-
-
- docker run -d \
- --name=webtop \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=webtop-abc \
- -e PASSWORD=webtopABC123 \
- -e LC_ALL=zh_CN.UTF-8 \
- -e DOCKER_MODS=linuxserver/mods:universal-package-install \
- -e INSTALL_PACKAGES=font-noto-cjk \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:latest
+ 81|jitsi)
+ local app_id="81"
+ local app_name="JitsiMeet视频会议"
+ local app_text="一个开源的安全视频会议解决方案,支持多人在线会议、屏幕共享与加密通信。"
+ local app_url="官方网站: https://jitsi.org/"
+ local docker_name="jitsi"
+ local docker_app_service="jitsi-web-1"
+ local docker_port="8081"
+ local app_size="3"
+ docker_app_install() {
- }
+ add_yuming
+ mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
+ rm -rf docker-jitsi-meet-* jitsi.zip
+ local jitsi_zip_url=$(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep -m1 '"zipball_url"' | cut -d\" -f4)
+ if [ -z "$jitsi_zip_url" ]; then
+ echo "Failed to get Jitsi Meet latest download URL"
+ return 1
+ fi
+ wget -O jitsi.zip "$jitsi_zip_url"
+ unzip -q jitsi.zip
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ cp env.example .env
+ ./gen-passwords.sh
+ mkdir -p /home/docker/jitsi/config/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
+ sed -i "s|^CONFIG=.*|CONFIG=/home/docker/jitsi/config|" .env
+ grep -q '^COMPOSE_PROJECT_NAME=' .env && sed -i "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=jitsi|" .env || echo "COMPOSE_PROJECT_NAME=jitsi" >> .env
+ sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
+ sed -i "s|^#\?PUBLIC_URL=.*|PUBLIC_URL=https://$yuming|" .env
+ grep -q '^PUBLIC_URL=' .env || echo "PUBLIC_URL=https://$yuming" >> .env
+ grep -q '^DISABLE_HTTPS=' .env && sed -i "s|^DISABLE_HTTPS=.*|DISABLE_HTTPS=1|" .env || echo "DISABLE_HTTPS=1" >> .env
+ grep -q '^ENABLE_HTTP_REDIRECT=' .env && sed -i "s|^ENABLE_HTTP_REDIRECT=.*|ENABLE_HTTP_REDIRECT=0|" .env || echo "ENABLE_HTTP_REDIRECT=0" >> .env
+ sed -i "/'\${HTTPS_PORT}:443'/d" docker-compose.yml
+ docker compose up -d
+ local jitsi_web_container=$(docker compose ps -q web)
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ [ -n "$jitsi_web_container" ] && block_container_port "$jitsi_web_container" "$ipv4_address"
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose pull
+ docker compose up -d
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose down --rmi all
+ rm -rf /home/docker/jitsi
+ echo "应用已卸载"
+ }
+
+ docker_app_plus
+ ;;
- local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: webtop-abc\""
- local docker_passwd="echo \"密码: webtopABC123\""
- local app_size="2"
- docker_app
- ;;
- 25)
- local docker_name="nextcloud"
- local docker_img="nextcloud:latest"
- local docker_port=8989
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
- docker_rum() {
+ 82|gpt-load)
- docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+ local app_id="82"
+ local docker_name="gpt-load"
+ local docker_img="tbphp/gpt-load:latest"
+ local docker_port=8082
- }
+ docker_rum() {
- local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
- local docker_url="官网介绍: https://nextcloud.com/"
- local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ read -e -p "设置${docker_name}的登录密钥(sk-开头字母和数字组合)如: sk-159kejilionyyds163: " app_passwd
- 26)
- local docker_name="qd"
- local docker_img="qdtoday/qd:latest"
- local docker_port=8923
+ mkdir -p /home/docker/gpt-load && \
+ docker run -d --name gpt-load \
+ -p ${docker_port}:3001 \
+ -e AUTH_KEY=${app_passwd} \
+ -v "/home/docker/gpt-load/data":/app/data \
+ tbphp/gpt-load:latest
- docker_rum() {
+ }
- docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+ local docker_describe="高性能AI接口透明代理服务"
+ local docker_url="官网介绍: https://www.gpt-load.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
- local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 27)
- local docker_name="dockge"
- local docker_img="louislam/dockge:latest"
- local docker_port=5003
- docker_rum() {
- docker run -d --name dockge --restart unless-stopped -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+ 83|komari)
- }
+ local app_id="83"
+ local docker_name="komari"
+ local docker_img="ghcr.io/komari-monitor/komari:latest"
+ local docker_port=8083
- local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 28)
- local docker_name="speedtest"
- local docker_img="ghcr.io/librespeed/speedtest"
- local docker_port=8028
+ mkdir -p /home/docker/komari && \
+ docker run -d \
+ --name komari \
+ -p ${docker_port}:25774 \
+ -v /home/docker/komari:/app/data \
+ -e ADMIN_USERNAME=admin \
+ -e ADMIN_PASSWORD=1212156 \
+ -e TZ=Asia/Shanghai \
+ --restart=always \
+ ghcr.io/komari-monitor/komari:latest
- docker_rum() {
+ }
- docker run -d -p ${docker_port}:8080 --name speedtest --restart always ghcr.io/librespeed/speedtest
+ local docker_describe="轻量级的自托管服务器监控工具"
+ local docker_url="官网介绍: ${gh_https_url}github.com/komari-monitor/komari/tree/main"
+ local docker_use="echo \"默认账号: admin 默认密码: 1212156\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
- local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 29)
- local docker_name="searxng"
- local docker_img="searxng/searxng"
- local docker_port=8029
- docker_rum() {
+ 84|wallos)
- docker run -d \
- --name searxng \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -v "/home/docker/searxng:/etc/searxng" \
- searxng/searxng
+ local app_id="84"
+ local docker_name="wallos"
+ local docker_img="bellamy/wallos:latest"
+ local docker_port=8084
- }
+ docker_rum() {
- local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
- local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/wallos && \
+ docker run -d --name wallos \
+ -v /home/docker/wallos/db:/var/www/html/db \
+ -v /home/docker/wallos/logos:/var/www/html/images/uploads/logos \
+ -e TZ=UTC \
+ -p ${docker_port}:80 \
+ --restart=always \
+ bellamy/wallos:latest
- 30)
- local docker_name="photoprism"
- local docker_img="photoprism/photoprism:latest"
- local docker_port=2342
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
-
- docker_rum() {
-
- docker run -d \
- --name photoprism \
- --restart always \
- --security-opt seccomp=unconfined \
- --security-opt apparmor=unconfined \
- -p ${docker_port}:2342 \
- -e PHOTOPRISM_UPLOAD_NSFW="true" \
- -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
- -v /home/docker/photoprism/storage:/photoprism/storage \
- -v /home/docker/photoprism/Pictures:/photoprism/originals \
- photoprism/photoprism
+ }
- }
+ local docker_describe="开源个人订阅追踪器,可用于财务管理"
+ local docker_url="官网介绍: ${gh_https_url}github.com/ellite/Wallos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="photoprism非常强大的私有相册系统"
- local docker_url="官网介绍: https://www.photoprism.app/"
- local docker_use="echo \"账号: admin 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 85|immich)
+ local app_id="85"
+ local app_name="immich图片视频管理器"
+ local app_text="高性能自托管照片和视频管理解决方案。"
+ local app_url="官网介绍: ${gh_https_url}github.com/immich-app/immich"
+ local docker_name="immich_server"
+ local docker_port="8085"
+ local app_size="3"
- 31)
- local docker_name="s-pdf"
- local docker_img="frooodle/s-pdf:latest"
- local docker_port=8020
-
- docker_rum() {
-
- docker run -d \
- --name s-pdf \
- --restart=always \
- -p ${docker_port}:8080 \
- -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
- -v /home/docker/s-pdf/extraConfigs:/configs \
- -v /home/docker/s-pdf/logs:/logs \
- -e DOCKER_ENABLE_SECURITY=false \
- frooodle/s-pdf:latest
- }
+ docker_app_install() {
+ install git openssl wget
+ mkdir -p /home/docker/${docker_name} && cd /home/docker/${docker_name}
- local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
- local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ wget -O docker-compose.yml ${gh_proxy}github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+ wget -O .env ${gh_proxy}github.com/immich-app/immich/releases/latest/download/example.env
+ sed -i "s/2283:2283/${docker_port}:2283/g" /home/docker/${docker_name}/docker-compose.yml
- 32)
- local docker_name="drawio"
- local docker_img="jgraph/drawio"
- local docker_port=7080
+ docker compose up -d
- docker_rum() {
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
- docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+ }
- }
+ docker_app_update() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ docker_app_install
+ }
+ docker_app_uninstall() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ rm -rf /home/docker/${docker_name}
+ echo "应用已卸载"
+ }
- local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
- local docker_url="官网介绍: https://www.drawio.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_plus
- 33)
- local docker_name="sun-panel"
- local docker_img="hslr/sun-panel"
- local docker_port=3009
- docker_rum() {
+ ;;
- docker run -d --restart=always -p ${docker_port}:3002 \
- -v /home/docker/sun-panel/conf:/app/conf \
- -v /home/docker/sun-panel/uploads:/app/uploads \
- -v /home/docker/sun-panel/database:/app/database \
- --name sun-panel \
- hslr/sun-panel
- }
+ 86|jellyfin)
- local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
- local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
- local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="86"
+ local docker_name="jellyfin"
+ local docker_img="jellyfin/jellyfin"
+ local docker_port=8086
- 34)
- local docker_name="pingvin-share"
- local docker_img="stonith404/pingvin-share"
- local docker_port=3060
-
- docker_rum() {
-
- docker run -d \
- --name pingvin-share \
- --restart always \
- -p ${docker_port}:3000 \
- -v /home/docker/pingvin-share/data:/opt/app/backend/data \
- stonith404/pingvin-share
- }
+ docker_rum() {
- local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
- local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/jellyfin/media
+ chmod -R 777 /home/docker/jellyfin
+ docker run -d \
+ --name jellyfin \
+ --user root \
+ --volume /home/docker/jellyfin/config:/config \
+ --volume /home/docker/jellyfin/cache:/cache \
+ --mount type=bind,source=/home/docker/jellyfin/media,target=/media \
+ -p ${docker_port}:8096 \
+ -p 7359:7359/udp \
+ --restart=always \
+ jellyfin/jellyfin
- 35)
- local docker_name="moments"
- local docker_img="kingwrcy/moments:latest"
- local docker_port=8035
-
- docker_rum() {
-
- docker run -d --restart unless-stopped \
- -p ${docker_port}:3000 \
- -v /home/docker/moments/data:/app/data \
- -v /etc/localtime:/etc/localtime:ro \
- -v /etc/timezone:/etc/timezone:ro \
- --name moments \
- kingwrcy/moments:latest
- }
+ }
- local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
- local docker_url="官网介绍: ${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
- local docker_use="echo \"账号: admin 密码: a123456\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="是一款开源媒体服务器软件"
+ local docker_url="官网介绍: https://jellyfin.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 36)
- local docker_name="lobe-chat"
- local docker_img="lobehub/lobe-chat:latest"
- local docker_port=8036
+ 87|synctv)
- docker_rum() {
+ local app_id="87"
+ local docker_name="synctv"
+ local docker_img="synctvorg/synctv"
+ local docker_port=8087
- docker run -d -p ${docker_port}:3210 \
- --name lobe-chat \
- --restart=always \
- lobehub/lobe-chat
- }
+ docker_rum() {
- local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
- local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker run -d \
+ --name synctv \
+ -v /home/docker/synctv:/root/.synctv \
+ -p ${docker_port}:8080 \
+ --restart=always \
+ synctvorg/synctv
- 37)
- local docker_name="myip"
- local docker_img="jason5ng32/myip:latest"
- local docker_port=8037
+ }
- docker_rum() {
+ local docker_describe="远程一起观看电影和直播的程序。它提供了同步观影、直播、聊天等功能"
+ local docker_url="官网介绍: ${gh_https_url}github.com/synctv-org/synctv"
+ local docker_use="echo \"初始账号和密码: root 登陆后请及时修改登录密码\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+ ;;
- }
+ 88|owncast)
- local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
- local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="88"
+ local docker_name="owncast"
+ local docker_img="owncast/owncast:latest"
+ local docker_port=8088
- 38)
- send_stats "小雅全家桶"
- clear
- install_docker
- check_disk_space 1
- bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
- ;;
+ docker_rum() {
- 39)
+ docker run -d \
+ --name owncast \
+ -p ${docker_port}:8080 \
+ -p 1935:1935 \
+ -v /home/docker/owncast/data:/app/data \
+ --restart=always \
+ owncast/owncast:latest
- if [ ! -d /home/docker/bililive-go/ ]; then
- mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
- wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
- fi
- local docker_name="bililive-go"
- local docker_img="chigusa/bililive-go"
- local docker_port=8039
+ }
- docker_rum() {
+ local docker_describe="开源、免费的自建直播平台"
+ local docker_url="官网介绍: https://owncast.online"
+ local docker_use="echo \"访问地址后面带 /admin 访问管理员页面\""
+ local docker_passwd="echo \"初始账号: admin 初始密码: abc123 登陆后请及时修改登录密码\""
+ local app_size="1"
+ docker_app
- docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+ ;;
- }
- local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 40)
- local docker_name="webssh"
- local docker_img="jrohy/webssh"
- local docker_port=8040
- docker_rum() {
- docker run -d -p ${docker_port}:5032 --restart always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
- }
+ 89|file-code-box)
- local docker_describe="简易在线ssh连接工具和sftp工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="89"
+ local docker_name="file-code-box"
+ local docker_img="lanol/filecodebox:latest"
+ local docker_port=8089
- 41)
+ docker_rum() {
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="耗子面板"
- local panelurl="官方地址: ${gh_proxy}github.com/TheTNB/panel"
+ docker run -d \
+ --name file-code-box \
+ -p ${docker_port}:12345 \
+ -v /home/docker/file-code-box/data:/app/data \
+ --restart=always \
+ lanol/filecodebox:latest
- panel_app_install() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
- cd ~
- }
+ }
- panel_app_manage() {
- panel-cli
- }
+ local docker_describe="匿名口令分享文本和文件,像拿快递一样取文件"
+ local docker_url="官网介绍: ${gh_https_url}github.com/vastsa/FileCodeBox"
+ local docker_use="echo \"访问地址后面带 /#/admin 访问管理员页面\""
+ local docker_passwd="echo \"管理员密码: FileCodeBox2023\""
+ local app_size="1"
+ docker_app
- panel_app_uninstall() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
- cd ~
- }
+ ;;
- install_panel
- ;;
- 42)
- local docker_name="nexterm"
- local docker_img="germannewsmaker/nexterm:latest"
- local docker_port=8042
+ 90|matrix)
- docker_rum() {
+ local app_id="90"
+ local docker_name="matrix"
+ local docker_img="matrixdotorg/synapse:latest"
+ local docker_port=8090
- docker run -d \
- --name nexterm \
- -p ${docker_port}:6989 \
- -v /home/docker/nexterm:/app/data \
- --restart unless-stopped \
- germannewsmaker/nexterm:latest
+ docker_rum() {
- }
+ add_yuming
- local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
- local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ if [ ! -d /home/docker/matrix/data ]; then
+ docker run --rm \
+ -v /home/docker/matrix/data:/data \
+ -e SYNAPSE_SERVER_NAME=${yuming} \
+ -e SYNAPSE_REPORT_STATS=yes \
+ --name matrix \
+ matrixdotorg/synapse:latest generate
+ fi
- 43)
- local docker_name="hbbs"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker run -d \
+ --name matrix \
+ -v /home/docker/matrix/data:/data \
+ -p ${docker_port}:8008 \
+ --restart=always \
+ matrixdotorg/synapse:latest
- docker_rum() {
+ echo "创建初始用户或管理员。请设置以下内容用户名和密码以及是否为管理员。"
+ docker exec -it matrix register_new_matrix_user \
+ http://localhost:8008 \
+ -c /data/homeserver.yaml
- docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbs
+ sed -i '/^enable_registration:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration: true' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^enable_registration_without_verification:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration_without_verification: true' /home/docker/matrix/data/homeserver.yaml
- }
+ docker restart matrix
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="docker logs hbbs"
- local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
- local app_size="1"
- docker_app
- ;;
+ }
- 44)
- local docker_name="hbbr"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ local docker_describe="Matrix是一个去中心化的聊天协议"
+ local docker_url="官网介绍: https://matrix.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbr
- }
- local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 91|gitea)
- 45)
- local docker_name="registry"
- local docker_img="registry:2"
- local docker_port=8045
+ local app_id="91"
- docker_rum() {
+ local app_name="gitea私有代码仓库"
+ local app_text="免费新一代的代码托管平台,提供接近 GitHub 的使用体验。"
+ local app_url="视频介绍: ${gh_https_url}github.com/go-gitea/gitea"
+ local docker_name="gitea"
+ local docker_port="8091"
+ local app_size="2"
- docker run -d \
- -p ${docker_port}:5000 \
- --name registry \
- -v /home/docker/registry:/var/lib/registry \
- -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
- --restart always \
- registry:2
+ docker_app_install() {
- }
+ mkdir -p /home/docker/gitea
+ mkdir -p /home/docker/gitea/gitea
+ mkdir -p /home/docker/gitea/data
+ mkdir -p /home/docker/gitea/postgres
+ cd /home/docker/gitea
- local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
- local docker_url="官网介绍: https://hub.docker.com/_/registry"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ curl -o /home/docker/gitea/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/gitea-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/gitea/docker-compose.yml
+ cd /home/docker/gitea/
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- 46)
- local docker_name="ghproxy"
- local docker_img="wjqserver/ghproxy:latest"
- local docker_port=8046
- docker_rum() {
+ docker_app_update() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ cd /home/docker/gitea/ && docker compose up -d
+ }
- docker run -d --name ghproxy --restart always -p ${docker_port}:8080 wjqserver/ghproxy:latest
- }
+ docker_app_uninstall() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ rm -rf /home/docker/gitea
+ echo "应用已卸载"
+ }
- local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
- local docker_url="官网介绍: https://github.com/WJQSERVER-STUDIO/ghproxy"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_plus
- 47)
+ ;;
- local app_name="普罗米修斯监控"
- local app_text="Prometheus+Grafana企业级监控系统"
- local app_url="官网介绍: https://prometheus.io"
- local docker_name="grafana"
- local docker_port="8047"
- local app_size="2"
- docker_app_install() {
- prometheus_install
- clear
- ip_address
- echo "已经安装完成"
- check_docker_app_ip
- echo "初始用户名密码均为: admin"
- }
+ 92|filebrowser)
- docker_app_update() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
- docker_app_install
- }
+ local app_id="92"
+ local docker_name="filebrowser"
+ local docker_img="hurlenko/filebrowser"
+ local docker_port=8092
- docker_app_uninstall() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
+ docker_rum() {
- rm -rf /home/docker/monitoring
- echo "应用已卸载"
- }
+ docker run -d \
+ --name filebrowser \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/filebrowser/data:/data \
+ -v /home/docker/filebrowser/config:/config \
+ -e FB_BASEURL=/filebrowser \
+ hurlenko/filebrowser
- docker_app_plus
- ;;
+ }
- 48)
- local docker_name="node-exporter"
- local docker_img="prom/node-exporter"
- local docker_port=8048
+ local docker_describe="是一个基于Web的文件管理器"
+ local docker_url="官网介绍: https://filebrowser.org/"
+ local docker_use="docker logs filebrowser"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run -d \
- --name=node-exporter \
- -p ${docker_port}:9100 \
- --restart unless-stopped \
- prom/node-exporter
+ 93|dufs)
+ local app_id="93"
+ local docker_name="dufs"
+ local docker_img="sigoden/dufs"
+ local docker_port=8093
- }
+ docker_rum() {
- local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/prometheus/node_exporter"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
-
- 49)
- local docker_name="cadvisor"
- local docker_img="gcr.io/cadvisor/cadvisor:latest"
- local docker_port=8049
-
- docker_rum() {
-
- docker run -d \
- --name=cadvisor \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- --volume=/:/rootfs:ro \
- --volume=/var/run:/var/run:rw \
- --volume=/sys:/sys:ro \
- --volume=/var/lib/docker/:/var/lib/docker:ro \
- gcr.io/cadvisor/cadvisor:latest \
- -housekeeping_interval=10s \
- -docker_only=true
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}:/data \
+ -p ${docker_port}:5000 \
+ ${docker_img} /data -A
- }
+ }
- local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/google/cadvisor"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="极简静态文件服务器,支持上传下载"
+ local docker_url="官网介绍: ${gh_https_url}github.com/sigoden/dufs"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 50)
- local docker_name="changedetection"
- local docker_img="dgtlmoon/changedetection.io:latest"
- local docker_port=8050
+ 94|gopeed)
- docker_rum() {
+ local app_id="94"
+ local docker_name="gopeed"
+ local docker_img="liwei2633/gopeed"
+ local docker_port=8094
- docker run -d --restart always -p ${docker_port}:5000 \
- -v /home/docker/datastore:/datastore \
- --name changedetection dgtlmoon/changedetection.io:latest
+ docker_rum() {
- }
+ read -e -p "设置登录用户名: " app_use
+ read -e -p "设置登录密码: " app_passwd
- local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
- local docker_url="官网介绍: https://github.com/dgtlmoon/changedetection.io"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}/downloads:/app/Downloads \
+ -v /home/docker/${docker_name}/storage:/app/storage \
+ -p ${docker_port}:9999 \
+ ${docker_img} -u ${app_use} -p ${app_passwd}
+ }
- 51)
- clear
- send_stats "PVE开小鸡"
- check_disk_space 1
- curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
- ;;
+ local docker_describe="分布式高速下载工具,支持多种协议"
+ local docker_url="官网介绍: ${gh_https_url}github.com/GopeedLab/gopeed"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 52)
- local docker_name="dpanel"
- local docker_img="dpanel/dpanel:lite"
- local docker_port=8052
- docker_rum() {
- docker run -it -d --name dpanel --restart=always \
- -p ${docker_port}:8080 -e APP_NAME=dpanel \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/dpanel:/dpanel \
- dpanel/dpanel:lite
+ 95|paperless)
- }
+ local app_id="95"
- local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
- local docker_url="官网介绍: https://github.com/donknap/dpanel"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_name="paperless文档管理平台"
+ local app_text="开源的电子文档管理系统,它的主要用途是把你的纸质文件数字化并管理起来。"
+ local app_url="视频介绍: https://docs.paperless-ngx.com/"
+ local docker_name="paperless-webserver-1"
+ local docker_port="8095"
+ local app_size="2"
- 53)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ docker_app_install() {
- docker_rum() {
+ mkdir -p /home/docker/paperless
+ mkdir -p /home/docker/paperless/export
+ mkdir -p /home/docker/paperless/consume
+ cd /home/docker/paperless
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ curl -o /home/docker/paperless/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/docker-compose.postgres-tika.yml
+ curl -o /home/docker/paperless/docker-compose.env ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/.env
- }
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/paperless/docker-compose.yml
+ cd /home/docker/paperless
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run llama3.2:1b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
- 54)
+ docker_app_update() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ docker_app_install
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="AMH面板"
- local panelurl="官方地址: https://amh.sh/index.htm?amh"
- panel_app_install() {
- cd ~
- wget https://dl.amh.sh/amh.sh && bash amh.sh
- }
+ docker_app_uninstall() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ rm -rf /home/docker/paperless
+ echo "应用已卸载"
+ }
- panel_app_manage() {
- panel_app_install
- }
+ docker_app_plus
- panel_app_uninstall() {
- panel_app_install
- }
+ ;;
- install_panel
- ;;
- 55)
- frps_panel
- ;;
+ 96|2fauth)
- 56)
- frpc_panel
- ;;
+ local app_id="96"
- 57)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ local app_name="2FAuth自托管二步验证器"
+ local app_text="自托管的双重身份验证 (2FA) 账户管理和验证码生成工具。"
+ local app_url="官网: ${gh_https_url}github.com/Bubka/2FAuth"
+ local docker_name="2fauth"
+ local docker_port="8096"
+ local app_size="1"
- docker_rum() {
+ docker_app_install() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ add_yuming
- }
+ mkdir -p /home/docker/2fauth
+ mkdir -p /home/docker/2fauth/data
+ chmod -R 777 /home/docker/2fauth/
+ cd /home/docker/2fauth
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ curl -o /home/docker/2fauth/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/2fauth-docker-compose.yml
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/2fauth/docker-compose.yml
+ sed -i "s/yuming.com/${yuming}/g" /home/docker/2fauth/docker-compose.yml
+ cd /home/docker/2fauth
+ docker compose up -d
- 58)
- local app_name="Dify知识库"
- local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
- local app_url="官方网站: https://docs.dify.ai/zh-hans"
- local docker_name="docker-nginx-1"
- local docker_port="8058"
- local app_size="3"
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
- # sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
- docker_app_update() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- cd /home/docker/dify/
- git pull origin main
- sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- cd /home/docker/dify/docker/ && docker compose up -d
- }
+ docker_app_update() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ docker_app_install
+ }
- docker_app_uninstall() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- rm -rf /home/docker/dify
- echo "应用已卸载"
- }
- docker_app_plus
+ docker_app_uninstall() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ rm -rf /home/docker/2fauth
+ echo "应用已卸载"
+ }
- ;;
+ docker_app_plus
- 59)
- local app_name="New API"
- local app_text="新一代大模型网关与AI资产管理系统"
- local app_url="官方网站: https://github.com/Calcium-Ion/new-api"
- local docker_name="new-api"
- local docker_port="8059"
- local app_size="3"
+ ;;
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ 97|wgs)
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
+ local app_id="97"
+ local docker_name="wireguard"
+ local docker_img="lscr.io/linuxserver/wireguard:latest"
+ local docker_port=8097
- docker_app_update() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- cd /home/docker/new-api/
- git pull origin main
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ docker_rum() {
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
+ read -e -p "请输入组网的客户端数量 (默认 5): " COUNT
+ COUNT=${COUNT:-5}
+ read -e -p "请输入 WireGuard 网段 (默认 10.13.13.0): " NETWORK
+ NETWORK=${NETWORK:-10.13.13.0}
- }
+ PEERS=$(seq -f "wg%02g" 1 "$COUNT" | paste -sd,)
- docker_app_uninstall() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- rm -rf /home/docker/new-api
- echo "应用已卸载"
- }
+ ip link delete wg0 &>/dev/null
- docker_app_plus
+ ip_address
+ docker run -d \
+ --name=wireguard \
+ --network host \
+ --cap-add=NET_ADMIN \
+ --cap-add=SYS_MODULE \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SERVERURL=${ipv4_address} \
+ -e SERVERPORT=51820 \
+ -e PEERS=${PEERS} \
+ -e INTERNAL_SUBNET=${NETWORK} \
+ -e ALLOWEDIPS=${NETWORK}/24 \
+ -e PERSISTENTKEEPALIVE_PEERS=all \
+ -e LOG_CONFS=true \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules \
+ --restart=always \
+ lscr.io/linuxserver/wireguard:latest
- ;;
+ sleep 3
- 60)
+ docker exec wireguard sh -c "
+ f='/config/wg_confs/wg0.conf'
+ sed -i 's/51820/${docker_port}/g' \$f
+ "
- local app_name="JumpServer开源堡垒机"
- local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
- local app_url="官方介绍: https://github.com/jumpserver/jumpserver"
- local docker_name="jms_web"
- local docker_port="80"
- local app_size="2"
+ docker exec wireguard sh -c "
+ for d in /config/peer_*; do
+ sed -i 's/51820/${docker_port}/g' \$d/*.conf
+ done
+ "
- docker_app_install() {
- curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
- clear
- echo "已经安装完成"
- check_docker_app_ip
- echo "初始用户名: admin"
- echo "初始密码: ChangeMe"
- }
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ sed -i "/^DNS/d" "$d"/*.conf
+ done
+ '
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ for f in "$d"/*.conf; do
+ grep -q "^PersistentKeepalive" "$f" || \
+ sed -i "/^AllowedIPs/ a PersistentKeepalive = 25" "$f"
+ done
+ done
+ '
+
+ docker exec wireguard bash -c '
+ for d in /config/peer_*; do
+ cd "$d" || continue
+ conf_file=$(ls *.conf)
+ base_name="${conf_file%.conf}"
+ qrencode -o "$base_name.png" < "$conf_file"
+ done
+ '
- docker_app_update() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh upgrade
- echo "应用已更新"
- }
+ docker restart wireguard
+ sleep 2
+ echo
+ echo -e "${gl_huang}所有客户端二维码配置: ${gl_bai}"
+ docker exec wireguard bash -c 'for i in $(ls /config | grep peer_ | sed "s/peer_//"); do echo "--- $i ---"; /app/show-peer $i; done'
+ sleep 2
+ echo
+ echo -e "${gl_huang}所有客户端配置代码: ${gl_bai}"
+ docker exec wireguard sh -c 'for d in /config/peer_*; do echo "# $(basename $d) "; cat $d/*.conf; echo; done'
+ sleep 2
+ echo -e "${gl_lv}${COUNT}个客户端配置全部输出,使用方法如下:${gl_bai}"
+ echo -e "${gl_lv}1. 手机下载wg的APP,扫描上方二维码,可以快速连接网络${gl_bai}"
+ echo -e "${gl_lv}2. Windows下载客户端,复制配置代码连接网络。${gl_bai}"
+ echo -e "${gl_lv}3. Linux用脚本部署WG客户端,复制配置代码连接网络。${gl_bai}"
+ echo -e "${gl_lv}官方客户端下载方式: https://www.wireguard.com/install/${gl_bai}"
+ break_end
- docker_app_uninstall() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh uninstall
- cd /opt
- rm -rf jumpserver-installer*/
- rm -rf jumpserver
- echo "应用已卸载"
- }
+ }
- docker_app_plus
- ;;
+ local docker_describe="现代化、高性能的虚拟专用网络工具"
+ local docker_url="官网介绍: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 61)
- local docker_name="libretranslate"
- local docker_img="libretranslate/libretranslate:latest"
- local docker_port=8061
+ ;;
- docker_rum() {
- docker run -d \
- -p ${docker_port}:5000 \
- --name libretranslate \
- libretranslate/libretranslate \
- --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+ 98|wgc)
- }
+ local app_id="98"
+ local docker_name="wireguardc"
+ local docker_img="kjlion/wireguard:alpine"
+ local docker_port=51820
- local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
- local docker_url="官网介绍: https://github.com/LibreTranslate/LibreTranslate"
- local docker_use=""
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_rum() {
+ mkdir -p /home/docker/wireguard/config/
+ local CONFIG_FILE="/home/docker/wireguard/config/wg0.conf"
- 62)
- local app_name="RAGFlow知识库"
- local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
- local app_url="官方网站: https://github.com/infiniflow/ragflow"
- local docker_name="ragflow-server"
- local docker_port="8062"
- local app_size="8"
+ # 创建目录(如果不存在)
+ mkdir -p "$(dirname "$CONFIG_FILE")"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- clear
- echo "已经安装完成"
- check_docker_app_ip
- }
+ echo "请粘贴你的客户端配置,连续按两次回车保存:"
- docker_app_update() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- cd /home/docker/ragflow/
- git pull origin main
- cd /home/docker/ragflow/docker/
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- }
+ # 初始化变量
+ input=""
+ empty_line_count=0
- docker_app_uninstall() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- rm -rf /home/docker/ragflow
- echo "应用已卸载"
- }
+ # 逐行读取用户输入
+ while IFS= read -r line; do
+ if [[ -z "$line" ]]; then
+ ((empty_line_count++))
+ if [[ $empty_line_count -ge 2 ]]; then
+ break
+ fi
+ else
+ empty_line_count=0
+ input+="$line"$'\n'
+ fi
+ done
- docker_app_plus
+ # 写入配置文件
+ echo "$input" > "$CONFIG_FILE"
- ;;
+ echo "客户端配置已保存到 $CONFIG_FILE"
+ ip link delete wg0 &>/dev/null
- 63)
- local docker_name="open-webui"
- local docker_img="ghcr.io/open-webui/open-webui:main"
- local docker_port=8063
+ docker run -d \
+ --name wireguardc \
+ --network host \
+ --cap-add NET_ADMIN \
+ --cap-add SYS_MODULE \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules:ro \
+ --restart=always \
+ kjlion/wireguard:alpine
- docker_rum() {
+ sleep 3
- docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+ docker logs wireguardc
- }
+ break_end
- local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use=""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ }
- 64)
- local docker_name="it-tools"
- local docker_img="corentinth/it-tools:latest"
- local docker_port=8064
+ local docker_describe="现代化、高性能的虚拟专用网络工具"
+ local docker_url="官网介绍: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
- docker run -d --name it-tools --restart unless-stopped -p ${docker_port}:80 corentinth/it-tools:latest
- }
+ ;;
- local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
- local docker_url="官网介绍: https://github.com/CorentinTh/it-tools"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 99|dsm)
- 65)
- local docker_name="n8n"
- local docker_img="docker.n8n.io/n8nio/n8n"
- local docker_port=8065
+ local app_id="99"
- docker_rum() {
+ local app_name="dsm群晖虚拟机"
+ local app_text="Docker容器中的虚拟DSM"
+ local app_url="官网: ${gh_https_url}github.com/vdsm/virtual-dsm"
+ local docker_name="dsm"
+ local docker_port="8099"
+ local app_size="16"
- add_yuming
- mkdir -p /home/docker/n8n
- chmod -R 777 /home/docker/n8n
-
- docker run -d --name n8n \
- --restart always \
- -p ${docker_port}:5678 \
- -v /home/docker/n8n:/home/node/.n8n \
- -e N8N_HOST=${yuming} \
- -e N8N_PORT=5678 \
- -e N8N_PROTOCOL=https \
- -e N8N_WEBHOOK_URL=https://${yuming}/ \
- docker.n8n.io/n8nio/n8n
+ docker_app_install() {
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ read -e -p "设置 CPU 核数 (默认 2): " CPU_CORES
+ local CPU_CORES=${CPU_CORES:-2}
- }
+ read -e -p "设置内存大小 (默认 4G): " RAM_SIZE
+ local RAM_SIZE=${RAM_SIZE:-4}
- local docker_describe="是一款功能强大的自动化工作流平台"
- local docker_url="官网介绍: https://github.com/n8n-io/n8n"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/dsm
+ mkdir -p /home/docker/dsm/dev
+ chmod -R 777 /home/docker/dsm/
+ cd /home/docker/dsm
- 66)
- yt_menu_pro
- ;;
+ curl -o /home/docker/dsm/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/dsm-docker-compose.yml
+ sed -i "s/5000:5000/${docker_port}:5000/g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|CPU_CORES: "2"|CPU_CORES: "${CPU_CORES}"|g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|RAM_SIZE: "2G"|RAM_SIZE: "${RAM_SIZE}G"|g" /home/docker/dsm/docker-compose.yml
+ cd /home/docker/dsm
+ docker compose up -d
- 67)
- local docker_name="ddns-go"
- local docker_img="jeessy/ddns-go"
- local docker_port=8067
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- docker_rum() {
- docker run -d \
- --name ddns-go \
- --restart=always \
- -p ${docker_port}:9876 \
- -v /home/docker/ddns-go:/root \
- jeessy/ddns-go
- }
+ docker_app_update() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ docker_app_install
+ }
- local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
- local docker_url="官网介绍: https://github.com/jeessy2/ddns-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 68)
- local docker_name="allinssl"
- local docker_img="allinssl/allinssl:latest"
- local docker_port=8068
+ docker_app_uninstall() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ rm -rf /home/docker/dsm
+ echo "应用已卸载"
+ }
- docker_rum() {
- docker run -itd --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
- }
+ docker_app_plus
- local docker_describe="开源免费的 SSL 证书自动化管理平台"
- local docker_url="官网介绍: https://allinssl.com"
- local docker_use="echo \"安全入口: /allinssl\""
- local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 69)
- local docker_name="sftpgo"
- local docker_img="drakkan/sftpgo:latest"
- local docker_port=8069
- docker_rum() {
+ 100|syncthing)
- mkdir -p /home/docker/sftpgo/data
- mkdir -p /home/docker/sftpgo/config
- chown -R 1000:1000 /home/docker/sftpgo
+ local app_id="100"
+ local docker_name="syncthing"
+ local docker_img="syncthing/syncthing:latest"
+ local docker_port=8100
- docker run -d \
- --name sftpgo \
- --restart=always \
- -p ${docker_port}:8080 \
- -p 22022:2022 \
- --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
- --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
- drakkan/sftpgo:latest
+ docker_rum() {
+ docker run -d \
+ --name=syncthing \
+ --hostname=my-syncthing \
+ --restart=always \
+ -p ${docker_port}:8384 \
+ -p 22000:22000/tcp \
+ -p 22000:22000/udp \
+ -p 21027:21027/udp \
+ -v /home/docker/syncthing:/var/syncthing \
+ syncthing/syncthing:latest
+ }
- }
+ local docker_describe="开源的点对点文件同步工具,类似于 Dropbox、Resilio Sync,但完全去中心化。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/syncthing/syncthing"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
- local docker_url="官网介绍: https://sftpgo.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 70)
- local docker_name="astrbot"
- local docker_img="soulter/astrbot:latest"
- local docker_port=8070
+ 101|moneyprinterturbo)
+ local app_id="101"
+ local app_name="AI视频生成工具"
+ local app_text="MoneyPrinterTurbo是一款使用AI大模型合成高清短视频的工具"
+ local app_url="官方网站: ${gh_https_url}github.com/harry0703/MoneyPrinterTurbo"
+ local docker_name="moneyprinterturbo"
+ local docker_port="8101"
+ local app_size="3"
- docker_rum() {
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git && cd MoneyPrinterTurbo/
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
- mkdir -p /home/docker/astrbot/data
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- sudo docker run -d \
- -p ${docker_port}:6185 \
- -p 6195:6195 \
- -p 6196:6196 \
- -p 6199:6199 \
- -p 11451:11451 \
- -v /home/docker/astrbot/data:/AstrBot/data \
- --restart unless-stopped \
- --name astrbot \
- soulter/astrbot:latest
+ docker_app_update() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ cd /home/docker/MoneyPrinterTurbo/
- }
+ git pull ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose up -d
+ }
- local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
- local docker_url="官网介绍: https://astrbot.app/"
- local docker_use="echo \"用户名: astrbot 密码: astrbot\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_uninstall() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ rm -rf /home/docker/MoneyPrinterTurbo
+ echo "应用已卸载"
+ }
+ docker_app_plus
- 71)
- local docker_name="navidrome"
- local docker_img="deluan/navidrome:latest"
- local docker_port=8071
+ ;;
- docker_rum() {
- docker run -d \
- --name navidrome \
- --restart=unless-stopped \
- --user $(id -u):$(id -g) \
- -v /home/docker/navidrome/music:/music \
- -v /home/docker/navidrome/data:/data \
- -p ${docker_port}:4533 \
- -e ND_LOGLEVEL=info \
- deluan/navidrome:latest
- }
+ 102|vocechat)
- local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
- local docker_url="官网介绍: https://www.navidrome.org/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="102"
+ local docker_name="vocechat-server"
+ local docker_img="privoce/vocechat-server:latest"
+ local docker_port=8102
+ docker_rum() {
- 72)
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ --name vocechat-server \
+ -v /home/docker/vocechat/data:/home/vocechat-server/data \
+ privoce/vocechat-server:latest
- local docker_name="bitwarden"
- local docker_img="vaultwarden/server"
- local docker_port=8072
+ }
- docker_rum() {
+ local docker_describe="是一款支持独立部署的个人云社交媒体聊天服务"
+ local docker_url="官网介绍: ${gh_https_url}github.com/Privoce/vocechat-web"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d \
- --name bitwarden \
- --restart always \
- -p ${docker_port}:80 \
- -v /home/docker/bitwarden/data:/data \
- vaultwarden/server
+ ;;
- }
- local docker_describe="一个你可以控制数据的密码管理器"
- local docker_url="官网介绍: https://bitwarden.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ 103|umami)
+ local app_id="103"
+ local app_name="Umami网站统计工具"
+ local app_text="开源、轻量、隐私友好的网站分析工具,类似于GoogleAnalytics。"
+ local app_url="官方网站: ${gh_https_url}github.com/umami-software/umami"
+ local docker_name="umami-umami-1"
+ local docker_port="8103"
+ local app_size="1"
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/umami-software/umami.git && cd umami
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/umami/docker-compose.yml
- ;;
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ echo "初始用户名: admin"
+ echo "初始密码: umami"
+ }
+ docker_app_update() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ cd /home/docker/umami/
+ git pull ${gh_proxy}github.com/umami-software/umami.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/umami/docker-compose.yml
+ cd /home/docker/umami/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ rm -rf /home/docker/umami
+ echo "应用已卸载"
+ }
- 73)
+ docker_app_plus
- local docker_name="libretv"
- local docker_img="bestzwei/libretv:latest"
- local docker_port=8073
+ ;;
- docker_rum() {
+ 104|nginx-stream)
+ stream_panel
+ ;;
- read -e -p "设置LibreTV的登录密码: " app_passwd
- docker run -d \
- --name libretv \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -e PASSWORD=${app_passwd} \
- bestzwei/libretv:latest
+ 105|siyuan)
- }
+ local app_id="105"
+ local docker_name="siyuan"
+ local docker_img="b3log/siyuan"
+ local docker_port=8105
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/LibreSpark/LibreTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ read -e -p "设置登录密码: " app_passwd
+ docker run -d \
+ --name siyuan \
+ --restart=always \
+ -v /home/docker/siyuan/workspace:/siyuan/workspace \
+ -p ${docker_port}:6806 \
+ -e PUID=1001 \
+ -e PGID=1002 \
+ b3log/siyuan \
+ --workspace=/siyuan/workspace/ \
+ --accessAuthCode="${app_passwd}"
- 74)
+ }
- local docker_name="moontv"
- local docker_img="ghcr.io/senshinya/moontv:latest"
- local docker_port=8074
+ local docker_describe="思源笔记是一款隐私优先的知识管理系统"
+ local docker_url="官网介绍: ${gh_https_url}github.com/siyuan-note/siyuan"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- read -e -p "设置MoonTV的登录密码: " app_passwd
- docker run -d \
- --name moontv \
- --restart unless-stopped \
- -p ${docker_port}:3000 \
- -e PASSWORD=${app_passwd} \
- ghcr.io/senshinya/moontv:latest
+ 106|drawnix)
- }
+ local app_id="106"
+ local docker_name="drawnix"
+ local docker_img="pubuzhixing/drawnix"
+ local docker_port=8106
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/senshinya/MoonTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ docker run -d \
+ --restart=always \
+ --name drawnix \
+ -p ${docker_port}:80 \
+ pubuzhixing/drawnix
+ }
- 75)
+ local docker_describe="是一款强大的开源白板工具,集成思维导图、流程图等。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/plait-board/drawnix"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="melody"
- local docker_img="foamzou/melody:latest"
- local docker_port=8075
+ ;;
- docker_rum() {
- docker run -d \
- --name melody \
- --restart unless-stopped \
- -p ${docker_port}:5566 \
- -v /home/docker/melody/.profile:/app/backend/.profile \
- foamzou/melody:latest
+ 107|pansou)
+ local app_id="107"
+ local docker_name="pansou"
+ local docker_img="ghcr.io/fish2018/pansou-web"
+ local docker_port=8107
- }
+ docker_rum() {
- local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
- local docker_url="官网介绍: https://github.com/foamzou/melody"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker run -d \
+ --name pansou \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/pansou/data:/app/data \
+ -v /home/docker/pansou/logs:/app/logs \
+ -e ENABLED_PLUGINS="hunhepan,jikepan,panwiki,pansearch,panta,qupansou,
+susu,thepiratebay,wanou,xuexizhinan,panyq,zhizhen,labi,muou,ouge,shandian,
+duoduo,huban,cyg,erxiao,miaoso,fox4k,pianku,clmao,wuji,cldi,xiaozhang,
+libvio,leijing,xb6v,xys,ddys,hdmoli,yuhuage,u3c3,javdb,clxiong,jutoushe,
+sdso,xiaoji,xdyh,haisou,bixin,djgou,nyaa,xinjuc,aikanzy,qupanshe,xdpan,
+discourse,yunsou,ahhhhfs,nsgame,gying" \
+ ghcr.io/fish2018/pansou-web
+ }
- ;;
+ local docker_describe="PanSou是一个高性能的网盘资源搜索API服务。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/fish2018/pansou"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 76)
- local docker_name="dosgame"
- local docker_img="oldiy/dosgame-web-docker:latest"
- local docker_port=8076
- docker_rum() {
- docker run -d \
- --name dosgame \
- --restart unless-stopped \
- -p ${docker_port}:262 \
- oldiy/dosgame-web-docker:latest
- }
+ 108|langbot)
+ local app_id="108"
+ local app_name="LangBot聊天机器人"
+ local app_text="是一个开源的大语言模型原生即时通信机器人开发平台"
+ local app_url="官方网站: ${gh_https_url}github.com/langbot-app/LangBot"
+ local docker_name="langbot_plugin_runtime"
+ local docker_port="8108"
+ local app_size="1"
- local docker_describe="是一个中文DOS游戏合集网站"
- local docker_url="官网介绍: https://github.com/rwv/chinese-dos-games"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langbot-app/LangBot && cd LangBot/docker
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
- ;;
+ docker_app_update() {
+ cd /home/docker/LangBot/docker && docker compose down --rmi all
+ cd /home/docker/LangBot/
+ git pull ${gh_proxy}github.com/langbot-app/LangBot main > /dev/null 2>&1
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ cd /home/docker/LangBot/docker/ && docker compose up -d
+ }
- 77)
+ docker_app_uninstall() {
+ cd /home/docker/LangBot/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/LangBot
+ echo "应用已卸载"
+ }
- local docker_name="xunlei"
- local docker_img="cnk3x/xunlei"
- local docker_port=8077
+ docker_app_plus
- docker_rum() {
+ ;;
- read -e -p "设置${docker_name}的登录用户名: " app_use
- read -e -p "设置${docker_name}的登录密码: " app_passwd
- docker run -d \
- --name xunlei \
- --restart unless-stopped \
- --privileged \
- -e XL_DASHBOARD_USERNAME=${app_use} \
- -e XL_DASHBOARD_PASSWORD=${app_passwd} \
- -v /home/docker/xunlei/data:/xunlei/data \
- -v /home/docker/xunlei/downloads:/xunlei/downloads \
- -p ${docker_port}:2345 \
- cnk3x/xunlei
+ 109|zfile)
- }
+ local app_id="109"
+ local docker_name="zfile"
+ local docker_img="zhaojun1998/zfile:latest"
+ local docker_port=8109
- local docker_describe="迅雷你的离线高速BT磁力下载工具"
- local docker_url="官网介绍: https://github.com/cnk3x/xunlei"
- local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ docker run -d --name=zfile --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/zfile/db:/root/.zfile-v4/db \
+ -v /home/docker/zfile/logs:/root/.zfile-v4/logs \
+ -v /home/docker/zfile/file:/data/file \
+ -v /home/docker/zfile/application.properties:/root/.zfile-v4/application.properties \
+ zhaojun1998/zfile:latest
- 78)
+ }
- local app_name="PandaWiki"
- local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
- local app_url="官方介绍: https://github.com/chaitin/PandaWiki"
- local docker_name="panda-wiki-nginx"
- local docker_port="2443"
- local app_size="2"
+ local docker_describe="是一个适用于个人或小团队的在线网盘程序。"
+ local docker_url="官网介绍: ${gh_https_url}github.com/zfile-dev/zfile"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_app_install() {
- bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
- }
+ ;;
- docker_app_update() {
- docker_app_install
- }
+ 110|karakeep)
+ local app_id="110"
+ local app_name="karakeep书签管理"
+ local app_text="是一款可自行托管的书签应用,带有人工智能功能,专为数据囤积者而设计。"
+ local app_url="官方网站: ${gh_https_url}github.com/karakeep-app/karakeep"
+ local docker_name="docker-web-1"
+ local docker_port="8110"
+ local app_size="1"
- docker_app_uninstall() {
- docker_app_install
- }
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/karakeep-app/karakeep.git && cd karakeep/docker && cp .env.sample .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
- docker_app_plus
- ;;
+ docker compose up -d
+ clear
+ echo "已经安装完成"
+ check_docker_app_ip
+ }
+ docker_app_update() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ cd /home/docker/karakeep/
+ git pull ${gh_proxy}github.com/karakeep-app/karakeep.git main > /dev/null 2>&1
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ cd /home/docker/karakeep/docker/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/karakeep
+ echo "应用已卸载"
+ }
- 79)
+ docker_app_plus
- local docker_name="beszel"
- local docker_img="henrygd/beszel"
- local docker_port=8079
+ ;;
- docker_rum() {
- mkdir -p /home/docker/beszel && \
- docker run -d \
- --name beszel \
- --restart=unless-stopped \
- -v /home/docker/beszel:/beszel_data \
- -p ${docker_port}:8090 \
- henrygd/beszel
- }
+ 111|convertx)
- local docker_describe="Beszel轻量易用的服务器监控"
- local docker_url="官网介绍: https://beszel.dev/zh/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="111"
+ local docker_name="convertx"
+ local docker_img="ghcr.io/c4illin/convertx:latest"
+ local docker_port=8111
- ;;
+ docker_rum() {
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/convertx:/app/data \
+ ${docker_img}
- 80)
- local app_name="linkwarden书签管理"
- local app_text="一个开源的自托管书签管理平台,支持标签、搜索和团队协作。"
- local app_url="官方网站: https://linkwarden.app/"
- local docker_name="linkwarden-linkwarden-1"
- local docker_port="8080"
- local app_size="3"
+ }
- docker_app_install() {
- install git openssl
- mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
+ local docker_describe="是一个功能强大的多格式文件转换工具(支持文档、图像、音频视频等)强烈建议添加域名访问"
+ local docker_url="项目地址: ${gh_https_url}github.com/c4illin/ConvertX"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- # 下载官方 docker-compose 和 env 文件
- curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
- curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
+ ;;
- # 生成随机密钥与密码
- local ADMIN_EMAIL="admin@example.com"
- local ADMIN_PASSWORD=$(openssl rand -hex 8)
- sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
- sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
- sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
- sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
+ 112|lucky)
- # 追加管理员账号信息
- echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
- echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
+ local app_id="112"
+ local docker_name="lucky"
+ local docker_img="gdy666/lucky:v2"
+ # 由于 Lucky 使用 host 网络模式,这里的端口仅作记录/说明参考,实际由应用自身控制(默认16601)
+ local docker_port=8112
- sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+ docker_rum() {
- # 启动容器
- docker compose up -d
+ docker run -d --name=${docker_name} --restart=always \
+ --network host \
+ -v /home/docker/lucky/conf:/app/conf \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ ${docker_img}
- clear
- echo "已经安装完成"
- check_docker_app_ip
+ echo "正在等待 Lucky 初始化..."
+ sleep 10
+ docker exec lucky /app/lucky -rSetHttpAdminPort ${docker_port}
- }
+ }
- docker_app_update() {
- cd /home/docker/linkwarden && docker compose down --rmi all
- curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
- curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
-
- # 保留原本的变量
- source .env
- mv .env.new .env
- echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
- echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
- echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
- echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
- echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
- echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
- sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
-
- docker compose up -d
- }
+ local docker_describe="Lucky 是一个大内网穿透及端口转发管理工具,支持 DDNS、反向代理、WOL 等功能。"
+ local docker_url="项目地址: ${gh_https_url}github.com/gdy666/lucky"
+ local docker_use="echo \"默认账号密码: 666\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_app_uninstall() {
- cd /home/docker/linkwarden && docker compose down --rmi all
- rm -rf /home/docker/linkwarden
- echo "应用已卸载"
- }
+ ;;
- docker_app_plus
- ;;
+ 113|firefox)
+ local app_id="113"
+ local docker_name="firefox"
+ local docker_img="jlesage/firefox:latest"
+ local docker_port=8113
+ docker_rum() {
- 81)
- local app_name="JitsiMeet视频会议"
- local app_text="一个开源的安全视频会议解决方案,支持多人在线会议、屏幕共享与加密通信。"
- local app_url="官方网站: https://jitsi.org/"
- local docker_name="jitsi"
- local docker_port="8081"
- local app_size="3"
+ read -e -p "设置登录密码: " admin_password
- docker_app_install() {
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:5800 \
+ -v /home/docker/firefox:/config:rw \
+ -e ENABLE_CJK_FONT=1 \
+ -e WEB_AUDIO=1 \
+ -e VNC_PASSWORD="${admin_password}" \
+ ${docker_img}
+ }
- add_yuming
- mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
- wget $(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep zip | cut -d\" -f4)
- unzip "$(ls -t | head -n 1)"
- cd "$(ls -dt */ | head -n 1)"
- cp env.example .env
- ./gen-passwords.sh
- mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
- sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
- sed -i "s|^#PUBLIC_URL=https://meet.example.com:\${HTTPS_PORT}|PUBLIC_URL=https://$yuming:443|" .env
- docker compose up -d
+ local docker_describe="是一个运行在 Docker 中的 Firefox 浏览器,支持通过网页直接访问桌面版浏览器界面。"
+ local docker_url="项目地址: ${gh_https_url}github.com/jlesage/docker-firefox"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ ;;
- }
+ 114|Moltbot|ClawdBot|moltbot|clawdbot|openclaw|OpenClaw)
+ moltbot_menu
+ ;;
- docker_app_update() {
- cd /home/docker/jitsi
- cd "$(ls -dt */ | head -n 1)"
- docker compose down --rmi all
- docker compose up -d
+ 115|hermes)
+ bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/hermes_manager.sh)
+ ;;
- }
+ b)
+ clear
+ send_stats "全部应用备份"
- docker_app_uninstall() {
- cd /home/docker/jitsi
- cd "$(ls -dt */ | head -n 1)"
- docker compose down --rmi all
- rm -rf /home/docker/jitsi
- echo "应用已卸载"
- }
+ local backup_filename="app_$(date +"%Y%m%d%H%M%S").tar.gz"
+ echo -e "${gl_kjlan}正在备份 $backup_filename ...${gl_bai}"
+ cd / && tar czvf "$backup_filename" home
- docker_app_plus
+ while true; do
+ clear
+ echo "备份文件已创建: /$backup_filename"
+ read -e -p "要传送备份数据到远程服务器吗?(Y/N): " choice
+ case "$choice" in
+ [Yy])
+ kj_ssh_read_host_port "请输入远端服务器IP: " "目标服务器SSH端口 [默认22]: " "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
+ local latest_tar=$(ls -t /app*.tar.gz | head -1)
+ if [ -n "$latest_tar" ]; then
+ ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
+ sleep 2 # 添加等待时间
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/"
+ echo "文件已传送至远程服务器/根目录。"
+ else
+ echo "未找到要传送的文件。"
+ fi
+ break
+ ;;
+ *)
+ echo "注意: 目前备份仅包含docker项目,不包含宝塔,1panel等建站面板的数据备份。"
+ break
+ ;;
+ esac
+ done
- ;;
+ ;;
+ r)
+ root_use
+ send_stats "全部应用还原"
+ echo "可用的应用备份"
+ echo "-------------------------"
+ ls -lt /app*.gz | awk '{print $NF}'
+ echo ""
+ read -e -p "回车键还原最新的备份,输入备份文件名还原指定的备份,输入0退出:" filename
+ if [ "$filename" == "0" ]; then
+ break_end
+ linux_panel
+ fi
+
+ # 如果用户没有输入文件名,使用最新的压缩包
+ if [ -z "$filename" ]; then
+ local filename=$(ls -t /app*.tar.gz | head -1)
+ fi
+
+ if [ -n "$filename" ]; then
+ echo -e "${gl_kjlan}正在解压 $filename ...${gl_bai}"
+ cd / && tar -xzf "$filename"
+ echo "应用数据已还原,目前请手动进入指定应用菜单,更新应用,即可还原应用。"
+ else
+ echo "没有找到压缩包。"
+ fi
+ ;;
- 0)
- kejilion
- ;;
- *)
- echo "无效的输入!"
- ;;
- esac
- break_end
+ 0)
+ kejilion
+ ;;
+ *)
+ cd ~
+ install git
+ if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+ else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+ fi
+ local custom_app="$HOME/apps/${sub_choice}.conf"
+ if [ -f "$custom_app" ]; then
+ . "$custom_app"
+ else
+ echo -e "${gl_hong}错误: 未找到编号为 ${sub_choice} 的应用配置${gl_bai}"
+ fi
+ ;;
+ esac
+ break_end
+ sub_choice=""
- done
+done
}
+
linux_work() {
while true; do
@@ -10977,37 +19605,527 @@ linux_work() {
done
;;
- 22)
- read -e -p "请输入你创建或进入的工作区名称,如1001 kj001 work1: " SESSION_NAME
- tmux_run
- send_stats "自定义工作区"
- ;;
+ 22)
+ read -e -p "请输入你创建或进入的工作区名称,如1001 kj001 work1: " SESSION_NAME
+ tmux_run
+ send_stats "自定义工作区"
+ ;;
+
+
+ 23)
+ read -e -p "请输入你要后台执行的命令,如:curl -fsSL https://get.docker.com | sh: " tmuxd
+ tmux_run_d
+ send_stats "注入命令到后台工作区"
+ ;;
+
+ 24)
+ read -e -p "请输入要删除的工作区名称: " gongzuoqu_name
+ tmux kill-window -t $gongzuoqu_name
+ send_stats "删除工作区"
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ echo "无效的输入!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+# 智能切换镜像源函数
+switch_mirror() {
+ # 可选参数,默认为 false
+ local upgrade_software=${1:-false}
+ local clean_cache=${2:-false}
+
+ # 获取用户国家
+ local country
+ country=$(curl -s ipinfo.io/country)
+
+ echo "检测到国家:$country"
+
+ if [ "$country" = "CN" ]; then
+ echo "使用国内镜像源..."
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.huaweicloud.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ echo "使用海外镜像源..."
+ if [ -f /etc/os-release ] && grep -qi "oracle" /etc/os-release; then
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.xtom.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --use-official-source true \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ fi
+ fi
+}
+
+
+fail2ban_panel() {
+ root_use
+ send_stats "ssh防御"
+ while true; do
+
+ check_f2b_status
+ echo -e "SSH防御程序 $check_f2b_status"
+ echo "fail2ban是一个SSH防止暴力破解工具"
+ echo "官网介绍: ${gh_proxy}github.com/fail2ban/fail2ban"
+ echo "------------------------"
+ echo "1. 安装防御程序"
+ echo "------------------------"
+ echo "2. 查看SSH拦截记录"
+ echo "3. 日志实时监控"
+ echo "------------------------"
+ echo "4. 基础参数配置(封禁时长/时间窗口/重试次数)"
+ echo "5. 编辑配置文件(nano)"
+ echo "------------------------"
+ echo "9. 卸载防御程序"
+ echo "------------------------"
+ echo "0. 返回上一级选单"
+ echo "------------------------"
+ read -e -p "请输入你的选择: " sub_choice
+ case $sub_choice in
+ 1)
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ break_end
+ ;;
+ 2)
+ echo "------------------------"
+ f2b_sshd
+ echo "------------------------"
+ break_end
+ ;;
+ 3)
+ tail -f /var/log/fail2ban.log
+ break
+ ;;
+ 4)
+ send_stats "SSH防御基础参数配置"
+ f2b_basic_config
+ break_end
+ ;;
+ 5)
+ send_stats "SSH防御编辑配置文件"
+ f2b_edit_config
+ break_end
+ ;;
+ 9)
+ remove fail2ban
+ rm -rf /etc/fail2ban
+ echo "Fail2Ban防御程序已卸载"
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+}
+
+
+
+
+
+net_menu() {
+
+ send_stats "网卡管理工具"
+ show_nics() {
+ echo "================ 当前网卡信息 ================"
+ printf "%-18s %-12s %-20s %-26s\n" "网卡名" "状态" "IP地址" "MAC地址"
+ echo "------------------------------------------------"
+ for nic in $(ls /sys/class/net); do
+ state=$(cat /sys/class/net/$nic/operstate 2>/dev/null)
+ ipaddr=$(ip -4 addr show $nic | awk '/inet /{print $2}' | head -n1)
+ mac=$(cat /sys/class/net/$nic/address 2>/dev/null)
+ printf "%-15s %-10s %-18s %-20s\n" "$nic" "$state" "${ipaddr:-无}" "$mac"
+ done
+ echo "================================================"
+ }
+
+ while true; do
+ clear
+ show_nics
+ echo
+ echo "=========== 网卡管理菜单 ==========="
+ echo "1. 启用网卡"
+ echo "2. 禁用网卡"
+ echo "3. 查看网卡详细信息"
+ echo "4. 刷新网卡信息"
+ echo "0. 返回上一级选单"
+ echo "===================================="
+ read -erp "请选择操作: " choice
+
+ case $choice in
+ 1)
+ send_stats "启用网卡"
+ read -erp "请输入要启用的网卡名: " nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" up && echo "✔ 网卡 $nic 已启用"
+ else
+ echo "✘ 网卡不存在"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 2)
+ send_stats "禁用网卡"
+ read -erp "请输入要禁用的网卡名: " nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" down && echo "✔ 网卡 $nic 已禁用"
+ else
+ echo "✘ 网卡不存在"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 3)
+ send_stats "查看网卡详情"
+ read -erp "请输入要查看的网卡名: " nic
+ if ip link show "$nic" &>/dev/null; then
+ echo "========== $nic 详细信息 =========="
+ ip addr show "$nic"
+ ethtool "$nic" 2>/dev/null | head -n 10
+ else
+ echo "✘ 网卡不存在"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 4)
+ send_stats "刷新网卡信息"
+ continue
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+log_menu() {
+ send_stats "系统日志管理工具"
+
+ show_log_overview() {
+ echo "============= 系统日志概览 ============="
+ echo "主机名: $(hostname)"
+ echo "系统时间: $(date)"
+ echo
+ echo "[ /var/log 目录占用 ]"
+ du -sh /var/log 2>/dev/null
+ echo
+ echo "[ journal 日志占用 ]"
+ journalctl --disk-usage 2>/dev/null
+ echo "========================================"
+ }
+
+ while true; do
+ clear
+ show_log_overview
+ echo
+ echo "=========== 系统日志管理菜单 ==========="
+ echo "1. 查看最近系统日志(journal)"
+ echo "2. 查看指定服务日志"
+ echo "3. 查看登录/安全日志"
+ echo "4. 实时跟踪日志"
+ echo "5. 清理旧 journal 日志"
+ echo "0. 返回上一级选单"
+ echo "======================================="
+ read -erp "请选择操作: " choice
+
+ case $choice in
+ 1)
+ send_stats "查看最近日志"
+ read -erp "查看最近多少行日志?[默认 100]: " lines
+ lines=${lines:-100}
+ journalctl -n "$lines" --no-pager
+ read -erp "按回车继续..."
+ ;;
+ 2)
+ send_stats "查看指定服务日志"
+ read -erp "请输入服务名(如 sshd、nginx): " svc
+ if systemctl list-unit-files | grep -q "^$svc"; then
+ journalctl -u "$svc" -n 100 --no-pager
+ else
+ echo "✘ 服务不存在或无日志"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 3)
+ send_stats "查看登录/安全日志"
+ echo "====== 最近登录日志 ======"
+ last -n 10
+ echo
+ echo "====== 认证日志 ======"
+ if [ -f /var/log/secure ]; then
+ tail -n 20 /var/log/secure
+ elif [ -f /var/log/auth.log ]; then
+ tail -n 20 /var/log/auth.log
+ else
+ echo "未找到安全日志文件"
+ fi
+ read -erp "按回车继续..."
+ ;;
+ 4)
+ send_stats "实时跟踪日志"
+ echo "1) 系统日志"
+ echo "2) 指定服务日志"
+ read -erp "选择跟踪类型: " t
+ if [ "$t" = "1" ]; then
+ journalctl -f
+ elif [ "$t" = "2" ]; then
+ read -erp "输入服务名: " svc
+ journalctl -u "$svc" -f
+ else
+ echo "无效选择"
+ fi
+ ;;
+ 5)
+ send_stats "清理旧 journal 日志"
+ echo "⚠️ 清理 journal 日志(安全方式)"
+ echo "1) 保留最近 7 天"
+ echo "2) 保留最近 3 天"
+ echo "3) 限制日志最大 500M"
+ read -erp "请选择清理方式: " c
+ case $c in
+ 1) journalctl --vacuum-time=7d ;;
+ 2) journalctl --vacuum-time=3d ;;
+ 3) journalctl --vacuum-size=500M ;;
+ *) echo "无效选项" ;;
+ esac
+ echo "✔ journal 日志清理完成"
+ sleep 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+env_menu() {
+
+ BASHRC="$HOME/.bashrc"
+ PROFILE="$HOME/.profile"
+
+ send_stats "系统变量管理工具"
+
+ show_env_vars() {
+ clear
+ send_stats "当前已生效环境变量"
+ echo "========== 当前已生效环境变量(节选) =========="
+ printf "%-20s %s\n" "变量名" "值"
+ echo "-----------------------------------------------"
+ for v in USER HOME SHELL LANG PWD; do
+ printf "%-20s %s\n" "$v" "${!v}"
+ done
+
+ echo
+ echo "PATH:"
+ echo "$PATH" | tr ':' '\n' | nl -ba
+
+ echo
+ echo "========== 配置文件中定义的变量(解析) =========="
+
+ parse_file_vars() {
+ local file="$1"
+ [ -f "$file" ] || return
+
+ echo
+ echo ">>> 来源文件:$file"
+ echo "-----------------------------------------------"
+
+ # 提取 export VAR=xxx 或 VAR=xxx
+ grep -Ev '^\s*#|^\s*$' "$file" \
+ | grep -E '^(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' \
+ | while read -r line; do
+ var=$(echo "$line" | sed -E 's/^(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*).*/\2/')
+ val=$(echo "$line" | sed -E 's/^[^=]+=//')
+ printf "%-20s %s\n" "$var" "$val"
+ done
+ }
+
+ parse_file_vars "$HOME/.bashrc"
+ parse_file_vars "$HOME/.profile"
+
+ echo
+ echo "==============================================="
+ read -erp "按回车继续..."
+ }
+
+
+ view_file() {
+ local file="$1"
+ send_stats "查看变量文件 $file"
+ clear
+ if [ -f "$file" ]; then
+ echo "========== 查看文件:$file =========="
+ cat -n "$file"
+ echo "===================================="
+ else
+ echo "文件不存在:$file"
+ fi
+ read -erp "按回车继续..."
+ }
+
+ edit_file() {
+ local file="$1"
+ send_stats "编辑变量文件 $file"
+ install nano
+ nano "$file"
+ }
+
+ source_files() {
+ echo "正在重新加载环境变量..."
+ send_stats "正在重新加载环境变量"
+ source "$BASHRC"
+ source "$PROFILE"
+ echo "✔ 环境变量已重新加载"
+ read -erp "按回车继续..."
+ }
+
+ while true; do
+ clear
+ echo "=========== 系统环境变量管理 =========="
+ echo "当前用户:$USER"
+ echo "--------------------------------------"
+ echo "1. 查看当前常用环境变量"
+ echo "2. 查看 ~/.bashrc"
+ echo "3. 查看 ~/.profile"
+ echo "4. 编辑 ~/.bashrc"
+ echo "5. 编辑 ~/.profile"
+ echo "6. 重新加载环境变量(source)"
+ echo "--------------------------------------"
+ echo "0. 返回上一级选单"
+ echo "--------------------------------------"
+ read -erp "请选择操作: " choice
+
+ case "$choice" in
+ 1)
+ show_env_vars
+ ;;
+ 2)
+ view_file "$BASHRC"
+ ;;
+ 3)
+ view_file "$PROFILE"
+ ;;
+ 4)
+ edit_file "$BASHRC"
+ ;;
+ 5)
+ edit_file "$PROFILE"
+ ;;
+ 6)
+ source_files
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "无效选项"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
+create_user_with_sshkey() {
+ local new_username="$1"
+ local is_sudo="${2:-false}"
+ local sshkey_vl
- 23)
- read -e -p "请输入你要后台执行的命令,如:curl -fsSL https://get.docker.com | sh: " tmuxd
- tmux_run_d
- send_stats "注入命令到后台工作区"
- ;;
+ if [[ -z "$new_username" ]]; then
+ echo "用法:create_user_with_sshkey <用户名>"
+ return 1
+ fi
- 24)
- read -e -p "请输入要删除的工作区名称: " gongzuoqu_name
- tmux kill-window -t $gongzuoqu_name
- send_stats "删除工作区"
- ;;
+ # 创建用户
+ useradd -m -s /bin/bash "$new_username" || return 1
- 0)
- kejilion
- ;;
- *)
- echo "无效的输入!"
- ;;
- esac
- break_end
+ echo "导入公钥范例:"
+ echo " - URL: ${gh_https_url}github.com/torvalds.keys"
+ echo " - 直接粘贴: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+ read -e -p "请导入 ${new_username} 的公钥: " sshkey_vl
- done
+ case "$sshkey_vl" in
+ http://*|https://*)
+ send_stats "从 URL 导入 SSH 公钥"
+ fetch_remote_ssh_keys "$sshkey_vl" "/home/$new_username"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa*)
+ send_stats "公钥直接导入"
+ import_sshkey "$sshkey_vl" "/home/$new_username"
+ ;;
+ *)
+ echo "错误:未知参数 '$sshkey_vl'"
+ return 1
+ ;;
+ esac
+ # 修正权限
+ chown -R "$new_username:$new_username" "/home/$new_username/.ssh"
+
+ install sudo
+
+ # sudo 免密
+ if [[ "$is_sudo" == "true" ]]; then
+ cat >"/etc/sudoers.d/$new_username" <> /etc/ssh/sshd_config
+ passwd -l "$new_username" &>/dev/null
+ restart_ssh
+
+ echo "用户 $new_username 创建完成"
}
@@ -11021,6 +20139,9 @@ linux_work() {
+
+
+
linux_Settings() {
while true; do
@@ -11029,7 +20150,7 @@ linux_Settings() {
echo -e "系统工具"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}设置脚本启动快捷键 ${gl_kjlan}2. ${gl_bai}修改登录密码"
- echo -e "${gl_kjlan}3. ${gl_bai}ROOT密码登录模式 ${gl_kjlan}4. ${gl_bai}安装Python指定版本"
+ echo -e "${gl_kjlan}3. ${gl_bai}用户密码登录模式 ${gl_kjlan}4. ${gl_bai}安装Python指定版本"
echo -e "${gl_kjlan}5. ${gl_bai}开放所有端口 ${gl_kjlan}6. ${gl_bai}修改SSH连接端口"
echo -e "${gl_kjlan}7. ${gl_bai}优化DNS地址 ${gl_kjlan}8. ${gl_bai}一键重装系统 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}9. ${gl_bai}禁用ROOT账户创建新账户 ${gl_kjlan}10. ${gl_bai}切换优先ipv4/ipv6"
@@ -11041,7 +20162,7 @@ linux_Settings() {
echo -e "${gl_kjlan}19. ${gl_bai}切换系统更新源 ${gl_kjlan}20. ${gl_bai}定时任务管理"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}本机host解析 ${gl_kjlan}22. ${gl_bai}SSH防御程序"
- echo -e "${gl_kjlan}23. ${gl_bai}限流自动关机 ${gl_kjlan}24. ${gl_bai}ROOT私钥登录模式"
+ echo -e "${gl_kjlan}23. ${gl_bai}限流自动关机 ${gl_kjlan}24. ${gl_bai}用户密钥登录模式"
echo -e "${gl_kjlan}25. ${gl_bai}TG-bot系统监控预警 ${gl_kjlan}26. ${gl_bai}修复OpenSSH高危漏洞"
echo -e "${gl_kjlan}27. ${gl_bai}红帽系Linux内核升级 ${gl_kjlan}28. ${gl_bai}Linux系统内核参数优化 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}29. ${gl_bai}病毒扫描工具 ${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}文件管理器"
@@ -11050,9 +20171,11 @@ linux_Settings() {
echo -e "${gl_kjlan}33. ${gl_bai}设置系统回收站 ${gl_kjlan}34. ${gl_bai}系统备份与恢复"
echo -e "${gl_kjlan}35. ${gl_bai}ssh远程连接工具 ${gl_kjlan}36. ${gl_bai}硬盘分区管理工具"
echo -e "${gl_kjlan}37. ${gl_bai}命令行历史记录 ${gl_kjlan}38. ${gl_bai}rsync远程同步工具"
- echo -e "${gl_kjlan}39. ${gl_bai}命令收藏夹 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}39. ${gl_bai}命令收藏夹 ${gl_huang}★${gl_bai} ${gl_kjlan}40. ${gl_bai}网卡管理工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}留言板 ${gl_kjlan}66. ${gl_bai}一条龙系统调优 ${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}41. ${gl_bai}系统日志管理工具 ${gl_huang}★${gl_bai} ${gl_kjlan}42. ${gl_bai}系统变量管理工具"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}61. ${gl_bai}留言板 ${gl_kjlan}66. ${gl_bai}一条龙系统调优 ${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}99. ${gl_bai}重启服务器 ${gl_kjlan}100. ${gl_bai}隐私与安全"
echo -e "${gl_kjlan}101. ${gl_bai}k命令高级用法 ${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}卸载科技lion脚本"
echo -e "${gl_kjlan}------------------------"
@@ -11070,7 +20193,10 @@ linux_Settings() {
linux_Settings
fi
find /usr/local/bin/ -type l -exec bash -c 'test "$(readlink -f {})" = "/usr/local/bin/k" && rm -f {}' \;
- ln -s /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ if [ "$kuaijiejian" != "k" ]; then
+ ln -sf /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ fi
+ ln -sf /usr/local/bin/k /usr/bin/$kuaijiejian > /dev/null 2>&1
echo "快捷键已设置"
send_stats "脚本快捷键已设置"
break_end
@@ -11085,8 +20211,7 @@ linux_Settings() {
passwd
;;
3)
- root_use
- send_stats "root密码模式"
+ clear
add_sshpasswd
;;
@@ -11188,7 +20313,7 @@ EOF
while true; do
clear
- sed -i 's/#Port/Port/' /etc/ssh/sshd_config
+ sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
# 读取当前的 SSH 端口号
local current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
@@ -11206,7 +20331,7 @@ EOF
if [[ $new_port =~ ^[0-9]+$ ]]; then # 检查输入是否为数字
if [[ $new_port -ge 1 && $new_port -le 65535 ]]; then
send_stats "SSH端口已修改"
- new_ssh_port
+ new_ssh_port $new_port
elif [[ $new_port -eq 0 ]]; then
send_stats "退出SSH端口修改"
break
@@ -11243,14 +20368,13 @@ EOF
linux_Settings
fi
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
+ create_user_with_sshkey $new_username true
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- passwd -l root
+ ssh-keygen -l -f /home/$new_username/.ssh/authorized_keys &>/dev/null && {
+ passwd -l root &>/dev/null
+ sed -i 's/^[[:space:]]*#\?[[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
+ }
- echo "操作已完成。"
;;
@@ -11261,13 +20385,14 @@ EOF
clear
echo "设置v4/v6优先级"
echo "------------------------"
- local ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
- if [ "$ipv6_disabled" -eq 1 ]; then
+
+ if grep -Eq '^\s*precedence\s+::ffff:0:0/96\s+100\s*$' /etc/gai.conf 2>/dev/null; then
echo -e "当前网络优先级设置: ${gl_huang}IPv4${gl_bai} 优先"
else
echo -e "当前网络优先级设置: ${gl_huang}IPv6${gl_bai} 优先"
fi
+
echo ""
echo "------------------------"
echo "1. IPv4 优先 2. IPv6 优先 3. IPv6 修复工具"
@@ -11278,12 +20403,10 @@ EOF
case $choice in
1)
- sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
- echo "已切换为 IPv4 优先"
- send_stats "已切换为 IPv4 优先"
+ prefer_ipv4
;;
2)
- sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
+ rm -f /etc/gai.conf
echo "已切换为 IPv6 优先"
send_stats "已切换为 IPv6 优先"
;;
@@ -11365,7 +20488,12 @@ EOF
printf "%-24s %-34s %-20s %-10s\n" "用户名" "用户权限" "用户组" "sudo权限"
while IFS=: read -r username _ userid groupid _ _ homedir shell; do
local groups=$(groups "$username" | cut -d : -f 2)
- local sudo_status=$(sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL : ALL)' && echo "Yes" || echo "No")
+ local sudo_status
+ if sudo -n -lU "$username" 2>/dev/null | grep -q "(ALL) \(NOPASSWD: \)\?ALL"; then
+ sudo_status="Yes"
+ else
+ sudo_status="No"
+ fi
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$groups" "$sudo_status"
done < /etc/passwd
@@ -11373,7 +20501,7 @@ EOF
echo ""
echo "账户操作"
echo "------------------------"
- echo "1. 创建普通账户 2. 创建高级账户"
+ echo "1. 创建普通用户 2. 创建高级用户"
echo "------------------------"
echo "3. 赋予最高权限 4. 取消最高权限"
echo "------------------------"
@@ -11387,42 +20515,34 @@ EOF
1)
# 提示用户输入新用户名
read -e -p "请输入新用户名: " new_username
+ create_user_with_sshkey $new_username false
- # 创建新用户并设置密码
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "操作已完成。"
;;
2)
# 提示用户输入新用户名
read -e -p "请输入新用户名: " new_username
-
- # 创建新用户并设置密码
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- # 赋予新用户sudo权限
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- echo "操作已完成。"
+ create_user_with_sshkey $new_username true
;;
3)
read -e -p "请输入用户名: " username
- # 赋予新用户sudo权限
- echo "$username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ install sudo
+ cat >"/etc/sudoers.d/$username" </dev/null | xargs rm -f
+ fi
+ sed -i "/^$username\s*ALL=(ALL)/d" /etc/sudoers
;;
5)
read -e -p "请输入要删除的用户名: " username
- # 删除用户及其主目录
userdel -r "$username"
;;
@@ -11430,6 +20550,7 @@ EOF
break # 跳出循环,退出菜单
;;
esac
+
done
;;
@@ -11617,7 +20738,7 @@ EOF
echo "选择更新源区域"
echo "接入LinuxMirrors切换系统更新源"
echo "------------------------"
- echo "1. 中国大陆【默认】 2. 中国大陆【教育网】 3. 海外地区"
+ echo "1. 中国大陆【默认】 2. 中国大陆【教育网】 3. 海外地区 4. 智能切换更新源"
echo "------------------------"
echo "0. 返回上一级选单"
echo "------------------------"
@@ -11636,6 +20757,11 @@ EOF
send_stats "海外源"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --abroad
;;
+ 4)
+ send_stats "智能切换更新源"
+ switch_mirror false false
+ ;;
+
*)
echo "已取消"
;;
@@ -11747,63 +20873,7 @@ EOF
;;
22)
- root_use
- send_stats "ssh防御"
- while true; do
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
- clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "SSH防御程序 $check_docker"
- echo "fail2ban是一个SSH防止暴力破解工具"
- echo "官网介绍: ${gh_proxy}github.com/fail2ban/fail2ban"
- echo "------------------------"
- echo "1. 安装防御程序"
- echo "------------------------"
- echo "2. 查看SSH拦截记录"
- echo "3. 日志实时监控"
- echo "------------------------"
- echo "9. 卸载防御程序"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "请输入你的选择: " sub_choice
- case $sub_choice in
- 1)
- install_docker
- f2b_install_sshd
-
- cd ~
- f2b_status
- break_end
- ;;
- 2)
- echo "------------------------"
- f2b_sshd
- echo "------------------------"
- break_end
- ;;
- 3)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
- break
- ;;
- 9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
- echo "Fail2Ban防御程序已卸载"
- ;;
- *)
- break
- ;;
- esac
- fi
- done
+ fail2ban_panel
;;
@@ -11881,53 +20951,7 @@ EOF
24)
-
- root_use
- send_stats "私钥登录"
- while true; do
- clear
- echo "ROOT私钥登录模式"
- echo "视频介绍: https://www.bilibili.com/video/BV1Q4421X78n?t=209.4"
- echo "------------------------------------------------"
- echo "将会生成密钥对,更安全的方式SSH登录"
- echo "------------------------"
- echo "1. 生成新密钥 2. 导入已有密钥 3. 查看本机密钥"
- echo "------------------------"
- echo "0. 返回上一级选单"
- echo "------------------------"
- read -e -p "请输入你的选择: " host_dns
-
- case $host_dns in
- 1)
- send_stats "生成新密钥"
- add_sshkey
- break_end
-
- ;;
- 2)
- send_stats "导入已有公钥"
- import_sshkey
- break_end
-
- ;;
- 3)
- send_stats "查看本机密钥"
- echo "------------------------"
- echo "公钥信息"
- cat ~/.ssh/authorized_keys
- echo "------------------------"
- echo "私钥信息"
- cat ~/.ssh/sshkey
- echo "------------------------"
- break_end
-
- ;;
- *)
- break # 跳出循环,退出菜单
- ;;
- esac
- done
-
+ sshkey_panel
;;
25)
@@ -12052,15 +21076,31 @@ EOF
39)
clear
- send_stats "命令行历史记录"
- bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+ linux_fav
+ ;;
+
+ 40)
+ clear
+ net_menu
;;
41)
+ clear
+ log_menu
+ ;;
+
+ 42)
+ clear
+ env_menu
+ ;;
+
+
+ 61)
clear
send_stats "留言板"
- echo "科技lion留言板已迁移至官方社区!请在官方社区进行留言噢!"
- echo "https://bbs.kejilion.pro/"
+ echo "访问科技lion官方留言板,您对脚本有任何想法欢迎留言交流!"
+ echo "https://board.kejilion.pro"
+ echo "公共密码: kejilion.sh"
;;
66)
@@ -12070,16 +21110,18 @@ EOF
echo "一条龙系统调优"
echo "------------------------------------------------"
echo "将对以下内容进行操作与优化"
- echo "1. 更新系统到最新"
+ echo "1. 优化系统更新源,更新系统到最新"
echo "2. 清理系统垃圾文件"
echo -e "3. 设置虚拟内存${gl_huang}1G${gl_bai}"
echo -e "4. 设置SSH端口号为${gl_huang}5522${gl_bai}"
- echo -e "5. 开放所有端口"
- echo -e "6. 开启${gl_huang}BBR${gl_bai}加速"
- echo -e "7. 设置时区到${gl_huang}上海${gl_bai}"
- echo -e "8. 自动优化DNS地址${gl_huang}海外: 1.1.1.1 8.8.8.8 国内: 223.5.5.5 ${gl_bai}"
- echo -e "9. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
- echo -e "10. Linux系统内核参数优化切换到${gl_huang}均衡优化模式${gl_bai}"
+ echo -e "5. 启动fail2ban防御SSH暴力破解"
+ echo -e "6. 开放所有端口"
+ echo -e "7. 开启${gl_huang}BBR${gl_bai}加速"
+ echo -e "8. 设置时区到${gl_huang}上海${gl_bai}"
+ echo -e "9. 自动优化DNS地址${gl_huang}海外: 1.1.1.1 8.8.8.8 国内: 223.5.5.5 ${gl_bai}"
+ echo -e "10. 设置网络为${gl_huang}ipv4优先${gl_bai}"
+ echo -e "11. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "12. Linux系统内核参数优化${gl_huang}自动根据网络环境调优${gl_bai}"
echo "------------------------------------------------"
read -e -p "确定一键保养吗?(Y/N): " choice
@@ -12088,58 +21130,53 @@ EOF
clear
send_stats "一条龙调优启动"
echo "------------------------------------------------"
+ switch_mirror false false
linux_update
- echo -e "[${gl_lv}OK${gl_bai}] 1/10. 更新系统到最新"
+ echo -e "[${gl_lv}OK${gl_bai}] 1/12. 更新系统到最新"
echo "------------------------------------------------"
linux_clean
- echo -e "[${gl_lv}OK${gl_bai}] 2/10. 清理系统垃圾文件"
+ echo -e "[${gl_lv}OK${gl_bai}] 2/12. 清理系统垃圾文件"
echo "------------------------------------------------"
add_swap 1024
- echo -e "[${gl_lv}OK${gl_bai}] 3/10. 设置虚拟内存${gl_huang}1G${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 3/12. 设置虚拟内存${gl_huang}1G${gl_bai}"
echo "------------------------------------------------"
- local new_port=5522
- new_ssh_port
- echo -e "[${gl_lv}OK${gl_bai}] 4/10. 设置SSH端口号为${gl_huang}5522${gl_bai}"
+ new_ssh_port 5522
+ echo -e "[${gl_lv}OK${gl_bai}] 4/12. 设置SSH端口号为${gl_huang}5522${gl_bai}"
+ echo "------------------------------------------------"
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ echo -e "[${gl_lv}OK${gl_bai}] 5/12. 启动fail2ban防御SSH暴力破解"
+
echo "------------------------------------------------"
- echo -e "[${gl_lv}OK${gl_bai}] 5/10. 开放所有端口"
+ echo -e "[${gl_lv}OK${gl_bai}] 6/12. 开放所有端口"
echo "------------------------------------------------"
bbr_on
- echo -e "[${gl_lv}OK${gl_bai}] 6/10. 开启${gl_huang}BBR${gl_bai}加速"
+ echo -e "[${gl_lv}OK${gl_bai}] 7/12. 开启${gl_huang}BBR${gl_bai}加速"
echo "------------------------------------------------"
set_timedate Asia/Shanghai
- echo -e "[${gl_lv}OK${gl_bai}] 7/10. 设置时区到${gl_huang}上海${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 8/12. 设置时区到${gl_huang}上海${gl_bai}"
echo "------------------------------------------------"
- local country=$(curl -s ipinfo.io/country)
- if [ "$country" = "CN" ]; then
- local dns1_ipv4="223.5.5.5"
- local dns2_ipv4="183.60.83.19"
- local dns1_ipv6="2400:3200::1"
- local dns2_ipv6="2400:da00::6666"
- else
- local dns1_ipv4="1.1.1.1"
- local dns2_ipv4="8.8.8.8"
- local dns1_ipv6="2606:4700:4700::1111"
- local dns2_ipv6="2001:4860:4860::8888"
- fi
-
- set_dns
- echo -e "[${gl_lv}OK${gl_bai}] 8/10. 自动优化DNS地址${gl_huang}${gl_bai}"
+ auto_optimize_dns
+ echo -e "[${gl_lv}OK${gl_bai}] 9/12. 自动优化DNS地址${gl_huang}${gl_bai}"
+ echo "------------------------------------------------"
+ prefer_ipv4
+ echo -e "[${gl_lv}OK${gl_bai}] 10/12. 设置网络为${gl_huang}ipv4优先${gl_bai}}"
echo "------------------------------------------------"
install_docker
install wget sudo tar unzip socat btop nano vim
- echo -e "[${gl_lv}OK${gl_bai}] 9/10. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 11/12. 安装基础工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
echo "------------------------------------------------"
- echo "------------------------------------------------"
- optimize_balanced
- echo -e "[${gl_lv}OK${gl_bai}] 10/10. Linux系统内核参数优化"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ echo -e "[${gl_lv}OK${gl_bai}] 12/12. Linux系统内核参数优化"
echo -e "${gl_lv}一条龙系统调优已完成${gl_bai}"
;;
@@ -12406,26 +21443,13 @@ linux_file() {
continue
fi
- read -e -p "请输入远端服务器IP: " remote_ip
- if [ -z "$remote_ip" ]; then
- echo "错误: 请输入远端服务器IP。"
- send_stats "传送文件失败: 未输入远端服务器IP"
- continue
- fi
-
- read -e -p "请输入远端服务器用户名 (默认root): " remote_user
- remote_user=${remote_user:-root}
-
- read -e -p "请输入远端服务器密码: " -s remote_password
- echo
- if [ -z "$remote_password" ]; then
- echo "错误: 请输入远端服务器密码。"
- send_stats "传送文件失败: 未输入远端服务器密码"
- continue
- fi
+ kj_ssh_read_host_user_port "请输入远端服务器IP: " "请输入远端服务器用户名 (默认root): " "请输入登录端口 (默认22): " "root" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local remote_user="$KJ_SSH_USER"
+ local remote_port="$KJ_SSH_PORT"
- read -e -p "请输入登录端口 (默认22): " remote_port
- remote_port=${remote_port:-22}
+ kj_ssh_read_password "请输入远端服务器密码: "
+ local remote_password="$KJ_SSH_PASSWORD"
# 清除已知主机的旧条目
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
@@ -12659,7 +21683,7 @@ echo -e "${gl_kjlan}B站: ${gl_bai}https://b23.tv/2mqnQyh ${gl_kjla
echo -e "${gl_kjlan}官网: ${gl_bai}https://kejilion.pro/ ${gl_kjlan}导航: ${gl_bai}https://dh.kejilion.pro/${gl_bai}"
echo -e "${gl_kjlan}博客: ${gl_bai}https://blog.kejilion.pro/ ${gl_kjlan}软件中心: ${gl_bai}https://app.kejilion.pro/${gl_bai}"
echo "------------------------"
-echo -e "${gl_kjlan}脚本官网: ${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub地址: ${gl_bai}https://github.com/kejilion/sh${gl_bai}"
+echo -e "${gl_kjlan}脚本官网: ${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub地址: ${gl_bai}${gh_https_url}github.com/kejilion/sh${gl_bai}"
echo "------------------------"
echo ""
}
@@ -12667,6 +21691,64 @@ echo ""
+games_server_tools() {
+
+ while true; do
+ clear
+ echo -e "游戏开服脚本合集"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}幻兽帕鲁开服脚本"
+ echo -e "${gl_kjlan}2. ${gl_bai}我的世界开服脚本"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主菜单"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "请输入你的选择: " sub_choice
+
+ case $sub_choice in
+
+ 1) send_stats "幻兽帕鲁开服脚本" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
+ exit
+ ;;
+ 2) send_stats "我的世界开服脚本" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/mc.sh ; chmod +x mc.sh ; ./mc.sh
+ exit
+ ;;
+
+ 0)
+ kejilion
+ ;;
+
+ *)
+ echo "无效的输入!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
kejilion_update() {
@@ -12679,10 +21761,13 @@ while true; do
echo "全部日志: ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt"
echo "------------------------"
- curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
- local sh_v_new=$(curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | cut -d '"' -f 2)
+ curl -s --max-time 15 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
+ # 只下载前5行获取版本号,避免下载整个脚本
+ local sh_v_new=$(curl -s --max-time 15 -r 0-200 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | head -1 | cut -d '"' -f 2)
- if [ "$sh_v" = "$sh_v_new" ]; then
+ if [ -z "$sh_v_new" ]; then
+ echo -e "${gl_hong}无法获取最新版本信息,请检查网络连接${gl_bai}"
+ elif [ "$sh_v" = "$sh_v_new" ]; then
echo -e "${gl_lv}你已经是最新版本!${gl_huang}v$sh_v${gl_bai}"
send_stats "脚本已经最新了,无需更新"
else
@@ -12708,37 +21793,76 @@ while true; do
case "$choice" in
1)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
+ local download_url
if [ "$country" = "CN" ]; then
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh && chmod +x kejilion.sh
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh"
+ else
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh"
+ fi
+
+ # 备份当前脚本
+ cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null
+
+ # 下载到临时文件,校验后再替换
+ local tmp_file=$(mktemp ~/kejilion_tmp.XXXXXX)
+ if curl -sS --max-time 60 --fail -o "$tmp_file" "$download_url" && \
+ [ -s "$tmp_file" ] && \
+ head -1 "$tmp_file" | grep -q '^#!/bin/bash'; then
+ chmod +x "$tmp_file"
+ mv -f "$tmp_file" ~/kejilion.sh
+ canshu_v6
+ CheckFirstRun_true
+ yinsiyuanquan2
+ cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
+ echo -e "${gl_lv}脚本已更新到最新版本!${gl_huang}v$sh_v_new${gl_bai}"
+ send_stats "脚本已经最新$sh_v_new"
else
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh
+ rm -f "$tmp_file"
+ # 恢复备份
+ if [ -f ~/kejilion.sh.bak ]; then
+ mv -f ~/kejilion.sh.bak ~/kejilion.sh
+ fi
+ echo -e "${gl_hong}更新失败!下载出错或文件校验不通过,已恢复原版本${gl_bai}"
+ send_stats "脚本更新失败"
fi
- canshu_v6
- CheckFirstRun_true
- yinsiyuanquan2
- cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
- echo -e "${gl_lv}脚本已更新到最新版本!${gl_huang}v$sh_v_new${gl_bai}"
- send_stats "脚本已经最新$sh_v_new"
break_end
~/kejilion.sh
exit
;;
2)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
local ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
+ local cron_proxy cron_sed_cmd
if [ "$country" = "CN" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ~/kejilion.sh"
elif [ -n "$ipv6_address" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ~/kejilion.sh"
else
- SH_Update_task="curl -sS -O https://raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh"
+ cron_proxy="https://"
+ cron_sed_cmd=""
+ fi
+
+ # 构建健壮的自动更新命令:下载到临时文件 → 校验 → 备份 → 替换 → 恢复本地设置 → 部署
+ SH_Update_task="cd ~ && tmp=\$(mktemp ~/kejilion_tmp.XXXXXX) && curl -sS --max-time 60 --fail -o \"\$tmp\" ${cron_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && [ -s \"\$tmp\" ] && head -1 \"\$tmp\" | grep -q '^#!/bin/bash' && cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null && chmod +x \"\$tmp\" && mv -f \"\$tmp\" ~/kejilion.sh"
+ # 追加设置恢复
+ if [ -n "$cron_sed_cmd" ]; then
+ SH_Update_task="$SH_Update_task && $cron_sed_cmd"
fi
+ # 从旧脚本恢复 permission_granted 和 ENABLE_STATS 设置
+ SH_Update_task="$SH_Update_task && grep -q 'permission_granted=\"true\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/permission_granted=\"false\"/permission_granted=\"true\"/' ~/kejilion.sh; grep -q 'ENABLE_STATS=\"false\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/ENABLE_STATS=\"true\"/ENABLE_STATS=\"false\"/' ~/kejilion.sh"
+ # 部署到 /usr/local/bin/k 和 /usr/bin/k
+ SH_Update_task="$SH_Update_task; cp -f ~/kejilion.sh /usr/local/bin/k 2>/dev/null; ln -sf /usr/local/bin/k /usr/bin/k 2>/dev/null"
+ # 下载失败时清理临时文件
+ SH_Update_task="$SH_Update_task || rm -f \"\$tmp\" 2>/dev/null"
+
check_crontab_installed
(crontab -l | grep -v "kejilion.sh") | crontab -
- # (crontab -l 2>/dev/null; echo "0 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c \"$SH_Update_task\"") | crontab -
+ (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c '$SH_Update_task'") | crontab -
echo -e "${gl_lv}自动更新已开启,每天凌晨2点脚本会自动更新!${gl_bai}"
send_stats "开启脚本自动更新"
break_end
@@ -12787,8 +21911,7 @@ echo -e "${gl_kjlan}12. ${gl_bai}后台工作区"
echo -e "${gl_kjlan}13. ${gl_bai}系统工具"
echo -e "${gl_kjlan}14. ${gl_bai}服务器集群控制"
echo -e "${gl_kjlan}15. ${gl_bai}广告专栏"
-echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}p. ${gl_bai}幻兽帕鲁开服脚本"
+echo -e "${gl_kjlan}16. ${gl_bai}游戏开服脚本合集"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}00. ${gl_bai}脚本更新"
echo -e "${gl_kjlan}------------------------${gl_bai}"
@@ -12797,7 +21920,7 @@ echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "请输入你的选择: " choice
case $choice in
- 1) linux_ps ;;
+ 1) linux_info ;;
2) clear ; send_stats "系统更新" ; linux_update ;;
3) clear ; send_stats "系统清理" ; linux_clean ;;
4) linux_tools ;;
@@ -12814,10 +21937,7 @@ case $choice in
13) linux_Settings ;;
14) linux_cluster ;;
15) kejilion_Affiliates ;;
- p) send_stats "幻兽帕鲁开服脚本" ; cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
- exit
- ;;
+ 16) games_server_tools ;;
00) kejilion_update ;;
0) clear ; exit ;;
*) echo "无效的输入!" ;;
@@ -12856,6 +21976,7 @@ echo "软件状态查看 k status sshd | k 状态 sshd "
echo "软件开机启动 k enable docker | k autostart docke | k 开机启动 docker "
echo "域名证书申请 k ssl"
echo "域名证书到期查询 k ssl ps"
+echo "docker管理平面 k docker"
echo "docker环境安装 k docker install |k docker 安装"
echo "docker容器管理 k docker ps |k docker 容器"
echo "docker镜像管理 k docker img |k docker 镜像"
@@ -12864,12 +21985,20 @@ echo "LDNMP缓存清理 k web cache"
echo "安装WordPress k wp |k wordpress |k wp xxx.com"
echo "安装反向代理 k fd |k rp |k 反代 |k fd xxx.com"
echo "安装负载均衡 k loadbalance |k 负载均衡"
+echo "安装L4负载均衡 k stream |k L4负载均衡"
echo "防火墙面板 k fhq |k 防火墙"
echo "开放端口 k dkdk 8080 |k 打开端口 8080"
echo "关闭端口 k gbdk 7800 |k 关闭端口 7800"
echo "放行IP k fxip 127.0.0.0/8 |k 放行IP 127.0.0.0/8"
echo "阻止IP k zzip 177.5.25.36 |k 阻止IP 177.5.25.36"
-
+echo "命令收藏夹 k fav | k 命令收藏夹"
+echo "应用市场管理 k app"
+echo "应用编号快捷管理 k app 26 | k app 1panel | k app npm"
+echo "fail2ban管理 k fail2ban | k f2b"
+echo "显示系统信息 k info"
+echo "ROOT密钥管理 k sshkey"
+echo "SSH公钥导入(URL) k sshkey "
+echo "SSH公钥导入(GitHub) k sshkey github "
}
@@ -12944,6 +22073,7 @@ else
echo "已阻止IP+端口访问该服务"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
;;
@@ -12952,6 +22082,11 @@ else
ldnmp_Proxy_backend
;;
+
+ stream|L4负载均衡)
+ ldnmp_Proxy_backend_stream
+ ;;
+
swap)
shift
send_stats "快速设置虚拟内存"
@@ -13002,6 +22137,10 @@ else
iptables_panel
;;
+ 命令收藏夹|fav)
+ linux_fav
+ ;;
+
status|状态)
shift
send_stats "软件状态查看"
@@ -13061,7 +22200,7 @@ else
docker_image
;;
*)
- k_info
+ linux_docker
;;
esac
;;
@@ -13081,6 +22220,59 @@ else
fi
;;
+
+ app)
+ shift
+ send_stats "应用$@"
+ linux_panel "$@"
+ ;;
+
+ claw|oc|OpenClaw)
+ moltbot_menu
+ ;;
+
+ info)
+ linux_info
+ ;;
+
+ fail2ban|f2b)
+ fail2ban_panel
+ ;;
+
+
+ sshkey)
+
+ shift
+ case "$1" in
+ "" )
+ # sshkey → 交互菜单
+ send_stats "SSHKey 交互菜单"
+ sshkey_panel
+ ;;
+ github )
+ shift
+ send_stats "从 GitHub 导入 SSH 公钥"
+ fetch_github_ssh_keys "$1"
+ ;;
+ http://*|https://* )
+ send_stats "从 URL 导入 SSH 公钥"
+ fetch_remote_ssh_keys "$1"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa* )
+ send_stats "公钥直接导入"
+ import_sshkey "$1"
+ ;;
+ * )
+ echo "错误:未知参数 '$1'"
+ echo "用法:"
+ echo " k sshkey 进入交互菜单"
+ echo " k sshkey \"\" 直接导入 SSH 公钥"
+ echo " k sshkey 从 URL 导入 SSH 公钥"
+ echo " k sshkey github 从 GitHub 导入 SSH 公钥"
+ ;;
+ esac
+
+ ;;
*)
k_info
;;
diff --git a/kejilion_sh_log.txt b/kejilion_sh_log.txt
index 634a8da6e..08bfc05f1 100644
--- a/kejilion_sh_log.txt
+++ b/kejilion_sh_log.txt
@@ -1249,5 +1249,273 @@ LDNMP建站flarum论坛安装时增加插件市场的安装,后期可以在后
应用市场新增Linkwarden书签管理器工具的安装及使用。
应用市场新增JitsiMeet视频会议具的安装及使用。
------------------------
-
+2025-08-10 v4.0.6
+系统工具重装系统新增Debian13的重装选项。Debian13 性能 安全 稳定全面提升。
+应用市场新增gpt-load高性能AI透明代理的安装及使用。
+应用市场新增komari服务器监控的安装及使用,平替哪吒监控。
+------------------------
+2025-08-13 v4.0.7
+应用市场新增Wallos个人财务管理工的安装及使用,
+应用市场新增immich图片视频管理器的安装及使用,
+应用市场新增jellyfin媒体管理系统的安装及使用,
+应用市场新增SyncTV一起看片神器的安装及使用,
+应用市场新增Owncast自托管直播平台的安装及使用,
+------------------------
+2025-08-14 v4.0.8
+应用市场增加快速启动方式 命令行输入 k app 即可启动应用市场。
+应用市场增加应用编号快速启动 k app 13 就是13号应用启动,也可以输入应用名称启动 k app dpanel。
+支持远程命令安装和管理应用,bash <(curl -sL kejilion.sh) app moontv | bash <(curl -sL kejilion.sh) app 1panel。
+------------------------
+2025-08-18 v4.0.9
+应用市场增加全部应用数据备份和还原的功能。支持迁移到其他服务器的操作。
+-全部的应用备份还原不包含宝塔1panel等建站面板的数据。
+应用市场gpt-load安装时修改成自定义登录密钥。
+应用市场新增FileCodeBox文件快递的安装及使用。
+应用市场新增matrix去中心化聊天协议安装及使用。
+应用市场重构moontv,采用全新架构安装及使用。
+应用市场Ubuntu等远程桌面程序允许自定义用户名和密码进行安装及使用。
+------------------------
+2025-08-21 v4.0.10
+应用市场新增gitea私有代码仓库的安装及使用,
+应用市场新增filebrowser文件管理器的安装及使用,
+------------------------
+2025-08-24 v4.1.0
+docker管理增加全局备份还原迁移功能。
+修复部分应用的部署不成功问题,
+------------------------
+2025-08-25 v4.1.1
+应用市场新增Dufs极简静态文件服务器的安装及使用。
+应用市场新增gopeed分布式高速下载工具的安装及使用。
+应用市场新增paperless文档管理平台的安装及使用。
+------------------------
+2025-08-27 v4.1.2
+应用市场新增WireGuard异地隧道组网工具的安装及使用。
+系统工具中一键重装系统新增Windows sreve 2025的安装。
+------------------------
+2025-09-03 v4.1.3
+应用市场尝试适配群晖系统文件结构。
+应用安装时的存储空间检测进行优化,可以更精准检测应用目录大小。
+应用市场新增2FAuth自托管二步验证器的安装及使用。
+系统工具IPV4和IPV6优先进行优化,不再粗暴关闭V6来实现V4优先了。
+------------------------
+2025-09-10 v4.1.4
+系统工具中DNS优化支持文件锁定功能,解决重启后文件复原问题。
+应用市场新增了DSM群晖虚拟机的安装及使用。
+系统工具中高级防火墙支持同时对多个国家地区的IP进行放行或封禁。
+系统工具中留言板功能重新上线,您对脚本有任何想法欢迎留言交流!
+------------------------
+2025-09-15 v4.1.5
+k快捷键增加 k docker 命令行输入后能够快速到达docker管理平面。
+docker,站点等数据迁移时支持用户自定义输入SSH端口号迁移数据。
+k快捷命令增加了 k info 系统信息快速查询。
+------------------------
+2025-09-25 v4.1.6
+应用市场新增了Syncthing开源的点对点文件同步工具的安装及使用。
+甲骨文云合集中增加大佬的R探长开机脚本,非常感谢大佬脚本。
+又来修复SSH防御fail2ban加载ssh规则报错的问题。
+------------------------
+2025-09-30 v4.1.7
+应用市场新增了MoneyPrinterTurbo视频生成器的安装及使用。
+------------------------
+2025-10-06 v4.1.8
+应用市场FileCodeBox文件快递增加管理员入口的提示路径与密码。
+应用市场新增了VoceChat多人聊天系统的安装及使用。
+------------------------
+2025-10-09 v4.1.9
+LDNMP建站中将更换站点域名功能调整成克隆站点功能,这样可以保留老站点,让用户自己处决。
+应用市场新增了Umami网站统计工具的安装及使用。
+应用市场优化了部分应用因网络原因无法安装的问题。
+------------------------
+2025-10-21 v4.2.0
+LDNMP建站和应用市场新增nginx的stream四层代理转发工具。
+应用市场新增了思源笔记的安装及使用。
+应用市场新增了开源Drawnix白板工具的安装及使用。
+应用市场新增了PanSou网盘搜索工具的安装及使用。
+优化LDNMP环境中nginx将根据核心数优化并发连接数。
+------------------------
+2025-10-30 v4.2.1
+安装LDNMP/NGINX建站环境是检测网络进行dns优化IP优先调整,解决因网络问题无法安装环境的问题。
+国内安装docker环境时的加速站列表进行更新。
+应用市场中PanSou网盘搜索增加更多搜索源。
+------------------------
+2025-11-06 v4.2.2
+系统工具中一条龙优化功能新增自动ipv4优先,
+系统工具中一条龙优化功能新增自动开启fail2ban防御SSH暴力破解。
+应用市场新增了LangBot开源聊天机器人的安装及使用。
+LDNMP安装环境时对mysql进行优化。
+------------------------
+2025-11-10 v4.2.3
+测试脚本合集中添加nodequality融合测试脚本。
+系统工具更换系统更新源新增智能切换更新源功能,识别国内自动切换华为源。
+------------------------
+2025-11-13 v4.2.4
+应用市场新增了karakeep书签管理的安装及使用。
+应用市场新增了zfile网盘的安装及使用。
+自动优化dns地址修复部分机型写入为空的问题。
+系统工具ssh防御修复个别系统安装后启动失败的问题。
+LDNMP环境中redis去除了不必要的清理机制。
+LDNMP环境NGINX环境优化安装流程,提升环境安装稳定性。
+------------------------
+2025-11-17 v4.2.5
+新增我的世界开服脚本工具,感谢阿卡林刘大佬进行适配,有我的世界开服需求的可以试试。
+主菜单新增游戏开服脚本合集,将幻兽帕鲁和我的世界的开服脚本加入合集。
+系统查询中添加TCP|UDP当前连接数显示。
+------------------------
+2025-11-19 v4.2.6
+安装docker环境采用更加成熟的linuxmirrors解决方案。提升安装兼容性和稳定性。
+系统工具一条龙优化增加了对系统源的优化,并将系统更新到最新。
+优化LDNMP环境中高性能模式无法切换的问题进行修复。
+LDNMP初始化时新增URL绑定,提升wordpress安装向导的稳定性。
+反向代理IP+端口功能优化,输入反代IP时回车可默认选择本机IP 127.0.0.1省去额外输入的麻烦。
+应用市场komari监控定义时区为上海,修复时间显示问题。
+------------------------
+2025-12-03 v4.2.7
+LDNMP建站为flarum论坛安装时增加了更多实用插件扩展。
+应用市场Dify知识库修复创建管理员出错的问题。官方API权限问题已擦屁股。
+对申请证书时80端口防火墙规则进行优化,提升申请证书成功率。
+------------------------
+2025-12-15 v4.2.8
+nginx反向代理优化,定义upstream和keepalive提升后端连接性能和稳定性。
+去掉docker自动化中无用的-it参数,提升运行稳定性,遵行最佳实践。
+添加kl快捷命令 k f2b 可以快速启动fail2ban管理工具。
+------------------------
+2025-12-19 v4.2.9
+nginx反向代理和负载均衡支持IP自定义监听端口功能,可实现反代内部IP加端口到网关的效果,
+SSL申请失败后追加选项菜单,你可以继续申请,使用HTTP访问或者中断申请。
+应用市场新增了ConvertX全能文件转换工具的安装及使用。
+------------------------
+2025-12-24 v4.2.10
+SSL申请失败后追加导入已有证书选项,你可以导入域名有效证书使用了(如CF15年证书)。
+应用市场新增了lucky大内网反向代理工具的安装及使用。
+应用市场新增了Firefox浏览器的安装及使用。
+------------------------
+2025-12-25 v4.3.0
+应用市场支持第三方入驻功能,开发者可以将自己的项目加入到脚本中让大家使用。
+入驻指南: https://github.com/kejilion/sh/tree/main/apps
+------------------------
+2026-01-06 v4.3.1
+应用市场应用发现新版本逻辑进行优化可以兼容github.io的新版本发现。
+应用市场memos拉取镜像源进行调整,采用官方更推荐的做法。
+应用市场优化部分项目的git更新机制,解决国内拉取卡顿的问题。
+基础工具中添加了目前流行的opencode编程AI助手可以在服务器上直接AI可视化开发编程。
+------------------------
+2026-01-13 v4.3.2
+对LDNMP站点和NGINX反代站点增加不停机续签域名SSL证书机制,老配置还是走端口申请证书。
+修复docker管理器无法进入容器的问题。
+------------------------
+2026-01-16 v4.3.3
+应用市场安装时添加了访问端口冲突检测机制。发现端口冲突提示用户换一个端口。
+------------------------
+2026-01-21 v4.3.4
+系统工具中SSH密钥登录新增GitHub远端公钥导入功能。
+站点反向代理后兼容对后端容器为host网络的服务IP+端口的处理。
+系统工具ROOT密钥登录功能增加了启用和未启用的状态显示功能。
+系统工具ROOT密钥登录功能增加了从URL导入公钥的功能。
+系统工具ROOT密钥登录功能交互菜单和表述进行优化。
+新增k sshkey url 快捷命令 支持快速导入SSH公钥并开启SSH密钥登录模式。
+------------------------
+2026-01-26 v4.3.5
+系统工具中新增网卡管理工具,快速查看网卡信息以及对网卡进行基础操作,
+系统工具中新增系统日志管理工具,快速查看日志占用,查看各类服务日志,清理日志等基础操作,
+申请证书失败后暂时移除不带证书HTTP访问会选项,该选项存在逻辑漏洞,导致站点异常。
+优化用户SSH登录权限配置,提升SSH连接稳定性。
+重构 SSH 密钥管理功能,使其支持自定义 SSH 密钥根目录。更新用户创建流程,以简化SSH密钥导入和权限管理。
+提升普通用户sudo环境下的k快捷键兼容性和可用性。
+基础组件功能增加了各个组件的安装状态。
+------------------------
+2026-01-28 v4.3.6
+调整正在处理事件时的等待文字的颜色方案,与脚本配色更统一。
+优化应用市场更新应用列表时的体验。
+优化系统信息查询时的等待体验。
+应用市场增加了最近比较火的ClawdBot/Moltbot机器人项目的安装与使用。
+------------------------
+2026-01-30 v4.3.7
+系统工具中新增系统变量管理工具,快速查看,编辑,刷新变量等操作,
+应用市场对ClawdBot/Moltbot机器人项目的安装环境进行优化,提高安装成功率。
+应用市场对ClawdBot/Moltbot机器人项目保活进行优化每分钟检测进程健康状态,如果异常自动重载。
+应用市场对ClawdBot/Moltbot机器人项目支持添加通用模型API, deepseek 硅基流动 测试有效!
+应用市场对ClawdBot/Moltbot机器人项目版本更新功能进行修复。
+应用市场对ClawdBot/Moltbot机器人项目版增加配置文件编辑功能,支持nano直接编辑。
+------------------------
+2026-02-03 v4.3.8
+应用市场对ClawdBot/Moltbot机器人项目版增加配置文件编辑功能,支持nano直接编辑。
+应用市场对ClawdBot/Moltbot机器人项目版增安装插件功能,可以安装飞书插件了。
+应用市场对ClawdBot/Moltbot机器人项目版增安装技能功能。
+应用市场对ClawdBot/Moltbot机器人项目加入新版本检测提醒功能。
+应用市场对ClawdBot/Moltbot机器人项目修复了卸载后仍然显示已安装的问题。
+------------------------
+2026-02-05 v4.3.9
+应用市场对ClawdBot/Moltbot机器人项目新增重置向导和健康检测功能。
+系统工具磁盘管理新增分区挂载持久化机制。
+应用市场对ClawdBot/Moltbot机器人项目优化技能和插件安装体验,修复安装失败问题。
+应用市场对ClawdBot/Moltbot机器人项目新增webui访问设置功能,可以用域名访问后台。
+------------------------
+2026-02-11 v4.3.10
+应用市场对ClawdBot/Moltbot机器人项目添加API支持批量导入模型。
+------------------------
+2026-02-22 v4.4.0
+应用市场对ClawdBot/Moltbot机器人项目使用原生启动保活机制,之前原生不稳定。
+应用市场对ClawdBot/Moltbot机器人项目日志选项进行优化,看日志同时显示运行状态信息。
+------------------------
+2026-03-01 v4.4.1
+系统工具内核调优功能全面重构,增加持久化机制。
+系统工具内核调优功能增加自动内核调优选项,会根据网络智能调整系统参数。
+系统工具一条龙调优原本均衡内核调优现在改为自动内核调优。
+应用市场对ClawdBot/Moltbot机器人项目添加API时对模型的最大输出输入等参数进行优化,让大模型更稳定。
+应用市场对ClawdBot/Moltbot机器人项目新增飞书和WhatsApp机器人扫码对接功能。
+------------------------
+2026-03-03 v4.4.2
+应用市场中OpenClaw管理,WebUI访问设置功能修复绑定域名不出现设备ID的问题。
+应用市场中OpenClaw管理,优化国内环境安装时的兼容性。
+应用市场中OpenClaw管理,修复卸载后配置目录残留的问题,卸载不彻底的问题。
+------------------------
+2026-03-06 v4.4.3
+应用市场中OpenClaw管理,新增TUI终端命令行聊天窗口,临时调试任务执行更方面。
+应用市场中OpenClaw管理,安装后自动调整权限为full,可以调用内部程序自动化。
+SSH防御菜单新增基础参数配置功能,支持交互式设置封禁时长、时间窗口、重试次数。
+SSH防御菜单新增配置文件编辑功能,使用nano直接编辑jail配置。
+SSH防御优化Alpine系统jail名称检测,默认使用sshd,仅检测到自定义规则时切换alpine-sshd。
+SSH防御优化Debian/Ubuntu系统,自动补充logpath配置避免minimal镜像启动失败。
+应用市场中OpenClaw管理,安装流程新增会话连续性策略配置:私聊7天连续、线程1天、群聊2小时,避免重置误判为“失忆”。
+------------------------
+2026-03-09 v4.4.5
+应用市场中OpenClaw管理,健康检测后支持API模型自动一致性同步,新增模型自动写入defaults.models,并维护默认模型与回退链路安全切换。
+应用市场中OpenClaw管理,API管理菜单支持查看列表、按提供商同步模型、删除API,且对/models探测失败增加3次重试与人工确认删除机制。
+应用市场中OpenClaw管理,添加了备份还原功能,包含记忆和完整项目。
+应用市场中OpenClaw管理,新增独立且中文化的记忆管理控制台,支持方案自动探测、索引状态查看与重复收录一键修复。
+------------------------
+2026-03-12 v4.4.6
+应用市场中OpenClaw管理,新增记忆管理控制台,支持索引状态、索引更新、文件查看、方案切换与重复索引修复。
+应用市场中OpenClaw管理,API新增自动探测并优先使用/v1/responses协议,避免legacy /v1/chat/completions报错。
+应用市场中OpenClaw管理,API模型同步一致性输出增强,逐行显示新增/删除模型名称。
+------------------------
+2026-03-13 v4.4.7
+OpenClaw模型管理新增gum交互式换模型(可搜索/橙色主题/显示可用模型数与当前默认)。
+------------------------
+2026-03-19 v4.4.8
+OpenClaw模型管理新增换模型前最小可用性检测,显示状态、延迟与返回摘要。
+OpenClaw模型探测逻辑优化,优先测试 /v1/responses,不通时再回退 /v1/chat/completions。
+OpenClaw模型管理优化gum异常时的兼容性,自动回退旧版输入流程。
+OpenClaw API同步一致性输出增强,汇总显示顺序调整为新增、删除、当前。
+OpenClaw模型管理交互细节优化,提升确认流程稳定性与可用性。
+------------------------
+2026-03-23 v4.4.9
+OpenClaw菜单新增 k claw 快捷入口。
+OpenClaw菜单新增权限管理,多智能体管理。
+OpenClaw菜单对接机器人管理 新增微信官方接入与状态显示。
+OpenClaw菜单对接机器人管理 调整了飞书接入方式 用官方接入方式。
+------------------------
+2026-04-09 v4.4.10
+应用市场新增 Hermes Agent 项目安装与管理的工具。快捷命令 k app 115。
+------------------------
+2026-05-02 v4.5.0
+系统工具BBRv3更新linux-xanmod官方部署方法。
+系统工具一键重装系统功能新增Ubuntu26.04安装和Fedora44/43版本安装。
+------------------------
+2026-05-20 v4.5.1
+应用市场 Hermes 管理增加新版本检测和API模型列表一致性同步功能。
+------------------------
+2026-06-18 v4.5.2
+系统工具中修复部分场景中SSH端口切换后无法连接的问题。
+应用市场中的多人会议项目部署报错问题。
diff --git a/kr/kejilion.sh b/kr/kejilion.sh
index 528d0abca..b3b502f78 100644
--- a/kr/kejilion.sh
+++ b/kr/kejilion.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-sh_v="4.0.3"
+sh_v="4.5.2"
gl_hui='\e[37m'
@@ -29,12 +29,14 @@ else
gh_proxy="https://"
fi
+gh_https_url="https://"
+
}
quanju_canshu
-# 명령을 실행할 함수를 정의합니다
+# 명령을 실행하는 함수 정의
run_command() {
if [ "$zhushi" -eq 0 ]; then
"$@"
@@ -45,6 +47,8 @@ run_command() {
canshu_v6() {
if grep -q '^canshu="V6"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
+ elif grep -q '^canshu="V6"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
fi
}
@@ -52,14 +56,16 @@ canshu_v6() {
CheckFirstRun_true() {
if grep -q '^permission_granted="true"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
+ elif grep -q '^permission_granted="true"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
fi
}
-# 기능 매장 지점 정보를 수집하는 기능, 현재 스크립트 버전 번호, 사용 시간, 시스템 버전, CPU 아키텍처, 기계 국가 및 사용자가 사용하는 기능 이름을 기록합니다. 그들은 절대적으로 민감한 정보를 포함하지 않습니다. 제발 나를 믿으세요!
-# 이 기능을 설계 해야하는 이유는 무엇입니까? 목적은 사용자가 사용하는 기능을 더 잘 이해하고 기능을 더욱 최적화하여 사용자 요구를 충족시키는 더 많은 기능을 시작하는 것입니다.
-# 전체 텍스트의 경우 Send_Stats 기능 호출 위치, 투명 및 오픈 소스를 검색 할 수 있으며 우려 사항이 있으면 사용을 거부 할 수 있습니다.
+# 이 기능은 함수에 묻혀있는 정보를 수집하고 사용자가 사용하는 현재 스크립트 버전 번호, 사용 시간, 시스템 버전, CPU 아키텍처, 시스템 국가 및 기능 이름을 기록합니다. 민감한 정보는 포함되어 있지 않으니 걱정하지 마세요! 저를 믿어주세요!
+# 이 기능은 왜 설계되었나요? 그 목적은 사용자가 사용하고 싶어하는 기능을 더 잘 이해하고, 기능을 더욱 최적화하고 사용자 요구에 맞는 더 많은 기능을 출시하는 것입니다.
+# send_stats 함수 호출 위치에 대한 전문을 검색할 수 있습니다. 투명하고 오픈 소스입니다. 우려되는 사항이 있는 경우 이용을 거부하실 수 있습니다.
@@ -86,6 +92,8 @@ yinsiyuanquan2() {
if grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
+elif grep -q '^ENABLE_STATS="false"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
fi
}
@@ -102,6 +110,7 @@ sed -i '/^alias k=/d' ~/.profile > /dev/null 2>&1
sed -i '/^alias k=/d' ~/.bash_profile > /dev/null 2>&1
cp -f ./kejilion.sh ~/kejilion.sh > /dev/null 2>&1
cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
@@ -111,22 +120,22 @@ CheckFirstRun_false() {
fi
}
-# 사용자에게 이용 약관에 동의하라는 메시지
+# 사용자에게 약관에 동의하라는 메시지를 표시합니다.
UserLicenseAgreement() {
clear
- echo -e "${gl_kjlan}Tech Lion Script Toolbox에 오신 것을 환영합니다${gl_bai}"
- echo "스크립트를 처음 사용하면 사용자 라이센스 계약을 읽고 동의하십시오."
- echo "사용자 라이센스 계약 : https://blog.kejilion.pro/user-license-agreement/"
+ echo -e "${gl_kjlan}기술 사자 스크립트 도구 상자에 오신 것을 환영합니다${gl_bai}"
+ echo "스크립트를 처음 사용하는 경우 사용자 라이센스 계약을 읽고 동의하십시오."
+ echo "사용자 라이센스 계약: https://blog.kejilion.pro/user-license-agreement/"
echo -e "----------------------"
- read -r -p "위의 용어에 동의하십니까? (Y/N) :" user_input
+ read -e -p "위의 약관에 동의하십니까? (예/아니요):" user_input
if [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]; then
- send_stats "라이센스 동의"
+ send_stats "라이센스 계약"
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
sed -i 's/^permission_granted="false"/permission_granted="true"/' /usr/local/bin/k
else
- send_stats "허가 거부"
+ send_stats "허가가 거부되었습니다"
clear
exit
fi
@@ -154,7 +163,7 @@ public_ip=$(get_public_ip)
isp_info=$(curl -s --max-time 3 http://ipinfo.io/org)
-if echo "$isp_info" | grep -Eiq 'china|mobile|unicom|telecom'; then
+if echo "$isp_info" | grep -Eiq 'CHINANET|mobile|unicom|telecom'; then
ipv4_address=$(get_local_ip)
else
ipv4_address="$public_ip"
@@ -170,13 +179,13 @@ ipv6_address=$(curl -s --max-time 1 https://v6.ipinfo.io/ip && echo)
install() {
if [ $# -eq 0 ]; then
- echo "패키지 매개 변수는 제공되지 않습니다!"
+ echo "패키지 매개변수가 제공되지 않았습니다!"
return 1
fi
for package in "$@"; do
if ! command -v "$package" &>/dev/null; then
- echo -e "${gl_huang}설치$package...${gl_bai}"
+ echo -e "${gl_kjlan}설치 중$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
dnf install -y epel-release
@@ -204,7 +213,7 @@ install() {
pkg update
pkg install -y "$package"
else
- echo "알 수없는 패키지 관리자!"
+ echo "알 수 없는 패키지 관리자입니다!"
return 1
fi
fi
@@ -213,35 +222,45 @@ install() {
check_disk_space() {
+ local required_gb=$1
+ local path=${2:-/}
- required_gb=$1
- required_space_mb=$((required_gb * 1024))
- available_space_mb=$(df -m / | awk 'NR==2 {print $4}')
+ mkdir -p "$path"
- if [ $available_space_mb -lt $required_space_mb ]; then
- echo -e "${gl_huang}힌트:${gl_bai}불충분 한 디스크 공간!"
- echo "현재 사용 가능한 공간 : $ ((uvery_space_mb/1024)) g"
- echo "최소 수요 공간 :${required_gb}G"
- echo "설치를 계속할 수 없습니다. 디스크 공간을 청소하고 다시 시도하십시오."
- send_stats "불충분 한 디스크 공간"
+ local required_space_mb=$((required_gb * 1024))
+ local available_space_mb=$(df -m "$path" | awk 'NR==2 {print $4}')
+
+ if [ "$available_space_mb" -lt "$required_space_mb" ]; then
+ echo -e "${gl_huang}힌트:${gl_bai}디스크 공간이 부족합니다!"
+ echo "현재 사용 가능한 공간: $((available_space_mb/1024))G"
+ echo "최소 필요 공간:${required_gb}G"
+ echo "설치를 계속할 수 없습니다. 디스크 공간을 비운 후 다시 시도하십시오."
+ send_stats "디스크 공간이 부족합니다."
break_end
kejilion
fi
}
+
install_dependency() {
+ switch_mirror false false
+ check_port
+ check_swap
+ prefer_ipv4
+ auto_optimize_dns
install wget unzip tar jq grep
+
}
remove() {
if [ $# -eq 0 ]; then
- echo "패키지 매개 변수는 제공되지 않습니다!"
+ echo "패키지 매개변수가 제공되지 않았습니다!"
return 1
fi
for package in "$@"; do
- echo -e "${gl_huang}제거$package...${gl_bai}"
+ echo -e "${gl_kjlan}제거 중$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf remove -y "$package"
elif command -v yum &>/dev/null; then
@@ -259,14 +278,14 @@ remove() {
elif command -v pkg &>/dev/null; then
pkg delete -y "$package"
else
- echo "알 수없는 패키지 관리자!"
+ echo "알 수 없는 패키지 관리자입니다!"
return 1
fi
done
}
-# 다양한 분포에 적합한 범용 SystemCTL 기능
+# 다양한 배포판에 적합한 범용 systemctl 기능
systemctl() {
local COMMAND="$1"
local SERVICE_NAME="$2"
@@ -279,23 +298,23 @@ systemctl() {
}
-# 서비스를 다시 시작하십시오
+# 서비스 다시 시작
restart() {
systemctl restart "$1"
if [ $? -eq 0 ]; then
echo "$1서비스가 다시 시작되었습니다."
else
- echo "오류 : 다시 시작합니다$1서비스가 실패했습니다."
+ echo "오류: 다시 시작$1서비스가 실패했습니다."
fi
}
-# 서비스를 시작하십시오
+# 서비스 시작
start() {
systemctl start "$1"
if [ $? -eq 0 ]; then
echo "$1서비스가 시작되었습니다."
else
- echo "오류 : 시작$1서비스가 실패했습니다."
+ echo "오류: 시작$1서비스가 실패했습니다."
fi
}
@@ -303,19 +322,19 @@ start() {
stop() {
systemctl stop "$1"
if [ $? -eq 0 ]; then
- echo "$1서비스가 중단되었습니다."
+ echo "$1서비스가 중지되었습니다."
else
- echo "오류 : 중지$1서비스가 실패했습니다."
+ echo "오류: 중지$1서비스가 실패했습니다."
fi
}
-# 서비스 상태를 확인하십시오
+# 서비스 상태 확인
status() {
systemctl status "$1"
if [ $? -eq 0 ]; then
echo "$1서비스 상태가 표시됩니다."
else
- echo "오류 : 표시 할 수 없습니다$1서비스 상태."
+ echo "오류: 표시할 수 없습니다.$1서비스 상태."
fi
}
@@ -328,14 +347,14 @@ enable() {
/bin/systemctl enable "$SERVICE_NAME"
fi
- echo "$SERVICE_NAME전원 켜기로 설정합니다."
+ echo "$SERVICE_NAME부팅시 자동으로 시작되도록 설정되어 있습니다."
}
break_end() {
- echo -e "${gl_lv}작업이 완료되었습니다${gl_bai}"
- echo "계속하려면 키를 누르십시오 ..."
+ echo -e "${gl_lv}작업 완료${gl_bai}"
+ echo "계속하려면 아무 키나 누르세요..."
read -n 1 -s -r -p ""
echo ""
clear
@@ -349,22 +368,22 @@ kejilion() {
-check_port() {
- install lsof
+stop_containers_or_kill_process() {
+ local port=$1
+ local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
- stop_containers_or_kill_process() {
- local port=$1
- local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
+ if [ -n "$containers" ]; then
+ docker stop $containers
+ else
+ install lsof
+ for pid in $(lsof -t -i:$port); do
+ kill -9 $pid
+ done
+ fi
+}
- if [ -n "$containers" ]; then
- docker stop $containers
- else
- for pid in $(lsof -t -i:$port); do
- kill -9 $pid
- done
- fi
- }
+check_port() {
stop_containers_or_kill_process 80
stop_containers_or_kill_process 443
}
@@ -377,23 +396,23 @@ if [ "$country" = "CN" ]; then
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
- "https://docker-0.unsee.tech",
- "https://docker.1panel.live",
- "https://registry.dockermirror.com",
- "https://docker.imgdb.de",
- "https://docker.m.daocloud.io",
- "https://hub.firefly.store",
- "https://hub.littlediary.cn",
+ "https://docker.1ms.run",
+ "https://docker.m.ixdev.cn",
"https://hub.rat.dev",
- "https://dhub.kubesre.xyz",
- "https://cjie.eu.org",
- "https://docker.1panelproxy.com",
+ "https://dockerproxy.net",
+ "https://docker-registry.nmqu.com",
+ "https://docker.amingg.com",
"https://docker.hlmirror.com",
- "https://hub.fast360.xyz",
- "https://dockerpull.cn",
- "https://cr.laoyou.ip-ddns.com",
- "https://docker.melikeme.cn",
- "https://docker.kejilion.pro"
+ "https://hub1.nat.tf",
+ "https://hub2.nat.tf",
+ "https://hub3.nat.tf",
+ "https://docker.m.daocloud.io",
+ "https://docker.kejilion.pro",
+ "https://docker.367231.xyz",
+ "https://hub.1panel.dev",
+ "https://dockerproxy.cool",
+ "https://docker.apiba.cn",
+ "https://proxy.vvvv.ee"
]
}
EOF
@@ -407,80 +426,40 @@ restart docker
}
-install_add_docker_guanfang() {
+
+linuxmirrors_install_docker() {
+
local country=$(curl -s ipinfo.io/country)
if [ "$country" = "CN" ]; then
- cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/install && chmod +x install
- sh install --mirror Aliyun
- rm -f install
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source mirrors.huaweicloud.com/docker-ce \
+ --source-registry docker.1ms.run \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
else
- curl -fsSL https://get.docker.com | sh
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source download.docker.com \
+ --source-registry registry.hub.docker.com \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
fi
-install_add_docker_cn
+install_add_docker_cn
}
install_add_docker() {
- echo -e "${gl_huang}Docker 환경 설치 ...${gl_bai}"
- if [ -f /etc/os-release ] && grep -q "Fedora" /etc/os-release; then
- install_add_docker_guanfang
- elif command -v dnf &>/dev/null; then
- dnf update -y
- dnf install -y yum-utils device-mapper-persistent-data lvm2
- rm -f /etc/yum.repos.d/docker*.repo > /dev/null
- country=$(curl -s ipinfo.io/country)
- arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo | tee /etc/yum.repos.d/docker-ce.repo > /dev/null
- else
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo > /dev/null
- fi
- dnf install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
- elif [ -f /etc/os-release ] && grep -q "Kali" /etc/os-release; then
- apt update
- apt upgrade -y
- apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
- rm -f /usr/share/keyrings/docker-archive-keyring.gpg
- local country=$(curl -s ipinfo.io/country)
- local arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- else
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- fi
- apt update
- apt install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
-
- elif command -v apt &>/dev/null || command -v yum &>/dev/null; then
- install_add_docker_guanfang
+ echo -e "${gl_kjlan}도커 환경 설치 중...${gl_bai}"
+ if command -v apt &>/dev/null || command -v yum &>/dev/null || command -v dnf &>/dev/null; then
+ linuxmirrors_install_docker
else
install docker docker-compose
install_add_docker_cn
@@ -504,59 +483,59 @@ while true; do
echo "도커 컨테이너 목록"
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
- echo "컨테이너 작동"
+ echo "컨테이너 운영"
echo "------------------------"
- echo "1. 새 컨테이너를 만듭니다"
+ echo "1. 새 컨테이너 생성"
echo "------------------------"
- echo "2. 지정된 컨테이너를 시작하십시오. 6. 모든 컨테이너를 시작하십시오"
- echo "3. 지정된 컨테이너를 중지하십시오. 7. 모든 컨테이너를 중지하십시오"
- echo "4. 지정된 컨테이너를 삭제합니다. 8. 모든 컨테이너를 삭제하십시오"
- echo "5. 지정된 컨테이너를 다시 시작하십시오. 9. 모든 컨테이너를 다시 시작하십시오"
+ echo "2. 지정된 컨테이너 시작 6. 모든 컨테이너 시작"
+ echo "3. 지정된 컨테이너를 중지합니다. 7. 모든 컨테이너를 중지합니다."
+ echo "4. 지정된 컨테이너 삭제 8. 모든 컨테이너 삭제"
+ echo "5. 지정된 컨테이너를 다시 시작합니다. 9. 모든 컨테이너를 다시 시작합니다."
echo "------------------------"
- echo "11. 지정된 컨테이너를 입력하십시오. 12. 컨테이너 로그보기"
- echo "13. 컨테이너 네트워크보기 14. 컨테이너 점유보기"
+ echo "11. 지정된 컨테이너를 입력합니다. 12. 컨테이너 로그를 봅니다."
+ echo "13. 컨테이너 네트워크 확인 14. 컨테이너 점유율 확인"
echo "------------------------"
- echo "15. 컨테이너 포트 액세스 켜기 16. 컨테이너 포트 액세스를 끄십시오."
+ echo "15. 컨테이너 포트 액세스 활성화 16. 컨테이너 포트 액세스 닫기"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- send_stats "새 컨테이너를 만듭니다"
- read -e -p "창조 명령을 입력하십시오 :" dockername
+ send_stats "새 컨테이너 만들기"
+ read -e -p "생성 명령을 입력하십시오:" dockername
$dockername
;;
2)
- send_stats "지정된 컨테이너를 시작하십시오"
- read -e -p "컨테이너 이름 (공백별로 분리 된 여러 컨테이너 이름)을 입력하십시오." dockername
+ send_stats "지정된 컨테이너 시작"
+ read -e -p "컨테이너 이름을 입력하세요(여러 컨테이너 이름을 공백으로 구분하세요)." dockername
docker start $dockername
;;
3)
- send_stats "지정된 컨테이너를 중지하십시오"
- read -e -p "컨테이너 이름 (공백별로 분리 된 여러 컨테이너 이름)을 입력하십시오." dockername
+ send_stats "지정된 컨테이너 중지"
+ read -e -p "컨테이너 이름을 입력하세요(여러 컨테이너 이름을 공백으로 구분하세요)." dockername
docker stop $dockername
;;
4)
- send_stats "지정된 컨테이너를 삭제하십시오"
- read -e -p "컨테이너 이름 (공백별로 분리 된 여러 컨테이너 이름)을 입력하십시오." dockername
+ send_stats "지정된 컨테이너 삭제"
+ read -e -p "컨테이너 이름을 입력하세요(여러 컨테이너 이름을 공백으로 구분하세요)." dockername
docker rm -f $dockername
;;
5)
- send_stats "지정된 컨테이너를 다시 시작하십시오"
- read -e -p "컨테이너 이름 (공백별로 분리 된 여러 컨테이너 이름)을 입력하십시오." dockername
+ send_stats "지정된 컨테이너를 다시 시작합니다."
+ read -e -p "컨테이너 이름을 입력하세요(여러 컨테이너 이름을 공백으로 구분하세요)." dockername
docker restart $dockername
;;
6)
- send_stats "모든 컨테이너를 시작하십시오"
+ send_stats "모든 컨테이너 시작"
docker start $(docker ps -a -q)
;;
7)
- send_stats "모든 컨테이너를 중지하십시오"
+ send_stats "모든 컨테이너 중지"
docker stop $(docker ps -q)
;;
8)
- send_stats "모든 컨테이너를 삭제하십시오"
+ send_stats "모든 컨테이너 삭제"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定删除所有容器吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -565,32 +544,32 @@ while true; do
[Nn])
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
9)
- send_stats "모든 컨테이너를 다시 시작하십시오"
+ send_stats "모든 컨테이너 다시 시작"
docker restart $(docker ps -q)
;;
11)
- send_stats "컨테이너를 입력하십시오"
- read -e -p "컨테이너 이름을 입력하십시오 :" dockername
+ send_stats "컨테이너를 입력하세요"
+ read -e -p "컨테이너 이름을 입력하세요:" dockername
docker exec -it $dockername /bin/sh
break_end
;;
12)
- send_stats "컨테이너 로그를 봅니다"
- read -e -p "컨테이너 이름을 입력하십시오 :" dockername
+ send_stats "컨테이너 로그 보기"
+ read -e -p "컨테이너 이름을 입력하세요:" dockername
docker logs $dockername
break_end
;;
13)
- send_stats "컨테이너 네트워크를 봅니다"
+ send_stats "컨테이너 네트워크 보기"
echo ""
container_ids=$(docker ps -q)
echo "------------------------------------------------------------"
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "컨테이너 이름" "네트워크 이름" "IP 주소"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
local container_name=$(echo "$container_info" | awk '{print $1}')
@@ -604,14 +583,14 @@ while true; do
break_end
;;
14)
- send_stats "컨테이너 점유를 봅니다"
+ send_stats "컨테이너 점유율 보기"
docker stats --no-stream
break_end
;;
15)
- send_stats "컨테이너 포트 액세스를 허용합니다"
- read -e -p "컨테이너 이름을 입력하십시오 :" docker_name
+ send_stats "컨테이너 포트 액세스 허용"
+ read -e -p "컨테이너 이름을 입력하세요:" docker_name
ip_address
clear_container_rules "$docker_name" "$ipv4_address"
local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
@@ -620,8 +599,8 @@ while true; do
;;
16)
- send_stats "컨테이너 포트 액세스 차단"
- read -e -p "컨테이너 이름을 입력하십시오 :" docker_name
+ send_stats "컨테이너 포트 접근 차단"
+ read -e -p "컨테이너 이름을 입력하세요:" docker_name
ip_address
block_container_port "$docker_name" "$ipv4_address"
local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
@@ -646,38 +625,38 @@ while true; do
echo ""
echo "미러 작동"
echo "------------------------"
- echo "1. 지정된 이미지 가져 오기 3. 지정된 이미지 삭제"
+ echo "1. 지정된 이미지 가져오기 3. 지정된 이미지 삭제"
echo "2. 지정된 이미지 업데이트 4. 모든 이미지 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- send_stats "거울을 당기십시오"
- read -e -p "거울 이름을 입력하십시오 (공백으로 여러 거울 이름을 별도로 분리하십시오) :" imagenames
+ send_stats "이미지 가져오기"
+ read -e -p "이미지 이름을 입력하세요(여러 이미지 이름은 공백으로 구분하세요):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}이미지 얻기 :$name${gl_bai}"
+ echo -e "${gl_kjlan}이미지 가져오기:$name${gl_bai}"
docker pull $name
done
;;
2)
- send_stats "이미지를 업데이트하십시오"
- read -e -p "거울 이름을 입력하십시오 (공백으로 여러 거울 이름을 별도로 분리하십시오) :" imagenames
+ send_stats "이미지 업데이트"
+ read -e -p "이미지 이름을 입력하세요(여러 이미지 이름은 공백으로 구분하세요):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}업데이트 된 이미지 :$name${gl_bai}"
+ echo -e "${gl_kjlan}이미지 업데이트 중:$name${gl_bai}"
docker pull $name
done
;;
3)
- send_stats "거울을 삭제하십시오"
- read -e -p "거울 이름을 입력하십시오 (공백으로 여러 거울 이름을 별도로 분리하십시오) :" imagenames
+ send_stats "이미지 삭제"
+ read -e -p "이미지 이름을 입력하세요(여러 이미지 이름은 공백으로 구분하세요):" imagenames
for name in $imagenames; do
docker rmi -f $name
done
;;
4)
- send_stats "모든 이미지를 삭제합니다"
+ send_stats "모든 이미지 삭제"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定删除所有镜像吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -686,7 +665,7 @@ while true; do
[Nn])
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -754,16 +733,16 @@ install_crontab() {
service cron start
;;
*)
- echo "지원되지 않는 분포 :$ID"
+ echo "지원되지 않는 배포판:$ID"
return
;;
esac
else
- echo "운영 체제를 결정할 수 없습니다."
+ echo "운영 체제를 확인할 수 없습니다."
return
fi
- echo -e "${gl_lv}Crontab이 설치되고 Cron 서비스가 실행 중입니다.${gl_bai}"
+ echo -e "${gl_lv}crontab이 설치되어 있고 cron 서비스가 실행 중입니다.${gl_bai}"
}
@@ -775,27 +754,27 @@ docker_ipv6_on() {
local CONFIG_FILE="/etc/docker/daemon.json"
local REQUIRED_IPV6_CONFIG='{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64"}'
- # 구성 파일이 존재하는지 확인하고 파일을 작성하고 기본 설정이 존재하지 않는 경우 작성하십시오.
+ # 구성 파일이 있는지 확인하고, 없으면 파일을 생성하고 기본 설정을 씁니다.
if [ ! -f "$CONFIG_FILE" ]; then
echo "$REQUIRED_IPV6_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
else
- # JQ를 사용하여 구성 파일 업데이트를 처리하십시오
+ # jq를 사용하여 구성 파일 업데이트 처리
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # 현재 구성에 이미 IPv6 설정이 있는지 확인하십시오
+ # 현재 구성에 이미 ipv6 설정이 있는지 확인하십시오.
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq '.ipv6 // false')
- # 구성 업데이트 및 IPv6을 활성화하십시오
+ # 구성 업데이트 및 IPv6 활성화
if [[ "$CURRENT_IPV6" == "false" ]]; then
UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq '. + {ipv6: true, "fixed-cidr-v6": "2001:db8:1::/64"}')
else
UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq '. + {"fixed-cidr-v6": "2001:db8:1::/64"}')
fi
- # 원래 구성과 새로운 구성을 비교합니다
+ # 원래 구성을 새 구성과 비교
if [[ "$ORIGINAL_CONFIG" == "$UPDATED_CONFIG" ]]; then
- echo -e "${gl_huang}IPv6 액세스가 현재 활성화되어 있습니다${gl_bai}"
+ echo -e "${gl_huang}현재 IPv6 액세스가 활성화되어 있습니다.${gl_bai}"
else
echo "$UPDATED_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
@@ -810,28 +789,28 @@ docker_ipv6_off() {
local CONFIG_FILE="/etc/docker/daemon.json"
- # 구성 파일이 있는지 확인하십시오
+ # 구성 파일이 있는지 확인하십시오.
if [ ! -f "$CONFIG_FILE" ]; then
- echo -e "${gl_hong}구성 파일이 존재하지 않습니다${gl_bai}"
+ echo -e "${gl_hong}구성 파일이 존재하지 않습니다.${gl_bai}"
return
fi
- # 현재 구성을 읽으십시오
+ # 현재 구성 읽기
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # JQ를 사용하여 구성 파일 업데이트를 처리하십시오
+ # jq를 사용하여 구성 파일 업데이트 처리
local UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq 'del(.["fixed-cidr-v6"]) | .ipv6 = false')
- # 현재 IPv6 상태를 확인하십시오
+ # 현재 ipv6 상태 확인
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq -r '.ipv6 // false')
- # 원래 구성과 새로운 구성을 비교합니다
+ # 원래 구성을 새 구성과 비교
if [[ "$CURRENT_IPV6" == "false" ]]; then
- echo -e "${gl_huang}IPv6 액세스는 현재 닫힙니다${gl_bai}"
+ echo -e "${gl_huang}IPv6 액세스는 현재 폐쇄되었습니다.${gl_bai}"
else
echo "$UPDATED_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
- echo -e "${gl_huang}IPv6 액세스가 성공적으로 닫혔습니다${gl_bai}"
+ echo -e "${gl_huang}IPv6 액세스가 성공적으로 종료되었습니다.${gl_bai}"
fi
}
@@ -870,117 +849,117 @@ iptables_open() {
open_port() {
local ports=($@) # 将传入的参数转换为数组
if [ ${#ports[@]} -eq 0 ]; then
- echo "하나 이상의 포트 번호를 제공하십시오"
+ echo "포트 번호를 하나 이상 입력하세요."
return 1
fi
install iptables
for port in "${ports[@]}"; do
- # 기존 마감 규칙을 삭제하십시오
+ # 기존 종료 규칙 삭제
iptables -D INPUT -p tcp --dport $port -j DROP 2>/dev/null
iptables -D INPUT -p udp --dport $port -j DROP 2>/dev/null
- # 열린 규칙을 추가하십시오
+ # 공개 규칙 추가
if ! iptables -C INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j ACCEPT
fi
if ! iptables -C INPUT -p udp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j ACCEPT
- echo "포트가 열렸습니다$port"
+ echo "포트가 열렸습니다.$port"
fi
done
save_iptables_rules
- send_stats "포트가 열렸습니다"
+ send_stats "포트가 열렸습니다."
}
close_port() {
local ports=($@) # 将传入的参数转换为数组
if [ ${#ports[@]} -eq 0 ]; then
- echo "하나 이상의 포트 번호를 제공하십시오"
+ echo "포트 번호를 하나 이상 입력하세요."
return 1
fi
install iptables
for port in "${ports[@]}"; do
- # 기존 열린 규칙을 삭제합니다
+ # 기존 공개 규칙 삭제
iptables -D INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp --dport $port -j ACCEPT 2>/dev/null
- # 가까운 규칙을 추가하십시오
+ # 종료 규칙 추가
if ! iptables -C INPUT -p tcp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j DROP
fi
if ! iptables -C INPUT -p udp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j DROP
- echo "포트 폐쇄$port"
+ echo "항구 폐쇄$port"
fi
done
- # 기존 규칙 삭제 (있는 경우)
+ # 기존 규칙 삭제(있는 경우)
iptables -D INPUT -i lo -j ACCEPT 2>/dev/null
iptables -D FORWARD -i lo -j ACCEPT 2>/dev/null
- # 새로운 규칙을 먼저 삽입하십시오
+ # 첫 번째 규칙에 새 규칙을 삽입합니다.
iptables -I INPUT 1 -i lo -j ACCEPT
iptables -I FORWARD 1 -i lo -j ACCEPT
save_iptables_rules
- send_stats "포트 폐쇄"
+ send_stats "항구 폐쇄"
}
allow_ip() {
local ips=($@) # 将传入的参数转换为数组
if [ ${#ips[@]} -eq 0 ]; then
- echo "하나 이상의 IP 주소 또는 IP 세그먼트를 제공하십시오."
+ echo "IP 주소 또는 IP 세그먼트를 하나 이상 입력하세요."
return 1
fi
install iptables
for ip in "${ips[@]}"; do
- # 기존 차단 규칙을 삭제하십시오
+ # 기존 차단 규칙 삭제
iptables -D INPUT -s $ip -j DROP 2>/dev/null
- # 허용 규칙을 추가하십시오
+ # 허용 규칙 추가
if ! iptables -C INPUT -s $ip -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j ACCEPT
- echo "IP 출시$ip"
+ echo "공개된 IP$ip"
fi
done
save_iptables_rules
- send_stats "IP 출시"
+ send_stats "공개된 IP"
}
block_ip() {
local ips=($@) # 将传入的参数转换为数组
if [ ${#ips[@]} -eq 0 ]; then
- echo "하나 이상의 IP 주소 또는 IP 세그먼트를 제공하십시오."
+ echo "IP 주소 또는 IP 세그먼트를 하나 이상 입력하세요."
return 1
fi
install iptables
for ip in "${ips[@]}"; do
- # 기존 허용 규칙을 삭제합니다
+ # 기존 허용 규칙 삭제
iptables -D INPUT -s $ip -j ACCEPT 2>/dev/null
- # 차단 규칙을 추가하십시오
+ # 차단 규칙 추가
if ! iptables -C INPUT -s $ip -j DROP 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j DROP
- echo "IP 차단$ip"
+ echo "IP가 차단됨$ip"
fi
done
save_iptables_rules
- send_stats "IP 차단"
+ send_stats "IP가 차단됨"
}
@@ -990,7 +969,7 @@ block_ip() {
enable_ddos_defense() {
- # 방어 DDO를 켜십시오
+ # DDoS 보호 켜기
iptables -A DOCKER-USER -p tcp --syn -m limit --limit 500/s --limit-burst 100 -j ACCEPT
iptables -A DOCKER-USER -p tcp --syn -j DROP
iptables -A DOCKER-USER -p udp -m limit --limit 3000/s -j ACCEPT
@@ -1000,12 +979,12 @@ enable_ddos_defense() {
iptables -A INPUT -p udp -m limit --limit 3000/s -j ACCEPT
iptables -A INPUT -p udp -j DROP
- send_stats "DDOS 방어를 켜십시오"
+ send_stats "DDoS 방어 켜기"
}
-# DDOS 방어를 끕니다
+# DDoS 방어 끄기
disable_ddos_defense() {
- # 방어 DDO를 끄십시오
+ # DDoS 보호 끄기
iptables -D DOCKER-USER -p tcp --syn -m limit --limit 500/s --limit-burst 100 -j ACCEPT 2>/dev/null
iptables -D DOCKER-USER -p tcp --syn -j DROP 2>/dev/null
iptables -D DOCKER-USER -p udp -m limit --limit 3000/s -j ACCEPT 2>/dev/null
@@ -1015,7 +994,7 @@ disable_ddos_defense() {
iptables -D INPUT -p udp -m limit --limit 3000/s -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp -j DROP 2>/dev/null
- send_stats "DDOS 방어를 끕니다"
+ send_stats "DDoS 방어 끄기"
}
@@ -1025,91 +1004,84 @@ disable_ddos_defense() {
# 국가 IP 규칙을 관리하는 기능
manage_country_rules() {
local action="$1"
- local country_code="$2"
- local ipset_name="${country_code,,}_block"
- local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
+ shift # 去掉第一个参数,剩下的全是国家代码
install ipset
- case "$action" in
- block)
- # IPSET가 존재하지 않는 경우 작성하십시오
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ for country_code in "$@"; do
+ local ipset_name="${country_code,,}_block"
+ local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
- # IP 영역 파일을 다운로드하십시오
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "오류 : 다운로드$country_codeIP 영역 파일이 실패했습니다"
- exit 1
- fi
+ case "$action" in
+ block)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
- # IPSET에 IP를 추가하십시오
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "오류: 다운로드$country_codeIP 영역 파일 실패"
+ continue
+ fi
- # iptables와 함께 IP를 차단하십시오
- iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- iptables -I OUTPUT -m set --match-set "$ipset_name" dst -j DROP
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- echo "성공적으로 차단되었습니다$country_codeIP 주소"
- rm "${country_code,,}.zone"
- ;;
+ iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- allow)
- # 허용 국가에 대한 IPSET 만들기 (존재하지 않는 경우)
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ echo "성공적으로 차단되었습니다$country_codeIP 주소"
+ rm "${country_code,,}.zone"
+ ;;
- # IP 영역 파일을 다운로드하십시오
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "오류 : 다운로드$country_codeIP 영역 파일이 실패했습니다"
- exit 1
- fi
+ allow)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
+
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "오류: 다운로드$country_codeIP 영역 파일 실패"
+ continue
+ fi
- # 기존 국가 규칙을 삭제합니다
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
- ipset flush "$ipset_name"
+ ipset flush "$ipset_name"
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- # IPSET에 IP를 추가하십시오
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
- # 지정된 국가의 IP 만 허용됩니다
- iptables -P INPUT DROP
- iptables -P OUTPUT DROP
- iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- iptables -A OUTPUT -m set --match-set "$ipset_name" dst -j ACCEPT
+ iptables -P INPUT DROP
+ iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- echo "성공적으로 만 허용됩니다$country_codeIP 주소"
- rm "${country_code,,}.zone"
- ;;
+ echo "허용되었습니다.$country_codeIP 주소"
+ rm "${country_code,,}.zone"
+ ;;
- unblock)
- # 국가의 iptables 규칙을 삭제하십시오
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
+ unblock)
+ iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- # ipset을 파괴하십시오
- if ipset list "$ipset_name" &> /dev/null; then
- ipset destroy "$ipset_name"
- fi
+ if ipset list "$ipset_name" &> /dev/null; then
+ ipset destroy "$ipset_name"
+ fi
- echo "성공적으로 해제했습니다$country_codeIP 주소 제한"
- ;;
+ echo "성공적으로 제거되었습니다$country_codeIP 주소 제한"
+ ;;
- *)
- ;;
- esac
+ *)
+ echo "사용법: prepare_country_rules {block|allow|unblock} "
+ ;;
+ esac
+ done
}
+
+
+
+
+
+
iptables_panel() {
root_use
install iptables
@@ -1123,35 +1095,35 @@ iptables_panel() {
echo ""
echo "방화벽 관리"
echo "------------------------"
- echo "1. 지정된 포트 2를 엽니 다. 지정된 포트를 닫으십시오."
- echo "3. 모든 포트를 엽니 다. 4. 모든 포트를 닫으십시오"
+ echo "1. 지정 포트 열기 2. 지정 포트 닫기"
+ echo "3. 모든 포트를 엽니다. 4. 모든 포트를 닫습니다."
echo "------------------------"
echo "5. IP 화이트리스트 6. IP 블랙리스트"
- echo "7. 지정된 IP를 지우십시오"
+ echo "7. 지정된 IP 지우기"
echo "------------------------"
- echo "11. 핑 허용 12. 핑을 비활성화하십시오"
+ echo "11. PING 허용 12. PING 비활성화"
echo "------------------------"
- echo "13. DDOS 방어 시작 14. DDOS 방어를 끄십시오"
+ echo "13. DDOS 방어 시작 14. DDOS 방어 끄기"
echo "------------------------"
- echo "15. 지정된 국가 IP 16. 지정된 국가 IP 만 허용됩니다."
- echo "17. 지정된 국가에서 IP 제한을 해제합니다"
+ echo "15. 특정 국가 IP 차단 16. 특정 국가 IP만 허용"
+ echo "17. 지정된 국가에서 IP 제한을 해제합니다."
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- read -e -p "열린 포트 번호를 입력하십시오 :" o_port
+ read -e -p "열려 있는 포트 번호를 입력하세요." o_port
open_port $o_port
- send_stats "지정된 포트를 엽니 다"
+ send_stats "지정된 포트 열기"
;;
2)
- read -e -p "닫힌 포트 번호를 입력하십시오 :" c_port
+ read -e -p "닫힌 포트 번호를 입력하세요:" c_port
close_port $c_port
- send_stats "지정된 포트를 닫습니다"
+ send_stats "지정된 포트 닫기"
;;
3)
- # 모든 포트를 엽니 다
+ # 모든 포트 열기
current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
iptables -F
iptables -X
@@ -1164,10 +1136,10 @@ iptables_panel() {
iptables -A FORWARD -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport $current_port -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "모든 포트를 엽니 다"
+ send_stats "모든 포트 열기"
;;
4)
- # 모든 포트를 닫습니다
+ # 모든 포트를 닫으세요
current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
iptables -F
iptables -X
@@ -1180,40 +1152,40 @@ iptables_panel() {
iptables -A FORWARD -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport $current_port -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "모든 포트를 닫습니다"
+ send_stats "모든 포트를 닫으세요"
;;
5)
# IP 화이트리스트
- read -e -p "릴리스하려면 IP 또는 IP 세그먼트를 입력하십시오." o_ip
+ read -e -p "허용된 IP 또는 IP 세그먼트를 입력하세요." o_ip
allow_ip $o_ip
;;
6)
# IP 블랙리스트
- read -e -p "차단 된 IP 또는 IP 세그먼트를 입력하십시오." c_ip
+ read -e -p "차단된 IP 또는 IP 범위를 입력하세요." c_ip
block_ip $c_ip
;;
7)
- # 지정된 IP를 지우십시오
- read -e -p "청산 된 IP를 입력하십시오 :" d_ip
+ # 지정된 IP 지우기
+ read -e -p "삭제된 IP를 입력하세요:" d_ip
iptables -D INPUT -s $d_ip -j ACCEPT 2>/dev/null
iptables -D INPUT -s $d_ip -j DROP 2>/dev/null
iptables-save > /etc/iptables/rules.v4
- send_stats "지정된 IP를 지우십시오"
+ send_stats "지정된 IP 지우기"
;;
11)
- # 핑을 허용하십시오
+ # 핑 허용
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "핑을 허용하십시오"
+ send_stats "핑 허용"
;;
12)
- # 핑을 비활성화합니다
+ # 핑 비활성화
iptables -D INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null
iptables -D OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT 2>/dev/null
iptables-save > /etc/iptables/rules.v4
- send_stats "핑을 비활성화합니다"
+ send_stats "핑 비활성화"
;;
13)
enable_ddos_defense
@@ -1223,20 +1195,20 @@ iptables_panel() {
;;
15)
- read -e -p "차단 된 국가 코드 (예 : CN, US, JP)를 입력하십시오." country_code
+ read -e -p "차단된 국가 코드를 입력하세요(여러 국가 코드는 CN US JP와 같이 공백으로 구분될 수 있음)." country_code
manage_country_rules block $country_code
- send_stats "허용 국가$country_codeIP"
+ send_stats "국가 허용$country_codeIP"
;;
16)
- read -e -p "허용 된 국가 코드 (예 : CN, US, JP)를 입력하십시오." country_code
+ read -e -p "허용되는 국가 코드를 입력하십시오(여러 국가 코드는 CN US JP와 같이 공백으로 구분할 수 있음)." country_code
manage_country_rules allow $country_code
- send_stats "나라를 차단하십시오$country_codeIP"
+ send_stats "블록 국가$country_codeIP"
;;
17)
- read -e -p "청산 된 국가 코드 (예 : CN, US, JP)를 입력하십시오." country_code
+ read -e -p "삭제된 국가 코드를 입력하십시오(여러 국가 코드는 CN US JP와 같이 공백으로 구분될 수 있음)." country_code
manage_country_rules unblock $country_code
- send_stats "나라를 정리하십시오$country_codeIP"
+ send_stats "명확한 국가$country_codeIP"
;;
*)
@@ -1252,28 +1224,26 @@ iptables_panel() {
-
-
add_swap() {
local new_swap=$1 # 获取传入的参数
- # 현재 시스템에서 모든 스왑 파티션을 얻으십시오
+ # 현재 시스템의 모든 스왑 파티션을 가져옵니다.
local swap_partitions=$(grep -E '^/dev/' /proc/swaps | awk '{print $1}')
- # 모든 스왑 파티션을 반복하고 삭제하십시오
+ # 모든 스왑 파티션을 탐색하고 삭제합니다.
for partition in $swap_partitions; do
swapoff "$partition"
wipefs -a "$partition"
mkswap -f "$partition"
done
- # /swapfile이 더 이상 사용되지 않도록하십시오
+ # /swapfile이 더 이상 사용되지 않는지 확인하세요.
swapoff /swapfile
- # 이전 /스왑 파일을 삭제하십시오
+ # 이전 /swap 파일 삭제
rm -f /swapfile
- # 새 스왑 파티션을 만듭니다
+ # 새로운 스왑 파티션 생성
fallocate -l ${new_swap}M /swapfile
chmod 600 /swapfile
mkswap /swapfile
@@ -1288,7 +1258,7 @@ add_swap() {
rc-update add local
fi
- echo -e "가상 메모리 크기가 크기가 커졌습니다${gl_huang}${new_swap}${gl_bai}M"
+ echo -e "가상 메모리 크기가 다음과 같이 조정되었습니다.${gl_huang}${new_swap}${gl_bai}M"
}
@@ -1298,7 +1268,7 @@ check_swap() {
local swap_total=$(free -m | awk 'NR==3{print $2}')
-# 가상 메모리를 만들어야하는지 확인하십시오
+# 가상 메모리를 생성해야 하는지 결정
[ "$swap_total" -gt 0 ] || add_swap 1024
@@ -1314,21 +1284,21 @@ local swap_total=$(free -m | awk 'NR==3{print $2}')
ldnmp_v() {
- # nginx 버전을 얻으십시오
+ # nginx 버전 받기
local nginx_version=$(docker exec nginx nginx -v 2>&1)
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e "nginx : ${gl_huang}v$nginx_version${gl_bai}"
- # MySQL 버전을 얻으십시오
+ # mysql 버전 받기
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local mysql_version=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SELECT VERSION();" 2>/dev/null | tail -n 1)
echo -n -e " mysql : ${gl_huang}v$mysql_version${gl_bai}"
- # PHP 버전을 얻으십시오
+ # PHP 버전 받기
local php_version=$(docker exec php php -v 2>/dev/null | grep -oP "PHP \K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e " php : ${gl_huang}v$php_version${gl_bai}"
- # Redis 버전을 얻으십시오
+ # Redis 버전 받기
local redis_version=$(docker exec redis redis-server -v 2>&1 | grep -oP "v=+\K[0-9]+\.[0-9]+")
echo -e " redis : ${gl_huang}v$redis_version${gl_bai}"
@@ -1341,20 +1311,18 @@ ldnmp_v() {
install_ldnmp_conf() {
- # 필요한 디렉토리 및 파일을 만듭니다
- cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/redis web/log/nginx && touch web/docker-compose.yml
+ # 필요한 디렉토리 및 파일 생성
+ cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/stream.d web/redis web/log/nginx web/letsencrypt && touch web/docker-compose.yml
wget -O /home/web/nginx.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default10.conf
- wget -O /home/web/redis/valkey.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/valkey.conf
-
default_server_ssl
- # docker-compose.yml 파일을 다운로드하여 교체하십시오
+ # docker-compose.yml 파일을 다운로드하여 교체하세요.
wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
dbrootpasswd=$(openssl rand -base64 16) ; dbuse=$(openssl rand -hex 4) ; dbusepasswd=$(openssl rand -base64 8)
- # docker-compose.yml 파일로 교체하십시오
+ # docker-compose.yml 파일에서 교체
sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
@@ -1362,31 +1330,69 @@ install_ldnmp_conf() {
}
+update_docker_compose_with_db_creds() {
+ cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
+ if ! grep -q "letsencrypt" /home/web/docker-compose.yml; then
+ wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
-install_ldnmp() {
+ dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+
+ sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
+ fi
+
+ if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
+ sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ fi
- check_swap
+}
- cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
- if ! grep -q "network_mode" /home/web/docker-compose.yml; then
- wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
- dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
- fi
- if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
- sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- fi
+auto_optimize_dns() {
+ # 국가 코드(예: CN, US 등)를 가져옵니다.
+ local country=$(curl -s ipinfo.io/country)
+
+ # 국가에 따라 DNS 설정
+ if [ "$country" = "CN" ]; then
+ local dns1_ipv4="223.5.5.5"
+ local dns2_ipv4="183.60.83.19"
+ local dns1_ipv6="2400:3200::1"
+ local dns2_ipv6="2400:da00::6666"
+ else
+ local dns1_ipv4="1.1.1.1"
+ local dns2_ipv4="8.8.8.8"
+ local dns1_ipv6="2606:4700:4700::1111"
+ local dns2_ipv6="2001:4860:4860::8888"
+ fi
+
+ set_dns
+
+
+}
+
+
+prefer_ipv4() {
+grep -q '^precedence ::ffff:0:0/96 100' /etc/gai.conf 2>/dev/null \
+ || echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
+echo "IPv4 우선순위로 전환됨"
+send_stats "IPv4 우선순위로 전환됨"
+}
+
+
+
+
+install_ldnmp() {
+
+ update_docker_compose_with_db_creds
cd /home/web && docker compose up -d
sleep 1
@@ -1395,9 +1401,17 @@ install_ldnmp() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- restart_ldnmp
+
+ # MySQL 튜닝
+ wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
+ docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
+ rm -rf /home/custom_mysql_config.cnf
+
+ restart_ldnmp
+ sleep 2
+
clear
echo "LDNMP 환경이 설치되었습니다"
echo "------------------------"
@@ -1416,19 +1430,18 @@ install_certbot() {
local cron_job="0 0 * * * ~/auto_cert_renewal.sh"
crontab -l 2>/dev/null | grep -vF "$cron_job" | crontab -
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "갱신 작업이 업데이트되었습니다"
+ echo "갱신 작업이 업데이트되었습니다."
}
install_ssltls() {
docker stop nginx > /dev/null 2>&1
- check_port > /dev/null 2>&1
cd ~
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ ! -f "$file_path" ]; then
local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
- local ipv6_pattern='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))))$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
if [[ ($yuming =~ $ipv4_pattern || $yuming =~ $ipv6_pattern) ]]; then
mkdir -p /etc/letsencrypt/live/$yuming/
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -1438,7 +1451,7 @@ install_ssltls() {
openssl req -x509 -key /etc/letsencrypt/live/$yuming/privkey.pem -out /etc/letsencrypt/live/$yuming/fullchain.pem -days 5475 -subj "/C=US/ST=State/L=City/O=Organization/OU=Organizational Unit/CN=Common Name"
fi
else
- docker run -it --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
fi
fi
mkdir -p /home/web/certs/
@@ -1451,15 +1464,15 @@ install_ssltls() {
install_ssltls_text() {
- echo -e "${gl_huang}$yuming공개 키 정보${gl_bai}"
+ echo -e "${gl_huang}$yuming공개키 정보${gl_bai}"
cat /etc/letsencrypt/live/$yuming/fullchain.pem
echo ""
echo -e "${gl_huang}$yuming개인 키 정보${gl_bai}"
cat /etc/letsencrypt/live/$yuming/privkey.pem
echo ""
echo -e "${gl_huang}인증서 저장 경로${gl_bai}"
- echo "공개 키 :/etc/letsencrypt/live/$yuming/fullchain.pem"
- echo "개인 키 :/etc/letsencrypt/live/$yuming/privkey.pem"
+ echo "공개 키: /etc/letsencrypt/live/$yuming/fullchain.pem"
+ echo "개인 키: /etc/letsencrypt/live/$yuming/privkey.pem"
echo ""
}
@@ -1468,14 +1481,14 @@ install_ssltls_text() {
add_ssl() {
-echo -e "${gl_huang}SSL 인증서를 신속하게 신청하고 만료 전에 서명을 자동으로 갱신하십시오.${gl_bai}"
+echo -e "${gl_huang}SSL 인증서를 빠르게 신청하고 만료되기 전에 자동으로 갱신하세요.${gl_bai}"
yuming="${1:-}"
if [ -z "$yuming" ]; then
add_yuming
fi
install_docker
install_certbot
-docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
install_ssltls_text
@@ -1484,7 +1497,7 @@ ssl_ps
ssl_ps() {
- echo -e "${gl_huang}응용 인증서 만료${gl_bai}"
+ echo -e "${gl_huang}신청한 인증서의 만료 상태${gl_bai}"
echo "사이트 정보 인증서 만료 시간"
echo "------------------------"
for cert_dir in /etc/letsencrypt/live/*; do
@@ -1524,22 +1537,79 @@ certs_status() {
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ -f "$file_path" ]; then
- send_stats "도메인 이름 인증서에 대한 성공적인 응용 프로그램"
+ send_stats "도메인 이름 인증서 신청이 성공했습니다."
else
- send_stats "도메인 이름 인증서 신청에 실패했습니다"
- echo -e "${gl_hong}알아채다:${gl_bai}인증서 응용 프로그램이 실패했습니다. 가능한 이유를 확인하고 다시 시도하십시오."
- echo -e "1. 도메인 이름 철자 오류 ➠ 도메인 이름이 올바르게 입력되었는지 확인하십시오."
- echo -e "2. DNS 해상도 문제 ➠ 도메인 이름 이이 서버 IP로 올바르게 해결되었는지 확인합니다."
- echo -e "3. 네트워크 구성 문제 ➠ CloudFlare Warp 및 기타 가상 네트워크를 사용하는 경우 임시로 종료하십시오."
- echo -e "4. 방화벽 제한 ➠ 검증에 액세스 할 수 있도록 포트 80/443이 열려 있는지 확인"
- echo -e "5. 응용 프로그램 수는 한계를 초과합니다. ➠ 암호화하자는 주간 제한 (5 배/도메인 이름/주)을 갖습니다."
- echo -e "6. 국내 등록 제한 ➠ 도메인 이름이 중국 본토에 등록되어 있는지 확인하십시오."
- break_end
- clear
- echo "다시 배포를 시도하십시오$webname"
- add_yuming
- install_ssltls
- certs_status
+ send_stats "도메인 이름 인증서 신청 실패"
+ echo -e "${gl_hong}알아채다:${gl_bai}인증서 신청이 실패했습니다. 다음 가능한 이유를 확인하고 다시 시도하십시오."
+ echo -e "1. 도메인 이름이 잘못 입력되었습니다. ➠ 도메인 이름이 올바르게 입력되었는지 확인하세요."
+ echo -e "2. DNS 확인 문제 ➠ 도메인 이름이 서버 IP로 올바르게 확인되었는지 확인"
+ echo -e "3. 네트워크 구성 문제 ➠ Cloudflare Warp 등 가상 네트워크를 사용하는 경우 일시적으로 종료하세요."
+ echo -e "4. 방화벽 제한사항 ➠ 포트 80/443이 열려 있는지 확인하고 접근이 가능한지 확인하세요."
+ echo -e "5. 신청 횟수가 한도를 초과했습니다. ➠ Let's Encrypt에는 주간 한도(5회/도메인 이름/주)가 있습니다."
+ echo -e "6. 국내 등록 제한 ➠ 중국 본토 환경의 경우 도메인 이름 등록 여부를 확인하시기 바랍니다."
+ echo "------------------------"
+ echo "1. 재적용 2. 기존 인증서 가져오기 0. 종료"
+ echo "------------------------"
+ read -e -p "선택사항을 입력하세요:" sub_choice
+ case $sub_choice in
+ 1)
+ send_stats "재신청"
+ echo "다시 배포해 보세요.$webname"
+ add_yuming
+ install_ssltls
+ certs_status
+
+ ;;
+ 2)
+ send_stats "기존 인증서 가져오기"
+
+ # 파일 경로 정의
+ local cert_file="/home/web/certs/${yuming}_cert.pem"
+ local key_file="/home/web/certs/${yuming}_key.pem"
+
+ mkdir -p /home/web/certs
+
+ # 1. 인증서를 입력합니다(ECC 및 RSA 인증서 모두 BEGIN CERTIFICATE로 시작함).
+ echo "인증서(CRT/PEM) 콘텐츠를 붙여넣으세요(종료하려면 Enter를 두 번 누르세요)."
+ local cert_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$cert_content" == *"-----BEGIN"* ]] && break
+ cert_content+="${line}"$'\n'
+ done
+
+ # 2. 개인 키를 입력하세요. (RSA, ECC, PKCS#8과 호환 가능)
+ echo "인증서 개인 키(개인 키) 내용을 붙여넣으세요(종료하려면 Enter를 두 번 누르세요)."
+ local key_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$key_content" == *"-----BEGIN"* ]] && break
+ key_content+="${line}"$'\n'
+ done
+
+ # 3. 지능형 검증
+ # 통과하려면 "BEGIN CERTIFICATE" 및 "PRIVATE KEY"만 포함하면 됩니다.
+ if [[ "$cert_content" == *"-----BEGIN CERTIFICATE-----"* && "$key_content" == *"PRIVATE KEY-----"* ]]; then
+ echo -n "$cert_content" > "$cert_file"
+ echo -n "$key_content" > "$key_file"
+
+ chmod 644 "$cert_file"
+ chmod 600 "$key_file"
+
+ # 현재 인증서 유형을 식별하고 표시합니다.
+ if [[ "$key_content" == *"EC PRIVATE KEY"* ]]; then
+ echo "ECC 인증서가 성공적으로 저장되었음을 감지했습니다."
+ else
+ echo "RSA 인증서가 성공적으로 저장되었음을 감지했습니다."
+ fi
+ auth_method="ssl_imported"
+ else
+ echo "오류: 잘못된 인증서 또는 개인 키 형식입니다!"
+ certs_status
+ fi
+ ;;
+ *)
+ exit
+ ;;
+ esac
fi
}
@@ -1556,11 +1626,45 @@ fi
add_yuming() {
ip_address
- echo -e "먼저 도메인 이름을 로컬 IP로 해결합니다.${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
- read -e -p "IP 또는 해결 된 도메인 이름을 입력하십시오." yuming
+ echo -e "먼저 도메인 이름을 로컬 IP로 확인합니다.${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
+ read -e -p "귀하의 IP 또는 확인된 도메인 이름을 입력하십시오:" yuming
+}
+
+
+check_ip_and_get_access_port() {
+ local yuming="$1"
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ read -e -p "액세스/수신 포트를 입력하고 Enter를 눌러 기본적으로 80을 사용하십시오." access_port
+ access_port=${access_port:-80}
+ fi
+}
+
+
+
+update_nginx_listen_port() {
+ local yuming="$1"
+ local access_port="$2"
+ local conf="/home/web/conf.d/${yuming}.conf"
+
+ # access_port가 비어 있으면 건너뛰세요.
+ [ -z "$access_port" ] && return 0
+
+ # 모든 청취 라인 제거
+ sed -i '/^[[:space:]]*listen[[:space:]]\+/d' "$conf"
+
+ # 서버 { 뒤에 새 수신 대기 삽입
+ sed -i "/server {/a\\
+ listen ${access_port};\\
+ listen [::]:${access_port};
+" "$conf"
}
+
add_db() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbname="${dbname}"
@@ -1571,28 +1675,8 @@ add_db() {
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "CREATE DATABASE $dbname; GRANT ALL PRIVILEGES ON $dbname.* TO \"$dbuse\"@\"%\";"
}
-reverse_proxy() {
- ip_address
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
- sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$ipv4_address/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$duankou|g" /home/web/conf.d/$yuming.conf
- nginx_http_on
- docker exec nginx nginx -s reload
-}
-
-
-restart_redis() {
- rm -rf /home/web/redis/*
- docker exec redis redis-cli FLUSHALL > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory 1gb > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory-policy allkeys-lru > /dev/null 2>&1
-}
-
-
restart_ldnmp() {
- restart_redis
docker exec nginx chown -R nginx:nginx /var/www/html > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/proxy > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/fastcgi > /dev/null 2>&1
@@ -1600,7 +1684,8 @@ restart_ldnmp() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi > /dev/null 2>&1
docker exec php chown -R www-data:www-data /var/www/html > /dev/null 2>&1
docker exec php74 chown -R www-data:www-data /var/www/html > /dev/null 2>&1
- cd /home/web && docker compose restart nginx php php74
+ cd /home/web && docker compose restart
+
}
@@ -1635,17 +1720,17 @@ phpmyadmin_upgrade() {
cd /home/web/
docker rm -f $ldnmp_pods > /dev/null 2>&1
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
- curl -sS -O https://raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
docker compose -f docker-compose.phpmyadmin.yml up -d
clear
ip_address
check_docker_app_ip
- echo "로그인 정보 :"
- echo "사용자 이름 :$dbuse"
+ echo "로그인 정보:"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
echo
- send_stats "시작$ldnmp_pods"
+ send_stats "시작하다$ldnmp_pods"
}
@@ -1655,29 +1740,29 @@ cf_purge_cache() {
local EMAIL
local ZONE_IDS
- # 구성 파일이 있는지 확인하십시오
+ # 구성 파일이 있는지 확인하십시오.
if [ -f "$CONFIG_FILE" ]; then
- # 구성 파일에서 API_TOKE 및 ZONE_ID를 읽으십시오
+ # 구성 파일에서 API_TOKEN 및 zone_id를 읽습니다.
read API_TOKEN EMAIL ZONE_IDS < "$CONFIG_FILE"
- # Zone_ids를 배열로 변환합니다
+ # ZONE_IDS를 어레이로 변환
ZONE_IDS=($ZONE_IDS)
else
- # 캐시 청소 여부를 사용자에게 프롬프트합니다
- read -e -p "CloudFlare의 캐시를 청소해야합니까? (Y/N) :" answer
+ # 캐시를 지울지 여부를 사용자에게 묻습니다.
+ read -e -p "Cloudflare의 캐시를 지워야 합니까? (예/아니요):" answer
if [[ "$answer" == "y" ]]; then
- echo "CF 정보가 저장됩니다$CONFIG_FILE, 나중에 CF 정보를 수정할 수 있습니다"
- read -e -p "API_Token을 입력하십시오 :" API_TOKEN
- read -e -p "CF 사용자 이름을 입력하십시오 :" EMAIL
- read -e -p "Zone_ID를 입력하십시오 (공백으로 여러 차례 분리) :" -a ZONE_IDS
+ echo "CF 정보는 다음 위치에 저장됩니다.$CONFIG_FILE, 나중에 CF 정보를 수정할 수 있습니다."
+ read -e -p "API_TOKEN을 입력하세요:" API_TOKEN
+ read -e -p "CF 사용자 이름을 입력하세요:" EMAIL
+ read -e -p "zone_id를 입력하십시오(여러 개는 공백으로 구분)." -a ZONE_IDS
mkdir -p /home/web/config/
echo "$API_TOKEN $EMAIL ${ZONE_IDS[*]}" > "$CONFIG_FILE"
fi
fi
- # 각 Zone_ID를 루프하고 CLEAR CACHE 명령을 실행하십시오.
+ # 각 zone_id를 반복하고 캐시 지우기 명령을 실행합니다.
for ZONE_ID in "${ZONE_IDS[@]}"; do
- echo "Zone_ID의 캐시 지우기 :$ZONE_ID"
+ echo "zone_id에 대한 캐시 지우기:$ZONE_ID"
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $API_TOKEN" \
@@ -1685,16 +1770,15 @@ cf_purge_cache() {
--data '{"purge_everything":true}'
done
- echo "캐시 클리어 요청이 전송되었습니다."
+ echo "캐시 삭제 요청이 전송되었습니다."
}
web_cache() {
- send_stats "사이트 캐시를 정리하십시오"
+ send_stats "사이트 캐시 지우기"
cf_purge_cache
cd /home/web && docker compose restart
- restart_redis
}
@@ -1704,25 +1788,25 @@ web_del() {
send_stats "사이트 데이터 삭제"
yuming_list="${1:-}"
if [ -z "$yuming_list" ]; then
- read -e -p "사이트 데이터를 삭제하려면 도메인 이름을 입력하십시오 (여러 도메인 이름이 공간별로 분리됩니다)." yuming_list
+ read -e -p "사이트 데이터를 삭제하려면 도메인 이름을 입력하세요(여러 도메인 이름은 공백으로 구분)." yuming_list
if [[ -z "$yuming_list" ]]; then
return
fi
fi
for yuming in $yuming_list; do
- echo "도메인 이름 삭제 :$yuming"
+ echo "도메인 이름을 삭제하는 중입니다:$yuming"
rm -r /home/web/html/$yuming > /dev/null 2>&1
rm /home/web/conf.d/$yuming.conf > /dev/null 2>&1
rm /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
rm /home/web/certs/${yuming}_cert.pem > /dev/null 2>&1
- # 도메인 이름을 데이터베이스 이름으로 변환합니다
+ # 도메인 이름을 데이터베이스 이름으로 변환
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
- # 오류를 피하기 위해 삭제하기 전에 데이터베이스가 존재하는지 확인하십시오.
- echo "데이터베이스 삭제 :$dbname"
+ # 오류를 방지하려면 데이터베이스를 삭제하기 전에 데이터베이스가 존재하는지 확인하세요.
+ echo "데이터베이스 삭제 중:$dbname"
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE ${dbname};" > /dev/null 2>&1
done
@@ -1738,23 +1822,23 @@ nginx_waf() {
wget -O /home/web/nginx.conf "${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf"
fi
- # 모드 매개 변수에 따라 WAF를 켜거나 끄기로 결정
+ # 모드 매개변수에 따라 WAF를 켤지 끌지 결정합니다.
if [ "$mode" == "on" ]; then
- # WAF 켜기 : 주석을 제거하십시오
+ # WAF 켜기: 댓글 제거
sed -i 's|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)# modsecurity on;|\1modsecurity on;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)# modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|\1modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|' /home/web/nginx.conf > /dev/null 2>&1
elif [ "$mode" == "off" ]; then
- # waf 닫기 : 댓글을 추가하십시오
+ # WAF 끄기: 댓글 추가
sed -i 's|^load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)modsecurity on;|\1# modsecurity on;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^\(\s*\)modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|\1# modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;|' /home/web/nginx.conf > /dev/null 2>&1
else
- echo "유효하지 않은 매개 변수 : 'ON'또는 'OFF'사용"
+ echo "잘못된 인수: 'on' 또는 'off'를 사용하세요."
return 1
fi
- # Nginx 이미지를 확인하고 상황에 따라 처리하십시오.
+ # nginx 이미지를 확인하고 그에 따라 처리하세요.
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1768,7 +1852,7 @@ check_waf_status() {
if grep -q "^\s*#\s*modsecurity on;" /home/web/nginx.conf; then
waf_status=""
elif grep -q "modsecurity on;" /home/web/nginx.conf; then
- waf_status=" WAF已开启"
+ waf_status="WAF가 켜져 있습니다"
else
waf_status=""
fi
@@ -1776,8 +1860,8 @@ check_waf_status() {
check_cf_mode() {
- if [ -f "/path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf" ]; then
- CFmessage=" cf模式已开启"
+ if [ -f "/etc/fail2ban/action.d/cloudflare-docker.conf" ]; then
+ CFmessage="cf 모드가 켜져 있습니다"
else
CFmessage=""
fi
@@ -1801,11 +1885,11 @@ patch_wp_memory_limit() {
local TARGET_DIR="/home/web/html" # 路径写死
find "$TARGET_DIR" -type f -name "wp-config.php" | while read -r FILE; do
- # 오래된 정의를 삭제하십시오
+ # 이전 정의 삭제
sed -i "/define(['\"]WP_MEMORY_LIMIT['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_MAX_MEMORY_LIMIT['\"].*/d" "$FILE"
- # "Happy Publishing"과 함께 새로운 정의를 삽입하십시오.
+ # "HappyPublishing"이 포함된 줄 앞에 새 정의를 삽입합니다.
awk -v insert="define('WP_MEMORY_LIMIT', '$MEMORY_LIMIT');\ndefine('WP_MAX_MEMORY_LIMIT', '$MAX_MEMORY_LIMIT');" \
'
/Happy publishing/ {
@@ -1828,12 +1912,12 @@ patch_wp_debug() {
local TARGET_DIR="/home/web/html" # 路径写死
find "$TARGET_DIR" -type f -name "wp-config.php" | while read -r FILE; do
- # 오래된 정의를 삭제하십시오
+ # 이전 정의 삭제
sed -i "/define(['\"]WP_DEBUG['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_DEBUG_DISPLAY['\"].*/d" "$FILE"
sed -i "/define(['\"]WP_DEBUG_LOG['\"].*/d" "$FILE"
- # "Happy Publishing"과 함께 새로운 정의를 삽입하십시오.
+ # "HappyPublishing"이 포함된 줄 앞에 새 정의를 삽입합니다.
awk -v insert="define('WP_DEBUG_DISPLAY', $DEBUG_DISPLAY);\ndefine('WP_DEBUG_LOG', $DEBUG_LOG);" \
'
/Happy publishing/ {
@@ -1847,6 +1931,43 @@ patch_wp_debug() {
}
+
+
+patch_wp_url() {
+ local HOME_URL="$1"
+ local SITE_URL="$2"
+ local TARGET_DIR="/home/web/html"
+
+ find "$TARGET_DIR" -type f -name "wp-config-sample.php" | while read -r FILE; do
+ # 이전 정의 삭제
+ sed -i "/define(['\"]WP_HOME['\"].*/d" "$FILE"
+ sed -i "/define(['\"]WP_SITEURL['\"].*/d" "$FILE"
+
+ # 삽입 콘텐츠 생성
+ INSERT="
+define('WP_HOME', '$HOME_URL');
+define('WP_SITEURL', '$SITE_URL');
+"
+
+ # '행복한 출판' 앞에 삽입
+ awk -v insert="$INSERT" '
+ /Happy publishing/ {
+ print insert
+ }
+ { print }
+ ' "$FILE" > "$FILE.tmp" && mv -f "$FILE.tmp" "$FILE"
+
+ echo "[+] Updated WP_HOME and WP_SITEURL in $FILE"
+ done
+}
+
+
+
+
+
+
+
+
nginx_br() {
local mode=$1
@@ -1856,7 +1977,7 @@ nginx_br() {
fi
if [ "$mode" == "on" ]; then
- # Brotli를 켜십시오 : 주석을 제거하십시오
+ # Brotli 켜기: 댓글 제거
sed -i 's|# load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|# load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1870,7 +1991,7 @@ nginx_br() {
sed -i '/brotli_types/,+6 s/^\(\s*\)#\s*/\1/' /home/web/nginx.conf
elif [ "$mode" == "off" ]; then
- # Brotli를 닫습니다 : 주석을 추가하십시오
+ # Brotli 닫기: 댓글 추가
sed -i 's|^load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|# load_module /etc/nginx/modules/ngx_http_brotli_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|# load_module /etc/nginx/modules/ngx_http_brotli_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1886,11 +2007,11 @@ nginx_br() {
}' /home/web/nginx.conf
else
- echo "유효하지 않은 매개 변수 : 'ON'또는 'OFF'사용"
+ echo "잘못된 인수: 'on' 또는 'off'를 사용하세요."
return 1
fi
- # Nginx 이미지를 확인하고 상황에 따라 처리하십시오.
+ # nginx 이미지를 확인하고 그에 따라 처리하세요.
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1912,7 +2033,7 @@ nginx_zstd() {
fi
if [ "$mode" == "on" ]; then
- # ZSTD를 켜십시오 : 주석을 제거하십시오
+ # Zstd 켜기: 주석 제거
sed -i 's|# load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|# load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1927,7 +2048,7 @@ nginx_zstd() {
elif [ "$mode" == "off" ]; then
- # Zstd를 닫습니다 : 주석을 추가하십시오
+ # Zstd 닫기: 설명 추가
sed -i 's|^load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|# load_module /etc/nginx/modules/ngx_http_zstd_filter_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
sed -i 's|^load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|# load_module /etc/nginx/modules/ngx_http_zstd_static_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1943,11 +2064,11 @@ nginx_zstd() {
else
- echo "유효하지 않은 매개 변수 : 'ON'또는 'OFF'사용"
+ echo "잘못된 인수: 'on' 또는 'off'를 사용하세요."
return 1
fi
- # Nginx 이미지를 확인하고 상황에 따라 처리하십시오.
+ # nginx 이미지를 확인하고 그에 따라 처리하세요.
if grep -q "kjlion/nginx:alpine" /home/web/docker-compose.yml; then
docker exec nginx nginx -s reload
else
@@ -1974,7 +2095,7 @@ nginx_gzip() {
elif [ "$mode" == "off" ]; then
sed -i 's|^\(\s*\)gzip on;|\1# gzip on;|' /home/web/nginx.conf > /dev/null 2>&1
else
- echo "유효하지 않은 매개 변수 : 'ON'또는 'OFF'사용"
+ echo "잘못된 인수: 'on' 또는 'off'를 사용하세요."
return 1
fi
@@ -1990,45 +2111,42 @@ nginx_gzip() {
web_security() {
send_stats "LDNMP 환경 방어"
while true; do
+ check_f2b_status
check_waf_status
check_cf_mode
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "서버 웹 사이트 방어 프로그램${check_docker}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
+ echo -e "서버 웹사이트 방어 프로그램${check_f2b_status}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
echo "------------------------"
- echo "1. 방어 프로그램을 설치하십시오"
+ echo "1. 방어 프로그램 설치"
echo "------------------------"
- echo "5. SSH 차단 레코드보기 6. 웹 사이트 차단 레코드보기"
- echo "7. 방어 규칙 목록보기 8. 로그의 실시간 모니터링보기"
+ echo "5. SSH 차단 기록 보기 6. 웹사이트 차단 기록 보기"
+ echo "7. 방어규칙 목록 보기 8. 실시간 모니터링을 위한 로그 보기"
echo "------------------------"
- echo "11. 인터셉트 매개 변수 구성 12. 차단 된 모든 IP를 지우십시오"
+ echo "11. 차단 매개변수 구성 12. 차단된 IP 모두 삭제"
echo "------------------------"
- echo "21. CloudFlare 모드 22. 5 초 방패의 높은 하중"
+ echo "21. cloudflare 모드 22. 고부하에서 5초 보호 활성화"
echo "------------------------"
- echo "31. WAF 32를 켜십시오. WAF를 끄십시오"
+ echo "31. WAF 켜기 32. WAF 끄기"
echo "33. DDOS 방어 켜기 34. DDOS 방어 끄기"
echo "------------------------"
- echo "9. 방어 프로그램을 제거하십시오"
+ echo "9. 방어 프로그램 제거"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
f2b_install_sshd
- cd /path/to/fail2ban/config/fail2ban/filter.d
+ cd /etc/fail2ban/filter.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/fail2ban-nginx-cc.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-418.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-deny.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-unauthorized.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-bad-request.conf
+
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- sed -i "/cloudflare/d" /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ sed -i "/cloudflare/d" /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
;;
5)
@@ -2042,93 +2160,94 @@ web_security() {
local xxx="fail2ban-nginx-cc"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-418"
+ local xxx="nginx-418"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-bad-request"
+ local xxx="nginx-bad-request"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-badbots"
+ local xxx="nginx-badbots"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-botsearch"
+ local xxx="nginx-botsearch"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-deny"
+ local xxx="nginx-deny"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-http-auth"
+ local xxx="nginx-http-auth"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-unauthorized"
+ local xxx="nginx-unauthorized"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-php-url-fopen"
+ local xxx="php-url-fopen"
f2b_status_xxx
echo "------------------------"
;;
7)
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
;;
8)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
+ tail -f /var/log/fail2ban.log
;;
9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
+ remove fail2ban
+ rm -rf /etc/fail2ban
crontab -l | grep -v "CF-Under-Attack.sh" | crontab - 2>/dev/null
- echo "Fail2ban 방어 프로그램은 제거되었습니다"
+ echo "Fail2Ban 방어 프로그램이 제거되었습니다."
+ break
;;
11)
install nano
- nano /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ nano /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
break
;;
12)
- docker exec -it fail2ban fail2ban-client unban --all
+ fail2ban-client unban --all
;;
21)
- send_stats "CloudFlare 모드"
- echo "CF 배경의 오른쪽 상단 모서리로 이동하여 왼쪽의 API 토큰을 선택하고 글로벌 API 키를 얻습니다."
+ send_stats "클라우드플레어 모드"
+ echo "cf 백엔드 오른쪽 상단에 있는 내 프로필로 이동하여 왼쪽에 있는 API 토큰을 선택하고 글로벌 API 키를 받으세요."
echo "https://dash.cloudflare.com/login"
- read -e -p "CF 계정 번호를 입력하십시오." cfuser
- read -e -p "CF의 글로벌 API 키를 입력하십시오." cftoken
+ read -e -p "CF의 계좌번호를 입력하세요:" cfuser
+ read -e -p "CF의 글로벌 API 키를 입력하세요:" cftoken
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default11.conf
docker exec nginx nginx -s reload
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- cd /path/to/fail2ban/config/fail2ban/action.d
+ cd /etc/fail2ban/action.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/cloudflare-docker.conf
- sed -i "s/kejilion@outlook.com/$cfuser/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
- sed -i "s/APIKEY00000/$cftoken/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/kejilion@outlook.com/$cfuser/g" /etc/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/APIKEY00000/$cftoken/g" /etc/fail2ban/action.d/cloudflare-docker.conf
f2b_status
- echo "CloudFlare 모드는 CF 배경, Site-Security-Events에서 인터셉트 레코드를 보도록 구성됩니다."
+ echo "Cloudflare 모드가 구성되었으며, 차단 기록은 cf 백그라운드, site-security-events에서 볼 수 있습니다."
;;
22)
- send_stats "5 초 방패의 높은 하중"
- echo -e "${gl_huang}웹 사이트는 5 분마다 자동으로 감지됩니다. 높은 부하가 감지되면 방패가 자동으로 켜지고 5 초 동안 낮은 부하가 자동으로 꺼집니다.${gl_bai}"
+ send_stats "고부하로 5초 쉴드 가능"
+ echo -e "${gl_huang}웹사이트는 5분마다 자동으로 감지합니다. 고부하를 감지하면 자동으로 실드를 열고, 저부하를 감지하면 자동으로 5초 동안 실드를 닫습니다.${gl_bai}"
echo "--------------"
- echo "CF 매개 변수 가져 오기 :"
- echo -e "CF 배경의 오른쪽 상단 모서리로 이동하여 왼쪽의 API 토큰을 선택하고 얻습니다.${gl_huang}Global API Key${gl_bai}"
- echo -e "CF 배경 도메인 이름 요약 페이지의 오른쪽 하단으로 이동하려면${gl_huang}지역 ID${gl_bai}"
+ echo "CF 매개변수 가져오기:"
+ echo -e "cf 백엔드 오른쪽 상단에 있는 내 프로필로 이동하여 왼쪽에 있는 API 토큰을 선택하고${gl_huang}Global API Key${gl_bai}"
+ echo -e "cf 백엔드 도메인 이름 요약 페이지 오른쪽 하단으로 이동하여 가져오세요.${gl_huang}지역 ID${gl_bai}"
echo "https://dash.cloudflare.com/login"
echo "--------------"
- read -e -p "CF 계정 번호를 입력하십시오." cfuser
- read -e -p "CF의 글로벌 API 키를 입력하십시오." cftoken
- read -e -p "CF에 도메인 이름의 영역 ID를 입력하십시오." cfzonID
+ read -e -p "CF의 계좌번호를 입력하세요:" cfuser
+ read -e -p "CF의 글로벌 API 키를 입력하세요:" cftoken
+ read -e -p "CF에 도메인 이름의 영역 ID를 입력하세요." cfzonID
cd ~
install jq bc
@@ -2145,23 +2264,23 @@ web_security() {
if [ -z "$existing_cron" ]; then
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "고 부하 자동 방패 오프닝 스크립트가 추가되었습니다"
+ echo "고부하 자동 쉴드 오픈 스크립트가 추가되었습니다."
else
- echo "자동 방패 스크립트가 이미 존재합니다. 추가 할 필요가 없습니다."
+ echo "자동 방패 열기 스크립트가 이미 있으므로 추가할 필요가 없습니다."
fi
;;
31)
nginx_waf on
- echo "사이트 waf가 활성화되어 있습니다"
- send_stats "사이트 waf가 활성화되어 있습니다"
+ echo "사이트 WAF가 활성화되었습니다"
+ send_stats "사이트 WAF가 활성화되었습니다"
;;
32)
nginx_waf off
- echo "사이트 waf가 닫혔습니다"
- send_stats "사이트 waf가 닫혔습니다"
+ echo "사이트 WAF가 다운되었습니다"
+ send_stats "사이트 WAF가 다운되었습니다"
;;
33)
@@ -2176,26 +2295,23 @@ web_security() {
break
;;
esac
- fi
break_end
done
}
-check_nginx_mode() {
+check_ldnmp_mode() {
-CONFIG_FILE="/home/web/nginx.conf"
+ local MYSQL_CONTAINER="mysql"
+ local MYSQL_CONF="/etc/mysql/conf.d/custom_mysql_config.cnf"
-# 현재 Worker_Processes 설정 값을 얻으십시오
-current_value=$(grep -E '^\s*worker_processes\s+[0-9]+;' "$CONFIG_FILE" | awk '{print $2}' | tr -d ';')
-
-# 값에 따라 모드 정보를 설정합니다
-if [ "$current_value" = "8" ]; then
- mode_info=" 高性能模式"
-else
- mode_info=" 标准模式"
-fi
+ # MySQL 구성 파일에 4096M이 포함되어 있는지 확인하세요.
+ if docker exec "$MYSQL_CONTAINER" grep -q "4096M" "$MYSQL_CONF" 2>/dev/null; then
+ mode_info="고성능 모드"
+ else
+ mode_info="표준 모드"
+ fi
@@ -2204,25 +2320,25 @@ fi
check_nginx_compression() {
- CONFIG_FILE="/home/web/nginx.conf"
+ local CONFIG_FILE="/home/web/nginx.conf"
- # ZSTD가 활성화되어 있고 주석이 없는지 확인하십시오 (전체 라인은 ZSTD 켜기로 시작합니다.)
+ # zstd가 켜져 있고 주석 처리가 해제되어 있는지 확인하세요. (전체 줄은 zstd on으로 시작됩니다.)
if grep -qE '^\s*zstd\s+on;' "$CONFIG_FILE"; then
- zstd_status=" zstd压缩已开启"
+ zstd_status="zstd 압축이 켜져 있습니다"
else
zstd_status=""
fi
- # Brotli가 활성화되어 있고 댓글이 없는지 확인하십시오
+ # brotli가 활성화되어 있고 주석 처리가 해제되어 있는지 확인하세요.
if grep -qE '^\s*brotli\s+on;' "$CONFIG_FILE"; then
- br_status=" br压缩已开启"
+ br_status="br압축이 켜져 있습니다"
else
br_status=""
fi
- # GZIP가 활성화되어 있고 댓글을 달지 않은지 확인하십시오
+ # gzip이 활성화되어 있고 주석 처리가 해제되어 있는지 확인하세요.
if grep -qE '^\s*gzip\s+on;' "$CONFIG_FILE"; then
- gzip_status=" gzip压缩已开启"
+ gzip_status="gzip 압축이 켜져 있습니다"
else
gzip_status=""
fi
@@ -2233,28 +2349,30 @@ check_nginx_compression() {
web_optimization() {
while true; do
- check_nginx_mode
+ check_ldnmp_mode
check_nginx_compression
clear
- send_stats "LDNMP 환경을 최적화하십시오"
- echo -e "LDNMP 환경을 최적화하십시오${gl_lv}${mode_info}${gzip_status}${br_status}${zstd_status}${gl_bai}"
+ send_stats "LDNMP 환경 최적화"
+ echo -e "LDNMP 환경 최적화${gl_lv}${mode_info}${gzip_status}${br_status}${zstd_status}${gl_bai}"
echo "------------------------"
- echo "1. 표준 모드 2. 고성능 모드 (2H4G 이상 권장)"
+ echo "1. 표준 모드 2. 고성능 모드(2H4G 이상 권장)"
echo "------------------------"
- echo "3. GZIP 압축 켜기 4. GZIP 압축 끄기"
- echo "5. BR 압축 켜기 6. BR 압축 끄기"
- echo "7. ZSTD 압축 켜기 8. ZSTD 압축 끄기"
+ echo "3. gzip 압축 켜기 4. gzip 압축 끄기"
+ echo "5. br 압축을 켭니다. 6. br 압축을 끕니다."
+ echo "7. zstd 압축을 켭니다. 8. zstd 압축을 끕니다."
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
send_stats "사이트 표준 모드"
- # nginx 튜닝
- sed -i 's/worker_connections.*/worker_connections 10240;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 4;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((1024 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
+
# PHP 튜닝
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2282,19 +2400,20 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_balanced
- echo "LDNMP 환경은 표준 모드로 설정되었습니다"
+ echo "LDNMP 환경이 표준 모드로 설정되었습니다."
;;
2)
send_stats "사이트 고성능 모드"
# nginx 튜닝
- sed -i 's/worker_connections.*/worker_connections 20480;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 8;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((2048 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
# PHP 튜닝
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2321,10 +2440,9 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_web_server
- echo "LDNMP 환경은 고성능 모드로 설정되었습니다"
+ echo "LDNMP 환경이 고성능 모드로 설정되었습니다."
;;
3)
@@ -2371,23 +2489,30 @@ web_optimization() {
+check_docker_app() {
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name" ; then
+ check_docker="${gl_lv}설치됨${gl_bai}"
+ else
+ check_docker="${gl_hui}설치되지 않음${gl_bai}"
+ fi
+}
-check_docker_app() {
+# check_docker_app() {
-if docker inspect "$docker_name" &>/dev/null; then
- check_docker="${gl_lv}已安装${gl_bai}"
-else
- check_docker="${gl_hui}未安装${gl_bai}"
-fi
+# if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+# check_docker="${gl_lv}가 ${gl_bai}를 설치했습니다."
+# else
+# check_docker="${gl_hui}이(가) ${gl_bai}" 설치되지 않았습니다.
+# fi
-}
+# }
check_docker_app_ip() {
echo "------------------------"
-echo "액세스 주소 :"
+echo "방문 주소:"
ip_address
@@ -2416,59 +2541,66 @@ done
check_docker_image_update() {
-
local container_name=$1
+ update_status=""
- local country=$(curl -s ipinfo.io/country)
- if [[ "$country" == "CN" ]]; then
- update_status=""
- return
- fi
+ # 1. 지역점검
+ local country=$(curl -s --max-time 2 ipinfo.io/country)
+ [[ "$country" == "CN" ]] && return
- # 컨테이너 생성 시간과 이미지 이름을 얻으십시오
+ # 2. 로컬 미러 정보 얻기
local container_info=$(docker inspect --format='{{.Created}},{{.Config.Image}}' "$container_name" 2>/dev/null)
- local container_created=$(echo "$container_info" | cut -d',' -f1)
- local image_name=$(echo "$container_info" | cut -d',' -f2)
-
- # 거울 창고 및 태그 추출
- local image_repo=${image_name%%:*}
- local image_tag=${image_name##*:}
-
- # 기본 레이블이 최신입니다
- [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ -z "$container_info" ]] && return
- # 공식 이미지에 대한 지원을 추가하십시오
- [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
-
- # Docker Hub API에서 이미지 게시 시간을 얻으십시오
- local hub_info=$(curl -s "https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag")
- local last_updated=$(echo "$hub_info" | jq -r '.last_updated' 2>/dev/null)
-
- # 획득 시간을 확인하십시오
- if [[ -n "$last_updated" && "$last_updated" != "null" ]]; then
- local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
- local last_updated_ts=$(date -d "$last_updated" +%s 2>/dev/null)
+ local container_created=$(echo "$container_info" | cut -d',' -f1)
+ local full_image_name=$(echo "$container_info" | cut -d',' -f2)
+ local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
+
+ # 3. 지능형 라우팅 판단
+ if [[ "$full_image_name" == ghcr.io* ]]; then
+ # --- 시나리오 A: GitHub(ghcr.io)의 미러링 ---
+ # 웨어하우스 경로를 추출합니다(예: ghcr.io/onexru/oneimg -> onexru/oneimg).
+ local repo_path=$(echo "$full_image_name" | sed 's/ghcr.io\///' | cut -d':' -f1)
+ # 참고: ghcr.io의 API는 비교적 복잡합니다. 일반적으로 가장 빠른 방법은 GitHub Repo 릴리스를 확인하는 것입니다.
+ local api_url="https://api.github.com/repos/$repo_path/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
+
+ elif [[ "$full_image_name" == *"oneimg"* ]]; then
+ # --- 시나리오 B: 특별 지정(Docker Hub에서도 GitHub 릴리스로 판단하고 싶습니다) ---
+ local api_url="https://api.github.com/repos/onexru/oneimg/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
- # 타임 스탬프를 비교하십시오
- if [[ $container_created_ts -lt $last_updated_ts ]]; then
- update_status="${gl_huang}发现新版本!${gl_bai}"
- else
- update_status=""
- fi
else
- update_status=""
+ # --- 시나리오 C: 표준 Docker 허브 ---
+ local image_repo=${full_image_name%%:*}
+ local image_tag=${full_image_name##*:}
+ [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+
+ local api_url="https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag"
+ local remote_date=$(curl -s "$api_url" | jq -r '.last_updated' 2>/dev/null)
fi
+ # 4. 타임스탬프 비교
+ if [[ -n "$remote_date" && "$remote_date" != "null" ]]; then
+ local remote_ts=$(date -d "$remote_date" +%s 2>/dev/null)
+ if [[ $container_created_ts -lt $remote_ts ]]; then
+ update_status="${gl_huang}새 버전이 발견되었습니다!${gl_bai}"
+ fi
+ fi
}
+
+
+
block_container_port() {
local container_name_or_id=$1
local allowed_ip=$2
- # 컨테이너의 IP 주소를 가져옵니다
+ # 컨테이너의 IP 주소 가져오기
local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name_or_id")
if [ -z "$container_ip" ]; then
@@ -2478,34 +2610,34 @@ block_container_port() {
install iptables
- # 다른 모든 IP를 점검하고 차단하십시오
+ # 다른 모든 IP를 확인하고 차단하세요.
if ! iptables -C DOCKER-USER -p tcp -d "$container_ip" -j DROP &>/dev/null; then
iptables -I DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
- # 지정된 IP를 확인하고 해제하십시오
+ # 지정된 IP 확인 및 해제
if ! iptables -C DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 로컬 네트워크 127.0.0.0/8을 확인하고 해제하십시오
+ # 로컬 네트워크 127.0.0.0/8 확인 및 허용
if ! iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
- # 다른 모든 IP를 점검하고 차단하십시오
+ # 다른 모든 IP를 확인하고 차단하세요.
if ! iptables -C DOCKER-USER -p udp -d "$container_ip" -j DROP &>/dev/null; then
iptables -I DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
- # 지정된 IP를 확인하고 해제하십시오
+ # 지정된 IP 확인 및 해제
if ! iptables -C DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 로컬 네트워크 127.0.0.0/8을 확인하고 해제하십시오
+ # 로컬 네트워크 127.0.0.0/8 확인 및 허용
if ! iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2515,7 +2647,7 @@ block_container_port() {
fi
- echo "IP+ 포트는 서비스에 액세스하는 것이 차단되었습니다"
+ echo "IP+포트가 서비스 접근을 차단했습니다."
save_iptables_rules
}
@@ -2526,7 +2658,7 @@ clear_container_rules() {
local container_name_or_id=$1
local allowed_ip=$2
- # 컨테이너의 IP 주소를 가져옵니다
+ # 컨테이너의 IP 주소 가져오기
local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name_or_id")
if [ -z "$container_ip" ]; then
@@ -2541,12 +2673,12 @@ clear_container_rules() {
iptables -D DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
- # 지정된 IP를 공개하기위한 규칙을 지우십시오
+ # 특정 IP를 허용하는 규칙 지우기
if iptables -C DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 로컬 네트워크 릴리스 규칙을 지우십시오. 127.0.0.0/8
+ # 로컬 네트워크 127.0.0.0/8을 허용하는 규칙을 지웁니다.
if iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2560,12 +2692,12 @@ clear_container_rules() {
iptables -D DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
- # 지정된 IP를 공개하기위한 규칙을 지우십시오
+ # 특정 IP를 허용하는 규칙 지우기
if iptables -C DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 로컬 네트워크 릴리스 규칙을 지우십시오. 127.0.0.0/8
+ # 로컬 네트워크 127.0.0.0/8을 허용하는 규칙을 지웁니다.
if iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2576,7 +2708,7 @@ clear_container_rules() {
fi
- echo "IP+포트는 서비스에 액세스 할 수있었습니다"
+ echo "IP+포트로 서비스 접속이 허용되었습니다"
save_iptables_rules
}
@@ -2590,25 +2722,25 @@ block_host_port() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "오류 : 액세스 할 수있는 포트 번호와 IP를 제공하십시오."
- echo "사용법 : block_host_port <포트 번호> <승인 된 ip>"
+ echo "오류: 액세스를 허용하려면 포트 번호와 IP를 입력하세요."
+ echo "사용법: block_host_port <포트 번호> <허용 IP>"
return 1
fi
install iptables
- # 다른 모든 IP 액세스를 거부했습니다
+ # 다른 모든 IP의 접근을 거부
if ! iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -j DROP
fi
- # 지정된 IP 액세스를 허용합니다
+ # 지정된 IP에 대한 접근을 허용
if ! iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # 로컬 액세스를 허용합니다
+ # 로컬 액세스 허용
if ! iptables -C INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
@@ -2617,27 +2749,27 @@ block_host_port() {
- # 다른 모든 IP 액세스를 거부했습니다
+ # 다른 모든 IP의 접근을 거부
if ! iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -j DROP
fi
- # 지정된 IP 액세스를 허용합니다
+ # 지정된 IP에 대한 접근을 허용
if ! iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # 로컬 액세스를 허용합니다
+ # 로컬 액세스 허용
if ! iptables -C INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 확립 및 관련 연결을 위해 트래픽을 허용합니다
+ # 확립된 관련 연결에 대한 트래픽 허용
if ! iptables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT &>/dev/null; then
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
fi
- echo "IP+ 포트는 서비스에 액세스하는 것이 차단되었습니다"
+ echo "IP+포트가 서비스 접근을 차단했습니다."
save_iptables_rules
}
@@ -2649,47 +2781,47 @@ clear_host_port_rules() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "오류 : 액세스 할 수있는 포트 번호와 IP를 제공하십시오."
- echo "사용법 : CLEAR_HOST_PORT_RULES <포트 번호> <승인 IP>"
+ echo "오류: 액세스를 허용하려면 포트 번호와 IP를 입력하세요."
+ echo "사용법:clear_host_port_rules <포트 번호> <허용 IP>"
return 1
fi
install iptables
- # 다른 모든 IP 액세스를 차단하는 명확한 규칙
+ # 다른 모든 IP의 접근을 차단하는 규칙을 삭제하세요.
if iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -j DROP
fi
- # 기본 액세스를 허용하는 명확한 규칙
+ # 로컬 액세스를 허용하는 명확한 규칙
if iptables -C INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 지정된 IP 액세스를 허용하는 명확한 규칙
+ # 지정된 IP로부터의 액세스를 허용하는 명확한 규칙
if iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # 다른 모든 IP 액세스를 차단하는 명확한 규칙
+ # 다른 모든 IP의 접근을 차단하는 규칙을 해제하세요.
if iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -j DROP
fi
- # 기본 액세스를 허용하는 명확한 규칙
+ # 로컬 액세스를 허용하는 명확한 규칙
if iptables -C INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 지정된 IP 액세스를 허용하는 명확한 규칙
+ # 지정된 IP로부터의 액세스를 허용하는 명확한 규칙
if iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- echo "IP+포트는 서비스에 액세스 할 수있었습니다"
+ echo "IP+포트로 서비스 접속이 허용되었습니다"
save_iptables_rules
}
@@ -2698,16 +2830,33 @@ clear_host_port_rules() {
setup_docker_dir() {
- mkdir -p /home/docker/ 2>/dev/null
+ mkdir -p /home /home/docker 2>/dev/null
+
if [ -d "/vol1/1000/" ] && [ ! -d "/vol1/1000/docker" ]; then
cp -f /home/docker /home/docker1 2>/dev/null
rm -rf /home/docker 2>/dev/null
mkdir -p /vol1/1000/docker 2>/dev/null
ln -s /vol1/1000/docker /home/docker 2>/dev/null
fi
+
+ if [ -d "/volume1/" ] && [ ! -d "/volume1/docker" ]; then
+ cp -f /home/docker /home/docker1 2>/dev/null
+ rm -rf /home/docker 2>/dev/null
+ mkdir -p /volume1/docker 2>/dev/null
+ ln -s /volume1/docker /home/docker 2>/dev/null
+ fi
+
+
}
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "${app_id}" /home/docker/appno.txt || echo "${app_id}" >> /home/docker/appno.txt
+
+}
+
docker_app() {
@@ -2716,11 +2865,11 @@ send_stats "${docker_name}관리하다"
while true; do
clear
check_docker_app
- check_docker_image_update $docker_name
+ check_docker_image_update "${docker_app_service:-$docker_name}"
echo -e "$docker_name $check_docker $update_status"
echo "$docker_describe"
echo "$docker_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
@@ -2734,26 +2883,37 @@ while true; do
echo "1. 설치 2. 업데이트 3. 제거"
echo "------------------------"
echo "5. 도메인 이름 액세스 추가 6. 도메인 이름 액세스 삭제"
- echo "7. IP+ 포트 액세스 허용 8. 블록 IP+ 포트 액세스"
+ echo "7. IP+포트 접근 허용 8. IP+포트 접근 차단"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "응용 프로그램 외부 서비스 포트를 입력하고 기본값을 입력하십시오.${docker_port}포트:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+ while true; do
+ read -e -p "애플리케이션 외부 서비스 포트를 입력하고 Enter 키를 누르면 기본적으로 사용됩니다.${docker_port}포트:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}실수:${gl_bai}포트$app_port이미 사용 중입니다. 포트를 변경하세요."
+ send_stats "애플리케이션 포트가 점유되어 있습니다."
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
install jq
install_docker
docker_rum
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+ add_app_id
+
clear
- echo "$docker_name설치"
+ echo "$docker_name설치 완료"
check_docker_app_ip
echo ""
$docker_use
@@ -2764,8 +2924,11 @@ while true; do
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
docker_rum
+
+ add_app_id
+
clear
- echo "$docker_name설치"
+ echo "$docker_name설치 완료"
check_docker_app_ip
echo ""
$docker_use
@@ -2777,20 +2940,22 @@ while true; do
docker rmi -f "$docker_img"
rm -rf "/home/docker/$docker_name"
rm -f /home/docker/${docker_name}_port.conf
- echo "앱이 제거되었습니다"
- send_stats "제거하십시오$docker_name"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "앱이 제거되었습니다."
+ send_stats "제거$docker_name"
;;
5)
- echo "${docker_name}도메인 액세스 설정"
- send_stats "${docker_name}도메인 액세스 설정"
+ echo "${docker_name}도메인 이름 액세스 설정"
+ send_stats "${docker_name}도메인 이름 액세스 설정"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
block_container_port "$docker_name" "$ipv4_address"
;;
6)
- echo "도메인 이름 형식 example.com은 https : //와 함께 제공되지 않습니다."
+ echo "https://가 없는 도메인 이름 형식 example.com"
web_del
;;
@@ -2800,7 +2965,7 @@ while true; do
;;
8)
- send_stats "IP 액세스를 차단하십시오${docker_name}"
+ send_stats "IP 접근 차단${docker_name}"
block_container_port "$docker_name" "$ipv4_address"
;;
@@ -2817,7 +2982,6 @@ done
-
docker_app_plus() {
send_stats "$app_name"
while true; do
@@ -2827,9 +2991,10 @@ docker_app_plus() {
echo -e "$app_name $check_docker $update_status"
echo "$app_text"
echo "$app_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_check_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
- local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
+ local docker_port=$(docker port "$docker_check_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
fi
@@ -2841,39 +3006,63 @@ docker_app_plus() {
echo "1. 설치 2. 업데이트 3. 제거"
echo "------------------------"
echo "5. 도메인 이름 액세스 추가 6. 도메인 이름 액세스 삭제"
- echo "7. IP+ 포트 액세스 허용 8. 블록 IP+ 포트 액세스"
+ echo "7. IP+포트 접근 허용 8. IP+포트 접근 차단"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "응용 프로그램 외부 서비스 포트를 입력하고 기본값을 입력하십시오.${docker_port}포트:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+
+ while true; do
+ read -e -p "애플리케이션 외부 서비스 포트를 입력하고 Enter 키를 누르면 기본적으로 사용됩니다.${docker_port}포트:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}실수:${gl_bai}포트$app_port이미 사용 중입니다. 포트를 변경하세요."
+ send_stats "애플리케이션 포트가 점유되어 있습니다."
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
+
install jq
install_docker
docker_app_install
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+
+ add_app_id
+ send_stats "$app_name설치하다"
;;
+
2)
docker_app_update
+ add_app_id
+ send_stats "$app_name고쳐 쓰다"
;;
+
3)
docker_app_uninstall
rm -f /home/docker/${docker_name}_port.conf
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "$app_name제거"
;;
+
5)
- echo "${docker_name}도메인 액세스 설정"
- send_stats "${docker_name}도메인 액세스 설정"
+ echo "${docker_name}도메인 이름 액세스 설정"
+ send_stats "${docker_name}도메인 이름 액세스 설정"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
+
;;
6)
- echo "도메인 이름 형식 example.com은 https : //와 함께 제공되지 않습니다."
+ echo "https://가 없는 도메인 이름 형식 example.com"
web_del
;;
7)
@@ -2881,8 +3070,9 @@ docker_app_plus() {
clear_container_rules "$docker_name" "$ipv4_address"
;;
8)
- send_stats "IP 액세스를 차단하십시오${docker_name}"
- block_container_port "$docker_name" "$ipv4_address"
+ send_stats "IP 접근 차단${docker_name}"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
;;
*)
break
@@ -2920,7 +3110,7 @@ docker network create $NETWORK_NAME
docker run -d \
--name=node-exporter \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
prom/node-exporter
# Run Prometheus container
@@ -2929,7 +3119,7 @@ docker run -d \
-v $PROMETHEUS_DIR/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $PROMETHEUS_DIR/data:/prometheus \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
--user 0:0 \
prom/prometheus:latest
@@ -2939,7 +3129,7 @@ docker run -d \
-p ${docker_port}:3000 \
-v $GRAFANA_DIR:/var/lib/grafana \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
grafana/grafana:latest
}
@@ -2966,17 +3156,17 @@ tmux_run_d() {
local base_name="tmuxd"
local tmuxd_ID=1
-# 세션이 존재하는지 확인하는 기능
+# 세션이 존재하는지 확인하는 함수
session_exists() {
tmux has-session -t $1 2>/dev/null
}
-# 존재하지 않는 세션 이름이 발견 될 때까지 루프
+# 존재하지 않는 세션 이름을 찾을 때까지 반복
while session_exists "$base_name-$tmuxd_ID"; do
local tmuxd_ID=$((tmuxd_ID + 1))
done
-# 새로운 TMUX 세션을 만듭니다
+# 새 tmux 세션 만들기
tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
@@ -2985,52 +3175,41 @@ tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
f2b_status() {
- docker exec -it fail2ban fail2ban-client reload
+ fail2ban-client reload
sleep 3
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
}
f2b_status_xxx() {
- docker exec -it fail2ban fail2ban-client status $xxx
+ fail2ban-client status $xxx
}
-f2b_install_sshd() {
-
- docker run -d \
- --name=fail2ban \
- --net=host \
- --cap-add=NET_ADMIN \
- --cap-add=NET_RAW \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e VERBOSITY=-vv \
- -v /path/to/fail2ban/config:/config \
- -v /var/log:/var/log:ro \
- -v /home/web/log/nginx/:/remotelogs/nginx:ro \
- --restart unless-stopped \
- lscr.io/linuxserver/fail2ban:latest
-
- sleep 3
- if grep -q 'Alpine' /etc/issue; then
- cd /path/to/fail2ban/config/fail2ban/filter.d
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd.conf
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd-ddos.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-ssh.conf
- elif command -v dnf &>/dev/null; then
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+check_f2b_status() {
+ if command -v fail2ban-client >/dev/null 2>&1; then
+ check_f2b_status="${gl_lv}설치됨${gl_bai}"
else
+ check_f2b_status="${gl_hui}설치되지 않음${gl_bai}"
+ fi
+}
+
+f2b_install_sshd() {
+
+ docker rm -f fail2ban >/dev/null 2>&1
+ install fail2ban
+ start fail2ban
+ enable fail2ban
+
+ if command -v dnf &>/dev/null; then
+ cd /etc/fail2ban/jail.d/
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
+ fi
+
+ if command -v apt &>/dev/null; then
install rsyslog
systemctl start rsyslog
systemctl enable rsyslog
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/linux-ssh.conf
- systemctl restart rsyslog
fi
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf
}
f2b_sshd() {
@@ -3043,6 +3222,80 @@ f2b_sshd() {
fi
}
+# 기본 매개변수 구성: 금지 기간(bantime), 기간(findtime), 재시도 횟수(maxretry)
+# 설명하다:
+# - /etc/fail2ban/jail.d/sshd.local에 대한 쓰기 우선 순위를 지정합니다(기본 감옥 구성을 재정의하며 업그레이드 시 손실되기 쉽지 않습니다).
+# - Alpine이고 감옥 이름이 다른 경우에도 sshd.local을 작성하세요. Fail2Ban은 감옥 이름에 따라 일치합니다.
+f2b_basic_config() {
+ root_use
+ install nano
+
+ if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo -e "${gl_hui}FAIL2BAN-클라이언트가 감지되지 않습니다. 먼저 Fail2ban을 설치하십시오.${gl_bai}"
+ return
+ fi
+
+ local jail_name="sshd"
+ if grep -qi 'Alpine' /etc/issue 2>/dev/null; then
+ # 알파인 기본 감옥은 일반적으로 sshd입니다. 사용자 정의 alpine-sshd 규칙이 감지된 경우에만 전환됩니다.
+ if [ -f /etc/fail2ban/filter.d/alpine-sshd.conf ] || [ -f /etc/fail2ban/jail.d/alpine-ssh.conf ] || [ -f /etc/fail2ban/jail.d/alpine-sshd.local ]; then
+ jail_name="alpine-sshd"
+ fi
+ fi
+
+ echo "SSH 감옥을 구성하려고 합니다:$jail_name"
+ read -e -p "금지 시간 금지 시간(초/분/시간, 예: 3600 또는 1h) [기본값 1h]:" bantime
+ read -e -p "시간 창 찾기 시간(초/분/시간, 예: 600 또는 10m) [기본값 10m]:" findtime
+ read -e -p "재시도 횟수 maxretry(정수) [기본값 5]:" maxretry
+
+ bantime=${bantime:-1h}
+ findtime=${findtime:-10m}
+ maxretry=${maxretry:-5}
+
+ mkdir -p /etc/fail2ban/jail.d
+ cat > /etc/fail2ban/jail.d/sshd.local <> /etc/fail2ban/jail.d/sshd.local
+ fi
+ fi
+
+ echo -e "${gl_lv}구성이 작성되었습니다.${gl_bai}: /etc/fail2ban/jail.d/sshd.local"
+ fail2ban-client reload >/dev/null 2>&1 || true
+ sleep 2
+ fail2ban-client status $jail_name || true
+}
+
+# 기본 구성/오버레이 구성 편집기(나노)를 직접 엽니다.
+# /etc/fail2ban/jail.d/sshd.local을 먼저 편집하고(보다 안전함) 존재하지 않는 경우 생성합니다.
+f2b_edit_config() {
+ root_use
+ install nano
+
+ if [ ! -d /etc/fail2ban ]; then
+ echo -e "${gl_hui}/etc/fail2ban이 존재하지 않습니다. 먼저 fall2ban을 설치하십시오.${gl_bai}"
+ return
+ fi
+
+ mkdir -p /etc/fail2ban/jail.d
+ local cfg="/etc/fail2ban/jail.d/sshd.local"
+ [ -f "$cfg" ] || printf "[sshd]\n# bantime/findtime/maxretry\n" > "$cfg"
+
+ nano "$cfg"
+ echo -e "${gl_lv}저장됨${gl_bai}, Fail2ban을 다시 로드하는 중..."
+ fail2ban-client reload >/dev/null 2>&1 || true
+}
@@ -3051,7 +3304,7 @@ server_reboot() {
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}现在重启服务器吗?(Y/N): ")" rboot
case "$rboot" in
[Yy])
- echo "다시 시작"
+ echo "다시 시작됨"
reboot
;;
*)
@@ -3099,7 +3352,7 @@ ldnmp_install_status_one() {
if docker inspect "php" &>/dev/null; then
clear
send_stats "LDNMP 환경을 다시 설치할 수 없습니다"
- echo -e "${gl_huang}힌트:${gl_bai}웹 사이트 구성 환경이 설치되었습니다. 다시 설치할 필요가 없습니다!"
+ echo -e "${gl_huang}힌트:${gl_bai}홈페이지 구축 환경이 설치되었습니다. 다시 설치할 필요가 없습니다!"
break_end
linux_ldnmp
fi
@@ -3109,12 +3362,11 @@ ldnmp_install_status_one() {
ldnmp_install_all() {
cd ~
-send_stats "LDNMP 환경을 설치하십시오"
+send_stats "LDNMP 환경 설치"
root_use
clear
-echo -e "${gl_huang}LDNMP 환경이 설치되지 않았으며 LDNMP 환경 설치를 시작하십시오 ...${gl_bai}"
-check_disk_space 3
-check_port
+echo -e "${gl_huang}LDNMP 환경이 설치되지 않았습니다. LDNMP 환경 설치를 시작합니다...${gl_bai}"
+check_disk_space 3 /home
install_dependency
install_docker
install_certbot
@@ -3126,12 +3378,10 @@ install_ldnmp
nginx_install_all() {
cd ~
-send_stats "Nginx 환경을 설치하십시오"
+send_stats "nginx 환경 설치"
root_use
clear
-echo -e "${gl_huang}Nginx가 설치되지 않았고 Nginx 환경 설치 시작 ...${gl_bai}"
-check_disk_space 1
-check_port
+echo -e "${gl_huang}nginx가 설치되지 않았습니다. nginx 환경 설치를 시작하세요...${gl_bai}"
install_dependency
install_docker
install_certbot
@@ -3140,8 +3390,8 @@ nginx_upgrade
clear
local nginx_version=$(docker exec nginx nginx -v 2>&1)
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
-echo "Nginx가 설치되었습니다"
-echo -e "현재 버전 :${gl_huang}v$nginx_version${gl_bai}"
+echo "nginx가 설치되었습니다"
+echo -e "현재 버전:${gl_huang}v$nginx_version${gl_bai}"
echo ""
}
@@ -3152,7 +3402,7 @@ echo ""
ldnmp_install_status() {
if ! docker inspect "php" &>/dev/null; then
- send_stats "먼저 LDNMP 환경을 설치하십시오"
+ send_stats "먼저 LDNMP 환경을 설치해 주세요"
ldnmp_install_all
fi
@@ -3162,7 +3412,7 @@ ldnmp_install_status() {
nginx_install_status() {
if ! docker inspect "nginx" &>/dev/null; then
- send_stats "Nginx 환경을 먼저 설치하십시오"
+ send_stats "먼저 nginx 환경을 설치해주세요"
nginx_install_all
fi
@@ -3173,18 +3423,29 @@ nginx_install_status() {
ldnmp_web_on() {
clear
- echo "당신 것$webname세워짐!"
+ echo "당신의$webname건설되었습니다!"
echo "https://$yuming"
echo "------------------------"
- echo "$webname설치 정보는 다음과 같습니다."
+ echo "$webname설치정보는 다음과 같습니다."
}
nginx_web_on() {
- clear
- echo "당신 것$webname세워짐!"
- echo "https://$yuming"
+ clear
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ echo "당신의$webname건설되었습니다!"
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ mv /home/web/conf.d/"$yuming".conf /home/web/conf.d/"${yuming}_${access_port}".conf
+ echo "http://$yuming:$access_port"
+ elif grep -q '^[[:space:]]*#.*if (\$scheme = http)' "/home/web/conf.d/"$yuming".conf"; then
+ echo "http://$yuming"
+ else
+ echo "https://$yuming"
+ fi
}
@@ -3195,73 +3456,95 @@ ldnmp_wp() {
webname="WordPress"
yuming="${1:-}"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
if [ -z "$yuming" ]; then
add_yuming
fi
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/wordpress.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
nginx_http_on
+
cd /home/web/html
mkdir $yuming
cd $yuming
wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/refs/heads/main/wp-latest.zip
- # wget -O latest.zip https://cn.wordpress.org/latest-zh_CN.zip
- # wget -O latest.zip https://wordpress.org/latest.zip
unzip latest.zip
rm latest.zip
- echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
+ echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379'); define('WP_REDIS_MAXTTL', 86400); define('WP_CACHE_KEY_SALT', '${yuming}_');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|database_name_here|$dbname|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|username_here|$dbuse|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|password_here|$dbusepasswd|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|localhost|mysql|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
+ patch_wp_url "https://$yuming" "https://$yuming"
cp /home/web/html/$yuming/wordpress/wp-config-sample.php /home/web/html/$yuming/wordpress/wp-config.php
+
restart_ldnmp
nginx_web_on
-# Echo "데이터베이스 이름 : $ dbname"
-# Echo "사용자 이름 : $ dbuse"
-# echo "비밀번호 : $ dbusepasswd"
-# Echo "데이터베이스 주소 : mysql"
-# 에코 "테이블 접두사 : wp_"
}
+
ldnmp_Proxy() {
clear
- webname="反向代理-IP+端口"
+ webname="역방향 프록시-IP+포트"
yuming="${1:-}"
reverseproxy="${2:-}"
port="${3:-}"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
if [ -z "$yuming" ]; then
add_yuming
fi
+
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy" ]; then
- read -e -p "반세대 IP를 입력하십시오 :" reverseproxy
+ read -e -p "세대 방지 IP를 입력하십시오(기본값은 로컬 IP 127.0.0.1로 설정하려면 Enter를 누르십시오)." reverseproxy
+ reverseproxy=${reverseproxy:-127.0.0.1}
fi
if [ -z "$port" ]; then
- read -e -p "반세대 포트를 입력하십시오 :" port
+ read -e -p "안티 세대 포트를 입력하십시오:" port
fi
nginx_install_status
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
+ wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/backend_$backend/g" /home/web/conf.d/"$yuming".conf
+
+
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$reverseproxy/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$port|g" /home/web/conf.d/$yuming.conf
+
+ reverseproxy_port="$reverseproxy:$port"
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 동적으로 추가/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i '/remote_addr/d' /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3271,24 +3554,25 @@ ldnmp_Proxy() {
ldnmp_Proxy_backend() {
clear
- webname="反向代理-负载均衡"
- yuming="${1:-}"
- reverseproxy_port="${2:-}"
+ webname="역방향 프록시-로드 밸런싱"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
if [ -z "$yuming" ]; then
add_yuming
fi
- # 사용자가 입력 한 여러 IP를 얻습니다 : 포트 (공간별로 분리)
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy_port" ]; then
- read -e -p "공간으로 분리 된 여러 반세대 IP+ 포트를 입력하십시오 (예 : 127.0.0.1:3000 127.0.1:3002) :" reverseproxy_port
+ read -e -p "여러 개의 세대 방지 IP+포트를 공백으로 구분하여 입력하세요(예: 127.0.0.1:3000 127.0.0.1:3002)." reverseproxy_port
fi
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
@@ -3298,14 +3582,15 @@ ldnmp_Proxy_backend() {
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- # 상류 구성을 동적으로 생성합니다
upstream_servers=""
for server in $reverseproxy_port; do
upstream_servers="$upstream_servers server $server;\n"
done
- # 템플릿의 자리 표시자를 교체하십시오
- sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i "s/# 동적으로 추가/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+
+
+ update_nginx_listen_port "$yuming" "$access_port"
nginx_http_on
docker exec nginx nginx -s reload
@@ -3314,6 +3599,200 @@ ldnmp_Proxy_backend() {
+
+
+
+list_stream_services() {
+
+ STREAM_DIR="/home/web/stream.d"
+ printf "%-25s %-18s %-25s %-20s\n" "서비스 이름" "통신 유형" "현지 주소" "백엔드 주소"
+
+ if [ -z "$(ls -A "$STREAM_DIR")" ]; then
+ return
+ fi
+
+ for conf in "$STREAM_DIR"/*; do
+ # 서비스 이름은 파일 이름을 사용합니다.
+ service_name=$(basename "$conf" .conf)
+
+ # 업스트림 블록에서 서버 백엔드 IP:포트 가져오기
+ backend=$(grep -Po '(?<=server )[^;]+' "$conf" | head -n1)
+
+ # 청취 포트 가져오기
+ listen_port=$(grep -Po '(?<=listen )[^;]+' "$conf" | head -n1)
+
+ # 기본 로컬 IP
+ ip_address
+ local_ip="$ipv4_address"
+
+ # 먼저 파일 이름 접미사 또는 내용으로 판단하여 통신 유형을 가져옵니다.
+ if grep -qi 'udp;' "$conf"; then
+ proto="udp"
+ else
+ proto="tcp"
+ fi
+
+ # 스플라이스 청취 IP:포트
+ local_addr="$local_ip:$listen_port"
+
+ printf "%-22s %-14s %-21s %-20s\n" "$service_name" "$proto" "$local_addr" "$backend"
+ done
+}
+
+
+
+
+
+
+
+
+
+stream_panel() {
+ send_stats "4계층 프록시 스트리밍"
+ local app_id="104"
+ local docker_name="nginx"
+
+ while true; do
+ clear
+ check_docker_app
+ check_docker_image_update $docker_name
+ echo -e "스트림 4계층 프록시 전달 도구$check_docker $update_status"
+ echo "NGINX 스트림은 고성능 전송 계층 트래픽 전달 및 로드 밸런싱을 달성하는 데 사용되는 NGINX의 TCP/UDP 프록시 모듈입니다."
+ echo "------------------------"
+ if [ -d "/home/web/stream.d" ]; then
+ list_stream_services
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 설치 2. 업데이트 3. 제거"
+ echo "------------------------"
+ echo "4. 포워딩 서비스 추가 5. 포워딩 서비스 수정 6. 포워딩 서비스 삭제"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택 항목을 입력하세요." choice
+ case $choice in
+ 1)
+ nginx_install_status
+ add_app_id
+ send_stats "Stream 4계층 에이전트 설치"
+ ;;
+ 2)
+ update_docker_compose_with_db_creds
+ nginx_upgrade
+ add_app_id
+ send_stats "Stream 4계층 프록시 업데이트"
+ ;;
+ 3)
+ read -e -p "nginx 컨테이너를 삭제하시겠습니까? 이는 웹사이트 기능에 영향을 미칠 수 있습니다! (예/아니요):" confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ docker rm -f nginx
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "Stream 4계층 프록시 업데이트"
+ echo "nginx 컨테이너가 삭제되었습니다."
+ else
+ echo "작업이 취소되었습니다."
+ fi
+
+ ;;
+
+ 4)
+ ldnmp_Proxy_backend_stream
+ add_app_id
+ send_stats "레이어 4 프록시 추가"
+ ;;
+ 5)
+ send_stats "전달 구성 수정"
+ read -e -p "편집하려는 서비스 이름을 입력하십시오:" stream_name
+ install nano
+ nano /home/web/stream.d/$stream_name.conf
+ docker restart nginx
+ send_stats "레이어 4 프록시 수정"
+ ;;
+ 6)
+ send_stats "전달 구성 삭제"
+ read -e -p "삭제하려는 서비스 이름을 입력하세요." stream_name
+ rm /home/web/stream.d/$stream_name.conf > /dev/null 2>&1
+ docker restart nginx
+ send_stats "레이어 4 프록시 삭제"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ break_end
+ done
+}
+
+
+
+ldnmp_Proxy_backend_stream() {
+ clear
+ webname="스트림 4계층 프록시-로드 밸런싱"
+
+ send_stats "설치하다$webname"
+ echo "배포 시작$webname"
+
+ # 에이전트 이름 가져오기
+ read -erp "프록시 전달 이름(예: mysql_proxy)을 입력하세요." proxy_name
+ if [ -z "$proxy_name" ]; then
+ echo "이름은 비워둘 수 없습니다."; return 1
+ fi
+
+ # 수신 포트 가져오기
+ read -erp "로컬 수신 포트(예: 3306)를 입력하십시오." listen_port
+ if ! [[ "$listen_port" =~ ^[0-9]+$ ]]; then
+ echo "포트는 숫자여야 합니다."; return 1
+ fi
+
+ echo "계약 유형을 선택하십시오:"
+ echo "1. TCP 2. UDP"
+ read -erp "일련 번호 [1-2]를 입력하십시오:" proto_choice
+
+ case "$proto_choice" in
+ 1) proto="tcp"; listen_suffix="" ;;
+ 2) proto="udp"; listen_suffix=" udp" ;;
+ *) echo "잘못된 선택"; return 1 ;;
+ esac
+
+ read -e -p "하나 이상의 백엔드 IP+포트를 공백으로 구분하여 입력하세요(예: 10.13.0.2:3306 10.13.0.3:3306)." reverseproxy_port
+
+ nginx_install_status
+ cd /home && mkdir -p web/stream.d
+ grep -q '^[[:space:]]*stream[[:space:]]*{' /home/web/nginx.conf || echo -e '\nstream {\n include /etc/nginx/stream.d/*.conf;\n}' | tee -a /home/web/nginx.conf
+ wget -O /home/web/stream.d/$proxy_name.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend-stream.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/${proxy_name}_${backend}/g" /home/web/stream.d/"$proxy_name".conf
+ sed -i "s|listen 80|listen $listen_port $listen_suffix|g" /home/web/stream.d/$proxy_name.conf
+ sed -i "s|listen \[::\]:|listen [::]:${listen_port} ${listen_suffix}|g" "/home/web/stream.d/${proxy_name}.conf"
+
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 동적으로 추가/$upstream_servers/g" /home/web/stream.d/$proxy_name.conf
+
+ docker exec nginx nginx -s reload
+ clear
+ echo "당신의$webname건설되었습니다!"
+ echo "------------------------"
+ echo "방문 주소:"
+ ip_address
+ if [ -n "$ipv4_address" ]; then
+ echo "$ipv4_address:${listen_port}"
+ fi
+ if [ -n "$ipv6_address" ]; then
+ echo "$ipv6_address:${listen_port}"
+ fi
+ echo ""
+}
+
+
+
+
+
find_container_by_host_port() {
port="$1"
docker_name=$(docker ps --format '{{.ID}} {{.Names}}' | while read id name; do
@@ -3331,11 +3810,11 @@ ldnmp_web_status() {
root_use
while true; do
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
- local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+ local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
- local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+ local db_output="${gl_lv}${db_count}${gl_bai}"
clear
send_stats "LDNMP 사이트 관리"
@@ -3343,8 +3822,7 @@ ldnmp_web_status() {
echo "------------------------"
ldnmp_v
- # ls -t /home/web/conf.d | sed 's/\.[^.]*$//'
- echo -e "${output}인증서 만료 시간"
+ echo -e "대지:${output}인증서 만료 시간"
echo -e "------------------------"
for cert_file in /home/web/certs/*_cert.pem; do
local domain=$(basename "$cert_file" | sed 's/_cert.pem//')
@@ -3355,9 +3833,28 @@ ldnmp_web_status() {
fi
done
+ for conf_file in /home/web/conf.d/*_*.conf; do
+ [ -e "$conf_file" ] || continue
+ basename "$conf_file" .conf
+ done
+
+ for conf_file in /home/web/conf.d/*.conf; do
+ [ -e "$conf_file" ] || continue
+
+ filename=$(basename "$conf_file")
+
+ if [ "$filename" = "map.conf" ] || [ "$filename" = "default.conf" ]; then
+ continue
+ fi
+
+ if ! grep -q "ssl_certificate" "$conf_file"; then
+ basename "$conf_file" .conf
+ fi
+ done
+
echo "------------------------"
echo ""
- echo -e "${db_output}"
+ echo -e "데이터 베이스:${db_output}"
echo -e "------------------------"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys"
@@ -3371,46 +3868,42 @@ ldnmp_web_status() {
echo ""
echo "작동하다"
echo "------------------------"
- echo "1. 도메인 이름 인증서 신청/업데이트 2. 사이트 도메인 이름 변경"
- echo "3. 사이트 캐시 정리 4. 관련 사이트 만들기"
- echo "5. 액세스 로그보기 6. 오류 로그보기"
- echo "7. 글로벌 구성 편집 8. 사이트 구성 편집"
- echo "9. 사이트 데이터베이스 관리 10. 사이트 분석 보고서보기"
+ echo "1. 도메인 이름 인증서 적용/업데이트 2. 사이트 도메인 이름 복제"
+ echo "3. 사이트 캐시 지우기 4. 관련 사이트 생성"
+ echo "5. 접속 로그 보기 6. 오류 로그 보기"
+ echo "7. 전역 구성 편집 8. 사이트 구성 편집"
+ echo "9. 사이트 데이터베이스 관리 10. 사이트 분석 보고서 보기"
echo "------------------------"
- echo "20. 지정된 사이트 데이터를 삭제합니다"
+ echo "20. 특정 사이트 데이터 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- send_stats "도메인 이름 인증서를 신청하십시오"
- read -e -p "도메인 이름을 입력하십시오 :" yuming
+ send_stats "도메인 이름 인증서 신청"
+ read -e -p "도메인 이름을 입력하세요:" yuming
install_certbot
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
;;
2)
- send_stats "사이트 도메인 이름을 변경하십시오"
- echo -e "${gl_hong}적극 권장 :${gl_bai}먼저 전체 사이트 데이터를 백업 한 다음 사이트 도메인 이름을 변경하십시오!"
- read -e -p "이전 도메인 이름을 입력하십시오 :" oddyuming
- read -e -p "새 도메인 이름을 입력하십시오 :" yuming
+ send_stats "복제 사이트 도메인 이름"
+ read -e -p "이전 도메인 이름을 입력하세요:" oddyuming
+ read -e -p "새 도메인 이름을 입력하세요:" yuming
install_certbot
install_ssltls
certs_status
- # MySQL 교체
- add_db
+ add_db
local odd_dbname=$(echo "$oddyuming" | sed -e 's/[^A-Za-z0-9]/_/g')
local odd_dbname="${odd_dbname}"
docker exec mysql mysqldump -u root -p"$dbrootpasswd" $odd_dbname | docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname
- docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE $odd_dbname;"
-
local tables=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -D $dbname -e "SHOW TABLES;" | awk '{ if (NR>1) print $1 }')
for table in $tables; do
@@ -3420,19 +3913,16 @@ ldnmp_web_status() {
done
done
- # 웹 사이트 디렉토리 교체
- mv /home/web/html/$oddyuming /home/web/html/$yuming
+ # 웹사이트 디렉토리 교체
+ cp -r /home/web/html/$oddyuming /home/web/html/$yuming
find /home/web/html/$yuming -type f -exec sed -i "s/$odd_dbname/$dbname/g" {} +
find /home/web/html/$yuming -type f -exec sed -i "s/$oddyuming/$yuming/g" {} +
- mv /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
+ cp /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
sed -i "s/$oddyuming/$yuming/g" /home/web/conf.d/$yuming.conf
- rm /home/web/certs/${oddyuming}_key.pem
- rm /home/web/certs/${oddyuming}_cert.pem
-
- docker exec nginx nginx -s reload
+ cd /home/web && docker compose restart
;;
@@ -3441,10 +3931,10 @@ ldnmp_web_status() {
web_cache
;;
4)
- send_stats "관련 사이트를 만듭니다"
- echo -e "액세스 용 기존 사이트의 새 도메인 이름을 연결하십시오."
- read -e -p "기존 도메인 이름을 입력하십시오 :" oddyuming
- read -e -p "새 도메인 이름을 입력하십시오 :" yuming
+ send_stats "연결된 사이트 만들기"
+ echo -e "액세스하려면 새 도메인 이름을 기존 사이트에 연결하세요."
+ read -e -p "기존 도메인 이름을 입력하세요:" oddyuming
+ read -e -p "새 도메인 이름을 입력하세요:" yuming
install_certbot
install_ssltls
certs_status
@@ -3458,17 +3948,17 @@ ldnmp_web_status() {
;;
5)
- send_stats "액세스 로그를 봅니다"
+ send_stats "접속 로그 보기"
tail -n 200 /home/web/log/nginx/access.log
break_end
;;
6)
- send_stats "오류 로그를 봅니다"
+ send_stats "오류 로그 보기"
tail -n 200 /home/web/log/nginx/error.log
break_end
;;
7)
- send_stats "글로벌 구성 편집"
+ send_stats "전역 구성 편집"
install nano
nano /home/web/nginx.conf
docker exec nginx nginx -s reload
@@ -3476,7 +3966,7 @@ ldnmp_web_status() {
8)
send_stats "사이트 구성 편집"
- read -e -p "사이트 구성을 편집하려면 편집 할 도메인 이름을 입력하십시오." yuming
+ read -e -p "사이트 구성을 편집하려면 편집하려는 도메인 이름을 입력하십시오:" yuming
install nano
nano /home/web/conf.d/$yuming.conf
docker exec nginx nginx -s reload
@@ -3486,14 +3976,14 @@ ldnmp_web_status() {
break_end
;;
10)
- send_stats "사이트 데이터를 봅니다"
+ send_stats "사이트 데이터 보기"
install goaccess
goaccess --log-format=COMBINED /home/web/log/nginx/access.log
;;
20)
web_del
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
;;
*)
@@ -3508,7 +3998,7 @@ ldnmp_web_status() {
check_panel_app() {
if $lujing > /dev/null 2>&1; then
- check_panel="${gl_lv}已安装${gl_bai}"
+ check_panel="${gl_lv}설치됨${gl_bai}"
else
check_panel=""
fi
@@ -3522,32 +4012,38 @@ while true; do
clear
check_panel_app
echo -e "$panelname $check_panel"
- echo "${panelname}요즘 인기 있고 강력한 운영 및 유지 관리 패널입니다."
- echo "공식 웹 사이트 소개 :$panelurl "
+ echo "${panelname}대중적이고 강력한 운영 및 유지 관리 관리 패널입니다."
+ echo "공식 웹사이트 소개:$panelurl "
echo ""
echo "------------------------"
echo "1. 설치 2. 관리 3. 제거"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case $choice in
1)
check_disk_space 1
install wget
iptables_open
panel_app_install
+
+ add_app_id
send_stats "${panelname}설치하다"
;;
2)
panel_app_manage
+
+ add_app_id
send_stats "${panelname}제어"
;;
3)
panel_app_uninstall
- send_stats "${panelname}제거하십시오"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "${panelname}제거"
;;
*)
break
@@ -3563,9 +4059,9 @@ done
check_frp_app() {
if [ -d "/home/frp/" ]; then
- check_frp="${gl_lv}已安装${gl_bai}"
+ check_frp="${gl_lv}설치됨${gl_bai}"
else
- check_frp="${gl_hui}未安装${gl_bai}"
+ check_frp="${gl_hui}설치되지 않음${gl_bai}"
fi
}
@@ -3591,8 +4087,8 @@ donlond_frp() {
generate_frps_config() {
- send_stats "FRP 서버를 설치하십시오"
- # 임의의 포트 및 자격 증명을 생성합니다
+ send_stats "FRP 서버 설치"
+ # 임의의 포트 및 자격 증명 생성
local bind_port=8055
local dashboard_port=8056
local token=$(openssl rand -hex 16)
@@ -3613,17 +4109,17 @@ EOF
donlond_frp frps
- # 출력 생성 정보
+ # 생성된 정보 출력
ip_address
echo "------------------------"
- echo "클라이언트 배포에 필요한 매개 변수"
- echo "서비스 IP :$ipv4_address"
+ echo "클라이언트 배포에 필요한 매개변수"
+ echo "서비스 IP:$ipv4_address"
echo "token: $token"
echo
echo "FRP 패널 정보"
- echo "FRP 패널 주소 : http : //$ipv4_address:$dashboard_port"
- echo "FRP 패널 사용자 이름 :$dashboard_user"
- echo "FRP 패널 비밀번호 :$dashboard_pwd"
+ echo "FRP 패널 주소: http://$ipv4_address:$dashboard_port"
+ echo "FRP 패널 사용자 이름:$dashboard_user"
+ echo "FRP 패널 비밀번호:$dashboard_pwd"
echo
open_port 8055 8056
@@ -3633,9 +4129,9 @@ EOF
configure_frpc() {
- send_stats "FRP 클라이언트를 설치하십시오"
- read -e -p "외부 네트워크 도킹 IP를 입력하십시오." server_addr
- read -e -p "외부 네트워크 도킹 토큰을 입력하십시오." token
+ send_stats "FRP 클라이언트 설치"
+ read -e -p "외부 네트워크 도킹 IP를 입력하세요." server_addr
+ read -e -p "외부 네트워크 도킹 토큰을 입력하세요." token
echo
mkdir -p /home/frp
@@ -3655,17 +4151,17 @@ EOF
}
add_forwarding_service() {
- send_stats "FRP 인트라넷 서비스를 추가하십시오"
- # 사용자에게 서비스 이름 및 전달 정보를 입력하라는 메시지
- read -e -p "서비스 이름을 입력하십시오 :" service_name
- read -e -p "전달 유형 (TCP/UDP)을 입력하십시오 [기본 TCP 입력] :" service_type
+ send_stats "FRP 인트라넷 서비스 추가"
+ # 사용자에게 서비스 이름 및 전달 정보를 묻는 메시지를 표시합니다.
+ read -e -p "서비스 이름을 입력하세요:" service_name
+ read -e -p "전달 유형(tcp/udp)을 입력하십시오. [기본값을 tcp로 입력]:" service_type
local service_type=${service_type:-tcp}
- read -e -p "인트라넷 IP를 입력하십시오 [기본값 127.0.0.1 입력] : :" local_ip
+ read -e -p "인트라넷 IP를 입력하십시오. [Enter를 누르면 기본값은 127.0.0.1입니다]:" local_ip
local local_ip=${local_ip:-127.0.0.1}
- read -e -p "인트라넷 포트를 입력하십시오 :" local_port
- read -e -p "외부 네트워크 포트를 입력하십시오 :" remote_port
+ read -e -p "인트라넷 포트를 입력하십시오:" local_port
+ read -e -p "외부 네트워크 포트를 입력하세요:" remote_port
- # 구성 파일에 사용자 입력을 쓰십시오
+ # 구성 파일에 사용자 입력 쓰기
cat <> /home/frp/frpc.toml
[$service_name]
type = ${service_type}
@@ -3675,8 +4171,8 @@ remote_port = ${remote_port}
EOF
- # 출력 생성 정보
- echo "제공하다$service_namefrpc.toml에 성공적으로 추가되었습니다"
+ # 생성된 정보 출력
+ echo "제공하다$service_namefrpc.toml에 성공적으로 추가되었습니다."
docker restart frpc
@@ -3687,12 +4183,12 @@ EOF
delete_forwarding_service() {
- send_stats "FRP 인트라넷 서비스를 삭제하십시오"
- # 삭제 해야하는 서비스 이름을 입력하라는 메시지
- read -e -p "삭제 해야하는 서비스 이름을 입력하십시오." service_name
- # SED를 사용하여 서비스 및 관련 구성을 삭제하십시오.
+ send_stats "FRP 인트라넷 서비스 삭제"
+ # 삭제해야 하는 서비스 이름을 입력하라는 메시지를 사용자에게 표시합니다.
+ read -e -p "삭제할 서비스 이름을 입력하세요:" service_name
+ # sed를 사용하여 서비스 및 관련 구성 삭제
sed -i "/\[$service_name\]/,/^$/d" /home/frp/frpc.toml
- echo "제공하다$service_namefrpc.toml에서 성공적으로 삭제되었습니다"
+ echo "제공하다$service_namefrpc.toml에서 성공적으로 제거되었습니다."
docker restart frpc
@@ -3702,8 +4198,8 @@ delete_forwarding_service() {
list_forwarding_services() {
local config_file="$1"
- # 헤더를 인쇄하십시오
- printf "%-20s %-25s %-30s %-10s\n" "服务名称" "内网地址" "外网地址" "协议"
+ # 헤더 인쇄
+ printf "%-20s %-25s %-30s %-10s\n" "서비스 이름" "인트라넷 주소" "외부 네트워크 주소" "규약"
awk '
BEGIN {
@@ -3723,7 +4219,7 @@ list_forwarding_services() {
}
/^\[.*\]/ {
- # 서비스 정보가있는 경우 새 서비스를 처리하기 전에 현재 서비스를 인쇄하십시오.
+ # 서비스 정보가 이미 존재하는 경우 새 서비스를 처리하기 전에 현재 서비스를 인쇄하십시오.
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3732,11 +4228,11 @@ list_forwarding_services() {
type
}
- # 현재 서비스 이름을 업데이트하십시오
+ # 현재 서비스 이름 업데이트
if ($1 != "[common]") {
gsub(/[\[\]]/, "", $1)
current_service=$1
- # 이전 값을 지우십시오
+ # 이전 값 지우기
local_ip=""
local_port=""
remote_port=""
@@ -3765,7 +4261,7 @@ list_forwarding_services() {
}
END {
- # 마지막 서비스에 대한 정보를 인쇄하십시오
+ # 마지막 서비스에 대한 정보 인쇄
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3778,17 +4274,17 @@ list_forwarding_services() {
-# FRP 서버 포트를 가져옵니다
+# FRP 서버 포트 가져오기
get_frp_ports() {
mapfile -t ports < <(ss -tulnape | grep frps | awk '{print $5}' | awk -F':' '{print $NF}' | sort -u)
}
-# 액세스 주소를 생성합니다
+# 액세스 주소 생성
generate_access_urls() {
- # 모든 포트를 먼저 얻으십시오
+ # 먼저 모든 포트를 확보하십시오.
get_frp_ports
- # 8055/8056 이외의 포트가 있는지 확인하십시오
+ # 8055/8056 이외의 포트가 있는지 확인하세요.
local has_valid_ports=false
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3797,18 +4293,18 @@ generate_access_urls() {
fi
done
- # 유효한 포트가있을 때만 제목과 콘텐츠 표시
+ # 유효한 포트가 있는 경우에만 제목과 내용 표시
if [ "$has_valid_ports" = true ]; then
- echo "FRP 서비스 외부 액세스 주소 :"
+ echo "FRP 서비스 외부 액세스 주소:"
- # 프로세스 IPv4 주소
+ # IPv4 주소 처리
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
echo "http://${ipv4_address}:${port}"
fi
done
- # Process IPv6 주소 (현재 경우)
+ # IPv6 주소가 있는 경우 처리
if [ -n "$ipv6_address" ]; then
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3845,6 +4341,7 @@ frps_main_ports() {
frps_panel() {
send_stats "FRP 서버"
+ local app_id="55"
local docker_name="frps"
local docker_port=8056
while true; do
@@ -3852,9 +4349,9 @@ frps_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRP 서버$check_frp $update_status"
- echo "FRP 인트라넷 침투 서비스 환경을 구축하여 인터넷에 공개 IP없이 장치를 노출시킵니다."
- echo "공식 웹 사이트 소개 : https://github.com/fatedier/frp/"
- echo "비디오 교육 : https://www.bilibili.com/video/bv1ymw6e2ewl?t=124.0"
+ echo "FRP 인트라넷 침투 서비스 환경을 구축하고 공인 IP가 없는 장치를 인터넷에 노출시킵니다."
+ echo "공식 웹사이트 소개:${gh_https_url}github.com/fatedier/frp/"
+ echo "비디오 튜토리얼: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
if [ -d "/home/frp/" ]; then
check_docker_app_ip
frps_main_ports
@@ -3863,18 +4360,20 @@ frps_panel() {
echo "------------------------"
echo "1. 설치 2. 업데이트 3. 제거"
echo "------------------------"
- echo "5. 인트라넷 서비스에 대한 도메인 이름 액세스 6. 도메인 이름 액세스 삭제"
+ echo "5. 인트라넷 서비스 도메인 이름 접근 6. 도메인 이름 접근 삭제"
echo "------------------------"
- echo "7. IP+ 포트 액세스 허용 8. 블록 IP+ 포트 액세스"
+ echo "7. IP+포트 접근 허용 8. IP+포트 접근 차단"
echo "------------------------"
- echo "00. 서비스 상태 새로 고침 0. 이전 메뉴로 돌아갑니다."
+ echo "00. 서비스 상태 새로고침 0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
install jq grep ss
install_docker
generate_frps_config
+
+ add_app_id
echo "FRP 서버가 설치되었습니다"
;;
2)
@@ -3883,6 +4382,8 @@ frps_panel() {
docker rm -f frps && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frps.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frps.toml /home/frp/frps.toml
donlond_frp frps
+
+ add_app_id
echo "FRP 서버가 업데이트되었습니다"
;;
3)
@@ -3893,37 +4394,38 @@ frps_panel() {
close_port 8055 8056
- echo "앱이 제거되었습니다"
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "앱이 제거되었습니다."
;;
5)
- echo "리버스 인트라넷 침투 서비스를 도메인 이름 액세스로 향합니다"
- send_stats "외부 도메인 이름에 대한 FRP 액세스"
+ echo "도메인 이름 액세스에 대한 역인트라넷 침투 서비스"
+ send_stats "FRP 외부 도메인 이름 액세스"
add_yuming
- read -e -p "인트라넷 침투 서비스 포트를 입력하십시오 :" frps_port
+ read -e -p "인트라넷 침투 서비스 포트를 입력하십시오:" frps_port
ldnmp_Proxy ${yuming} 127.0.0.1 ${frps_port}
block_host_port "$frps_port" "$ipv4_address"
;;
6)
- echo "도메인 이름 형식 example.com은 https : //와 함께 제공되지 않습니다."
+ echo "https://가 없는 도메인 이름 형식 example.com"
web_del
;;
7)
send_stats "IP 액세스 허용"
- read -e -p "해제 할 포트를 입력하십시오." frps_port
+ read -e -p "해제해야 하는 포트를 입력하세요." frps_port
clear_host_port_rules "$frps_port" "$ipv4_address"
;;
8)
- send_stats "IP 액세스를 차단하십시오"
- echo "반세기 도메인 이름에 액세스 한 경우이 기능을 사용하여 IP+ 포트 액세스를 차단할 수 있습니다."
- read -e -p "차단 해야하는 포트를 입력하십시오." frps_port
+ send_stats "IP 접근 차단"
+ echo "역방향 도메인 이름 접근을 가지고 있는 경우, 이 기능을 사용하면 IP+포트 접근을 차단할 수 있어 더욱 안전합니다."
+ read -e -p "차단할 포트를 입력하세요:" frps_port
block_host_port "$frps_port" "$ipv4_address"
;;
00)
- send_stats "FRP 서비스 상태를 새로 고치십시오"
- echo "FRP 서비스 상태가 새로 고쳐졌습니다"
+ send_stats "FRP 서비스 상태 새로 고침"
+ echo "FRP 서비스 상태가 새로 고쳐졌습니다."
;;
*)
@@ -3937,6 +4439,7 @@ frps_panel() {
frpc_panel() {
send_stats "FRP 클라이언트"
+ local app_id="56"
local docker_name="frpc"
local docker_port=8055
while true; do
@@ -3944,9 +4447,9 @@ frpc_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRP 클라이언트$check_frp $update_status"
- echo "서버로 도킹, 도킹 후 인터넷 액세스에 인트라넷 침투 서비스를 만들 수 있습니다."
- echo "공식 웹 사이트 소개 : https://github.com/fatedier/frp/"
- echo "비디오 교육 : https://www.bilibili.com/video/bv1ymw6e2ewl?t=173.9"
+ echo "서버에 연결하세요. 연결 후에는 인트라넷 침투 서비스를 만들어 인터넷에 액세스할 수 있습니다."
+ echo "공식 웹사이트 소개:${gh_https_url}github.com/fatedier/frp/"
+ echo "비디오 튜토리얼: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
echo "------------------------"
if [ -d "/home/frp/" ]; then
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
@@ -3956,16 +4459,18 @@ frpc_panel() {
echo "------------------------"
echo "1. 설치 2. 업데이트 3. 제거"
echo "------------------------"
- echo "4. 외부 서비스 추가 5. 외부 서비스 삭제 6. 서비스 구성 수동으로 서비스 구성"
+ echo "4. 외부 서비스 추가 5. 외부 서비스 삭제 6. 서비스 수동 구성"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
install jq grep ss
install_docker
configure_frpc
+
+ add_app_id
echo "FRP 클라이언트가 설치되었습니다"
;;
2)
@@ -3974,7 +4479,9 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
donlond_frp frpc
- echo "FRP 클라이언트가 업데이트되었습니다"
+
+ add_app_id
+ echo "FRP 클라이언트가 업데이트되었습니다."
;;
3)
@@ -3983,7 +4490,9 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine
rm -rf /home/frp
close_port 8055
- echo "앱이 제거되었습니다"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "앱이 제거되었습니다."
;;
4)
@@ -4013,6 +4522,7 @@ frpc_panel() {
yt_menu_pro() {
+ local app_id="66"
local VIDEO_DIR="/home/yt-dlp"
local URL_FILE="$VIDEO_DIR/urls.txt"
local ARCHIVE_FILE="$VIDEO_DIR/archive.txt"
@@ -4022,68 +4532,74 @@ yt_menu_pro() {
while true; do
if [ -x "/usr/local/bin/yt-dlp" ]; then
- local YTDLP_STATUS="${gl_lv}已安装${gl_bai}"
+ local YTDLP_STATUS="${gl_lv}설치됨${gl_bai}"
else
- local YTDLP_STATUS="${gl_hui}未安装${gl_bai}"
+ local YTDLP_STATUS="${gl_hui}설치되지 않음${gl_bai}"
fi
clear
- send_stats "YT-DLP 다운로드 도구"
+ send_stats "yt-dlp 다운로드 도구"
echo -e "yt-dlp $YTDLP_STATUS"
- echo -e "YT-DLP는 YouTube, Bilibili, Twitter 등을 포함한 수천 개의 사이트를 지원하는 강력한 비디오 다운로드 도구입니다."
- echo -e "공식 웹 사이트 주소 : https://github.com/yt-dlp/yt-dlp"
+ echo -e "yt-dlp는 YouTube, Bilibili, Twitter 등과 같은 수천 개의 사이트를 지원하는 강력한 비디오 다운로드 도구입니다."
+ echo -e "공식 웹사이트 주소:${gh_https_url}github.com/yt-dlp/yt-dlp"
echo "-------------------------"
- echo "다운로드 된 비디오 목록 :"
+ echo "다운로드한 동영상 목록:"
ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(아직 없음)"
echo "-------------------------"
echo "1. 설치 2. 업데이트 3. 제거"
echo "-------------------------"
- echo "5. 단일 비디오 다운로드 6. 배치 비디오 다운로드 7. 사용자 정의 매개 변수 다운로드"
- echo "8. MP3 오디오 9. 비디오 디렉토리 삭제 10. 쿠키 관리 (개발 중)"
+ echo "5. 단일 비디오 다운로드 6. 일괄 비디오 다운로드 7. 사용자 정의 매개변수 다운로드"
+ echo "8. MP3 오디오로 다운로드 9. 비디오 디렉터리 삭제 10. 쿠키 관리(개발 중)"
echo "-------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "-------------------------"
- read -e -p "옵션 번호를 입력하십시오 :" choice
+ read -e -p "옵션 번호를 입력하세요:" choice
case $choice in
1)
- send_stats "yt-dlp 설치 ..."
- echo "yt-dlp 설치 ..."
+ send_stats "yt-dlp 설치 중..."
+ echo "yt-dlp 설치 중..."
install ffmpeg
- sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
- sudo chmod a+rx /usr/local/bin/yt-dlp
- echo "설치가 완료되었습니다. 계속하려면 키를 누르십시오 ..."
+ curl -L ${gh_https_url}github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ chmod a+rx /usr/local/bin/yt-dlp
+
+ add_app_id
+ echo "설치가 완료되었습니다. 계속하려면 아무 키나 누르세요..."
read ;;
2)
- send_stats "yt-dlp 업데이트 ..."
- echo "yt-dlp 업데이트 ..."
- sudo yt-dlp -U
- echo "업데이트가 완료되었습니다. 계속하려면 키를 누르십시오 ..."
+ send_stats "yt-dlp 업데이트 중..."
+ echo "yt-dlp 업데이트 중..."
+ yt-dlp -U
+
+ add_app_id
+ echo "업데이트가 완료되었습니다. 계속하려면 아무 키나 누르세요..."
read ;;
3)
- send_stats "yt-dlp 제거 ..."
- echo "yt-dlp 제거 ..."
- sudo rm -f /usr/local/bin/yt-dlp
- echo "제거가 완료되었습니다. 계속하려면 키를 누르십시오 ..."
+ send_stats "yt-dlp 제거 중..."
+ echo "yt-dlp 제거 중..."
+ rm -f /usr/local/bin/yt-dlp
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "제거가 완료되었습니다. 계속하려면 아무 키나 누르세요..."
read ;;
5)
send_stats "단일 비디오 다운로드"
- read -e -p "비디오 링크를 입력하십시오 :" url
+ read -e -p "동영상 링크를 입력하세요:" url
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "다운로드가 완료되면 키를 눌러 계속하십시오 ..." ;;
+ read -e -p "다운로드가 완료되었습니다. 계속하려면 아무 키나 누르세요..." ;;
6)
- send_stats "배치 비디오 다운로드"
+ send_stats "일괄 비디오 다운로드"
install nano
if [ ! -f "$URL_FILE" ]; then
- echo -e "# 여러 비디오 링크 주소를 입력하십시오 \ n# https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.episode.0" > "$URL_FILE"
+ echo -e "# 여러 개의 동영상 링크 주소를 입력하세요\n# https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.episode.0" > "$URL_FILE"
fi
nano $URL_FILE
- echo "이제 배치 다운로드를 시작하십시오 ..."
+ echo "지금 일괄 다운로드를 시작하세요..."
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
@@ -4091,31 +4607,31 @@ yt_menu_pro() {
-a "$URL_FILE" \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "배치 다운로드가 완료되었습니다. 키를 눌러 계속하십시오 ..." ;;
+ read -e -p "일괄 다운로드가 완료되었습니다. 계속하려면 아무 키나 누르세요..." ;;
7)
send_stats "맞춤형 비디오 다운로드"
- read -e -p "전체 YT-DLP 매개 변수를 입력하십시오 (YT-DLP 제외) :" custom
+ read -e -p "전체 yt-dlp 매개변수를 입력하세요(yt-dlp 제외)." custom
yt-dlp -P "$VIDEO_DIR" $custom \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "실행이 완료되면 키를 눌러 계속하십시오 ..." ;;
+ read -e -p "실행이 완료되었습니다. 계속하려면 아무 키나 누르세요..." ;;
8)
send_stats "MP3 다운로드"
- read -e -p "비디오 링크를 입력하십시오 :" url
+ read -e -p "동영상 링크를 입력하세요:" url
yt-dlp -P "$VIDEO_DIR" -x --audio-format mp3 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "오디오 다운로드가 완료되었습니다. 키를 누르면 계속하십시오 ..." ;;
+ read -e -p "오디오 다운로드가 완료되었습니다. 계속하려면 아무 키나 누르세요..." ;;
9)
- send_stats "비디오 삭제"
- read -e -p "삭제 비디오의 이름을 입력하십시오." rmdir
+ send_stats "동영상 삭제"
+ read -e -p "삭제된 동영상의 이름을 입력하세요:" rmdir
rm -rf "$VIDEO_DIR/$rmdir"
;;
*)
@@ -4151,7 +4667,7 @@ set_timedate() {
-# DPKG 인터럽트 문제를 수정하십시오
+# dpkg 중단 문제 수정
fix_dpkg() {
pkill -9 -f 'apt|dpkg'
rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock
@@ -4160,7 +4676,7 @@ fix_dpkg() {
linux_update() {
- echo -e "${gl_huang}시스템 업데이트 ...${gl_bai}"
+ echo -e "${gl_kjlan}시스템 업데이트 진행 중...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
elif command -v yum &>/dev/null; then
@@ -4179,7 +4695,7 @@ linux_update() {
elif command -v opkg &>/dev/null; then
opkg update
else
- echo "알 수없는 패키지 관리자!"
+ echo "알 수 없는 패키지 관리자입니다!"
return
fi
}
@@ -4187,7 +4703,7 @@ linux_update() {
linux_clean() {
- echo -e "${gl_huang}시스템 정리 ...${gl_bai}"
+ echo -e "${gl_kjlan}시스템 청소 진행 중...${gl_bai}"
if command -v dnf &>/dev/null; then
rpm --rebuilddb
dnf autoremove -y
@@ -4216,13 +4732,13 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v apk &>/dev/null; then
- echo "패키지 관리자 캐시 청소 ..."
+ echo "패키지 관리자 캐시 정리..."
apk cache clean
- echo "시스템 로그 삭제 ..."
+ echo "시스템 로그 삭제..."
rm -rf /var/log/*
- echo "APK 캐시 삭제 ..."
+ echo "APK 캐시 삭제..."
rm -rf /var/cache/apk/*
- echo "임시 파일 삭제 ..."
+ echo "임시 파일 삭제..."
rm -rf /tmp/*
elif command -v pacman &>/dev/null; then
@@ -4240,23 +4756,23 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v opkg &>/dev/null; then
- echo "시스템 로그 삭제 ..."
+ echo "시스템 로그 삭제..."
rm -rf /var/log/*
- echo "임시 파일 삭제 ..."
+ echo "임시 파일 삭제..."
rm -rf /tmp/*
elif command -v pkg &>/dev/null; then
- echo "사용하지 않는 의존성 정리 ..."
+ echo "사용하지 않는 종속성을 정리합니다..."
pkg autoremove -y
- echo "패키지 관리자 캐시 청소 ..."
+ echo "패키지 관리자 캐시 정리..."
pkg clean -y
- echo "시스템 로그 삭제 ..."
+ echo "시스템 로그 삭제..."
rm -rf /var/log/*
- echo "임시 파일 삭제 ..."
+ echo "임시 파일 삭제..."
rm -rf /tmp/*
else
- echo "알 수없는 패키지 관리자!"
+ echo "알 수 없는 패키지 관리자입니다!"
return
fi
return
@@ -4266,10 +4782,17 @@ linux_clean() {
bbr_on() {
-cat > /etc/sysctl.conf << EOF
-net.ipv4.tcp_congestion_control=bbr
-EOF
-sysctl -p
+# 커널 튜닝 모듈과의 충돌을 방지하기 위해 sysctl.d에 대한 통합 쓰기
+local CONF="/etc/sysctl.d/99-kejilion-bbr.conf"
+mkdir -p /etc/sysctl.d
+echo "net.core.default_qdisc=fq" > "$CONF"
+echo "net.ipv4.tcp_congestion_control=bbr" >> "$CONF"
+
+# 충돌을 일으킬 수 있는 이전 sysctl.conf의 남은 부분을 정리합니다.
+sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf 2>/dev/null
+
+sysctl -p "$CONF" >/dev/null 2>&1 || sysctl --system >/dev/null 2>&1
}
@@ -4278,8 +4801,8 @@ set_dns() {
ip_address
-rm /etc/resolv.conf
-touch /etc/resolv.conf
+chattr -i /etc/resolv.conf
+> /etc/resolv.conf
if [ -n "$ipv4_address" ]; then
echo "nameserver $dns1_ipv4" >> /etc/resolv.conf
@@ -4291,6 +4814,13 @@ if [ -n "$ipv6_address" ]; then
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
fi
+if [ ! -s /etc/resolv.conf ]; then
+ echo "nameserver 223.5.5.5" >> /etc/resolv.conf
+ echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+fi
+
+chattr +i /etc/resolv.conf
+
}
@@ -4299,23 +4829,23 @@ root_use
send_stats "DNS 최적화"
while true; do
clear
- echo "DNS 주소를 최적화합니다"
+ echo "DNS 주소 최적화"
echo "------------------------"
echo "현재 DNS 주소"
cat /etc/resolv.conf
echo "------------------------"
echo ""
- echo "1. 외국 DNS 최적화 :"
+ echo "1. 외국 DNS 최적화:"
echo " v4: 1.1.1.1 8.8.8.8"
echo " v6: 2606:4700:4700::1111 2001:4860:4860::8888"
- echo "2. 국내 DNS 최적화 :"
+ echo "2. 국내 DNS 최적화:"
echo " v4: 223.5.5.5 183.60.83.19"
echo " v6: 2400:3200::1 2400:da00::6666"
- echo "3. DNS 구성을 수동으로 편집합니다"
+ echo "3. DNS 구성을 수동으로 편집"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" Limiting
+ read -e -p "선택사항을 입력하세요:" Limiting
case "$Limiting" in
1)
local dns1_ipv4="1.1.1.1"
@@ -4335,8 +4865,10 @@ while true; do
;;
3)
install nano
+ chattr -i /etc/resolv.conf
nano /etc/resolv.conf
- send_stats "DNS 구성을 수동으로 편집합니다"
+ chattr +i /etc/resolv.conf
+ send_stats "DNS 구성을 수동으로 편집"
;;
*)
break
@@ -4359,45 +4891,38 @@ correct_ssh_config() {
local sshd_config="/etc/ssh/sshd_config"
- # PasswordAuthentication이 발견되면 예로 설정하십시오
- if grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
- # 발견 된 경우 PubKeyAuthentication이 예로 설정됩니다
- if grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
+ if grep -Eq "^\s*PasswordAuthentication\s+no" "$sshd_config"; then
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
-e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' "$sshd_config"
+ else
+ sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin yes/' \
+ -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication yes/' \
+ -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' "$sshd_config"
fi
- # PasswordAuthentication 또는 PubKeyAuthentication이 일치하지 않는 경우 기본값을 설정하십시오.
- if ! grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config" && ! grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
-
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
}
new_ssh_port() {
- # 백업 SSH 구성 파일
+ local new_port=$1
+
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
open_port $new_port
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
- echo "SSH 포트는 다음으로 수정되었습니다.$new_port"
+ echo "SSH 포트가 다음과 같이 수정되었습니다.$new_port"
sleep 1
@@ -4405,21 +4930,7 @@ new_ssh_port() {
-add_sshkey() {
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f /root/.ssh/sshkey -N ""
- cat ~/.ssh/sshkey.pub >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
-
- ip_address
- echo -e "개인 키 정보가 생성되었습니다. 복사하고 저장하십시오.${gl_huang}${ipv4_address}_ssh.key${gl_bai}향후 SSH 로그인 파일"
-
- echo "--------------------------------"
- cat ~/.ssh/sshkey
- echo "--------------------------------"
+sshkey_on() {
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
@@ -4427,194 +4938,473 @@ add_sshkey() {
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
- echo -e "${gl_lv}루트 프라이빗 키 로그인이 활성화되고 루트 비밀번호 로그인이 닫히고 재 연결이 적용됩니다.${gl_bai}"
+ echo -e "${gl_lv}사용자 키 로그인 모드가 켜져 있고 비밀번호 로그인 모드가 꺼졌습니다. 다시 연결이 적용됩니다.${gl_bai}"
}
-import_sshkey() {
- read -e -p "SSH 공개 키 내용을 입력하십시오 (일반적으로 'SSH-RSA'또는 'SSH-ED25519'로 시작) :" public_key
+add_sshkey() {
+ chmod 700 "${HOME}"
+ mkdir -p "${HOME}/.ssh"
+ chmod 700 "${HOME}/.ssh"
+ touch "${HOME}/.ssh/authorized_keys"
- if [[ -z "$public_key" ]]; then
- echo -e "${gl_hong}오류 : 공개 키 컨텐츠가 입력되지 않았습니다.${gl_bai}"
- return 1
- fi
+ ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f "${HOME}/.ssh/sshkey" -N ""
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- echo "$public_key" >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
+ cat "${HOME}/.ssh/sshkey.pub" >> "${HOME}/.ssh/authorized_keys"
+ chmod 600 "${HOME}/.ssh/authorized_keys"
- sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
- -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
- -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
- -e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
+ ip_address
+ echo -e "개인키 정보가 생성되었습니다. 꼭 복사해서 저장하세요. 다음과 같이 저장될 수 있습니다.${gl_huang}${ipv4_address}_ssh.key${gl_bai}향후 SSH 로그인을 위한 파일"
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
- restart_ssh
- echo -e "${gl_lv}공개 키가 성공적으로 가져 왔고 루트 개인 키 로그인이 활성화되었고 루트 비밀번호 로그인이 닫히고 재 연결이 적용됩니다.${gl_bai}"
+ echo "--------------------------------"
+ cat "${HOME}/.ssh/sshkey"
+ echo "--------------------------------"
+ sshkey_on
}
-add_sshpasswd() {
-echo "루트 비밀번호를 설정하십시오"
-passwd
-sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
-sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
-rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
-restart_ssh
-echo -e "${gl_lv}루트 로그인이 설정되었습니다!${gl_bai}"
+import_sshkey() {
-}
+ local public_key="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local auth_keys="${ssh_dir}/authorized_keys"
+ if [[ -z "$public_key" ]]; then
+ read -e -p "SSH 공개 키(일반적으로 'ssh-rsa' 또는 'ssh-ed25519'로 시작)의 내용을 입력하세요." public_key
+ fi
-root_use() {
-clear
-[ "$EUID" -ne 0 ] && echo -e "${gl_huang}힌트:${gl_bai}이 기능은 루트 사용자가 실행해야합니다!" && break_end && kejilion
-}
+ if [[ -z "$public_key" ]]; then
+ echo -e "${gl_hong}오류: 공개 키 내용이 입력되지 않았습니다.${gl_bai}"
+ return 1
+ fi
+ if [[ ! "$public_key" =~ ^ssh-(rsa|ed25519|ecdsa) ]]; then
+ echo -e "${gl_hong}오류: 합법적인 SSH 공개 키가 아닌 것 같습니다.${gl_bai}"
+ return 1
+ fi
+ if grep -Fxq "$public_key" "$auth_keys" 2>/dev/null; then
+ echo "공개 키가 이미 존재하므로 다시 추가할 필요가 없습니다."
+ return 0
+ fi
-dd_xitong() {
- send_stats "시스템을 다시 설치하십시오"
- dd_xitong_MollyLau() {
- wget --no-check-certificate -qO InstallNET.sh "${gh_proxy}raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh" && chmod a+x InstallNET.sh
+ mkdir -p "$ssh_dir"
+ chmod 700 "$ssh_dir"
+ touch "$auth_keys"
+ echo "$public_key" >> "$auth_keys"
+ chmod 600 "$auth_keys"
- }
+ sshkey_on
+}
- dd_xitong_bin456789() {
+
+
+fetch_remote_ssh_keys() {
+
+ local keys_url="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local authorized_keys="${ssh_dir}/authorized_keys"
+ local temp_file
+
+ if [[ -z "${keys_url}" ]]; then
+ read -e -p "원격 공개 키 URL을 입력하세요:" keys_url
+ fi
+
+ echo "이 스크립트는 원격 URL에서 SSH 공개 키를 가져와서${authorized_keys}"
+ echo ""
+ echo "원격 공개 키 주소:"
+ echo " ${keys_url}"
+ echo ""
+
+ # 임시 파일 만들기
+ temp_file=$(mktemp)
+
+ # 공개 키 다운로드
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 10 "${keys_url}" -o "${temp_file}" || {
+ echo "오류: URL에서 공개 키를 다운로드할 수 없습니다(네트워크 문제 또는 잘못된 주소)." >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q --timeout=10 -O "${temp_file}" "${keys_url}" || {
+ echo "오류: URL에서 공개 키를 다운로드할 수 없습니다(네트워크 문제 또는 잘못된 주소)." >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ else
+ echo "오류: 시스템에서 컬 또는 wget을 찾을 수 없어 공개 키를 다운로드할 수 없습니다." >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ # 콘텐츠가 유효한지 확인하세요.
+ if [[ ! -s "${temp_file}" ]]; then
+ echo "오류: 다운로드한 파일이 비어 있으며 URL에 공개 키가 포함되어 있지 않을 수 있습니다." >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ mkdir -p "${ssh_dir}"
+ chmod 700 "${ssh_dir}"
+ touch "${authorized_keys}"
+ chmod 600 "${authorized_keys}"
+
+ # 원본 인증_키 백업
+ if [[ -f "${authorized_keys}" ]]; then
+ cp "${authorized_keys}" "${authorized_keys}.bak.$(date +%Y%m%d-%H%M%S)"
+ echo "원본 Authorized_keys 파일이 백업되었습니다."
+ fi
+
+ # 공개 키 추가(중복 방지)
+ local added=0
+ while IFS= read -r line; do
+ [[ -z "${line}" || "${line}" =~ ^# ]] && continue
+
+ if ! grep -Fxq "${line}" "${authorized_keys}" 2>/dev/null; then
+ echo "${line}" >> "${authorized_keys}"
+ ((added++))
+ fi
+ done < "${temp_file}"
+
+ rm -f "${temp_file}"
+
+ echo ""
+ if (( added > 0 )); then
+ echo "성공적으로 추가되었습니다${added}새로운 공개 키가 도착합니다${authorized_keys}"
+ sshkey_on
+ else
+ echo "새로운 공개 키를 추가할 필요가 없습니다(모두 이미 존재할 수도 있음)."
+ fi
+
+ echo ""
+}
+
+
+
+
+fetch_github_ssh_keys() {
+
+ local username="$1"
+ local base_dir="${2:-$HOME}"
+
+ echo "계속하기 전에 SSH 공개 키를 GitHub 계정에 추가했는지 확인하세요."
+ echo "1. 로그인${gh_https_url}github.com/settings/keys"
+ echo "2. 새 SSH 키 또는 SSH 키 추가를 클릭하세요."
+ echo "3. 제목은 원하는 대로 입력할 수 있습니다. (예: Home Laptop 2026)"
+ echo "4. 로컬 공개 키의 내용(일반적으로 ~/.ssh/id_ed25519.pub 또는 id_rsa.pub의 전체 내용)을 키 필드에 붙여넣습니다."
+ echo "5. SSH 키 추가를 클릭하여 추가를 완료합니다."
+ echo ""
+ echo "추가되면 모든 공개 키는 다음 GitHub에서 공개적으로 사용할 수 있습니다."
+ echo " ${gh_https_url}github.com/yourusername.keys"
+ echo ""
+
+
+ if [[ -z "${username}" ]]; then
+ read -e -p "GitHub 사용자 이름(@ 없는 사용자 이름)을 입력하세요." username
+ fi
+
+ if [[ -z "${username}" ]]; then
+ echo "오류: GitHub 사용자 이름은 비워둘 수 없습니다." >&2
+ return 1
+ fi
+
+ keys_url="${gh_https_url}github.com/${username}.keys"
+
+ fetch_remote_ssh_keys "${keys_url}" "${base_dir}"
+
+}
+
+
+sshkey_panel() {
+ root_use
+ send_stats "사용자 키 로그인"
+ while true; do
+ clear
+ local REAL_STATUS=$(grep -i "^PubkeyAuthentication" /etc/ssh/sshd_config | tr '[:upper:]' '[:lower:]')
+ if [[ "$REAL_STATUS" =~ "yes" ]]; then
+ IS_KEY_ENABLED="${gl_lv}활성화됨${gl_bai}"
+ else
+ IS_KEY_ENABLED="${gl_hui}활성화되지 않음${gl_bai}"
+ fi
+ echo -e "사용자 키 로그인 모드${IS_KEY_ENABLED}"
+ echo "고급 게임플레이: https://blog.kejilion.pro/ssh-key"
+ echo "------------------------------------------------"
+ echo "SSH를 통해 더욱 안전하게 로그인할 수 있는 키 쌍이 생성됩니다."
+ echo "------------------------"
+ echo "1. 새 키 쌍 생성 2. 기존 공개 키를 수동으로 입력"
+ echo "3. GitHub에서 기존 공개 키를 가져옵니다. 4. URL에서 기존 공개 키를 가져옵니다."
+ echo "5. 공개 키 파일 편집 6. 로컬 키 보기"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택사항을 입력하세요:" host_dns
+ case $host_dns in
+ 1)
+ send_stats "새 키 생성"
+ add_sshkey
+ break_end
+ ;;
+ 2)
+ send_stats "기존 공개 키 가져오기"
+ import_sshkey
+ break_end
+ ;;
+ 3)
+ send_stats "GitHub 원격 공개 키 가져오기"
+ fetch_github_ssh_keys
+ break_end
+ ;;
+ 4)
+ send_stats "URL 원격 공개 키 가져오기"
+ read -e -p "원격 공개 키 URL을 입력하세요:" keys_url
+ fetch_remote_ssh_keys "${keys_url}"
+ break_end
+ ;;
+
+ 5)
+ send_stats "공개 키 파일 편집"
+ install nano
+ nano ${HOME}/.ssh/authorized_keys
+ break_end
+ ;;
+
+ 6)
+ send_stats "로컬 키 보기"
+ echo "------------------------"
+ echo "공개키 정보"
+ cat ${HOME}/.ssh/authorized_keys
+ echo "------------------------"
+ echo "개인 키 정보"
+ cat ${HOME}/.ssh/sshkey
+ echo "------------------------"
+ break_end
+ ;;
+ *)
+ break # 跳出循环,退出菜单
+ ;;
+ esac
+ done
+
+
+}
+
+
+
+
+
+
+add_sshpasswd() {
+
+ root_use
+ send_stats "비밀번호 로그인 모드 설정"
+ echo "비밀번호 로그인 모드 설정"
+
+ local target_user="$1"
+
+ # 매개변수가 전달되지 않으면 대화형으로 입력하세요.
+ if [[ -z "$target_user" ]]; then
+ read -e -p "비밀번호를 변경하려는 사용자 이름을 입력하십시오(기본 루트):" target_user
+ fi
+
+ # Enter를 누르고 입력하지 마십시오. 기본값은 root입니다.
+ target_user=${target_user:-root}
+
+ # 사용자가 존재하는지 확인
+ if ! id "$target_user" >/dev/null 2>&1; then
+ echo "오류: 사용자$target_user존재하지 않는다"
+ return 1
+ fi
+
+ passwd "$target_user"
+
+ if [[ "$target_user" == "root" ]]; then
+ sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
+ fi
+
+ sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
+
+ restart_ssh
+
+ echo -e "${gl_lv}비밀번호가 설정되어 비밀번호 로그인 모드로 변경되었습니다!${gl_bai}"
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+root_use() {
+clear
+[ "$EUID" -ne 0 ] && echo -e "${gl_huang}힌트:${gl_bai}이 기능을 실행하려면 루트 사용자가 필요합니다!" && break_end && kejilion
+}
+
+
+
+
+
+
+
+
+
+
+
+
+dd_xitong() {
+ send_stats "시스템 재설치"
+ dd_xitong_MollyLau() {
+ wget --no-check-certificate -qO InstallNET.sh "${gh_proxy}raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh" && chmod a+x InstallNET.sh
+
+ }
+
+ dd_xitong_bin456789() {
curl -O ${gh_proxy}raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh
}
dd_xitong_1() {
- echo -e "재설치 후 초기 사용자 이름 :${gl_huang}root${gl_bai}초기 비밀번호 :${gl_huang}LeitboGi0ro${gl_bai}초기 포트 :${gl_huang}22${gl_bai}"
- echo -e "계속하려면 키를 누르십시오 ..."
+ echo -e "재설치 후 초기 사용자 이름:${gl_huang}root${gl_bai}초기 비밀번호:${gl_huang}LeitboGi0ro${gl_bai}초기 포트:${gl_huang}22${gl_bai}"
+ echo -e "${gl_huang}재설치 후에는 초기 비밀번호를 적시에 변경하여 폭력적인 침입을 방지하시기 바랍니다. 비밀번호를 변경하려면 명령줄에 passwd를 입력하세요.${gl_bai}"
+ echo -e "계속하려면 아무 키나 누르세요..."
read -n 1 -s -r -p ""
install wget
dd_xitong_MollyLau
}
dd_xitong_2() {
- echo -e "재설치 후 초기 사용자 이름 :${gl_huang}Administrator${gl_bai}초기 비밀번호 :${gl_huang}Teddysun.com${gl_bai}초기 포트 :${gl_huang}3389${gl_bai}"
- echo -e "계속하려면 키를 누르십시오 ..."
+ echo -e "재설치 후 초기 사용자 이름:${gl_huang}Administrator${gl_bai}초기 비밀번호:${gl_huang}Teddysun.com${gl_bai}초기 포트:${gl_huang}3389${gl_bai}"
+ echo -e "계속하려면 아무 키나 누르세요..."
read -n 1 -s -r -p ""
install wget
dd_xitong_MollyLau
}
dd_xitong_3() {
- echo -e "재설치 후 초기 사용자 이름 :${gl_huang}root${gl_bai}초기 비밀번호 :${gl_huang}123@@@${gl_bai}초기 포트 :${gl_huang}22${gl_bai}"
- echo -e "계속하려면 키를 누르십시오 ..."
+ echo -e "재설치 후 초기 사용자 이름:${gl_huang}root${gl_bai}초기 비밀번호:${gl_huang}123@@@${gl_bai}초기 포트:${gl_huang}22${gl_bai}"
+ echo -e "계속하려면 아무 키나 누르세요..."
read -n 1 -s -r -p ""
dd_xitong_bin456789
}
dd_xitong_4() {
- echo -e "재설치 후 초기 사용자 이름 :${gl_huang}Administrator${gl_bai}초기 비밀번호 :${gl_huang}123@@@${gl_bai}초기 포트 :${gl_huang}3389${gl_bai}"
- echo -e "계속하려면 키를 누르십시오 ..."
+ echo -e "재설치 후 초기 사용자 이름:${gl_huang}Administrator${gl_bai}초기 비밀번호:${gl_huang}123@@@${gl_bai}초기 포트:${gl_huang}3389${gl_bai}"
+ echo -e "계속하려면 아무 키나 누르세요..."
read -n 1 -s -r -p ""
dd_xitong_bin456789
}
while true; do
root_use
- echo "시스템을 다시 설치하십시오"
+ echo "시스템 재설치"
echo "--------------------------------"
- echo -e "${gl_hong}알아채다:${gl_bai}다시 설치는 접촉을 잃을 위험이 있으며 걱정하는 사람들은 그것을주의해서 사용해야합니다. 재설치는 15 분이 걸릴 것으로 예상됩니다. 데이터를 미리 백업하십시오."
- echo -e "${gl_hui}스크립트 지원을 위해 Mollylau 및 Bin456789에게 감사드립니다!${gl_bai} "
+ echo -e "${gl_hong}알아채다:${gl_bai}재설치 시 연결이 끊어질 수 있으니 걱정되시는 분들은 주의해서 사용해주세요. 재설치에는 약 15분 정도 소요될 예정이오니, 사전에 데이터를 백업해 주시기 바랍니다."
+ echo -e "${gl_hui}스크립트 지원을 해주신 bin456789 보스와 leitbogioro 보스에게 감사드립니다!${gl_bai} "
+ echo -e "${gl_hui}bin456789 프로젝트 주소:${gh_https_url}github.com/bin456789/reinstall${gl_bai}"
+ echo -e "${gl_hui}leitbogioro 프로젝트 주소:${gh_https_url}github.com/leitbogioro/Tools${gl_bai}"
echo "------------------------"
- echo "1. Debian 12 2. Debian 11"
- echo "3. Debian 10 4. Debian 9"
+ echo "1. Debian 13 2. Debian 12"
+ echo "3. Debian 11 4. Debian 10"
echo "------------------------"
- echo "11. Ubuntu 24.04 12. Ubuntu 22.04"
- echo "13. Ubuntu 20.04 14. Ubuntu 18.04"
+ echo "11. Ubuntu 26.04 12. Ubuntu 24.04"
+ echo "13. Ubuntu 22.04 14. Ubuntu 20.04"
echo "------------------------"
echo "21. Rocky Linux 10 22. Rocky Linux 9"
echo "23. Alma Linux 10 24. Alma Linux 9"
echo "25. oracle Linux 10 26. oracle Linux 9"
- echo "27. Fedora Linux 42 28. Fedora Linux 41"
+ echo "27. Fedora Linux 44 28. Fedora Linux 43"
echo "29. CentOS 10 30. CentOS 9"
echo "------------------------"
echo "31. Alpine Linux 32. Arch Linux"
echo "33. Kali Linux 34. openEuler"
- echo "35. OpenSuse Tumbleweed 36. FNOS FEINIU 공개 베타 버전"
+ echo "35. openSUSE Tumbleweed 36. fnos Feiniu 공개 베타 버전"
echo "------------------------"
echo "41. Windows 11 42. Windows 10"
- echo "43. Windows 7 44. Windows Server 2022"
- echo "45. Windows Server 2019 46. Windows Server 2016"
+ echo "43. Windows 7 44. Windows Server 2025"
+ echo "45. Windows Server 2022 46. Windows Server 2019"
echo "47. Windows 11 ARM"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "다시 설치할 시스템을 선택하십시오." sys_choice
+ read -e -p "다시 설치하려는 시스템을 선택하십시오:" sys_choice
case "$sys_choice" in
+
+
1)
- send_stats "데비안 12를 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -debian 12
+ send_stats "데비안 13 재설치"
+ dd_xitong_3
+ bash reinstall.sh debian 13
reboot
exit
;;
+
2)
- send_stats "데비안 11을 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -debian 11
+ send_stats "데비안 12 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh debian 12
reboot
exit
;;
3)
- send_stats "데비안 10을 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -debian 10
+ send_stats "데비안 11 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh debian 11
reboot
exit
;;
4)
- send_stats "데비안 9를 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -debian 9
+ send_stats "데비안 10 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh debian 10
reboot
exit
;;
11)
- send_stats "우분투 24.04를 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 24.04
+ send_stats "우분투 26.04 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 26.04
reboot
exit
;;
12)
- send_stats "우분투 22.04를 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 22.04
+ send_stats "우분투 24.04 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 24.04
reboot
exit
;;
13)
- send_stats "Ubuntu 20.04를 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 20.04
+ send_stats "우분투 22.04 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 22.04
reboot
exit
;;
14)
- send_stats "우분투 18.04를 다시 설치하십시오"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 18.04
+ send_stats "우분투 20.04 다시 설치"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 20.04
reboot
exit
;;
-
21)
- send_stats "Rockylinux10을 다시 설치하십시오"
+ send_stats "rockylinux10 재설치"
dd_xitong_3
bash reinstall.sh rocky
reboot
@@ -4622,7 +5412,7 @@ dd_xitong() {
;;
22)
- send_stats "Rockylinux9를 다시 설치하십시오"
+ send_stats "rockylinux9 재설치"
dd_xitong_3
bash reinstall.sh rocky 9
reboot
@@ -4630,7 +5420,7 @@ dd_xitong() {
;;
23)
- send_stats "Alma10을 다시 설치하십시오"
+ send_stats "alma10 재설치"
dd_xitong_3
bash reinstall.sh almalinux
reboot
@@ -4638,7 +5428,7 @@ dd_xitong() {
;;
24)
- send_stats "Alma9를 다시 설치하십시오"
+ send_stats "alma9 재설치"
dd_xitong_3
bash reinstall.sh almalinux 9
reboot
@@ -4646,7 +5436,7 @@ dd_xitong() {
;;
25)
- send_stats "Oracle10을 다시 설치하십시오"
+ send_stats "oracle10 재설치"
dd_xitong_3
bash reinstall.sh oracle
reboot
@@ -4654,7 +5444,7 @@ dd_xitong() {
;;
26)
- send_stats "Oracle9를 다시 설치하십시오"
+ send_stats "oracle9 재설치"
dd_xitong_3
bash reinstall.sh oracle 9
reboot
@@ -4662,23 +5452,23 @@ dd_xitong() {
;;
27)
- send_stats "Fedora42를 다시 설치하십시오"
+ send_stats "fedora44 재설치"
dd_xitong_3
- bash reinstall.sh fedora
+ bash reinstall.sh fedora 44
reboot
exit
;;
28)
- send_stats "Fedora41을 다시 설치하십시오"
+ send_stats "fedora43 재설치"
dd_xitong_3
- bash reinstall.sh fedora 41
+ bash reinstall.sh fedora 43
reboot
exit
;;
29)
- send_stats "CentOS10을 다시 설치하십시오"
+ send_stats "centos10 재설치"
dd_xitong_3
bash reinstall.sh centos 10
reboot
@@ -4686,7 +5476,7 @@ dd_xitong() {
;;
30)
- send_stats "CentOS9를 다시 설치하십시오"
+ send_stats "centos9 재설치"
dd_xitong_3
bash reinstall.sh centos 9
reboot
@@ -4694,7 +5484,7 @@ dd_xitong() {
;;
31)
- send_stats "알파인을 다시 설치하십시오"
+ send_stats "알파인 재설치"
dd_xitong_1
bash InstallNET.sh -alpine
reboot
@@ -4702,7 +5492,7 @@ dd_xitong() {
;;
32)
- send_stats "아치를 다시 설치하십시오"
+ send_stats "아치 재설치"
dd_xitong_3
bash reinstall.sh arch
reboot
@@ -4710,7 +5500,7 @@ dd_xitong() {
;;
33)
- send_stats "칼리를 다시 설치하십시오"
+ send_stats "칼리 재설치"
dd_xitong_3
bash reinstall.sh kali
reboot
@@ -4718,7 +5508,7 @@ dd_xitong() {
;;
34)
- send_stats "Openeuler를 다시 설치하십시오"
+ send_stats "openeuler 다시 설치"
dd_xitong_3
bash reinstall.sh openeuler
reboot
@@ -4726,7 +5516,7 @@ dd_xitong() {
;;
35)
- send_stats "재설치 OpenSuse"
+ send_stats "Openuse 다시 설치"
dd_xitong_3
bash reinstall.sh opensuse
reboot
@@ -4734,60 +5524,63 @@ dd_xitong() {
;;
36)
- send_stats "비행 소부소"
+ send_stats "Feiniu 재설치"
dd_xitong_3
bash reinstall.sh fnos
reboot
exit
;;
-
41)
- send_stats "Windows 11을 다시 설치하십시오"
+ send_stats "윈도우 11 재설치"
dd_xitong_2
bash InstallNET.sh -windows 11 -lang "cn"
reboot
exit
;;
+
42)
dd_xitong_2
- send_stats "Windows 10을 다시 설치하십시오"
+ send_stats "윈도우 10 재설치"
bash InstallNET.sh -windows 10 -lang "cn"
reboot
exit
;;
+
43)
- send_stats "Windows 7을 다시 설치하십시오"
+ send_stats "Windows7을 다시 설치하세요."
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://archive.org/download/en_windows_7_professional_with_sp1_x64_dvd_u_676939_201906/en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso" --image-name='windows 7 professional'
reboot
exit
;;
44)
- send_stats "Windows Server 22를 다시 설치하십시오"
+ send_stats "Windows Server 25를 다시 설치하세요."
dd_xitong_2
- bash InstallNET.sh -windows 2022 -lang "cn"
+ bash InstallNET.sh -windows 2025 -lang "cn"
reboot
exit
;;
+
45)
- send_stats "Windows Server 19를 다시 설치하십시오"
+ send_stats "윈도우 서버 22 재설치"
dd_xitong_2
- bash InstallNET.sh -windows 2019 -lang "cn"
+ bash InstallNET.sh -windows 2022 -lang "cn"
reboot
exit
;;
+
46)
- send_stats "Windows Server 16을 다시 설치하십시오"
+ send_stats "윈도우 서버 19 재설치"
dd_xitong_2
- bash InstallNET.sh -windows 2016 -lang "cn"
+ bash InstallNET.sh -windows 2019 -lang "cn"
reboot
exit
;;
47)
- send_stats "Windows11 Arm을 다시 설치하십시오"
+ send_stats "windows11 ARM 재설치"
dd_xitong_4
bash reinstall.sh dd --img https://r2.hotdog.eu.org/win11-arm-with-pagefile-15g.xz
reboot
@@ -4804,7 +5597,147 @@ dd_xitong() {
bbrv3() {
root_use
- send_stats "BBRV3 관리"
+ send_stats "bbrv3 관리"
+
+ xanmod_add_repo() {
+ local keyring="/usr/share/keyrings/xanmod-archive-keyring.gpg"
+ local list_file="/etc/apt/sources.list.d/xanmod-release.list"
+ local key_url="https://dl.xanmod.org/archive.key"
+ local fallback_key_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key"
+ local os_codename=""
+
+ if command -v lsb_release >/dev/null 2>&1; then
+ os_codename=$(lsb_release -sc)
+ elif [ -r /etc/os-release ]; then
+ os_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
+ fi
+
+ # 공식적으로 제거된 이전 시스템 코드명과 호환 가능(이전 패키지 라이브러리를 사용해 보려면 릴리스를 사용하도록 대체)
+ if ! echo "bookworm trixie forky sid noble plucky questing resolute faye gigi wilma xia zara zena" | grep -qw "$os_codename"; then
+ os_codename="releases"
+ fi
+
+ # 공식적으로 jammy, focus, Bullseye 및 기타 기존 시스템에 대한 적절한 지원이 완전히 제거되었습니다.
+ if echo "jammy focal bullseye buster" | grep -qw "$os_codename" || [ "$os_codename" = "releases" ]; then
+ echo -e "${gl_hong}XanMod는 공식적으로 현재 시스템 지원을 중단했습니다($os_codename), Debian12/Ubuntu24 이상으로 업그레이드하세요.${gl_bai}"
+ return 1
+ fi
+
+ if [ -z "$os_codename" ]; then
+ echo "시스템 코드명을 얻을 수 없으며 XanMod 소스를 구성할 수 없습니다."
+ return 1
+ fi
+
+ install wget gnupg ca-certificates
+ mkdir -p /usr/share/keyrings /etc/apt/sources.list.d
+ if ! wget -qO - "$key_url" | gpg --dearmor -o "$keyring" --yes; then
+ echo "공식 키 다운로드에 실패했습니다. 다른 다운로드 소스를 사용해 보세요..."
+ wget -qO - "$fallback_key_url" | gpg --dearmor -o "$keyring" --yes || return 1
+ fi
+ chmod 644 "$keyring"
+ echo "deb [signed-by=$keyring] http://deb.xanmod.org $os_codename main" > "$list_file"
+ }
+
+ xanmod_detect_psabi_level() {
+ local psabi_output=""
+ psabi_output=$(awk 'BEGIN {
+ while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
+ if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
+ if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
+ if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
+ if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
+ if (level > 0) { print level; exit }
+ exit 1
+ }' /proc/cpuinfo 2>/dev/null) || return 1
+ printf '%s' "$psabi_output" | tr -dc '0-9' | head -c 1
+ }
+
+ xanmod_package_available() {
+ local package="$1"
+ apt-cache policy "$package" 2>/dev/null | grep -q 'Candidate: [^ ]'
+ }
+
+ xanmod_detect_package() {
+ local psabi_level=""
+ local level=""
+ local package=""
+ local prefix_list="linux-xanmod linux-xanmod-lts"
+
+ psabi_level=$(xanmod_detect_psabi_level) || return 1
+ [ -n "$psabi_level" ] || return 1
+ [ "$psabi_level" -gt 3 ] && psabi_level=3
+
+ apt update -y >/dev/null 2>&1
+
+ for prefix in $prefix_list; do
+ level="$psabi_level"
+ while [ "$level" -ge 1 ]; do
+ package="${prefix}-x64v${level}"
+ if xanmod_package_available "$package"; then
+ if [ "$level" != "$psabi_level" ] || [ "$prefix" = "linux-xanmod-lts" ]; then
+ echo "적절한 설치 패키지가 자동으로 일치되었습니다.$package" >&2
+ fi
+ printf '%s\n' "$package"
+ return 0
+ fi
+ level=$((level - 1))
+ done
+ done
+
+ echo "이 CPU에 적합한 XanMod 커널 패키지를 소프트웨어 소스에서 찾을 수 없습니다." >&2
+ return 1
+ }
+
+ xanmod_installed() {
+ dpkg-query -W -f='${Package}\n' 'linux-*xanmod*' 2>/dev/null | grep -q '^linux-.*xanmod'
+ }
+
+ xanmod_install_or_update() {
+ local action="$1"
+ local package=""
+
+ check_disk_space 3
+ check_swap
+ xanmod_add_repo || {
+ echo "XanMod 공식 창고 구성에 실패했습니다. 나중에 다시 시도해 주세요."
+ return 1
+ }
+
+ package=$(xanmod_detect_package) || {
+ echo "현재 CPU를 인식할 수 없거나 일치하는 커널 패키지를 찾을 수 없습니다. 설치가 취소되었습니다."
+ return 1
+ }
+
+ apt update -y
+ if [ "$action" = "update" ]; then
+ apt install -y --only-upgrade "$package" || apt install -y "$package" || {
+ echo "XanMod 커널 업데이트에 실패했습니다. 소프트웨어 소스를 확인하거나 나중에 다시 시도하십시오."
+ return 1
+ }
+ else
+ apt install -y "$package" || {
+ echo "XanMod 커널 설치에 실패했습니다. 소프트웨어 소스를 확인하거나 나중에 다시 시도하십시오."
+ return 1
+ }
+ fi
+
+ bbr_on || {
+ echo "BBR3 매개변수 쓰기에 실패했습니다. 시스템 구성을 확인하십시오."
+ return 1
+ }
+ echo "XanMod BBRv3 커널 처리가 완료되었습니다. 재시작 후 적용"
+ server_reboot
+ }
+
+ xanmod_uninstall() {
+ apt purge -y 'linux-*xanmod*'
+ apt autoremove -y
+ update-grub 2>/dev/null || true
+ rm -f /etc/apt/sources.list.d/xanmod-release.list
+ rm -f /usr/share/keyrings/xanmod-archive-keyring.gpg
+ echo "XanMod 커널이 제거되었습니다. 재시작 후 적용"
+ server_reboot
+ }
local cpu_arch=$(uname -m)
if [ "$cpu_arch" = "aarch64" ]; then
@@ -4813,157 +5746,109 @@ bbrv3() {
linux_Settings
fi
- if dpkg -l | grep -q 'linux-xanmod'; then
+ if [ -r /etc/os-release ]; then
+ . /etc/os-release
+ if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
+ echo "현재 환경에서는 지원하지 않습니다. Debian 및 Ubuntu 시스템만 지원됩니다."
+ break_end
+ linux_Settings
+ fi
+ else
+ echo "운영 체제 유형을 확인할 수 없습니다."
+ break_end
+ linux_Settings
+ fi
+
+ if xanmod_installed; then
while true; do
clear
local kernel_version=$(uname -r)
- echo "Xanmod의 BBRV3 커널을 설치했습니다"
- echo "현재 커널 버전 :$kernel_version"
+ echo "xanmod의 BBRv3 커널이 설치되어 있습니다."
+ echo "현재 커널 버전:$kernel_version"
echo ""
echo "커널 관리"
echo "------------------------"
- echo "1. BBRV3 커널 업데이트 2. BBRV3 커널 제거"
+ echo "1. BBRv3 커널 업데이트 2. BBRv3 커널 제거"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- apt purge -y 'linux-*xanmod1*'
- update-grub
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 3 단계 : 저장소를 추가합니다
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- echo "Xanmod 커널이 업데이트되었습니다. 다시 시작한 후에도 적용됩니다"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
-
- server_reboot
-
- ;;
+ xanmod_install_or_update update
+ ;;
2)
- apt purge -y 'linux-*xanmod1*'
- update-grub
- echo "Xanmod 커널은 제거되었습니다. 다시 시작한 후에도 적용됩니다"
- server_reboot
- ;;
-
+ xanmod_uninstall
+ ;;
*)
- break # 跳出循环,退出菜单
- ;;
+ break
+ ;;
esac
done
else
clear
- echo "BBR3 가속도를 설정하십시오"
- echo "비디오 소개 : https://www.bilibili.com/video/bv14k421x7bs?t=0.1"
+ echo "BBR3 가속 설정"
+ echo "영상 소개: https://www.bilibili.com/video/BV14K421x7BS?t=0.1"
echo "------------------------------------------------"
- echo "데비안/우분투 만 지원합니다"
- echo "데이터를 백업하고 BBR3에서 Linux 커널을 업그레이드 할 수 있습니다."
- echo "VPS는 512m 메모리를 가지고 있습니다. 메모리가 충분하지 않아 접점 누락을 방지하기 위해 1G 가상 메모리를 미리 추가하십시오!"
+ echo "데비안/우분투만 지원"
+ echo "데이터를 백업해 주시면 Linux 커널을 업그레이드하고 BBR3을 활성화하겠습니다."
echo "------------------------------------------------"
- read -e -p "계속할거야? (Y/N) :" choice
+ read -e -p "계속하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
- check_disk_space 3
- if [ -r /etc/os-release ]; then
- . /etc/os-release
- if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
- echo "현재 환경은이를 지원하지 않으며 데비안 및 우분투 시스템 만 지원합니다."
- break_end
- linux_Settings
- fi
- else
- echo "운영 체제 유형을 결정할 수 없습니다"
- break_end
- linux_Settings
- fi
-
- check_swap
- install wget gnupg
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 3 단계 : 저장소를 추가합니다
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- bbr_on
-
- echo "Xanmod 커널이 설치되고 BBR3이 성공적으로 활성화됩니다. 다시 시작한 후에도 적용됩니다"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
- server_reboot
-
+ xanmod_install_or_update install
;;
[Nn])
echo "취소"
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
fi
}
-
elrepo_install() {
- # Elrepo GPG 공개 키를 가져 오십시오
- echo "Elrepo GPG 공개 키를 가져 오십시오 ..."
+ # ELRepo GPG 공개 키 가져오기
+ echo "ELRepo GPG 공개 키 가져오기..."
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
- # 시스템 버전을 감지하십시오
+ # 시스템 버전 확인
local os_version=$(rpm -q --qf "%{VERSION}" $(rpm -qf /etc/os-release) 2>/dev/null | awk -F '.' '{print $1}')
local os_name=$(awk -F= '/^NAME/{print $2}' /etc/os-release)
- # 지원되는 운영 체제에서 실행하십시오
+ # 지원되는 운영 체제에서 실행되고 있는지 확인하세요.
if [[ "$os_name" != *"Red Hat"* && "$os_name" != *"AlmaLinux"* && "$os_name" != *"Rocky"* && "$os_name" != *"Oracle"* && "$os_name" != *"CentOS"* ]]; then
- echo "지원되지 않는 운영 체제 :$os_name"
+ echo "지원되지 않는 운영 체제:$os_name"
break_end
linux_Settings
fi
- # 감지 된 운영 체제 정보를 인쇄합니다
- echo "운영 체제 감지 :$os_name $os_version"
- # 시스템 버전에 따라 해당 Elrepo 창고 구성을 설치하십시오.
+ # 감지된 운영 체제 정보 인쇄
+ echo "감지된 운영 체제:$os_name $os_version"
+ # 시스템 버전에 따라 해당 ELRepo 창고 구성을 설치하십시오.
if [[ "$os_version" == 8 ]]; then
- echo "Elrepo 저장소 구성 (버전 8)을 설치하십시오 ..."
+ echo "ELRepo 저장소 구성(버전 8) 설치 중..."
yum -y install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm
elif [[ "$os_version" == 9 ]]; then
- echo "Elrepo 저장소 구성 (버전 9)을 설치하십시오 ..."
+ echo "ELRepo 저장소 구성(버전 9) 설치 중..."
yum -y install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm
elif [[ "$os_version" == 10 ]]; then
- echo "Elrepo 저장소 구성 (버전 10)을 설치하십시오 ..."
+ echo "ELRepo 저장소 구성(버전 10) 설치 중..."
yum -y install https://www.elrepo.org/elrepo-release-10.el10.elrepo.noarch.rpm
else
- echo "지원되지 않는 시스템 버전 :$os_version"
+ echo "지원되지 않는 시스템 버전:$os_version"
break_end
linux_Settings
fi
- # Elrepo 커널 저장소를 활성화하고 최신 메인 라인 커널을 설치하십시오.
- echo "Elrepo 커널 저장소를 활성화하고 최신 메인 라인 커널을 설치하십시오 ..."
+ # ELRepo 커널 저장소를 활성화하고 최신 메인라인 커널을 설치합니다.
+ echo "ELRepo 커널 저장소를 활성화하고 최신 메인라인 커널을 설치하세요..."
# yum -y --enablerepo=elrepo-kernel install kernel-ml
yum --nogpgcheck -y --enablerepo=elrepo-kernel install kernel-ml
- echo "Elrepo 저장소 구성이 설치되어 최신 메인 라인 커널로 업데이트됩니다."
+ echo "ELRepo 저장소 구성을 설치하고 최신 메인라인 커널로 업데이트했습니다."
server_reboot
}
@@ -4971,37 +5856,37 @@ elrepo_install() {
elrepo() {
root_use
- send_stats "레드 모자 커널 관리"
+ send_stats "Red Hat 커널 관리"
if uname -r | grep -q 'elrepo'; then
while true; do
clear
kernel_version=$(uname -r)
- echo "Elrepo 커널을 설치했습니다"
- echo "현재 커널 버전 :$kernel_version"
+ echo "elrepo 커널을 설치했습니다"
+ echo "현재 커널 버전:$kernel_version"
echo ""
echo "커널 관리"
echo "------------------------"
- echo "1. Elrepo 커널 업데이트 2. Elrepo 커널 제거"
+ echo "1. elrepo 커널 업데이트 2. elrepo 커널 제거"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
elrepo_install
- send_stats "Red Hat 커널을 업데이트하십시오"
+ send_stats "Red Hat 커널 업데이트"
server_reboot
;;
2)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
- echo "Elrepo 커널은 제거되었습니다. 다시 시작한 후에도 적용됩니다"
- send_stats "빨간 모자 커널을 제거하십시오"
+ echo "elrepo 커널이 제거되었습니다. 재시작 후 적용"
+ send_stats "Red Hat 커널 제거"
server_reboot
;;
@@ -5014,26 +5899,26 @@ elrepo() {
else
clear
- echo "데이터를 백업하고 Linux 커널을 업그레이드합니다."
- echo "비디오 소개 : https://www.bilibili.com/video/bv1mh4y1w7qa?t=529.2"
+ echo "데이터를 백업해 주시면 Linux 커널을 업그레이드해 드리겠습니다."
+ echo "영상 소개: https://www.bilibili.com/video/BV1mH4y1w7qA?t=529.2"
echo "------------------------------------------------"
- echo "Red Hat 시리즈 배포 Centos/Redhat/Alma/Rocky/Oracle 만 지원합니다"
- echo "Linux 커널을 업그레이드하면 시스템 성능 및 보안이 향상 될 수 있습니다. 조건이 허용되고 생산 환경을 조심스럽게 업그레이드하는 경우 시도하는 것이 좋습니다!"
+ echo "Red Hat 시리즈 배포판 CentOS/RedHat/Alma/Rocky/oracle만 지원"
+ echo "Linux 커널을 업그레이드하면 시스템 성능과 보안이 향상될 수 있습니다. 가능하다면 시도해 보시고, 프로덕션 환경을 주의해서 업그레이드하시는 것을 추천드립니다!"
echo "------------------------------------------------"
- read -e -p "계속할거야? (Y/N) :" choice
+ read -e -p "계속하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
check_swap
elrepo_install
- send_stats "Red Hat 커널을 업그레이드하십시오"
+ send_stats "Red Hat 커널 업그레이드"
server_reboot
;;
[Nn])
echo "취소"
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
fi
@@ -5044,7 +5929,7 @@ elrepo() {
clamav_freshclam() {
- echo -e "${gl_huang}바이러스 데이터베이스 업데이트 ...${gl_bai}"
+ echo -e "${gl_kjlan}바이러스 데이터베이스 업데이트 중...${gl_bai}"
docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
@@ -5054,19 +5939,19 @@ clamav_freshclam() {
clamav_scan() {
if [ $# -eq 0 ]; then
- echo "스캔 할 디렉토리를 지정하십시오."
+ echo "스캔할 디렉터리를 지정하세요."
return
fi
- echo -e "${gl_huang}스캔 디렉토리 $@...${gl_bai}"
+ echo -e "${gl_kjlan}$@ 디렉터리를 검색하는 중...${gl_bai}"
- # 마운트 매개 변수를 빌드하십시오
+ # 마운트 매개변수 빌드
local MOUNT_PARAMS=""
for dir in "$@"; do
MOUNT_PARAMS+="--mount type=bind,source=${dir},target=/mnt/host${dir} "
done
- # Clamscan 명령 매개 변수를 작성하십시오
+ # clamscan 명령 매개변수 빌드
local SCAN_PARAMS=""
for dir in "$@"; do
SCAN_PARAMS+="/mnt/host${dir} "
@@ -5075,8 +5960,8 @@ clamav_scan() {
mkdir -p /home/docker/clamav/log/ > /dev/null 2>&1
> /home/docker/clamav/log/scan.log > /dev/null 2>&1
- # Docker 명령을 실행하십시오
- docker run -it --rm \
+ # 도커 명령 실행
+ docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
$MOUNT_PARAMS \
@@ -5084,8 +5969,8 @@ clamav_scan() {
clamav/clamav-debian:latest \
clamscan -r --log=/var/log/clamav/scan.log $SCAN_PARAMS
- echo -e "${gl_lv}$@ scan이 완료되고 바이러스 보고서가 저장됩니다${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
- echo -e "${gl_lv}바이러스가 있다면 제발${gl_huang}scan.log${gl_lv}파일에서 찾은 키워드를 검색하여 바이러스의 위치를 확인하십시오.${gl_bai}"
+ echo -e "${gl_lv}$@ 扫描完成,病毒报告存放在${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
+ echo -e "${gl_lv}바이러스 있으면 제발${gl_huang}scan.log${gl_lv}파일에서 FOUND 키워드를 검색하여 바이러스 위치를 확인하세요.${gl_bai}"
}
@@ -5097,23 +5982,23 @@ clamav_scan() {
clamav() {
root_use
- send_stats "바이러스 스캔 관리"
+ send_stats "바이러스 검사 관리"
while true; do
clear
- echo "Clamav 바이러스 스캐닝 도구"
- echo "비디오 소개 : https://www.bilibili.com/video/bv1tqvze4eqm?t=0.1"
+ echo "clamav 바이러스 검사 도구"
+ echo "영상 소개: https://www.bilibili.com/video/BV1TqvZe4EQm?t=0.1"
echo "------------------------"
- echo "오픈 소스 바이러스 백신 소프트웨어 도구로 주로 다양한 유형의 맬웨어를 감지하고 제거하는 데 사용됩니다."
- echo "바이러스, 트로이 목마, 스파이웨어, 악성 스크립트 및 기타 유해한 소프트웨어를 포함합니다."
+ echo "다양한 유형의 악성 코드를 탐지하고 제거하는 데 주로 사용되는 오픈 소스 바이러스 백신 소프트웨어 도구입니다."
+ echo "바이러스, 트로이 목마, 스파이웨어, 악성 스크립트 및 기타 유해한 소프트웨어가 포함됩니다."
echo "------------------------"
- echo -e "${gl_lv}1. 전체 디스크 스캔${gl_bai} ${gl_huang}2. 중요한 디렉토리를 스캔하십시오${gl_bai} ${gl_kjlan}3. 사용자 정의 디렉토리 스캔${gl_bai}"
+ echo -e "${gl_lv}1. 전체 스캔${gl_bai} ${gl_huang}2. 중요한 디렉토리 스캔${gl_bai} ${gl_kjlan}3. 사용자 정의 디렉터리 검색${gl_bai}"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- send_stats "전체 디스크 스캔"
+ send_stats "전체 스캔"
install_docker
docker volume create clam_db > /dev/null 2>&1
clamav_freshclam
@@ -5131,7 +6016,7 @@ clamav() {
;;
3)
send_stats "사용자 정의 디렉토리 스캔"
- read -e -p "스캔 할 디렉토리를 입력하십시오." directories
+ read -e -p "스캔할 디렉터리를 공백으로 구분하여 입력하십시오(예: /etc /var /usr /home /root)." directories
install_docker
clamav_freshclam
clamav_scan $directories
@@ -5146,171 +6031,348 @@ clamav() {
}
+# ============================================================================
+# Linux 커널 조정 모듈(리팩터링 버전)
+# 통합 핵심 기능 + 장면 차별화 매개변수 + 구성 파일 지속성 + 하드웨어 적응
+# 원래의optim_high_performance/optim_balanced/optim_web_server/restore_defaults를 교체하십시오.
+# ============================================================================
+# 메모리 크기(MB) 가져오기
+_get_mem_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
+}
-# 고성능 모드 최적화 기능
-optimize_high_performance() {
- echo -e "${gl_lv}전환하십시오${tiaoyou_moshi}...${gl_bai}"
+# 통합 커널 튜닝 핵심 기능
+# 매개변수: $1 = 모드 이름, $2 = 장면(하이/밸런스/웹/스트림/게임)
+_kernel_optimize_core() {
+ local mode_name="$1"
+ local scene="${2:-high}"
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
+ local MEM_MB=$(_get_mem_mb)
+
+ echo -e "${gl_lv}로 전환하다${mode_name}...${gl_bai}"
+
+ # ──장면에 따라 매개변수를 설정합니다──
+ local SWAPPINESS DIRTY_RATIO DIRTY_BG_RATIO OVERCOMMIT MIN_FREE_KB VFS_PRESSURE
+ local RMEM_MAX WMEM_MAX TCP_RMEM TCP_WMEM
+ local SOMAXCONN BACKLOG SYN_BACKLOG
+ local PORT_RANGE SCHED_AUTOGROUP THP NUMA FIN_TIMEOUT
+ local KEEPALIVE_TIME KEEPALIVE_INTVL KEEPALIVE_PROBES
+
+ case "$scene" in
+ high|stream|game)
+ # 고성능/생방송/게임: 급진적인 매개변수
+ SWAPPINESS=10
+ DIRTY_RATIO=15
+ DIRTY_BG_RATIO=5
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=67108864
+ WMEM_MAX=67108864
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ SOMAXCONN=8192
+ BACKLOG=250000
+ SYN_BACKLOG=8192
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=10
+ KEEPALIVE_TIME=300
+ KEEPALIVE_INTVL=30
+ KEEPALIVE_PROBES=5
+ ;;
+ web)
+ # 웹사이트 서버: 높은 동시성 우선순위
+ SWAPPINESS=10
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=33554432
+ WMEM_MAX=33554432
+ TCP_RMEM="4096 131072 33554432"
+ TCP_WMEM="4096 131072 33554432"
+ SOMAXCONN=16384
+ BACKLOG=10000
+ SYN_BACKLOG=16384
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=15
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ balanced)
+ # 균형 모드: 보통 최적화
+ SWAPPINESS=30
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=0
+ VFS_PRESSURE=75
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ SOMAXCONN=4096
+ BACKLOG=5000
+ SYN_BACKLOG=4096
+ PORT_RANGE="1024 49151"
+ SCHED_AUTOGROUP=1
+ THP="always"
+ NUMA=1
+ FIN_TIMEOUT=30
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ esac
- echo -e "${gl_lv}파일 설명자 최적화 ...${gl_bai}"
- ulimit -n 65535
+ # ── 메모리 크기에 따라 적응적으로 조정 ──
+ if [ "$MEM_MB" -ge 16384 ]; then
+ MIN_FREE_KB=131072
+ [ "$scene" != "balanced" ] && SWAPPINESS=5
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ MIN_FREE_KB=32768
+ # 작은 메모리 축소 버퍼
+ if [ "$scene" != "balanced" ]; then
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ fi
+ else
+ MIN_FREE_KB=16384
+ SWAPPINESS=30
+ OVERCOMMIT=0
+ RMEM_MAX=4194304
+ WMEM_MAX=4194304
+ TCP_RMEM="4096 32768 4194304"
+ TCP_WMEM="4096 32768 4194304"
+ SOMAXCONN=1024
+ BACKLOG=1000
+ fi
- echo -e "${gl_lv}가상 메모리 최적화 ...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=15 2>/dev/null
- sysctl -w vm.dirty_background_ratio=5 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # ── 추가 라이브 방송 시나리오: UDP 버퍼 확대 ──
+ local STREAM_EXTRA=""
+ if [ "$scene" = "stream" ]; then
+ STREAM_EXTRA="
+# 라이브 스트리밍 UDP 최적화
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
- echo -e "${gl_lv}네트워크 설정 최적화 ...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=250000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
-
- echo -e "${gl_lv}캐시 관리 최적화 ...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
-
- echo -e "${gl_lv}CPU 설정 최적화 ...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
-
- echo -e "${gl_lv}기타 최적화 ...${gl_bai}"
- # 대형 투명 페이지를 비활성화하여 대기 시간을 줄입니다
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # NUMA 밸런싱을 비활성화합니다
- sysctl -w kernel.numa_balancing=0 2>/dev/null
-
-
-}
-
-# 이퀄라이제이션 모드 최적화 기능
-optimize_balanced() {
- echo -e "${gl_lv}이퀄라이제이션 모드로 전환 ...${gl_bai}"
-
- echo -e "${gl_lv}파일 설명자 최적화 ...${gl_bai}"
- ulimit -n 32768
+ # ── 게임 서버 장면 추가: 낮은 대기 시간 우선 ──
+ local GAME_EXTRA=""
+ if [ "$scene" = "game" ]; then
+ GAME_EXTRA="
+# 게임 서버 낮은 대기 시간 최적화
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384
+net.ipv4.tcp_slow_start_after_idle = 0"
+ fi
- echo -e "${gl_lv}가상 메모리 최적화 ...${gl_bai}"
- sysctl -w vm.swappiness=30 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=32768 2>/dev/null
+ # ── BBR 모듈 로드 ──
+ local CC="bbr"
+ local QDISC="fq"
+ local KVER
+ KVER=$(uname -r | grep -oP '^\d+\.\d+')
+ if printf '%s\n%s' "4.9" "$KVER" | sort -V -C; then
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ fi
+ if ! sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
+ else
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
- echo -e "${gl_lv}네트워크 설정 최적화 ...${gl_bai}"
- sysctl -w net.core.rmem_max=8388608 2>/dev/null
- sysctl -w net.core.wmem_max=8388608 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=125000 2>/dev/null
- sysctl -w net.core.somaxconn=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 32768 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 49151' 2>/dev/null
+ # ── 기존 구성 백업 ──
+ [ -f "$CONF" ] && cp "$CONF" "${CONF}.bak.$(date +%s)"
+
+ # ── 구성 파일 쓰기(지속성) ──
+ echo -e "${gl_lv}최적화 구성 쓰기...${gl_bai}"
+ cat > "$CONF" << SYSCTL
+# 케질리온 커널 튜닝 구성
+# 모드: $mode_name | 장면: $장면
+# 메모리: ${MEM_MB}MB | 생성 시간: $(날짜 '+%Y-%m-%d %H:%M:%S')
+
+# ──TCP 혼잡 제어──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $CC
+
+# ── TCP 버퍼 ──
+net.core.rmem_max = $RMEM_MAX
+net.core.wmem_max = $WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+
+# ── 연결 큐 ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $BACKLOG
+net.ipv4.tcp_max_syn_backlog = $SYN_BACKLOG
+
+# ── TCP 연결 최적화 ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = $FIN_TIMEOUT
+net.ipv4.tcp_keepalive_time = $KEEPALIVE_TIME
+net.ipv4.tcp_keepalive_intvl = $KEEPALIVE_INTVL
+net.ipv4.tcp_keepalive_probes = $KEEPALIVE_PROBES
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── 포트와 메모리 ──
+net.ipv4.ip_local_port_range = $PORT_RANGE
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── 가상 메모리 ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = $DIRTY_RATIO
+vm.dirty_background_ratio = $DIRTY_BG_RATIO
+vm.overcommit_memory = $OVERCOMMIT
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = $VFS_PRESSURE
+
+# ──CPU/커널 스케줄링──
+kernel.sched_autogroup_enabled = $SCHED_AUTOGROUP
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = $NUMA" || echo "# numa_balancing은 지원되지 않습니다")
+
+# ──안전 보호──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── 파일 설명자 ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── 연결 추적 ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+echo "net.netfilter.nf_conntrack_max = $((SOMAXCONN * 32))"
+echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+echo "# conntrack이 활성화되어 있지 않습니다"
+fi)
+$STREAM_EXTRA
+$GAME_EXTRA
+SYSCTL
+
+ # ── 구성 적용(한 줄씩, 지원되지 않는 매개변수 건너뛰기) ──
+ echo -e "${gl_lv}최적화 매개변수 적용...${gl_bai}"
+ local applied=0 skipped=0
+ while IFS= read -r line; do
+ # 주석과 빈 줄 건너뛰기
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
+ [[ -z "${line// /}" ]] && continue
+ if sysctl -w "$line" >/dev/null 2>&1; then
+ applied=$((applied + 1))
+ else
+ skipped=$((skipped + 1))
+ fi
+ done < "$CONF"
+ echo -e "${gl_lv}적용된${applied}항목 매개변수 ${skipped:+, 건너뛰기${skipped}지원되지 않는 항목 매개변수}${gl_bai}"
- echo -e "${gl_lv}캐시 관리 최적화 ...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=75 2>/dev/null
+ # ── 투명 대형 페이지 ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo "$THP" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
+ fi
- echo -e "${gl_lv}CPU 설정 최적화 ...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
+ # ── 파일 설명자 제한 사항 ──
+ if ! grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
- echo -e "${gl_lv}기타 최적화 ...${gl_bai}"
- # 투명 페이지를 복원하십시오
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # NUMA 밸런싱을 복원하십시오
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# kejilion-optimize
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ fi
+ # ── BBR 지속성 ──
+ if [ "$CC" = "bbr" ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ # 이전 sysctl.conf에서 bbr 구성을 정리합니다(충돌을 방지하기 위해).
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+ fi
+ echo -e "${gl_lv}${mode_name}최적화 완료! 구성은 다음과 같이 유지되었습니다.${CONF}${gl_bai}"
+ echo -e "${gl_lv}메모리:${MEM_MB}MB | 혼잡 알고리즘:${CC}| 대기줄:${QDISC}${gl_bai}"
}
-# 기본 설정 기능을 복원하십시오
-restore_defaults() {
- echo -e "${gl_lv}기본 설정으로 복원하십시오 ...${gl_bai}"
-
- echo -e "${gl_lv}파일 디스크립터 복원 ...${gl_bai}"
- ulimit -n 1024
-
- echo -e "${gl_lv}가상 메모리 복원 ...${gl_bai}"
- sysctl -w vm.swappiness=60 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=16384 2>/dev/null
-
- echo -e "${gl_lv}네트워크 설정 복원 ...${gl_bai}"
- sysctl -w net.core.rmem_max=212992 2>/dev/null
- sysctl -w net.core.wmem_max=212992 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=1000 2>/dev/null
- sysctl -w net.core.somaxconn=128 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=0 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='32768 60999' 2>/dev/null
-
- echo -e "${gl_lv}캐시 관리 복원 ...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=100 2>/dev/null
-
- echo -e "${gl_lv}CPU 설정 복원 ...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}다른 최적화를 복원 ...${gl_bai}"
- # 투명 페이지를 복원하십시오
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # NUMA 밸런싱을 복원하십시오
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# ── 각 모드의 진입 기능(원래 호출 인터페이스를 그대로 유지) ──
+optimize_high_performance() {
+ _kernel_optimize_core "${tiaoyou_moshi:-高性能优化模式}" "high"
}
+optimize_balanced() {
+ _kernel_optimize_core "균형 잡힌 최적화 모드" "balanced"
+}
-
-# 웹 사이트 구축 최적화 기능
optimize_web_server() {
- echo -e "${gl_lv}웹 사이트 구축 최적화 모드로 전환하십시오 ...${gl_bai}"
+ _kernel_optimize_core "웹사이트 구축 최적화 모드" "web"
+}
- echo -e "${gl_lv}파일 설명자 최적화 ...${gl_bai}"
- ulimit -n 65535
+# ── 기본 설정 복원(완전 청소) ──
+restore_defaults() {
+ echo -e "${gl_lv}기본 설정으로 되돌리기...${gl_bai}"
+
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
- echo -e "${gl_lv}가상 메모리 최적화 ...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # 최적화 구성 파일 삭제(외부 링크 자동 튜닝 구성 포함)
+ rm -f "$CONF"
+ rm -f /etc/sysctl.d/99-network-optimize.conf
- echo -e "${gl_lv}네트워크 설정 최적화 ...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=5000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # sysctl.conf에서 가능한 나머지 bbr 구성을 정리합니다.
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
- echo -e "${gl_lv}캐시 관리 최적화 ...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # 시스템 기본 구성 다시 로드
+ sysctl --system 2>/dev/null | tail -1
- echo -e "${gl_lv}CPU 설정 최적화 ...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # 투명한 대용량 페이지 복원
+ [ -f /sys/kernel/mm/transparent_hugepage/enabled ] && \
+ echo always > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
- echo -e "${gl_lv}기타 최적화 ...${gl_bai}"
- # 대형 투명 페이지를 비활성화하여 대기 시간을 줄입니다
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # NUMA 밸런싱을 비활성화합니다
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # 파일 설명자 구성 정리
+ if grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# kejilion-optimize/,+4d' /etc/security/limits.conf
+ fi
+ # BBR 지속성 정리
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+ echo -e "${gl_lv}시스템이 기본 설정으로 복원되었습니다.${gl_bai}"
}
@@ -5319,27 +6381,35 @@ Kernel_optimize() {
while true; do
clear
send_stats "Linux 커널 튜닝 관리"
- echo "Linux 시스템에서 커널 매개 변수의 최적화"
- echo "비디오 소개 : https://www.bilibili.com/video/bv1kb421j7 yg?t=0.1"
+ local current_mode=$(grep "^# 모드:" /etc/sysctl.d/99-kejilion-optimize.conf 2>/dev/null | sed 's/# 모드: //' | awk -F'|' '{print $1}' | xargs)
+ [ -z "$current_mode" ] && [ -f /etc/sysctl.d/99-network-optimize.conf ] && current_mode="자동 튜닝 모드"
+ echo "Linux 시스템 커널 매개변수 최적화"
+ if [ -n "$current_mode" ]; then
+ echo -e "현재 모드:${gl_lv}${current_mode}${gl_bai}"
+ else
+ echo -e "현재 모드:${gl_hui}최적화되지 않음${gl_bai}"
+ fi
+ echo "영상 소개: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
echo "------------------------------------------------"
- echo "다양한 시스템 매개 변수 튜닝 모드가 제공되며 사용자는 자체 사용 시나리오에 따라 선택하고 전환 할 수 있습니다."
- echo -e "${gl_huang}힌트:${gl_bai}생산 환경에서주의해서 사용하십시오!"
- echo "--------------------"
- echo "1. 고성능 최적화 모드 : 시스템 성능을 극대화하고 파일 설명기, 가상 메모리, 네트워크 설정, 캐시 관리 및 CPU 설정을 최적화합니다."
- echo "2. 균형 최적화 모드 : 매일 사용하기에 적합한 성능과 자원 소비 사이의 균형."
- echo "3. 웹 사이트 최적화 모드 : 웹 사이트 서버에 최적화하여 동시 연결 처리 기능, 응답 속도 및 전반적인 성능을 향상시킵니다."
- echo "4. 라이브 브로드 캐스트 최적화 모드 : 라이브 방송 스트리밍의 특별한 요구를 최적화하여 대기 시간을 줄이고 전송 성능을 향상시킵니다."
- echo "5. 게임 서버 최적화 모드 : 게임 서버를 위해 동시 처리 기능 및 응답 속도를 향상시킬 최적화."
- echo "6. 기본 설정을 복원하십시오. 시스템 설정을 기본 구성으로 복원하십시오."
+ echo "다양한 시스템 매개변수 조정 모드를 제공하며 사용자는 자신의 사용 시나리오에 따라 전환하도록 선택할 수 있습니다."
+ echo -e "${gl_huang}힌트:${gl_bai}프로덕션 환경에서는 주의해서 사용해주세요!"
+ echo -e "--------------------"
+ echo -e "1. 고성능 최적화 모드: 시스템 성능, 적극적인 메모리 및 네트워크 매개변수를 최대화합니다."
+ echo -e "2. 균형 잡힌 최적화 모드: 일상적인 사용에 적합한 성능과 리소스 소비 사이의 균형을 유지합니다."
+ echo -e "3. 웹사이트 최적화 모드: 웹사이트 서버에 최적화되어 있으며 동시 연결 대기열이 매우 높습니다."
+ echo -e "4. 라이브 방송 최적화 모드: 라이브 스트리밍 최적화를 위해 UDP 버퍼가 확장되어 지연이 줄어듭니다."
+ echo -e "5. 게임 서버 최적화 모드: 게임 서버에 최적화되어 낮은 대기 시간을 우선시합니다."
+ echo -e "6. 기본 설정 복원: 시스템 설정을 기본 구성으로 복원합니다."
+ echo -e "7. 자동 튜닝: 테스트 데이터를 기반으로 커널 매개변수를 자동으로 튜닝합니다.${gl_huang}★${gl_bai}"
echo "--------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "--------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
cd ~
clear
- local tiaoyou_moshi="高性能优化模式"
+ local tiaoyou_moshi="고성능 최적화 모드"
optimize_high_performance
send_stats "고성능 모드 최적화"
;;
@@ -5353,28 +6423,35 @@ Kernel_optimize() {
cd ~
clear
optimize_web_server
- send_stats "웹 사이트 최적화 모델"
+ send_stats "웹사이트 최적화 모드"
;;
4)
cd ~
clear
- local tiaoyou_moshi="直播优化模式"
- optimize_high_performance
+ _kernel_optimize_core "라이브 방송 최적화 모드" "stream"
send_stats "라이브 스트리밍 최적화"
;;
5)
cd ~
clear
- local tiaoyou_moshi="游戏服优化模式"
- optimize_high_performance
+ _kernel_optimize_core "게임 서버 최적화 모드" "game"
send_stats "게임 서버 최적화"
;;
6)
cd ~
clear
restore_defaults
- send_stats "기본 설정을 복원하십시오"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh -o /tmp/network-optimize.sh && source /tmp/network-optimize.sh && restore_network_defaults
+ send_stats "기본 설정 복원"
;;
+
+ 7)
+ cd ~
+ clear
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ send_stats "커널 자동 튜닝"
+ ;;
+
*)
break
;;
@@ -5387,6 +6464,8 @@ Kernel_optimize() {
+
+
update_locale() {
local lang=$1
local locale_file=$2
@@ -5400,7 +6479,7 @@ update_locale() {
locale-gen
echo "LANG=${lang}" > /etc/default/locale
export LANG=${lang}
- echo -e "${gl_lv}시스템 언어는 다음으로 수정되었습니다.$langSSH를 다시 연결하면 적용됩니다.${gl_bai}"
+ echo -e "${gl_lv}시스템 언어가 다음과 같이 수정되었습니다.$lang적용하려면 SSH에 다시 연결하세요.${gl_bai}"
hash -r
break_end
@@ -5409,17 +6488,17 @@ update_locale() {
install glibc-langpack-zh
localectl set-locale LANG=${lang}
echo "LANG=${lang}" | tee /etc/locale.conf
- echo -e "${gl_lv}시스템 언어는 다음으로 수정되었습니다.$langSSH를 다시 연결하면 적용됩니다.${gl_bai}"
+ echo -e "${gl_lv}시스템 언어가 다음과 같이 수정되었습니다.$lang적용하려면 SSH에 다시 연결하세요.${gl_bai}"
hash -r
break_end
;;
*)
- echo "지원되지 않는 시스템 :$ID"
+ echo "지원되지 않는 시스템:$ID"
break_end
;;
esac
else
- echo "지원되지 않는 시스템, 시스템 유형을 인식 할 수 없습니다."
+ echo "지원되지 않는 시스템, 시스템 유형을 식별할 수 없습니다."
break_end
fi
}
@@ -5429,29 +6508,29 @@ update_locale() {
linux_language() {
root_use
-send_stats "스위치 시스템 언어"
+send_stats "시스템 언어 전환"
while true; do
clear
- echo "현재 시스템 언어 :$LANG"
+ echo "현재 시스템 언어:$LANG"
echo "------------------------"
- echo "1. 영어 2. 중국어 3. 전통 중국어"
+ echo "1. 영어 2. 중국어 간체 3. 중국어 번체"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
update_locale "en_US.UTF-8" "en_US.UTF-8"
- send_stats "영어로 전환하십시오"
+ send_stats "영어로 전환"
;;
2)
update_locale "zh_CN.UTF-8" "zh_CN.UTF-8"
- send_stats "단순화 된 중국어로 전환하십시오"
+ send_stats "중국어 간체로 전환"
;;
3)
update_locale "zh_TW.UTF-8" "zh_TW.UTF-8"
- send_stats "전통적인 중국어로 전환하십시오"
+ send_stats "중국어 번체로 전환"
;;
*)
break
@@ -5473,7 +6552,7 @@ else
echo "${bianse}" >> ~/.profile
# source ~/.profile
fi
-echo -e "${gl_lv}변경이 완료되었습니다. 변경 사항을 볼 수 있도록 SSH를 다시 연결하십시오!${gl_bai}"
+echo -e "${gl_lv}변경이 완료되었습니다. 변경 사항을 보려면 SSH에 다시 연결하세요!${gl_bai}"
hash -r
break_end
@@ -5484,10 +6563,10 @@ break_end
shell_bianse() {
root_use
- send_stats "명령 라인 미화 도구"
+ send_stats "명령줄 미화 도구"
while true; do
clear
- echo "명령 라인 미화 도구"
+ echo "명령줄 미화 도구"
echo "------------------------"
echo -e "1. \033[1;32mroot \033[1;34mlocalhost \033[1;31m~ \033[0m${gl_bai}#"
echo -e "2. \033[1;35mroot \033[1;36mlocalhost \033[1;33m~ \033[0m${gl_bai}#"
@@ -5497,9 +6576,9 @@ shell_bianse() {
echo -e "6. \033[1;33mroot \033[1;34mlocalhost \033[1;35m~ \033[0m${gl_bai}#"
echo -e "7. root localhost ~ #"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
@@ -5544,7 +6623,7 @@ shell_bianse() {
linux_trash() {
root_use
- send_stats "시스템 재활용 스테이션"
+ send_stats "시스템 휴지통"
local bashrc_profile="/root/.bashrc"
local TRASH_DIR="$HOME/.local/share/Trash/files"
@@ -5553,23 +6632,23 @@ linux_trash() {
local trash_status
if ! grep -q "trash-put" "$bashrc_profile"; then
- trash_status="${gl_hui}未启用${gl_bai}"
+ trash_status="${gl_hui}활성화되지 않음${gl_bai}"
else
- trash_status="${gl_lv}已启用${gl_bai}"
+ trash_status="${gl_lv}활성화됨${gl_bai}"
fi
clear
- echo -e "현재 재활용 쓰레기통${trash_status}"
- echo -e "활성화 후 RM이 삭제 한 파일은 먼저 재활용 빈에 입력하여 중요한 파일의 잘못된 삭제를 방지합니다!"
+ echo -e "현재 휴지통${trash_status}"
+ echo -e "활성화한 후에는 중요한 파일이 실수로 삭제되는 것을 방지하기 위해 rm으로 삭제된 파일이 먼저 휴지통에 들어갑니다!"
echo "------------------------------------------------"
- ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "재활용 쓰레기통은 비어 있습니다"
+ ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "휴지통이 비어 있습니다."
echo "------------------------"
- echo "1. 재활용 빈을 활성화합니다. 2. 재활용 쓰레기통을 닫습니다."
- echo "3. 컨텐츠를 복원 4. 재활용 쓰레기통을 지 웁니다"
+ echo "1. 휴지통 활성화 2. 휴지통 닫기"
+ echo "3. 콘텐츠 복원 4. 휴지통 비우기"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
@@ -5577,7 +6656,7 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='trash-put'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "Recycle Bin이 활성화되고 삭제 된 파일이 Recycle Bin으로 이동됩니다."
+ echo "휴지통이 활성화되면 삭제된 파일은 휴지통으로 이동됩니다."
sleep 2
;;
2)
@@ -5585,11 +6664,11 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='rm -i'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "재활용 빈이 닫히고 파일이 직접 삭제됩니다."
+ echo "휴지통이 닫히고 파일이 직접 삭제됩니다."
sleep 2
;;
3)
- read -e -p "복원 할 파일 이름을 입력하십시오." file_to_restore
+ read -e -p "복원할 파일 이름을 입력하세요:" file_to_restore
if [ -e "$TRASH_DIR/$file_to_restore" ]; then
mv "$TRASH_DIR/$file_to_restore" "$HOME/"
echo "$file_to_restore홈 디렉토리로 복원되었습니다."
@@ -5598,10 +6677,10 @@ linux_trash() {
fi
;;
4)
- read -e -p "재활용 쓰레기통을 지우셨습니까? [Y/N] :" confirm
+ read -e -p "휴지통을 비우시겠습니까? [예/아니요]:" confirm
if [[ "$confirm" == "y" ]]; then
trash-empty
- echo "재활용 쓰레기통이 지워졌습니다."
+ echo "휴지통이 비워졌습니다."
fi
;;
*)
@@ -5611,21 +6690,24 @@ linux_trash() {
done
}
+linux_fav() {
+send_stats "명령 즐겨찾기"
+bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+}
-
-# 백업을 만듭니다
+# 백업 만들기
create_backup() {
- send_stats "백업을 만듭니다"
+ send_stats "백업 만들기"
local TIMESTAMP=$(date +"%Y%m%d%H%M%S")
- # 사용자에게 백업 디렉토리를 입력하라는 메시지를 표시하십시오
- echo "백업 예제 :"
- echo "- 단일 디렉토리를 백업 : /var /www"
- echo "- 여러 디렉토리 백업 : /etc /home /var /log"
- echo "- Direct Enter는 기본 디렉토리 ( /etc /usr /home)를 사용합니다."
- read -r -p "백업 디렉토리를 입력하십시오 (여러 디렉토리가 공간별로 구분되며 직접 입력하면 기본 디렉토리를 사용하십시오)." input
+ # 사용자에게 백업 디렉터리를 묻는 메시지 표시
+ echo "백업 생성 예:"
+ echo "- 단일 디렉터리 백업: /var/www"
+ echo "- 여러 디렉터리 백업: /etc /home /var/log"
+ echo "- Enter를 눌러 기본 디렉터리(/etc/usr/home)를 사용합니다."
+ read -e -p "백업할 디렉터리를 입력하십시오(여러 디렉터리를 공백으로 구분하고 Enter를 눌러 기본 디렉터리를 사용하십시오)." input
- # 사용자가 디렉토리를 입력하지 않으면 기본 디렉토리를 사용하십시오.
+ # 사용자가 디렉터리를 입력하지 않으면 기본 디렉터리가 사용됩니다.
if [ -z "$input" ]; then
BACKUP_PATHS=(
"/etc" # 配置文件和软件包配置
@@ -5633,92 +6715,92 @@ create_backup() {
"/home" # 用户数据
)
else
- # 사용자가 입력 한 디렉토리를 공백 별 배열로 분리합니다.
+ # 사용자가 입력한 디렉토리를 공백으로 배열로 구분합니다.
IFS=' ' read -r -a BACKUP_PATHS <<< "$input"
fi
- # 백업 파일 접두사를 생성합니다
+ # 백업 파일 접두사 생성
local PREFIX=""
for path in "${BACKUP_PATHS[@]}"; do
- # 디렉토리 이름을 추출하고 슬래시를 제거하십시오
+ # 디렉토리 이름 추출 및 슬래시 제거
dir_name=$(basename "$path")
PREFIX+="${dir_name}_"
done
- # 마지막 밑줄을 제거하십시오
+ # 마지막 밑줄 제거
local PREFIX=${PREFIX%_}
- # 백업 파일 이름을 생성합니다
+ # 백업 파일 이름 생성
local BACKUP_NAME="${PREFIX}_$TIMESTAMP.tar.gz"
- # 사용자가 선택한 디렉토리를 인쇄하십시오
- echo "선택한 백업 디렉토리는 다음과 같습니다."
+ # 사용자가 선택한 디렉토리 인쇄
+ echo "선택한 백업 디렉터리는 다음과 같습니다."
for path in "${BACKUP_PATHS[@]}"; do
echo "- $path"
done
- # 백업을 만듭니다
+ # 백업 만들기
echo "백업 생성$BACKUP_NAME..."
install tar
tar -czvf "$BACKUP_DIR/$BACKUP_NAME" "${BACKUP_PATHS[@]}"
- # 명령이 성공했는지 확인하십시오
+ # 명령이 성공했는지 확인
if [ $? -eq 0 ]; then
- echo "백업은 성공적으로 생성되었습니다.$BACKUP_DIR/$BACKUP_NAME"
+ echo "백업이 성공적으로 생성되었습니다:$BACKUP_DIR/$BACKUP_NAME"
else
- echo "백업 생성이 실패했습니다!"
+ echo "백업 생성에 실패했습니다!"
exit 1
fi
}
-# 백업을 복원하십시오
+# 백업 복원
restore_backup() {
- send_stats "백업을 복원하십시오"
- # 복원하려는 백업을 선택하십시오
- read -e -p "복원하려면 백업 파일 이름을 입력하십시오." BACKUP_NAME
+ send_stats "백업 복원"
+ # 복원할 백업을 선택하세요
+ read -e -p "복원할 백업 파일 이름을 입력하십시오:" BACKUP_NAME
- # 백업 파일이 있는지 확인하십시오
+ # 백업 파일이 있는지 확인
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
echo "백업 파일이 존재하지 않습니다!"
exit 1
fi
- echo "백업 복구$BACKUP_NAME..."
+ echo "백업 복원 중$BACKUP_NAME..."
tar -xzvf "$BACKUP_DIR/$BACKUP_NAME" -C /
if [ $? -eq 0 ]; then
- echo "백업 및 복원을 성공적으로 복원하십시오!"
+ echo "백업 및 복원 성공!"
else
- echo "백업 복구 실패!"
+ echo "백업 복원에 실패했습니다!"
exit 1
fi
}
-# 백업을 나열합니다
+# 백업 나열
list_backups() {
- echo "사용 가능한 백업 :"
+ echo "사용 가능한 백업:"
ls -1 "$BACKUP_DIR"
}
-# 백업을 삭제하십시오
+# 백업 삭제
delete_backup() {
- send_stats "백업을 삭제하십시오"
+ send_stats "백업 삭제"
- read -e -p "삭제하려면 백업 파일 이름을 입력하십시오." BACKUP_NAME
+ read -e -p "삭제할 백업 파일 이름을 입력하십시오:" BACKUP_NAME
- # 백업 파일이 있는지 확인하십시오
+ # 백업 파일이 있는지 확인
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
echo "백업 파일이 존재하지 않습니다!"
exit 1
fi
- # 백업을 삭제하십시오
+ # 백업 삭제
rm -f "$BACKUP_DIR/$BACKUP_NAME"
if [ $? -eq 0 ]; then
- echo "백업이 성공적으로 삭제되었습니다!"
+ echo "백업이 삭제되었습니다!"
else
- echo "백업 삭제가 실패했습니다!"
+ echo "백업 삭제에 실패했습니다!"
exit 1
fi
}
@@ -5734,18 +6816,18 @@ linux_backup() {
echo "------------------------"
list_backups
echo "------------------------"
- echo "1. 백업 만들기 2. 백업 복원 3. 백업 삭제"
+ echo "1. 백업 생성 2. 백업 복원 3. 백업 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case $choice in
1) create_backup ;;
2) restore_backup ;;
3) delete_backup ;;
*) break ;;
esac
- read -e -p "계속하려면 Enter를 누르십시오 ..."
+ read -e -p "계속하려면 Enter를 누르세요..."
done
}
@@ -5757,89 +6839,209 @@ linux_backup() {
-# 연결 목록 표시
-list_connections() {
- echo "저장된 연결 :"
- echo "------------------------"
- cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
- echo "------------------------"
+# SSH 입력 정규화 기능
+kj_ssh_validate_host() {
+ local host="$1"
+ [[ -n "$host" && ! "$host" =~ [[:space:]] && "$host" =~ ^[A-Za-z0-9._:-]+$ ]]
+}
+
+kj_ssh_validate_port() {
+ local port="$1"
+ [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
}
+kj_ssh_validate_user() {
+ local user="$1"
+ [[ -n "$user" && "$user" =~ ^[A-Za-z_][A-Za-z0-9._-]*$ ]]
+}
-# 새 연결을 추가하십시오
-add_connection() {
- send_stats "새 연결을 추가하십시오"
- echo "새 연결을 만드는 예 :"
- echo "- 연결 이름 : my_server"
- echo "-IP 주소 : 192.168.1.100"
- echo "- 사용자 이름 : 루트"
- echo "- 포트 : 22"
- echo "------------------------"
- read -e -p "연결 이름을 입력하십시오 :" name
- read -e -p "IP 주소를 입력하십시오 :" ip
- read -e -p "사용자 이름 (기본값 : 루트)을 입력하십시오 :" user
- local user=${user:-root} # 如果用户未输入,则使用默认值 root
- read -e -p "포트 번호를 입력하십시오 (기본값 : 22) :" port
- local port=${port:-22} # 如果用户未输入,则使用默认值 22
-
- echo "인증 방법을 선택하십시오 :"
+kj_ssh_read_host_port() {
+ local host_prompt="$1"
+ local port_prompt="$2"
+ local default_port="${3:-22}"
+
+ while true; do
+ read -e -p "$host_prompt" KJ_SSH_HOST
+ if kj_ssh_validate_host "$KJ_SSH_HOST"; then
+ break
+ fi
+ echo "오류: 유효한 서버 주소를 입력하세요."
+ done
+
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ break
+ fi
+ echo "오류: 포트는 1~65535 사이의 숫자여야 합니다."
+ done
+}
+
+kj_ssh_read_host_user_port() {
+ local host_prompt="$1"
+ local user_prompt="$2"
+ local port_prompt="$3"
+ local default_user="${4:-root}"
+ local default_port="${5:-22}"
+
+ kj_ssh_read_host_port "$host_prompt" "$port_prompt" "$default_port"
+
+ while true; do
+ read -e -p "$user_prompt" KJ_SSH_USER
+ KJ_SSH_USER=${KJ_SSH_USER:-$default_user}
+ if kj_ssh_validate_user "$KJ_SSH_USER"; then
+ break
+ fi
+ echo "오류: 사용자 이름 형식이 잘못되었습니다."
+ done
+}
+
+kj_ssh_parse_remote() {
+ local remote_raw="$1"
+ local default_user="${2:-root}"
+ local remote_user remote_host
+
+ if [[ "$remote_raw" == *@* ]]; then
+ remote_user="${remote_raw%@*}"
+ remote_host="${remote_raw#*@}"
+ else
+ remote_user="$default_user"
+ remote_host="$remote_raw"
+ fi
+
+ if ! kj_ssh_validate_user "$remote_user"; then
+ echo "오류: SSH 사용자 이름 형식이 잘못되었습니다."
+ return 1
+ fi
+
+ if ! kj_ssh_validate_host "$remote_host"; then
+ echo "오류: SSH 호스트 주소 형식이 잘못되었습니다."
+ return 1
+ fi
+
+ KJ_SSH_USER="$remote_user"
+ KJ_SSH_HOST="$remote_host"
+ KJ_SSH_REMOTE="$remote_user@$remote_host"
+}
+
+kj_ssh_read_auth() {
+ local key_file="$1"
+ local password_or_key=""
+
+ echo "인증 방법을 선택하세요:"
echo "1. 비밀번호"
- echo "2. 키"
- read -e -p "선택 (1/2)을 입력하십시오 :" auth_choice
+ echo "2. 열쇠"
+ read -e -p "원하는 항목(1/2)을 입력하세요." auth_choice
case $auth_choice in
1)
- read -s -p "비밀번호를 입력하십시오 :" password_or_key
- echo # 换行
+ read -s -p "비밀번호를 입력하세요:" password_or_key
+ echo
+ if [ -z "$password_or_key" ]; then
+ echo "오류: 비밀번호는 비워둘 수 없습니다."
+ return 1
+ fi
+ KJ_SSH_AUTH_METHOD="password"
+ KJ_SSH_AUTH_SECRET="$password_or_key"
;;
2)
- echo "키 내용을 붙여 넣으십시오 (붙여 넣기 후 Enter Enter를 두 번 누릅니다) :"
- local password_or_key=""
+ echo "주요 내용을 붙여넣으세요(붙인 후 Enter를 두 번 누르세요)."
while IFS= read -r line; do
- # 입력이 비어 있고 키 컨텐츠에 이미 시작이 포함되어 있으면 입력이 끝납니다.
if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
break
fi
- # 첫 번째 줄이거나 키 컨텐츠가 입력 된 경우 계속 추가하십시오.
if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- local password_or_key+="${line}"$'\n'
+ password_or_key+="${line}"$'\n'
fi
done
- # 주요 내용인지 확인하십시오
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/$name.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- local password_or_key="$key_file"
+ if [[ "$password_or_key" != *"-----BEGIN"* || "$password_or_key" != *"PRIVATE KEY-----"* ]]; then
+ echo "잘못된 키 콘텐츠입니다!"
+ return 1
fi
+
+ mkdir -p "$(dirname "$key_file")"
+ echo -n "$password_or_key" > "$key_file"
+ chmod 600 "$key_file"
+ KJ_SSH_AUTH_METHOD="key"
+ KJ_SSH_AUTH_SECRET="$key_file"
;;
*)
- echo "잘못된 선택!"
- return
+ echo "잘못된 선택입니다!"
+ return 1
;;
esac
+}
+
+kj_ssh_read_password() {
+ local prompt="${1:-请输入密码: }"
+ while true; do
+ read -e -s -p "$prompt" KJ_SSH_PASSWORD
+ echo
+ [ -n "$KJ_SSH_PASSWORD" ] && break
+ echo "오류: 비밀번호는 비워둘 수 없습니다."
+ done
+}
+
+kj_ssh_read_port() {
+ local port_prompt="$1"
+ local default_port="${2:-22}"
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ return 0
+ fi
+ echo "오류: 포트는 1~65535 사이의 숫자여야 합니다."
+ done
+}
- echo "$name|$ip|$user|$port|$password_or_key" >> "$CONFIG_FILE"
- echo "연결이 저장됩니다!"
+# 연결 목록 표시
+list_connections() {
+ echo "저장된 연결:"
+ echo "------------------------"
+ cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
+ echo "------------------------"
+}
+
+
+# 새 연결 추가
+add_connection() {
+ send_stats "새 연결 추가"
+ echo "새 연결 생성의 예:"
+ echo "- 연결 이름: my_server"
+ echo "- IP 주소: 192.168.1.100"
+ echo "- 사용자 이름: 루트"
+ echo "- 포트: 22"
+ echo "------------------------"
+ read -e -p "연결 이름을 입력하세요:" name
+
+ kj_ssh_read_host_user_port "IP 주소를 입력하세요:" "사용자 이름을 입력하십시오(기본값: 루트):" "포트 번호를 입력하십시오(기본값: 22):" "root" "22"
+ if ! kj_ssh_read_auth "$KEY_DIR/$name.key"; then
+ return
+ fi
+
+ echo "$name|$KJ_SSH_HOST|$KJ_SSH_USER|$KJ_SSH_PORT|$KJ_SSH_AUTH_SECRET" >> "$CONFIG_FILE"
+ echo "연결이 저장되었습니다!"
}
-# 연결을 삭제하십시오
+# 연결 삭제
delete_connection() {
- send_stats "연결을 삭제하십시오"
- read -e -p "삭제하려면 연결 번호를 입력하십시오." num
+ send_stats "연결 삭제"
+ read -e -p "삭제할 연결 번호를 입력하세요:" num
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "오류 : 해당 연결을 찾을 수 없었습니다."
+ echo "오류: 해당 연결을 찾을 수 없습니다."
return
fi
IFS='|' read -r name ip user port password_or_key <<< "$connection"
- # 연결이 키 파일을 사용하는 경우 키 파일을 삭제하십시오.
+ # 연결이 키 파일을 사용하는 경우 키 파일을 삭제하세요.
if [[ "$password_or_key" == "$KEY_DIR"* ]]; then
rm -f "$password_or_key"
fi
@@ -5848,44 +7050,44 @@ delete_connection() {
echo "연결이 삭제되었습니다!"
}
-# 연결을 사용하십시오
+# 연결 사용
use_connection() {
- send_stats "연결을 사용하십시오"
- read -e -p "사용할 연결 번호를 입력하십시오." num
+ send_stats "연결 사용"
+ read -e -p "사용할 연결 번호를 입력하세요:" num
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "오류 : 해당 연결을 찾을 수 없었습니다."
+ echo "오류: 해당 연결을 찾을 수 없습니다."
return
fi
IFS='|' read -r name ip user port password_or_key <<< "$connection"
- echo "연결$name ($ip)..."
+ echo "연결 중$name ($ip)..."
if [[ -f "$password_or_key" ]]; then
- # 키와 연결하십시오
+ # 키를 사용하여 연결
ssh -o StrictHostKeyChecking=no -i "$password_or_key" -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
- echo "연결 실패! 다음을 확인하십시오."
- echo "1. 키 파일 경로가 정확합니까?$password_or_key"
- echo "2. 키 파일 권한이 올바른지 여부 (600이어야 함)."
- echo "3. 대상 서버가 키를 사용하여 로그인 할 수 있는지 여부."
+ echo "연결에 실패했습니다! 다음 사항을 확인하세요."
+ echo "1. 키파일 경로가 맞나요?$password_or_key"
+ echo "2. 키 파일 권한이 올바른가요(600이어야 함)."
+ echo "3. 대상 서버에서 키를 이용한 로그인을 허용하는지 여부."
fi
else
- # 비밀번호로 연결하십시오
+ # 비밀번호를 사용하여 연결
if ! command -v sshpass &> /dev/null; then
- echo "오류 : Sshpass가 설치되지 않았습니다. 먼저 Sshpass를 설치하십시오."
- echo "설치 방법 :"
+ echo "오류: sshpass가 설치되지 않았습니다. 먼저 sshpass를 설치하십시오."
+ echo "설치 방법:"
echo " - Ubuntu/Debian: apt install sshpass"
echo " - CentOS/RHEL: yum install sshpass"
return
fi
sshpass -p "$password_or_key" ssh -o StrictHostKeyChecking=no -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
- echo "연결 실패! 다음을 확인하십시오."
- echo "1. 사용자 이름과 비밀번호가 올바른지 여부."
- echo "2. 대상 서버가 암호 로그인을 허용하는지 여부."
- echo "3. 대상 서버의 SSH 서비스가 정상적으로 실행되는지 여부."
+ echo "연결에 실패했습니다! 다음 사항을 확인하세요."
+ echo "1. 사용자 이름과 비밀번호가 정확합니까?"
+ echo "2. 대상 서버에서 비밀번호 로그인을 허용하는지 여부."
+ echo "3. 대상 서버의 SSH 서비스가 정상적으로 실행되고 있는지 여부."
fi
fi
}
@@ -5897,7 +7099,7 @@ ssh_manager() {
CONFIG_FILE="$HOME/.ssh_connections"
KEY_DIR="$HOME/.ssh/ssh_manager_keys"
- # 구성 파일과 키 디렉토리가 존재하는지 확인하고 존재하지 않으면 작성하십시오.
+ # 구성 파일과 키 디렉터리가 있는지 확인하고 없으면 만듭니다.
if [[ ! -f "$CONFIG_FILE" ]]; then
touch "$CONFIG_FILE"
fi
@@ -5910,20 +7112,20 @@ ssh_manager() {
while true; do
clear
echo "SSH 원격 연결 도구"
- echo "SSH를 통해 다른 Linux 시스템에 연결할 수 있습니다"
+ echo "SSH를 통해 다른 Linux 시스템에 연결할 수 있습니다."
echo "------------------------"
list_connections
- echo "1. 새 연결 만들기 2. 연결 사용 3. 연결 삭제"
+ echo "1. 새 연결 생성 2. 연결 사용 3. 연결 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case $choice in
1) add_connection ;;
2) use_connection ;;
3) delete_connection ;;
0) break ;;
- *) echo "잘못된 선택, 다시 시도하십시오." ;;
+ *) echo "선택이 잘못되었습니다. 다시 시도해 주세요." ;;
esac
done
}
@@ -5939,156 +7141,184 @@ ssh_manager() {
-# 사용 가능한 하드 디스크 파티션을 나열하십시오
+# 사용 가능한 하드 디스크 파티션 나열
list_partitions() {
- echo "사용 가능한 하드 디스크 파티션 :"
+ echo "사용 가능한 하드 드라이브 파티션:"
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT | grep -v "sr\|loop"
}
-# 파티션을 장착하십시오
+
+# 지속적으로 마운트된 파티션
mount_partition() {
- send_stats "파티션을 장착하십시오"
- read -e -p "장착 할 파티션 이름을 입력하십시오 (예 : SDA1) :" PARTITION
+ send_stats "마운트 파티션"
+ read -e -p "마운트할 파티션의 이름을 입력하십시오(예: sda1):" PARTITION
- # 파티션이 있는지 확인하십시오
- if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
+ DEVICE="/dev/$PARTITION"
+ MOUNT_POINT="/mnt/$PARTITION"
+
+ # 파티션이 존재하는지 확인
+ if ! lsblk -no NAME | grep -qw "$PARTITION"; then
echo "파티션이 존재하지 않습니다!"
- return
+ return 1
fi
- # 파티션이 이미 장착되어 있는지 확인하십시오
- if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "파티션은 이미 장착되어 있습니다!"
- return
+ # 마운트되어 있는지 확인해보세요
+ if mount | grep -qw "$DEVICE"; then
+ echo "파티션이 마운트되었습니다!"
+ return 1
fi
- # 마운트 포인트를 만듭니다
- MOUNT_POINT="/mnt/$PARTITION"
- mkdir -p "$MOUNT_POINT"
+ # UUID 가져오기
+ UUID=$(blkid -s UUID -o value "$DEVICE")
+ if [ -z "$UUID" ]; then
+ echo "UUID를 얻을 수 없습니다!"
+ return 1
+ fi
- # 파티션을 장착하십시오
- mount "/dev/$PARTITION" "$MOUNT_POINT"
+ # 파일 시스템 유형 가져오기
+ FSTYPE=$(blkid -s TYPE -o value "$DEVICE")
+ if [ -z "$FSTYPE" ]; then
+ echo "파일 시스템 유형을 가져올 수 없습니다!"
+ return 1
+ fi
- if [ $? -eq 0 ]; then
- echo "파티션 마운트 성공적으로 :$MOUNT_POINT"
- else
- echo "파티션 마운트 실패!"
+ # 마운트 지점 생성
+ mkdir -p "$MOUNT_POINT"
+
+ # 산
+ if ! mount "$DEVICE" "$MOUNT_POINT"; then
+ echo "파티션 마운트에 실패했습니다!"
rmdir "$MOUNT_POINT"
+ return 1
+ fi
+
+ echo "파티션이 성공적으로 마운트되었습니다.$MOUNT_POINT"
+
+ # /etc/fstab을 확인하여 UUID 또는 마운트 지점이 이미 존재하는지 확인하세요.
+ if grep -qE "UUID=$UUID|[[:space:]]$MOUNT_POINT[[:space:]]" /etc/fstab; then
+ echo "파티션 기록이 /etc/fstab에 이미 존재합니다. 쓰기를 건너뛰세요."
+ return 0
fi
+
+ # /etc/fstab에 쓰기
+ echo "UUID=$UUID $MOUNT_POINT $FSTYPE defaults,nofail 0 2" >> /etc/fstab
+
+ echo "지속적인 마운트를 위해 /etc/fstab에 기록"
}
-# 파티션을 제거하십시오
+
+# 파티션 마운트 해제
unmount_partition() {
- send_stats "파티션을 제거하십시오"
- read -e -p "파티션 이름 (예 : SDA1)을 입력하십시오." PARTITION
+ send_stats "파티션 마운트 해제"
+ read -e -p "마운트 해제할 파티션의 이름을 입력하십시오(예: sda1):" PARTITION
- # 파티션이 이미 장착되어 있는지 확인하십시오
+ # 파티션이 마운트되었는지 확인
MOUNT_POINT=$(lsblk -o MOUNTPOINT | grep -w "$PARTITION")
if [ -z "$MOUNT_POINT" ]; then
- echo "파티션이 장착되지 않았습니다!"
+ echo "파티션이 마운트되지 않았습니다!"
return
fi
- # 파티션을 제거하십시오
+ # 파티션 마운트 해제
umount "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "파티션 분할 해제 성공 :$MOUNT_POINT"
+ echo "파티션이 성공적으로 제거되었습니다:$MOUNT_POINT"
rmdir "$MOUNT_POINT"
else
- echo "파티션 제거 실패!"
+ echo "파티션 제거에 실패했습니다!"
fi
}
-# 목록 장착 파티션
+# 마운트된 파티션 나열
list_mounted_partitions() {
- echo "마운트 파티션 :"
+ echo "마운트된 파티션:"
df -h | grep -v "tmpfs\|udev\|overlay"
}
-# 형식 파티션
+# 파티션 포맷
format_partition() {
- send_stats "형식 파티션"
- read -e -p "파티션 이름을 형식 (예 : SDA1)에 입력하십시오." PARTITION
+ send_stats "파티션 포맷"
+ read -e -p "포맷할 파티션의 이름을 입력하십시오(예: sda1):" PARTITION
- # 파티션이 있는지 확인하십시오
+ # 파티션이 존재하는지 확인
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
echo "파티션이 존재하지 않습니다!"
return
fi
- # 파티션이 이미 장착되어 있는지 확인하십시오
+ # 파티션이 마운트되었는지 확인
if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "파티션이 장착되었습니다. 먼저 제거하십시오!"
+ echo "파티션이 마운트되었습니다. 먼저 마운트를 해제하세요!"
return
fi
- # 파일 시스템 유형을 선택하십시오
- echo "파일 시스템 유형을 선택하십시오 :"
+ # 파일 시스템 유형 선택
+ echo "파일 시스템 유형을 선택하십시오:"
echo "1. ext4"
echo "2. xfs"
echo "3. ntfs"
echo "4. vfat"
- read -e -p "선택을 입력하십시오 :" FS_CHOICE
+ read -e -p "선택사항을 입력하세요:" FS_CHOICE
case $FS_CHOICE in
1) FS_TYPE="ext4" ;;
2) FS_TYPE="xfs" ;;
3) FS_TYPE="ntfs" ;;
4) FS_TYPE="vfat" ;;
- *) echo "잘못된 선택!"; return ;;
+ *) echo "잘못된 선택입니다!"; return ;;
esac
- # 형식을 확인하십시오
- read -e -p "파티션 형식 확인 /dev /$PARTITION~을 위한$FS_TYPE그게? (Y/N) :" CONFIRM
+ # 포맷 확인
+ read -e -p "포맷된 파티션 /dev/ 확인$PARTITION~을 위한$FS_TYPE? (예/아니요):" CONFIRM
if [ "$CONFIRM" != "y" ]; then
echo "작업이 취소되었습니다."
return
fi
- # 형식 파티션
- echo "파티션 서식 /dev /$PARTITION~을 위한$FS_TYPE ..."
+ # 파티션 포맷
+ echo "파티션 /dev/ 포맷 중$PARTITION~을 위한$FS_TYPE ..."
mkfs.$FS_TYPE "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "파티션 형식이 성공적이었습니다!"
+ echo "파티션이 성공적으로 포맷되었습니다!"
else
- echo "파티션 형식이 실패했습니다!"
+ echo "파티션 포맷에 실패했습니다!"
fi
}
-# 파티션 상태를 확인하십시오
+# 파티션 상태 확인
check_partition() {
- send_stats "파티션 상태를 확인하십시오"
- read -e -p "파티션 이름을 입력하여 확인하십시오 (예 : SDA1) :" PARTITION
+ send_stats "파티션 상태 확인"
+ read -e -p "확인할 파티션 이름을 입력하세요(예: sda1):" PARTITION
- # 파티션이 있는지 확인하십시오
+ # 파티션이 존재하는지 확인
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
echo "파티션이 존재하지 않습니다!"
return
fi
- # 파티션 상태를 확인하십시오
- echo "파티션 /개발자 /$PARTITION상태:"
+ # 파티션 상태 확인
+ echo "파티션 확인 /dev/$PARTITION상태:"
fsck "/dev/$PARTITION"
}
# 메인 메뉴
disk_manager() {
- send_stats "하드 디스크 관리 기능"
+ send_stats "하드디스크 관리 기능"
while true; do
clear
echo "하드 디스크 파티션 관리"
- echo -e "${gl_huang}이 기능은 테스트 기간 동안 내부적으로 테스트되므로 생산 환경에서 사용하지 마십시오.${gl_bai}"
+ echo -e "${gl_huang}이 기능은 내부 테스트 중이므로 프로덕션 환경에서는 사용하면 안 됩니다.${gl_bai}"
echo "------------------------"
list_partitions
echo "------------------------"
- echo "1. 파티션 마운트 2. 파티션 3. 마운트 파티션보기"
- echo "4. 파티션 형식 5. 파티션 상태를 확인하십시오"
+ echo "1. 파티션 마운트 2. 파티션 마운트 해제 3. 마운트된 파티션 보기"
+ echo "4. 파티션 포맷 5. 파티션 상태 확인"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case $choice in
1) mount_partition ;;
2) unmount_partition ;;
@@ -6097,7 +7327,7 @@ disk_manager() {
5) check_partition ;;
*) break ;;
esac
- read -e -p "계속하려면 Enter를 누르십시오 ..."
+ read -e -p "계속하려면 Enter를 누르세요..."
done
}
@@ -6106,103 +7336,75 @@ disk_manager() {
# 작업 목록 표시
list_tasks() {
- echo "저장된 동기화 작업 :"
+ echo "저장된 동기화 작업:"
echo "---------------------------------"
awk -F'|' '{print NR " - " $1 " ( " $2 " -> " $3":"$4 " )"}' "$CONFIG_FILE"
echo "---------------------------------"
}
-# 새로운 작업을 추가하십시오
+# 새 작업 추가
add_task() {
- send_stats "새 동기화 작업을 추가하십시오"
- echo "새 동기화 작업 작성 예 :"
- echo "- 작업 이름 : Backup_www"
- echo "- 로컬 디렉토리 : /var /www"
- echo "- 원격 주소 : user@192.168.1.100"
- echo "- 원격 디렉토리 : /백업 /www"
- echo "- 포트 번호 (기본 22)"
+ send_stats "새 동기화 작업 추가"
+ echo "새 동기화 작업 생성의 예:"
+ echo "- 작업 이름: backup_www"
+ echo "- 로컬 디렉터리: /var/www"
+ echo "- 원격 주소: user@192.168.1.100"
+ echo "- 원격 디렉터리: /backup/www"
+ echo "- 포트 번호(기본값 22)"
echo "---------------------------------"
- read -e -p "작업 이름을 입력하십시오 :" name
- read -e -p "로컬 디렉토리를 입력하십시오 :" local_path
- read -e -p "원격 디렉토리를 입력하십시오 :" remote_path
- read -e -p "원격 사용자 @IP를 입력하십시오 :" remote
- read -e -p "SSH 포트 (기본값 22)를 입력하십시오 :" port
- port=${port:-22}
-
- echo "인증 방법을 선택하십시오 :"
- echo "1. 비밀번호"
- echo "2. 키"
- read -e -p "선택하십시오 (1/2) :" auth_choice
+ read -e -p "작업 이름을 입력하세요:" name
+ read -e -p "로컬 디렉토리를 입력하십시오:" local_path
+ read -e -p "원격 디렉토리를 입력하십시오:" remote_path
- case $auth_choice in
- 1)
- read -s -p "비밀번호를 입력하십시오 :" password_or_key
- echo # 换行
- auth_method="password"
- ;;
- 2)
- echo "키 내용을 붙여 넣으십시오 (붙여 넣기 후 Enter Enter를 두 번 누릅니다) :"
- local password_or_key=""
- while IFS= read -r line; do
- # 입력이 비어 있고 키 컨텐츠에 이미 시작이 포함되어 있으면 입력이 끝납니다.
- if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
- break
- fi
- # 첫 번째 줄이거나 키 컨텐츠가 입력 된 경우 계속 추가하십시오.
- if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- password_or_key+="${line}"$'\n'
- fi
- done
+ while true; do
+ read -e -p "원격 user@IP를 입력하십시오:" remote
+ if kj_ssh_parse_remote "$remote" "root"; then
+ remote="$KJ_SSH_REMOTE"
+ break
+ fi
+ done
- # 주요 내용인지 확인하십시오
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/${name}_sync.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- password_or_key="$key_file"
- auth_method="key"
- else
- echo "잘못된 키 컨텐츠!"
- return
- fi
- ;;
- *)
- echo "잘못된 선택!"
- return
- ;;
- esac
+ kj_ssh_read_port "SSH 포트(기본값 22)를 입력하세요." "22"
+ port="$KJ_SSH_PORT"
+
+ if ! kj_ssh_read_auth "$KEY_DIR/${name}_sync.key"; then
+ return
+ fi
+ auth_method="$KJ_SSH_AUTH_METHOD"
+ password_or_key="$KJ_SSH_AUTH_SECRET"
- echo "동기화 모드를 선택하십시오 :"
- echo "1. 표준 모드 (-avz)"
- echo "2. 대상 파일 삭제 (-avz-delete)"
- read -e -p "선택하십시오 (1/2) :" mode
+ echo "동기화 모드를 선택하십시오:"
+ echo "1. 표준 모드(-avz)"
+ echo "2. 대상 파일 삭제(-avz --delete)"
+ read -e -p "(1/2)을 선택하세요:" mode
case $mode in
1) options="-avz" ;;
2) options="-avz --delete" ;;
- *) echo "유효하지 않은 선택, 기본값 -AVZ를 사용하십시오"; options="-avz" ;;
+ *) echo "선택이 잘못되었습니다. 기본값 -avz를 사용하세요."; options="-avz" ;;
esac
echo "$name|$local_path|$remote|$remote_path|$port|$options|$auth_method|$password_or_key" >> "$CONFIG_FILE"
install rsync rsync
- echo "작업을 저장했습니다!"
+ echo "임무가 저장되었습니다!"
}
-# 작업을 삭제하십시오
+
+# 할 일 삭제
delete_task() {
- send_stats "동기화 작업을 삭제합니다"
- read -e -p "삭제하려면 작업 번호를 입력하십시오." num
+ send_stats "동기화 작업 삭제"
+ read -e -p "삭제할 작업 번호를 입력하세요:" num
local task=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$task" ]]; then
- echo "오류 : 해당 작업을 찾을 수 없었습니다."
+ echo "오류: 해당 작업을 찾을 수 없습니다."
return
fi
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # 작업이 키 파일을 사용하는 경우 키 파일을 삭제하십시오.
+ # 작업이 키 파일을 사용하는 경우 키 파일을 삭제하세요.
if [[ "$auth_method" == "key" && "$password_or_key" == "$KEY_DIR"* ]]; then
rm -f "$password_or_key"
fi
@@ -6213,12 +7415,12 @@ delete_task() {
run_task() {
- send_stats "동기화 작업을 수행하십시오"
+ send_stats "동기화 작업 수행"
CONFIG_FILE="$HOME/.rsync_tasks"
CRON_FILE="$HOME/.rsync_cron"
- # 매개 변수를 분석하십시오
+ # 매개변수 구문 분석
local direction="push" # 默认是推送到远端
local num
@@ -6229,51 +7431,51 @@ run_task() {
num="$1"
fi
- # 들어오는 작업 번호가없는 경우 사용자에게 입력하라는 메시지를 표시하십시오.
+ # 작업 번호가 전달되지 않으면 사용자에게 입력하라는 메시지가 표시됩니다.
if [[ -z "$num" ]]; then
- read -e -p "실행할 작업 번호를 입력하십시오." num
+ read -e -p "실행할 작업 번호를 입력하세요:" num
fi
local task=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$task" ]]; then
- echo "오류 : 작업이 찾을 수 없었습니다!"
+ echo "오류: 작업을 찾을 수 없습니다!"
return
fi
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # 동기화 방향에 따라 소스 및 대상 경로를 조정하십시오
+ # 동기화 방향에 따라 소스 및 대상 경로 조정
if [[ "$direction" == "pull" ]]; then
- echo "로컬로 동기화를 당기기 :$remote:$local_path -> $remote_path"
+ echo "로컬로 가져오기 및 동기화:$remote:$local_path -> $remote_path"
source="$remote:$local_path"
destination="$remote_path"
else
- echo "동기화를 원격 끝으로 푸시합니다.$local_path -> $remote:$remote_path"
+ echo "원격 끝에 푸시 및 동기화:$local_path -> $remote:$remote_path"
source="$local_path"
destination="$remote:$remote_path"
fi
- # SSH 연결 공통 매개 변수를 추가하십시오
+ # SSH 연결 공통 매개변수 추가
local ssh_options="-p $port -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
if [[ "$auth_method" == "password" ]]; then
if ! command -v sshpass &> /dev/null; then
- echo "오류 : Sshpass가 설치되지 않았습니다. 먼저 Sshpass를 설치하십시오."
- echo "설치 방법 :"
+ echo "오류: sshpass가 설치되지 않았습니다. 먼저 sshpass를 설치하십시오."
+ echo "설치 방법:"
echo " - Ubuntu/Debian: apt install sshpass"
echo " - CentOS/RHEL: yum install sshpass"
return
fi
sshpass -p "$password_or_key" rsync $options -e "ssh $ssh_options" "$source" "$destination"
else
- # 키 파일이 존재하는지 여부와 권한이 올바른지 확인
+ # 키 파일이 존재하는지, 권한이 올바른지 확인하세요.
if [[ ! -f "$password_or_key" ]]; then
- echo "오류 : 키 파일이 존재하지 않습니다.$password_or_key"
+ echo "오류: 키 파일이 존재하지 않습니다:$password_or_key"
return
fi
if [[ "$(stat -c %a "$password_or_key")" != "600" ]]; then
- echo "경고 : 키 파일 권한이 잘못되었고 수리 중입니다 ..."
+ echo "경고: 잘못된 키 파일 권한, 수정 중..."
chmod 600 "$password_or_key"
fi
@@ -6283,30 +7485,30 @@ run_task() {
if [[ $? -eq 0 ]]; then
echo "동기화가 완료되었습니다!"
else
- echo "동기화 실패! 다음을 확인하십시오."
- echo "1. 네트워크 연결이 정상입니까?"
- echo "2. 원격 호스트가 액세스 할 수 있습니까?"
- echo "3. 인증 정보가 정확합니까?"
- echo "4. 로컬 및 원격 디렉토리에 올바른 액세스 권한이 있습니까?"
+ echo "동기화에 실패했습니다! 다음 사항을 확인하세요."
+ echo "1. 네트워크 연결이 정상인가요?"
+ echo "2. 원격 호스트에 접근 가능한지 여부"
+ echo "3. 인증정보가 정확합니까?"
+ echo "4. 로컬 및 원격 디렉터리에 올바른 액세스 권한이 있습니까?"
fi
}
-# 시간이 정한 작업을 만듭니다
+# 예약된 작업 만들기
schedule_task() {
- send_stats "동기화 타이밍 작업을 추가하십시오"
+ send_stats "동기화 예약 작업 추가"
- read -e -p "정기적으로 동기화 할 작업 번호를 입력하십시오." num
+ read -e -p "정기적으로 동기화할 작업 번호를 입력하세요:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
- echo "오류 : 유효한 작업 번호를 입력하십시오!"
+ echo "오류: 유효한 작업 번호를 입력하십시오!"
return
fi
- echo "시간이 정한 실행 간격을 선택하십시오."
- echo "1) 한 시간에 한 번 실행하십시오"
- echo "2) 하루에 한 번 수행하십시오"
- echo "3) 일주일에 한 번 실행하십시오"
- read -e -p "옵션을 입력하십시오 (1/2/3) :" interval
+ echo "예약된 실행 간격을 선택하십시오."
+ echo "1) 매 시간마다 한 번씩 실행"
+ echo "2) 하루에 한 번 실행"
+ echo "3) 일주일에 한 번 실행"
+ read -e -p "옵션을 입력하세요(1/2/3):" interval
local random_minute=$(shuf -i 0-59 -n 1) # 生成 0-59 之间的随机分钟数
local cron_time=""
@@ -6314,42 +7516,42 @@ schedule_task() {
1) cron_time="$random_minute * * * *" ;; # 每小时,随机分钟执行
2) cron_time="$random_minute 0 * * *" ;; # 每天,随机分钟执行
3) cron_time="$random_minute 0 * * 1" ;; # 每周,随机分钟执行
- *) echo "오류 : 유효한 옵션을 입력하십시오!" ; return ;;
+ *) echo "오류: 유효한 옵션을 입력하십시오!" ; return ;;
esac
local cron_job="$cron_time k rsync_run $num"
local cron_job="$cron_time k rsync_run $num"
- # 동일한 작업이 이미 존재하는지 확인하십시오
+ # 동일한 작업이 이미 존재하는지 확인하세요.
if crontab -l | grep -q "k rsync_run $num"; then
- echo "오류 :이 작업의 타이밍 동기화가 이미 존재합니다!"
+ echo "오류: 이 작업에 대해 예약된 동기화가 이미 존재합니다!"
return
fi
- # 사용자에게 크론AB를 만듭니다
+ # 사용자의 crontab에 생성
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "타이밍 작업이 만들어졌습니다.$cron_job"
+ echo "예약된 작업이 생성되었습니다:$cron_job"
}
-# 예정된 작업을 봅니다
+# 예약된 작업 보기
view_tasks() {
- echo "현재 타이밍 작업 :"
+ echo "현재 예약된 작업:"
echo "---------------------------------"
crontab -l | grep "k rsync_run"
echo "---------------------------------"
}
-# 타이밍 작업을 삭제하십시오
+# 예약된 작업 삭제
delete_task_schedule() {
- send_stats "동기화 타이밍 작업을 삭제합니다"
- read -e -p "삭제하려면 작업 번호를 입력하십시오." num
+ send_stats "동기화 예약 작업 삭제"
+ read -e -p "삭제할 작업 번호를 입력하세요:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
- echo "오류 : 유효한 작업 번호를 입력하십시오!"
+ echo "오류: 유효한 작업 번호를 입력하십시오!"
return
fi
crontab -l | grep -v "k rsync_run $num" | crontab -
- echo "삭제 된 작업 번호$num타이밍 작업"
+ echo "태스크 번호가 삭제되었습니다.$num예약된 작업"
}
@@ -6360,20 +7562,20 @@ rsync_manager() {
while true; do
clear
- echo "RSYNC 원격 동기화 도구"
- echo "원격 디렉토리 간의 동기화는 증분 동기화, 효율적이고 안정적인 지원을 지원합니다."
+ echo "Rsync 원격 동기화 도구"
+ echo "원격 디렉터리 간의 동기화는 효율적이고 안정적인 증분 동기화를 지원합니다."
echo "---------------------------------"
list_tasks
echo
view_tasks
echo
- echo "1. 새 작업 생성 2. 작업을 삭제하십시오"
- echo "3. 원격 끝에 로컬 동기화 수행 4. 로컬 엔드에 대한 원격 동기화 수행"
- echo "5. 타이밍 작업 만들기 6. 타이밍 작업 삭제"
+ echo "1. 새 작업 생성 2. 작업 삭제"
+ echo "3. 원격 사이트에 대한 로컬 동기화 수행 4. 로컬 사이트에 대한 원격 동기화 수행"
+ echo "5. 예약된 작업 생성 6. 예약된 작업 삭제"
echo "---------------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "---------------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case $choice in
1) add_task ;;
2) delete_task ;;
@@ -6382,9 +7584,9 @@ rsync_manager() {
5) schedule_task ;;
6) delete_task_schedule ;;
0) break ;;
- *) echo "잘못된 선택, 다시 시도하십시오." ;;
+ *) echo "선택이 잘못되었습니다. 다시 시도해 주세요." ;;
esac
- read -e -p "계속하려면 Enter를 누르십시오 ..."
+ read -e -p "계속하려면 Enter를 누르세요..."
done
}
@@ -6396,9 +7598,12 @@ rsync_manager() {
-linux_ps() {
+linux_info() {
+
+
clear
+ echo -e "${gl_kjlan}시스템 정보를 쿼리하는 중...${gl_bai}"
send_stats "시스템 정보 쿼리"
ip_address
@@ -6443,45 +7648,48 @@ linux_ps() {
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1 % 3600) / 60); if (run_days > 0) printf("%d天 ", run_days); if (run_hours > 0) printf("%d时 ", run_hours); printf("%d分\n", run_minutes)}')
+ local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1% 3600) / 60); if (run_days > 0) printf("%d day ", run_days); if (run_hours > 0) printf("%d시간 ", run_hours); printf("%d분\n", run_mins)}')
local timezone=$(current_timezone)
+ local tcp_count=$(ss -t | wc -l)
+ local udp_count=$(ss -u | wc -l)
- echo ""
+ clear
echo -e "시스템 정보 쿼리"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}호스트 이름 :${gl_bai}$hostname"
- echo -e "${gl_kjlan}시스템 버전 :${gl_bai}$os_info"
- echo -e "${gl_kjlan}리눅스 버전 :${gl_bai}$kernel_version"
+ echo -e "${gl_kjlan}호스트 이름:${gl_bai}$hostname"
+ echo -e "${gl_kjlan}시스템 버전:${gl_bai}$os_info"
+ echo -e "${gl_kjlan}리눅스 버전:${gl_bai}$kernel_version"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU 아키텍처 :${gl_bai}$cpu_arch"
- echo -e "${gl_kjlan}CPU 모델 :${gl_bai}$cpu_info"
- echo -e "${gl_kjlan}CPU 코어 수 :${gl_bai}$cpu_cores"
- echo -e "${gl_kjlan}CPU 주파수 :${gl_bai}$cpu_freq"
+ echo -e "${gl_kjlan}CPU 아키텍처:${gl_bai}$cpu_arch"
+ echo -e "${gl_kjlan}CPU 모델:${gl_bai}$cpu_info"
+ echo -e "${gl_kjlan}CPU 코어 수:${gl_bai}$cpu_cores"
+ echo -e "${gl_kjlan}CPU 주파수:${gl_bai}$cpu_freq"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}CPU 점유 :${gl_bai}$cpu_usage_percent%"
- echo -e "${gl_kjlan}시스템 부하 :${gl_bai}$load"
- echo -e "${gl_kjlan}물리적 기억 :${gl_bai}$mem_info"
- echo -e "${gl_kjlan}가상 메모리 :${gl_bai}$swap_info"
- echo -e "${gl_kjlan}하드 디스크 직업 :${gl_bai}$disk_info"
+ echo -e "${gl_kjlan}CPU 사용량:${gl_bai}$cpu_usage_percent%"
+ echo -e "${gl_kjlan}시스템 부하:${gl_bai}$load"
+ echo -e "${gl_kjlan}TCP|UDP 연결 수:${gl_bai}$tcp_count|$udp_count"
+ echo -e "${gl_kjlan}물리적 메모리:${gl_bai}$mem_info"
+ echo -e "${gl_kjlan}가상 메모리:${gl_bai}$swap_info"
+ echo -e "${gl_kjlan}하드 드라이브 사용량:${gl_bai}$disk_info"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}총 수신 :${gl_bai}$rx"
- echo -e "${gl_kjlan}총 보내기 :${gl_bai}$tx"
+ echo -e "${gl_kjlan}받은 총액:${gl_bai}$rx"
+ echo -e "${gl_kjlan}보낸 총액:${gl_bai}$tx"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}네트워크 알고리즘 :${gl_bai}$congestion_algorithm $queue_algorithm"
+ echo -e "${gl_kjlan}네트워크 알고리즘:${gl_bai}$congestion_algorithm $queue_algorithm"
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}연산자:${gl_bai}$isp_info"
if [ -n "$ipv4_address" ]; then
- echo -e "${gl_kjlan}IPv4 주소 :${gl_bai}$ipv4_address"
+ echo -e "${gl_kjlan}IPv4 주소:${gl_bai}$ipv4_address"
fi
if [ -n "$ipv6_address" ]; then
- echo -e "${gl_kjlan}IPv6 주소 :${gl_bai}$ipv6_address"
+ echo -e "${gl_kjlan}IPv6 주소:${gl_bai}$ipv6_address"
fi
- echo -e "${gl_kjlan}DNS 주소 :${gl_bai}$dns_addresses"
- echo -e "${gl_kjlan}지리적 위치 :${gl_bai}$country $city"
- echo -e "${gl_kjlan}시스템 시간 :${gl_bai}$timezone $current_time"
+ echo -e "${gl_kjlan}DNS 주소:${gl_bai}$dns_addresses"
+ echo -e "${gl_kjlan}위치:${gl_bai}$country $city"
+ echo -e "${gl_kjlan}시스템 시간:${gl_bai}$timezone $current_time"
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}실행 시간:${gl_bai}$runtime"
echo
@@ -6498,109 +7706,161 @@ linux_tools() {
clear
# send_stats "기본 도구"
echo -e "기본 도구"
+
+ tools=(
+ curl wget sudo socat htop iftop unzip tar tmux ffmpeg
+ btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders
+ vim nano git
+ )
+
+ if command -v apt >/dev/null 2>&1; then
+ PM="apt"
+ elif command -v dnf >/dev/null 2>&1; then
+ PM="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PM="yum"
+ elif command -v pacman >/dev/null 2>&1; then
+ PM="pacman"
+ elif command -v apk >/dev/null 2>&1; then
+ PM="apk"
+ elif command -v zypper >/dev/null 2>&1; then
+ PM="zypper"
+ elif command -v opkg >/dev/null 2>&1; then
+ PM="opkg"
+ elif command -v pkg >/dev/null 2>&1; then
+ PM="pkg"
+ else
+ echo "❌ 인식할 수 없는 패키지 관리자"
+ exit 1
+ fi
+
+ echo "📦 패키지 관리자를 사용하세요:$PM"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+
+ for ((i=0; i<${#tools[@]}; i+=2)); do
+ # 왼쪽 열
+ if command -v "${tools[i]}" >/dev/null 2>&1; then
+ left=$(printf "✅ %-12s 설치됨" "${tools[i]}")
+ else
+ left=$(printf "❌ %-12s가 설치되지 않았습니다." "${tools[i]}")
+ fi
+
+ # 오른쪽 열(배열이 범위를 벗어나는 것을 방지하기 위해)
+ if [[ -n "${tools[i+1]}" ]]; then
+ if command -v "${tools[i+1]}" >/dev/null 2>&1; then
+ right=$(printf "✅ %-12s 설치됨" "${tools[i+1]}")
+ else
+ right=$(printf "❌ %-12s가 설치되지 않았습니다." "${tools[i+1]}")
+ fi
+ printf "%-42s %s\n" "$left" "$right"
+ else
+ printf "%s\n" "$left"
+ fi
+ done
+
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}컬 다운로드 도구${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}WGET 다운로드 도구${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}3. ${gl_bai}Sudo Super Management 권한 도구${gl_kjlan}4. ${gl_bai}소사이어티 커뮤니케이션 연결 도구"
- echo -e "${gl_kjlan}5. ${gl_bai}HTOP 시스템 모니터링 도구${gl_kjlan}6. ${gl_bai}IFTOP 네트워크 트래픽 모니터링 도구"
- echo -e "${gl_kjlan}7. ${gl_bai}압축 지퍼 압축 압축 압축 도구${gl_kjlan}8. ${gl_bai}TAR GZ 압축 감압 도구"
- echo -e "${gl_kjlan}9. ${gl_bai}Tmux 다중 채널 배경 달리기 도구${gl_kjlan}10. ${gl_bai}FFMPEG 비디오 라이브 스트리밍 도구 인코딩"
+ echo -e "${gl_kjlan}1. ${gl_bai}컬 다운로드 도구${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}wget 다운로드 도구${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}3. ${gl_bai}sudo 최고 관리 권한 도구${gl_kjlan}4. ${gl_bai}socat 통신 연결 도구"
+ echo -e "${gl_kjlan}5. ${gl_bai}htop 시스템 모니터링 도구${gl_kjlan}6. ${gl_bai}iftop 네트워크 트래픽 모니터링 도구"
+ echo -e "${gl_kjlan}7. ${gl_bai}unzip ZIP 압축 및 압축 풀기 도구${gl_kjlan}8. ${gl_bai}tar GZ 압축 및 압축 해제 도구"
+ echo -e "${gl_kjlan}9. ${gl_bai}tmux 다중 채널 백그라운드 실행 도구${gl_kjlan}10. ${gl_bai}ffmpeg 비디오 인코딩 라이브 스트리밍 도구"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}BTOP 최신 모니터링 도구${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}범위 파일 관리 도구"
- echo -e "${gl_kjlan}13. ${gl_bai}NCDU 디스크 직업 관찰 도구${gl_kjlan}14. ${gl_bai}FZF 글로벌 검색 도구"
+ echo -e "${gl_kjlan}11. ${gl_bai}btop 최신 모니터링 도구${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}레인저 파일 관리 도구"
+ echo -e "${gl_kjlan}13. ${gl_bai}ncdu 디스크 사용량 보기 도구${gl_kjlan}14. ${gl_bai}fzf 글로벌 검색 도구"
echo -e "${gl_kjlan}15. ${gl_bai}vim 텍스트 편집기${gl_kjlan}16. ${gl_bai}나노 텍스트 편집기${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}17. ${gl_bai}GIT 버전 제어 시스템"
+ echo -e "${gl_kjlan}17. ${gl_bai}Git 버전 관리 시스템${gl_kjlan}18. ${gl_bai}오픈코드 AI 프로그래밍 도우미${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}매트릭스 화면 보증${gl_kjlan}22. ${gl_bai}열차 스크린 보안"
- echo -e "${gl_kjlan}26. ${gl_bai}테트리스 게임${gl_kjlan}27. ${gl_bai}뱀 먹는 게임"
- echo -e "${gl_kjlan}28. ${gl_bai}우주 침략자 게임"
+ echo -e "${gl_kjlan}21. ${gl_bai}매트릭스 스크린세이버${gl_kjlan}22. ${gl_bai}달리는 기차 화면 보호기"
+ echo -e "${gl_kjlan}26. ${gl_bai}테트리스 미니 게임${gl_kjlan}27. ${gl_bai}뱀 미니게임"
+ echo -e "${gl_kjlan}28. ${gl_bai}우주 침략자 미니 게임"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}모두 설치하십시오${gl_kjlan}32. ${gl_bai}모든 설치 (스크린 세이버 및 게임 제외)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}모든 것을 제거하십시오"
+ echo -e "${gl_kjlan}31. ${gl_bai}모두 설치${gl_kjlan}32. ${gl_bai}모두 설치(화면 보호기 및 게임 제외)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}모두 제거"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}지정된 도구를 설치하십시오${gl_kjlan}42. ${gl_bai}지정된 도구를 제거하십시오"
+ echo -e "${gl_kjlan}41. ${gl_bai}지정된 도구 설치${gl_kjlan}42. ${gl_bai}지정된 도구 제거"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
clear
install curl
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
curl --help
- send_stats "컬을 설치하십시오"
+ send_stats "컬 설치"
;;
2)
clear
install wget
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
wget --help
- send_stats "wget을 설치하십시오"
+ send_stats "wget 설치"
;;
3)
clear
install sudo
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
sudo --help
- send_stats "Sudo를 설치하십시오"
+ send_stats "sudo 설치"
;;
4)
clear
install socat
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
socat -h
- send_stats "Socat을 설치하십시오"
+ send_stats "socat 설치"
;;
5)
clear
install htop
clear
htop
- send_stats "HTOP를 설치하십시오"
+ send_stats "htop 설치"
;;
6)
clear
install iftop
clear
iftop
- send_stats "iftop을 설치하십시오"
+ send_stats "iftop 설치"
;;
7)
clear
install unzip
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
unzip
- send_stats "압축을 설치하십시오"
+ send_stats "설치압축 해제"
;;
8)
clear
install tar
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
tar --help
- send_stats "타르를 설치하십시오"
+ send_stats "타르 설치"
;;
9)
clear
install tmux
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
tmux --help
- send_stats "tmux를 설치하십시오"
+ send_stats "tmux 설치"
;;
10)
clear
install ffmpeg
clear
- echo "도구가 설치되었으며 사용법은 다음과 같습니다."
+ echo "도구가 설치되었으며 다음과 같이 사용됩니다."
ffmpeg --help
- send_stats "FFMPEG를 설치하십시오"
+ send_stats "ffmpeg 설치"
;;
11)
@@ -6608,7 +7868,7 @@ linux_tools() {
install btop
clear
btop
- send_stats "Btop을 설치하십시오"
+ send_stats "btop 설치"
;;
12)
clear
@@ -6617,7 +7877,7 @@ linux_tools() {
clear
ranger
cd ~
- send_stats "레인저를 설치하십시오"
+ send_stats "레인저 설치"
;;
13)
clear
@@ -6626,7 +7886,7 @@ linux_tools() {
clear
ncdu
cd ~
- send_stats "NCDU를 설치하십시오"
+ send_stats "ncdu 설치"
;;
14)
clear
@@ -6635,7 +7895,7 @@ linux_tools() {
clear
fzf
cd ~
- send_stats "FZF를 설치하십시오"
+ send_stats "fzf 설치"
;;
15)
clear
@@ -6644,7 +7904,7 @@ linux_tools() {
clear
vim -h
cd ~
- send_stats "VIM을 설치하십시오"
+ send_stats "vim 설치"
;;
16)
clear
@@ -6653,7 +7913,7 @@ linux_tools() {
clear
nano -h
cd ~
- send_stats "나노를 설치하십시오"
+ send_stats "나노 설치"
;;
@@ -6664,75 +7924,89 @@ linux_tools() {
clear
git --help
cd ~
- send_stats "git을 설치하십시오"
+ send_stats "자식 설치"
+ ;;
+
+ 18)
+ clear
+ cd ~
+ curl -fsSL https://opencode.ai/install | bash
+ source ~/.bashrc
+ source ~/.profile
+ opencode
+ send_stats "오픈코드 설치"
;;
+
21)
clear
install cmatrix
clear
cmatrix
- send_stats "cmatrix를 설치하십시오"
+ send_stats "cmatrix 설치"
;;
22)
clear
install sl
clear
sl
- send_stats "SL을 설치하십시오"
+ send_stats "sl 설치"
;;
26)
clear
install bastet
clear
bastet
- send_stats "Bastet을 설치하십시오"
+ send_stats "바스테트 설치"
;;
27)
clear
install nsnake
clear
nsnake
- send_stats "NSNAKE를 설치하십시오"
+ send_stats "nsnake 설치"
;;
+
28)
clear
install ninvaders
clear
ninvaders
- send_stats "Ninvaders를 설치하십시오"
+ send_stats "닌베이더 설치"
;;
31)
clear
- send_stats "모두 설치하십시오"
+ send_stats "모두 설치"
install curl wget sudo socat htop iftop unzip tar tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
;;
32)
clear
- send_stats "모든 설치 (게임 및 화면 보호기 제외)"
+ send_stats "모두 설치(게임 및 화면 보호기 제외)"
install curl wget sudo socat htop iftop unzip tar tmux ffmpeg btop ranger ncdu fzf vim nano git
;;
33)
clear
- send_stats "모든 것을 제거하십시오"
+ send_stats "모두 제거"
remove htop iftop tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
+ opencode uninstall
+ rm -rf ~/.opencode
;;
41)
clear
- read -e -p "설치된 도구 이름 (WGET CURL SUDO HTOP)을 입력하십시오." installname
+ read -e -p "설치된 도구 이름을 입력하십시오(wget 컬 sudo htop):" installname
install $installname
- send_stats "지정된 소프트웨어를 설치하십시오"
+ send_stats "지정된 소프트웨어 설치"
;;
42)
clear
- read -e -p "제거되지 않은 도구 이름 (HTOP UFW TMUX CMATRIX)을 입력하십시오." removename
+ read -e -p "제거된 도구 이름(htop ufw tmux cmatrix)을 입력하십시오." removename
remove $removename
- send_stats "지정된 소프트웨어를 제거하십시오"
+ send_stats "지정된 소프트웨어 제거"
;;
0)
@@ -6740,7 +8014,7 @@ linux_tools() {
;;
*)
- echo "잘못된 입력!"
+ echo "입력이 잘못되었습니다!"
;;
esac
break_end
@@ -6760,24 +8034,24 @@ linux_bbr() {
clear
local congestion_algorithm=$(sysctl -n net.ipv4.tcp_congestion_control)
local queue_algorithm=$(sysctl -n net.core.default_qdisc)
- echo "현재 TCP 차단 알고리즘 :$congestion_algorithm $queue_algorithm"
+ echo "현재 TCP 차단 알고리즘:$congestion_algorithm $queue_algorithm"
echo ""
echo "BBR 관리"
echo "------------------------"
- echo "1. BBRV3 켜기 2. BBRV3 끄기 (재시작)"
+ echo "1. BBRv3를 켭니다. 2. BBRv3을 끕니다(다시 시작됩니다)."
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
bbr_on
- send_stats "알파인 활성화 BBR3"
+ send_stats "알파인이 열립니다 bbr3"
;;
2)
- sed -i '/net.ipv4.tcp_congestion_control=bbr/d' /etc/sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control=/d' /etc/sysctl.conf
sysctl -p
server_reboot
;;
@@ -6801,75 +8075,397 @@ linux_bbr() {
-linux_docker() {
+docker_ssh_migration() {
- while true; do
- clear
- # Send_stats "Docker Management"
- echo -e "도커 관리"
- docker_tato
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Docker 환경을 설치하고 업데이트하십시오${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}2. ${gl_bai}Docker Global Status를 봅니다${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}3. ${gl_bai}도커 컨테이너 관리${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}4. ${gl_bai}도커 이미지 관리"
- echo -e "${gl_kjlan}5. ${gl_bai}도커 네트워크 관리"
- echo -e "${gl_kjlan}6. ${gl_bai}도커 볼륨 관리"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}7. ${gl_bai}쓸모없는 도커 컨테이너 및 미러 네트워크 데이터 볼륨을 청소하십시오"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}8. ${gl_bai}Docker 소스를 교체하십시오"
- echo -e "${gl_kjlan}9. ${gl_bai}daemon.json 파일 편집"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}Docker-IPV6 액세스를 활성화하십시오"
- echo -e "${gl_kjlan}12. ${gl_bai}Docker-IPV6 액세스를 닫습니다"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}20. ${gl_bai}Docker 환경을 제거하십시오"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ is_compose_container() {
+ local container=$1
+ docker inspect "$container" | jq -e '.[0].Config.Labels["com.docker.compose.project"]' >/dev/null 2>&1
+ }
- case $sub_choice in
- 1)
- clear
- send_stats "Docker 환경을 설치하십시오"
- install_add_docker
+ list_backups() {
+ local BACKUP_ROOT="/tmp"
+ echo -e "${gl_kjlan}현재 백업 목록:${gl_bai}"
+ ls -1dt ${BACKUP_ROOT}/docker_backup_* 2>/dev/null || echo "백업 없음"
+ }
- ;;
- 2)
- clear
- local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
- local image_count=$(docker images -q 2>/dev/null | wc -l)
- local network_count=$(docker network ls -q 2>/dev/null | wc -l)
- local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
- send_stats "도커 글로벌 상태"
- echo "도커 버전"
- docker -v
- docker compose version
- echo ""
- echo -e "도커 이미지 :${gl_lv}$image_count${gl_bai} "
- docker image ls
- echo ""
- echo -e "도커 컨테이너 :${gl_lv}$container_count${gl_bai}"
- docker ps -a
- echo ""
- echo -e "도커 볼륨 :${gl_lv}$volume_count${gl_bai}"
- docker volume ls
- echo ""
- echo -e "도커 네트워크 :${gl_lv}$network_count${gl_bai}"
- docker network ls
- echo ""
+ # ----------------------------
+ # 지원
+ # ----------------------------
+ backup_docker() {
+ send_stats "도커 백업"
- ;;
- 3)
- docker_ps
- ;;
- 4)
+ echo -e "${gl_kjlan}Docker 컨테이너 백업 중...${gl_bai}"
+ docker ps --format '{{.Names}}'
+ read -e -p "백업할 컨테이너의 이름을 입력하십시오(실행 중인 모든 컨테이너를 백업하려면 여러 개의 공백을 구분하고 Enter 키를 누르십시오)." containers
+
+ install tar jq gzip
+ install_docker
+
+ local BACKUP_ROOT="/tmp"
+ local DATE_STR=$(date +%Y%m%d_%H%M%S)
+ local TARGET_CONTAINERS=()
+ if [ -z "$containers" ]; then
+ mapfile -t TARGET_CONTAINERS < <(docker ps --format '{{.Names}}')
+ else
+ read -ra TARGET_CONTAINERS <<< "$containers"
+ fi
+ [[ ${#TARGET_CONTAINERS[@]} -eq 0 ]] && { echo -e "${gl_hong}컨테이너를 찾을 수 없습니다.${gl_bai}"; return; }
+
+ local BACKUP_DIR="${BACKUP_ROOT}/docker_backup_${DATE_STR}"
+ mkdir -p "$BACKUP_DIR"
+
+ local RESTORE_SCRIPT="${BACKUP_DIR}/docker_restore.sh"
+ echo "#!/bin/bash" > "$RESTORE_SCRIPT"
+ echo "set -e" >> "$RESTORE_SCRIPT"
+ echo "# 자동으로 생성된 복원 스크립트" >> "$RESTORE_SCRIPT"
+
+ # 반복되는 패키징을 피하기 위해 패키징된 Compose 프로젝트 경로를 기록하세요.
+ declare -A PACKED_COMPOSE_PATHS=()
+
+ for c in "${TARGET_CONTAINERS[@]}"; do
+ echo -e "${gl_lv}백업 컨테이너:$c${gl_bai}"
+ local inspect_file="${BACKUP_DIR}/${c}_inspect.json"
+ docker inspect "$c" > "$inspect_file"
+
+ if is_compose_container "$c"; then
+ echo -e "${gl_kjlan}감지됨$c도커 작성 컨테이너입니다${gl_bai}"
+ local project_dir=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty')
+ local project_name=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty')
+
+ if [ -z "$project_dir" ]; then
+ read -e -p "작성 디렉터리가 감지되지 않습니다. 경로를 수동으로 입력하세요." project_dir
+ fi
+
+ # Compose 프로젝트가 이미 패키징된 경우 건너뛰세요.
+ if [[ -n "${PACKED_COMPOSE_PATHS[$project_dir]}" ]]; then
+ echo -e "${gl_huang}프로젝트 작성 [$project_name] 이미 백업되어 있으니 반복 패키징은 생략하세요...${gl_bai}"
+ continue
+ fi
+
+ if [ -f "$project_dir/docker-compose.yml" ]; then
+ echo "compose" > "${BACKUP_DIR}/backup_type_${project_name}"
+ echo "$project_dir" > "${BACKUP_DIR}/compose_path_${project_name}.txt"
+ tar -czf "${BACKUP_DIR}/compose_project_${project_name}.tar.gz" -C "$project_dir" .
+ echo "# docker-compose 복원:$project_name" >> "$RESTORE_SCRIPT"
+ echo "cd \"$project_dir\" && docker compose up -d" >> "$RESTORE_SCRIPT"
+ PACKED_COMPOSE_PATHS["$project_dir"]=1
+ echo -e "${gl_lv}프로젝트 작성 [$project_name] 포장:${project_dir}${gl_bai}"
+ else
+ echo -e "${gl_hong}docker-compose.yml을 찾을 수 없습니다. 이 컨테이너를 건너뛰는 중입니다...${gl_bai}"
+ fi
+ else
+ # 일반 컨테이너 백업 볼륨
+ local VOL_PATHS
+ VOL_PATHS=$(docker inspect "$c" --format '{{range .Mounts}}{{.Source}} {{end}}')
+ for path in $VOL_PATHS; do
+ echo "포장량:$path"
+ tar -czpf "${BACKUP_DIR}/${c}_$(basename $path).tar.gz" -C / "$(echo $path | sed 's/^\///')"
+ done
+
+ # 포트
+ local PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[] | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$inspect_file" 2>/dev/null)
+ for p in "${PORTS[@]}"; do PORT_ARGS+="-p $p "; done
+
+ # 환경 변수
+ local ENV_VARS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[] | @sh' "$inspect_file")
+ for e in "${ENVS[@]}"; do ENV_VARS+="-e $e "; done
+
+ # 볼륨 매핑
+ local VOL_ARGS=""
+ for path in $VOL_PATHS; do VOL_ARGS+="-v $path:$path "; done
+
+ # 거울
+ local IMAGE
+ IMAGE=$(jq -r '.[0].Config.Image' "$inspect_file")
+
+ echo -e "\n# 복원 컨테이너:$c" >> "$RESTORE_SCRIPT"
+ echo "docker run -d --name $c $PORT_ARGS $VOL_ARGS $ENV_VARS $IMAGE" >> "$RESTORE_SCRIPT"
+ fi
+ done
+
+
+ # /home/docker 아래의 모든 파일을 백업합니다(하위 디렉터리 제외).
+ if [ -d "/home/docker" ]; then
+ echo -e "${gl_kjlan}/home/docker 아래의 파일을 백업합니다...${gl_bai}"
+ find /home/docker -maxdepth 1 -type f | tar -czf "${BACKUP_DIR}/home_docker_files.tar.gz" -T -
+ echo -e "${gl_lv}/home/docker 아래의 파일은 다음과 같이 패키지되었습니다.${BACKUP_DIR}/home_docker_files.tar.gz${gl_bai}"
+ fi
+
+ chmod +x "$RESTORE_SCRIPT"
+ echo -e "${gl_lv}백업 완료:${BACKUP_DIR}${gl_bai}"
+ echo -e "${gl_lv}사용 가능한 복원 스크립트:${RESTORE_SCRIPT}${gl_bai}"
+
+
+ }
+
+ # ----------------------------
+ # 절감
+ # ----------------------------
+ restore_docker() {
+
+ send_stats "도커 복원"
+ read -e -p "복원할 백업 디렉터리를 입력하십시오:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}백업 디렉터리가 존재하지 않습니다.${gl_bai}"; return; }
+
+ echo -e "${gl_kjlan}복원 작업을 시작하는 중...${gl_bai}"
+
+ install tar jq gzip
+ install_docker
+
+ # --------- Compose 프로젝트 복원 우선순위 지정 ---------
+ for f in "$BACKUP_DIR"/backup_type_*; do
+ [[ ! -f "$f" ]] && continue
+ if grep -q "compose" "$f"; then
+ project_name=$(basename "$f" | sed 's/backup_type_//')
+ path_file="$BACKUP_DIR/compose_path_${project_name}.txt"
+ [[ -f "$path_file" ]] && original_path=$(cat "$path_file") || original_path=""
+ [[ -z "$original_path" ]] && read -e -p "원래 경로를 찾을 수 없습니다. 복원 디렉터리 경로를 입력하십시오:" original_path
+
+ # Compose 프로젝트의 컨테이너가 이미 실행 중인지 확인하세요.
+ running_count=$(docker ps --filter "label=com.docker.compose.project=$project_name" --format '{{.Names}}' | wc -l)
+ if [[ "$running_count" -gt 0 ]]; then
+ echo -e "${gl_huang}프로젝트 작성 [$project_name] 컨테이너가 이미 실행 중입니다. 복원을 건너뛰세요...${gl_bai}"
+ continue
+ fi
+
+ read -e -p "Compose 프로젝트 복원 확인 [$project_name] 경로 [$original_path] ? (y/n): " confirm
+ [[ "$confirm" != "y" ]] && read -e -p "새 복원 경로를 입력하십시오." original_path
+
+ mkdir -p "$original_path"
+ tar -xzf "$BACKUP_DIR/compose_project_${project_name}.tar.gz" -C "$original_path"
+ echo -e "${gl_lv}프로젝트 작성 [$project_name]는 다음 위치로 추출되었습니다.$original_path${gl_bai}"
+
+ cd "$original_path" || return
+ docker compose down || true
+ docker compose up -d
+ echo -e "${gl_lv}프로젝트 작성 [$project_name] 복원 완료!${gl_bai}"
+ fi
+ done
+
+ # --------- 일반 컨테이너를 계속 복원합니다 ---------
+ echo -e "${gl_kjlan}일반 Docker 컨테이너 확인 및 복원...${gl_bai}"
+ local has_container=false
+ for json in "$BACKUP_DIR"/*_inspect.json; do
+ [[ ! -f "$json" ]] && continue
+ has_container=true
+ container=$(basename "$json" | sed 's/_inspect.json//')
+ echo -e "${gl_lv}처리용기:$container${gl_bai}"
+
+ # 컨테이너가 이미 존재하고 실행 중인지 확인하세요.
+ if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}컨테이너 [$container] 이미 실행 중입니다. 복원을 건너뛰는 중...${gl_bai}"
+ continue
+ fi
+
+ IMAGE=$(jq -r '.[0].Config.Image' "$json")
+ [[ -z "$IMAGE" || "$IMAGE" == "null" ]] && { echo -e "${gl_hong}미러 정보를 찾을 수 없습니다. 건너뛰세요:$container${gl_bai}"; continue; }
+
+ # 포트 매핑
+ PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$json")
+ for p in "${PORTS[@]}"; do
+ [[ -n "$p" ]] && PORT_ARGS="$PORT_ARGS -p $p"
+ done
+
+ # 환경 변수
+ ENV_ARGS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[]' "$json")
+ for e in "${ENVS[@]}"; do
+ ENV_ARGS="$ENV_ARGS -e \"$e\""
+ done
+
+ # 볼륨 매핑 + 볼륨 데이터 복구
+ VOL_ARGS=""
+ mapfile -t VOLS < <(jq -r '.[0].Mounts[] | "\(.Source):\(.Destination)"' "$json")
+ for v in "${VOLS[@]}"; do
+ VOL_SRC=$(echo "$v" | cut -d':' -f1)
+ VOL_DST=$(echo "$v" | cut -d':' -f2)
+ mkdir -p "$VOL_SRC"
+ VOL_ARGS="$VOL_ARGS -v $VOL_SRC:$VOL_DST"
+
+ VOL_FILE="$BACKUP_DIR/${container}_$(basename $VOL_SRC).tar.gz"
+ if [[ -f "$VOL_FILE" ]]; then
+ echo "볼륨 데이터 복구:$VOL_SRC"
+ tar -xzf "$VOL_FILE" -C /
+ fi
+ done
+
+ # 기존이지만 실행되지 않는 컨테이너 삭제
+ if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}컨테이너 [$container]가 있지만 실행 중이 아닙니다. 이전 컨테이너를 삭제하세요...${gl_bai}"
+ docker rm -f "$container"
+ fi
+
+ # 컨테이너 시작
+ echo "복원 명령을 실행합니다: docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ eval "docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ done
+
+ [[ "$has_container" == false ]] && echo -e "${gl_huang}공통 컨테이너에 대한 백업 정보가 없습니다.${gl_bai}"
+
+ # /home/docker 아래의 파일 복원
+ if [ -f "$BACKUP_DIR/home_docker_files.tar.gz" ]; then
+ echo -e "${gl_kjlan}/home/docker 아래의 파일을 복원하는 중...${gl_bai}"
+ mkdir -p /home/docker
+ tar -xzf "$BACKUP_DIR/home_docker_files.tar.gz" -C /
+ echo -e "${gl_lv}/home/docker 아래의 파일이 복원되었습니다.${gl_bai}"
+ else
+ echo -e "${gl_huang}/home/docker 아래의 파일 백업을 찾을 수 없습니다. 건너뛰는 중...${gl_bai}"
+ fi
+
+
+ }
+
+
+ # ----------------------------
+ # 이주하다
+ # ----------------------------
+ migrate_docker() {
+ send_stats "도커 마이그레이션"
+ install jq
+ read -e -p "마이그레이션할 백업 디렉터리를 입력하세요." BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}백업 디렉터리가 존재하지 않습니다.${gl_bai}"; return; }
+
+ kj_ssh_read_host_user_port "대상 서버 IP:" "대상 서버 SSH 사용자 이름 [기본 루트]:" "대상 서버 SSH 포트 [기본값 22]:" "root" "22"
+ local TARGET_IP="$KJ_SSH_HOST"
+ local TARGET_USER="$KJ_SSH_USER"
+ local TARGET_PORT="$KJ_SSH_PORT"
+
+ local LATEST_TAR="$BACKUP_DIR"
+
+ echo -e "${gl_huang}백업 전송 중...${gl_bai}"
+ if [[ -z "$TARGET_PASS" ]]; then
+ # 키를 사용하여 로그인
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no -r "$LATEST_TAR" "$TARGET_USER@$TARGET_IP:/tmp/"
+ fi
+
+ }
+
+ # ----------------------------
+ # 백업 삭제
+ # ----------------------------
+ delete_backup() {
+ send_stats "Docker 백업 파일 삭제"
+ read -e -p "삭제할 백업 디렉터리를 입력하십시오:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}백업 디렉터리가 존재하지 않습니다.${gl_bai}"; return; }
+ rm -rf "$BACKUP_DIR"
+ echo -e "${gl_lv}삭제된 백업:${BACKUP_DIR}${gl_bai}"
+ }
+
+ # ----------------------------
+ # 메인 메뉴
+ # ----------------------------
+ main_menu() {
+ send_stats "Docker 백업 마이그레이션 복원"
+ while true; do
+ clear
+ echo "------------------------"
+ echo -e "Docker 백업/마이그레이션/복원 도구"
+ echo "------------------------"
+ list_backups
+ echo -e ""
+ echo "------------------------"
+ echo -e "1. 도커 프로젝트 백업"
+ echo -e "2. 도커 프로젝트 마이그레이션"
+ echo -e "3. 도커 프로젝트 복원"
+ echo -e "4. Docker 프로젝트의 백업 파일을 삭제합니다."
+ echo "------------------------"
+ echo -e "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택하세요:" choice
+ case $choice in
+ 1) backup_docker ;;
+ 2) migrate_docker ;;
+ 3) restore_docker ;;
+ 4) delete_backup ;;
+ 0) return ;;
+ *) echo -e "${gl_hong}잘못된 옵션${gl_bai}" ;;
+ esac
+ break_end
+ done
+ }
+
+ main_menu
+}
+
+
+
+
+
+linux_docker() {
+
+ while true; do
+ clear
+ # send_stats "도커 관리"
+ echo -e "도커 관리"
+ docker_tato
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}Docker 환경 설치 및 업데이트${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}2. ${gl_bai}Docker 전역 상태 보기${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}3. ${gl_bai}도커 컨테이너 관리${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}4. ${gl_bai}도커 이미지 관리"
+ echo -e "${gl_kjlan}5. ${gl_bai}도커 네트워크 관리"
+ echo -e "${gl_kjlan}6. ${gl_bai}도커 볼륨 관리"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}7. ${gl_bai}쓸모없는 도커 컨테이너를 정리하고 네트워크 데이터 볼륨을 미러링하세요."
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}8. ${gl_bai}Docker 소스 변경"
+ echo -e "${gl_kjlan}9. ${gl_bai}daemon.json 파일 편집"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}11. ${gl_bai}Docker-ipv6 액세스 활성화"
+ echo -e "${gl_kjlan}12. ${gl_bai}Docker-ipv6 액세스 끄기"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}19. ${gl_bai}Docker 환경 백업/마이그레이션/복원"
+ echo -e "${gl_kjlan}20. ${gl_bai}Docker 환경 제거"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "선택사항을 입력하세요:" sub_choice
+
+ case $sub_choice in
+ 1)
+ clear
+ send_stats "도커 환경 설치"
+ install_add_docker
+
+ ;;
+ 2)
+ clear
+ local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
+ local image_count=$(docker images -q 2>/dev/null | wc -l)
+ local network_count=$(docker network ls -q 2>/dev/null | wc -l)
+ local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
+
+ send_stats "도커 전역 상태"
+ echo "도커 버전"
+ docker -v
+ docker compose version
+
+ echo ""
+ echo -e "도커 이미지:${gl_lv}$image_count${gl_bai} "
+ docker image ls
+ echo ""
+ echo -e "도커 컨테이너:${gl_lv}$container_count${gl_bai}"
+ docker ps -a
+ echo ""
+ echo -e "Docker 볼륨:${gl_lv}$volume_count${gl_bai}"
+ docker volume ls
+ echo ""
+ echo -e "도커 네트워크:${gl_lv}$network_count${gl_bai}"
+ docker network ls
+ echo ""
+
+ ;;
+ 3)
+ docker_ps
+ ;;
+ 4)
docker_image
;;
@@ -6884,7 +8480,7 @@ linux_docker() {
echo "------------------------------------------------------------"
container_ids=$(docker ps -q)
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "컨테이너 이름" "네트워크 이름" "IP 주소"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
@@ -6901,36 +8497,36 @@ linux_docker() {
done
echo ""
- echo "네트워크 작동"
+ echo "네트워크 운영"
echo "------------------------"
- echo "1. 네트워크를 만듭니다"
- echo "2. 인터넷에 가입하십시오"
- echo "3. 네트워크를 종료하십시오"
- echo "4. 네트워크를 삭제합니다"
+ echo "1. 네트워크 생성"
+ echo "2. 네트워크에 가입하세요"
+ echo "3. 네트워크 종료"
+ echo "4. 네트워크 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- send_stats "네트워크를 만듭니다"
- read -e -p "새 네트워크 이름 설정 :" dockernetwork
+ send_stats "네트워크 생성"
+ read -e -p "새 네트워크 이름 설정:" dockernetwork
docker network create $dockernetwork
;;
2)
- send_stats "인터넷에 가입하십시오"
- read -e -p "네트워크 이름에 가입 :" dockernetwork
- read -e -p "해당 컨테이너는 네트워크에 추가됩니다 (여러 컨테이너 이름은 공간으로 분리됩니다)." dockernames
+ send_stats "네트워크에 가입하세요"
+ read -e -p "네트워크 이름 추가:" dockernetwork
+ read -e -p "네트워크에 참여하는 컨테이너(여러 컨테이너 이름을 공백으로 구분하세요):" dockernames
for dockername in $dockernames; do
docker network connect $dockernetwork $dockername
done
;;
3)
- send_stats "인터넷에 가입하십시오"
- read -e -p "종료 네트워크 이름 :" dockernetwork
- read -e -p "해당 컨테이너는 네트워크를 종료합니다 (여러 컨테이너 이름은 공간별로 분리됩니다)." dockernames
+ send_stats "네트워크에 가입하세요"
+ read -e -p "종료 네트워크 이름:" dockernetwork
+ read -e -p "이러한 컨테이너는 네트워크를 종료합니다(여러 컨테이너 이름을 공백으로 구분하세요)." dockernames
for dockername in $dockernames; do
docker network disconnect $dockernetwork $dockername
@@ -6939,8 +8535,8 @@ linux_docker() {
;;
4)
- send_stats "네트워크를 삭제하십시오"
- read -e -p "삭제하려면 네트워크 이름을 입력하십시오." dockernetwork
+ send_stats "네트워크 삭제"
+ read -e -p "삭제할 네트워크 이름을 입력하세요:" dockernetwork
docker network rm $dockernetwork
;;
@@ -6958,25 +8554,25 @@ linux_docker() {
echo "도커 볼륨 목록"
docker volume ls
echo ""
- echo "볼륨 작동"
+ echo "볼륨 작업"
echo "------------------------"
- echo "1. 새 볼륨을 만듭니다"
- echo "2. 지정된 볼륨을 삭제합니다"
- echo "3. 모든 볼륨을 삭제하십시오"
+ echo "1. 새 볼륨 생성"
+ echo "2. 지정된 볼륨 삭제"
+ echo "3. 모든 볼륨 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- send_stats "새 볼륨을 만듭니다"
- read -e -p "새 볼륨 이름 설정 :" dockerjuan
+ send_stats "새 볼륨 생성"
+ read -e -p "새 볼륨 이름 설정:" dockerjuan
docker volume create $dockerjuan
;;
2)
- read -e -p "볼륨 삭제 이름을 입력하십시오 (공백으로 여러 볼륨 이름을 분리하십시오)." dockerjuans
+ read -e -p "삭제 볼륨 이름을 입력하십시오(여러 볼륨 이름을 공백으로 구분하십시오):" dockerjuans
for dockerjuan in $dockerjuans; do
docker volume rm $dockerjuan
@@ -6985,7 +8581,7 @@ linux_docker() {
;;
3)
- send_stats "모든 볼륨을 삭제하십시오"
+ send_stats "모든 볼륨 삭제"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定删除所有未使用的卷吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -6994,7 +8590,7 @@ linux_docker() {
[Nn])
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -7007,7 +8603,7 @@ linux_docker() {
;;
7)
clear
- send_stats "도커 청소"
+ send_stats "도커 정리"
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}将清理无用的镜像容器网络,包括停止的容器,确定清理吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -7016,7 +8612,7 @@ linux_docker() {
[Nn])
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -7033,21 +8629,29 @@ linux_docker() {
restart docker
;;
+
+
+
11)
clear
- send_stats "Docker V6 열기"
+ send_stats "도커 v6 켜짐"
docker_ipv6_on
;;
12)
clear
- send_stats "Docker V6 레벨"
+ send_stats "도커 v6 닫기"
docker_ipv6_off
;;
+ 19)
+ docker_ssh_migration
+ ;;
+
+
20)
clear
- send_stats "Docker는 제거합니다"
+ send_stats "도커 제거"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定卸载docker环境吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -7059,7 +8663,7 @@ linux_docker() {
[Nn])
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -7068,7 +8672,7 @@ linux_docker() {
kejilion
;;
*)
- echo "잘못된 입력!"
+ echo "입력이 잘못되었습니다!"
;;
esac
break_end
@@ -7085,44 +8689,45 @@ linux_test() {
while true; do
clear
- # Send_stats "테스트 스크립트 컬렉션"
- echo -e "스크립트 수집 테스트"
+ # send_stats "테스트 스크립트 수집"
+ echo -e "테스트 스크립트 수집"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}IP 및 잠금 해제 상태 감지"
- echo -e "${gl_kjlan}1. ${gl_bai}ChatGpt는 상태 감지를 잠금 해제합니다"
+ echo -e "${gl_kjlan}1. ${gl_bai}ChatGPT 잠금 해제 상태 감지"
echo -e "${gl_kjlan}2. ${gl_bai}지역 스트리밍 미디어 잠금 해제 테스트"
- echo -e "${gl_kjlan}3. ${gl_bai}YEAHWU 스트리밍 미디어 잠금 해제 탐지"
- echo -e "${gl_kjlan}4. ${gl_bai}XYKT IP 품질 신체 검사 스크립트${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}3. ${gl_bai}예우 스트리밍 미디어 잠금 해제 감지"
+ echo -e "${gl_kjlan}4. ${gl_bai}xykt IP 품질 확인 스크립트${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}네트워크 속도 측정"
- echo -e "${gl_kjlan}11. ${gl_bai}Besttrace 3 개의 네트워크 백홀 지연 라우팅 테스트"
- echo -e "${gl_kjlan}12. ${gl_bai}MTR_TRACE 3- 네트워크 백홀 라인 테스트"
- echo -e "${gl_kjlan}13. ${gl_bai}Superspeed 3 Net 속도 측정"
+ echo -e "${gl_kjlan}네트워크 회선 속도 테스트"
+ echo -e "${gl_kjlan}11. ${gl_bai}besttrace 3 네트워크 백홀 지연 라우팅 테스트"
+ echo -e "${gl_kjlan}12. ${gl_bai}mtr_trace 삼중 네트워크 백홀 회선 테스트"
+ echo -e "${gl_kjlan}13. ${gl_bai}초고속 트리플 네트워크 속도 테스트"
echo -e "${gl_kjlan}14. ${gl_bai}nxtrace 빠른 백홀 테스트 스크립트"
- echo -e "${gl_kjlan}15. ${gl_bai}nxtrace는 ip backhaul 테스트 스크립트를 지정합니다"
- echo -e "${gl_kjlan}16. ${gl_bai}Ludashi2020 3 네트워크 라인 테스트"
- echo -e "${gl_kjlan}17. ${gl_bai}I-ABC 다기능 속도 테스트 스크립트"
- echo -e "${gl_kjlan}18. ${gl_bai}Netquality Network 품질 신체 검사 스크립트${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}15. ${gl_bai}nxtrace는 IP 백홀 테스트 스크립트를 지정합니다."
+ echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 세 개의 네트워크 라인 테스트"
+ echo -e "${gl_kjlan}17. ${gl_bai}i-abc 다기능 속도 테스트 스크립트"
+ echo -e "${gl_kjlan}18. ${gl_bai}NetQuality 네트워크 품질 확인 스크립트${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}하드웨어 성능 테스트"
- echo -e "${gl_kjlan}21. ${gl_bai}YABS 성능 테스트"
- echo -e "${gl_kjlan}22. ${gl_bai}IICU/GB5 CPU 성능 테스트 스크립트"
+ echo -e "${gl_kjlan}21. ${gl_bai}Yabs 성능 테스트"
+ echo -e "${gl_kjlan}22. ${gl_bai}icu/gb5 CPU 성능 테스트 스크립트"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}포괄적 인 테스트"
+ echo -e "${gl_kjlan}종합적인 테스트"
echo -e "${gl_kjlan}31. ${gl_bai}벤치 성능 테스트"
- echo -e "${gl_kjlan}32. ${gl_bai}SpiritySDX 퓨전 몬스터 검토${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}32. ${gl_bai}spiritysdx 퓨전 몬스터 평가${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}Nodequality 융합 몬스터 평가${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
clear
- send_stats "ChatGpt는 상태 감지를 잠금 해제합니다"
+ send_stats "ChatGPT 잠금 해제 상태 감지"
bash <(curl -Ls https://cdn.jsdelivr.net/gh/missuo/OpenAI-Checker/openai.sh)
;;
2)
@@ -7132,31 +8737,31 @@ linux_test() {
;;
3)
clear
- send_stats "YEAHWU 스트리밍 미디어 잠금 해제 탐지"
+ send_stats "예우 스트리밍 미디어 잠금 해제 감지"
install wget
wget -qO- ${gh_proxy}github.com/yeahwu/check/raw/main/check.sh | bash
;;
4)
clear
- send_stats "xykt_ip 품질 신체 검사 스크립트"
+ send_stats "xykt_IP 품질 확인 스크립트"
bash <(curl -Ls IP.Check.Place)
;;
11)
clear
- send_stats "Besttrace 3 개의 네트워크 백홀 지연 라우팅 테스트"
+ send_stats "besttrace 삼중 네트워크 백홀 지연 라우팅 테스트"
install wget
wget -qO- git.io/besttrace | bash
;;
12)
clear
- send_stats "MTR_TRACE 3 개의 네트워크 리턴 라인 테스트"
+ send_stats "mtr_trace 삼중 네트워크 백홀 회선 테스트"
curl ${gh_proxy}raw.githubusercontent.com/zhucaidan/mtr_trace/main/mtr_trace.sh | bash
;;
13)
clear
- send_stats "Superspeed 3 Net 속도 측정"
+ send_stats "초고속 트리플 네트워크 속도 테스트"
bash <(curl -Lso- https://git.io/superspeed_uxh)
;;
14)
@@ -7167,40 +8772,40 @@ linux_test() {
;;
15)
clear
- send_stats "nxtrace는 ip backhaul 테스트 스크립트를 지정합니다"
- echo "참조 할 수있는 IP 목록"
+ send_stats "nxtrace는 IP 백홀 테스트 스크립트를 지정합니다."
+ echo "참조 IP 목록"
echo "------------------------"
- echo "베이징 통신 : 219.141.136.12"
- echo "베이징 유니폼 : 202.106.50.1"
- echo "베이징 모바일 : 221.179.155.161"
- echo "상하이 통신 : 202.96.209.133"
- echo "상하이 유니폼 : 210.22.97.1"
- echo "상하이 모바일 : 211.136.112.200"
- echo "광저우 통신 : 58.60.188.222"
- echo "광저우 유니폼 : 210.21.196.6"
- echo "광저우 모바일 : 120.196.165.24"
- echo "청두 통신 : 61.139.2.69"
- echo "청두 유니폼 : 119.6.6.6"
- echo "청두 모바일 : 211.137.96.205"
- echo "Hunan Telecom : 36.111.200.100"
- echo "후난 유니폼 : 42.48.16.100"
- echo "후난 모바일 : 39.134.254.6"
+ echo "베이징 통신: 219.141.136.12"
+ echo "베이징 유니콤: 202.106.50.1"
+ echo "베이징 모바일: 221.179.155.161"
+ echo "상하이 통신: 202.96.209.133"
+ echo "상하이 유니콤: 210.22.97.1"
+ echo "상하이 모바일: 211.136.112.200"
+ echo "광저우 통신: 58.60.188.222"
+ echo "광저우 차이나 유니콤: 210.21.196.6"
+ echo "광저우 모바일: 120.196.165.24"
+ echo "청두통신: 61.139.2.69"
+ echo "청두 차이나 유니콤: 119.6.6.6"
+ echo "청두 모바일: 211.137.96.205"
+ echo "후난 통신: 36.111.200.100"
+ echo "후난 유니콤: 42.48.16.100"
+ echo "후난 모바일: 39.134.254.6"
echo "------------------------"
- read -e -p "지정된 IP를 입력하십시오." testip
+ read -e -p "특정 IP를 입력하세요:" testip
curl nxtrace.org/nt |bash
nexttrace $testip
;;
16)
clear
- send_stats "Ludashi2020 3 네트워크 라인 테스트"
+ send_stats "ludashi2020 세 개의 네트워크 라인 테스트"
curl ${gh_proxy}raw.githubusercontent.com/ludashi2020/backtrace/main/install.sh -sSf | sh
;;
17)
clear
- send_stats "I-ABC 다기능 속도 테스트 스크립트"
+ send_stats "i-abc 다기능 속도 테스트 스크립트"
bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/i-abc/Speedtest/main/speedtest.sh)
;;
@@ -7212,13 +8817,13 @@ linux_test() {
21)
clear
- send_stats "YABS 성능 테스트"
+ send_stats "Yabs 성능 테스트"
check_swap
curl -sL yabs.sh | bash -s -- -i -5
;;
22)
clear
- send_stats "IICU/GB5 CPU 성능 테스트 스크립트"
+ send_stats "icu/gb5 CPU 성능 테스트 스크립트"
check_swap
bash <(curl -sL bash.icu/gb5)
;;
@@ -7229,17 +8834,25 @@ linux_test() {
curl -Lso- bench.sh | bash
;;
32)
- send_stats "SpiritySDX 퓨전 몬스터 검토"
+ send_stats "spiritysdx 퓨전 몬스터 리뷰"
clear
- curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ curl -L ${gh_proxy}github.com/spiritLHLS/ecs/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
;;
+ 33)
+ send_stats "Nodequality 융합 몬스터 평가"
+ clear
+ bash <(curl -sL https://run.NodeQuality.com)
+ ;;
+
+
+
0)
kejilion
;;
*)
- echo "잘못된 입력!"
+ echo "입력이 잘못되었습니다!"
;;
esac
break_end
@@ -7258,49 +8871,49 @@ linux_Oracle() {
send_stats "Oracle Cloud 스크립트 컬렉션"
echo -e "Oracle Cloud 스크립트 컬렉션"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}유휴 기계 활성 스크립트를 설치하십시오"
- echo -e "${gl_kjlan}2. ${gl_bai}유휴 기계 활성 스크립트를 제거하십시오"
+ echo -e "${gl_kjlan}1. ${gl_bai}유휴 머신 활성 스크립트 설치"
+ echo -e "${gl_kjlan}2. ${gl_bai}유휴 컴퓨터에서 활성 스크립트 제거"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}3. ${gl_bai}DD 다시 설치 시스템 스크립트"
- echo -e "${gl_kjlan}4. ${gl_bai}형사 R 스크립트 시작"
- echo -e "${gl_kjlan}5. ${gl_bai}루트 비밀번호 로그인 모드를 켭니다"
- echo -e "${gl_kjlan}6. ${gl_bai}IPv6 복구 도구"
+ echo -e "${gl_kjlan}3. ${gl_bai}DD 재설치 시스템 스크립트"
+ echo -e "${gl_kjlan}4. ${gl_bai}R 형사 시작 스크립트"
+ echo -e "${gl_kjlan}5. ${gl_bai}ROOT 비밀번호 로그인 모드 활성화"
+ echo -e "${gl_kjlan}6. ${gl_bai}IPV6 복구 도구"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
clear
- echo "활성 스크립트 : CPU는 10-20% 메모리를 점유합니다."
- read -e -p "설치 하시겠습니까? (Y/N) :" choice
+ echo "활성 스크립트: CPU 사용량 10-20% 메모리 사용량 20%"
+ read -e -p "정말로 설치하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
install_docker
- # 기본값을 설정합니다
+ # 기본값 설정
local DEFAULT_CPU_CORE=1
local DEFAULT_CPU_UTIL="10-20"
local DEFAULT_MEM_UTIL=20
local DEFAULT_SPEEDTEST_INTERVAL=120
- # 사용자에게 CPU 코어 수와 점유율 백분율을 입력하라는 메시지를 표시하고 입력 한 경우 기본값을 사용하십시오.
- read -e -p "CPU 코어 수를 입력하십시오 [기본값 :$DEFAULT_CPU_CORE]: " cpu_core
+ # 사용자에게 CPU 코어 수와 점유율을 입력하라는 메시지를 표시합니다. 사용자가 Enter 키를 누르면 기본값이 사용됩니다.
+ read -e -p "CPU 코어 수를 입력하십시오.[기본값:$DEFAULT_CPU_CORE]: " cpu_core
local cpu_core=${cpu_core:-$DEFAULT_CPU_CORE}
- read -e -p "CPU 사용 백분율 범위 (예 : 10-20) [기본값 :$DEFAULT_CPU_UTIL]: " cpu_util
+ read -e -p "CPU 사용량 백분율 범위(예: 10-20)를 입력하십시오. [기본값:$DEFAULT_CPU_UTIL]: " cpu_util
local cpu_util=${cpu_util:-$DEFAULT_CPU_UTIL}
- read -e -p "메모리 사용 백분율을 입력하십시오 [기본값 :$DEFAULT_MEM_UTIL]: " mem_util
+ read -e -p "메모리 사용량 비율을 입력하십시오.[기본값:$DEFAULT_MEM_UTIL]: " mem_util
local mem_util=${mem_util:-$DEFAULT_MEM_UTIL}
- read -e -p "스피드 테스트 간격 시간 (초)을 입력하십시오 [기본값 :$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
+ read -e -p "속도 테스트 간격 시간(초)을 입력하십시오. [기본값:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
local speedtest_interval=${speedtest_interval:-$DEFAULT_SPEEDTEST_INTERVAL}
- # 도커 컨테이너를 실행하십시오
- docker run -itd --name=lookbusy --restart=always \
+ # Docker 컨테이너 실행
+ docker run -d --name=lookbusy --restart=always \
-e TZ=Asia/Shanghai \
-e CPU_UTIL="$cpu_util" \
-e CPU_CORE="$cpu_core" \
@@ -7314,7 +8927,7 @@ linux_Oracle() {
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -7322,20 +8935,20 @@ linux_Oracle() {
clear
docker rm -f lookbusy
docker rmi fogforest/lookbusy
- send_stats "Oracle Cloud는 활성 스크립트를 제거합니다"
+ send_stats "Oracle Cloud 제거 활성 스크립트"
;;
3)
clear
- echo "시스템을 다시 설치하십시오"
+ echo "시스템 재설치"
echo "--------------------------------"
- echo -e "${gl_hong}알아채다:${gl_bai}다시 설치는 접촉을 잃을 위험이 있으며 걱정하는 사람들은 그것을주의해서 사용해야합니다. 재설치는 15 분이 걸릴 것으로 예상됩니다. 데이터를 미리 백업하십시오."
- read -e -p "계속할거야? (Y/N) :" choice
+ echo -e "${gl_hong}알아채다:${gl_bai}재설치 시 연결이 끊어질 수 있으니 걱정되시는 분들은 주의해서 사용해주세요. 재설치에는 약 15분 정도 소요될 예정이오니, 사전에 데이터를 백업해 주시기 바랍니다."
+ read -e -p "계속하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
while true; do
- read -e -p "다시 설치할 시스템을 선택하십시오 : 1. Debian12 | 2. Ubuntu20.04 :" sys_choice
+ read -e -p "다시 설치하려는 시스템을 선택하십시오: 1. Debian12 | 2. 우분투20.04:" sys_choice
case "$sys_choice" in
1)
@@ -7347,46 +8960,46 @@ linux_Oracle() {
break # 结束循环
;;
*)
- echo "유효하지 않은 선택, 다시 입력하십시오."
+ echo "선택이 잘못되었습니다. 다시 입력해 주세요."
;;
esac
done
- read -e -p "다시 설치 한 비밀번호를 입력하십시오." vpspasswd
+ read -e -p "재설치 후 비밀번호를 입력해주세요:" vpspasswd
install wget
bash <(wget --no-check-certificate -qO- "${gh_proxy}raw.githubusercontent.com/MoeClub/Note/master/InstallNET.sh") $xitong -v 64 -p $vpspasswd -port 22
- send_stats "Oracle Cloud 회복 시스템 스크립트"
+ send_stats "Oracle Cloud 재설치 시스템 스크립트"
;;
[Nn])
echo "취소"
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
4)
clear
- echo "이 기능은 개발 단계에 있으므로 계속 지켜봐 주시기 바랍니다!"
+ send_stats "R 형사 시작 스크립트"
+ bash <(wget -qO- ${gh_proxy}github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh)
;;
5)
clear
add_sshpasswd
-
;;
6)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "이 기능은 마스터 JHB가 제공합니다."
- send_stats "IPv6 수정"
+ echo "이 기능은 jhb에서 제공합니다. 감사합니다!"
+ send_stats "IPv6 수리"
;;
0)
kejilion
;;
*)
- echo "잘못된 입력!"
+ echo "입력이 잘못되었습니다!"
;;
esac
break_end
@@ -7398,6 +9011,9 @@ linux_Oracle() {
}
+
+
+
docker_tato() {
local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
@@ -7407,7 +9023,7 @@ docker_tato() {
if command -v docker &> /dev/null; then
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_lv}환경이 설치되었습니다${gl_bai}컨테이너:${gl_lv}$container_count${gl_bai}거울:${gl_lv}$image_count${gl_bai}회로망:${gl_lv}$network_count${gl_bai}연타:${gl_lv}$volume_count${gl_bai}"
+ echo -e "${gl_lv}환경이 설치되었습니다.${gl_bai}컨테이너:${gl_lv}$container_count${gl_bai}거울:${gl_lv}$image_count${gl_bai}회로망:${gl_lv}$network_count${gl_bai}연타:${gl_lv}$volume_count${gl_bai}"
fi
}
@@ -7415,20 +9031,20 @@ docker_tato() {
ldnmp_tato() {
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
-local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml 2>/dev/null | tr -d '[:space:]')
if [ -n "$dbrootpasswd" ]; then
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
fi
-local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+local db_output="${gl_lv}${db_count}${gl_bai}"
if command -v docker &>/dev/null; then
if docker ps --filter "name=nginx" --filter "status=running" | grep -q nginx; then
echo -e "${gl_huang}------------------------"
- echo -e "${gl_lv}환경이 설치됩니다${gl_bai} $output $db_output"
+ echo -e "${gl_lv}환경이 설치되었습니다${gl_bai}대지:$output데이터 베이스:$db_output"
fi
fi
@@ -7456,31 +9072,31 @@ linux_ldnmp() {
while true; do
clear
- # send_stats "ldnmp 웹 사이트 빌딩"
- echo -e "${gl_huang}LDNMP 웹 사이트 구축"
+ # send_stats "LDNMP 웹사이트 구축"
+ echo -e "${gl_huang}LDNMP 웹사이트 구축"
ldnmp_tato
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}1. ${gl_bai}LDNMP 환경을 설치하십시오${gl_huang}★${gl_bai} ${gl_huang}2. ${gl_bai}WordPress를 설치하십시오${gl_huang}★${gl_bai}"
- echo -e "${gl_huang}3. ${gl_bai}Discuz 포럼을 설치하십시오${gl_huang}4. ${gl_bai}Kadao 클라우드 데스크탑을 설치하십시오"
- echo -e "${gl_huang}5. ${gl_bai}Apple CMS 영화 및 텔레비전 방송국을 설치하십시오${gl_huang}6. ${gl_bai}유니콘 디지털 카드 네트워크를 설치하십시오"
- echo -e "${gl_huang}7. ${gl_bai}Flarum Forum 웹 사이트를 설치하십시오${gl_huang}8. ${gl_bai}Typecho Lightweight 블로그 웹 사이트를 설치하십시오"
- echo -e "${gl_huang}9. ${gl_bai}LinkStack 공유 링크 플랫폼을 설치하십시오${gl_huang}20. ${gl_bai}동적 사이트를 사용자 정의합니다"
+ echo -e "${gl_huang}1. ${gl_bai}LDNMP 환경 설치${gl_huang}★${gl_bai} ${gl_huang}2. ${gl_bai}워드프레스 설치${gl_huang}★${gl_bai}"
+ echo -e "${gl_huang}3. ${gl_bai}Discuz 포럼 설치${gl_huang}4. ${gl_bai}Kedao 클라우드 데스크탑 설치"
+ echo -e "${gl_huang}5. ${gl_bai}Apple CMS 영화 및 TV 스테이션 설치${gl_huang}6. ${gl_bai}Unicorn 디지털 카드 네트워크 설치"
+ echo -e "${gl_huang}7. ${gl_bai}flarum 포럼 웹사이트 설치${gl_huang}8. ${gl_bai}typecho 경량 블로그 웹사이트 설치"
+ echo -e "${gl_huang}9. ${gl_bai}LinkStack 공유 링크 플랫폼 설치${gl_huang}20. ${gl_bai}맞춤 동적 사이트"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}21. ${gl_bai}nginx 만 설치하십시오${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}사이트 리디렉션"
- echo -e "${gl_huang}23. ${gl_bai}사이트 리버스 프록시 -IP+포트${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}사이트 리버스 프록시 - 도메인 이름"
- echo -e "${gl_huang}25. ${gl_bai}Bitwarden 비밀번호 관리 플랫폼을 설치하십시오${gl_huang}26. ${gl_bai}후광 블로그 웹 사이트를 설치하십시오"
- echo -e "${gl_huang}27. ${gl_bai}AI 페인팅 프롬프트 워드 생성기를 설치하십시오${gl_huang}28. ${gl_bai}사이트 리버스 프록시로드 밸런싱"
- echo -e "${gl_huang}30. ${gl_bai}정적 사이트를 사용자 정의합니다"
+ echo -e "${gl_huang}21. ${gl_bai}nginx만 설치하세요${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}사이트 리디렉션"
+ echo -e "${gl_huang}23. ${gl_bai}사이트 역방향 프록시-IP+포트${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}사이트 역방향 프록시 도메인 이름"
+ echo -e "${gl_huang}25. ${gl_bai}Bitwarden 비밀번호 관리 플랫폼 설치${gl_huang}26. ${gl_bai}Halo 블로그 사이트 설치"
+ echo -e "${gl_huang}27. ${gl_bai}AI 그림 프롬프트 단어 생성기 설치${gl_huang}28. ${gl_bai}사이트 역방향 프록시-로드 밸런싱"
+ echo -e "${gl_huang}29. ${gl_bai}스트림 4계층 프록시 전달${gl_huang}30. ${gl_bai}사용자 정의 정적 사이트"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}31. ${gl_bai}사이트 데이터 관리${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}전체 사이트 데이터를 백업합니다"
- echo -e "${gl_huang}33. ${gl_bai}시간이 지정된 원격 백업${gl_huang}34. ${gl_bai}전체 사이트 데이터를 복원하십시오"
+ echo -e "${gl_huang}31. ${gl_bai}사이트 데이터 관리${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}사이트 전체 데이터 백업"
+ echo -e "${gl_huang}33. ${gl_bai}예약된 원격 백업${gl_huang}34. ${gl_bai}전체 사이트 데이터 복원"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}35. ${gl_bai}LDNMP 환경을 보호하십시오${gl_huang}36. ${gl_bai}LDNMP 환경을 최적화하십시오"
- echo -e "${gl_huang}37. ${gl_bai}LDNMP 환경을 업데이트하십시오${gl_huang}38. ${gl_bai}LDNMP 환경을 제거하십시오"
+ echo -e "${gl_huang}35. ${gl_bai}LDNMP 환경 보호${gl_huang}36. ${gl_bai}LDNMP 환경 최적화"
+ echo -e "${gl_huang}37. ${gl_bai}LDNMP 환경 업데이트${gl_huang}38. ${gl_bai}LDNMP 환경 제거"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}0. ${gl_bai}메인 메뉴로 돌아갑니다"
+ echo -e "${gl_huang}0. ${gl_bai}메인 메뉴로 돌아가기"
echo -e "${gl_huang}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
@@ -7494,37 +9110,45 @@ linux_ldnmp() {
3)
clear
- # Discuz 포럼
- webname="Discuz论坛"
+ # 토론 포럼
+ webname="토론 포럼"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/discuz.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/main/Discuz_X3.5_SC_UTF8_20240520.zip
- unzip latest.zip
+ LATEST_URL=$(curl -s https://api.gitee.com/api/v5/repos/Discuz/DiscuzX/releases/latest | grep -o 'https://[^"]*Discuz_X[^"]*SC_UTF8[^"]*\.zip' | head -n 1)
+ wget -O latest.zip ${LATEST_URL}
+ unzip -q latest.zip
+ mv upload/* .
+ rm -rf upload readme readme.html utility.html LICENSE qqqun.png
rm latest.zip
restart_ldnmp
ldnmp_web_on
- echo "데이터베이스 주소 : MySQL"
- echo "데이터베이스 이름 :$dbname"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "데이터베이스 이름:$dbname"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
- echo "테이블 접두사 : discuz_"
+ echo "테이블 접두사: discuz_"
;;
@@ -7532,53 +9156,62 @@ linux_ldnmp() {
4)
clear
# Kedao 클라우드 데스크탑
- webname="可道云桌面"
+ webname="Kedao 클라우드 데스크탑"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/kdy.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/1.50.02.zip
+ LATEST_VERSION=$(curl -s https://api.github.com/repos/kalcaddle/kodbox/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/${LATEST_VERSION}.zip
unzip -o latest.zip
rm latest.zip
mv /home/web/html/$yuming/kodbox* /home/web/html/$yuming/kodbox
restart_ldnmp
ldnmp_web_on
- echo "데이터베이스 주소 : MySQL"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
- echo "데이터베이스 이름 :$dbname"
- echo "Redis 호스트 : Redis"
+ echo "데이터베이스 이름:$dbname"
+ echo "레디스 호스트: 레디스"
;;
5)
clear
- # Apple CMS
- webname="苹果CMS"
+ # AppleCMS
+ webname="AppleCMS"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/maccms.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7595,33 +9228,39 @@ linux_ldnmp() {
ldnmp_web_on
- echo "데이터베이스 주소 : MySQL"
- echo "데이터베이스 포트 : 3306"
- echo "데이터베이스 이름 :$dbname"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "데이터베이스 포트: 3306"
+ echo "데이터베이스 이름:$dbname"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
- echo "데이터베이스 접두사 : MAC_"
+ echo "데이터베이스 접두사: mac_"
echo "------------------------"
- echo "설치가 성공한 후 배경 주소에 로그인하십시오."
+ echo "설치가 성공적으로 완료되면 백엔드 주소로 로그인하세요."
echo "https://$yuming/vip.php"
;;
6)
clear
- # 한 다리 카운팅 카드
- webname="独脚数卡"
+ # 한쪽다리 숫자카드
+ webname="한쪽다리 숫자카드"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/dujiaoka.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7633,43 +9272,49 @@ linux_ldnmp() {
ldnmp_web_on
- echo "데이터베이스 주소 : MySQL"
- echo "데이터베이스 포트 : 3306"
- echo "데이터베이스 이름 :$dbname"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "데이터베이스 포트: 3306"
+ echo "데이터베이스 이름:$dbname"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
echo ""
- echo "Redis 주소 : Redis"
- echo "Redis Password : 기본적으로 채워지지 않습니다"
- echo "Redis Port : 6379"
+ echo "레디스 주소 : 레디스"
+ echo "redis 비밀번호: 기본적으로 입력되지 않음"
+ echo "레디스 포트: 6379"
echo ""
- echo "웹 사이트 URL : https : //$yuming"
- echo "백그라운드 로그인 경로 : /admin"
+ echo "웹사이트 URL: https://$yuming"
+ echo "백엔드 로그인 경로: /admin"
echo "------------------------"
- echo "사용자 이름 : 관리자"
- echo "비밀번호 : 관리자"
+ echo "사용자 이름: 관리자"
+ echo "비밀번호: 관리자"
echo "------------------------"
- echo "로그인 할 때 오른쪽 상단에 빨간색 Error0이 나타나면 다음 명령을 사용하십시오."
- echo "나는 또한 유니콘 번호 카드가 너무 귀찮다는 것에 대해 매우 화가 났으며 그러한 문제가있을 것입니다!"
+ echo "로그인 시 오른쪽 상단에 빨간색 error0이 나타나는 경우, 다음 명령어를 사용해주세요."
+ echo "유니콘 숫자카드가 왜 이렇게 귀찮고 이런 문제가 있는지에 대해서도 너무 화가 납니다!"
echo "sed -i 's/ADMIN_HTTPS=false/ADMIN_HTTPS=true/g' /home/web/html/$yuming/dujiaoka/.env"
;;
7)
clear
- # Flarum 포럼
- webname="flarum论坛"
+ # 플라럼 포럼
+ webname="플라럼 포럼"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/flarum.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec php rm -f /usr/local/etc/php/conf.d/optimized_php.ini
@@ -7690,19 +9335,23 @@ linux_ldnmp() {
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/sitemap"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/oauth"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/best-answer:*"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/upload"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/gamification"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/byobu:*"
docker exec php sh -c "cd /var/www/html/$yuming && composer require v17development/flarum-seo"
docker exec php sh -c "cd /var/www/html/$yuming && composer require clarkwinkelmann/flarum-ext-emojionearea"
+
restart_ldnmp
ldnmp_web_on
- echo "데이터베이스 주소 : MySQL"
- echo "데이터베이스 이름 :$dbname"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "데이터베이스 이름:$dbname"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
- echo "테이블 접두사 : flarum_"
- echo "관리자 정보는 직접 설정됩니다"
+ echo "테이블 접두사: flarum_"
+ echo "관리자 정보는 직접 설정 가능"
;;
@@ -7711,16 +9360,22 @@ linux_ldnmp() {
# typecho
webname="typecho"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/typecho.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7735,11 +9390,11 @@ linux_ldnmp() {
clear
ldnmp_web_on
- echo "데이터베이스 접두사 : typecho_"
- echo "데이터베이스 주소 : MySQL"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 접두사: typecho_"
+ echo "데이터베이스 주소: mysql"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
- echo "데이터베이스 이름 :$dbname"
+ echo "데이터베이스 이름:$dbname"
;;
@@ -7749,17 +9404,21 @@ linux_ldnmp() {
# LinkStack
webname="LinkStack"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/refs/heads/main/index_php.conf
sed -i "s|/var/www/html/yuming.com/|/var/www/html/yuming.com/linkstack|g" /home/web/conf.d/$yuming.conf
sed -i "s|yuming.com|$yuming|g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7774,27 +9433,30 @@ linux_ldnmp() {
clear
ldnmp_web_on
- echo "데이터베이스 주소 : MySQL"
- echo "데이터베이스 포트 : 3306"
- echo "데이터베이스 이름 :$dbname"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "데이터베이스 포트: 3306"
+ echo "데이터베이스 이름:$dbname"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
;;
20)
clear
- webname="PHP动态站点"
+ webname="PHP 동적 사이트"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/index_php.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7802,10 +9464,10 @@ linux_ldnmp() {
cd $yuming
clear
- echo -e "[${gl_huang}1/6${gl_bai}] PHP 소스 코드를 업로드하십시오"
+ echo -e "[${gl_huang}1/6${gl_bai}] PHP 소스 코드 업로드"
echo "-------------"
- echo "현재 Zip-Format 소스 코드 패키지 만 허용됩니다. 소스 코드 패키지를/home/web/html에 넣으십시오.${yuming}디렉토리에서"
- read -e -p "다운로드 링크를 입력하여 소스 코드 패키지를 원격으로 다운로드 할 수도 있습니다. 원격 다운로드를 건너 뛰려면 Enter를 직접 누르십시오." url_download
+ echo "현재는 zip 형식의 소스 코드 패키지만 업로드할 수 있습니다. 소스 코드 패키지를 /home/web/html/에 넣어주세요.${yuming}디렉토리 아래"
+ read -e -p "다운로드 링크를 입력하여 소스 코드 패키지를 원격으로 다운로드할 수도 있습니다. 원격 다운로드를 건너뛰려면 Enter를 직접 누르세요." url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -7815,20 +9477,20 @@ linux_ldnmp() {
rm -f $(ls -t *.zip | head -n 1)
clear
- echo -e "[${gl_huang}2/6${gl_bai}] index.php가있는 경로"
+ echo -e "[${gl_huang}2/6${gl_bai}] index.php가 위치한 경로"
echo "-------------"
# find "$(realpath .)" -name "index.php" -print
find "$(realpath .)" -name "index.php" -print | xargs -I {} dirname {}
- read -e -p "(/home/web/html/와 유사한 Index.php의 경로를 입력하십시오.$yuming/wordpress/): " index_lujing
+ read -e -p "(/home/web/html/과 유사하게 index.php 경로를 입력하세요.$yuming/wordpress/): " index_lujing
sed -i "s#root /var/www/html/$yuming/#root $index_lujing#g" /home/web/conf.d/$yuming.conf
sed -i "s#/home/web/#/var/www/#g" /home/web/conf.d/$yuming.conf
clear
- echo -e "[${gl_huang}3/6${gl_bai}] PHP 버전을 선택하십시오"
+ echo -e "[${gl_huang}3/6${gl_bai}] PHP 버전을 선택해주세요."
echo "-------------"
- read -e -p "1. PHP의 최신 버전 | 2. PHP7.4 :" pho_v
+ read -e -p "1. PHP 최신 버전 | 2.php7.4:" pho_v
case "$pho_v" in
1)
sed -i "s#php:9000#php:9000#g" /home/web/conf.d/$yuming.conf
@@ -7839,15 +9501,15 @@ linux_ldnmp() {
local PHP_Version="php74"
;;
*)
- echo "유효하지 않은 선택, 다시 입력하십시오."
+ echo "선택이 잘못되었습니다. 다시 입력해 주세요."
;;
esac
clear
- echo -e "[${gl_huang}4/6${gl_bai}] 지정된 확장자를 설치하십시오"
+ echo -e "[${gl_huang}4/6${gl_bai}] 지정된 확장 설치"
echo "-------------"
- echo "설치된 확장"
+ echo "설치된 확장 프로그램"
docker exec php php -m
read -e -p "$(echo -e "输入需要安装的扩展名称,如 ${gl_huang}SourceGuardian imap ftp${gl_bai} 等等。直接回车将跳过安装 : ")" php_extensions
@@ -7859,7 +9521,7 @@ linux_ldnmp() {
clear
echo -e "[${gl_huang}5/6${gl_bai}] 사이트 구성 편집"
echo "-------------"
- echo "계속하려면 모든 키를 누르면 의사 정적 내용 등과 같은 사이트 구성을 자세히 설정할 수 있습니다."
+ echo "계속하려면 아무 키나 누르세요. 의사정적 콘텐츠 등 사이트 구성을 세부적으로 설정할 수 있습니다."
read -n 1 -s -r -p ""
install nano
nano /home/web/conf.d/$yuming.conf
@@ -7868,14 +9530,14 @@ linux_ldnmp() {
clear
echo -e "[${gl_huang}6/6${gl_bai}] 데이터베이스 관리"
echo "-------------"
- read -e -p "1. 새 사이트를 구축합니다. 2. 이전 사이트를 작성하고 데이터베이스 백업이 있습니다." use_db
+ read -e -p "1. 새 사이트를 구축합니다. 2. 이전 사이트를 구축하고 데이터베이스 백업을 가지고 있습니다." use_db
case $use_db in
1)
echo
;;
2)
- echo "데이터베이스 백업은 .gz-end 압축 패키지 여야합니다. Pagoda/1Panel의 백업 데이터 가져 오기를 지원하려면/홈/디렉토리에 넣으십시오."
- read -e -p "다운로드 링크를 입력하여 백업 데이터를 원격으로 다운로드 할 수도 있습니다. Enter가 직접 누르면 원격 다운로드를 건너 뜁니다." url_download_db
+ echo "데이터베이스 백업은 .gz로 끝나는 압축 패키지여야 합니다. Pagoda/1panel 백업 데이터 가져오기를 지원하려면 /home/ 디렉토리에 넣으십시오."
+ read -e -p "다운로드 링크를 입력하여 원격으로 백업 데이터를 다운로드할 수도 있습니다. 원격 다운로드를 건너뛰려면 Enter를 직접 누르세요." url_download_db
cd /home/
if [ -n "$url_download_db" ]; then
@@ -7885,10 +9547,10 @@ linux_ldnmp() {
latest_sql=$(ls -t *.sql | head -n 1)
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname < "/home/$latest_sql"
- echo "데이터베이스 가져 오기 테이블 데이터"
+ echo "데이터베이스에서 가져온 테이블 데이터"
docker exec -i mysql mysql -u root -p"$dbrootpasswd" -e "USE $dbname; SHOW TABLES;"
rm -f *.sql
- echo "데이터베이스 가져 오기가 완료되었습니다"
+ echo "데이터베이스 가져오기 완료"
;;
*)
echo
@@ -7900,12 +9562,12 @@ linux_ldnmp() {
restart_ldnmp
ldnmp_web_on
prefix="web$(shuf -i 10-99 -n 1)_"
- echo "데이터베이스 주소 : MySQL"
- echo "데이터베이스 이름 :$dbname"
- echo "사용자 이름 :$dbuse"
+ echo "데이터베이스 주소: mysql"
+ echo "데이터베이스 이름:$dbname"
+ echo "사용자 이름:$dbuse"
echo "비밀번호:$dbusepasswd"
- echo "테이블 접두사 :$prefix"
- echo "관리자 로그인 정보는 직접 설정됩니다"
+ echo "테이블 접두사:$prefix"
+ echo "관리자 로그인 정보는 직접 설정합니다."
;;
@@ -7917,18 +9579,23 @@ linux_ldnmp() {
22)
clear
- webname="站点重定向"
+ webname="사이트 리디렉션"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
- read -e -p "점프 도메인 이름을 입력하십시오 :" reverseproxy
+ read -e -p "리디렉션 도메인 이름을 입력하세요." reverseproxy
nginx_install_status
+
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/rewrite.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s/baidu.com/$reverseproxy/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7943,9 +9610,10 @@ linux_ldnmp() {
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "IP+ 포트는 서비스에 액세스하는 것이 차단되었습니다"
+ echo "IP+포트가 서비스 접근을 차단했습니다."
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
@@ -7953,19 +9621,23 @@ linux_ldnmp() {
24)
clear
- webname="反向代理-域名"
+ webname="역방향 프록시 도메인 이름"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
- echo -e "도메인 이름 형식 :${gl_huang}google.com${gl_bai}"
- read -e -p "반세기 도메인 이름을 입력하십시오 :" fandai_yuming
+ echo -e "도메인 이름 형식:${gl_huang}google.com${gl_bai}"
+ read -e -p "역방향 프록시 도메인 이름을 입력하세요:" fandai_yuming
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-domain.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s|fandaicom|$fandai_yuming|g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7979,22 +9651,19 @@ linux_ldnmp() {
clear
webname="Bitwarden"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
docker run -d \
--name bitwarden \
- --restart always \
+ --restart=always \
-p 3280:80 \
-v /home/web/html/$yuming/bitwarden/data:/data \
vaultwarden/server
+
duankou=3280
- reverse_proxy
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
- nginx_web_on
;;
@@ -8002,32 +9671,31 @@ linux_ldnmp() {
clear
webname="halo"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
- docker run -d --name halo --restart always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- duankou=8010
- reverse_proxy
+ docker run -d --name halo --restart=always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- nginx_web_on
+ duankou=8010
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
;;
27)
clear
- webname="AI绘画提示词生成器"
+ webname="AI 그림 프롬프트 단어 생성기"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8050,19 +9718,26 @@ linux_ldnmp() {
;;
+ 29)
+ stream_panel
+ ;;
+
30)
clear
- webname="静态站点"
+ webname="정적 사이트"
send_stats "설치하다$webname"
- echo "배포를 시작하십시오$webname"
+ echo "배포 시작$webname"
add_yuming
repeat_add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8071,10 +9746,10 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}1/2${gl_bai}] 정적 소스 코드를 업로드하십시오"
+ echo -e "[${gl_huang}1/2${gl_bai}] 정적 소스 코드 업로드"
echo "-------------"
- echo "현재 Zip-Format 소스 코드 패키지 만 허용됩니다. 소스 코드 패키지를/home/web/html에 넣으십시오.${yuming}디렉토리에서"
- read -e -p "다운로드 링크를 입력하여 소스 코드 패키지를 원격으로 다운로드 할 수도 있습니다. 원격 다운로드를 건너 뛰려면 Enter를 직접 누르십시오." url_download
+ echo "현재는 zip 형식의 소스 코드 패키지만 업로드할 수 있습니다. 소스 코드 패키지를 /home/web/html/에 넣어주세요.${yuming}디렉토리 아래"
+ read -e -p "다운로드 링크를 입력하여 소스 코드 패키지를 원격으로 다운로드할 수도 있습니다. 원격 다운로드를 건너뛰려면 Enter를 직접 누르세요." url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -8084,12 +9759,12 @@ linux_ldnmp() {
rm -f $(ls -t *.zip | head -n 1)
clear
- echo -e "[${gl_huang}2/2${gl_bai}] index.html이있는 경로"
+ echo -e "[${gl_huang}2/2${gl_bai}] index.html이 위치한 경로"
echo "-------------"
# find "$(realpath .)" -name "index.html" -print
find "$(realpath .)" -name "index.html" -print | xargs -I {} dirname {}
- read -e -p "(/home/web/html/와 유사한 index.html로가는 경로를 입력하십시오.$yuming/index/): " index_lujing
+ read -e -p "(/home/web/html/과 유사하게 index.html 경로를 입력하세요.$yuming/index/): " index_lujing
sed -i "s#root /var/www/html/$yuming/#root $index_lujing#g" /home/web/conf.d/$yuming.conf
sed -i "s#/home/web/#/var/www/#g" /home/web/conf.d/$yuming.conf
@@ -8117,28 +9792,26 @@ linux_ldnmp() {
send_stats "LDNMP 환경 백업"
local backup_filename="web_$(date +"%Y%m%d%H%M%S").tar.gz"
- echo -e "${gl_huang}백업$backup_filename ...${gl_bai}"
+ echo -e "${gl_kjlan}백업 중$backup_filename ...${gl_bai}"
cd /home/ && tar czvf "$backup_filename" web
while true; do
clear
- echo "백업 파일이 작성되었습니다 : /home /$backup_filename"
- read -e -p "백업 데이터를 원격 서버로 전송 하시겠습니까? (Y/N) :" choice
+ echo "생성된 백업 파일: /home/$backup_filename"
+ read -e -p "백업 데이터를 원격 서버로 전송하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
- read -e -p "원격 서버 IP를 입력하십시오 :" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "오류 : 원격 서버 IP를 입력하십시오."
- continue
- fi
+ kj_ssh_read_host_port "원격 서버 IP를 입력하세요:" "대상 서버 SSH 포트 [기본값 22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
local latest_tar=$(ls -t /home/*.tar.gz | head -1)
if [ -n "$latest_tar" ]; then
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 添加等待时间
- scp -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
- echo "파일은 원격 서버 홈 디렉토리로 전송되었습니다."
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
+ echo "파일이 원격 서버 홈 디렉터리로 전송되었습니다."
else
- echo "전송할 파일은 찾을 수 없었습니다."
+ echo "전송할 파일을 찾을 수 없습니다."
fi
break
;;
@@ -8146,7 +9819,7 @@ linux_ldnmp() {
break
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
done
@@ -8154,9 +9827,9 @@ linux_ldnmp() {
33)
clear
- send_stats "시간이 지정된 원격 백업"
- read -e -p "원격 서버 IP를 입력하십시오." useip
- read -e -p "원격 서버 비밀번호를 입력하십시오." usepasswd
+ send_stats "예약된 원격 백업"
+ read -e -p "원격 서버 IP를 입력하세요." useip
+ read -e -p "원격 서버 비밀번호를 입력하세요:" usepasswd
cd ~
wget -O ${useip}_beifen.sh ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/beifen.sh > /dev/null 2>&1
@@ -8166,18 +9839,18 @@ linux_ldnmp() {
sed -i "s/123456/$usepasswd/g" ${useip}_beifen.sh
echo "------------------------"
- echo "1. 주간 백업 2. 매일 백업"
- read -e -p "선택을 입력하십시오 :" dingshi
+ echo "1. 주간 백업 2. 일일 백업"
+ read -e -p "선택사항을 입력하세요:" dingshi
case $dingshi in
1)
check_crontab_installed
- read -e -p "주간 백업의 요일을 선택하십시오 (0-6, 0은 일요일을 나타냅니다) :" weekday
+ read -e -p "주간 백업 요일을 선택합니다(0-6, 0은 일요일을 나타냄)." weekday
(crontab -l ; echo "0 0 * * $weekday ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
2)
check_crontab_installed
- read -e -p "매일 백업 시간을 선택하십시오 (시간, 0-23) :" hour
+ read -e -p "일일 백업 시간 선택(시간, 0-23):" hour
(crontab -l ; echo "0 $hour * * * ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
*)
@@ -8196,14 +9869,14 @@ linux_ldnmp() {
echo "-------------------------"
ls -lt /home/*.gz | awk '{print $NF}'
echo ""
- read -e -p "최신 백업을 복원하려면 입력하고 백업 파일 이름을 입력하여 지정된 백업을 복원하고 0을 입력하려면 다음을 종료하십시오." filename
+ read -e -p "최신 백업을 복원하려면 Enter 키를 누르고, 지정된 백업을 복원하려면 백업 파일 이름을 입력하고, 종료하려면 0을 입력하세요." filename
if [ "$filename" == "0" ]; then
break_end
linux_ldnmp
fi
- # 사용자가 파일 이름을 입력하지 않으면 최신 압축 패키지를 사용하십시오.
+ # 사용자가 파일명을 입력하지 않으면 최신 압축 패키지가 사용됩니다.
if [ -z "$filename" ]; then
local filename=$(ls -t /home/*.tar.gz | head -1)
fi
@@ -8213,16 +9886,15 @@ linux_ldnmp() {
docker compose down > /dev/null 2>&1
rm -rf /home/web > /dev/null 2>&1
- echo -e "${gl_huang}감압이 수행되고 있습니다$filename ...${gl_bai}"
+ echo -e "${gl_kjlan}압축 해제 중$filename ...${gl_bai}"
cd /home/ && tar -xzf "$filename"
- check_port
install_dependency
install_docker
install_certbot
install_ldnmp
else
- echo "압축 패키지가 발견되지 않았습니다."
+ echo "압축된 패키지를 찾을 수 없습니다."
fi
;;
@@ -8240,11 +9912,11 @@ linux_ldnmp() {
root_use
while true; do
clear
- send_stats "LDNMP 환경을 업데이트하십시오"
- echo "LDNMP 환경을 업데이트하십시오"
+ send_stats "LDNMP 환경 업데이트"
+ echo "LDNMP 환경 업데이트"
echo "------------------------"
ldnmp_v
- echo "구성 요소의 새 버전을 발견하십시오"
+ echo "새 버전의 구성 요소가 발견되었습니다."
echo "------------------------"
check_docker_image_update nginx
if [ -n "$update_status" ]; then
@@ -8264,13 +9936,13 @@ linux_ldnmp() {
fi
echo "------------------------"
echo
- echo "1. Nginx 업데이트 2. MySQL 업데이트 3. PHP 업데이트 4. Redis 업데이트"
+ echo "1. nginx 업데이트 2. mysql 업데이트 3. PHP 업데이트 4. redis 업데이트"
echo "------------------------"
- echo "5. 전체 환경을 업데이트하십시오"
+ echo "5. 전체 환경 업데이트"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
nginx_upgrade
@@ -8279,7 +9951,7 @@ linux_ldnmp() {
2)
local ldnmp_pods="mysql"
- read -e -p "입력하십시오${ldnmp_pods}버전 번호 (예 : 8.0 8.3 8.4 9.0) (최신 버전을 얻으려면 입력) :" version
+ read -e -p "입력해주세요${ldnmp_pods}버전 번호(예: 8.0 8.3 8.4 9.0)(최신 버전을 얻으려면 Enter 키를 누르세요):" version
local version=${version:-latest}
cd /home/web/
@@ -8296,7 +9968,7 @@ linux_ldnmp() {
;;
3)
local ldnmp_pods="php"
- read -e -p "입력하십시오${ldnmp_pods}버전 번호 (예 : 7.4 8.0 8.1 8.2 8.3) (최신 버전을 얻으려면 입력) :" version
+ read -e -p "입력해주세요${ldnmp_pods}버전 번호(예: 7.4 8.0 8.1 8.2 8.3)(최신 버전을 얻으려면 Enter 키를 누르세요):" version
local version=${version:-8.3}
cd /home/web/
cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
@@ -8340,7 +10012,6 @@ linux_ldnmp() {
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods > /dev/null 2>&1
- restart_redis
send_stats "고쳐 쓰다$ldnmp_pods"
echo "고쳐 쓰다${ldnmp_pods}마치다"
@@ -8349,11 +10020,10 @@ linux_ldnmp() {
read -e -p "$(echo -e "${gl_huang}提示: ${gl_bai}长时间不更新环境的用户,请慎重更新LDNMP环境,会有数据库更新失败的风险。确定更新LDNMP环境吗?(Y/N): ")" choice
case "$choice" in
[Yy])
- send_stats "LDNMP 환경을 완전히 업데이트하십시오"
+ send_stats "LDNMP 환경 전체 업데이트"
cd /home/web/
docker compose down --rmi all
- check_port
install_dependency
install_docker
install_certbot
@@ -8375,7 +10045,7 @@ linux_ldnmp() {
38)
root_use
- send_stats "LDNMP 환경을 제거하십시오"
+ send_stats "LDNMP 환경 제거"
read -e -p "$(echo -e "${gl_hong}强烈建议:${gl_bai}先备份全部网站数据,再卸载LDNMP环境。确定删除所有网站数据吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -8389,7 +10059,7 @@ linux_ldnmp() {
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -8399,7 +10069,7 @@ linux_ldnmp() {
;;
*)
- echo "잘못된 입력!"
+ echo "입력이 잘못되었습니다!"
esac
break_end
@@ -8409,2266 +10079,9413 @@ linux_ldnmp() {
-linux_panel() {
- while true; do
- clear
- # Send_stats "앱 시장"
- echo -e "응용 프로그램 시장"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}Baota 패널의 공식 버전${gl_kjlan}2. ${gl_bai}Aapanel International Edition"
- echo -e "${gl_kjlan}3. ${gl_bai}1 파넬 신세대 관리 패널${gl_kjlan}4. ${gl_bai}nginxproxymanager 시각적 패널"
- echo -e "${gl_kjlan}5. ${gl_bai}OpenList 멀티 스토어 파일 목록 프로그램${gl_kjlan}6. ${gl_bai}우분투 원격 데스크탑 웹 에디션"
- echo -e "${gl_kjlan}7. ${gl_bai}Nezha 프로브 VPS 모니터링 패널${gl_kjlan}8. ${gl_bai}QB 오프라인 BT 자기 다운로드 패널"
- echo -e "${gl_kjlan}9. ${gl_bai}Poste.io 메일 서버 프로그램${gl_kjlan}10. ${gl_bai}Rocketchat 멀티 플레이어 온라인 채팅 시스템"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}Zendao 프로젝트 관리 소프트웨어${gl_kjlan}12. ${gl_bai}Qinglong 패널 시간 작업 관리 플랫폼"
- echo -e "${gl_kjlan}13. ${gl_bai}CloudReve 네트워크 디스크${gl_huang}★${gl_bai} ${gl_kjlan}14. ${gl_bai}간단한 그림 침대 그림 관리 프로그램"
- echo -e "${gl_kjlan}15. ${gl_bai}EMBY 멀티미디어 관리 시스템${gl_kjlan}16. ${gl_bai}스피드 테스트 속도 테스트 패널"
- echo -e "${gl_kjlan}17. ${gl_bai}Adguardhome Adware${gl_kjlan}18. ${gl_bai}Office Office Online Office Office"
- echo -e "${gl_kjlan}19. ${gl_bai}썬더 풀 WAF 방화벽 패널${gl_kjlan}20. ${gl_bai}Portainer 컨테이너 관리 패널"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}VSCODE 웹 버전${gl_kjlan}22. ${gl_bai}Uptimekuma 모니터링 도구"
- echo -e "${gl_kjlan}23. ${gl_bai}메모 웹 페이지 메모${gl_kjlan}24. ${gl_bai}WebTop 원격 데스크탑 웹 에디션${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}25. ${gl_bai}NextCloud 네트워크 디스크${gl_kjlan}26. ${gl_bai}QD-Today 타이밍 작업 관리 프레임 워크"
- echo -e "${gl_kjlan}27. ${gl_bai}도크 컨테이너 스택 관리 패널${gl_kjlan}28. ${gl_bai}Librespeed 속도 테스트 도구"
- echo -e "${gl_kjlan}29. ${gl_bai}searxng 집계 검색 사이트${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}Photoprism 개인 앨범 시스템"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}Stirlingpdf 도구 컬렉션${gl_kjlan}32. ${gl_bai}Drawio 무료 온라인 차트 소프트웨어${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}썬 패널 탐색 패널${gl_kjlan}34. ${gl_bai}Pingvin-Share 파일 공유 플랫폼"
- echo -e "${gl_kjlan}35. ${gl_bai}미니멀리스트 친구들${gl_kjlan}36. ${gl_bai}LobeChatai 채팅 집계 웹 사이트"
- echo -e "${gl_kjlan}37. ${gl_bai}MYIP 도구 상자${gl_huang}★${gl_bai} ${gl_kjlan}38. ${gl_bai}Xiaoya Alist 가족 버킷"
- echo -e "${gl_kjlan}39. ${gl_bai}Bilililive 라이브 방송 녹음 도구${gl_kjlan}40. ${gl_bai}Websh 웹 버전 SSH 연결 도구"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}마우스 관리 패널${gl_kjlan}42. ${gl_bai}Nexte 원격 연결 도구"
- echo -e "${gl_kjlan}43. ${gl_bai}Rustdesk 원격 책상 (서버)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${gl_bai}Rustdesk 원격 책상 (릴레이)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}45. ${gl_bai}도커 가속 스테이션${gl_kjlan}46. ${gl_bai}Github Acceleration Station${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}47. ${gl_bai}프로 메테우스 모니터링${gl_kjlan}48. ${gl_bai}프로 메테우스 (호스트 모니터링)"
- echo -e "${gl_kjlan}49. ${gl_bai}Prometheus (컨테이너 모니터링)${gl_kjlan}50. ${gl_bai}보충 모니터링 도구"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}51. ${gl_bai}PVE 치킨 패널${gl_kjlan}52. ${gl_bai}DPANEL 컨테이너 관리 패널"
- echo -e "${gl_kjlan}53. ${gl_bai}llama3 채팅 AI 모델${gl_kjlan}54. ${gl_bai}AMH 호스트 웹 사이트 빌딩 관리 패널"
- echo -e "${gl_kjlan}55. ${gl_bai}FRP 인트라넷 침투 (서버 측)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${gl_bai}FRP 인트라넷 침투 (클라이언트)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}57. ${gl_bai}DeepSeek 채팅 AI 큰 모델${gl_kjlan}58. ${gl_bai}Dify Big Model 지식 기반${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}59. ${gl_bai}Newapi 큰 모델 자산 관리${gl_kjlan}60. ${gl_bai}점프 서버 오픈 소스 요새 기계"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}61. ${gl_bai}온라인 번역 서버${gl_kjlan}62. ${gl_bai}래그 플로 큰 모델 지식 기반"
- echo -e "${gl_kjlan}63. ${gl_bai}OpenWebui 자체 호스팅 AI 플랫폼${gl_huang}★${gl_bai} ${gl_kjlan}64. ${gl_bai}it-tools 도구 상자"
- echo -e "${gl_kjlan}65. ${gl_bai}N8N 자동화 워크 플로 플랫폼${gl_huang}★${gl_bai} ${gl_kjlan}66. ${gl_bai}YT-DLP 비디오 다운로드 도구"
- echo -e "${gl_kjlan}67. ${gl_bai}DDNS-GO 동적 DNS 관리 도구${gl_huang}★${gl_bai} ${gl_kjlan}68. ${gl_bai}AllInsSL 인증서 관리 플랫폼"
- echo -e "${gl_kjlan}69. ${gl_bai}sftpgo 파일 전송 도구${gl_kjlan}70. ${gl_bai}Astrbot 채팅 로봇 프레임 워크"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}71. ${gl_bai}Navidrome 개인 음악 서버${gl_kjlan}72. ${gl_bai}Bitwarden 비밀번호 관리자${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}73. ${gl_bai}Libretv 개인 영화 및 텔레비전${gl_kjlan}74. ${gl_bai}Moontv 개인 영화"
- echo -e "${gl_kjlan}75. ${gl_bai}멜로디 음악 엘프${gl_kjlan}76. ${gl_bai}온라인 dos 오래된 게임"
- echo -e "${gl_kjlan}77. ${gl_bai}천둥 오프라인 다운로드 도구${gl_kjlan}78. ${gl_bai}Pandawiki 지능형 문서 관리 시스템"
- echo -e "${gl_kjlan}79. ${gl_bai}Beszel 서버 모니터링"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
- case $sub_choice in
- 1)
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="宝塔面板"
- local panelurl="https://www.bt.cn/new/index.html"
+moltbot_menu() {
+ local app_id="114"
- panel_app_install() {
- if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
- }
+ send_stats "클로드봇/몰트봇 관리"
- panel_app_manage() {
- bt
- }
+ check_openclaw_update() {
+ if ! command -v npm >/dev/null 2>&1; then
+ return 1
+ fi
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ # --no-update-notifier를 추가하고 오류 리디렉션이 올바른 위치에 있는지 확인하세요.
+ local_version=$(npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null | grep openclaw | awk '{print $NF}' | sed 's/^.*@//')
- install_panel
+ if [ -z "$local_version" ]; then
+ return 1
+ fi
+ remote_version=$(npm view openclaw version --no-update-notifier 2>/dev/null)
+ if [ -z "$remote_version" ]; then
+ return 1
+ fi
- ;;
- 2)
-
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="aapanel"
- local panelurl="https://www.aapanel.com/new/index.html"
-
- panel_app_install() {
- URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
- }
-
- panel_app_manage() {
- bt
- }
-
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
-
- install_panel
-
- ;;
- 3)
-
- local lujing="command -v 1pctl"
- local panelname="1Panel"
- local panelurl="https://1panel.cn/"
+ if [ "$local_version" != "$remote_version" ]; then
+ echo "${gl_huang}새 버전이 감지되었습니다.$remote_version${gl_bai}"
+ else
+ echo "${gl_lv}현재 버전이 최신 버전입니다.$local_version${gl_bai}"
+ fi
+ }
- panel_app_install() {
- install bash
- bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
- }
- panel_app_manage() {
- 1pctl user-info
- 1pctl update password
- }
+ get_install_status() {
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "${gl_lv}설치됨${gl_bai}"
+ else
+ echo "${gl_hui}설치되지 않음${gl_bai}"
+ fi
+ }
- panel_app_uninstall() {
- 1pctl uninstall
- }
+ get_running_status() {
+ if pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
+ echo "${gl_lv}달리기${gl_bai}"
+ else
+ echo "${gl_hui}실행되지 않음${gl_bai}"
+ fi
+ }
- install_panel
- ;;
- 4)
+ show_menu() {
- local docker_name="npm"
- local docker_img="jc21/nginx-proxy-manager:latest"
- local docker_port=81
- docker_rum() {
+ clear
- docker run -d \
- --name=$docker_name \
- -p ${docker_port}:81 \
- -p 80:80 \
- -p 443:443 \
- -v /home/docker/npm/data:/data \
- -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
- --restart=always \
- $docker_img
+ local install_status=$(get_install_status)
+ local running_status=$(get_running_status)
+ local update_message=$(check_openclaw_update)
+
+ echo "======================================="
+ echo -e "🦞 KEJILION의 OPENCLAW 관리 도구 🦞"
+ echo -e "💡 터미널 실행 \033[1;33mk 클로\033[0m 빠르게 메뉴 진입"
+ echo -e "$install_status $running_status $update_message"
+ echo "======================================="
+ echo "1. 설치"
+ echo "2. 시작"
+ echo "3. 중지"
+ echo "--------------------"
+ echo "4. 상태 로그 보기"
+ echo "5. 모델 변경"
+ echo "6. API 관리"
+ echo "7. 로봇 연결 및 도킹"
+ echo "8. 플러그인 관리(설치/제거)"
+ echo "9. 스킬 관리(설치/제거)"
+ echo "10. 기본 구성 파일 편집"
+ echo "11. 구성 마법사"
+ echo "12. 건강 상태 감지 및 복구"
+ echo "13. WebUI 접속 및 설정"
+ echo "14. TUI 명령줄 대화 상자 창"
+ echo "15. 기억/기억"
+ echo "16. 권한 관리"
+ echo "17. 다중 에이전트 관리"
+ echo "--------------------"
+ echo "18. 백업 및 복원"
+ echo "19. 업데이트"
+ echo "20. 제거"
+ echo "--------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "--------------------"
+ printf "옵션을 입력하고 Enter를 누르십시오."
+ }
- }
+ start_gateway() {
+ openclaw gateway stop
+ openclaw gateway start
+ sleep 3
+ }
- local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
- local docker_url="官网介绍: https://nginxproxymanager.com/"
- local docker_use="echo \"初始用户名: admin@example.com\""
- local docker_passwd="echo \"初始密码: changeme\""
- local app_size="1"
- docker_app
+ install_node_and_tools() {
+ if command -v dnf &>/dev/null; then
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
+ dnf update -y
+ dnf group install -y "Development Tools" "Development Libraries"
+ dnf install -y cmake libatomic nodejs
+ fi
- ;;
+ if command -v apt &>/dev/null; then
+ curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
+ apt update -y
+ apt install build-essential python3 libatomic1 nodejs -y
+ fi
+ }
- 5)
+ sync_openclaw_api_models() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ [ ! -f "$config_file" ] && return 0
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$ENABLE_STATS" "$sh_v" <<'PY'
+import copy
+import json
+import os
+import platform
+import sys
+import time
+import urllib.request
+from datetime import datetime, timezone
+
+path = sys.argv[1]
+stats_enabled = (sys.argv[2].lower() == "true") if len(sys.argv) > 2 else True
+script_version = sys.argv[3] if len(sys.argv) > 3 else ""
+
+def send_stat(action):
+ if not stats_enabled:
+ return
+ payload = {
+ "action": action,
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
+ "country": "",
+ "os_info": platform.platform(),
+ "cpu_arch": platform.machine(),
+ "version": script_version,
+ }
+ try:
+ req = urllib.request.Request(
+ "https://api.kejilion.pro/api/log",
+ data=json.dumps(payload).encode("utf-8"),
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=3):
+ pass
+ except Exception:
+ pass
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('ℹ️ API 제공업체가 감지되지 않음, 모델 동기화 건너뛰기')
+ raise SystemExit(0)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+changed = False
+fatal_errors = []
+summary = []
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+def prompt_delete_provider(name):
+ prompt = f"⚠️ {name} /models 프로브가 3번 연속 실패했습니다. 이 API 제공업체 및 모든 관련 모델을 삭제하시겠습니까? [예/아니요]:"
+ try:
+ ans = input(prompt).strip().lower()
+ except EOFError:
+ return False
+ return ans in ('y', 'yes')
+
+
+def rebind_defaults_before_delete(name):
+ global changed
+
+ replacement = None
+
+ def get_replacement():
+ nonlocal replacement
+ if replacement is None:
+ candidates = collect_available_refs(exclude_provider=name)
+ replacement = candidates[0] if candidates else None
+ return replacement
+
+ primary_ref = get_primary_ref(defaults)
+ if ref_provider(primary_ref) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: 기본 기본 모델이 이 공급자를 가리키지만 사용 가능한 대체 모델이 없어 삭제가 중단되었습니다.')
+ return False
+ set_primary_ref(defaults, repl)
+ changed = True
+ summary.append(f'🔁 삭제 전에 기본 기본 모델이 전환되었습니다: {primary_ref} -> {repl}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: {fk}가 공급자를 가리키지만 대체 모델을 사용할 수 없어 삭제가 중단되었습니다.')
+ return False
+ defaults[fk] = repl
+ changed = True
+ summary.append(f'🔁 삭제 전 전환됨 {fk}: {val} -> {repl}')
+
+ return True
+
+
+def delete_provider_and_refs(name):
+ global changed
+
+ if not rebind_defaults_before_delete(name):
+ return False
+
+ removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+ for r in removed_refs:
+ defaults_models.pop(r, None)
+ if removed_refs:
+ changed = True
+
+ if name in providers:
+ providers.pop(name, None)
+ changed = True
+
+ summary.append(f'🗑️ 제공자 {name}이(가) 삭제되었으며 defaults.models 아래의 {len(removed_refs)} 모델 참조가 제거되었습니다.')
+ return True
+
+
+def fetch_remote_models_with_retry(name, base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ data = json.loads(payload)
+ return data, None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+for name, provider in list(providers.items()):
+ if not isinstance(provider, dict):
+ summary.append(f'ℹ️ {name} 건너뛰기: 공급자 구조가 불법입니다.')
+ continue
+
+ api = provider.get('api', '')
+ base_url = provider.get('baseUrl')
+ api_key = provider.get('apiKey')
+ model_list = provider.get('models', [])
+
+ if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ summary.append(f'ℹ️ {name} 건너뛰기: 없음 baseUrl/apiKey/models')
+ continue
+
+ if api not in SUPPORTED_APIS:
+ summary.append(f'🔁 {name}: 잘못된 프로토콜 {api 또는 "(unset)"}이 발견되어 다시 감지됩니다.')
+ provider['api'] = ''
+ api = ''
+ changed = True
+
+ data, err, attempts = fetch_remote_models_with_retry(name, base_url, api_key, retries=3)
+ if err is not None:
+ summary.append(f'⚠️ {name}: /models 감지 실패, {attempts} 번 재시도됨 ({type(err).__name__}: {err})')
+ send_stat('OpenClaw API 확인 개입')
+ if prompt_delete_provider(name):
+ deleted = delete_provider_and_refs(name)
+ if deleted:
+ send_stat('OpenClaw API 삭제 실패 공급자 확인')
+ summary.append(f'✅ {name}: 사용자가 공급자 및 모든 관련 모델 참조 삭제를 확인했습니다.')
+ else:
+ send_stat('OpenClaw API 삭제 실패 공급자 거부')
+ summary.append(f'ℹ️ {name}: 사용자가 삭제를 확인하지 않았으며 기존 공급자 구성을 유지합니다.')
+ continue
+
+ if attempts > 1:
+ summary.append(f'🔁 {name}: /models {시도}번 재시도 후 성공')
+
+ if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ summary.append(f'⚠️ {name} 건너뛰기: /models 반환 구조가 인식되지 않습니다.')
+ continue
+
+ remote_ids = []
+ for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+ remote_set = set(remote_ids)
+
+ if not remote_set:
+ fatal_errors.append(f'❌ {name}의 업스트림 /models가 비어 있으며 이 공급자에 대한 상향식 모델을 제공할 수 없습니다.')
+ continue
+
+ local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+ local_ids = [str(m['id']) for m in local_models]
+ local_set = set(local_ids)
+
+ template = None
+ for m in local_models:
+ template = copy.deepcopy(m)
+ break
+ if template is None:
+ summary.append(f'⚠️ {name} 건너뛰기: 로컬 모델에 유효한 템플릿 모델이 없습니다.')
+ continue
+
+ removed_ids = [mid for mid in local_ids if mid not in remote_set]
+ added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+ kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+ new_models = kept_models[:]
+
+ for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{name} / {mid}'
+ new_models.append(nm)
+
+ if not new_models:
+ fatal_errors.append(f'❌ {name}은(는) 동기화 후 사용 가능한 모델이 없으며 기본 모델/폴백 모델을 보장할 수 없습니다.')
+ continue
+
+ expected_refs = {model_ref(name, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+ local_refs = {model_ref(name, mid) for mid in local_ids}
+
+ first_ref = model_ref(name, str(new_models[0]['id']))
+
+ primary_ref = get_primary_ref(defaults)
+ if isinstance(primary_ref, str) and primary_ref in (local_refs - expected_refs):
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ summary.append(f'🔁 기본 모델이 완전히 교체되었습니다: {primary_ref} -> {first_ref}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in (local_refs - expected_refs):
+ defaults[fk] = first_ref
+ changed = True
+ summary.append(f'🔁 {fk}가 완전히 대체되었습니다: {val} -> {first_ref}')
+
+ stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/') and r not in expected_refs]
+ for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+ for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+ if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+ summary.append(f'✅ {name}: {len(add_ids)} 추가됨, 삭제됨 {len(removed_ids)}, 현재 {len(new_models)}')
+
+ if added_ids:
+ summary.append(f'➕ 새 모델 추가({len(add_ids)}):')
+ for mid in added_ids:
+ summary.append(f' + {mid}')
+ if removed_ids:
+ summary.append(f'➖ 모델 삭제({len(removed_ids)}):')
+ for mid in removed_ids:
+ summary.append(f' - {mid}')
+
+
+if fatal_errors:
+ for line in summary:
+ print(line)
+ for err in fatal_errors:
+ print(err)
+ print('❌ 모델 동기화 실패: 공급자가 있습니다. 동기화 후 사용 가능한 모델이 없으며 쓰기가 중단되었습니다.')
+ raise SystemExit(2)
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+ for line in summary:
+ print(line)
+ print('✅ OpenClaw API 모델 일관성 동기화가 완료되고 구성이 작성되었습니다.')
+else:
+ for line in summary:
+ print(line)
+ print('ℹ️ 동기화가 필요하지 않습니다. 구성이 이미 업스트림/모델과 일치합니다.')
+PY
+ }
- local docker_name="openlist"
- local docker_img="openlistteam/openlist:latest-aria2"
- local docker_port=5244
- docker_rum() {
- docker run -d \
- --restart=always \
- -v /home/docker/openlist:/opt/openlist/data \
- -p ${docker_port}:5244 \
- -e PUID=0 \
- -e PGID=0 \
- -e UMASK=022 \
- --name="openlist" \
- openlistteam/openlist:latest-aria2
+ install_moltbot() {
+ echo "OpenClaw 설치를 시작합니다..."
+ send_stats "OpenClaw 설치를 시작합니다..."
+ install git jq
- }
+ install_node_and_tools
+ country=$(curl -s ipinfo.io/country)
+ if [[ "$country" == "CN" || "$country" == "HK" ]]; then
+ npm config set registry https://registry.npmmirror.com
+ fi
- local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
- local docker_url="官网介绍: https://github.com/OpenListTeam/OpenList"
- local docker_use="docker exec -it openlist ./openlist admin random"
- local docker_passwd=""
- local app_size="1"
- docker_app
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
- ;;
+ npm install -g openclaw@latest
+ openclaw onboard --install-daemon
+ start_gateway
+ add_app_id
+ break_end
- 6)
+ }
- local docker_name="webtop-ubuntu"
- local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
- local docker_port=3006
- docker_rum() {
+ start_bot() {
+ echo "OpenClaw를 시작하는 중..."
+ send_stats "OpenClaw를 시작하는 중..."
+ start_gateway
+ break_end
+ }
+ stop_bot() {
+ echo "OpenClaw를 중지하세요..."
+ send_stats "OpenClaw를 중지하세요..."
+ tmux kill-session -t gateway > /dev/null 2>&1
+ openclaw gateway stop
+ break_end
+ }
- docker run -d \
- --name=webtop-ubuntu \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=ubuntu-abc \
- -e PASSWORD=ubuntuABC123 \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:ubuntu-kde
+ view_logs() {
+ echo "OpenClaw 상태 로그 보기"
+ send_stats "OpenClaw 로그 보기"
+ openclaw status
+ openclaw gateway status
+ openclaw logs
+ break_end
+ }
- }
- local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: ubuntu-abc\""
- local docker_passwd="echo \"密码: ubuntuABC123\""
- local app_size="2"
- docker_app
+ # OpenClaw API 프로토콜 감지 논리가 제거되었습니다. API 유형은 더 이상 자동으로 감지/결정되지 않습니다.
+ # 참고: API 유형은 사용자(models.providers..api)에 의해 명시적으로 구성되며 스크립트는 더 이상 추론을 위해 /responses를 호출하려고 시도하지 않습니다.
- ;;
- 7)
- clear
- send_stats "Nezha를 구축하십시오"
- local docker_name="nezha-dashboard"
- local docker_port=8008
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
- clear
- echo -e "Nezha 모니터링$check_docker $update_status"
- echo "오픈 소스, 가볍고 사용하기 쉬운 서버 모니터링 및 작동 및 유지 보수 도구"
- echo "공식 웹 사이트 구성 문서 : https://nezha.wiki/guide/dashboard.html"
- if docker inspect "$docker_name" &>/dev/null; then
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. 사용"
- echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
- echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ # 모델 구성 JSON 구성
+ build-openclaw-provider-models-json() {
+ local provider_name="$1"
+ local model_ids="$2"
+ local models_array="["
+ local first=true
- case $choice in
- 1)
- check_disk_space 1
- install unzip jq
- install_docker
- curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- ;;
+ while read -r model_id; do
+ [ -z "$model_id" ] && continue
+ [[ $first == false ]] && models_array+=","
+ first=false
- *)
- break
- ;;
+ local context_window=1048576
+ local max_tokens=128000
+ local input_cost=0.15
+ local output_cost=0.60
- esac
- break_end
- done
- ;;
+ case "$model_id" in
+ *opus*|*pro*|*preview*|*thinking*|*sonnet*)
+ input_cost=2.00
+ output_cost=12.00
+ ;;
+ *gpt-5*|*codex*)
+ input_cost=1.25
+ output_cost=10.00
+ ;;
+ *flash*|*lite*|*haiku*|*mini*|*nano*)
+ input_cost=0.10
+ output_cost=0.40
+ ;;
+ esac
- 8)
+ models_array+=$(cat < 0 then
+ .["\($prov)/\($mid)"] //= {}
else
- echo -e "${gl_hong}포트$port현재 사용할 수 없습니다${gl_bai}"
- fi
- echo ""
-
- if docker inspect "$docker_name" &>/dev/null; then
- yuming=$(cat /home/docker/mail.txt)
- echo "액세스 주소 :"
- echo "https://$yuming"
- fi
-
- echo "------------------------"
- echo "1. 설치 2. 업데이트 3. 제거"
- echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
- echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ .
+ end
+ )
+ )
+ ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
+ }
- case $choice in
- 1)
- check_disk_space 2
- read -e -p "이메일 도메인 이름 (예 : Mail.yuming.com)을 설정하십시오." yuming
- mkdir -p /home/docker
- echo "$yuming" > /home/docker/mail.txt
- echo "------------------------"
- ip_address
- echo "이 DNS 레코드를 먼저 구문 분석하십시오"
- echo "A mail $ipv4_address"
- echo "CNAME imap $yuming"
- echo "CNAME pop $yuming"
- echo "CNAME smtp $yuming"
- echo "MX @ $yuming"
- echo "TXT @ v=spf1 mx ~all"
- echo "TXT ? ?"
- echo ""
- echo "------------------------"
- echo "계속하려면 키를 누르십시오 ..."
- read -n 1 -s -r -p ""
+ # 핵심 기능: 모든 모델 가져오기 및 추가
+ add-all-models-from-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
- install jq
- install_docker
+ echo "🔍 받기$provider_name사용 가능한 모든 모델..."
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.io
+ local models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
- clear
- echo "Poste.io가 설치되었습니다"
- echo "------------------------"
- echo "다음 주소를 사용하여 Poste.io에 액세스 할 수 있습니다."
- echo "https://$yuming"
- echo ""
+ if [[ -z "$models_json" ]]; then
+ echo "❌ 모델 목록을 가져올 수 없습니다"
+ return 1
+ fi
- ;;
+ local model_ids=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+')
- 2)
- docker rm -f mailserver
- docker rmi -f analogic/poste.i
- yuming=$(cat /home/docker/mail.txt)
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.i
- clear
- echo "Poste.io가 설치되었습니다"
- echo "------------------------"
- echo "다음 주소를 사용하여 Poste.io에 액세스 할 수 있습니다."
- echo "https://$yuming"
- echo ""
- ;;
- 3)
- docker rm -f mailserver
- docker rmi -f analogic/poste.io
- rm /home/docker/mail.txt
- rm -rf /home/docker/mail
- echo "앱이 제거되었습니다"
- ;;
+ if [[ -z "$model_ids" ]]; then
+ echo "❌ 모델을 찾을 수 없습니다"
+ return 1
+ fi
- *)
- break
- ;;
+ local model_count=$(echo "$model_ids" | wc -l)
+ echo "✅ 발견$model_count모델"
- esac
- break_end
- done
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$model_ids")
- ;;
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
- 10)
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 성공적으로 추가되었습니다$model_count모델 도착$provider_name"
+ echo "📦 모델 참조 형식:$provider_name/"
+ return 0
+ else
+ echo "❌ 구성 삽입 실패"
+ return 1
+ fi
+ }
- local app_name="Rocket.Chat聊天系统"
- local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
- local app_url="官方介绍: https://www.rocket.chat/"
- local docker_name="rocketchat"
- local docker_port="3897"
- local app_size="2"
-
- docker_app_install() {
- docker run --name db -d --restart=always \
- -v /home/docker/mongo/dump:/dump \
- mongo:latest --replSet rs5 --oplogSize 256
- sleep 1
- docker exec -it db mongosh --eval "printjson(rs.initiate())"
- sleep 5
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ # 기본 모델만 추가하고 공급자는 유지
+ add-default-model-only-to-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+ local default_model="$4"
- clear
- ip_address
- echo "설치"
- check_docker_app_ip
- }
+ if [[ -z "$default_model" ]]; then
+ echo "❌ 기본 모델은 비워둘 수 없습니다."
+ return 1
+ fi
- docker_app_update() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat:latest
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "Rocket.chat이 설치되었습니다"
- check_docker_app_ip
- }
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$default_model")
- docker_app_uninstall() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat
- docker rm -f db
- docker rmi -f mongo:latest
- rm -rf /home/docker/mongo
- echo "앱이 제거되었습니다"
- }
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
- docker_app_plus
- ;;
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 제공업체 추가됨:$provider_name"
+ echo "✅ 기본 모델에만 쓰기:$default_model"
+ return 0
+ else
+ echo "❌ 구성 삽입 실패"
+ return 1
+ fi
+ }
+ add-openclaw-provider-interactive() {
+ send_stats "OpenClaw API가 추가되었습니다."
+ echo "=== 대화식으로 OpenClaw 공급자 추가(전체 모델) ==="
+ # 1. 제공자 이름
+ read -erp "공급자 이름을 입력하십시오(예: deepseek):" provider_name
+ while [[ -z "$provider_name" ]]; do
+ echo "❌ 제공업체 이름은 비워둘 수 없습니다."
+ read -erp "제공업체 이름을 입력하세요." provider_name
+ done
- 11)
- local docker_name="zentao-server"
- local docker_img="idoop/zentao:latest"
- local docker_port=82
+ # 2. Base URL
+ read -erp "기본 URL을 입력하세요(예: https://api.xxx.com/v1):" base_url
+ while [[ -z "$base_url" ]]; do
+ echo "❌ 기본 URL은 비워둘 수 없습니다."
+ read -erp "기본 URL을 입력하세요:" base_url
+ done
+ base_url="${base_url%/}"
+ # 3. API Key
+ read -rsp "API 키를 입력하세요(입력은 표시되지 않습니다):" api_key
+ echo
+ while [[ -z "$api_key" ]]; do
+ echo "❌ API 키는 비워둘 수 없습니다."
+ read -rsp "API 키를 입력하세요:" api_key
+ echo
+ done
- docker_rum() {
+ # 4. 더 이상 API 유형을 감지/결정하지 않습니다. 프로토콜은 사용자가 직접 선택하고 유지 관리합니다.
+
+ # 5. 모델 목록 가져오기
+ echo "🔍 사용 가능한 모델 목록을 가져오는 중..."
+ models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -n "$models_json" ]]; then
+ available_models=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+' | sort)
+
+ if [[ -n "$available_models" ]]; then
+ model_count=$(echo "$available_models" | wc -l)
+ echo "✅ 발견$model_count사용 가능한 모델:"
+ echo "--------------------------------"
+ # 일련번호와 함께 모두 표시
+ i=1
+ model_list=()
+ while read -r model; do
+ echo "[$i] $model"
+ model_list+=("$model")
+ ((i++))
+ done <<< "$available_models"
+ echo "--------------------------------"
+ fi
+ fi
+ # 5. 기본 모델을 선택하세요
+ echo
+ read -erp "기본 모델 ID(또는 일련번호, 첫 번째 번호를 사용하려면 비워 두세요)를 입력하세요." input_model
+
+ if [[ -z "$input_model" && -n "$available_models" ]]; then
+ default_model=$(echo "$available_models" | head -1)
+ echo "🎯 첫 번째 모델 사용:$default_model"
+ elif [[ "$input_model" =~ ^[0-9]+$ ]] && [ "${#model_list[@]}" -gt 0 ] && [ "$input_model" -ge 1 ] && [ "$input_model" -le "${#model_list[@]}" ]; then
+ default_model="${model_list[$((input_model-1))]}"
+ echo "🎯 선택 모델:$default_model"
+ else
+ default_model="$input_model"
+ fi
- docker run -d -p ${docker_port}:80 \
- -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
- -e BIND_ADDRESS="false" \
- -v /home/docker/zentao-server/:/opt/zbox/ \
- --add-host smtp.exmail.qq.com:163.177.90.125 \
- --name zentao-server \
- --restart=always \
- idoop/zentao:latest
+ # 6. 정보 확인
+ echo
+ echo "====== 확인 ======"
+ echo "Provider : $provider_name"
+ echo "Base URL : $base_url"
+ echo "API Key : ${api_key:0:8}****"
+ echo "기본 모델:$default_model"
+ echo "총 모델 수:$model_count"
+ echo "======================"
+
+ read -erp "사용 가능한 다른 모든 모델을 동시에 추가하시겠습니까? (예/아니요):" confirm
+
+ install jq
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ add-all-models-from-provider "$provider_name" "$base_url" "$api_key"
+ add_result=$?
+ finish_msg="✅ 완료! 모두$model_count로드된 모델"
+ else
+ add-default-model-only-to-provider "$provider_name" "$base_url" "$api_key" "$default_model"
+ add_result=$?
+ finish_msg="✅ 완료! 공급자는 유지되고 기본 모델만 로드됩니다.$default_model"
+ fi
+ if [[ $add_result -eq 0 ]]; then
+ echo
+ echo "🔄 기본 모델을 설정하고 게이트웨이를 다시 시작하세요..."
+ openclaw models set "$provider_name/$default_model"
+ openclaw_sync_sessions_model "$provider_name/$default_model"
+ start_gateway
+ echo "$finish_msg"
+ echo "✅ 현재 API 프로토콜 유형:$DETECTED_API"
+ fi
- }
+ break_end
+ }
- local docker_describe="禅道是通用的项目管理软件"
- local docker_url="官网介绍: https://www.zentao.net/"
- local docker_use="echo \"初始用户名: admin\""
- local docker_passwd="echo \"初始密码: 123456\""
- local app_size="2"
- docker_app
- ;;
- 12)
- local docker_name="qinglong"
- local docker_img="whyour/qinglong:latest"
- local docker_port=5700
+openclaw_api_manage_list() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API 목록"
- docker_rum() {
+ while IFS=$'\t' read -r rec_type idx name base_url model_count api_type latency_txt latency_level; do
+ case "$rec_type" in
+ MSG)
+ echo "$idx"
+ ;;
+ ROW)
+ local latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
+ printf '%b\n' "[$idx] ${name} | API: ${base_url}| 합의:${api_type}| 모델 수:${gl_huang}${model_count}${gl_bai}| 지연/상태:${latency_color}${latency_txt}${gl_bai}"
+ ;;
+ esac
+ done < <(python3 - "$config_file" <<-'PY'
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+
+def ping_models(base_url, api_key):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'OpenClaw-API-Manage/1.0',
+ },
+ )
+ start = time.perf_counter()
+ with urllib.request.urlopen(req, timeout=4) as resp:
+ resp.read(2048)
+ return int((time.perf_counter() - start) * 1000)
+
+
+def classify_latency(latency):
+ if latency == '사용할 수 없음':
+ return '사용할 수 없음', 'unavailable'
+ if latency == '감지되지 않음':
+ return '감지되지 않음', 'unchecked'
+ if isinstance(latency, int):
+ if latency <= 800:
+ level = 'low'
+ elif latency <= 2000:
+ level = 'medium'
+ else:
+ level = 'high'
+ return f'{latency}ms', level
+ return str(latency), 'unchecked'
+
+
+try:
+ with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+except FileNotFoundError:
+ print('MSG\tℹ️ openclaw.json을 찾을 수 없습니다. 먼저 설치/초기화를 완료하세요.')
+ raise SystemExit(0)
+except Exception as e:
+ print(f'MSG\t❌ 구성을 읽지 못했습니다: {type(e).__name__}: {e}')
+ raise SystemExit(0)
+
+providers = ((obj.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or not providers:
+ print('MSG\tℹ️ 현재 구성된 API 제공업체가 없습니다.')
+ raise SystemExit(0)
+
+print('MSG\t--- 구성된 API 목록 ---')
+
+for idx, name in enumerate(sorted(providers.keys()), start=1):
+ provider = providers.get(name)
+ if not isinstance(provider, dict):
+ base_url = '-'
+ model_count = 0
+ latency_raw = '사용할 수 없음'
+ else:
+ base_url = provider.get('baseUrl') or provider.get('url') or provider.get('endpoint') or '-'
+ models = provider.get('models') if isinstance(provider.get('models'), list) else []
+ model_count = sum(1 for m in models if isinstance(m, dict) and m.get('id'))
+ api = provider.get('api', '')
+ api_key = provider.get('apiKey')
+
+ latency_raw = '감지되지 않음'
+ if api in SUPPORTED_APIS:
+ if isinstance(base_url, str) and base_url != '-' and isinstance(api_key, str) and api_key:
+ try:
+ latency_raw = ping_models(base_url, api_key)
+ except Exception:
+ latency_raw = '사용할 수 없음'
+ else:
+ latency_raw = '사용할 수 없음'
+
+ latency_text, latency_level = classify_latency(latency_raw)
+ api_label = api if api in SUPPORTED_APIS else '-'
+ print(
+ 'ROW\t' + '\t'.join([
+ str(idx),
+ str(name),
+ str(base_url),
+ str(model_count),
+ str(api_label),
+ str(latency_text),
+ str(latency_level),
+ ])
+ )
+PY
+)
+}
+sync-openclaw-provider-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "공급자별 OpenClaw API 동기화"
- docker run -d \
- -v /home/docker/qinglong/data:/ql/data \
- -p ${docker_port}:5700 \
- --name qinglong \
- --hostname qinglong \
- --restart unless-stopped \
- whyour/qinglong:latest
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 구성 파일을 찾을 수 없습니다:$config_file"
+ break_end
+ return 1
+ fi
+ read -erp "동기화할 API 이름(공급자)을 입력하고 Enter를 눌러 모두 동기화하세요." provider_name
+ if [ -z "$provider_name" ]; then
+ if sync_openclaw_api_models; then
+ start_gateway
+ else
+ echo "❌ API 모델 동기화에 실패하여 게이트웨이 다시 시작이 중단되었습니다. 공급자/모델을 확인하고 반환 후 다시 시도하십시오."
+ return 1
+ fi
+ break_end
+ return 0
+ fi
- }
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" <<'PY2'
+import copy
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+target = sys.argv[2]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('❌ API 제공업체가 감지되지 않아 동기화할 수 없습니다.')
+ raise SystemExit(2)
+
+provider = providers.get(target)
+if not isinstance(provider, dict):
+ print(f'❌ 제공자: {target}을(를) 찾을 수 없습니다')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def fetch_remote_models_with_retry(base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ return json.loads(payload), None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+api = provider.get('api', '')
+base_url = provider.get('baseUrl')
+api_key = provider.get('apiKey')
+model_list = provider.get('models', [])
+
+if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ print(f'❌ 공급자 {target}에 baseUrl/apiKey/models가 누락되어 동기화를 수행할 수 없습니다.')
+ raise SystemExit(3)
+
+if api not in SUPPORTED_APIS:
+ print(f'ℹ️ 공급자 {target}은 현재 api={api}이지만 스크립트는 더 이상 프로토콜을 감지/수정하지 않습니다. openai-completions 또는 openai-responses로 수동으로 설정하세요.')
+
+protocol_msg = None
+
+data, err, attempts = fetch_remote_models_with_retry(base_url, api_key, retries=3)
+if err is not None:
+ print(f'❌ {target}: /models 감지 실패, {attempts}번 재시도됨 ({type(err).__name__}: {err})')
+ raise SystemExit(4)
+
+if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ print(f'❌ {target}: /models 반환된 구조가 인식되지 않습니다.')
+ raise SystemExit(4)
+
+remote_ids = []
+for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+remote_set = set(remote_ids)
+if not remote_set:
+ print(f'❌ {target}: 업스트림 /models가 비어 있고 동기화가 중단되었습니다.')
+ raise SystemExit(5)
+
+local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+local_ids = [str(m['id']) for m in local_models]
+local_set = set(local_ids)
+
+template = copy.deepcopy(local_models[0]) if local_models else None
+if template is None:
+ print(f'❌ {target}: 로컬 모델에 유효한 템플릿 모델이 없어 새 모델을 추가할 수 없습니다.')
+ raise SystemExit(3)
+
+removed_ids = [mid for mid in local_ids if mid not in remote_set]
+added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+new_models = kept_models[:]
+for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{target} / {mid}'
+ new_models.append(nm)
+
+if not new_models:
+ print(f'❌ {target}: 동기화 후 사용할 수 있는 모델이 없어 쓰기가 중단되었습니다.')
+ raise SystemExit(5)
+
+expected_refs = {model_ref(target, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+local_refs = {model_ref(target, mid) for mid in local_ids}
+removed_refs = local_refs - expected_refs
+first_ref = model_ref(target, str(new_models[0]['id']))
+
+changed = False
+primary_ref = get_primary_ref(defaults)
+if isinstance(primary_ref, str) and primary_ref in removed_refs:
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ print(f'🔁 기본 모델이 완전히 교체되었습니다: {primary_ref} -> {first_ref}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in removed_refs:
+ defaults[fk] = first_ref
+ changed = True
+ print(f'🔁 {fk}가 완전히 대체되었습니다: {val} -> {first_ref}')
+
+stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(target + '/') and r not in expected_refs]
+for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ {target}: {len(add_ids)} 추가, 삭제 {len(removed_ids)}, 현재 {len(new_models)}')
+
+if added_ids:
+ print(f'➕ 새 모델 추가({len(add_ids)}):')
+ for mid in added_ids:
+ print(f' + {mid}')
+if removed_ids:
+ print(f'➖ 모델 삭제({len(removed_ids)}):')
+ for mid in removed_ids:
+ print(f' - {mid}')
+
+if changed:
+ print('✅ 지정된 공급자 모델 일관성 동기화가 완료되고 구성이 작성되었습니다.')
+else:
+ print('ℹ️ 동기화가 필요하지 않습니다. 공급자 구성이 이미 업스트림/모델과 일치합니다.')
+PY2
+ local rc=$?
+ case "$rc" in
+ 0)
+ echo "✅ 동기 실행 완료"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 동기화 실패: 공급자가 존재하지 않거나 구성되지 않았습니다."
+ ;;
+ 3)
+ echo "❌ 동기화 실패: 공급자 구성이 불완전하거나 유형이 지원되지 않습니다."
+ ;;
+ 4)
+ echo "❌ 동기화 실패: 업스트림 /models 요청 실패"
+ ;;
+ 5)
+ echo "❌ 동기화 실패: 업스트림 모델이 비어 있거나 동기화 후 사용 가능한 모델이 없습니다."
+ ;;
+ *)
+ echo "❌ 동기화 실패: 구성 파일 구조 또는 로그 출력을 확인하세요."
+ ;;
+ esac
- local docker_describe="青龙面板是一个定时任务管理平台"
- local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ break_end
+}
- ;;
- 13)
+openclaw_detect_api_protocol_by_provider() {
+ # 프로토콜 감지 논리가 제거되었습니다. 스크립트는 더 이상 API 유형을 자동으로 감지/결정하지 않습니다.
+ # 이 기능은 메뉴 호출과의 호환성을 위해 유지되지만 재작성은 수행되지 않습니다.
+ echo "ℹ️ 프로토콜 감지가 꺼졌습니다. 수동으로 감지하세요.${HOME}/.openclaw/openclaw.json의 공급자.api를 openai-completions 또는 openai-responses로 설정합니다."
+ return 0
+}
- local app_name="cloudreve网盘"
- local app_text="cloudreve是一个支持多家云存储的网盘系统"
- local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
- local docker_name="cloudreve"
- local docker_port="5212"
- local app_size="2"
-
- docker_app_install() {
- cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
- curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
- sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
- cd /home/docker/cloud/
- docker compose up -d
- clear
- echo "설치"
- check_docker_app_ip
- }
+fix-openclaw-provider-protocol-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API 프로토콜 전환"
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 구성 파일을 찾을 수 없습니다:$config_file"
+ break_end
+ return 1
+ fi
- docker_app_update() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- cd /home/docker/cloud/ && docker compose up -d
- }
+ read -erp "프로토콜을 전환하려면 API 이름(공급자)을 입력하세요." provider_name
+ if [ -z "$provider_name" ]; then
+ echo "❌ 제공업체 이름은 비워둘 수 없습니다."
+ break_end
+ return 1
+ fi
+ echo "설정하려는 API 유형을 선택하세요."
+ echo "1. openai-completions"
+ echo "2. openai-responses"
+ read -erp "원하는 항목(1/2)을 입력하세요." proto_choice
- docker_app_uninstall() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- rm -rf /home/docker/cloud
- echo "앱이 제거되었습니다"
- }
+ local new_api=""
+ case "$proto_choice" in
+ 1) new_api="openai-completions" ;;
+ 2) new_api="openai-responses" ;;
+ *)
+ echo "❌ 잘못된 선택"
+ break_end
+ return 1
+ ;;
+ esac
- docker_app_plus
- ;;
+ install python3 >/dev/null 2>&1
- 14)
- local docker_name="easyimage"
- local docker_img="ddsderek/easyimage:latest"
- local docker_port=85
- docker_rum() {
-
- docker run -d \
- --name easyimage \
- -p ${docker_port}:80 \
- -e TZ=Asia/Shanghai \
- -e PUID=1000 \
- -e PGID=1000 \
- -v /home/docker/easyimage/config:/app/web/config \
- -v /home/docker/easyimage/i:/app/web/i \
- --restart unless-stopped \
- ddsderek/easyimage:latest
+ python3 - "$config_file" "$provider_name" "$new_api" <<'PY'
+import copy
+import json
+import sys
- }
+path = sys.argv[1]
+name = sys.argv[2]
+new_api = sys.argv[3]
- local docker_describe="简单图床是一个简单的图床程序"
- local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+if new_api not in SUPPORTED_APIS:
+ print('❌ 잘못된 프로토콜 값')
+ raise SystemExit(3)
- 15)
- local docker_name="emby"
- local docker_img="linuxserver/emby:latest"
- local docker_port=8096
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
- docker_rum() {
+work = copy.deepcopy(obj)
+providers = ((work.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or name not in providers or not isinstance(providers.get(name), dict):
+ print(f'❌ 제공자: {name}을(를) 찾을 수 없습니다')
+ raise SystemExit(2)
- docker run -d --name=emby --restart=always \
- -v /home/docker/emby/config:/config \
- -v /home/docker/emby/share1:/mnt/share1 \
- -v /home/docker/emby/share2:/mnt/share2 \
- -v /mnt/notify:/mnt/notify \
- -p ${docker_port}:8096 \
- -e UID=1000 -e GID=100 -e GIDLIST=100 \
- linuxserver/emby:latest
+providers[name]['api'] = new_api
- }
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+print(f'✅ 공급자 {name} 프로토콜이 다음으로 업데이트되었습니다: {new_api}')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 전환 실패: 공급자가 존재하지 않거나 구성되지 않았습니다."
+ ;;
+ 3)
+ echo "❌ 전환 실패: 프로토콜 값이 잘못되었습니다."
+ ;;
+ *)
+ echo "❌ 전환 실패: 구성 파일 구조 또는 로그 출력을 확인하세요."
+ ;;
+ esac
- local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
- local docker_url="官网介绍: https://emby.media/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ break_end
+}
- 16)
- local docker_name="looking-glass"
- local docker_img="wikihostinc/looking-glass-server"
- local docker_port=89
+ delete-openclaw-provider-interactive() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ send_stats "OpenClaw API 삭제 항목"
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 구성 파일을 찾을 수 없습니다:$config_file"
+ break_end
+ return 1
+ fi
- docker_rum() {
+ read -erp "삭제할 API 이름(공급자)을 입력하세요." provider_name
+ if [ -z "$provider_name" ]; then
+ send_stats "OpenClaw API 삭제 취소"
+ echo "❌ 제공업체 이름은 비워둘 수 없습니다."
+ break_end
+ return 1
+ fi
- docker run -d --name looking-glass --restart always -p ${docker_port}:80 wikihostinc/looking-glass-server
+ python3 - "$config_file" "$provider_name" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or name not in providers:
+ print(f'❌ 제공자: {name}을(를) 찾을 수 없습니다')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+replacement_candidates = collect_available_refs(exclude_provider=name)
+replacement = replacement_candidates[0] if replacement_candidates else None
+
+primary_ref = get_primary_ref(defaults)
+if ref_provider(primary_ref) == name:
+ if not replacement:
+ print('❌ 삭제가 중단됨: 기본 기본 모델이 공급자를 가리키며 대체 모델을 사용할 수 없습니다.')
+ raise SystemExit(3)
+ set_primary_ref(defaults, replacement)
+ print(f'🔁 기본 기본 모델 전환: {primary_ref} -> {replacement}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ if not replacement:
+ print(f'❌ 삭제가 중단되었습니다. {fk}가 공급자를 가리키며 대체 모델을 사용할 수 없습니다.')
+ raise SystemExit(3)
+ defaults[fk] = replacement
+ print(f'🔁 {fk} 스위치: {val} -> {교체}')
+
+removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+for r in removed_refs:
+ defaults_models.pop(r, None)
+
+providers.pop(name, None)
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'🗑️ 삭제된 제공자: {name}')
+print(f'🧹 defaults.models에서 {len(removed_refs)} 관련 모델 참조를 정리했습니다.')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ send_stats "OpenClaw API 삭제 확인"
+ echo "✅삭제 완료"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 삭제 실패: 공급자가 존재하지 않습니다."
+ ;;
+ 3)
+ send_stats "OpenClaw API 삭제 취소"
+ echo "❌ 삭제 실패: 사용 가능한 대체 모델이 없으며 원래 구성이 유지되었습니다."
+ ;;
+ *)
+ echo "❌ 삭제 실패: 구성 파일 구조 또는 로그 출력을 확인하세요."
+ ;;
+ esac
- }
+ break_end
+ }
- local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
- local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
-
- ;;
- 17)
-
- local docker_name="adguardhome"
- local docker_img="adguard/adguardhome"
- local docker_port=3000
-
- docker_rum() {
-
- docker run -d \
- --name adguardhome \
- -v /home/docker/adguardhome/work:/opt/adguardhome/work \
- -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
- -p 53:53/tcp \
- -p 53:53/udp \
- -p ${docker_port}:3000/tcp \
- --restart always \
- adguard/adguardhome
+ openclaw_api_providers_showcase() {
+ send_stats "OpenClaw API 공급업체 권장사항"
+ clear
+ echo ""
+ echo -e "${gl_kjlan}╔════════════════════════════════════════════════════════════╗${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_huang}🌟 API 벤더 추천 목록${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_zi}일부 입구에는 AFF가 포함되어 있습니다.${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}╚════════════════════════════════════════════════════════════╝${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● DeepSeek${gl_bai}"
+ echo -e " ${gl_kjlan}https://api-docs.deepseek.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● OpenRouter${gl_bai}"
+ echo -e " ${gl_kjlan}https://openrouter.ai/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Kimi${gl_bai}"
+ echo -e " ${gl_kjlan}https://platform.moonshot.cn/docs/guide/start-using-kimi-api${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 슈퍼컴퓨팅 인터넷${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.scnet.cn/${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● Youyun 지능형 컴퓨팅${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://passport.compshare.cn/register?referral_code=4mscFZXfutfFi8swMVsPuf${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 실리콘 기반 흐름${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://cloud.siliconflow.cn/i/irWVdPic${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● GLM${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.bigmodel.cn/glm-coding?ic=HYOTDOAJMR${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● PackyAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.packyapi.com/register?aff=wHri${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 클라우드 API${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://yunwu.ai/register?aff=ZuyK${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 플라톤 AI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://api.bltcy.ai/register?aff=TBzb114019${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● MiniMax${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.minimaxi.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● NVIDIA${gl_bai}"
+ echo -e " ${gl_kjlan}https://build.nvidia.com/settings/api-keys${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Ollama${gl_bai}"
+ echo -e " ${gl_kjlan}https://ollama.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 백산구름${gl_bai}"
+ echo -e " ${gl_kjlan}https://ai.baishan.com/${gl_bai}"
+ echo ""
+ echo -e "${gl_kjlan}────────────────────────────────────────────────────────────${gl_bai}"
+ echo -e " ${gl_zi}전설:${gl_lv}● 공식 입장${gl_bai} ${gl_huang}● AFF 추천 입장${gl_bai}"
+ echo ""
+ echo -e "${gl_huang}팁: 링크를 복사하고 브라우저에서 열어서 액세스하세요.${gl_bai}"
+ echo ""
+ read -erp "돌아가려면 Enter를 누르세요..." dummy
+ }
- }
+ openclaw_api_manage_menu() {
+ send_stats "OpenClaw API 입구"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw API 관리"
+ echo "======================================="
+ openclaw_api_manage_list
+ echo "---------------------------------------"
+ echo "1. API 추가"
+ echo "2. API 제공자 모델 목록 동기화"
+ echo "3. API 유형 전환(완료/응답)"
+ echo "4. API 삭제"
+ echo "5. API 공급업체에서 권장하는 사항"
+ echo "0. 종료"
+ echo "---------------------------------------"
+ read -erp "선택사항을 입력하세요:" api_choice
+ case "$api_choice" in
+ 1)
+ add-openclaw-provider-interactive
+ ;;
+ 2)
+ sync-openclaw-provider-interactive
+ ;;
+ 3)
+ fix-openclaw-provider-protocol-interactive
+ ;;
+ 4)
+ delete-openclaw-provider-interactive
+ ;;
+ 5)
+ openclaw_api_providers_showcase
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ ;;
+ esac
+ done
+ }
- local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
- local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update && apt install -y gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then
+ dnf install -y gum
+ else
+ yum install -y gum
+ fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive refresh
+ zypper --non-interactive install gum
+ fi
+ }
- 18)
- local docker_name="onlyoffice"
- local docker_img="onlyoffice/documentserver"
- local docker_port=8082
- docker_rum() {
+ change_model() {
+ send_stats "모델 변경"
- docker run -d -p ${docker_port}:80 \
- --restart=always \
- --name onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
- onlyoffice/documentserver
+ local orange="#FF8C00"
+ openclaw_probe_status_line() {
+ local status_text="$1"
+ local status_color_ok='[32m'
+ local status_color_fail='[31m'
+ local status_color_reset='[0m'
+ if [ "$status_text" = "사용 가능" ]; then
+ printf "%b最小检测结果:%s%b
+" "$status_color_ok" "$status_text" "$status_color_reset"
+ else
+ printf "%b最小检测结果:%s%b
+" "$status_color_fail" "$status_text" "$status_color_reset"
+ fi
+ }
+ openclaw_model_probe() {
+ local target_model="$1"
+ local probe_timeout=25
+ local tmp_payload tmp_response probe_result probe_status reply_preview reply_trimmed
+ local oc_config provider_name base_url api_key request_model
+ local first_endpoint second_endpoint
+ local first_exit first_http first_latency second_exit second_http second_latency
+ local first_reply second_reply
+
+ oc_config=$(openclaw_get_config_file)
+ [ ! -f "$oc_config" ] && {
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="openclaw 구성 파일을 찾을 수 없습니다"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
}
- local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
- local docker_url="官网介绍: https://www.onlyoffice.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
-
- ;;
+ provider_name="${target_model%%/*}"
+ request_model="${target_model#*/}"
+ base_url=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].baseUrl // empty' "$oc_config" 2>/dev/null)
+ api_key=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].apiKey // empty' "$oc_config" 2>/dev/null)
+ if [ -z "$provider_name" ] || [ -z "$base_url" ] || [ -z "$api_key" ]; then
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="공급자/baseUrl/apiKey를 읽을 수 없습니다."
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ fi
- 19)
- send_stats "천둥 수영장을 건설하십시오"
+ base_url="${base_url%/}"
+ first_endpoint="/responses"
+ second_endpoint="/chat/completions"
+
+ openclaw_extract_probe_reply() {
+ python3 - "$1" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+path = Path(sys.argv[1])
+raw = path.read_text(encoding='utf-8', errors='replace').strip()
+reply = ''
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get('choices') or []
+ if choices and isinstance(choices[0], dict):
+ message = choices[0].get('message') or {}
+ if isinstance(message, dict):
+ reply = message.get('content') or ''
+ if not reply:
+ output = data.get('output') or []
+ if isinstance(output, list):
+ texts = []
+ for item in output:
+ if not isinstance(item, dict):
+ continue
+ for content in item.get('content') or []:
+ if not isinstance(content, dict):
+ continue
+ text = content.get('text')
+ if isinstance(text, str) and text.strip():
+ texts.append(text.strip())
+ if texts:
+ break
+ if texts:
+ reply = ' '.join(texts)
+ if not reply:
+ for key in ('error', 'message', 'detail'):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ reply = value.strip()
+ break
+ if isinstance(value, dict):
+ nested = value.get('message')
+ if isinstance(nested, str) and nested.strip():
+ reply = nested.strip()
+ break
+ except Exception:
+ reply = raw
+reply = ' '.join(str(reply).split())
+print(reply)
+PYTHON_EOF
+ }
- local docker_name=safeline-mgt
- local docker_port=9443
- while true; do
- check_docker_app
- clear
- echo -e "썬더 풀 서비스$check_docker"
- echo "Lei Chi는 변경 기술이 개발 한 WAF 사이트 방화벽 프로그램 패널로, 자동 방어를 위해 대행사 사이트를 역전시킬 수 있습니다."
- echo "비디오 소개 : https://www.bilibili.com/video/bv1mz421t74c?t=0.1"
- if docker inspect "$docker_name" &>/dev/null; then
- check_docker_app_ip
+ openclaw_run_probe() {
+ local endpoint="$1"
+ tmp_payload=$(mktemp)
+ tmp_response=$(mktemp)
+ if [ "$endpoint" = "/responses" ]; then
+ printf '{"model":"%s","input":"hi","temperature":0,"max_output_tokens":16}' "$request_model" > "$tmp_payload"
+ else
+ printf '{"model":"%s","messages":[{"role":"user","content":"hi"}],"temperature":0,"max_tokens":16}' "$request_model" > "$tmp_payload"
fi
- echo ""
-
- echo "------------------------"
- echo "1. 설치 2. 업데이트 3. 비밀번호 재설정 4. 제거"
- echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
- echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
-
- case $choice in
- 1)
- install_docker
- check_disk_space 5
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
- clear
- echo "Thunder Pool WAF 패널이 설치되었습니다"
- check_docker_app_ip
- docker exec safeline-mgt resetadmin
-
- ;;
- 2)
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
- docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
- echo ""
- clear
- echo "Thunder Pool WAF 패널이 업데이트되었습니다"
- check_docker_app_ip
- ;;
- 3)
- docker exec safeline-mgt resetadmin
- ;;
- 4)
- cd /data/safeline
- docker compose down --rmi all
- echo "기본 설치 디렉토리 인 경우 프로젝트가 제거되었습니다. 설치 디렉토리를 사용자 정의하는 경우 직접 실행하려면 설치 디렉토리로 이동해야합니다."
- echo "docker compose down && docker compose down --rmi all"
- ;;
- *)
- break
- ;;
+ probe_result=$(python3 - "$base_url" "$api_key" "$tmp_payload" "$tmp_response" "$probe_timeout" "$endpoint" <<'PYTHON_EOF'
+import sys
+import time
+import urllib.error
+import urllib.request
+
+base_url, api_key, payload_path, response_path, timeout, endpoint = sys.argv[1:7]
+timeout = int(timeout)
+url = base_url + endpoint
+payload = open(payload_path, 'rb').read()
+req = urllib.request.Request(
+ url,
+ data=payload,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'Bearer {api_key}',
+ },
+ method='POST',
+)
+start = time.time()
+body = b''
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, 'status', 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, 'code', 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode('utf-8', errors='replace')
+ exit_code = 1
+elapsed = int((time.time() - start) * 1000)
+with open(response_path, 'wb') as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYTHON_EOF
+)
+ probe_status=$?
+ reply_preview=$(openclaw_extract_probe_reply "$tmp_response")
+ rm -f "$tmp_payload" "$tmp_response"
+ return $probe_status
+ }
- esac
- break_end
- done
+ openclaw_run_probe "$first_endpoint"
+ first_exit=${probe_result%%|*}
+ first_http=${probe_result#*|}
+ first_http=${first_http%%|*}
+ first_latency=${probe_result##*|}
+ first_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(빈 반환)"
+
+ if [ "$first_exit" = "0" ] && [ "$first_http" -ge 200 ] && [ "$first_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http}"
+ OPENCLAW_PROBE_LATENCY="${first_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
- ;;
+ openclaw_run_probe "$second_endpoint"
+ second_exit=${probe_result%%|*}
+ second_http=${probe_result#*|}
+ second_http=${second_http%%|*}
+ second_latency=${probe_result##*|}
+ second_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(빈 반환)"
+
+ if [ "$second_exit" = "0" ] && [ "$second_http" -ge 200 ] && [ "$second_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint}-> HTTP ${first_http:-0}, 스위치${second_endpoint} -> HTTP ${second_http}"
+ OPENCLAW_PROBE_LATENCY="${second_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
- 20)
- local docker_name="portainer"
- local docker_img="portainer/portainer"
- local docker_port=9050
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(빈 반환)"
- docker_rum() {
+ OPENCLAW_PROBE_STATUS="FAIL"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0} / exit ${first_exit:-1};${second_endpoint} -> HTTP ${second_http:-0} / exit ${second_exit:-1}"
+ OPENCLAW_PROBE_LATENCY="${first_latency:-?}ms -> ${second_latency:-?}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 1
+ }
- docker run -d \
- --name portainer \
- -p ${docker_port}:9000 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/portainer:/data \
- --restart always \
- portainer/portainer
+ clear
- }
+ while true; do
+ local models_raw models_list default_model model_count selected_model confirm_switch
+ # 구성 파일에서 모델 키 읽기(openclaw 모델 목록을 호출하지 않고)
+ local oc_config
+ oc_config=$(openclaw_get_config_file)
- local docker_describe="portainer是一个轻量级的docker容器管理面板"
- local docker_url="官网介绍: https://www.portainer.io/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ models_raw=$(jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d')
+ if [ -z "$models_raw" ]; then
+ echo "모델 목록을 가져오지 못했습니다. 구성 파일에서 Agent.defaults.models를 찾을 수 없습니다."
+ break_end
+ return 1
+ fi
- ;;
+ # 빠른 위치를 쉽게 찾을 수 있도록 각 모델에 번호를 매깁니다(예: "(10) or-api/...:free")
+ models_list=$(echo "$models_raw" | awk '{print "(" NR ") " $0}')
+ model_count=$(echo "$models_list" | sed '/^\s*$/d' | wc -l | tr -d ' ')
- 21)
- local docker_name="vscode-web"
- local docker_img="codercom/code-server"
- local docker_port=8180
+ # 구성 파일에서 기본 모델을 읽습니다(더 빠름). 실패 시 openclaw 명령으로 대체
+ default_model=$(jq -r '.agents.defaults.model.primary // empty' "$oc_config" 2>/dev/null)
+ [ -z "$default_model" ] && default_model="(unknown)"
+ clear
- docker_rum() {
+ install_gum
+ install gum
- docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart always codercom/code-server
+ # 껌이 존재하지 않을 경우 기존의 수동 입력 과정으로 다운그레이드됩니다.
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "---모델 관리 ---"
+ echo "현재 사용 가능한 모델:"
+ jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d'
+ echo "----------------"
+ read -e -p "설정할 모델 이름을 입력하세요(예: openrouter/openai/gpt-4o)(종료하려면 0 입력)." selected_model
- }
+ if [ "$selected_model" = "0" ]; then
+ echo "작업이 취소되었습니다. 종료하는 중..."
+ break
+ fi
+ if [ -z "$selected_model" ]; then
+ echo "오류: 모델 이름은 비워둘 수 없습니다. 다시 시도해 주세요."
+ echo ""
+ continue
+ fi
- local docker_describe="VScode是一款强大的在线代码编写工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
- local docker_use="sleep 3"
- local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
- local app_size="1"
- docker_app
- ;;
- 22)
- local docker_name="uptime-kuma"
- local docker_img="louislam/uptime-kuma:latest"
- local docker_port=3003
+ echo "전환되는 모델은 다음과 같습니다.$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "전환 실패: openclaw 모델 세트에서 오류가 반환되었습니다."
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+ break_end
+ return 0
+ else
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "껌을 사용할 수 없습니다. 이전 입력 모드로 돌아갑니다."
+ sleep 1
+ continue
+ fi
+ gum style --foreground "$orange" --bold "모델 관리"
+ gum style --foreground "$orange" "사용 가능한 모델(인증=예):${model_count}"
+ gum style --foreground "$orange" "현재 기본값:${default_model}"
+ echo ""
+ gum style --faint "↑↓ 테스트하려면 선택 / Enter / 종료하려면 Esc"
+ echo ""
- docker_rum() {
+ selected_model=$(echo "$models_list" | gum filter --placeholder "검색 모델(예: cli-api/gpt-5.2)" --prompt "모델 선택 >" --indicator "➜ " --prompt.foreground "$orange" --indicator.foreground "$orange" --cursor-text.foreground "$orange" --match.foreground "$orange" --header "" --height 35)
- docker run -d \
- --name=uptime-kuma \
- -p ${docker_port}:3001 \
- -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
- --restart=always \
- louislam/uptime-kuma:latest
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "작업이 취소되었습니다. 종료하는 중..."
+ break
+ fi
+ fi
- }
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
+ echo ""
+ echo "모델 감지:$selected_model"
+ if openclaw_model_probe "$selected_model"; then
+ openclaw_probe_status_line "사용 가능"
+ else
+ openclaw_probe_status_line "사용할 수 없음"
+ fi
+ echo "상태:$OPENCLAW_PROBE_MESSAGE"
+ echo "지연:$OPENCLAW_PROBE_LATENCY"
+ echo "요약:$OPENCLAW_PROBE_REPLY"
+ echo ""
- local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ printf "이 모델로 전환하시겠습니까? [y/N, Esc가 목록으로 돌아갑니다]:"
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ [nN]|"") confirm_switch="no" ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
- 23)
- local docker_name="memos"
- local docker_img="ghcr.io/usememos/memos:latest"
- local docker_port=5230
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "모델 선택 목록이 반환되었습니다."
+ sleep 1
+ continue
+ fi
- docker_rum() {
+ echo "전환되는 모델은 다음과 같습니다.$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "전환 실패: openclaw 모델 세트에서 오류가 반환되었습니다."
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
- docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart always ghcr.io/usememos/memos:latest
+ break_end
+ done
+ }
- }
- local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
- local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ openclaw_get_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
- 24)
- local docker_name="webtop"
- local docker_img="lscr.io/linuxserver/webtop:latest"
- local docker_port=3083
-
- docker_rum() {
-
-
- docker run -d \
- --name=webtop \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=webtop-abc \
- -e PASSWORD=webtopABC123 \
- -e LC_ALL=zh_CN.UTF-8 \
- -e DOCKER_MODS=linuxserver/mods:universal-package-install \
- -e INSTALL_PACKAGES=font-noto-cjk \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:latest
+ openclaw_get_agents_dir() {
+ local user_agents="${HOME}/.openclaw/agents"
+ local root_agents="/root/.openclaw/agents"
+ if [ -d "$user_agents" ]; then
+ echo "$user_agents"
+ elif [ "$HOME" = "/root" ] && [ -d "$root_agents" ]; then
+ echo "$root_agents"
+ else
+ echo "$user_agents"
+ fi
+ }
+ openclaw_sync_sessions_model() {
+ local model_ref="$1"
+ [ -z "$model_ref" ] && return 1
+
+ local agents_dir
+ agents_dir=$(openclaw_get_agents_dir)
+ [ ! -d "$agents_dir" ] && return 0
+
+ local provider="${model_ref%%/*}"
+ local model="${model_ref#*/}"
+ [ "$provider" = "$model_ref" ] && { provider=""; model="$model_ref"; }
+
+ local count=0
+ local agent_dir sessions_file backup_file
+
+ for agent_dir in "$agents_dir"/*/; do
+ [ ! -d "$agent_dir" ] && continue
+ sessions_file="$agent_dir/sessions/sessions.json"
+ [ ! -f "$sessions_file" ] && continue
+
+ backup_file="${sessions_file}.bak"
+ cp "$sessions_file" "$backup_file" 2>/dev/null || continue
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if [ -n "$provider" ]; then
+ jq --arg model "$model" --arg provider "$provider" \
+ 'to_entries | map(.value.modelOverride = $model | .value.providerOverride = $provider) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ else
+ jq --arg model "$model" \
+ 'to_entries | map(.value.modelOverride = $model | del(.value.providerOverride)) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ fi
+ fi
+ done
- }
+ [ "$count" -gt 0 ] && echo "✅ 동기화됨$count에이전트의 세션 모델은 다음과 같습니다.$model_ref"
+ return 0
+ }
+ resolve_openclaw_plugin_id() {
+ local raw_input="$1"
+ local plugin_id="$raw_input"
- local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: webtop-abc\""
- local docker_passwd="echo \"密码: webtopABC123\""
- local app_size="2"
- docker_app
- ;;
+ plugin_id="${plugin_id#@openclaw/}"
+ if [[ "$plugin_id" == @*/* ]]; then
+ plugin_id="${plugin_id##*/}"
+ fi
+ plugin_id="${plugin_id%%@*}"
+ echo "$plugin_id"
+ }
- 25)
- local docker_name="nextcloud"
- local docker_img="nextcloud:latest"
- local docker_port=8989
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+ sync_openclaw_plugin_allowlist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
- docker_rum() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
- docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
- }
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | if (.plugins.allow | index($pid)) == null then .plugins.allow += [$pid] else . end
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 동기화된 플러그인.허용 목록:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
- local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
- local docker_url="官网介绍: https://nextcloud.com/"
- local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+if plugin_id not in a:
+ a.append(plugin_id)
+
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 동기화된 플러그인.허용 목록:$plugin_id"
+ return 0
+ fi
+ fi
- 26)
- local docker_name="qd"
- local docker_img="qdtoday/qd:latest"
- local docker_port=8923
+ echo "⚠️ 플러그인이 설치되었지만,plugins.allow의 동기화에 실패했습니다. 수동으로 확인하세요.$config_file"
+ return 1
+ }
- docker_rum() {
+ sync_openclaw_plugin_denylist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
- docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+ local config_file
+ config_file=$(openclaw_get_config_file)
- }
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
- local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
- local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 27)
- local docker_name="dockge"
- local docker_img="louislam/dockge:latest"
- local docker_port=5003
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | .plugins.allow = (.plugins.allow | map(select(. != $pid)))
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅plugins.allow에서 제거됨:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
- docker_rum() {
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+a = [x for x in a if x != plugin_id]
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅plugins.allow에서 제거됨:$plugin_id"
+ return 0
+ fi
+ fi
- docker run -d --name dockge --restart unless-stopped -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+ echo "⚠️plugins.allow 제거에 실패했습니다. 수동으로 확인하세요.$config_file"
+ return 1
+ }
- }
- local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 28)
- local docker_name="speedtest"
- local docker_img="ghcr.io/librespeed/speedtest"
- local docker_port=8028
- docker_rum() {
- docker run -d -p ${docker_port}:8080 --name speedtest --restart always ghcr.io/librespeed/speedtest
- }
+ install_plugin() {
+ send_stats "플러그인 관리"
+ while true; do
+ clear
+ echo "========================================"
+ echo "플러그인 관리(설치/제거)"
+ echo "========================================"
+ echo "현재 플러그인 목록:"
+ openclaw plugins list
+ echo "--------------------------------------------------------"
+ echo "일반적으로 사용되는 권장 플러그인 ID(괄호 안에 ID를 복사하면 됩니다):"
+ echo "--------------------------------------------------------"
+ echo "📱 커뮤니케이션 채널:"
+ echo "- [feishu] # 페이슈/종달새 통합"
+ echo "- [텔레그램] # 텔레그램 봇"
+ echo "- [slack] #Slack 기업 커뮤니케이션"
+ echo " - [msteams] # Microsoft Teams"
+ echo "- [디스코드] # 디스코드 커뮤니티 관리"
+ echo "- [whatsapp] #WhatsApp 자동화"
+ echo ""
+ echo "🧠 메모리와 AI:"
+ echo "- [memory-core] # 기본 메모리(파일 검색)"
+ echo "- [memory-lancedb] # 향상된 메모리(벡터 데이터베이스)"
+ echo "- [copilot-proxy] # Copilot 인터페이스 전달"
+ echo ""
+ echo "⚙️ 기능 확장:"
+ echo "- [랍스터] # 승인 흐름(수동 확인 포함)"
+ echo "- [음성통화] # 음성통화 기능"
+ echo "- [nostr] # 암호화된 비공개 채팅"
+ echo "--------------------------------------------------------"
+
+ echo "1) 플러그인 설치/활성화"
+ echo "2) 플러그인 삭제/비활성화"
+ echo "0) 복귀"
+ read -e -p "작업을 선택하십시오:" plugin_action
+
+ [ "$plugin_action" = "0" ] && break
+ [ -z "$plugin_action" ] && continue
+
+ read -e -p "플러그인 ID를 입력하십시오(공백으로 구분, 종료하려면 0 입력)." raw_input
+ [ "$raw_input" = "0" ] && break
+ [ -z "$raw_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ for token in $raw_input; do
+ local plugin_id
+ local plugin_full
+ plugin_id=$(resolve_openclaw_plugin_id "$token")
+ plugin_full="$token"
+ [ -z "$plugin_id" ] && continue
+
+ if [ "$plugin_action" = "1" ]; then
+ echo "🔍 플러그인 상태 확인 중:$plugin_id"
+ local plugin_list
+ plugin_list=$(openclaw plugins list 2>/dev/null)
+
+ if echo "$plugin_list" | grep -qw "$plugin_id" && echo "$plugin_list" | grep "$plugin_id" | grep -q "disabled"; then
+ echo "💡 플러그인 [$plugin_id] 사전 설치되어 활성화 중..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
- local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
- local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ if [ -d "/usr/lib/node_modules/openclaw/extensions/$plugin_id" ]; then
+ echo "💡 플러그인이 시스템 내장 디렉토리에 존재하는 것을 발견했습니다. 직접 활성화해 보십시오..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
- 29)
- local docker_name="searxng"
- local docker_img="searxng/searxng"
- local docker_port=8029
+ echo "📥 로컬에서 찾을 수 없습니다. 다운로드하여 설치해 보세요.$plugin_full"
+ rm -rf "${HOME}/.openclaw/extensions/$plugin_id"
+ [ "$HOME" != "/root" ] && rm -rf "/root/.openclaw/extensions/$plugin_id"
+ if openclaw plugins install "$plugin_full"; then
+ echo "✅ 다운로드 성공, 활성화 중..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "❌ 설치 실패:$plugin_full"
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "🗑️ 플러그인 제거/비활성화:$plugin_id"
+ openclaw plugins disable "$plugin_id" >/dev/null 2>&1
+ if openclaw plugins uninstall "$plugin_id"; then
+ echo "✅ 제거됨:$plugin_id"
+ else
+ echo "⚠️ 제거에 실패했습니다. 사전 설치된 플러그인일 수 있습니다. 다음만 비활성화하세요.$plugin_id"
+ fi
+ sync_openclaw_plugin_denylist "$plugin_id" >/dev/null 2>&1
+ success_list="$success_list $plugin_id"
+ changed=true
+ fi
+ done
- docker_rum() {
+ echo ""
+ echo "====== 작업 요약 ======"
+ echo "✅ 성공:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 실패:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 건너뛰기:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 변경 사항을 로드하기 위해 OpenClaw 서비스를 다시 시작하는 중..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
- docker run -d \
- --name searxng \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -v "/home/docker/searxng:/etc/searxng" \
- searxng/searxng
- }
+ install_skill() {
+ send_stats "스킬 관리"
+ while true; do
+ clear
+ echo "========================================"
+ echo "스킬 관리(설치/제거)"
+ echo "========================================"
+ echo "현재 설치된 스킬:"
+ openclaw skills list
+ echo "----------------------------------------"
+
+ # 추천 실기 목록 출력
+ echo "추천 실기 (이름을 직접 복사해서 입력하셔도 됩니다):"
+ echo "github # GitHub 문제/PR/CI 관리(gh CLI)"
+ echo "notion # Notion 페이지, 데이터베이스 및 블록을 조작합니다."
+ echo "apple-notes # macOS 기본 노트 관리(생성/편집/검색)"
+ echo "apple-reminders # macOS 미리 알림 관리(할 일 목록)"
+ echo "1password # 1Password 키 읽기 및 삽입 자동화"
+ echo "gog # Google Workspace(Gmail/클라우드 디스크/문서) 만능 도우미"
+ echo "things-mac # Things 3 작업 관리의 심층 통합"
+ echo "bluebubbles # BlueBubbles를 사용하여 iMessage를 완벽하게 보내고 받습니다."
+ echo "히말라야 # 터미널 메일 관리(IMAP/SMTP 강력한 도구)"
+ echo "summary # 웹페이지/팟캐스트/YouTube 비디오 콘텐츠를 원클릭으로 요약"
+ echo "openhue # Philips Hue 스마트 조명 장면 제어"
+ echo "video-frames # 비디오 프레임 추출 및 짧은 클립 편집(ffmpeg 드라이버)"
+ echo "openai-whisper # 로컬 오디오를 텍스트로 변환(오프라인 개인 정보 보호)"
+ echo "코딩 에이전트 # Claude Code/Codex와 같은 프로그래밍 도우미를 자동으로 실행합니다."
+ echo "----------------------------------------"
+
+ echo "1) 설치 기술"
+ echo "2) 스킬 삭제"
+ echo "0) 복귀"
+ read -e -p "작업을 선택하십시오:" skill_action
+
+ [ "$skill_action" = "0" ] && break
+ [ -z "$skill_action" ] && continue
+
+ read -e -p "스킬 이름을 입력하십시오(공백으로 구분, 종료하려면 0 입력):" skill_input
+ [ "$skill_input" = "0" ] && break
+ [ -z "$skill_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ if [ "$skill_action" = "2" ]; then
+ read -e -p "보조 확인: 삭제는 사용자 디렉터리 ~/.openclaw/workspace/skills에만 영향을 미칩니다. 계속하시겠습니까? (예/아니요):" confirm_del
+ if [[ ! "$confirm_del" =~ ^[Yy]$ ]]; then
+ echo "삭제가 취소되었습니다."
+ break_end
+ continue
+ fi
+ fi
- local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
- local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ for token in $skill_input; do
+ local skill_name
+ skill_name="$token"
+ [ -z "$skill_name" ] && continue
+
+ if [ "$skill_action" = "1" ]; then
+ local skill_found=false
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ echo "💡 스킬 [$skill_name]는 사용자 디렉토리에 설치됩니다."
+ skill_found=true
+ elif [ -d "/usr/lib/node_modules/openclaw/skills/${skill_name}" ]; then
+ echo "💡 스킬 [$skill_name]는 시스템 디렉토리에 설치됩니다."
+ skill_found=true
+ fi
- 30)
- local docker_name="photoprism"
- local docker_img="photoprism/photoprism:latest"
- local docker_port=2342
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
-
- docker_rum() {
-
- docker run -d \
- --name photoprism \
- --restart always \
- --security-opt seccomp=unconfined \
- --security-opt apparmor=unconfined \
- -p ${docker_port}:2342 \
- -e PHOTOPRISM_UPLOAD_NSFW="true" \
- -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
- -v /home/docker/photoprism/storage:/photoprism/storage \
- -v /home/docker/photoprism/Pictures:/photoprism/originals \
- photoprism/photoprism
+ if [ "$skill_found" = true ]; then
+ read -e -p "스킬 [$skill_name] 이미 설치되어 있습니다. 다시 설치하시겠습니까? (예/아니요):" reinstall
+ if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
+ skipped_list="$skipped_list $skill_name"
+ continue
+ fi
+ fi
- }
+ echo "기술 설치:$skill_name ..."
+ if npx clawhub install "$skill_name" --yes --no-input 2>/dev/null || npx clawhub install "$skill_name"; then
+ echo "✅ 스킬$skill_name설치가 성공했습니다."
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "❌ 설치 실패:$skill_name"
+ failed_list="$failed_list $skill_name"
+ fi
+ else
+ echo "🗑️ 스킬 삭제:$skill_name"
+ npx clawhub uninstall "$skill_name" --yes --no-input 2>/dev/null || npx clawhub uninstall "$skill_name" >/dev/null 2>&1
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ rm -rf "${HOME}/.openclaw/workspace/skills/${skill_name}"
+ echo "✅ 사용자 스킬 디렉토리가 삭제되었습니다:$skill_name"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "⏭️ 사용자 스킬 디렉터리를 찾을 수 없습니다:$skill_name"
+ skipped_list="$skipped_list $skill_name"
+ fi
+ fi
+ done
+ echo ""
+ echo "====== 작업 요약 ======"
+ echo "✅ 성공:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 실패:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 건너뛰기:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 변경 사항을 로드하기 위해 OpenClaw 서비스를 다시 시작하는 중..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
- local docker_describe="photoprism非常强大的私有相册系统"
- local docker_url="官网介绍: https://www.photoprism.app/"
- local docker_use="echo \"账号: admin 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+openclaw_json_get_bool() {
+ local expr="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r "$expr" "$config_file" 2>/dev/null || echo "false"
+ }
+ openclaw_channel_has_cfg() {
+ local channel="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r --arg c "$channel" '
+ (.channels[$c] // null) as $v
+ | if ($v | type) != "object" then
+ false
+ else
+ ([ $v
+ | to_entries[]
+ | select((.key == "enabled" or .key == "dmPolicy" or .key == "groupPolicy" or .key == "streaming") | not)
+ | .value
+ | select(. != null and . != "" and . != false)
+ ] | length) > 0
+ end
+ ' "$config_file" 2>/dev/null || echo "false"
+ }
- 31)
- local docker_name="s-pdf"
- local docker_img="frooodle/s-pdf:latest"
- local docker_port=8020
-
- docker_rum() {
-
- docker run -d \
- --name s-pdf \
- --restart=always \
- -p ${docker_port}:8080 \
- -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
- -v /home/docker/s-pdf/extraConfigs:/configs \
- -v /home/docker/s-pdf/logs:/logs \
- -e DOCKER_ENABLE_SECURITY=false \
- frooodle/s-pdf:latest
- }
+ openclaw_dir_has_files() {
+ local dir="$1"
+ [ -d "$dir" ] && find "$dir" -type f -print -quit 2>/dev/null | grep -q .
+ }
- local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
- local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ openclaw_plugin_local_installed() {
+ local plugin="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -s "$config_file" ] && jq -e --arg p "$plugin" '.plugins.installs[$p]' "$config_file" >/dev/null 2>&1; then
+ return 0
+ fi
- 32)
- local docker_name="drawio"
- local docker_img="jgraph/drawio"
- local docker_port=7080
+ # 두 가지 일반적인 디렉터리 이름과 호환됩니다.
+ # - ~/.openclaw/extensions/qqbot
+ # - ~/.openclaw/extensions/openclaw-qqbot
+ # 두뇌 없는 하위 문자열을 피하고 정확한 일치와 openclaw- 접두사 일치를 선호합니다.
+ [ -d "${HOME}/.openclaw/extensions/${plugin}" ] \
+ || [ -d "${HOME}/.openclaw/extensions/openclaw-${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/openclaw-${plugin}" ]
+ }
- docker_rum() {
+ openclaw_bot_status_text() {
+ local enabled="$1"
+ local configured="$2"
+ local connected="$3"
+ local abnormal="$4"
+ if [ "$abnormal" = "true" ]; then
+ echo "이상"
+ elif [ "$enabled" != "true" ]; then
+ echo "활성화되지 않음"
+ elif [ "$connected" = "true" ]; then
+ echo "연결됨"
+ elif [ "$configured" = "true" ]; then
+ echo "구성된"
+ else
+ echo "구성되지 않음"
+ fi
+ }
- docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+ openclaw_colorize_bot_status() {
+ local status="$1"
+ case "$status" in
+ 已连接) echo -e "${gl_lv}${status}${gl_bai}" ;;
+ 已配置) echo -e "${gl_huang}${status}${gl_bai}" ;;
+ 异常) echo -e "${gl_hong}${status}${gl_bai}" ;;
+ *) echo "$status" ;;
+ esac
+ }
- }
+ openclaw_print_bot_status_line() {
+ local label="$1"
+ local status="$2"
+ echo -e "- ${label}: $(openclaw_colorize_bot_status "$status")"
+ }
+ openclaw_show_bot_local_status_block() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local json_ok="false"
+ if [ -s "$config_file" ] && jq empty "$config_file" >/dev/null 2>&1; then
+ json_ok="true"
+ fi
- local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
- local docker_url="官网介绍: https://www.drawio.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local tg_enabled tg_cfg tg_connected tg_abnormal tg_status
+ tg_enabled=$(openclaw_json_get_bool '.channels.telegram.enabled // .plugins.entries.telegram.enabled // false')
+ tg_cfg=$(openclaw_channel_has_cfg "telegram")
+ tg_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/telegram"; then
+ tg_connected="true"
+ fi
+ tg_abnormal="false"
+ if [ "$tg_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ tg_abnormal="true"
+ fi
+ tg_status=$(openclaw_bot_status_text "$tg_enabled" "$tg_cfg" "$tg_connected" "$tg_abnormal")
- 33)
- local docker_name="sun-panel"
- local docker_img="hslr/sun-panel"
- local docker_port=3009
+ local feishu_enabled feishu_cfg feishu_connected feishu_abnormal feishu_status
+ feishu_enabled=$(openclaw_json_get_bool '.plugins.entries.feishu.enabled // .plugins.entries["openclaw-lark"].enabled // .channels.feishu.enabled // .channels.lark.enabled // false')
+ feishu_cfg=$(openclaw_channel_has_cfg "feishu")
+ if [ "$feishu_cfg" != "true" ]; then
+ feishu_cfg=$(openclaw_channel_has_cfg "lark")
+ fi
+ feishu_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/feishu" || openclaw_dir_has_files "${HOME}/.openclaw/lark" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-lark"; then
+ feishu_connected="true"
+ fi
+ feishu_abnormal="false"
+ if [ "$feishu_enabled" = "true" ] && ! openclaw_plugin_local_installed "feishu" && ! openclaw_plugin_local_installed "lark" && ! openclaw_plugin_local_installed "openclaw-lark"; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_connected" != "true" ] && [ "$feishu_enabled" = "true" ] && [ "$feishu_cfg" = "true" ] && { openclaw_plugin_local_installed "feishu" || openclaw_plugin_local_installed "lark" || openclaw_plugin_local_installed "openclaw-lark"; }; then
+ feishu_connected="true"
+ fi
+ feishu_status=$(openclaw_bot_status_text "$feishu_enabled" "$feishu_cfg" "$feishu_connected" "$feishu_abnormal")
+
+ local wa_enabled wa_cfg wa_connected wa_abnormal wa_status
+ wa_enabled=$(openclaw_json_get_bool '.plugins.entries.whatsapp.enabled // .channels.whatsapp.enabled // false')
+ wa_cfg=$(openclaw_channel_has_cfg "whatsapp")
+ wa_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/whatsapp"; then
+ wa_connected="true"
+ fi
+ wa_abnormal="false"
+ if [ "$wa_enabled" = "true" ] && ! openclaw_plugin_local_installed "whatsapp"; then
+ wa_abnormal="true"
+ fi
+ if [ "$wa_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wa_abnormal="true"
+ fi
+ wa_status=$(openclaw_bot_status_text "$wa_enabled" "$wa_cfg" "$wa_connected" "$wa_abnormal")
+
+ local dc_enabled dc_cfg dc_connected dc_abnormal dc_status
+ dc_enabled=$(openclaw_json_get_bool '.channels.discord.enabled // .plugins.entries.discord.enabled // false')
+ dc_cfg=$(openclaw_channel_has_cfg "discord")
+ dc_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/discord"; then
+ dc_connected="true"
+ fi
+ dc_abnormal="false"
+ if [ "$dc_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ dc_abnormal="true"
+ fi
+ dc_status=$(openclaw_bot_status_text "$dc_enabled" "$dc_cfg" "$dc_connected" "$dc_abnormal")
+
+ local slack_enabled slack_cfg slack_connected slack_abnormal slack_status
+ slack_enabled=$(openclaw_json_get_bool '.plugins.entries.slack.enabled // .channels.slack.enabled // false')
+ slack_cfg=$(openclaw_channel_has_cfg "slack")
+ slack_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/slack"; then
+ slack_connected="true"
+ fi
+ slack_abnormal="false"
+ if [ "$slack_enabled" = "true" ] && ! openclaw_plugin_local_installed "slack"; then
+ slack_abnormal="true"
+ fi
+ if [ "$slack_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ slack_abnormal="true"
+ fi
+ slack_status=$(openclaw_bot_status_text "$slack_enabled" "$slack_cfg" "$slack_connected" "$slack_abnormal")
+
+ local qq_enabled qq_cfg qq_connected qq_abnormal qq_status
+ qq_enabled=$(openclaw_json_get_bool '.plugins.entries.qqbot.enabled // .channels.qqbot.enabled // false')
+ qq_cfg=$(openclaw_channel_has_cfg "qqbot")
+ qq_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/qqbot/sessions" || openclaw_dir_has_files "${HOME}/.openclaw/qqbot/data"; then
+ qq_connected="true"
+ fi
+ qq_abnormal="false"
+ if [ "$qq_enabled" = "true" ] && ! openclaw_plugin_local_installed "qqbot"; then
+ qq_abnormal="true"
+ fi
+ if [ "$qq_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ qq_abnormal="true"
+ fi
+ qq_status=$(openclaw_bot_status_text "$qq_enabled" "$qq_cfg" "$qq_connected" "$qq_abnormal")
+
+ local wx_enabled wx_cfg wx_connected wx_abnormal wx_status
+ wx_enabled=$(openclaw_json_get_bool '.plugins.entries.weixin.enabled // .plugins.entries["openclaw-weixin"].enabled // .channels.weixin.enabled // .channels["openclaw-weixin"].enabled // false')
+ wx_cfg=$(openclaw_channel_has_cfg "weixin")
+ if [ "$wx_cfg" != "true" ]; then
+ wx_cfg=$(openclaw_channel_has_cfg "openclaw-weixin")
+ fi
+ wx_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/weixin" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-weixin"; then
+ wx_connected="true"
+ fi
+ wx_abnormal="false"
+ if [ "$wx_enabled" = "true" ] && ! openclaw_plugin_local_installed "weixin" && ! openclaw_plugin_local_installed "openclaw-weixin"; then
+ wx_abnormal="true"
+ fi
+ if [ "$wx_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wx_abnormal="true"
+ fi
+ wx_status=$(openclaw_bot_status_text "$wx_enabled" "$wx_cfg" "$wx_connected" "$wx_abnormal")
+
+ echo "로컬 상태(로컬 구성/캐시만, 네트워크 감지 없음):"
+ openclaw_print_bot_status_line "Telegram" "$tg_status"
+ openclaw_print_bot_status_line "종달새" "$feishu_status"
+ openclaw_print_bot_status_line "WhatsApp" "$wa_status"
+ openclaw_print_bot_status_line "Discord" "$dc_status"
+ openclaw_print_bot_status_line "Slack" "$slack_status"
+ openclaw_print_bot_status_line "QQ Bot" "$qq_status"
+ openclaw_print_bot_status_line "위챗(웨이신)" "$wx_status"
+ }
+
+ change_tg_bot_code() {
+ send_stats "로봇 도킹"
+ while true; do
+ clear
+ echo "========================================"
+ echo "로봇 연결 및 도킹"
+ echo "========================================"
+ openclaw_show_bot_local_status_block
+ echo "----------------------------------------"
+ echo "1. 텔레그램 로봇 도킹"
+ echo "2. Feishu(Lark) 로봇 도킹"
+ echo "3. WhatsApp 봇 도킹"
+ echo "4. QQ 로봇 도킹"
+ echo "5. 위챗 로봇 도킹"
+ echo "----------------------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "----------------------------------------"
+ read -e -p "선택사항을 입력하세요:" bot_choice
+
+ case $bot_choice in
+ 1)
+ read -e -p "TG 로봇이 수신한 연결 코드(예: NYA99R2F)를 입력하세요(종료하려면 0 입력)." code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "오류: 연결 코드는 비워둘 수 없습니다."; sleep 1; continue; fi
+ openclaw pairing approve telegram "$code"
+ break_end
+ ;;
+ 2)
+ npx -y @larksuite/openclaw-lark install
+ openclaw config set channels.feishu.streaming true
+ openclaw config set channels.feishu.requireMention true --json
+ break_end
+ ;;
+ 3)
+ read -e -p "WhatsApp에서 받은 연결 코드(예: NYA99R2F)를 입력하세요(종료하려면 0 입력)." code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "오류: 연결 코드는 비워둘 수 없습니다."; sleep 1; continue; fi
+ openclaw pairing approve whatsapp "$code"
+ break_end
+ ;;
+ 4)
+ echo "QQ 공식 도킹 주소:"
+ echo "https://q.qq.com/qqbot/openclaw/login.html"
+ break_end
+ ;;
+ 5)
+ npx -y @tencent-weixin/openclaw-weixin-cli@latest install
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ openclaw_backup_root() {
+ echo "${HOME}/.openclaw/backups"
+ }
+
+ openclaw_is_interactive_terminal() {
+ [ -t 0 ] && [ -t 1 ]
+ }
+
+ openclaw_has_command() {
+ command -v "$1" >/dev/null 2>&1
+ }
+
+
+ openclaw_is_safe_relpath() {
+ local rel="$1"
+ [ -z "$rel" ] && return 1
+ [[ "$rel" = /* ]] && return 1
+ [[ "$rel" == *"//"* ]] && return 1
+ [[ "$rel" == *$'\n'* ]] && return 1
+ [[ "$rel" == *$'\r'* ]] && return 1
+ case "$rel" in
+ ../*|*/../*|*/..|..)
+ return 1
+ ;;
+ esac
+ return 0
+ }
+
+ openclaw_restore_path_allowed() {
+ local mode="$1"
+ local rel="$2"
+ case "$mode" in
+ memory)
+ case "$rel" in
+ MEMORY.md|AGENTS.md|USER.md|SOUL.md|TOOLS.md|memory/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ project)
+ case "$rel" in
+ openclaw.json|workspace/*|extensions/*|skills/*|prompts/*|tools/*|telegram/*|feishu/*|whatsapp/*|discord/*|slack/*|qqbot/*|logs/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ }
+
+ openclaw_pack_backup_archive() {
+ local backup_type="$1"
+ local export_mode="$2"
+ local payload_dir="$3"
+ local output_file="$4"
+
+ local tmp_root
+ tmp_root=$(mktemp -d) || return 1
+ local pack_dir="$tmp_root/package"
+ mkdir -p "$pack_dir"
+
+ cp -a "$payload_dir" "$pack_dir/payload"
+
+ (
+ cd "$pack_dir/payload" || exit 1
+ find . -type f | sed 's|^\./||' | sort > "$pack_dir/manifest.files"
+ : > "$pack_dir/manifest.sha256"
+ while IFS= read -r f; do
+ [ -z "$f" ] && continue
+ sha256sum "$f" >> "$pack_dir/manifest.sha256"
+ done < "$pack_dir/manifest.files"
+ ) || { rm -rf "$tmp_root"; return 1; }
+
+ cat > "$pack_dir/backup.meta" </dev/null
+ ) || { echo "❌ sha256 확인이 실패하고 복원이 거부되었습니다."; return 1; }
+
+ echo "$pkg_dir"
+ return 0
+ }
+
+ openclaw_get_all_agent_workspaces() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json, sys, os
+try:
+ with open(sys.argv[1]) as f: data = json.load(f)
+ agents = data.get("agents", {}).get("list", [])
+ results = [{"id": "main", "ws": os.path.expanduser("~/.openclaw/workspace")}]
+ for a in agents:
+ aid = a.get("id"); ws = a.get("workspace")
+ if aid and ws and aid != "main": results.append({"id": aid, "ws": os.path.expanduser(ws)})
+ print(json.dumps(results))
+except: print("[]")
+PY
+ else
+ echo '[{"id": "main", "ws": "'"${HOME}"'/.openclaw/workspace"}]'
+ fi
+ }
+
+ openclaw_memory_backup_export() {
+ send_stats "OpenClaw 메모리 전체 백업"
+ local backup_root=$(openclaw_backup_root)
+ local ts=$(date +%Y%m%d-%H%M%S)
+ local out_file="$backup_root/openclaw-memory-full-${ts}.tar.gz"
+ mkdir -p "$backup_root"
+ local tmp_payload=$(mktemp -d) || return 1
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c "import json, sys, os, shutil;
+workspaces = json.loads(sys.argv[1]); tmp_payload = sys.argv[2]
+for item in workspaces:
+ aid = item['id']; ws = item['ws']
+ if not os.path.isdir(ws): continue
+ target_dir = os.path.join(tmp_payload, 'agents', aid)
+ os.makedirs(target_dir, exist_ok=True)
+ for f in ['MEMORY.md', 'memory']:
+ src = os.path.join(ws, f)
+ if os.path.exists(src):
+ if os.path.isfile(src): shutil.copy2(src, target_dir)
+ else: shutil.copytree(src, os.path.join(target_dir, f), dirs_exist_ok=True)
+" "$workspaces_json" "$tmp_payload"
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 백업 메모리 파일이 없습니다."; rm -rf "$tmp_payload"; break_end; return 1
+ fi
+ if openclaw_pack_backup_archive "memory-full" "multi-agent" "$tmp_payload" "$out_file"; then
+ echo "✅ 전체 메모리 백업 완료(멀티 에이전트 포함):$out_file"; openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ 전체 메모리 백업 실패"
+ fi
+ rm -rf "$tmp_payload"; break_end
+ }
+
+ openclaw_memory_backup_import() {
+ send_stats "OpenClaw 메모리 전체 복원"
+ local archive_path=$(openclaw_read_import_path "전체 메모리 양 복원(멀티 에이전트 지원)")
+ [ -z "$archive_path" ] && { echo "❌ 입력된 경로가 없습니다."; break_end; return 1; }
+ local tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir=$(openclaw_prepare_import_archive "memory-full" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c 'import json, sys, os, shutil;
+workspaces = {item["id"]: item["ws"] for item in json.loads(sys.argv[1])};
+payload_dir = sys.argv[2]; agents_root = os.path.join(payload_dir, "agents")
+if os.path.isdir(agents_root):
+ for aid in os.listdir(agents_root):
+ if aid in workspaces:
+ src_agent_dir = os.path.join(agents_root, aid); dest_ws = workspaces[aid]
+ os.makedirs(dest_ws, exist_ok=True)
+ for f in os.listdir(src_agent_dir):
+ src = os.path.join(src_agent_dir, f); dest = os.path.join(dest_ws, f)
+ if os.path.isfile(src): shutil.copy2(src, dest)
+ else: shutil.copytree(src, dest, dirs_exist_ok=True)
+ print(f"✅ 에이전트 메모리 복원됨: {aid}")' "$workspaces_json" "$pkg_dir/payload"
+ rm -rf "$tmp_unpack"; echo "✅ 메모리 전체 복구 완료"; break_end
+ }
+
+
+ openclaw_project_backup_export() {
+ send_stats "OpenClaw 프로젝트 백업"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ if [ ! -d "$openclaw_root" ]; then
+ echo "❌ OpenClaw 루트 디렉터리를 찾을 수 없습니다:$openclaw_root"
+ break_end
+ return 1
+ fi
+
+ echo "백업 모드:"
+ echo "1. 안전 모드(기본값, 권장): 작업 공간 + openclaw.json + 확장/기술/프롬프트/도구(존재하는 경우)"
+ echo "2. 전체 모드(더 많은 상태 포함, 더 높은 민감도 위험)"
+ read -e -p "백업 모드를 선택하십시오(기본값 1):" export_mode
+ [ -z "$export_mode" ] && export_mode="1"
+
+ local mode_label="safe"
+ local tmp_payload
+ tmp_payload=$(mktemp -d) || return 1
+
+ if [ "$export_mode" = "2" ]; then
+ mode_label="full"
+ for d in workspace extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in telegram feishu whatsapp discord slack qqbot logs; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ else
+ [ -d "$openclaw_root/workspace" ] && cp -a "$openclaw_root/workspace" "$tmp_payload/"
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ fi
+
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 백업 가능한 OpenClaw 프로젝트 콘텐츠가 없습니다."
+ rm -rf "$tmp_payload"
+ break_end
+ return 1
+ fi
+
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ local out_file="$backup_root/openclaw-project-${mode_label}-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ if openclaw_pack_backup_archive "openclaw-project" "$mode_label" "$tmp_payload" "$out_file"; then
+ echo "✅ OpenClaw 프로젝트 백업 완료 (${mode_label}): $out_file"
+ openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ OpenClaw 프로젝트 백업 실패"
+ fi
+
+ rm -rf "$tmp_payload"
+ break_end
+ }
+
+ openclaw_project_backup_import() {
+ send_stats "OpenClaw 프로젝트 복원"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ mkdir -p "$openclaw_root"
+
+ echo "⚠️ 고위험 작업: 프로젝트 복원은 OpenClaw 구성 및 작업 공간 콘텐츠를 덮어씁니다."
+ echo "⚠️ 복원 전 매니페스트/sha256 확인, 화이트리스트 복원, 게이트웨이 종료 및 상태 점검이 수행됩니다."
+ read -e -p "계속하려면 확인 단어 [위험도가 높다는 것을 알고 있으며 복원을 계속합니다]를 입력하십시오." confirm_text
+ if [ "$confirm_text" != "위험성이 높다는 것을 인지하고 복원을 계속하고 있습니다." ]; then
+ echo "❌ 확인문자가 일치하지 않아 복원이 취소되었습니다."
+ break_end
+ return 1
+ fi
+
+ local archive_path
+ archive_path=$(openclaw_read_import_path "OpenClaw 프로젝트 백업 패키지 경로를 입력해주세요")
+ [ -z "$archive_path" ] && { echo "❌ 백업 경로가 입력되지 않았습니다."; break_end; return 1; }
+
+ local tmp_unpack
+ tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir
+ pkg_dir=$(openclaw_prepare_import_archive "openclaw-project" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+
+ local invalid=0
+ local valid_list
+ valid_list=$(mktemp)
+ while IFS= read -r rel; do
+ [ -z "$rel" ] && continue
+ if ! openclaw_is_safe_relpath "$rel" || ! openclaw_restore_path_allowed project "$rel"; then
+ echo "❌ 불법 또는 승인되지 않은 경로가 감지되었습니다:$rel"
+ invalid=1
+ break
+ fi
+ echo "$rel" >> "$valid_list"
+ done < "$pkg_dir/manifest.files"
+
+ if [ "$invalid" -ne 0 ]; then
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "❌ 복원이 중단되었습니다: 안전하지 않은 경로가 존재합니다."
+ break_end
+ return 1
+ fi
+
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "⏸️ 복원하기 전에 OpenClaw 게이트웨이를 중지하세요..."
+ openclaw gateway stop >/dev/null 2>&1
+ fi
+
+ while IFS= read -r rel; do
+ mkdir -p "$openclaw_root/$(dirname "$rel")"
+ cp -a "$pkg_dir/payload/$rel" "$openclaw_root/$rel"
+ done < "$valid_list"
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "▶️ 복원 후 OpenClaw 게이트웨이를 시작하세요..."
+ openclaw gateway start >/dev/null 2>&1
+ sleep 2
+ echo "🩺 게이트웨이 상태 점검:"
+ openclaw gateway status || true
+ fi
+
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "✅ OpenClaw 프로젝트 복원 완료"
+ break_end
+ }
+
+ openclaw_backup_detect_type() {
+ local file_name="$1"
+ if [[ "$file_name" == openclaw-memory-full-*.tar.gz ]]; then
+ echo "메모리 백업 파일"
+ elif [[ "$file_name" == openclaw-project-*.tar.gz ]]; then
+ echo "프로젝트 백업 파일"
+ else
+ echo "기타 백업 파일"
+ fi
+ }
+
+ openclaw_backup_collect_files() {
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ mapfile -t OPENCLAW_BACKUP_FILES < <(find "$backup_root" -maxdepth 1 -type f -name '*.tar.gz' -printf '%f\n' | sort -r)
+ }
+
+
+ openclaw_backup_render_file_list() {
+ local backup_root i file_name file_path file_type file_size file_time
+ local has_memory=0 has_project=0 has_other=0
+ backup_root=$(openclaw_backup_root)
+ openclaw_backup_collect_files
+
+ echo "백업 디렉터리:$backup_root"
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ echo "아직 백업 파일이 없습니다."
+ return 0
+ fi
+
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_type=$(openclaw_backup_detect_type "${OPENCLAW_BACKUP_FILES[$i]}")
+ case "$file_type" in
+ "메모리 백업 파일") has_memory=1 ;;
+ "프로젝트 백업 파일") has_project=1 ;;
+ "기타 백업 파일") has_other=1 ;;
+ esac
+ done
+
+ if [ "$has_memory" -eq 1 ]; then
+ echo "메모리 백업 파일"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "메모리 백업 파일" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_project" -eq 1 ]; then
+ echo "프로젝트 백업 파일"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "프로젝트 백업 파일" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_other" -eq 1 ]; then
+ echo "기타 백업 파일"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "기타 백업 파일" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+ }
+
+ openclaw_backup_file_exists_in_list() {
+ local target_file="$1"
+ local item
+ for item in "${OPENCLAW_BACKUP_FILES[@]}"; do
+ [ "$item" = "$target_file" ] && return 0
+ done
+ return 1
+ }
+
+ openclaw_backup_delete_file() {
+ send_stats "OpenClaw 삭제 백업 파일"
+ local backup_root backup_root_real user_input target_file target_path target_type
+ backup_root=$(openclaw_backup_root)
+
+ openclaw_backup_render_file_list
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ break_end
+ return 0
+ fi
+
+ read -e -p "삭제할 파일 이름이나 전체 경로를 입력하십시오. (취소하려면 0):" user_input
+ if [ "$user_input" = "0" ]; then
+ echo "삭제가 취소되었습니다."
+ break_end
+ return 0
+ fi
+ if [ -z "$user_input" ]; then
+ echo "❌ 입력은 비워둘 수 없습니다."
+ break_end
+ return 1
+ fi
+
+ backup_root_real=$(realpath -m "$backup_root")
+ if [[ "$user_input" == /* ]]; then
+ target_path=$(realpath -m "$user_input")
+ case "$target_path" in
+ "$backup_root_real"/*) ;;
+ *)
+ echo "❌ 경로 초과: 백업 루트 디렉터리에 있는 파일만 삭제가 허용됩니다."
+ break_end
+ return 1
+ ;;
+ esac
+ target_file=$(basename "$target_path")
+ else
+ target_file=$(basename -- "$user_input")
+ target_path="$backup_root/$target_file"
+ fi
+
+ if [ ! -f "$target_path" ]; then
+ echo "❌ 대상 파일이 존재하지 않습니다:$target_path"
+ break_end
+ return 1
+ fi
+
+ if ! openclaw_backup_file_exists_in_list "$target_file"; then
+ echo "❌ 현재 백업 목록에 대상 파일이 없습니다."
+ break_end
+ return 1
+ fi
+
+ target_type=$(openclaw_backup_detect_type "$target_file")
+
+ echo "삭제 예정: [$target_type] $target_path"
+ read -e -p "첫 번째 확인: 확인하고 계속하려면 yes를 입력하세요." confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "삭제가 취소되었습니다."
+ break_end
+ return 0
+ fi
+ read -e -p "보조 확인: 삭제하려면 DELETE를 입력하세요." confirm_step2
+ if [ "$confirm_step2" != "DELETE" ]; then
+ echo "삭제가 취소되었습니다."
+ break_end
+ return 0
+ fi
+
+ if rm -f -- "$target_path"; then
+ echo "✅ 삭제 성공:$target_file"
+ else
+ echo "❌ 삭제 실패:$target_file"
+ fi
+ break_end
+ }
+
+ openclaw_backup_list_files() {
+ openclaw_backup_render_file_list
+ break_end
+ }
+
+ openclaw_memory_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_memory_config_get() {
+ local key="$1"
+ local default_value="${2:-}"
+ local value
+ value=$(openclaw config get "$key" 2>/dev/null | head -n 1 | sed -e 's/^"//' -e 's/"$//')
+ if [ -z "$value" ] || [ "$value" = "null" ] || [ "$value" = "undefined" ]; then
+ echo "$default_value"
+ return 0
+ fi
+ echo "$value"
+ }
+
+ openclaw_memory_config_set() {
+ local key="$1"
+ shift
+ openclaw config set "$key" "$@" >/dev/null 2>&1
+ }
+
+ openclaw_memory_config_unset() {
+ local key="$1"
+ openclaw config unset "$key" >/dev/null 2>&1
+ }
+
+ openclaw_memory_cleanup_legacy_keys() {
+ openclaw_memory_config_unset "memory.local"
+ }
+
+ openclaw_memory_list_agents() {
+ if command -v openclaw >/dev/null 2>&1; then
+ local agents_json
+ agents_json=$(openclaw agents list --json 2>/dev/null || true)
+ if [ -n "$agents_json" ]; then
+ python3 - "$agents_json" <<'PY'
+import json, os, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ data = None
+seen = set()
+results = []
+if isinstance(data, list):
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ if not aid or aid in seen:
+ continue
+ ws = item.get('workspace') or ("~/.openclaw/workspace" if aid == 'main' else f"~/.openclaw/workspace-{aid}")
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+if results:
+ for aid, ws in results:
+ print(f"{aid}\t{ws}")
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+ [ $? -eq 0 ] && return 0
+ fi
+ fi
+ local config_path
+ config_path=$(openclaw_memory_config_file)
+ python3 - "$config_path" <<'PY'
+import json, os, sys
+config_path = sys.argv[1]
+results = [("main", os.path.expanduser("~/.openclaw/workspace"))]
+seen = {"main"}
+try:
+ if os.path.exists(config_path):
+ with open(config_path, encoding='utf-8') as f:
+ data = json.load(f)
+ agents = data.get('agents', {}).get('list', [])
+ if isinstance(agents, list):
+ for item in agents:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ ws = item.get('workspace')
+ if not aid or aid in seen:
+ continue
+ if not ws:
+ ws = f"~/.openclaw/workspace-{aid}"
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+except Exception:
+ pass
+for aid, ws in results:
+ print(f"{aid}\t{ws}")
+PY
+ }
+
+ openclaw_memory_status_value() {
+ local key="$1"
+ local agent_id="${2:-}"
+ if [ -n "$agent_id" ]; then
+ openclaw memory status --agent "$agent_id" 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ else
+ openclaw memory status 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ fi
+ }
+
+ openclaw_memory_expand_path() {
+ local raw_path="$1"
+ if [ -z "$raw_path" ]; then
+ echo ""
+ return 0
+ fi
+ raw_path=$(echo "$raw_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [[ "$raw_path" == ~* ]]; then
+ echo "${raw_path/#\~/$HOME}"
+ else
+ echo "$raw_path"
+ fi
+ }
+
+ openclaw_memory_rebuild_index_single() {
+ local agent_id="${1:-main}"
+ local store_raw store_file ts backup_file
+ store_raw=$(openclaw_memory_status_value "Store" "$agent_id")
+ store_file=$(openclaw_memory_expand_path "$store_raw")
+ if [ -z "$store_file" ] || [ ! -f "$store_file" ]; then
+ echo "⚠️ [$agent_id] 인덱스 라이브러리 파일을 찾을 수 없습니다. 비어 있거나 존재하지 않을 수 있습니다."
+ echo "원시 값 저장: ${store_raw:-}"
+ echo "재인덱싱은 계속 수행됩니다."
+ else
+ ts=$(date +%Y%m%d_%H%M%S)
+ backup_file="${store_file}.bak.${ts}"
+ if mv "$store_file" "$backup_file"; then
+ echo "✅ [$agent_id] 백업된 인덱스:$backup_file"
+ else
+ echo "⚠️ [$agent_id] 인덱스 백업이 실패했습니다. 재구축을 계속하세요."
+ fi
+ fi
+ openclaw memory index --agent "$agent_id" --force
+ }
+
+ openclaw_memory_rebuild_index_safe() {
+ local agent_id="${1:-main}"
+ openclaw_memory_rebuild_index_single "$agent_id"
+ openclaw gateway restart
+ echo "✅ 인덱스가 재구축되었으며 게이트웨이가 자동으로 다시 시작되었습니다."
+ echo ""
+ openclaw_memory_render_status
+ }
+
+ openclaw_memory_rebuild_index_all() {
+ local count=0
+ local agent_lines agent_id workspace
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id workspace; do
+ [ -z "$agent_id" ] && continue
+ openclaw_memory_rebuild_index_single "$agent_id"
+ count=$((count+1))
+ done </dev/null)
+ if [ -z "$json_output" ]; then
+ echo "메모리 상태를 가져오지 못했습니다(openclaw 메모리 상태 --json 출력 없음)."
+ return 1
+ fi
+ python3 - "$json_output" <<'PY'
+import json, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ print("메모리 상태를 가져오지 못했습니다(JSON 구문 분석 오류).")
+ raise SystemExit(1)
+if not isinstance(data, list) or len(data) == 0:
+ print("에이전트 메모리 상태가 감지되지 않았습니다.")
+ raise SystemExit(0)
+first = True
+for entry in data:
+ if not isinstance(entry, dict):
+ continue
+ agent_id = entry.get("agentId", "?")
+ s = entry.get("status", {})
+ if not isinstance(s, dict):
+ s = {}
+ if not first:
+ print("")
+ first = False
+ print("Agent: %s" % agent_id)
+ backend = s.get("backend") or s.get("provider") or "-"
+ print("기본 솔루션: %s" % backend)
+ files = s.get("files", 0)
+ chunks = s.get("chunks", 0)
+ print("포함됨: %s개 파일 / %s개 블록" % (files, chunks))
+ dirty = s.get("dirty")
+ dirty_str = "예" if dirty else "아니요"
+ print("새로 고칠 예정: %s" % dirty_str)
+ vec = s.get("vector", {})
+ if isinstance(vec, dict) and vec.get("enabled"):
+ vec_str = "준비가 된" if vec.get("available") else "활성화됨(사용할 수 없음)"
+ else:
+ vec_str = "활성화되지 않음"
+ print("벡터 라이브러리: %s" % vec_str)
+ ws = s.get("workspaceDir") or "-"
+ print("작업공간: %s" % ws)
+ db = s.get("dbPath") or "-"
+ print("인덱스 라이브러리: %s" % db)
+ scan = entry.get("scan", {})
+ if isinstance(scan, dict):
+ issues = scan.get("issues", [])
+ if issues:
+ for issue in issues[:3]:
+ print(" ⚠️ %s" % issue)
+PY
+ }
+
+ openclaw_memory_get_backend() {
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "local" ]; then
+ echo "builtin"
+ else
+ echo "$backend"
+ fi
+ }
+
+ openclaw_memory_get_local_model_path() {
+ openclaw_memory_config_get "agents.defaults.memorySearch.local.modelPath"
+ }
+
+ openclaw_memory_local_model_status() {
+ local model_path="$1"
+ if [ -z "$model_path" ]; then
+ echo "missing"
+ return
+ fi
+ if [[ "$model_path" == hf:* ]]; then
+ echo "hf"
+ return
+ fi
+ if [ -f "$model_path" ]; then
+ echo "ok"
+ else
+ echo "missing"
+ fi
+ }
+
+ openclaw_memory_qmd_available() {
+ if command -v qmd >/dev/null 2>&1; then
+ echo "true"
+ return
+ fi
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "qmd" ]; then
+ echo "true"
+ return
+ fi
+ echo "false"
+ }
+
+ openclaw_memory_probe_url() {
+ local url="$1"
+ if ! command -v curl >/dev/null 2>&1; then
+ echo "unknown"
+ return
+ fi
+ if [ -z "$url" ]; then
+ echo "unknown"
+ return
+ fi
+ if curl -I -m 2 -s "$url" >/dev/null 2>&1; then
+ echo "ok"
+ else
+ echo "fail"
+ fi
+ }
+
+ openclaw_memory_recommend() {
+ local qmd_ok model_path model_status hf_ok mirror_ok
+ qmd_ok=$(openclaw_memory_qmd_available)
+ model_path=$(openclaw_memory_get_local_model_path)
+ model_status=$(openclaw_memory_local_model_status "$model_path")
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+
+ OPENCLAW_MEMORY_RECOMMEND_REASON=()
+ if [ "$qmd_ok" = "true" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD 사용 가능")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD가 감지되지 않음")
+ fi
+ if [ -n "$model_path" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("로컬 모델 경로:$model_path")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("로컬 모델 경로가 구성되지 않았습니다.")
+ fi
+ case "$model_status" in
+ ok) OPENCLAW_MEMORY_RECOMMEND_REASON+=("로컬 모델 파일이 존재합니다.") ;;
+ hf) OPENCLAW_MEMORY_RECOMMEND_REASON+=("모델은 HF 다운로드 소스에서 제공됩니다(중국에서는 느리거나 실패할 수 있음).") ;;
+ *) OPENCLAW_MEMORY_RECOMMEND_REASON+=("로컬 모델 파일이 존재하지 않거나 사용할 수 없습니다.") ;;
+ esac
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("Huggingface.co에 액세스할 수 있습니다.")
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("hf-mirror.com에 접속할 수 있습니다")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("Huggingface.co / hf-mirror.com에 연결할 수 없을 수 있습니다(국내/제한된 네트워크로 의심됨).")
+ fi
+
+ if [ "$qmd_ok" = "true" ]; then
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && { [ "$hf_ok" = "ok" ] || [ "$mirror_ok" = "ok" ]; }; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && [ "$hf_ok" = "fail" ] && [ "$mirror_ok" = "fail" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ else
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ fi
+ }
+
+
+ openclaw_memory_detect_region() {
+ OPENCLAW_MEMORY_COUNTRY="unknown"
+ OPENCLAW_MEMORY_USE_MIRROR="false"
+ if command -v curl >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_COUNTRY=$(curl -s -m 2 ipinfo.io/country | tr -d '
+' | tr -d '
+')
+ fi
+ case "$OPENCLAW_MEMORY_COUNTRY" in
+ CN|HK)
+ OPENCLAW_MEMORY_USE_MIRROR="true"
+ ;;
+ esac
+ }
+
+ openclaw_memory_select_sources() {
+ local hf_ok mirror_ok
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+ OPENCLAW_MEMORY_HF_OK="$hf_ok"
+ OPENCLAW_MEMORY_MIRROR_OK="$mirror_ok"
+ if [ "$OPENCLAW_MEMORY_USE_MIRROR" = "true" ]; then
+ if [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ elif [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://gh.kejilion.pro/"
+ else
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://"
+ fi
+ }
+
+ openclaw_memory_download_file() {
+ local url="$1"
+ local dest="$2"
+ mkdir -p "$(dirname "$dest")"
+ if command -v curl >/dev/null 2>&1; then
+ curl -L --fail --retry 2 -o "$dest" "$url"
+ return $?
+ fi
+ if command -v wget >/dev/null 2>&1; then
+ wget -O "$dest" "$url"
+ return $?
+ fi
+ echo "❌ Curl이나 wget이 감지되지 않아 다운로드할 수 없습니다."
+ return 1
+ }
+
+ openclaw_memory_check_sqlite() {
+ if ! command -v sqlite3 >/dev/null 2>&1; then
+ echo "⚠️ sqlite3이 감지되지 않으면 QMD가 제대로 작동하지 않을 수 있습니다."
+ return 1
+ fi
+ local ver
+ ver=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
+ echo "✅ sqlite3 사용 가능: ${ver:-unknown}"
+ echo "ℹ️ sqlite 확장 지원은 안정적으로 감지될 수 없으며 계속됩니다."
+ return 0
+ }
+
+ openclaw_memory_ensure_bun() {
+ if [ -x "$HOME/.bun/bin/bun" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ 롤빵이 이미 존재합니다"
+ return 0
+ fi
+ echo "⬇️ 번 설치..."
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL https://bun.sh/install | bash
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO- https://bun.sh/install | bash
+ else
+ echo "❌ 컬 또는 wget이 감지되지 않아 롤빵을 설치할 수 없습니다."
+ return 1
+ fi
+ if [ -d "$HOME/.bun/bin" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ 번 설치 완료"
+ return 0
+ fi
+ echo "❌ 번 설치 실패"
+ return 1
+ }
+
+ openclaw_memory_ensure_qmd() {
+ local qmd_path
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -n "$qmd_path" ]; then
+ if qmd --version >/dev/null 2>&1; then
+ echo "✅ qmd가 이미 존재하며 사용 가능합니다:$qmd_path"
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ return 0
+ else
+ echo "⚠️ qmd 명령이 존재하지만 모듈이 손상되었습니다. 다시 설치하세요..."
+ fi
+ fi
+ echo "⬇️ npm을 통해 qmd 설치: @tobilu/qmd"
+ npm install -g @tobilu/qmd
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -z "$qmd_path" ]; then
+ echo "❌ qmd 설치 실패"
+ return 1
+ fi
+ if ! qmd --version >/dev/null 2>&1; then
+ echo "❌ 설치 후에도 qmd를 실행할 수 없습니다."
+ return 1
+ fi
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ echo "✅ qmd 설치 완료:$qmd_path"
+ return 0
+ }
+
+ openclaw_memory_render_auto_summary() {
+ echo "---------------------------------------"
+ echo "✅ 환경 준비"
+ echo "구성표: ${OPENCLAW_MEMORY_AUTO_SCHEME:-unknown}"
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "모드: 쓰기 구성만(설치되지 않음/다운로드되지 않음)"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "색인: 실행됨"
+ else
+ echo "색인: 건너뛰었습니다."
+ fi
+ if [ "$OPENCLAW_MEMORY_RESTARTED" = "true" ]; then
+ echo "다시 시작: 실행됨"
+ else
+ echo "다시 시작: 건너뛰었습니다."
+ fi
+ if [ -n "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ echo "qmd: $OPENCLAW_MEMORY_QMD_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_MODEL_PATH" ]; then
+ echo "모델:$OPENCLAW_MEMORY_MODEL_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_COUNTRY" ]; then
+ echo "영역:$OPENCLAW_MEMORY_COUNTRY"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_HF_BASE" ]; then
+ echo "다운로드 소스:$OPENCLAW_MEMORY_HF_BASE"
+ fi
+ echo "최종 상태:"
+ openclaw_memory_render_status
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_auto_confirm() {
+ local scheme_label="$1"
+ OPENCLAW_MEMORY_PREHEAT="true"
+ OPENCLAW_MEMORY_RESTARTED="false"
+ OPENCLAW_MEMORY_CONFIG_ONLY="false"
+ echo "자동 배포가 곧 수행됩니다(상세 모드)."
+ echo "목표 계획:$scheme_label"
+ echo "지역: ${OPENCLAW_MEMORY_COUNTRY:-unknown}"
+ echo "미러 소스 감지:huggingface.co=${OPENCLAW_MEMORY_HF_OK:-unknown} hf-mirror.com=${OPENCLAW_MEMORY_MIRROR_OK:-unknown}"
+ echo "다운로드 소스: ${OPENCLAW_MEMORY_HF_BASE:-unknown}"
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_PATH" ]; then
+ echo "예상 다운로드 경로:$OPENCLAW_MEMORY_EXPECT_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_SIZE" ]; then
+ echo "가능한 트래픽/디스크 사용량:$OPENCLAW_MEMORY_EXPECT_SIZE"
+ else
+ echo "가능한 트래픽/디스크 사용량: 실제 상황에 따라 다름"
+ fi
+ echo "확인 후 자동으로 설치/다운로드, 구성 작성, 색인 작성 및 게이트웨이를 다시 시작합니다."
+ echo "고급 옵션: 구성만 작성하려면 구성을 입력하세요(설치 없음, 다운로드 없음, 인덱싱 없음, 다시 시작 없음)."
+ read -e -p "계속하려면 yes를 입력하세요(기본값 N)." confirm_step
+ case "$confirm_step" in
+ yes|YES)
+ OPENCLAW_MEMORY_PREHEAT="true"
+ ;;
+ config|CONFIG)
+ OPENCLAW_MEMORY_CONFIG_ONLY="true"
+ OPENCLAW_MEMORY_PREHEAT="false"
+ ;;
+ *)
+ echo "자동 배포가 취소되었습니다."
+ return 1
+ ;;
+ esac
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "⚠️ 설치나 다운로드 없이 구성만 작성하도록 선택됨"
+ else
+ echo "✅ 인덱스가 자동으로 구축되고 게이트웨이가 다시 시작됩니다."
+ fi
+ return 0
+ }
+
+ openclaw_memory_auto_setup_qmd() {
+ echo "🔍 QMD 환경 감지"
+ openclaw_memory_cleanup_legacy_keys
+ openclaw_memory_check_sqlite || true
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ if command -v qmd >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_QMD_PATH=$(command -v qmd)
+ else
+ OPENCLAW_MEMORY_QMD_PATH="qmd"
+ fi
+ else
+ openclaw_memory_ensure_qmd || return 1
+ fi
+ local backend
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ]; then
+ echo "✅ memory.backend는 이미 qmd입니다."
+ else
+ openclaw_memory_config_set "memory.backend" "qmd"
+ echo "✅ memory.backend=qmd가 설정되었습니다"
+ fi
+ local qmd_cmd
+ qmd_cmd=$(openclaw_memory_config_get "memory.qmd.command")
+ if [ -z "$qmd_cmd" ] || [[ "$qmd_cmd" != /* ]] || [ "$qmd_cmd" != "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ openclaw_memory_config_set "memory.qmd.command" "$OPENCLAW_MEMORY_QMD_PATH"
+ echo "✅ memory.qmd.command에 작성:$OPENCLAW_MEMORY_QMD_PATH"
+ else
+ echo "✅ memory.qmd.command가 정확합니다"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "🔥 따뜻한 지수(모델 다운로드 가능)"
+ openclaw_memory_prepare_workspace_all
+ local preh_agent_lines preh_agent_id preh_workspace
+ preh_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r preh_agent_id preh_workspace; do
+ [ -z "$preh_agent_id" ] && continue
+ openclaw memory index --agent "$preh_agent_id" --force
+ done </dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ OPENCLAW_MEMORY_RESTARTED="true"
+ openclaw_memory_render_auto_summary
+ return 0
+ }
+
+ openclaw_memory_auto_setup_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "메모리 솔루션 자동 배포"
+ echo "======================================="
+ echo "1. QMD"
+ echo "2. Local"
+ echo "3. 자동(자동 선택)"
+ echo "0. 이전 레벨로 돌아갑니다"
+ echo "---------------------------------------"
+ read -e -p "선택사항을 입력하세요:" auto_choice
+ case "$auto_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_apply_scheme() {
+ local scheme="$1"
+ openclaw_memory_cleanup_legacy_keys
+ case "$scheme" in
+ qmd)
+ openclaw_memory_config_set "memory.backend" "qmd"
+ if [ $? -ne 0 ]; then
+ echo "❌ 구성을 쓰지 못했습니다."
+ return 1
+ fi
+ openclaw_memory_config_set "memory.qmd.command" "qmd" >/dev/null 2>&1
+ ;;
+ local)
+ openclaw_memory_config_set "memory.backend" "builtin"
+ if [ $? -ne 0 ]; then
+ echo "❌ 구성을 쓰지 못했습니다."
+ return 1
+ fi
+ openclaw_memory_config_set "agents.defaults.memorySearch.provider" "local" >/dev/null 2>&1
+ ;;
+ *)
+ echo "❌ 알 수 없는 해결책:$scheme"
+ return 1
+ esac
+ echo "✅ 메모리 체계 구성이 업데이트되었습니다."
+ return 0
+ }
+
+ openclaw_memory_offer_restart() {
+ echo "구성이 작성되었으며 OpenClaw 게이트웨이를 다시 시작한 후 적용하려면 다시 시작해야 합니다."
+ read -e -p "지금 OpenClaw Gateway를 다시 시작하시겠습니까? (예/아니요):" restart_choice
+ if [[ "$restart_choice" =~ ^[Nn]$ ]]; then
+ echo "다시 시작을 건너뛰었습니다. 나중에 수행할 수 있습니다. openclaw 게이트웨이 다시 시작"
+ return 0
+ fi
+ if declare -F start_gateway >/dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ }
+
+ openclaw_memory_fix_index() {
+ local backend include_dm
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ] && ! command -v qmd >/dev/null 2>&1; then
+ echo "⚠️ 현재 구성표는 QMD로 감지되지만 qmd 명령은 설치되지 않습니다."
+ echo "로컬로 전환하거나 bun + qmd를 설치하고 다시 시도할 수 있습니다."
+ fi
+ include_dm=$(openclaw config get memory.qmd.includeDefaultMemory 2>/dev/null)
+ echo "======================================="
+ echo "인덱스 복구 진단"
+ echo "======================================="
+ echo "현재 includeDefaultMemory: ${include_dm:-not set}"
+ echo ""
+ if [ "$include_dm" = "false" ]; then
+ echo "⚠️ includeDefaultMemory=false 감지됨"
+ echo "이로 인해 기본 메모리 파일(MEMORY.md + memory/*.md)이 색인화되지 않습니다."
+ echo "따라서 Indexed는 항상 0/N을 표시합니다."
+ echo ""
+ read -e -p "true로 되돌리고 인덱스를 다시 작성하시겠습니까? (예/아니요):" fix_choice
+ if [[ ! "$fix_choice" =~ ^[Nn]$ ]]; then
+ openclaw_memory_config_set "memory.qmd.includeDefaultMemory" true
+ if [ $? -ne 0 ]; then
+ echo "❌ 구성을 쓰지 못했습니다."
+ break_end
+ return 1
+ fi
+ echo "✅ 복원된 includeDefaultMemory=true"
+ openclaw_memory_rebuild_index_all
+ else
+ echo "취소."
+ fi
+ else
+ echo "includeDefaultMemory 구성이 정상입니다."
+ echo "실행 예정: 기존 인덱스 정리 → 모든 에이전트 인덱스 완전히 재구축"
+ echo ""
+ read -e -p "실행을 확인하시겠습니까? (예/아니요):" confirm_fix
+ if [[ ! "$confirm_fix" =~ ^[Nn]$ ]]; then
+ openclaw_memory_rebuild_index_all
+ else
+ echo "취소."
+ fi
+ fi
+ break_end
+ }
+
+ openclaw_memory_scheme_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 메모리 솔루션"
+ echo "======================================="
+ local backend current_label
+ backend=$(openclaw_memory_get_backend)
+ case "$backend" in
+ qmd) current_label="QMD" ;;
+ builtin|local) current_label="Local" ;;
+ *) current_label="구성되지 않음" ;;
+ esac
+ echo "현재 계획:$current_label"
+ echo ""
+ echo "QMD: qmd 명령을 사용하는 경량 인덱스(네트워크 제약 조건에 적합)"
+ echo "로컬: 임베딩 모델 파일에 따라 달라지는 로컬 벡터 검색"
+ echo "자동: 자동 추천(가용성 + 네트워크 감지 기준)"
+ echo "---------------------------------------"
+ echo "1. QMD 전환(자동 배포/이미 설치된 경우 건너뛰기)"
+ echo "2. 로컬로 전환(자동 배포/이미 설치된 경우 건너뛰기)"
+ echo "3. 자동(자동 추천 및 자동 배포)"
+ echo "0. 이전 레벨로 돌아갑니다"
+ echo "---------------------------------------"
+ read -e -p "선택사항을 입력하세요:" scheme_choice
+ case "$scheme_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_file_collect() {
+ OPENCLAW_MEMORY_FILES=()
+ OPENCLAW_MEMORY_FILE_LABELS=()
+ local agent_lines agent_id base_dir memory_dir memory_file rel
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id base_dir; do
+ [ -z "$agent_id" ] && continue
+ memory_dir="$base_dir/memory"
+ memory_file="$base_dir/MEMORY.md"
+ if [ -f "$memory_file" ]; then
+ OPENCLAW_MEMORY_FILES+=("$memory_file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/MEMORY.md")
+ fi
+ if [ -d "$memory_dir" ]; then
+ while IFS= read -r file; do
+ [ -f "$file" ] || continue
+ rel="${file#$base_dir/}"
+ OPENCLAW_MEMORY_FILES+=("$file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/$rel")
+ done < <(find "$memory_dir" -type f -name '*.md' | sort)
+ fi
+ done </dev/null || stat -c %y "$file" | awk '{print $1" "$2}')
+ printf "%s | %s | %s | %s\\n" "$((i+1))" "$rel" "$size" "$mtime"
+ done
+ }
+
+ openclaw_memory_view_file() {
+ local file="$1"
+ [ -f "$file" ] || {
+ echo "❌ 파일이 존재하지 않습니다:$file"
+ return 1
+ }
+ local total_lines
+ total_lines=$(wc -l < "$file" 2>/dev/null || echo 0)
+ local default_lines=120
+ local start_line count
+ echo "문서:$file"
+ echo "총 행 수:$total_lines"
+ read -e -p "시작 라인을 입력하십시오. (끝까지 기본값으로 설정하려면 Enter를 누르십시오.$default_lines좋아요):" start_line
+ read -e -p "표시할 행 수를 입력하십시오(기본값은 Enter 키를 누르는 것입니다)$default_lines): " count
+ [ -z "$count" ] && count=$default_lines
+ if [ -z "$start_line" ]; then
+ if [ "$total_lines" -le "$count" ]; then
+ start_line=1
+ else
+ start_line=$((total_lines - count + 1))
+ fi
+ fi
+ if ! [[ "$start_line" =~ ^[0-9]+$ ]] || ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ echo "❌ 유효한 번호를 입력하세요."
+ return 1
+ fi
+ if [ "$start_line" -lt 1 ]; then
+ start_line=1
+ fi
+ if [ "$count" -le 0 ]; then
+ echo "❌ 행 개수는 0보다 커야 합니다."
+ return 1
+ fi
+ local end_line=$((start_line + count - 1))
+ if [ "$end_line" -gt "$total_lines" ]; then
+ end_line=$total_lines
+ fi
+ if [ "$total_lines" -eq 0 ]; then
+ echo "(빈 파일)"
+ return 0
+ fi
+ echo "---------------------------------------"
+ sed -n "${start_line},${end_line}p" "$file"
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_files_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 메모리 파일"
+ echo "======================================="
+ openclaw_memory_file_render_list
+ echo "---------------------------------------"
+ read -e -p "보려는 파일 번호를 입력하십시오(0을 반환)." file_choice
+ if [ "$file_choice" = "0" ]; then
+ return 0
+ fi
+ if ! [[ "$file_choice" =~ ^[0-9]+$ ]]; then
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ continue
+ fi
+ openclaw_memory_file_collect
+ if [ ${#OPENCLAW_MEMORY_FILES[@]} -eq 0 ]; then
+ read -p "메모리 파일을 찾을 수 없습니다. 돌아가려면 Enter를 누르세요..."
+ return 0
+ fi
+ local idx=$((file_choice-1))
+ if [ "$idx" -lt 0 ] || [ "$idx" -ge ${#OPENCLAW_MEMORY_FILES[@]} ]; then
+ echo "잘못된 번호입니다. 다시 시도해 주세요."
+ sleep 1
+ continue
+ fi
+ openclaw_memory_view_file "${OPENCLAW_MEMORY_FILES[$idx]}"
+ read -p "목록으로 돌아가려면 Enter를 누르세요..."
+ done
+ }
+
+
+ openclaw_memory_search_test() {
+ read -e -p "검색 키워드를 입력하세요:" query
+ if [ -z "$query" ]; then
+ echo "키워드는 비워둘 수 없습니다."
+ return 1
+ fi
+ echo "기억을 찾는 중..."
+ openclaw memory search "$query" --max-results 5
+ }
+
+ openclaw_memory_deep_status() {
+ echo "내장된 모델 준비 상태 감지 중..."
+ openclaw memory status --deep
+ }
+
+ openclaw_memory_menu() {
+ send_stats "OpenClaw 메모리 관리"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 메모리 관리"
+ echo "======================================="
+ openclaw_memory_render_status
+ echo "1. 메모리 인덱스 업데이트"
+ echo "2. 메모리 파일 보기"
+ echo "3. 인덱스 복구(인덱스 예외)"
+ echo "4. 메모리 솔루션(QMD/Local/Auto)"
+ echo "5. 검색 테스트(색인이 작동하는지 확인)"
+ echo "6. 심층 상태 감지(임베디드 모델 확인)"
+ echo "0. 이전 레벨로 돌아갑니다"
+ echo "---------------------------------------"
+ read -e -p "선택사항을 입력하세요:" memory_choice
+ case "$memory_choice" in
+ 1)
+ echo "메모리 인덱스는 곧 업데이트될 예정입니다."
+ read -e -p "첫 번째 확인: 계속하려면 yes를 입력하세요." confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "취소."
+ break_end
+ continue
+ fi
+ openclaw_memory_prepare_workspace_all
+ read -e -p "2차 확인: 전체 금액을 사용하려면 강제를 입력하세요(증분하려면 비워 두세요)." confirm_step2
+ if [ "$confirm_step2" = "force" ]; then
+ echo "⚠️ 전체 재구성은 더 철저하지만 시간이 더 오래 걸립니다."
+ echo "권장 사항: 안전한 재구축을 위해 재구축을 입력하세요(인덱스 데이터베이스를 먼저 백업하세요)."
+ read -e -p "세 번째 확인: 재구축을 입력하여 안전한 재구축을 수행합니다. 수직력을 계속하려면 Enter를 누르십시오." confirm_step3
+ if [ "$confirm_step3" = "rebuild" ]; then
+ openclaw_memory_rebuild_index_all
+ else
+ local fl_agent_lines fl_agent_id fl_workspace
+ fl_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r fl_agent_id fl_workspace; do
+ [ -z "$fl_agent_id" ] && continue
+ openclaw memory index --agent "$fl_agent_id" --force
+ done </dev/null 2>&1 || {
+ echo "⚠️ 권한 백업 실패:$backup_file"
+ return 1
+ }
+ echo "✅ 현재 권한 구성이 백업되었습니다:$backup_file"
+ return 0
+ }
+
+ openclaw_permission_restore_backup() {
+ local config_file backup_file
+ config_file=$(openclaw_permission_config_file)
+ backup_file=$(openclaw_permission_backup_file)
+ if [ ! -s "$backup_file" ]; then
+ echo "❌ 복원 가능한 권한 백업 파일이 없습니다."
+ return 1
+ fi
+ cp -f "$backup_file" "$config_file" >/dev/null 2>&1 || {
+ echo "❌ 권한 복구 실패:$backup_file"
+ return 1
+ }
+ echo "✅ 스위치가 복원되기 전의 권한 구성"
+ openclaw_permission_restart_gateway || true
+ return 0
+ }
+
+ openclaw_permission_restart_gateway() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ openclaw가 감지되지 않아 OpenClaw Gateway를 다시 시작할 수 없습니다."
+ return 1
+ fi
+ echo "OpenClaw 게이트웨이 다시 시작 중..."
+ openclaw gateway restart >/dev/null 2>&1 || {
+ openclaw gateway stop >/dev/null 2>&1
+ openclaw gateway start >/dev/null 2>&1
+ }
+ }
+
+ openclaw_permission_get_value() {
+ local path="$1"
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+
+ if openclaw_has_command openclaw; then
+ local value
+ value=$(openclaw config get "$path" 2>&1 | head -n 1)
+ if [ -n "$value" ]; then
+ if echo "$value" | grep -qi "config path not found"; then
+ echo "(unset)"
+ return 0
+ fi
+ if [ "$value" = "null" ]; then
+ echo "(unset)"
+ else
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ fi
+ return 0
+ fi
+ fi
+
+ [ -f "$config_file" ] || { echo "(unset)"; return 0; }
+
+ if openclaw_has_command jq; then
+ local jq_value
+ jq_value=$(jq -r --arg p "$path" 'getpath($p|split(".")) // "(unset)"' "$config_file" 2>/dev/null) || jq_value="(unset)"
+ [ "$jq_value" = "null" ] && jq_value="(unset)"
+ echo "$jq_value"
+ return 0
+ fi
+
+ if openclaw_has_command python3; then
+ python3 - "$config_file" "$path" <<'PY'
+import json, sys
+path = sys.argv[2]
+with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+cur = obj
+for part in path.split('.'):
+ if isinstance(cur, dict) and part in cur:
+ cur = cur[part]
+ else:
+ print('(unset)')
+ raise SystemExit(0)
+if isinstance(cur, bool):
+ print('true' if cur else 'false')
+elif cur is None:
+ print('(unset)')
+else:
+ print(json.dumps(cur, ensure_ascii=False) if isinstance(cur, (dict, list)) else str(cur))
+PY
+ return 0
+ fi
+
+ echo "(unset)"
+ return 0
+ }
+
+ openclaw_permission_unset_optional() {
+ local key="$1"
+ local probe
+ if ! openclaw_has_command openclaw; then
+ return 1
+ fi
+ if openclaw config unset "$key" >/dev/null 2>&1; then
+ return 0
+ fi
+ probe=$(openclaw config get "$key" 2>&1 | head -n 1)
+ if [ -z "$probe" ] || [ "$probe" = "null" ] || [ "$probe" = "(unset)" ] || echo "$probe" | grep -qi "config path not found"; then
+ return 0
+ fi
+ return 1
+ }
+
+ openclaw_permission_detect_mode() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ [ ! -f "$config_file" ] && { echo "알 수 없는 모드"; return; }
+
+ python3 - "$config_file" <<'PY'
+import json, sys
+
+def get_v(o, p):
+ for k in p.split('.'):
+ if isinstance(o, dict) and k in o:
+ o = o[k]
+ else:
+ return "(unset)"
+ return str(o).lower()
+
+try:
+ with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ d = json.load(f)
+ p = get_v(d, "tools.profile")
+ s = get_v(d, "tools.exec.security")
+ a = get_v(d, "tools.exec.ask")
+ e = get_v(d, "tools.elevated.enabled")
+ b = get_v(d, "commands.bash")
+ ap = get_v(d, "tools.exec.applyPatch.enabled")
+ w = get_v(d, "tools.exec.applyPatch.workspaceOnly")
+
+ if p == "coding" and s == "allowlist" and a == "on-miss" and e == "false" and b == "false" and ap == "false":
+ print("표준 안전 모드")
+ elif p == "coding" and s == "allowlist" and a == "on-miss" and e == "true" and b == "true" and ap == "true" and w == "true":
+ print("향상된 모드 개발")
+ elif (p == "full" or p == "(unset)") and s == "full" and a == "off" and e == "true" and b == "true" and ap == "true":
+ print("완전 개방 모드")
+ else:
+ print("커스텀 모드")
+except Exception:
+ print("커스텀 모드")
+PY
+ }
+
+ openclaw_permission_update_exec_approvals() {
+ local sec="$1"
+ local ask="$2"
+ local fallback="$3"
+ local approvals_file="$HOME/.openclaw/exec-approvals.json"
+
+ mkdir -p "$HOME/.openclaw"
+
+ # JSON을 생성하고 openclaw 승인 세트 --stdin을 통해 작성(선호)
+ # CLI가 이를 지원하지 않으면 파일을 직접 작성하는 것으로 대체됩니다.
+ local json_payload
+ json_payload=$(python3 -c '
+import json, sys, os
+path = sys.argv[1]
+try:
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ data = json.load(f)
+ else:
+ data = {"version": 1, "defaults": {}}
+except Exception:
+ data = {"version": 1, "defaults": {}}
+if "defaults" not in data:
+ data["defaults"] = {}
+data["defaults"]["security"] = sys.argv[2]
+data["defaults"]["ask"] = sys.argv[3]
+data["defaults"]["askFallback"] = sys.argv[4]
+data["defaults"]["autoAllowSkills"] = True
+print(json.dumps(data, indent=2))
+' "$approvals_file" "$sec" "$ask" "$fallback")
+
+ if openclaw_has_command openclaw && echo "$json_payload" | openclaw approvals set --stdin >/dev/null 2>&1; then
+ return 0
+ fi
+ # 대체: 파일을 직접 작성
+ echo "$json_payload" > "$approvals_file"
+ }
+
+ openclaw_permission_render_status() {
+ echo "애플리케이션 계층 구성: ~/.openclaw/openclaw.json"
+ echo "호스트 승인: ~/.openclaw/exec-approvals.json"
+ echo "---------------------------------------"
+ local current_profile current_sec current_ask current_elevated
+ current_profile=$(openclaw config get tools.profile 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_sec=$(openclaw config get tools.exec.security 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_ask=$(openclaw config get tools.exec.ask 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_elevated=$(openclaw config get tools.elevated.enabled 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ # Null 값 정리
+ [ -z "$current_profile" ] || echo "$current_profile" | grep -qi "config path not found" && current_profile=""
+ [ -z "$current_sec" ] || echo "$current_sec" | grep -qi "config path not found" && current_sec=""
+ [ -z "$current_ask" ] || echo "$current_ask" | grep -qi "config path not found" && current_ask=""
+ [ -z "$current_elevated" ] || echo "$current_elevated" | grep -qi "config path not found" && current_elevated=""
+
+ local current_mode="알 수 없음/커스텀"
+ if [ "$current_profile" = "full" ] && [ "$current_sec" = "full" ] && [ "$current_ask" = "off" ]; then
+ current_mode="\033[1;31m완전 개방 모드\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" = "true" ]; then
+ current_mode="\033[1;33m향상된 모드 개발\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" != "true" ]; then
+ current_mode="\033[1;32m표준 안전 모드\033[0m"
+ elif [ -z "$current_profile" ] && [ -z "$current_sec" ]; then
+ current_mode="\033[1;36m공식 샌드박스가 진실을 말해줍니다\033[0m"
+ fi
+ echo -e "현재 포괄적인 보안 수준:${current_mode}"
+ echo "---------------------------------------"
+ echo -e "${gl_huang}[애플리케이션 레이어 도구 정책 현황]${gl_bai}"
+ echo "프로필(기본값): ${current_profile:-(unset)}"
+ echo "실행 제한: ${current_sec:-(unset)}"
+ echo "승인 프롬프트: ${current_ask:-(unset)}"
+ echo "권한 상승 스위치: ${current_elevated:-(unset)}"
+
+ echo -e "\n${gl_huang}[기본 Exec 승인 상태]${gl_bai}"
+ if openclaw_has_command openclaw; then
+ local approvals_json
+ approvals_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$approvals_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ defaults = d.get("file", {}).get("defaults", {})
+ if not defaults:
+ defaults = d.get("defaults", {})
+ sec = defaults.get("security", "(unset)")
+ ask = defaults.get("ask", "(unset)")
+ fb = defaults.get("askFallback", "(unset)")
+ auto = defaults.get("autoAllowSkills", False)
+ print("차단 전략(보안):" + str(sec))
+ print("신속한 전략(질문):" + str(ask))
+ print("UI 커버 없음(AskFallback):" + str(fb))
+ print("AutoAllowSkills:" + ("on" if auto else "off"))
+ exists = d.get("exists", True)
+ if not exists:
+ print("(승인문서가 존재하지 않으므로 시스템에 내장된 안전망을 이용하세요)")
+except Exception as e:
+ print("(구문 분석 실패:" + str(e) + ")")
+' "$approvals_json"
+ else
+ echo "(openclaw 승인은 --json 출력 없음)"
+ fi
+ elif [ -f "$HOME/.openclaw/exec-approvals.json" ]; then
+ python3 -c '
+import json, os
+path = os.path.expanduser("~/.openclaw/exec-approvals.json")
+try:
+ with open(path) as f:
+ d = json.load(f).get("defaults", {})
+ print("차단 전략(보안):" + str(d.get("security", "(unset)")))
+ print("신속한 전략(질문):" + str(d.get("ask", "(unset)")))
+ print("UI 커버 없음(AskFallback):" + str(d.get("askFallback", "(unset)")))
+except Exception:
+ print("(구성 파일 구문 분석 실패)")
+'
+ else
+ echo "(구성되지 않음, 시스템에 내장된 보안 정책을 강제로 사용함)"
+ fi
+ }
+
+ openclaw_permission_apply_standard() {
+ send_stats "OpenClaw 권한 - 표준 보안 모드"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "애플리케이션 레이어 정책 구성 중..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled false >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval true >/dev/null 2>&1 # 拦截危险的内联代码
+ openclaw config unset commands.bash >/dev/null 2>&1 # 废弃旧版参数
+
+ echo "호스트 승인 차단 구성 중..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 표준 안전 모드로 전환(위험한 명령은 모두 UI/TG를 통해 승인을 요청합니다)${gl_bai}"
+ }
+
+ openclaw_permission_apply_developer() {
+ send_stats "OpenClaw 권한 - 개발 강화 모드"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "애플리케이션 레이어 정책 구성 중..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1 # 允许智能体申请提权
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "호스트 승인 차단 구성 중..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 개발 강화 모드로 전환(권한 상승은 허용되지만, 일반적으로 위험한 명령은 여전히 승인이 필요함)${gl_bai}"
+ }
+
+ openclaw_permission_apply_full() {
+ send_stats "OpenClaw 권한 - 완전 개방 모드"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "애플리케이션 레이어 정책 구성 중..."
+ openclaw config set tools.profile full >/dev/null 2>&1
+ openclaw config set tools.exec.security full >/dev/null 2>&1
+ openclaw config set tools.exec.ask off >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "호스트 차단 방어 붕괴…"
+ # 여기서 전체 및 해제는 기본 호스트의 실행 승인 시스템을 완전히 우회합니다.
+ openclaw_permission_update_exec_approvals "full" "off" "full"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 완전 개방 모드로 전환되었습니다. (경고: 모든 호스트 명령 차단이 만료되었으며 에이전트는 가장 높은 권한을 가집니다.)${gl_bai}"
+ }
+
+ openclaw_permission_restore_official_defaults() {
+ send_stats "OpenClaw 권한-공식 기본값 복원"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "깨끗한 애플리케이션 레이어 힘 범위..."
+ openclaw config unset tools.profile >/dev/null 2>&1
+ openclaw config unset tools.exec.security >/dev/null 2>&1
+ openclaw config unset tools.exec.ask >/dev/null 2>&1
+ openclaw config unset tools.elevated.enabled >/dev/null 2>&1
+ openclaw config unset tools.exec.strictInlineEval >/dev/null 2>&1
+
+ echo "호스트 차단 구성을 정리합니다..."
+ # CLI를 통해 승인 구성을 지우는 데 우선순위를 두고 파일을 직접 삭제하는 방법으로 대체합니다.
+ if echo '{"version":1,"defaults":{}}' | openclaw approvals set --stdin >/dev/null 2>&1; then
+ true
+ else
+ rm -f "$HOME/.openclaw/exec-approvals.json"
+ fi
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ OpenClaw 공식 보안 샌드박스 방어 메커니즘으로 복귀${gl_bai}"
+ }
+
+ openclaw_permission_run_audit() {
+ echo "======================================="
+ echo "공식 OpenClaw 보안 감사를 실행하고 물리적..."
+ echo "======================================="
+ openclaw security audit
+ echo "---------------------------------------"
+ read -e -p "발견된 보안 취약점을 자동으로 해결하려고 합니까? (예/아니요):" fix_choice
+ if [[ "$fix_choice" == "y" || "$fix_choice" == "Y" || "$fix_choice" == "yes" ]]; then
+ openclaw security audit --fix
+ echo -e "${gl_lv}✅ 자동 복구가 완료되었습니다.${gl_bai}"
+ fi
+ echo "돌아가려면 아무 키나 누르세요..."
+ read -n 1 -s
+ }
+
+
+ openclaw_permission_manage_allowlist() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "실행 명령 화이트리스트 관리"
+ echo "======================================="
+ echo "현재 허용 목록:"
+ local allowlist_json
+ allowlist_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$allowlist_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ f = d.get("file", {})
+ agents = f.get("agents", {})
+ found = False
+ for agent_id, agent_data in agents.items():
+ al = agent_data.get("allowlist", [])
+ if al:
+ found = True
+ print("에이전트 [%s]:" % agent_id)
+ for item in al:
+ print(" - %s" % item)
+ if not found:
+ print("(비어 있음, 화이트리스트 규칙이 구성되지 않음)")
+except Exception as e:
+ print("(구문 분석 실패:" + str(e) + ")")
+' "$allowlist_json"
+ else
+ echo "(얻을 수 없음)"
+ fi
+ echo "---------------------------------------"
+ echo "1. 화이트리스트 규칙 추가"
+ echo "2. 화이트리스트 규칙 제거"
+ echo "0. 반품"
+ echo "---------------------------------------"
+ read -e -p "선택하세요:" al_choice
+ case "$al_choice" in
+ 1)
+ read -e -p "해제할 명령 경로를 입력하십시오(/usr/bin/git와 같은 glob 지원)." pattern
+ [ -z "$pattern" ] && { echo "비워둘 수 없습니다."; break_end; continue; }
+ read -e -p "에이전트 ID 지정(비워 두기 = 모든 에이전트 *):" agent_id
+ agent_id="${agent_id:-*}"
+ openclaw approvals allowlist add --agent "$agent_id" "$pattern"
+ break_end
+ ;;
+ 2)
+ read -e -p "제거할 명령 경로를 입력하십시오:" pattern
+ [ -z "$pattern" ] && { echo "비워둘 수 없습니다."; break_end; continue; }
+ openclaw approvals allowlist remove "$pattern"
+ break_end
+ ;;
+ 0) return 0 ;;
+ *) echo "잘못된 선택"; sleep 1 ;;
+ esac
+ done
+ }
+
+ openclaw_permission_menu() {
+ send_stats "OpenClaw 권한 관리"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 권한 관리(이중 레이어 아키텍처 심층 적응)"
+ echo "======================================="
+ openclaw_permission_render_status
+ echo "---------------------------------------"
+ echo -e "${gl_kjlan}1.${gl_bai}표준보안모드로 전환(일일권장, 팝업카드 승인)"
+ echo -e "${gl_kjlan}2.${gl_bai}개발 강화 모드로 전환(에이전트가 권한 승격을 신청할 수 있도록 허용)"
+ echo -e "${gl_kjlan}3.${gl_bai}완전 개방 모드로 전환(${gl_hong}위험! 모든 호스트 차단을 완전히 제거합니다.${gl_bai})"
+ echo -e "${gl_kjlan}4.${gl_bai}공식 기본 샌드박스 방어 전략 복원"
+ echo -e "${gl_kjlan}5.${gl_bai}기본 보안 감사 및 자동 복구 실행"
+ echo -e "${gl_kjlan}6.${gl_bai}Exec 명령 화이트리스트 관리"
+ echo -e "${gl_kjlan}0.${gl_bai}이전 레벨로 돌아가기"
+ echo "---------------------------------------"
+ read -e -p "선택사항을 입력하세요:" perm_choice
+ case "$perm_choice" in
+ 1)
+ echo "적용 준비: 표준 안전 모드"
+ read -e -p "확인하려면 yes를 입력하세요." confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_standard; else echo "취소"; fi
+ break_end
+ ;;
+ 2)
+ echo "앱 준비: 고급 모드 개발"
+ read -e -p "확인하려면 yes를 입력하세요." confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_developer; else echo "취소"; fi
+ break_end
+ ;;
+ 3)
+ echo -e "${gl_hong}⚠️ 완전 개방 모드는 실행 승인을 완전히 무너뜨리고 고위험 코드를 자동으로 공개합니다.${gl_bai}"
+ read -e -p "계속하려면 FULL을 입력하여 확인하세요." confirm
+ if [ "$confirm" = "FULL" ]; then openclaw_permission_apply_full; else echo "취소"; fi
+ break_end
+ ;;
+ 4)
+ echo "모든 사용자 정의 재정의가 지워지고 OpenClaw를 처음 설치할 때 엄격한 샌드박스 상태로 복원됩니다."
+ read -e -p "확인하려면 yes를 입력하세요." confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_restore_official_defaults; else echo "취소"; fi
+ break_end
+ ;;
+ 5)
+ openclaw_permission_run_audit
+ ;;
+ 6)
+ openclaw_permission_manage_allowlist
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_multiagent_config_file() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ echo "$config_file"
+ return 0
+ fi
+ openclaw config file 2>/dev/null | tail -n 1
+ }
+
+ openclaw_multiagent_default_agent() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+value="(unset)"
+try:
+ with open(path) as f:
+ data=json.load(f)
+ defaults=data.get("agents",{}).get("defaults",{}) if isinstance(data,dict) else {}
+ value=defaults.get("agent") or None
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and (item.get("isDefault") or item.get("default")):
+ value=item.get("id")
+ break
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and item.get("id"):
+ value=item.get("id")
+ break
+except Exception:
+ value="(unset)"
+print(value or "(unset)")
+PY
+ return 0
+ fi
+ local value
+ value=$(openclaw config get agents.defaults.agent 2>&1 | head -n 1)
+ if [ -z "$value" ] || echo "$value" | grep -qi "config path not found"; then
+ value=$(openclaw agents list --json 2>/dev/null | python3 -c 'import json,sys
+try:
+ data=json.load(sys.stdin)
+ print(next((x.get("id","(unset)") for x in data if x.get("isDefault")), "(unset)"))
+except Exception:
+ print("(unset)")' 2>/dev/null)
+ fi
+ [ -z "$value" ] && value="(unset)"
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ }
+
+ openclaw_multiagent_require_openclaw() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ openclaw 명령이 감지되지 않습니다. 먼저 OpenClaw를 설치하거나 초기화하세요."
+ return 1
+ fi
+ return 0
+ }
+
+ openclaw_multiagent_agents_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents list --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 대체: 구성 파일에서 읽기
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ agents=data.get("agents",{}).get("list",[])
+ if not isinstance(agents,list):
+ agents=[]
+ print(json.dumps(agents, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_bindings_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents bindings --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 대체: 구성 파일에서 읽기
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ bindings=data.get("agents",{}).get("bindings",[])
+ if not isinstance(bindings,list):
+ bindings=[]
+ results=[]
+ for item in bindings:
+ if not isinstance(item,dict):
+ continue
+ results.append({"agentId": item.get("agentId") or item.get("agent") or "?", "description": item.get("description") or "-"})
+ print(json.dumps(results, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_sessions_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw sessions --json 2>/dev/null | grep -v '^\[')
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 대체: 파일 시스템에서 읽기
+ python3 <<'PY'
+import json,os
+base=os.path.expanduser("~/.openclaw/agents")
+sessions=[]
+try:
+ agent_dirs=[d for d in os.listdir(base) if os.path.isdir(os.path.join(base,d))]
+except Exception:
+ agent_dirs=[]
+for agent_id in agent_dirs:
+ path=os.path.join(base,agent_id,"sessions","sessions.json")
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path) as f:
+ data=json.load(f)
+ except Exception:
+ continue
+ if isinstance(data,dict):
+ items=data.items()
+ elif isinstance(data,list):
+ items=[(item.get("key") or "?", item) for item in data if isinstance(item,dict)]
+ else:
+ continue
+ for key,item in items:
+ if not isinstance(item,dict):
+ continue
+ model=item.get("model") or "-"
+ sessions.append({"agentId": agent_id, "key": key, "model": model})
+print(json.dumps({"path":"(filesystem)","count":len(sessions),"sessions":sessions}, ensure_ascii=False))
+PY
+ }
+
+ openclaw_multiagent_render_status() {
+ local config_file default_agent
+ config_file=$(openclaw_multiagent_config_file)
+ default_agent=$(openclaw_multiagent_default_agent)
+ echo "구성 파일: ${config_file:-$(openclaw_permission_config_file)}"
+ echo "기본 에이전트:$default_agent"
+ python3 -c '
+import json,sys
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+sess_obj=json.loads(sys.argv[3] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+print("구성된 에이전트 수: %s" % len(agents))
+print("경로 바인딩 수: %s" % len(bindings))
+print("총 세션: %s" % len(sessions))
+print("---------------------------------------")
+if not agents:
+ print("현재 다중 에이전트가 구성되어 있지 않습니다.")
+else:
+ for item in agents[:8]:
+ aid = item.get("id","?")
+ identity = item.get("identityName") or item.get("name") or "-"
+ emoji = item.get("identityEmoji") or ""
+ ws = item.get("workspace") or "-"
+ model = item.get("model") or "-"
+ is_default = item.get("isDefault", False)
+ bcount = item.get("bindings", 0)
+ default_tag = "[기본]" if is_default else ""
+ print("- 에이전트 ID: \033[1;36m%s\033[0m%s" % (aid, default_tag))
+ print("ID 이름: %s %s" % (identity, emoji))
+ print("모델: %s" % model)
+ print("작업 디렉터리: %s" % ws)
+ print("바인딩 수: %s" % bcount)
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)" "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_list_agents() {
+ send_stats "OpenClaw 다중 에이전트 - 에이전트 나열"
+ python3 -c 'import json,sys; agents=json.loads(sys.argv[1] or "[]");
+if not agents: print("아직 구성된 에이전트가 없습니다."); raise SystemExit(0)
+for idx,item in enumerate(agents,1):
+ print("%s. %s" % (idx, item.get("id","?"))); print(" workspace : %s" % item.get("workspace","-")); ident=(item.get("identityName") or "-") + ((" " + item.get("identityEmoji")) if item.get("identityEmoji") else ""); print(" identity : %s" % ident.strip()); print(" model : %s" % (item.get("model") or "-")); print(" bindings : %s" % item.get("bindings",0)); print(" default : %s" % ("yes" if item.get("isDefault") else "no"))' "$(openclaw_multiagent_agents_json)"
+ }
+
+ openclaw_multiagent_add_agent() {
+ send_stats "OpenClaw 다중 에이전트-새 에이전트"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id workspace confirm
+ read -e -p "새 에이전트 ID를 입력하세요." agent_id
+ [ -z "$agent_id" ] && echo "취소됨: 에이전트 ID는 비워둘 수 없습니다." && return 1
+ read -e -p "작업공간 경로를 입력해주세요(기본값 ~/.openclaw/workspace-${agent_id}): " workspace
+ [ -z "$workspace" ] && workspace="~/.openclaw/workspace-${agent_id}"
+ echo "에이전트가 생성됩니다:$agent_id"
+ echo "작업 디렉토리:$workspace"
+ read -e -p "계속하려면 yes를 입력하여 확인하세요." confirm
+ [ "$confirm" = "yes" ] || { echo "취소"; return 1; }
+ if openclaw agents add "$agent_id" --workspace "$workspace"; then
+ echo "✅ 에이전트가 성공적으로 생성되었습니다:$agent_id"
+ local name theme
+ read -e -p "에이전트 ID 이름(예: Code Expert)을 입력하세요." name
+ [ -z "$name" ] && name="$agent_id"
+ read -e -p "상담원 성격 주제(예: 엄격함, 효율적)를 입력하세요." theme
+ [ -z "$theme" ] && theme="어시스턴트"
+ echo "에이전트 ID 구성 중..."
+ openclaw agents set-identity --agent "$agent_id" --name "$name" --theme "$theme"
+ else
+ echo "❌ 에이전트 생성 실패"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_delete_agent() {
+ send_stats "OpenClaw 다중 에이전트 삭제 에이전트"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id confirm
+ read -e -p "삭제할 에이전트 ID를 입력하세요:" agent_id
+ [ -z "$agent_id" ] && echo "취소됨: 에이전트 ID는 비워둘 수 없습니다." && return 1
+ echo "⚠️ 에이전트를 삭제하면 작업 디렉터리, 경로 바인딩 및 세션 라우팅에 영향을 미칠 수 있습니다."
+ read -e -p "삭제를 확인하려면 DELETE를 입력하세요.${agent_id}: " confirm
+ [ "$confirm" = "DELETE" ] || { echo "취소"; return 1; }
+ if openclaw agents delete "$agent_id"; then
+ echo "✅ 에이전트가 성공적으로 삭제되었습니다:$agent_id"
+ else
+ echo "❌ 에이전트 삭제 실패"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_list_bindings() {
+ send_stats "OpenClaw 다중 에이전트 - 라우팅 바인딩 보기"
+ python3 -c '
+import json,sys
+bindings=json.loads(sys.argv[1] or "[]")
+if not bindings:
+ print("아직 경로 바인딩이 없습니다.")
+ raise SystemExit(0)
+for idx,item in enumerate(bindings,1):
+ desc = item.get("description") or "-"
+ print("%s. agent=%s | %s" % (idx, item.get("agentId","?"), desc))
+' "$(openclaw_multiagent_bindings_json)"
+ }
+
+ openclaw_multiagent_add_binding() {
+ send_stats "OpenClaw 다중 에이전트-새 라우팅 바인딩"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "상담원 ID를 입력하세요:" agent_id
+ read -e -p "경로 바인딩 값을 입력하세요(예: telegram:ops / discord:guild-a):" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "취소됨: 매개변수는 비워둘 수 없습니다." && return 1
+ echo "에이전트를 바인딩합니다 [$agent_id] -> [$bind_value]"
+ read -e -p "계속하려면 yes를 입력하여 확인하세요." confirm
+ [ "$confirm" = "yes" ] || { echo "취소"; return 1; }
+ if openclaw agents bind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 경로 바인딩이 성공적으로 추가되었습니다."
+ else
+ echo "❌ 경로 바인딩을 추가하지 못했습니다."
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_remove_binding() {
+ send_stats "OpenClaw 다중 에이전트 - 경로 바인딩 제거"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "상담원 ID를 입력하세요:" agent_id
+ read -e -p "제거할 경로 바인딩 값을 입력하세요." bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "취소됨: 매개변수는 비워둘 수 없습니다." && return 1
+ echo "에이전트를 제거합니다. [$agent_id] 경로 바인딩 [$bind_value]"
+ read -e -p "계속하려면 yes를 입력하여 확인하세요." confirm
+ [ "$confirm" = "yes" ] || { echo "취소"; return 1; }
+ if openclaw agents unbind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 경로 바인딩이 성공적으로 제거되었습니다."
+ else
+ echo "❌ 경로 바인딩 제거 실패"
+ return 1
+ fi
+ }
+
+
+ openclaw_multiagent_show_sessions() {
+ send_stats "OpenClaw 다중 에이전트 - 세션 개요"
+ python3 -c '
+import json,sys
+sess_obj=json.loads(sys.argv[1] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+if not sessions:
+ print("아직 세션 데이터가 없습니다.")
+ raise SystemExit(0)
+by_agent={}
+for item in sessions:
+ aid = item.get("agentId","?")
+ by_agent[aid] = by_agent.get(aid, 0) + 1
+print("세션 요약:")
+for agent_id,count in sorted(by_agent.items()):
+ print("- %s: %s" % (agent_id, count))
+print("---------------------------------------")
+for item in sessions[:10]:
+ key = item.get("key","-")
+ model = item.get("model") or "-"
+ aid = item.get("agentId","?")
+ tokens = ""
+ it = item.get("inputTokens")
+ ot = item.get("outputTokens")
+ if it is not None:
+ tokens = " | in=%s out=%s" % (it, ot or 0)
+ print("%s | %s | %s%s" % (aid, key, model, tokens))
+' "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_health_check() {
+ send_stats "OpenClaw 다중 에이전트 상태 확인"
+ openclaw_multiagent_require_openclaw || return 1
+ local config_file
+ config_file=$(openclaw_multiagent_config_file)
+ echo "구성 파일을 확인하세요: ${config_file:-$(openclaw_permission_config_file)}"
+ openclaw config validate || echo "⚠️ 구성 확인에 실패했습니다. 위의 출력을 확인하세요."
+ python3 -c '
+import json,sys,os
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+print("---------------------------------------")
+if not agents:
+ print("⚠️ 구성된 에이전트를 찾을 수 없습니다.")
+else:
+ for item in agents:
+ ws = item.get("workspace") or ""
+ aid = item.get("id","?")
+ if ws and os.path.isdir(os.path.expanduser(ws)):
+ state = "OK"
+ elif aid == "main":
+ state = "OK"
+ else:
+ state = "MISSING"
+ model = item.get("model") or "-"
+ bcount = item.get("bindings", 0)
+ print("agent=%s workspace=%s model=%s bindings=%s [%s]" % (aid, ws or "-", model, bcount, state))
+print("경로 바인딩 수=%s" % len(bindings))
+print("✅멀티에이전트 헬스체크 완료")
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)"
+ echo ""
+ echo "보안 감사를 실행하세요..."
+ openclaw security audit 2>/dev/null || echo "⚠️ 보안 감사 명령을 사용할 수 없습니다."
+ }
+
+
+ openclaw_multiagent_set_identity() {
+ openclaw_multiagent_require_openclaw || return 1
+ openclaw_multiagent_list_agents
+ read -e -p "ID를 수정하려는 상담원의 ID를 입력하세요." agent_id
+ [ -z "$agent_id" ] && { echo "ID는 비워둘 수 없습니다."; return 1; }
+ echo "옵션 수정(건너뛰려면 비워 두세요):"
+ read -e -p "새 이름:" new_name
+ read -e -p "새로운 이모티콘:" new_emoji
+ local cmd="openclaw agents set-identity --agent $agent_id"
+ [ -n "$new_name" ] && cmd="$cmd --name $new_name"
+ [ -n "$new_emoji" ] && cmd="$cmd --emoji $new_emoji"
+ echo "IDENTITY.md에서 신원 정보를 자동으로 읽을 수도 있습니다."
+ read -e -p "IDENTITY.md에서 읽으시겠습니까? (예/아니요):" from_id
+ if [ "$from_id" = "y" ]; then
+ cmd="openclaw agents set-identity --agent $agent_id --from-identity"
+ fi
+ eval "$cmd"
+ }
+
+ openclaw_multiagent_cleanup_sessions() {
+ openclaw_multiagent_require_openclaw || return 1
+ echo "만료되거나 중복된 세션 데이터를 곧 정리합니다..."
+ read -e -p "확인하려면 yes를 입력하세요." confirm
+ [ "$confirm" != "yes" ] && { echo "취소"; return 0; }
+ openclaw sessions cleanup
+ }
+
+ openclaw_multiagent_menu() {
+ send_stats "OpenClaw 다중 에이전트 관리"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 다중 에이전트 관리"
+ echo "======================================="
+ openclaw_multiagent_render_status
+ echo "---------------------------------------"
+ echo "1. 새로운 지능형 에이전트 추가"
+ echo "2. 에이전트 삭제"
+ echo "3. 라우팅 바인딩 보기"
+ echo "4. 새로운 경로 바인딩 추가"
+ echo "5. 경로 바인딩 제거"
+ echo "6. 세션 개요 보기"
+ echo "7. 다중 에이전트 상태 확인 실행"
+ echo "8. 상담원 신원(이름/이모지) 수정"
+ echo "9. 만료된 세션 정리"
+ echo "0. 이전 레벨로 돌아갑니다"
+ echo "---------------------------------------"
+ read -e -p "선택사항을 입력하세요:" multi_choice
+ case "$multi_choice" in
+ 1) openclaw_multiagent_add_agent; break_end ;;
+ 2) openclaw_multiagent_delete_agent; break_end ;;
+ 3) openclaw_multiagent_list_bindings; break_end ;;
+ 4) openclaw_multiagent_add_binding; break_end ;;
+ 5) openclaw_multiagent_remove_binding; break_end ;;
+ 6) openclaw_multiagent_show_sessions; break_end ;;
+ 7) openclaw_multiagent_health_check; break_end ;;
+ 8) openclaw_multiagent_set_identity; break_end ;;
+ 9) openclaw_multiagent_cleanup_sessions; break_end ;;
+ 0) return 0 ;;
+ *) echo "선택이 잘못되었습니다. 다시 시도해 주세요."; sleep 1 ;;
+ esac
+ done
+ }
+
+
+openclaw_backup_restore_menu() {
+
+ send_stats "OpenClaw 백업 및 복원"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 백업 및 복원"
+ echo "======================================="
+ openclaw_backup_render_file_list
+ echo "---------------------------------------"
+ echo "1. 메모리 전체를 백업하세요"
+ echo "2. 전체 메모리 복원"
+ echo "3. OpenClaw 프로젝트 백업(기본 안전 모드)"
+ echo "4. OpenClaw 프로젝트 복원(고급/고위험)"
+ echo "5. 백업 파일 삭제"
+ echo "0. 이전 레벨로 돌아갑니다"
+ echo "---------------------------------------"
+ read -e -p "선택사항을 입력하세요:" backup_choice
+
+ case "$backup_choice" in
+ 1) openclaw_memory_backup_export ;;
+ 2) openclaw_memory_backup_import ;;
+ 3) openclaw_project_backup_export ;;
+ 4) openclaw_project_backup_import ;;
+ 5) openclaw_backup_delete_file ;;
+ 0) return 0 ;;
+ *)
+ echo "선택이 잘못되었습니다. 다시 시도해 주세요."
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ update_moltbot() {
+ echo "오픈클로 업데이트..."
+ send_stats "오픈클로 업데이트..."
+ install_node_and_tools
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+ npm install -g openclaw@latest
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ start_gateway
+ hash -r
+ add_app_id
+ echo "업데이트 완료"
+ break_end
+ }
+
+
+ uninstall_moltbot() {
+ echo "OpenClaw 제거..."
+ send_stats "OpenClaw 제거..."
+ openclaw uninstall
+ npm uninstall -g openclaw
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ rm -rf "$HOME/.openclaw"
+ [ "$HOME" != "/root" ] && [ -d /root/.openclaw ] && echo "⚠️ 루트 디렉터리에 /root/.openclaw가 여전히 존재하는 것으로 감지됩니다. 청소가 필요한 경우 수동으로 처리하십시오."
+ hash -r
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "제거가 완료되었습니다."
+ break_end
+ }
+
+ nano_openclaw_json() {
+ send_stats "OpenClaw 구성 파일 편집"
+ install nano
+ nano "$(openclaw_get_config_file)"
+ start_gateway
+ }
+
+
+
+
+
+
+ openclaw_find_webui_domain() {
+ local conf domain_list
+
+ domain_list=$(
+ grep -R "18789" /home/web/conf.d/*.conf 2>/dev/null \
+ | awk -F: '{print $1}' \
+ | sort -u \
+ | while read conf; do
+ basename "$conf" .conf
+ done
+ )
+
+ if [ -n "$domain_list" ]; then
+ echo "$domain_list"
+ fi
+ }
+
+
+
+ openclaw_show_webui_addr() {
+ local local_ip token domains
+
+ echo "=================================="
+ echo "OpenClaw WebUI 접속 주소"
+ local_ip="127.0.0.1"
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+ echo
+ echo "현지 주소:"
+ echo "http://${local_ip}:18789/#token=${token}"
+
+ domains=$(openclaw_find_webui_domain)
+ if [ -n "$domains" ]; then
+ echo "도메인 이름 주소:"
+ echo "$domains" | while read d; do
+ echo "https://${d}/#token=${token}"
+ done
+ fi
+
+ echo "=================================="
+ }
+
+
+
+ # 도메인 이름 추가(제공한 기능 호출)
+ openclaw_domain_webui() {
+ add_yuming
+ ldnmp_Proxy ${yuming} 127.0.0.1 18789
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+
+ clear
+ echo "방문 주소:"
+ echo "https://${yuming}/#token=$token"
+ echo "먼저 URL에 액세스하여 장치 ID를 트리거한 다음 Enter를 눌러 페어링을 진행하세요."
+ read
+ echo -e "${gl_kjlan}기기 목록 로드 중...${gl_bai}"
+ # allowedOrigins에 도메인 이름을 자동으로 추가합니다.
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ new_origin="https://${yuming}"
+ # 구조가 존재하고 도메인 이름이 반복적으로 추가되지 않도록 jq를 사용하여 JSON을 안전하게 수정하세요.
+ if command -v jq >/dev/null 2>&1; then
+ tmp_json=$(mktemp)
+ jq 'if .gateway.controlUi == null then .gateway.controlUi = {"allowedOrigins": ["http://127.0.0.1"]} else . end | if (.gateway.controlUi.allowedOrigins | contains([$origin]) | not) then .gateway.controlUi.allowedOrigins += [$origin] else . end' --arg origin "$new_origin" "$config_file" > "$tmp_json" && mv "$tmp_json" "$config_file"
+ echo -e "${gl_kjlan}도메인 이름이${yuming}allowedOrigins 구성 추가${gl_bai}"
+ openclaw gateway restart >/dev/null 2>&1
+ fi
+ fi
+
+ openclaw devices list
+
+ read -e -p "Request_Key를 입력하십시오:" Request_Key
+
+ [ -z "$Request_Key" ] && {
+ echo "Request_Key는 비워둘 수 없습니다."
+ return 1
+ }
+
+ openclaw devices approve "$Request_Key"
+
+ }
+
+ # 도메인 이름 삭제
+ openclaw_remove_domain() {
+ echo "https://가 없는 도메인 이름 형식 example.com"
+ web_del
+ }
+
+ # 메인 메뉴
+ openclaw_webui_menu() {
+
+ send_stats "WebUI 액세스 및 설정"
+ while true; do
+ clear
+ openclaw_show_webui_addr
+ echo
+ echo "1. 도메인 이름 액세스 추가"
+ echo "2. 도메인 이름 액세스 삭제"
+ echo "0. 종료"
+ echo
+ read -e -p "선택하세요:" choice
+
+ case "$choice" in
+ 1)
+ openclaw_domain_webui
+ echo
+ read -p "메뉴로 돌아가려면 Enter를 누르세요..."
+ ;;
+ 2)
+ openclaw_remove_domain
+ read -p "메뉴로 돌아가려면 Enter를 누르세요..."
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "잘못된 옵션"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ # 메인 루프
+ while true; do
+ show_menu
+ read choice
+ case $choice in
+ 1) install_moltbot ;;
+ 2) start_bot ;;
+ 3) stop_bot ;;
+ 4) view_logs ;;
+ 5) change_model ;;
+ 6) openclaw_api_manage_menu ;;
+ 7) change_tg_bot_code ;;
+ 8) install_plugin ;;
+ 9) install_skill ;;
+ 10) nano_openclaw_json ;;
+ 11) send_stats "초기 구성 마법사"
+ openclaw onboard --install-daemon
+ break_end
+ ;;
+ 12) send_stats "상태 감지 및 복구"
+ openclaw doctor --fix
+ break_end
+ ;;
+ 13) openclaw_webui_menu ;;
+ 14) send_stats "TUI 명령줄 대화"
+ openclaw tui
+ break_end
+ ;;
+ 15) openclaw_memory_menu ;;
+ 16) openclaw_permission_menu ;;
+ 17) openclaw_multiagent_menu ;;
+ 18) openclaw_backup_restore_menu ;;
+ 19) update_moltbot ;;
+ 20) uninstall_moltbot ;;
+ *) break ;;
+ esac
+ done
+
+}
+
+
+
+
+linux_panel() {
+
+local sub_choice="$1"
+
+clear
+cd ~
+install git
+echo -e "${gl_kjlan}신청 목록이 업데이트 중입니다. 기다리세요...${gl_bai}"
+if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+fi
+
+while true; do
+
+ if [ -z "$sub_choice" ]; then
+ clear
+ echo -e "응용 시장"
+ echo -e "${gl_kjlan}-------------------------"
+
+ local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+
+ # 루프를 사용하여 색상 설정
+ for i in {1..150}; do
+ if echo "$app_numbers" | grep -q "^$i$"; then
+ declare "color$i=${gl_lv}"
+ else
+ declare "color$i=${gl_bai}"
+ fi
+ done
+
+ echo -e "${gl_kjlan}1. ${color1}파고다 패널 공식 버전${gl_kjlan}2. ${color2}aaPanel Pagoda 국제 버전"
+ echo -e "${gl_kjlan}3. ${color3}1패널 차세대 관리 패널${gl_kjlan}4. ${color4}NginxProxyManager 시각화 패널"
+ echo -e "${gl_kjlan}5. ${color5}OpenList 다중 저장소 파일 목록 프로그램${gl_kjlan}6. ${color6}Ubuntu 원격 데스크톱 웹 에디션"
+ echo -e "${gl_kjlan}7. ${color7}나타 프로브 VPS 모니터링 패널${gl_kjlan}8. ${color8}QB 오프라인 BT 자기 다운로드 패널"
+ echo -e "${gl_kjlan}9. ${color9}Poste.io 메일 서버 프로그램${gl_kjlan}10. ${color10}RocketChat 다자간 온라인 채팅 시스템"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}11. ${color11}ZenTao 프로젝트 관리 소프트웨어${gl_kjlan}12. ${color12}Qinglong 패널 예정된 작업 관리 플랫폼"
+ echo -e "${gl_kjlan}13. ${color13}Cloudreve 네트워크 디스크${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}간단한 그림 침대 그림 관리 프로그램"
+ echo -e "${gl_kjlan}15. ${color15}emby 멀티미디어 관리 시스템${gl_kjlan}16. ${color16}Speedtest 속도 테스트 패널"
+ echo -e "${gl_kjlan}17. ${color17}AdGuardHome은 애드웨어를 제거합니다${gl_kjlan}18. ${color18}onlyoffice온라인 오피스 OFFICE"
+ echo -e "${gl_kjlan}19. ${color19}Leichi WAF 방화벽 패널${gl_kjlan}20. ${color20}포테이너 컨테이너 관리 패널"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}21. ${color21}VScode 웹 버전${gl_kjlan}22. ${color22}UptimeKuma 모니터링 도구"
+ echo -e "${gl_kjlan}23. ${color23}메모 웹 메모${gl_kjlan}24. ${color24}Webtop 원격 데스크톱 웹 버전${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}25. ${color25}Nextcloud 네트워크 디스크${gl_kjlan}26. ${color26}QD-오늘 예약된 작업 관리 프레임워크"
+ echo -e "${gl_kjlan}27. ${color27}Dockge 컨테이너 스택 관리 패널${gl_kjlan}28. ${color28}LibreSpeed 속도 테스트 도구"
+ echo -e "${gl_kjlan}29. ${color29}searxng 집계 검색 스테이션${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism 개인 앨범 시스템"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}31. ${color31}StirlingPDF 도구 모음${gl_kjlan}32. ${color32}drawio 무료 온라인 차트 작성 소프트웨어${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${color33}Sun 패널 탐색 패널${gl_kjlan}34. ${color34}Pingvin-Share 파일 공유 플랫폼"
+ echo -e "${gl_kjlan}35. ${color35}미니멀리스트 친구들${gl_kjlan}36. ${color36}LobeChatAI 채팅 집계 웹사이트"
+ echo -e "${gl_kjlan}37. ${color37}MyIP 도구 상자${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}Xiaoya alist 가족 버킷"
+ echo -e "${gl_kjlan}39. ${color39}Bililive 라이브 방송 녹음 도구${gl_kjlan}40. ${color40}webssh 웹 버전 SSH 연결 도구"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}41. ${color41}마우스 관리 패널${gl_kjlan}42. ${color42}Nexterm 원격 연결 도구"
+ echo -e "${gl_kjlan}43. ${color43}RustDesk 원격 데스크톱(서버)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk 원격 데스크톱(릴레이)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}45. ${color45}도커 가속 스테이션${gl_kjlan}46. ${color46}GitHub 가속 스테이션${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}47. ${color47}프로메테우스 모니터링${gl_kjlan}48. ${color48}프로메테우스(호스트 모니터링)"
+ echo -e "${gl_kjlan}49. ${color49}프로메테우스(컨테이너 모니터링)${gl_kjlan}50. ${color50}보충 모니터링 도구"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}51. ${color51}PVE 오픈 병아리 패널${gl_kjlan}52. ${color52}DPanel 컨테이너 관리 패널"
+ echo -e "${gl_kjlan}53. ${color53}라마3 채팅 AI 대형 모델${gl_kjlan}54. ${color54}AMH 호스트 웹사이트 구축 관리 패널"
+ echo -e "${gl_kjlan}55. ${color55}FRP 인트라넷 침투(서버)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP 인트라넷 침투(클라이언트)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}57. ${color57}Deepseek 채팅 AI 대형 모델${gl_kjlan}58. ${color58}대규모 모델 지식 기반 확장${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}59. ${color59}NewAPI 대형 모델 자산 관리${gl_kjlan}60. ${color60}JumpServer 오픈 소스 요새 머신"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}61. ${color61}온라인 번역 서버${gl_kjlan}62. ${color62}RAGFlow 대규모 모델 지식 기반"
+ echo -e "${gl_kjlan}63. ${color63}OpenWebUI 자체 호스팅 AI 플랫폼${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools 도구 상자"
+ echo -e "${gl_kjlan}65. ${color65}n8n 자동화된 워크플로우 플랫폼${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp 비디오 다운로드 도구"
+ echo -e "${gl_kjlan}67. ${color67}ddns-go 동적 DNS 관리 도구${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL 인증서 관리 플랫폼"
+ echo -e "${gl_kjlan}69. ${color69}SFTPGo 파일 전송 도구${gl_kjlan}70. ${color70}AstrBot 챗봇 프레임워크"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}71. ${color71}Navidrome 개인 음악 서버${gl_kjlan}72. ${color72}비트워든 비밀번호 관리자${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}73. ${color73}LibreTV 개인 영화${gl_kjlan}74. ${color74}MoonTV 개인 영화"
+ echo -e "${gl_kjlan}75. ${color75}멜로디 음악 마법사${gl_kjlan}76. ${color76}온라인 DOS 오래된 게임"
+ echo -e "${gl_kjlan}77. ${color77}Thunder 오프라인 다운로드 도구${gl_kjlan}78. ${color78}PandaWiki 지능형 문서 관리 시스템"
+ echo -e "${gl_kjlan}79. ${color79}베젤 서버 모니터링${gl_kjlan}80. ${color80}링크워든 북마크 관리"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}81. ${color81}JitsiMeet 화상 회의${gl_kjlan}82. ${color82}gpt-load 고성능 AI 투명 프록시"
+ echo -e "${gl_kjlan}83. ${color83}코마리 서버 모니터링 도구${gl_kjlan}84. ${color84}Wallos 개인 재무 관리 도구"
+ echo -e "${gl_kjlan}85. ${color85}이미치 픽처 비디오 매니저${gl_kjlan}86. ${color86}젤리핀 미디어 관리 시스템"
+ echo -e "${gl_kjlan}87. ${color87}SyncTV는 함께 영화를 볼 수 있는 훌륭한 도구입니다${gl_kjlan}88. ${color88}Owncast 자체 호스팅 라이브 스트리밍 플랫폼"
+ echo -e "${gl_kjlan}89. ${color89}FileCodeBox 파일 익스프레스${gl_kjlan}90. ${color90}매트릭스 분산형 채팅 프로토콜"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}91. ${color91}gitea 비공개 코드 저장소${gl_kjlan}92. ${color92}FileBrowser 파일 관리자"
+ echo -e "${gl_kjlan}93. ${color93}Dufs 미니멀리스트 정적 파일 서버${gl_kjlan}94. ${color94}Gopeed 고속 다운로드 도구"
+ echo -e "${gl_kjlan}95. ${color95}종이 없는 문서 관리 플랫폼${gl_kjlan}96. ${color96}2FAuth 자체 호스팅 2단계 인증자"
+ echo -e "${gl_kjlan}97. ${color97}WireGuard 네트워킹(서버)${gl_kjlan}98. ${color98}WireGuard 네트워킹(클라이언트)"
+ echo -e "${gl_kjlan}99. ${color99}DSM Synology 가상 컴퓨터${gl_kjlan}100. ${color100}P2P 파일 동기화 도구 동기화"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}101. ${color101}AI 영상 생성 도구${gl_kjlan}102. ${color102}VoceChat 다자간 온라인 채팅 시스템"
+ echo -e "${gl_kjlan}103. ${color103}Umami 웹사이트 통계 도구${gl_kjlan}104. ${color104}스트림 4계층 프록시 전달 도구"
+ echo -e "${gl_kjlan}105. ${color105}쓰위안 노트${gl_kjlan}106. ${color106}Drawix 오픈 소스 화이트보드 도구"
+ echo -e "${gl_kjlan}107. ${color107}PanSou 네트워크 디스크 검색${gl_kjlan}108. ${color108}LangBot 챗봇"
+ echo -e "${gl_kjlan}109. ${color109}ZFile 온라인 네트워크 디스크${gl_kjlan}110. ${color110}Karakeep 북마크 관리"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}111. ${color111}다중 형식 파일 변환 도구${gl_kjlan}112. ${color112}행운의 대형 인트라넷 침투 도구"
+ echo -e "${gl_kjlan}113. ${color113}파이어폭스 브라우저${gl_kjlan}114. ${color114}OpenClaw 봇 관리 도구${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}115. ${color115}헤르메스 로봇 관리 도구${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}타사 애플리케이션 목록"
+ echo -e "${gl_kjlan}귀하의 앱이 여기에 표시되기를 원하십니까? 개발자 가이드를 확인하세요.${gl_huang}https://dev.kejilion.sh/${gl_bai}"
+
+ for f in "$HOME"/apps/*.conf; do
+ [ -e "$f" ] || continue
+ local base_name=$(basename "$f" .conf)
+ # 앱 설명 가져오기
+ local app_text=$(grep "app_text=" "$f" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
+
+ # 설치 상태 확인(appno.txt의 ID 일치)
+ # 여기서는 appno.txt에 기록되는 내용이 base_name(즉, 파일 이름)이라고 가정합니다.
+ if echo "$app_numbers" | grep -q "^$base_name$"; then
+ # 설치된 경우: show base_name - 설명 [설치됨](녹색)
+ echo -e "${gl_kjlan}$base_name${gl_bai} - ${gl_lv}$app_text[설치됨]${gl_bai}"
+ else
+ # 설치되지 않은 경우: 정상적으로 표시됩니다.
+ echo -e "${gl_kjlan}$base_name${gl_bai} - $app_text"
+ fi
+ done
+
+
+
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}b. ${gl_bai}모든 애플리케이션 데이터 백업${gl_kjlan}r. ${gl_bai}모든 앱 데이터 복원"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "선택사항을 입력하세요:" sub_choice
+ fi
+
+ case $sub_choice in
+ 1|bt|baota)
+ local app_id="1"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="탑 패널"
+ local panelurl="https://www.bt.cn/new/index.html"
+
+ panel_app_install() {
+ if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+
+
+ ;;
+ 2|aapanel)
+
+
+ local app_id="2"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="aapanel"
+ local panelurl="https://www.aapanel.com/new/index.html"
+
+ panel_app_install() {
+ URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+ ;;
+ 3|1p|1panel)
+
+ local app_id="3"
+ local lujing="command -v 1pctl"
+ local panelname="1Panel"
+ local panelurl="https://1panel.cn/"
+
+ panel_app_install() {
+ install bash
+ bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
+ }
+
+ panel_app_manage() {
+ 1pctl user-info
+ 1pctl update password
+ }
+
+ panel_app_uninstall() {
+ 1pctl uninstall
+ }
+
+ install_panel
+
+ ;;
+ 4|npm)
+
+ local app_id="4"
+ local docker_name="npm"
+ local docker_img="jc21/nginx-proxy-manager:latest"
+ local docker_port=81
+
+ docker_rum() {
+
+ docker run -d \
+ --name=$docker_name \
+ -p ${docker_port}:81 \
+ -p 80:80 \
+ -p 443:443 \
+ -v /home/docker/npm/data:/data \
+ -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
+ --restart=always \
+ $docker_img
+
+
+ }
+
+ local docker_describe="도메인 이름 액세스 추가를 지원하지 않는 Nginx 역방향 프록시 도구 패널."
+ local docker_url="공식 홈페이지 소개: https://nginxproxymanager.com/"
+ local docker_use="echo \"초기 사용자 이름: admin@example.com\""
+ local docker_passwd="echo \"초기 비밀번호:changeme\""
+ local app_size="1"
+
+ docker_app
+
+ ;;
+
+ 5|openlist)
+
+ local app_id="5"
+ local docker_name="openlist"
+ local docker_img="openlistteam/openlist:latest-aria2"
+ local docker_port=5244
+
+ docker_rum() {
+
+ mkdir -p /home/docker/openlist
+ chmod -R 777 /home/docker/openlist
+
+ docker run -d \
+ --restart=always \
+ -v /home/docker/openlist:/opt/openlist/data \
+ -p ${docker_port}:5244 \
+ -e PUID=0 \
+ -e PGID=0 \
+ -e UMASK=022 \
+ --name="openlist" \
+ openlistteam/openlist:latest-aria2
+
+ }
+
+
+ local docker_describe="gin 및 Solidjs로 구동되는 다중 저장소, 웹 브라우징 및 WebDAV를 지원하는 파일 목록 프로그램"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/OpenListTeam/OpenList"
+ local docker_use="docker exec openlist ./openlist admin random"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 6|webtop-ubuntu)
+
+ local app_id="6"
+ local docker_name="webtop-ubuntu"
+ local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
+ local docker_port=3006
+
+ docker_rum() {
+
+ read -e -p "로그인 사용자 이름 설정:" admin
+ read -e -p "로그인 사용자 비밀번호 설정:" admin_password
+ docker run -d \
+ --name=webtop-ubuntu \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:ubuntu-kde
+
+
+ }
+
+
+ local docker_describe="webtop은 Ubuntu 기반 컨테이너입니다. 해당 IP에 접속할 수 없는 경우, 접속할 도메인 이름을 추가해 주세요."
+ local docker_url="공식 홈페이지 소개: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+ 7|nezha)
+ clear
+ send_stats "나타 빌드"
+
+ local app_id="7"
+ local docker_name="nezha-dashboard"
+ local docker_port=8008
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+ clear
+ echo -e "나타 모니터링$check_docker $update_status"
+ echo "오픈 소스, 가볍고 사용하기 쉬운 서버 모니터링 및 운영 및 유지 관리 도구"
+ echo "공식 웹사이트 구축 문서: https://nezha.wiki/guide/dashboard.html"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 사용"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택 항목을 입력하세요." choice
+
+ case $choice in
+ 1)
+ check_disk_space 1
+ install unzip jq
+ install_docker
+ curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+ ;;
+
+ 8|qb|QB)
+
+ local app_id="8"
+ local docker_name="qbittorrent"
+ local docker_img="lscr.io/linuxserver/qbittorrent:latest"
+ local docker_port=8081
+
+ docker_rum() {
+
+ docker run -d \
+ --name=qbittorrent \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e WEBUI_PORT=${docker_port} \
+ -e TORRENTING_PORT=56881 \
+ -p ${docker_port}:${docker_port} \
+ -p 56881:56881 \
+ -p 56881:56881/udp \
+ -v /home/docker/qbittorrent/config:/config \
+ -v /home/docker/qbittorrent/downloads:/downloads \
+ --restart=always \
+ lscr.io/linuxserver/qbittorrent:latest
+
+ }
+
+ local docker_describe="qbittorrent 오프라인 BT 자기 다운로드 서비스"
+ local docker_url="공식 홈페이지 소개: https://hub.docker.com/r/linuxserver/qbittorrent"
+ local docker_use="sleep 3"
+ local docker_passwd="docker logs qbittorrent"
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 9|mail)
+ send_stats "우체국을 짓다"
+ clear
+ install telnet
+ local app_id="9"
+ local docker_name=“mailserver”
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+
+ clear
+ echo -e "우편 서비스$check_docker $update_status"
+ echo "poste.io는 오픈 소스 메일 서버 솔루션입니다."
+ echo "영상 소개: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+
+ echo ""
+ echo "포트 감지"
+ port=25
+ timeout=3
+ if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
+ echo -e "${gl_lv}포트$port현재 사용 가능${gl_bai}"
+ else
+ echo -e "${gl_hong}포트$port현재는 이용할 수 없습니다${gl_bai}"
+ fi
+ echo ""
+
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ yuming=$(cat /home/docker/mail.txt)
+ echo "방문 주소:"
+ echo "https://$yuming"
+ fi
+
+ echo "------------------------"
+ echo "1. 설치 2. 업데이트 3. 제거"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택 항목을 입력하세요." choice
+
+ case $choice in
+ 1)
+ setup_docker_dir
+ check_disk_space 2 /home/docker
+ read -e -p "이메일 도메인 이름을 설정하세요(예: mail.yuming.com):" yuming
+ mkdir -p /home/docker
+ echo "$yuming" > /home/docker/mail.txt
+ echo "------------------------"
+ ip_address
+ echo "먼저 이 DNS 레코드를 구문 분석하세요."
+ echo "A mail $ipv4_address"
+ echo "CNAME imap $yuming"
+ echo "CNAME pop $yuming"
+ echo "CNAME smtp $yuming"
+ echo "MX @ $yuming"
+ echo "TXT @ v=spf1 mx ~all"
+ echo "TXT ? ?"
+ echo ""
+ echo "------------------------"
+ echo "계속하려면 아무 키나 누르세요..."
+ read -n 1 -s -r -p ""
+
+ install jq
+ install_docker
+
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.io
+
+
+ add_app_id
+
+ clear
+ echo "poste.io가 설치되었습니다"
+ echo "------------------------"
+ echo "다음 주소를 사용하여 poste.io에 액세스할 수 있습니다."
+ echo "https://$yuming"
+ echo ""
+
+ ;;
+
+ 2)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.i
+ yuming=$(cat /home/docker/mail.txt)
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.i
+
+
+ add_app_id
+
+ clear
+ echo "poste.io가 설치되었습니다"
+ echo "------------------------"
+ echo "다음 주소를 사용하여 poste.io에 액세스할 수 있습니다."
+ echo "https://$yuming"
+ echo ""
+ ;;
+ 3)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.io
+ rm /home/docker/mail.txt
+ rm -rf /home/docker/mail
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "앱이 제거되었습니다."
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 10|rocketchat)
+
+ local app_id="10"
+ local app_name="Rocket.Chat 채팅 시스템"
+ local app_text="Rocket.Chat은 실시간 채팅, 음성 및 영상 통화, 파일 공유 및 기타 기능을 지원하는 오픈 소스 팀 커뮤니케이션 플랫폼입니다."
+ local app_url="공식소개 : https://www.rocket.chat/"
+ local docker_name="rocketchat"
+ local docker_port="3897"
+ local app_size="2"
+
+ docker_app_install() {
+ docker run --name db -d --restart=always \
+ -v /home/docker/mongo/dump:/dump \
+ mongo:latest --replSet rs5 --oplogSize 256
+ sleep 1
+ docker exec db mongosh --eval "printjson(rs.initiate())"
+ sleep 5
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+
+ clear
+ ip_address
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat:latest
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ clear
+ ip_address
+ echo "Rocket.chat이 설치되었습니다"
+ check_docker_app_ip
+ }
+
+ docker_app_uninstall() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat
+ docker rm -f db
+ docker rmi -f mongo:latest
+ rm -rf /home/docker/mongo
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 11|zentao)
+ local app_id="11"
+ local docker_name="zentao-server"
+ local docker_img="idoop/zentao:latest"
+ local docker_port=82
+
+
+ docker_rum() {
+
+
+ docker run -d -p ${docker_port}:80 \
+ -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
+ -e BIND_ADDRESS="false" \
+ -v /home/docker/zentao-server/:/opt/zbox/ \
+ --add-host smtp.exmail.qq.com:163.177.90.125 \
+ --name zentao-server \
+ --restart=always \
+ idoop/zentao:latest
+
+
+ }
+
+ local docker_describe="ZenTao는 범용 프로젝트 관리 소프트웨어입니다"
+ local docker_url="공식 홈페이지 소개 : https://www.zentao.net/"
+ local docker_use="echo \"초기 사용자 이름: admin\""
+ local docker_passwd="echo \"초기 비밀번호: 123456\""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 12|qinglong)
+ local app_id="12"
+ local docker_name="qinglong"
+ local docker_img="whyour/qinglong:latest"
+ local docker_port=5700
+
+ docker_rum() {
+
+
+ docker run -d \
+ -v /home/docker/qinglong/data:/ql/data \
+ -p ${docker_port}:5700 \
+ --name qinglong \
+ --hostname qinglong \
+ --restart=always \
+ whyour/qinglong:latest
+
+
+ }
+
+ local docker_describe="Qinglong Panel은 예약된 작업 관리 플랫폼입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/whyour/qinglong"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 13|cloudreve)
+
+ local app_id="13"
+ local app_name="클라우드리브 네트워크 디스크"
+ local app_text="cloudreve는 여러 클라우드 스토리지를 지원하는 네트워크 디스크 시스템입니다."
+ local app_url="영상 소개: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
+ local docker_name="cloudreve"
+ local docker_port="5212"
+ local app_size="2"
+
+ docker_app_install() {
+ cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
+ curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
+ sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
+ cd /home/docker/cloud/
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ cd /home/docker/cloud/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ rm -rf /home/docker/cloud
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+ ;;
+
+ 14|easyimage)
+ local app_id="14"
+ local docker_name="easyimage"
+ local docker_img="ddsderek/easyimage:latest"
+ local docker_port=8014
+ docker_rum() {
+
+ docker run -d \
+ --name easyimage \
+ -p ${docker_port}:80 \
+ -e TZ=Asia/Shanghai \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -v /home/docker/easyimage/config:/app/web/config \
+ -v /home/docker/easyimage/i:/app/web/i \
+ --restart=always \
+ ddsderek/easyimage:latest
+
+ }
+
+ local docker_describe="심플드로잉베드는 심플드로잉베드 프로그램입니다"
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/icret/EasyImages2.0"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 15|emby)
+ local app_id="15"
+ local docker_name="emby"
+ local docker_img="linuxserver/emby:latest"
+ local docker_port=8015
+
+ docker_rum() {
+
+ docker run -d --name=emby --restart=always \
+ -v /home/docker/emby/config:/config \
+ -v /home/docker/emby/share1:/mnt/share1 \
+ -v /home/docker/emby/share2:/mnt/share2 \
+ -v /mnt/notify:/mnt/notify \
+ -p ${docker_port}:8096 \
+ -e UID=1000 -e GID=100 -e GIDLIST=100 \
+ linuxserver/emby:latest
+
+ }
+
+
+ local docker_describe="emby는 서버의 비디오 및 오디오를 구성하고 오디오 및 비디오를 클라이언트 장치로 스트리밍하는 데 사용할 수 있는 마스터-슬레이브 아키텍처 미디어 서버 소프트웨어입니다."
+ local docker_url="공식 홈페이지 소개 : https://emby.media/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 16|looking)
+ local app_id="16"
+ local docker_name="looking-glass"
+ local docker_img="wikihostinc/looking-glass-server"
+ local docker_port=8016
+
+
+ docker_rum() {
+
+ docker run -d --name looking-glass --restart=always -p ${docker_port}:80 wikihostinc/looking-glass-server
+
+ }
+
+ local docker_describe="Speedtest 속도 측정 패널은 다양한 테스트 기능을 갖춘 VPS 네트워크 속도 테스트 도구이며 VPS 인바운드 및 아웃바운드 트래픽을 실시간으로 모니터링할 수도 있습니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/wikihost-opensource/als"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 17|adguardhome)
+
+ local app_id="17"
+ local docker_name="adguardhome"
+ local docker_img="adguard/adguardhome"
+ local docker_port=8017
+
+ docker_rum() {
+
+ docker run -d \
+ --name adguardhome \
+ -v /home/docker/adguardhome/work:/opt/adguardhome/work \
+ -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
+ -p 53:53/tcp \
+ -p 53:53/udp \
+ -p ${docker_port}:3000/tcp \
+ --restart=always \
+ adguard/adguardhome
+
+
+ }
+
+
+ local docker_describe="AdGuardHome은 미래에 단순한 DNS 서버 이상의 역할을 할 네트워크 전체의 광고 차단 및 추적 방지 소프트웨어입니다."
+ local docker_url="공식 홈페이지 소개: https://hub.docker.com/r/adguard/adguardhome"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 18|onlyoffice)
+
+ local app_id="18"
+ local docker_name="onlyoffice"
+ local docker_img="onlyoffice/documentserver"
+ local docker_port=8018
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:80 \
+ --restart=always \
+ --name onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
+ onlyoffice/documentserver
+
+
+ }
+
+ local docker_describe="onlyoffice는 오픈 소스 온라인 오피스 도구로 매우 강력합니다!"
+ local docker_url="공식 홈페이지 소개 : https://www.onlyoffice.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 19|safeline)
+ send_stats "천둥 웅덩이를 만들어라"
+
+ local app_id="19"
+ local docker_name=safeline-mgt
+ local docker_port=9443
+ while true; do
+ check_docker_app
+ clear
+ echo -e "썬더 풀 서비스$check_docker"
+ echo "레이치(Leichi)는 창팅테크놀로지(Changting Technology)가 개발한 WAF 사이트 방화벽 프로그램 패널로, 자동화된 방어를 위해 사이트를 반전시킬 수 있다."
+ echo "영상 소개: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ check_docker_app_ip
+ fi
+ echo ""
+
+ echo "------------------------"
+ echo "1. 설치 2. 업데이트 3. 비밀번호 재설정 4. 제거"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택 항목을 입력하세요." choice
+
+ case $choice in
+ 1)
+ install_docker
+ check_disk_space 5
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
+
+ add_app_id
+ clear
+ echo "Leichi WAF 패널이 설치되었습니다."
+ check_docker_app_ip
+ docker exec safeline-mgt resetadmin
+
+ ;;
+
+ 2)
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
+ docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
+ echo ""
+
+ add_app_id
+ clear
+ echo "Leichi WAF 패널이 업데이트되었습니다."
+ check_docker_app_ip
+ ;;
+ 3)
+ docker exec safeline-mgt resetadmin
+ ;;
+ 4)
+ cd /data/safeline
+ docker compose down --rmi all
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "기본 설치 디렉터리에 있다면 이제 프로젝트가 제거된 것입니다. 설치 디렉터리를 사용자 정의하는 경우 설치 디렉터리로 이동하여 직접 실행해야 합니다."
+ echo "docker compose down && docker compose down --rmi all"
+ ;;
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 20|portainer)
+ local app_id="20"
+ local docker_name="portainer"
+ local docker_img="portainer/portainer"
+ local docker_port=8020
+
+ docker_rum() {
+
+ docker run -d \
+ --name portainer \
+ -p ${docker_port}:9000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/portainer:/data \
+ --restart=always \
+ portainer/portainer
+
+ }
+
+
+ local docker_describe="portainer는 경량 도커 컨테이너 관리 패널입니다."
+ local docker_url="공식 홈페이지 소개 : https://www.portainer.io/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 21|vscode)
+ local app_id="21"
+ local docker_name="vscode-web"
+ local docker_img="codercom/code-server"
+ local docker_port=8021
+
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart=always codercom/code-server
+
+ }
+
+
+ local docker_describe="VScode는 강력한 온라인 코드 작성 도구입니다"
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/coder/code-server"
+ local docker_use="sleep 3"
+ local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 22|uptime-kuma)
+ local app_id="22"
+ local docker_name="uptime-kuma"
+ local docker_img="louislam/uptime-kuma:latest"
+ local docker_port=8022
+
+
+ docker_rum() {
+
+ docker run -d \
+ --name=uptime-kuma \
+ -p ${docker_port}:3001 \
+ -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
+ --restart=always \
+ louislam/uptime-kuma:latest
+
+ }
+
+
+ local docker_describe="가동 시간 Kuma 사용하기 쉬운 자체 호스팅 모니터링 도구"
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/louislam/uptime-kuma"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 23|memos)
+ local app_id="23"
+ local docker_name="memos"
+ local docker_img="neosmemo/memos:stable"
+ local docker_port=8023
+
+ docker_rum() {
+
+ docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart=always neosmemo/memos:stable
+
+ }
+
+ local docker_describe="Memos는 경량의 자체 호스팅 메모 센터입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/usememos/memos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 24|webtop)
+ local app_id="24"
+ local docker_name="webtop"
+ local docker_img="lscr.io/linuxserver/webtop:latest"
+ local docker_port=8024
+
+ docker_rum() {
+
+ read -e -p "로그인 사용자 이름 설정:" admin
+ read -e -p "로그인 사용자 비밀번호 설정:" admin_password
+ docker run -d \
+ --name=webtop \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -e LC_ALL=zh_CN.UTF-8 \
+ -e DOCKER_MODS=linuxserver/mods:universal-package-install \
+ -e INSTALL_PACKAGES=font-noto-cjk \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:latest
+
+ }
+
+
+ local docker_describe="웹탑은 중국어 버전의 Alpine 컨테이너를 기반으로 합니다. 해당 IP에 접속할 수 없는 경우, 접속할 도메인 이름을 추가해 주세요."
+ local docker_url="공식 홈페이지 소개: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 25|nextcloud)
+ local app_id="25"
+ local docker_name="nextcloud"
+ local docker_img="nextcloud:latest"
+ local docker_port=8025
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+
+ }
+
+ local docker_describe="400,000개 이상 배포된 Nextcloud는 다운로드할 수 있는 가장 인기 있는 로컬 콘텐츠 협업 플랫폼입니다."
+ local docker_url="공식 홈페이지 소개 : https://nextcloud.com/"
+ local docker_use="echo \"계정: nextcloud 비밀번호:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 26|qd)
+ local app_id="26"
+ local docker_name="qd"
+ local docker_img="qdtoday/qd:latest"
+ local docker_port=8026
+
+ docker_rum() {
+
+ docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+
+ }
+
+ local docker_describe="QD-Today는 HTTP 요청 예약 작업 자동 실행 프레임워크입니다."
+ local docker_url="공식 홈페이지 소개: https://qd-today.github.io/qd/zh_CN/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 27|dockge)
+ local app_id="27"
+ local docker_name="dockge"
+ local docker_img="louislam/dockge:latest"
+ local docker_port=8027
+
+ docker_rum() {
+
+ docker run -d --name dockge --restart=always -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+
+ }
+
+ local docker_describe="Dockge는 시각적 Docker 작성 컨테이너 관리 패널입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/louislam/dockge"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 28|speedtest)
+ local app_id="28"
+ local docker_name="speedtest"
+ local docker_img="ghcr.io/librespeed/speedtest"
+ local docker_port=8028
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 --name speedtest --restart=always ghcr.io/librespeed/speedtest
+
+ }
+
+ local docker_describe="librespeed는 즉시 사용할 수 있는 Javascript로 구현된 경량 속도 테스트 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/librespeed/speedtest"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 29|searxng)
+ local app_id="29"
+ local docker_name="searxng"
+ local docker_img="searxng/searxng"
+ local docker_port=8029
+
+ docker_rum() {
+
+ docker run -d \
+ --name searxng \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v "/home/docker/searxng:/etc/searxng" \
+ searxng/searxng
+
+ }
+
+ local docker_describe="searxng는 비공개 및 비공개 검색 엔진 사이트입니다."
+ local docker_url="공식 홈페이지 소개 : https://hub.docker.com/r/alandoyle/searxng"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 30|photoprism)
+ local app_id="30"
+ local docker_name="photoprism"
+ local docker_img="photoprism/photoprism:latest"
+ local docker_port=8030
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d \
+ --name photoprism \
+ --restart=always \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ -p ${docker_port}:2342 \
+ -e PHOTOPRISM_UPLOAD_NSFW="true" \
+ -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
+ -v /home/docker/photoprism/storage:/photoprism/storage \
+ -v /home/docker/photoprism/Pictures:/photoprism/originals \
+ photoprism/photoprism
+
+ }
+
+
+ local docker_describe="포토프리즘은 매우 강력한 개인 사진 앨범 시스템입니다."
+ local docker_url="공식 홈페이지 소개 : https://www.photoprism.app/"
+ local docker_use="echo \"계정: admin 비밀번호:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 31|s-pdf)
+ local app_id="31"
+ local docker_name="s-pdf"
+ local docker_img="frooodle/s-pdf:latest"
+ local docker_port=8031
+
+ docker_rum() {
+
+ docker run -d \
+ --name s-pdf \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
+ -v /home/docker/s-pdf/extraConfigs:/configs \
+ -v /home/docker/s-pdf/logs:/logs \
+ -e DOCKER_ENABLE_SECURITY=false \
+ frooodle/s-pdf:latest
+ }
+
+ local docker_describe="이는 분할 병합, 변환, 재구성, 이미지 추가, 회전, 압축 등과 같은 PDF 파일에 대한 다양한 작업을 수행할 수 있는 docker를 사용하는 강력한 로컬 호스팅 웹 기반 PDF 조작 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 32|drawio)
+ local app_id="32"
+ local docker_name="drawio"
+ local docker_img="jgraph/drawio"
+ local docker_port=8032
+
+ docker_rum() {
+
+ docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+
+ }
+
+
+ local docker_describe="이것은 강력한 차트 작성 소프트웨어입니다. 마인드맵, 토폴로지 다이어그램, 흐름도를 그릴 수 있습니다."
+ local docker_url="공식 홈페이지 소개 : https://www.drawio.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 33|sun-panel)
+ local app_id="33"
+ local docker_name="sun-panel"
+ local docker_img="hslr/sun-panel"
+ local docker_port=8033
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:3002 \
+ -v /home/docker/sun-panel/conf:/app/conf \
+ -v /home/docker/sun-panel/uploads:/app/uploads \
+ -v /home/docker/sun-panel/database:/app/database \
+ --name sun-panel \
+ hslr/sun-panel
+
+ }
+
+ local docker_describe="Sun-Panel 서버, NAS 탐색 패널, 홈페이지, 브라우저 홈페이지"
+ local docker_url="공식 홈페이지 소개: https://doc.sun-panel.top/zh_cn/"
+ local docker_use="echo \"계정: admin@sun.cc 비밀번호: 12345678\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 34|pingvin-share)
+ local app_id="34"
+ local docker_name="pingvin-share"
+ local docker_img="stonith404/pingvin-share"
+ local docker_port=8034
+
+ docker_rum() {
+
+ docker run -d \
+ --name pingvin-share \
+ --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/pingvin-share/data:/opt/app/backend/data \
+ stonith404/pingvin-share
+ }
+
+ local docker_describe="Pingvin Share는 자체 구축 가능한 파일 공유 플랫폼이자 WeTransfer의 대안입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/stonith404/pingvin-share"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 35|moments)
+ local app_id="35"
+ local docker_name="moments"
+ local docker_img="kingwrcy/moments:latest"
+ local docker_port=8035
+
+ docker_rum() {
+
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/moments/data:/app/data \
+ -v /etc/localtime:/etc/localtime:ro \
+ -v /etc/timezone:/etc/timezone:ro \
+ --name moments \
+ kingwrcy/moments:latest
+ }
+
+
+ local docker_describe="미니멀리스트 순간, 높은 모방 WeChat 순간, 멋진 삶을 기록하세요"
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
+ local docker_use="echo \"계정: admin 비밀번호: a123456\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+
+ 36|lobe-chat)
+ local app_id="36"
+ local docker_name="lobe-chat"
+ local docker_img="lobehub/lobe-chat:latest"
+ local docker_port=8036
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:3210 \
+ --name lobe-chat \
+ --restart=always \
+ lobehub/lobe-chat
+ }
+
+ local docker_describe="LobeChat은 시장의 주류 AI 대형 모델인 ChatGPT/Claude/Gemini/Groq/Ollama를 통합합니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/lobehub/lobe-chat"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 37|myip)
+ local app_id="37"
+ local docker_name="myip"
+ local docker_img="jason5ng32/myip:latest"
+ local docker_port=8037
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+
+ }
+
+
+ local docker_describe="자신의 IP 정보와 연결성을 확인하고 웹 패널을 통해 표시할 수 있는 다기능 IP 도구 상자입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 38|xiaoya)
+ send_stats "Xiaoya 가족 버킷"
+ clear
+ install_docker
+ check_disk_space 1
+ bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
+ ;;
+
+ 39|bililive)
+
+ if [ ! -d /home/docker/bililive-go/ ]; then
+ mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
+ wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
+ fi
+
+ local app_id="39"
+ local docker_name="bililive-go"
+ local docker_img="chigusa/bililive-go"
+ local docker_port=8039
+
+ docker_rum() {
+
+ docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+
+ }
+
+ local docker_describe="Bililive-go는 다양한 라이브 방송 플랫폼을 지원하는 라이브 방송 녹음 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/hr3lxphr6j/bililive-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 40|webssh)
+ local app_id="40"
+ local docker_name="webssh"
+ local docker_img="jrohy/webssh"
+ local docker_port=8040
+ docker_rum() {
+ docker run -d -p ${docker_port}:5032 --restart=always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
+ }
+
+ local docker_describe="간단한 온라인 SSH 연결 도구 및 SFTP 도구"
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/Jrohy/webssh"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 41|haozi|acepanel)
+
+ local app_id="41"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AcePanel 오리지널 마우스 패널"
+ local panelurl="공식 주소:${gh_proxy}github.com/acepanel/panel"
+
+ panel_app_install() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+ }
+
+ panel_app_manage() {
+ acepanel help
+ }
+
+ panel_app_uninstall() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+
+ }
+
+ install_panel
+
+ ;;
+
+
+ 42|nexterm)
+ local app_id="42"
+ local docker_name="nexterm"
+ local docker_img="germannewsmaker/nexterm:latest"
+ local docker_port=8042
+
+ docker_rum() {
+
+ ENCRYPTION_KEY=$(openssl rand -hex 32)
+ docker run -d \
+ --name nexterm \
+ -e ENCRYPTION_KEY=${ENCRYPTION_KEY} \
+ -p ${docker_port}:6989 \
+ -v /home/docker/nexterm:/app/data \
+ --restart=always \
+ germannewsmaker/nexterm:latest
+
+ }
+
+ local docker_describe="nexterm은 강력한 온라인 SSH/VNC/RDP 연결 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_proxy}github.com/gnmyt/Nexterm"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 43|hbbs)
+ local app_id="43"
+ local docker_name="hbbs"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbs
+
+ }
+
+
+ local docker_describe="Rustdesk의 오픈 소스 원격 데스크톱(서버)은 자체 Sunflower 개인 서버와 유사합니다."
+ local docker_url="공식 홈페이지 소개: https://rustdesk.com/zh-cn/"
+ local docker_use="docker logs hbbs"
+ local docker_passwd="echo \"원격 데스크톱 클라이언트에서 사용될 IP와 키를 기록하세요. 릴레이를 설치하려면 옵션 44로 이동하세요!\""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 44|hbbr)
+ local app_id="44"
+ local docker_name="hbbr"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbr
+
+ }
+
+ local docker_describe="Rustdesk의 오픈 소스 원격 데스크톱(릴레이)은 자체 Sunflower 개인 서버와 유사합니다."
+ local docker_url="공식 홈페이지 소개: https://rustdesk.com/zh-cn/"
+ local docker_use="echo \"원격 데스크톱 클라이언트를 다운로드하려면 공식 웹사이트로 이동하세요: https://rustdesk.com/zh-cn/\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 45|registry)
+ local app_id="45"
+ local docker_name="registry"
+ local docker_img="registry:2"
+ local docker_port=8045
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name registry \
+ -v /home/docker/registry:/var/lib/registry \
+ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
+ --restart=always \
+ registry:2
+
+ }
+
+ local docker_describe="Docker Registry는 Docker 이미지를 저장하고 배포하는 서비스입니다."
+ local docker_url="공식 홈페이지 소개: https://hub.docker.com/_/registry"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 46|ghproxy)
+ local app_id="46"
+ local docker_name="ghproxy"
+ local docker_img="wjqserver/ghproxy:latest"
+ local docker_port=8046
+
+ docker_rum() {
+
+ docker run -d --name ghproxy --restart=always -p ${docker_port}:8080 -v /home/docker/ghproxy/config:/data/ghproxy/config wjqserver/ghproxy:latest
+
+ }
+
+ local docker_describe="Go를 사용하여 구현된 GHProxy는 일부 영역에서 Github 저장소 가져오기를 가속화하는 데 사용됩니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/WJQSERVER-STUDIO/ghproxy"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 47|prometheus|grafana)
+
+ local app_id="47"
+ local app_name="프로메테우스 모니터링"
+ local app_text="Prometheus+Grafana 전사적 모니터링 시스템"
+ local app_url="공식 홈페이지 소개 : https://prometheus.io"
+ local docker_name="grafana"
+ local docker_port="8047"
+ local app_size="2"
+
+ docker_app_install() {
+ prometheus_install
+ clear
+ ip_address
+ echo "설치 완료"
+ check_docker_app_ip
+ echo "초기 사용자 이름과 비밀번호는 admin입니다."
+ }
+
+ docker_app_update() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+
+ rm -rf /home/docker/monitoring
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+ ;;
+
+ 48|node-exporter)
+ local app_id="48"
+ local docker_name="node-exporter"
+ local docker_img="prom/node-exporter"
+ local docker_port=8048
+
+ docker_rum() {
+
+ docker run -d \
+ --name=node-exporter \
+ -p ${docker_port}:9100 \
+ --restart=always \
+ prom/node-exporter
+
+
+ }
+
+ local docker_describe="이는 Prometheus 호스트 데이터 수집 구성 요소입니다. 모니터링되는 호스트에 배포하세요."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/prometheus/node_exporter"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 49|cadvisor)
+ local app_id="49"
+ local docker_name="cadvisor"
+ local docker_img="gcr.io/cadvisor/cadvisor:latest"
+ local docker_port=8049
+
+ docker_rum() {
+
+ docker run -d \
+ --name=cadvisor \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ --volume=/:/rootfs:ro \
+ --volume=/var/run:/var/run:rw \
+ --volume=/sys:/sys:ro \
+ --volume=/var/lib/docker/:/var/lib/docker:ro \
+ gcr.io/cadvisor/cadvisor:latest \
+ -housekeeping_interval=10s \
+ -docker_only=true
+
+ }
+
+ local docker_describe="이는 Prometheus 컨테이너 데이터 수집 구성 요소입니다. 모니터링되는 호스트에 배포하세요."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/google/cadvisor"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 50|changedetection)
+ local app_id="50"
+ local docker_name="changedetection"
+ local docker_img="dgtlmoon/changedetection.io:latest"
+ local docker_port=8050
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:5000 \
+ -v /home/docker/datastore:/datastore \
+ --name changedetection dgtlmoon/changedetection.io:latest
+
+ }
+
+ local docker_describe="이는 웹사이트 변경 감지, 보충 모니터링 및 알림을 위한 작은 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/dgtlmoon/changedetection.io"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 51|pve)
+ clear
+ send_stats "PVE 오픈 병아리"
+ check_disk_space 1
+ curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
+ ;;
+
+
+ 52|dpanel)
+ local app_id="52"
+ local docker_name="dpanel"
+ local docker_img="dpanel/dpanel:lite"
+ local docker_port=8052
+
+ docker_rum() {
+
+ docker run -d --name dpanel --restart=always \
+ -p ${docker_port}:8080 -e APP_NAME=dpanel \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/dpanel:/dpanel \
+ dpanel/dpanel:lite
+
+ }
+
+ local docker_describe="Docker 시각적 패널 시스템은 완전한 Docker 관리 기능을 제공합니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/donknap/dpanel"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 53|llama3)
+ local app_id="53"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI는 새로운 llama3 대규모 언어 모델에 연결된 대규모 언어 모델 웹 페이지 프레임워크입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run llama3.2:1b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+ 54|amh)
+
+ local app_id="54"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AMH 패널"
+ local panelurl="공식 주소 : https://amh.sh/index.htm?amh"
+
+ panel_app_install() {
+ cd ~
+ wget https://dl.amh.sh/amh.sh && bash amh.sh
+ }
+
+ panel_app_manage() {
+ panel_app_install
+ }
+
+ panel_app_uninstall() {
+ panel_app_install
+ }
+
+ install_panel
+ ;;
+
+
+ 55|frps)
+ frps_panel
+ ;;
+
+ 56|frpc)
+ frpc_panel
+ ;;
+
+ 57|deepseek)
+ local app_id="57"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI는 새로운 DeepSeek R1 대규모 언어 모델에 연결된 대규모 언어 모델 웹 페이지 프레임워크입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+ 58|dify)
+ local app_id="58"
+ local app_name="DifyKnowledge Base"
+ local app_text="오픈 소스 LLM(대형 언어 모델) 애플리케이션 개발 플랫폼입니다. AI 생성을 위한 자체 호스팅 학습 데이터"
+ local app_url="공식 홈페이지: https://docs.dify.ai/zh-hans"
+ local docker_name="docker-nginx-1"
+ local docker_port="8058"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
+ sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+
+ docker compose up -d
+
+ chown -R 1001:1001 /home/docker/dify/docker/volumes/app/storage
+ chmod -R 755 /home/docker/dify/docker/volumes/app/storage
+ docker compose down
+ docker compose up -d
+
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ cd /home/docker/dify/
+ git pull ${gh_proxy}github.com/langgenius/dify.git main > /dev/null 2>&1
+ sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
+ cd /home/docker/dify/docker/ && docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/dify
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+ ;;
+
+ 59|new-api)
+ local app_id="59"
+ local app_name="NewAPI"
+ local app_text="차세대 대형 모델 게이트웨이 및 AI 자산 관리 시스템"
+ local app_url="공식 웹사이트:${gh_https_url}github.com/Calcium-Ion/new-api"
+ local docker_name="new-api"
+ local docker_port="8059"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ cd /home/docker/new-api/
+
+ git pull ${gh_proxy}github.com/Calcium-Ion/new-api.git main > /dev/null 2>&1
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ rm -rf /home/docker/new-api
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 60|jms)
+
+ local app_id="60"
+ local app_name="JumpServer 오픈 소스 요새 머신"
+ local app_text="오픈소스 PAM(Privileged Access Management) 도구입니다. 이 프로그램은 포트 80을 사용하며 액세스를 위한 도메인 이름 추가를 지원하지 않습니다."
+ local app_url="공식 소개:${gh_https_url}github.com/jumpserver/jumpserver"
+ local docker_name="jms_web"
+ local docker_port="80"
+ local app_size="2"
+
+ docker_app_install() {
+ curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ echo "초기 사용자 이름: admin"
+ echo "초기 비밀번호: ChangeMe"
+ }
+
+
+ docker_app_update() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh upgrade
+ echo "앱이 업데이트되었습니다"
+ }
+
+
+ docker_app_uninstall() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh uninstall
+ cd /opt
+ rm -rf jumpserver-installer*/
+ rm -rf jumpserver
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+ ;;
+
+ 61|libretranslate)
+ local app_id="61"
+ local docker_name="libretranslate"
+ local docker_img="libretranslate/libretranslate:latest"
+ local docker_port=8061
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name libretranslate \
+ libretranslate/libretranslate \
+ --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+
+ }
+
+ local docker_describe="무료 오픈 소스 기계 번역 API, 완전 자체 호스팅 및 번역 엔진은 오픈 소스 Argos Translate 라이브러리에 의해 구동됩니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/LibreTranslate/LibreTranslate"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+
+ 62|ragflow)
+ local app_id="62"
+ local app_name="RAGFlow 지식 기반"
+ local app_text="깊은 문서 이해를 기반으로 한 오픈소스 RAG(Retrieval Augmented Generation) 엔진"
+ local app_url="공식 웹사이트:${gh_https_url}github.com/infiniflow/ragflow"
+ local docker_name="ragflow-server"
+ local docker_port="8062"
+ local app_size="8"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ cd /home/docker/ragflow/
+ git pull ${gh_proxy}github.com/infiniflow/ragflow.git main > /dev/null 2>&1
+ cd /home/docker/ragflow/docker/
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/ragflow
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 63|open-webui)
+ local app_id="63"
+ local docker_name="open-webui"
+ local docker_img="ghcr.io/open-webui/open-webui:main"
+ local docker_port=8063
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart=always ghcr.io/open-webui/open-webui:main
+
+ }
+
+ local docker_describe="OpenWebUI는 대규모 언어 모델 웹 페이지 프레임워크로, 공식 단순화 버전은 모든 주요 모델에 대한 API 액세스를 지원합니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 64|it-tools)
+ local app_id="64"
+ local docker_name="it-tools"
+ local docker_img="corentinth/it-tools:latest"
+ local docker_port=8064
+
+ docker_rum() {
+ docker run -d --name it-tools --restart=always -p ${docker_port}:80 corentinth/it-tools:latest
+ }
+
+ local docker_describe="개발자와 IT 작업자에게 매우 유용한 도구"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/CorentinTh/it-tools"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 65|n8n)
+ local app_id="65"
+ local docker_name="n8n"
+ local docker_img="docker.n8n.io/n8nio/n8n"
+ local docker_port=8065
+
+ docker_rum() {
+
+ add_yuming
+ mkdir -p /home/docker/n8n
+ chmod -R 777 /home/docker/n8n
+
+ docker run -d --name n8n \
+ --restart=always \
+ -p ${docker_port}:5678 \
+ -v /home/docker/n8n:/home/node/.n8n \
+ -e N8N_HOST=${yuming} \
+ -e N8N_PORT=5678 \
+ -e N8N_PROTOCOL=https \
+ -e WEBHOOK_URL=https://${yuming}/ \
+ docker.n8n.io/n8nio/n8n
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
+
+ }
+
+ local docker_describe="강력한 자동화된 워크플로우 플랫폼입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/n8n-io/n8n"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 66|yt)
+ yt_menu_pro
+ ;;
+
+
+ 67|ddns)
+ local app_id="67"
+ local docker_name="ddns-go"
+ local docker_img="jeessy/ddns-go"
+ local docker_port=8067
- docker_rum() {
+ docker_rum() {
+ docker run -d \
+ --name ddns-go \
+ --restart=always \
+ -p ${docker_port}:9876 \
+ -v /home/docker/ddns-go:/root \
+ jeessy/ddns-go
- docker run -d --restart=always -p ${docker_port}:3002 \
- -v /home/docker/sun-panel/conf:/app/conf \
- -v /home/docker/sun-panel/uploads:/app/uploads \
- -v /home/docker/sun-panel/database:/app/database \
- --name sun-panel \
- hslr/sun-panel
+ }
- }
+ local docker_describe="동적 도메인 이름 확인을 위해 공용 IP(IPv4/IPv6)를 주요 DNS 서비스 제공업체에 실시간으로 자동 업데이트합니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/jeessy2/ddns-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
- local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
- local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 68|allinssl)
+ local app_id="68"
+ local docker_name="allinssl"
+ local docker_img="allinssl/allinssl:latest"
+ local docker_port=8068
- 34)
- local docker_name="pingvin-share"
- local docker_img="stonith404/pingvin-share"
- local docker_port=3060
-
- docker_rum() {
-
- docker run -d \
- --name pingvin-share \
- --restart always \
- -p ${docker_port}:3000 \
- -v /home/docker/pingvin-share/data:/opt/app/backend/data \
- stonith404/pingvin-share
- }
+ docker_rum() {
+ docker run -d --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
+ }
- local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
- local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="오픈 소스 무료 SSL 인증서 자동화 관리 플랫폼"
+ local docker_url="공식 홈페이지 소개: https://allinssl.com"
+ local docker_use="echo \"보안 입구: /allinssl\""
+ local docker_passwd="echo \"사용자 이름: allinssl 비밀번호: allinssldocker\""
+ local app_size="1"
+ docker_app
+ ;;
- 35)
- local docker_name="moments"
- local docker_img="kingwrcy/moments:latest"
- local docker_port=8035
-
- docker_rum() {
-
- docker run -d --restart unless-stopped \
- -p ${docker_port}:3000 \
- -v /home/docker/moments/data:/app/data \
- -v /etc/localtime:/etc/localtime:ro \
- -v /etc/timezone:/etc/timezone:ro \
- --name moments \
- kingwrcy/moments:latest
- }
+ 69|sftpgo)
+ local app_id="69"
+ local docker_name="sftpgo"
+ local docker_img="drakkan/sftpgo:latest"
+ local docker_port=8069
+ docker_rum() {
- local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
- local docker_url="공식 웹 사이트 소개 :${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
- local docker_use="echo \"账号: admin 密码: a123456\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/sftpgo/data
+ mkdir -p /home/docker/sftpgo/config
+ chown -R 1000:1000 /home/docker/sftpgo
+ docker run -d \
+ --name sftpgo \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -p 22022:2022 \
+ --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
+ --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
+ drakkan/sftpgo:latest
+ }
- 36)
- local docker_name="lobe-chat"
- local docker_img="lobehub/lobe-chat:latest"
- local docker_port=8036
+ local docker_describe="언제 어디서나 무료 오픈 소스 SFTP FTP WebDAV 파일 전송 도구"
+ local docker_url="공식 홈페이지 소개 : https://sftpgo.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_rum() {
- docker run -d -p ${docker_port}:3210 \
- --name lobe-chat \
- --restart=always \
- lobehub/lobe-chat
- }
+ 70|astrbot)
+ local app_id="70"
+ local docker_name="astrbot"
+ local docker_img="soulter/astrbot:latest"
+ local docker_port=8070
- local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
- local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker_rum() {
- 37)
- local docker_name="myip"
- local docker_img="jason5ng32/myip:latest"
- local docker_port=8037
+ mkdir -p /home/docker/astrbot/data
+
+ docker run -d \
+ -p ${docker_port}:6185 \
+ -p 6195:6195 \
+ -p 6196:6196 \
+ -p 6199:6199 \
+ -p 11451:11451 \
+ -v /home/docker/astrbot/data:/AstrBot/data \
+ --restart=always \
+ --name astrbot \
+ soulter/astrbot:latest
+
+ }
+
+ local docker_describe="대규모 AI 모델에 대한 WeChat, QQ 및 TG 액세스를 지원하는 오픈 소스 AI 챗봇 프레임워크"
+ local docker_url="공식 홈페이지 소개: https://astrbot.app/"
+ local docker_use="echo \"사용자 이름: astrbot 비밀번호: astrbot\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 71|navidrome)
+ local app_id="71"
+ local docker_name="navidrome"
+ local docker_img="deluan/navidrome:latest"
+ local docker_port=8071
+
+ docker_rum() {
+
+ docker run -d \
+ --name navidrome \
+ --restart=always \
+ --user $(id -u):$(id -g) \
+ -v /home/docker/navidrome/music:/music \
+ -v /home/docker/navidrome/data:/data \
+ -p ${docker_port}:4533 \
+ -e ND_LOGLEVEL=info \
+ deluan/navidrome:latest
+
+ }
+
+ local docker_describe="경량, 고성능 음악 스트리밍 서버입니다."
+ local docker_url="공식 홈페이지 소개 : https://www.navidrome.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 72|bitwarden)
+
+ local app_id="72"
+ local docker_name="bitwarden"
+ local docker_img="vaultwarden/server"
+ local docker_port=8072
+
+ docker_rum() {
+
+ docker run -d \
+ --name bitwarden \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/bitwarden/data:/data \
+ vaultwarden/server
+
+ }
+
+ local docker_describe="귀하의 데이터를 통제할 수 있는 비밀번호 관리자"
+ local docker_url="공식 홈페이지 소개: https://bitwarden.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+
+ ;;
+
+
+
+ 73|libretv)
+
+ local app_id="73"
+ local docker_name="libretv"
+ local docker_img="bestzwei/libretv:latest"
+ local docker_port=8073
+
+ docker_rum() {
+
+ read -e -p "LibreTV 로그인 비밀번호 설정:" app_passwd
+
+ docker run -d \
+ --name libretv \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -e PASSWORD=${app_passwd} \
+ bestzwei/libretv:latest
+
+ }
+
+ local docker_describe="무료 온라인 비디오 검색 및 시청 플랫폼"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/LibreSpark/LibreTV"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 74|moontv)
+
+ local app_id="74"
+
+ local app_name="Moontv 개인 영화 및 TV"
+ local app_text="무료 온라인 비디오 검색 및 시청 플랫폼"
+ local app_url="비디오 소개:${gh_https_url}github.com/MoonTechLab/LunaTV"
+ local docker_name="moontv-core"
+ local docker_port="8074"
+ local app_size="2"
+
+ docker_app_install() {
+ read -e -p "로그인 사용자 이름 설정:" admin
+ read -e -p "로그인 사용자 비밀번호 설정:" admin_password
+ read -e -p "인증 코드 입력:" shouquanma
+
+
+ mkdir -p /home/docker/moontv
+ mkdir -p /home/docker/moontv/config
+ mkdir -p /home/docker/moontv/data
+ cd /home/docker/moontv
+
+ curl -o /home/docker/moontv/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/moontv-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin_password|${admin_password}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin|${admin}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|shouquanma|${shouquanma}|g" /home/docker/moontv/docker-compose.yml
+ cd /home/docker/moontv/
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ cd /home/docker/moontv/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ rm -rf /home/docker/moontv
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 75|melody)
+
+ local app_id="75"
+ local docker_name="melody"
+ local docker_img="foamzou/melody:latest"
+ local docker_port=8075
+
+ docker_rum() {
+
+ docker run -d \
+ --name melody \
+ --restart=always \
+ -p ${docker_port}:5566 \
+ -v /home/docker/melody/.profile:/app/backend/.profile \
+ foamzou/melody:latest
+
+
+ }
+
+ local docker_describe="음악을 더 잘 관리할 수 있도록 설계된 음악 마법사입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/foamzou/melody"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+
+ ;;
+
+
+ 76|dosgame)
+
+ local app_id="76"
+ local docker_name="dosgame"
+ local docker_img="oldiy/dosgame-web-docker:latest"
+ local docker_port=8076
+
+ docker_rum() {
+ docker run -d \
+ --name dosgame \
+ --restart=always \
+ -p ${docker_port}:262 \
+ oldiy/dosgame-web-docker:latest
+
+ }
+
+ local docker_describe="중국의 DOS 게임 컬렉션 웹사이트입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/rwv/chinese-dos-games"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+
+ 77|xunlei)
+
+ local app_id="77"
+ local docker_name="xunlei"
+ local docker_img="cnk3x/xunlei"
+ local docker_port=8077
+
+ docker_rum() {
+
+ read -e -p "로그인 사용자 이름 설정:" app_use
+ read -e -p "로그인 비밀번호 설정:" app_passwd
+
+ docker run -d \
+ --name xunlei \
+ --restart=always \
+ --privileged \
+ -e XL_DASHBOARD_USERNAME=${app_use} \
+ -e XL_DASHBOARD_PASSWORD=${app_passwd} \
+ -v /home/docker/xunlei/data:/xunlei/data \
+ -v /home/docker/xunlei/downloads:/xunlei/downloads \
+ -p ${docker_port}:2345 \
+ cnk3x/xunlei
+
+ }
+
+ local docker_describe="오프라인 고속 BT 자기 다운로드 도구인 Xunlei"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/cnk3x/xunlei"
+ local docker_use="echo \"휴대폰으로 Xunlei에 로그인하고 초대 코드를 입력하세요. 초대 코드: Xunlei Niutong\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 78|PandaWiki)
+
+ local app_id="78"
+ local app_name="PandaWiki"
+ local app_text="PandaWiki는 AI 대형 모델을 기반으로 하는 오픈 소스 지능형 문서 관리 시스템입니다. 포트 배포를 사용자 정의하지 않는 것이 좋습니다."
+ local app_url="공식 소개:${gh_https_url}github.com/chaitin/PandaWiki"
+ local docker_name="panda-wiki-nginx"
+ local docker_port="2443"
+ local app_size="2"
+
+ docker_app_install() {
+ bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
+ }
+
+ docker_app_update() {
+ docker_app_install
+ }
+
+
+ docker_app_uninstall() {
+ docker_app_install
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 79|beszel)
+
+ local app_id="79"
+ local docker_name="beszel"
+ local docker_img="henrygd/beszel"
+ local docker_port=8079
+
+ docker_rum() {
+
+ mkdir -p /home/docker/beszel && \
+ docker run -d \
+ --name beszel \
+ --restart=always \
+ -v /home/docker/beszel:/beszel_data \
+ -p ${docker_port}:8090 \
+ henrygd/beszel
+
+ }
+
+ local docker_describe="Beszel은 가볍고 사용하기 쉬운 서버 모니터링입니다."
+ local docker_url="공식 홈페이지 소개: https://beszel.dev/zh/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 80|linkwarden)
+
+ local app_id="80"
+ local app_name="링크워든 북마크 관리"
+ local app_text="태그 지정, 검색 및 팀 협업을 지원하는 오픈 소스, 자체 호스팅 북마크 관리 플랫폼입니다."
+ local app_url="공식 홈페이지: https://linkwarden.app/"
+ local docker_name="linkwarden-linkwarden-1"
+ local docker_port="8080"
+ local app_size="3"
+
+ docker_app_install() {
+ install git openssl
+ mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
+
+ # 공식 docker-compose 및 env 파일 다운로드
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
+
+ # 임의의 키와 비밀번호 생성
+ local ADMIN_EMAIL="admin@example.com"
+ local ADMIN_PASSWORD=$(openssl rand -hex 8)
+
+ sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
+ sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
+ sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
+ sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
+
+ # 관리자 계정 정보 추가
+ echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
+ echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
+
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ # 컨테이너 시작
+ docker compose up -d
+
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
+
+ # 원래 변수 유지
+ source .env
+ mv .env.new .env
+ echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
+ echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
+ echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
+ echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
+ echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
+ echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ rm -rf /home/docker/linkwarden
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+
+ 81|jitsi)
+ local app_id="81"
+ local app_name="JitsiMeet 화상 회의"
+ local app_text="다자간 온라인 회의, 화면 공유, 암호화된 통신을 지원하는 오픈 소스 보안 화상 회의 솔루션입니다."
+ local app_url="공식 홈페이지: https://jitsi.org/"
+ local docker_name="jitsi"
+ local docker_app_service="jitsi-web-1"
+ local docker_port="8081"
+ local app_size="3"
+
+ docker_app_install() {
+
+ add_yuming
+ mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
+ rm -rf docker-jitsi-meet-* jitsi.zip
+ local jitsi_zip_url=$(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep -m1 '"zipball_url"' | cut -d\" -f4)
+ if [ -z "$jitsi_zip_url" ]; then
+ echo "Failed to get Jitsi Meet latest download URL"
+ return 1
+ fi
+ wget -O jitsi.zip "$jitsi_zip_url"
+ unzip -q jitsi.zip
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ cp env.example .env
+ ./gen-passwords.sh
+ mkdir -p /home/docker/jitsi/config/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
+ sed -i "s|^CONFIG=.*|CONFIG=/home/docker/jitsi/config|" .env
+ grep -q '^COMPOSE_PROJECT_NAME=' .env && sed -i "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=jitsi|" .env || echo "COMPOSE_PROJECT_NAME=jitsi" >> .env
+ sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
+ sed -i "s|^#\?PUBLIC_URL=.*|PUBLIC_URL=https://$yuming|" .env
+ grep -q '^PUBLIC_URL=' .env || echo "PUBLIC_URL=https://$yuming" >> .env
+ grep -q '^DISABLE_HTTPS=' .env && sed -i "s|^DISABLE_HTTPS=.*|DISABLE_HTTPS=1|" .env || echo "DISABLE_HTTPS=1" >> .env
+ grep -q '^ENABLE_HTTP_REDIRECT=' .env && sed -i "s|^ENABLE_HTTP_REDIRECT=.*|ENABLE_HTTP_REDIRECT=0|" .env || echo "ENABLE_HTTP_REDIRECT=0" >> .env
+ sed -i "/'\${HTTPS_PORT}:443'/d" docker-compose.yml
+ docker compose up -d
+ local jitsi_web_container=$(docker compose ps -q web)
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ [ -n "$jitsi_web_container" ] && block_container_port "$jitsi_web_container" "$ipv4_address"
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose pull
+ docker compose up -d
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose down --rmi all
+ rm -rf /home/docker/jitsi
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+
+ 82|gpt-load)
+
+ local app_id="82"
+ local docker_name="gpt-load"
+ local docker_img="tbphp/gpt-load:latest"
+ local docker_port=8082
+
+ docker_rum() {
+
+ read -e -p "설정${docker_name}로그인 키(sk-다음과 같이 시작하는 문자와 숫자의 조합): sk-159kejilionyyds163:" app_passwd
+
+ mkdir -p /home/docker/gpt-load && \
+ docker run -d --name gpt-load \
+ -p ${docker_port}:3001 \
+ -e AUTH_KEY=${app_passwd} \
+ -v "/home/docker/gpt-load/data":/app/data \
+ tbphp/gpt-load:latest
+
+ }
+
+ local docker_describe="고성능 AI 인터페이스 투명 프록시 서비스"
+ local docker_url="공식 홈페이지 소개: https://www.gpt-load.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 83|komari)
+
+ local app_id="83"
+ local docker_name="komari"
+ local docker_img="ghcr.io/komari-monitor/komari:latest"
+ local docker_port=8083
+
+ docker_rum() {
+
+ mkdir -p /home/docker/komari && \
+ docker run -d \
+ --name komari \
+ -p ${docker_port}:25774 \
+ -v /home/docker/komari:/app/data \
+ -e ADMIN_USERNAME=admin \
+ -e ADMIN_PASSWORD=1212156 \
+ -e TZ=Asia/Shanghai \
+ --restart=always \
+ ghcr.io/komari-monitor/komari:latest
+
+ }
+
+ local docker_describe="경량 자체 호스팅 서버 모니터링 도구"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/komari-monitor/komari/tree/main"
+ local docker_use="echo \"기본 계정: admin 기본 비밀번호: 1212156\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 84|wallos)
+
+ local app_id="84"
+ local docker_name="wallos"
+ local docker_img="bellamy/wallos:latest"
+ local docker_port=8084
+
+ docker_rum() {
+
+ mkdir -p /home/docker/wallos && \
+ docker run -d --name wallos \
+ -v /home/docker/wallos/db:/var/www/html/db \
+ -v /home/docker/wallos/logos:/var/www/html/images/uploads/logos \
+ -e TZ=UTC \
+ -p ${docker_port}:80 \
+ --restart=always \
+ bellamy/wallos:latest
+
+ }
+
+ local docker_describe="재무 관리를 위한 오픈 소스 개인 구독 추적기"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/ellite/Wallos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 85|immich)
+
+ local app_id="85"
+ local app_name="이미치 픽처 비디오 매니저"
+ local app_text="고성능 자체 호스팅 사진 및 비디오 관리 솔루션입니다."
+ local app_url="공식 웹사이트 소개:${gh_https_url}github.com/immich-app/immich"
+ local docker_name="immich_server"
+ local docker_port="8085"
+ local app_size="3"
+
+ docker_app_install() {
+ install git openssl wget
+ mkdir -p /home/docker/${docker_name} && cd /home/docker/${docker_name}
+
+ wget -O docker-compose.yml ${gh_proxy}github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+ wget -O .env ${gh_proxy}github.com/immich-app/immich/releases/latest/download/example.env
+ sed -i "s/2283:2283/${docker_port}:2283/g" /home/docker/${docker_name}/docker-compose.yml
+
+ docker compose up -d
+
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ rm -rf /home/docker/${docker_name}
+ echo "앱이 제거되었습니다."
+ }
+
+ docker_app_plus
+
+
+ ;;
+
+
+ 86|jellyfin)
+
+ local app_id="86"
+ local docker_name="jellyfin"
+ local docker_img="jellyfin/jellyfin"
+ local docker_port=8086
+
+ docker_rum() {
+
+ mkdir -p /home/docker/jellyfin/media
+ chmod -R 777 /home/docker/jellyfin
+
+ docker run -d \
+ --name jellyfin \
+ --user root \
+ --volume /home/docker/jellyfin/config:/config \
+ --volume /home/docker/jellyfin/cache:/cache \
+ --mount type=bind,source=/home/docker/jellyfin/media,target=/media \
+ -p ${docker_port}:8096 \
+ -p 7359:7359/udp \
+ --restart=always \
+ jellyfin/jellyfin
+
+
+ }
+
+ local docker_describe="오픈 소스 미디어 서버 소프트웨어입니다"
+ local docker_url="공식 홈페이지 소개 : https://jellyfin.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 87|synctv)
+
+ local app_id="87"
+ local docker_name="synctv"
+ local docker_img="synctvorg/synctv"
+ local docker_port=8087
+
+ docker_rum() {
+
+ docker run -d \
+ --name synctv \
+ -v /home/docker/synctv:/root/.synctv \
+ -p ${docker_port}:8080 \
+ --restart=always \
+ synctvorg/synctv
+
+ }
+
+ local docker_describe="영화와 생방송을 원격으로 함께 시청할 수 있는 프로그램입니다. 동시 시청, 라이브 방송, 채팅 및 기타 기능을 제공합니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/synctv-org/synctv"
+ local docker_use="echo \"초기 계정 및 비밀번호: root. 로그인 후 시간에 맞춰 로그인 비밀번호를 변경하세요\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 88|owncast)
+
+ local app_id="88"
+ local docker_name="owncast"
+ local docker_img="owncast/owncast:latest"
+ local docker_port=8088
+
+ docker_rum() {
+
+ docker run -d \
+ --name owncast \
+ -p ${docker_port}:8080 \
+ -p 1935:1935 \
+ -v /home/docker/owncast/data:/app/data \
+ --restart=always \
+ owncast/owncast:latest
+
+
+ }
+
+ local docker_describe="오픈 소스, 자체 구축한 무료 라이브 방송 플랫폼"
+ local docker_url="공식 홈페이지 소개 : https://owncast.online"
+ local docker_use="echo \"접속 주소 뒤에는 /admin이 붙어서 관리자 페이지에 접근합니다\""
+ local docker_passwd="echo \"초기 계정: admin 초기 비밀번호: abc123 로그인 후 시간 내에 로그인 비밀번호를 변경해주세요\""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+ 89|file-code-box)
+
+ local app_id="89"
+ local docker_name="file-code-box"
+ local docker_img="lanol/filecodebox:latest"
+ local docker_port=8089
+
+ docker_rum() {
+
+ docker run -d \
+ --name file-code-box \
+ -p ${docker_port}:12345 \
+ -v /home/docker/file-code-box/data:/app/data \
+ --restart=always \
+ lanol/filecodebox:latest
+
+ }
+
+ local docker_describe="익명의 비밀번호로 텍스트와 파일을 공유하고, 빠른 배송과 같은 파일 픽업"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/vastsa/FileCodeBox"
+ local docker_use="echo \"접속 주소 뒤에는 /#/admin이 붙어서 관리자 페이지에 접속합니다\""
+ local docker_passwd="echo \"관리자 비밀번호: FileCodeBox2023\""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+
+
+ 90|matrix)
+
+ local app_id="90"
+ local docker_name="matrix"
+ local docker_img="matrixdotorg/synapse:latest"
+ local docker_port=8090
+
+ docker_rum() {
+
+ add_yuming
+
+ if [ ! -d /home/docker/matrix/data ]; then
+ docker run --rm \
+ -v /home/docker/matrix/data:/data \
+ -e SYNAPSE_SERVER_NAME=${yuming} \
+ -e SYNAPSE_REPORT_STATS=yes \
+ --name matrix \
+ matrixdotorg/synapse:latest generate
+ fi
- docker_rum() {
+ docker run -d \
+ --name matrix \
+ -v /home/docker/matrix/data:/data \
+ -p ${docker_port}:8008 \
+ --restart=always \
+ matrixdotorg/synapse:latest
- docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+ echo "초기 사용자 또는 관리자를 생성합니다. 다음 사용자 이름과 비밀번호를 설정하고 관리자인지 여부를 설정하세요."
+ docker exec -it matrix register_new_matrix_user \
+ http://localhost:8008 \
+ -c /data/homeserver.yaml
- }
+ sed -i '/^enable_registration:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration: true' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^enable_registration_without_verification:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration_without_verification: true' /home/docker/matrix/data/homeserver.yaml
+ docker restart matrix
- local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
- local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 38)
- send_stats "Xiaoya 가족 버킷"
- clear
- install_docker
- check_disk_space 1
- bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
- ;;
+ }
- 39)
+ local docker_describe="Matrix는 분산형 채팅 프로토콜입니다."
+ local docker_url="공식 홈페이지 소개 : https://matrix.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- if [ ! -d /home/docker/bililive-go/ ]; then
- mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
- wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
- fi
+ ;;
- local docker_name="bililive-go"
- local docker_img="chigusa/bililive-go"
- local docker_port=8039
- docker_rum() {
- docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+ 91|gitea)
- }
+ local app_id="91"
- local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_name="gitea 비공개 코드 저장소"
+ local app_text="GitHub에 가까운 경험을 제공하는 무료 차세대 코드 호스팅 플랫폼입니다."
+ local app_url="비디오 소개:${gh_https_url}github.com/go-gitea/gitea"
+ local docker_name="gitea"
+ local docker_port="8091"
+ local app_size="2"
- 40)
- local docker_name="webssh"
- local docker_img="jrohy/webssh"
- local docker_port=8040
- docker_rum() {
- docker run -d -p ${docker_port}:5032 --restart always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
- }
+ docker_app_install() {
- local docker_describe="简易在线ssh连接工具和sftp工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/gitea
+ mkdir -p /home/docker/gitea/gitea
+ mkdir -p /home/docker/gitea/data
+ mkdir -p /home/docker/gitea/postgres
+ cd /home/docker/gitea
- 41)
+ curl -o /home/docker/gitea/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/gitea-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/gitea/docker-compose.yml
+ cd /home/docker/gitea/
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="耗子面板"
- local panelurl="官方地址: ${gh_proxy}github.com/TheTNB/panel"
- panel_app_install() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
- cd ~
- }
+ docker_app_update() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ cd /home/docker/gitea/ && docker compose up -d
+ }
- panel_app_manage() {
- panel-cli
- }
- panel_app_uninstall() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
- cd ~
- }
+ docker_app_uninstall() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ rm -rf /home/docker/gitea
+ echo "앱이 제거되었습니다."
+ }
- install_panel
+ docker_app_plus
- ;;
+ ;;
- 42)
- local docker_name="nexterm"
- local docker_img="germannewsmaker/nexterm:latest"
- local docker_port=8042
- docker_rum() {
- docker run -d \
- --name nexterm \
- -p ${docker_port}:6989 \
- -v /home/docker/nexterm:/app/data \
- --restart unless-stopped \
- germannewsmaker/nexterm:latest
+ 92|filebrowser)
- }
+ local app_id="92"
+ local docker_name="filebrowser"
+ local docker_img="hurlenko/filebrowser"
+ local docker_port=8092
- local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
- local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 43)
- local docker_name="hbbs"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker run -d \
+ --name filebrowser \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/filebrowser/data:/data \
+ -v /home/docker/filebrowser/config:/config \
+ -e FB_BASEURL=/filebrowser \
+ hurlenko/filebrowser
- docker_rum() {
+ }
- docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbs
+ local docker_describe="웹 기반 파일 관리자입니다"
+ local docker_url="공식 홈페이지 소개 : https://filebrowser.org/"
+ local docker_use="docker logs filebrowser"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
+ 93|dufs)
- local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="docker logs hbbs"
- local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
- local app_size="1"
- docker_app
- ;;
+ local app_id="93"
+ local docker_name="dufs"
+ local docker_img="sigoden/dufs"
+ local docker_port=8093
- 44)
- local docker_name="hbbr"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker_rum() {
- docker_rum() {
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}:/data \
+ -p ${docker_port}:5000 \
+ ${docker_img} /data -A
- docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbr
+ }
- }
+ local docker_describe="미니멀리스트 정적 파일 서버, 업로드 및 다운로드 지원"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/sigoden/dufs"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 45)
- local docker_name="registry"
- local docker_img="registry:2"
- local docker_port=8045
+ 94|gopeed)
- docker_rum() {
+ local app_id="94"
+ local docker_name="gopeed"
+ local docker_img="liwei2633/gopeed"
+ local docker_port=8094
- docker run -d \
- -p ${docker_port}:5000 \
- --name registry \
- -v /home/docker/registry:/var/lib/registry \
- -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
- --restart always \
- registry:2
+ docker_rum() {
- }
+ read -e -p "로그인 사용자 이름 설정:" app_use
+ read -e -p "로그인 비밀번호 설정:" app_passwd
- local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
- local docker_url="官网介绍: https://hub.docker.com/_/registry"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}/downloads:/app/Downloads \
+ -v /home/docker/${docker_name}/storage:/app/storage \
+ -p ${docker_port}:9999 \
+ ${docker_img} -u ${app_use} -p ${app_passwd}
- 46)
- local docker_name="ghproxy"
- local docker_img="wjqserver/ghproxy:latest"
- local docker_port=8046
+ }
- docker_rum() {
+ local docker_describe="여러 프로토콜을 지원하는 분산 고속 다운로드 도구"
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/GopeedLab/gopeed"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d --name ghproxy --restart always -p ${docker_port}:8080 wjqserver/ghproxy:latest
+ ;;
- }
- local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
- local docker_url="官网介绍: https://github.com/WJQSERVER-STUDIO/ghproxy"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 47)
+ 95|paperless)
+ local app_id="95"
+ local app_name="종이 없는 문서 관리 플랫폼"
+ local app_text="오픈 소스 전자 문서 관리 시스템으로, 주요 목적은 종이 문서를 디지털화하고 관리하는 것입니다."
+ local app_url="영상 소개: https://docs.paperless-ngx.com/"
+ local docker_name="paperless-webserver-1"
+ local docker_port="8095"
+ local app_size="2"
- local app_name="普罗米修斯监控"
- local app_text="Prometheus+Grafana企业级监控系统"
- local app_url="官网介绍: https://prometheus.io"
- local docker_name="grafana"
- local docker_port="8047"
- local app_size="2"
+ docker_app_install() {
- docker_app_install() {
- prometheus_install
- clear
- ip_address
- echo "설치"
- check_docker_app_ip
- echo "초기 사용자 이름과 비밀번호는 다음과 같습니다"
- }
+ mkdir -p /home/docker/paperless
+ mkdir -p /home/docker/paperless/export
+ mkdir -p /home/docker/paperless/consume
+ cd /home/docker/paperless
- docker_app_update() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
- docker_app_install
- }
+ curl -o /home/docker/paperless/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/docker-compose.postgres-tika.yml
+ curl -o /home/docker/paperless/docker-compose.env ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/.env
- docker_app_uninstall() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/paperless/docker-compose.yml
+ cd /home/docker/paperless
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
- rm -rf /home/docker/monitoring
- echo "앱이 제거되었습니다"
- }
- docker_app_plus
- ;;
+ docker_app_update() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ docker_app_install
+ }
- 48)
- local docker_name="node-exporter"
- local docker_img="prom/node-exporter"
- local docker_port=8048
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ rm -rf /home/docker/paperless
+ echo "앱이 제거되었습니다."
+ }
- docker run -d \
- --name=node-exporter \
- -p ${docker_port}:9100 \
- --restart unless-stopped \
- prom/node-exporter
+ docker_app_plus
+ ;;
- }
- local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/prometheus/node_exporter"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
-
- 49)
- local docker_name="cadvisor"
- local docker_img="gcr.io/cadvisor/cadvisor:latest"
- local docker_port=8049
-
- docker_rum() {
-
- docker run -d \
- --name=cadvisor \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- --volume=/:/rootfs:ro \
- --volume=/var/run:/var/run:rw \
- --volume=/sys:/sys:ro \
- --volume=/var/lib/docker/:/var/lib/docker:ro \
- gcr.io/cadvisor/cadvisor:latest \
- -housekeeping_interval=10s \
- -docker_only=true
- }
+ 96|2fauth)
- local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/google/cadvisor"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="96"
+ local app_name="2FAuth 자체 호스팅 2단계 인증자"
+ local app_text="자체 호스팅 2FA(2단계 인증) 계정 관리 및 확인 코드 생성 도구입니다."
+ local app_url="공식 웹사이트:${gh_https_url}github.com/Bubka/2FAuth"
+ local docker_name="2fauth"
+ local docker_port="8096"
+ local app_size="1"
- 50)
- local docker_name="changedetection"
- local docker_img="dgtlmoon/changedetection.io:latest"
- local docker_port=8050
+ docker_app_install() {
- docker_rum() {
+ add_yuming
- docker run -d --restart always -p ${docker_port}:5000 \
- -v /home/docker/datastore:/datastore \
- --name changedetection dgtlmoon/changedetection.io:latest
+ mkdir -p /home/docker/2fauth
+ mkdir -p /home/docker/2fauth/data
+ chmod -R 777 /home/docker/2fauth/
+ cd /home/docker/2fauth
- }
+ curl -o /home/docker/2fauth/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/2fauth-docker-compose.yml
- local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
- local docker_url="官网介绍: https://github.com/dgtlmoon/changedetection.io"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/2fauth/docker-compose.yml
+ sed -i "s/yuming.com/${yuming}/g" /home/docker/2fauth/docker-compose.yml
+ cd /home/docker/2fauth
+ docker compose up -d
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 51)
clear
- send_stats "PVE 치킨"
- check_disk_space 1
- curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
- ;;
-
+ echo "설치 완료"
+ check_docker_app_ip
+ }
- 52)
- local docker_name="dpanel"
- local docker_img="dpanel/dpanel:lite"
- local docker_port=8052
- docker_rum() {
+ docker_app_update() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ docker_app_install
+ }
- docker run -it -d --name dpanel --restart=always \
- -p ${docker_port}:8080 -e APP_NAME=dpanel \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/dpanel:/dpanel \
- dpanel/dpanel:lite
- }
+ docker_app_uninstall() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ rm -rf /home/docker/2fauth
+ echo "앱이 제거되었습니다."
+ }
- local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
- local docker_url="官网介绍: https://github.com/donknap/dpanel"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_plus
- 53)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ ;;
- docker_rum() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
- }
+ 97|wgs)
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run llama3.2:1b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ local app_id="97"
+ local docker_name="wireguard"
+ local docker_img="lscr.io/linuxserver/wireguard:latest"
+ local docker_port=8097
- 54)
+ docker_rum() {
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="AMH面板"
- local panelurl="官方地址: https://amh.sh/index.htm?amh"
+ read -e -p "네트워크의 클라이언트 수를 입력하십시오(기본값 5):" COUNT
+ COUNT=${COUNT:-5}
+ read -e -p "WireGuard 네트워크 세그먼트(기본값 10.13.13.0)를 입력하세요." NETWORK
+ NETWORK=${NETWORK:-10.13.13.0}
- panel_app_install() {
- cd ~
- wget https://dl.amh.sh/amh.sh && bash amh.sh
- }
+ PEERS=$(seq -f "wg%02g" 1 "$COUNT" | paste -sd,)
- panel_app_manage() {
- panel_app_install
- }
+ ip link delete wg0 &>/dev/null
- panel_app_uninstall() {
- panel_app_install
- }
+ ip_address
+ docker run -d \
+ --name=wireguard \
+ --network host \
+ --cap-add=NET_ADMIN \
+ --cap-add=SYS_MODULE \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SERVERURL=${ipv4_address} \
+ -e SERVERPORT=51820 \
+ -e PEERS=${PEERS} \
+ -e INTERNAL_SUBNET=${NETWORK} \
+ -e ALLOWEDIPS=${NETWORK}/24 \
+ -e PERSISTENTKEEPALIVE_PEERS=all \
+ -e LOG_CONFS=true \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules \
+ --restart=always \
+ lscr.io/linuxserver/wireguard:latest
- install_panel
- ;;
+ sleep 3
- 55)
- frps_panel
- ;;
+ docker exec wireguard sh -c "
+ f='/config/wg_confs/wg0.conf'
+ sed -i 's/51820/${docker_port}/g' \$f
+ "
- 56)
- frpc_panel
- ;;
+ docker exec wireguard sh -c "
+ for d in /config/peer_*; do
+ sed -i 's/51820/${docker_port}/g' \$d/*.conf
+ done
+ "
- 57)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ sed -i "/^DNS/d" "$d"/*.conf
+ done
+ '
- docker_rum() {
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ for f in "$d"/*.conf; do
+ grep -q "^PersistentKeepalive" "$f" || \
+ sed -i "/^AllowedIPs/ a PersistentKeepalive = 25" "$f"
+ done
+ done
+ '
+
+ docker exec wireguard bash -c '
+ for d in /config/peer_*; do
+ cd "$d" || continue
+ conf_file=$(ls *.conf)
+ base_name="${conf_file%.conf}"
+ qrencode -o "$base_name.png" < "$conf_file"
+ done
+ '
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ docker restart wireguard
- }
+ sleep 2
+ echo
+ echo -e "${gl_huang}모든 클라이언트 QR 코드 구성:${gl_bai}"
+ docker exec wireguard bash -c 'for i in $(ls /config | grep peer_ | sed "s/peer_//"); do echo "--- $i ---"; /app/show-peer $i; done'
+ sleep 2
+ echo
+ echo -e "${gl_huang}모든 클라이언트 구성 코드:${gl_bai}"
+ docker exec wireguard sh -c 'for d in /config/peer_*; do echo "# $(basename $d) "; cat $d/*.conf; echo; done'
+ sleep 2
+ echo -e "${gl_lv}${COUNT}각 클라이언트에 대한 모든 출력을 구성합니다. 사용방법은 다음과 같습니다.${gl_bai}"
+ echo -e "${gl_lv}1. 휴대폰에 wg APP을 다운로드하고 위의 QR 코드를 스캔하면 빠르게 인터넷에 연결됩니다.${gl_bai}"
+ echo -e "${gl_lv}2. Windows용 클라이언트를 다운로드하고 구성 코드를 복사하여 네트워크에 연결합니다.${gl_bai}"
+ echo -e "${gl_lv}3. 스크립트를 사용하여 Linux에 WG 클라이언트를 배포하고 구성 코드를 복사하여 네트워크에 연결합니다.${gl_bai}"
+ echo -e "${gl_lv}공식 클라이언트 다운로드 방법: https://www.wireguard.com/install/${gl_bai}"
+ break_end
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ }
+ local docker_describe="최신 고성능 가상 사설망 도구"
+ local docker_url="공식 홈페이지 소개 : https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 58)
- local app_name="Dify知识库"
- local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
- local app_url="官方网站: https://docs.dify.ai/zh-hans"
- local docker_name="docker-nginx-1"
- local docker_port="8058"
- local app_size="3"
+ ;;
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
- # sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
- docker compose up -d
- clear
- echo "설치"
- check_docker_app_ip
- }
+ 98|wgc)
- docker_app_update() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- cd /home/docker/dify/
- git pull origin main
- sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- cd /home/docker/dify/docker/ && docker compose up -d
- }
+ local app_id="98"
+ local docker_name="wireguardc"
+ local docker_img="kjlion/wireguard:alpine"
+ local docker_port=51820
- docker_app_uninstall() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- rm -rf /home/docker/dify
- echo "앱이 제거되었습니다"
- }
+ docker_rum() {
- docker_app_plus
+ mkdir -p /home/docker/wireguard/config/
- ;;
+ local CONFIG_FILE="/home/docker/wireguard/config/wg0.conf"
- 59)
- local app_name="New API"
- local app_text="新一代大模型网关与AI资产管理系统"
- local app_url="官方网站: https://github.com/Calcium-Ion/new-api"
- local docker_name="new-api"
- local docker_port="8059"
- local app_size="3"
+ # 디렉터리가 없으면 생성
+ mkdir -p "$(dirname "$CONFIG_FILE")"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+ echo "클라이언트 구성을 붙여넣고 Enter를 두 번 눌러 저장하세요."
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ # 변수 초기화
+ input=""
+ empty_line_count=0
+ # 사용자 입력을 한 줄씩 읽습니다.
+ while IFS= read -r line; do
+ if [[ -z "$line" ]]; then
+ ((empty_line_count++))
+ if [[ $empty_line_count -ge 2 ]]; then
+ break
+ fi
+ else
+ empty_line_count=0
+ input+="$line"$'\n'
+ fi
+ done
- docker compose up -d
- clear
- echo "설치"
- check_docker_app_ip
- }
+ # 구성 파일 쓰기
+ echo "$input" > "$CONFIG_FILE"
- docker_app_update() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- cd /home/docker/new-api/
- git pull origin main
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ echo "클라이언트 구성이 다음에 저장되었습니다.$CONFIG_FILE"
- docker compose up -d
- clear
- echo "설치"
- check_docker_app_ip
+ ip link delete wg0 &>/dev/null
- }
+ docker run -d \
+ --name wireguardc \
+ --network host \
+ --cap-add NET_ADMIN \
+ --cap-add SYS_MODULE \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules:ro \
+ --restart=always \
+ kjlion/wireguard:alpine
- docker_app_uninstall() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- rm -rf /home/docker/new-api
- echo "앱이 제거되었습니다"
- }
+ sleep 3
- docker_app_plus
+ docker logs wireguardc
- ;;
+ break_end
+ }
- 60)
+ local docker_describe="최신 고성능 가상 사설망 도구"
+ local docker_url="공식 홈페이지 소개 : https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local app_name="JumpServer开源堡垒机"
- local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
- local app_url="官方介绍: https://github.com/jumpserver/jumpserver"
- local docker_name="jms_web"
- local docker_port="80"
- local app_size="2"
+ ;;
- docker_app_install() {
- curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
- clear
- echo "설치"
- check_docker_app_ip
- echo "초기 사용자 이름 : 관리자"
- echo "초기 비밀번호 : changeme"
- }
+ 99|dsm)
- docker_app_update() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh upgrade
- echo "앱이 업데이트되었습니다"
- }
+ local app_id="99"
+ local app_name="dsm 시놀로지 가상 머신"
+ local app_text="Docker 컨테이너의 가상 DSM"
+ local app_url="공식 웹사이트:${gh_https_url}github.com/vdsm/virtual-dsm"
+ local docker_name="dsm"
+ local docker_port="8099"
+ local app_size="16"
- docker_app_uninstall() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh uninstall
- cd /opt
- rm -rf jumpserver-installer*/
- rm -rf jumpserver
- echo "앱이 제거되었습니다"
- }
+ docker_app_install() {
- docker_app_plus
- ;;
+ read -e -p "CPU 코어 수를 설정합니다(기본값 2):" CPU_CORES
+ local CPU_CORES=${CPU_CORES:-2}
- 61)
- local docker_name="libretranslate"
- local docker_img="libretranslate/libretranslate:latest"
- local docker_port=8061
+ read -e -p "메모리 크기 설정(기본 4G):" RAM_SIZE
+ local RAM_SIZE=${RAM_SIZE:-4}
- docker_rum() {
+ mkdir -p /home/docker/dsm
+ mkdir -p /home/docker/dsm/dev
+ chmod -R 777 /home/docker/dsm/
+ cd /home/docker/dsm
- docker run -d \
- -p ${docker_port}:5000 \
- --name libretranslate \
- libretranslate/libretranslate \
- --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+ curl -o /home/docker/dsm/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/dsm-docker-compose.yml
- }
+ sed -i "s/5000:5000/${docker_port}:5000/g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|CPU_CORES: "2"|CPU_CORES: "${CPU_CORES}"|g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|RAM_SIZE: "2G"|RAM_SIZE: "${RAM_SIZE}G"|g" /home/docker/dsm/docker-compose.yml
+ cd /home/docker/dsm
+ docker compose up -d
- local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
- local docker_url="官网介绍: https://github.com/LibreTranslate/LibreTranslate"
- local docker_use=""
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+ docker_app_update() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ docker_app_install
+ }
- 62)
- local app_name="RAGFlow知识库"
- local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
- local app_url="官方网站: https://github.com/infiniflow/ragflow"
- local docker_name="ragflow-server"
- local docker_port="8062"
- local app_size="8"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- clear
- echo "설치"
- check_docker_app_ip
- }
+ docker_app_uninstall() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ rm -rf /home/docker/dsm
+ echo "앱이 제거되었습니다."
+ }
- docker_app_update() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- cd /home/docker/ragflow/
- git pull origin main
- cd /home/docker/ragflow/docker/
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- }
+ docker_app_plus
- docker_app_uninstall() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- rm -rf /home/docker/ragflow
- echo "앱이 제거되었습니다"
- }
+ ;;
- docker_app_plus
- ;;
+ 100|syncthing)
- 63)
- local docker_name="open-webui"
- local docker_img="ghcr.io/open-webui/open-webui:main"
- local docker_port=8063
+ local app_id="100"
+ local docker_name="syncthing"
+ local docker_img="syncthing/syncthing:latest"
+ local docker_port=8100
- docker_rum() {
+ docker_rum() {
+ docker run -d \
+ --name=syncthing \
+ --hostname=my-syncthing \
+ --restart=always \
+ -p ${docker_port}:8384 \
+ -p 22000:22000/tcp \
+ -p 22000:22000/udp \
+ -p 21027:21027/udp \
+ -v /home/docker/syncthing:/var/syncthing \
+ syncthing/syncthing:latest
+ }
- docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+ local docker_describe="Dropbox 및 Resilio Sync와 유사하지만 완전히 분산된 오픈 소스 P2P 파일 동기화 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/syncthing/syncthing"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use=""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
- 64)
- local docker_name="it-tools"
- local docker_img="corentinth/it-tools:latest"
- local docker_port=8064
+ 101|moneyprinterturbo)
+ local app_id="101"
+ local app_name="AI 영상 생성 도구"
+ local app_text="MoneyPrinterTurbo는 AI 대형 모델을 사용하여 고화질 단편 동영상을 합성하는 도구입니다."
+ local app_url="공식 웹사이트:${gh_https_url}github.com/harry0703/MoneyPrinterTurbo"
+ local docker_name="moneyprinterturbo"
+ local docker_port="8101"
+ local app_size="3"
- docker_rum() {
- docker run -d --name it-tools --restart unless-stopped -p ${docker_port}:80 corentinth/it-tools:latest
- }
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git && cd MoneyPrinterTurbo/
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
- local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
- local docker_url="官网介绍: https://github.com/CorentinTh/it-tools"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+ docker_app_update() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ cd /home/docker/MoneyPrinterTurbo/
- 65)
- local docker_name="n8n"
- local docker_img="docker.n8n.io/n8nio/n8n"
- local docker_port=8065
+ git pull ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose up -d
+ }
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ rm -rf /home/docker/MoneyPrinterTurbo
+ echo "앱이 제거되었습니다."
+ }
- add_yuming
- mkdir -p /home/docker/n8n
- chmod -R 777 /home/docker/n8n
-
- docker run -d --name n8n \
- --restart always \
- -p ${docker_port}:5678 \
- -v /home/docker/n8n:/home/node/.n8n \
- -e N8N_HOST=${yuming} \
- -e N8N_PORT=5678 \
- -e N8N_PROTOCOL=https \
- -e N8N_WEBHOOK_URL=https://${yuming}/ \
- docker.n8n.io/n8nio/n8n
+ docker_app_plus
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ ;;
- }
- local docker_describe="是一款功能强大的自动化工作流平台"
- local docker_url="官网介绍: https://github.com/n8n-io/n8n"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 66)
- yt_menu_pro
- ;;
+ 102|vocechat)
+ local app_id="102"
+ local docker_name="vocechat-server"
+ local docker_img="privoce/vocechat-server:latest"
+ local docker_port=8102
- 67)
- local docker_name="ddns-go"
- local docker_img="jeessy/ddns-go"
- local docker_port=8067
+ docker_rum() {
- docker_rum() {
- docker run -d \
- --name ddns-go \
- --restart=always \
- -p ${docker_port}:9876 \
- -v /home/docker/ddns-go:/root \
- jeessy/ddns-go
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ --name vocechat-server \
+ -v /home/docker/vocechat/data:/home/vocechat-server/data \
+ privoce/vocechat-server:latest
- }
+ }
- local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
- local docker_url="官网介绍: https://github.com/jeessy2/ddns-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="독립적인 배포를 지원하는 개인용 클라우드 소셜 미디어 채팅 서비스입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/Privoce/vocechat-web"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 68)
- local docker_name="allinssl"
- local docker_img="allinssl/allinssl:latest"
- local docker_port=8068
+ ;;
- docker_rum() {
- docker run -itd --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
- }
- local docker_describe="开源免费的 SSL 证书自动化管理平台"
- local docker_url="官网介绍: https://allinssl.com"
- local docker_use="echo \"安全入口: /allinssl\""
- local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
- local app_size="1"
- docker_app
- ;;
+ 103|umami)
+ local app_id="103"
+ local app_name="Umami 웹사이트 통계 도구"
+ local app_text="Google Analytics와 유사한 오픈 소스, 가볍고 개인정보 보호 친화적인 웹사이트 분석 도구입니다."
+ local app_url="공식 웹사이트:${gh_https_url}github.com/umami-software/umami"
+ local docker_name="umami-umami-1"
+ local docker_port="8103"
+ local app_size="1"
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/umami-software/umami.git && cd umami
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/umami/docker-compose.yml
- 69)
- local docker_name="sftpgo"
- local docker_img="drakkan/sftpgo:latest"
- local docker_port=8069
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ echo "초기 사용자 이름: admin"
+ echo "초기 비밀번호 : umami"
+ }
- docker_rum() {
+ docker_app_update() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ cd /home/docker/umami/
+ git pull ${gh_proxy}github.com/umami-software/umami.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/umami/docker-compose.yml
+ cd /home/docker/umami/ && docker compose up -d
+ }
- mkdir -p /home/docker/sftpgo/data
- mkdir -p /home/docker/sftpgo/config
- chown -R 1000:1000 /home/docker/sftpgo
+ docker_app_uninstall() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ rm -rf /home/docker/umami
+ echo "앱이 제거되었습니다."
+ }
- docker run -d \
- --name sftpgo \
- --restart=always \
- -p ${docker_port}:8080 \
- -p 22022:2022 \
- --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
- --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
- drakkan/sftpgo:latest
+ docker_app_plus
- }
+ ;;
- local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
- local docker_url="官网介绍: https://sftpgo.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 104|nginx-stream)
+ stream_panel
+ ;;
- 70)
- local docker_name="astrbot"
- local docker_img="soulter/astrbot:latest"
- local docker_port=8070
+ 105|siyuan)
- docker_rum() {
+ local app_id="105"
+ local docker_name="siyuan"
+ local docker_img="b3log/siyuan"
+ local docker_port=8105
- mkdir -p /home/docker/astrbot/data
+ docker_rum() {
- sudo docker run -d \
- -p ${docker_port}:6185 \
- -p 6195:6195 \
- -p 6196:6196 \
- -p 6199:6199 \
- -p 11451:11451 \
- -v /home/docker/astrbot/data:/AstrBot/data \
- --restart unless-stopped \
- --name astrbot \
- soulter/astrbot:latest
+ read -e -p "로그인 비밀번호 설정:" app_passwd
- }
+ docker run -d \
+ --name siyuan \
+ --restart=always \
+ -v /home/docker/siyuan/workspace:/siyuan/workspace \
+ -p ${docker_port}:6806 \
+ -e PUID=1001 \
+ -e PGID=1002 \
+ b3log/siyuan \
+ --workspace=/siyuan/workspace/ \
+ --accessAuthCode="${app_passwd}"
- local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
- local docker_url="官网介绍: https://astrbot.app/"
- local docker_use="echo \"用户名: astrbot 密码: astrbot\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ }
+ local docker_describe="Siyuan Notes는 개인 정보 보호를 최우선으로 하는 지식 관리 시스템입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/siyuan-note/siyuan"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 71)
- local docker_name="navidrome"
- local docker_img="deluan/navidrome:latest"
- local docker_port=8071
+ ;;
- docker_rum() {
- docker run -d \
- --name navidrome \
- --restart=unless-stopped \
- --user $(id -u):$(id -g) \
- -v /home/docker/navidrome/music:/music \
- -v /home/docker/navidrome/data:/data \
- -p ${docker_port}:4533 \
- -e ND_LOGLEVEL=info \
- deluan/navidrome:latest
+ 106|drawnix)
- }
+ local app_id="106"
+ local docker_name="drawnix"
+ local docker_img="pubuzhixing/drawnix"
+ local docker_port=8106
- local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
- local docker_url="官网介绍: https://www.navidrome.org/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
+ docker run -d \
+ --restart=always \
+ --name drawnix \
+ -p ${docker_port}:80 \
+ pubuzhixing/drawnix
- 72)
+ }
- local docker_name="bitwarden"
- local docker_img="vaultwarden/server"
- local docker_port=8072
+ local docker_describe="마인드맵, 플로우차트 등을 통합한 강력한 오픈소스 화이트보드 도구입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/plait-board/drawnix"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run -d \
- --name bitwarden \
- --restart always \
- -p ${docker_port}:80 \
- -v /home/docker/bitwarden/data:/data \
- vaultwarden/server
- }
+ 107|pansou)
- local docker_describe="一个你可以控制数据的密码管理器"
- local docker_url="官网介绍: https://bitwarden.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="107"
+ local docker_name="pansou"
+ local docker_img="ghcr.io/fish2018/pansou-web"
+ local docker_port=8107
+ docker_rum() {
- ;;
+ docker run -d \
+ --name pansou \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/pansou/data:/app/data \
+ -v /home/docker/pansou/logs:/app/logs \
+ -e ENABLED_PLUGINS="hunhepan,jikepan,panwiki,pansearch,panta,qupansou,
+susu,thepiratebay,wanou,xuexizhinan,panyq,zhizhen,labi,muou,ouge,shandian,
+duoduo,huban,cyg,erxiao,miaoso,fox4k,pianku,clmao,wuji,cldi,xiaozhang,
+libvio,leijing,xb6v,xys,ddys,hdmoli,yuhuage,u3c3,javdb,clxiong,jutoushe,
+sdso,xiaoji,xdyh,haisou,bixin,djgou,nyaa,xinjuc,aikanzy,qupanshe,xdpan,
+discourse,yunsou,ahhhhfs,nsgame,gying" \
+ ghcr.io/fish2018/pansou-web
+ }
+ local docker_describe="PanSou는 고성능 네트워크 디스크 리소스 검색 API 서비스입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/fish2018/pansou"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 73)
+ ;;
- local docker_name="libretv"
- local docker_img="bestzwei/libretv:latest"
- local docker_port=8073
- docker_rum() {
- read -e -p "libretv 로그인 비밀번호 설정 :" app_passwd
- docker run -d \
- --name libretv \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -e PASSWORD=${app_passwd} \
- bestzwei/libretv:latest
+ 108|langbot)
+ local app_id="108"
+ local app_name="LangBot 챗봇"
+ local app_text="오픈 소스 대형 언어 모델 기반 인스턴트 메시징 로봇 개발 플랫폼입니다."
+ local app_url="공식 웹사이트:${gh_https_url}github.com/langbot-app/LangBot"
+ local docker_name="langbot_plugin_runtime"
+ local docker_port="8108"
+ local app_size="1"
- }
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langbot-app/LangBot && cd LangBot/docker
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/LibreSpark/LibreTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
- ;;
+ docker_app_update() {
+ cd /home/docker/LangBot/docker && docker compose down --rmi all
+ cd /home/docker/LangBot/
+ git pull ${gh_proxy}github.com/langbot-app/LangBot main > /dev/null 2>&1
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ cd /home/docker/LangBot/docker/ && docker compose up -d
+ }
+ docker_app_uninstall() {
+ cd /home/docker/LangBot/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/LangBot
+ echo "앱이 제거되었습니다."
+ }
- 74)
+ docker_app_plus
- local docker_name="moontv"
- local docker_img="ghcr.io/senshinya/moontv:latest"
- local docker_port=8074
+ ;;
- docker_rum() {
- read -e -p "MOONTV 로그인 비밀번호 설정 :" app_passwd
+ 109|zfile)
- docker run -d \
- --name moontv \
- --restart unless-stopped \
- -p ${docker_port}:3000 \
- -e PASSWORD=${app_passwd} \
- ghcr.io/senshinya/moontv:latest
+ local app_id="109"
+ local docker_name="zfile"
+ local docker_img="zhaojun1998/zfile:latest"
+ local docker_port=8109
- }
+ docker_rum() {
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/senshinya/MoonTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d --name=zfile --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/zfile/db:/root/.zfile-v4/db \
+ -v /home/docker/zfile/logs:/root/.zfile-v4/logs \
+ -v /home/docker/zfile/file:/data/file \
+ -v /home/docker/zfile/application.properties:/root/.zfile-v4/application.properties \
+ zhaojun1998/zfile:latest
- 75)
+ }
- local docker_name="melody"
- local docker_img="foamzou/melody:latest"
- local docker_port=8075
+ local docker_describe="개인이나 소규모 팀에 적합한 온라인 네트워크 디스크 프로그램입니다."
+ local docker_url="공식 웹사이트 소개:${gh_https_url}github.com/zfile-dev/zfile"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run -d \
- --name melody \
- --restart unless-stopped \
- -p ${docker_port}:5566 \
- -v /home/docker/melody/.profile:/app/backend/.profile \
- foamzou/melody:latest
+ 110|karakeep)
+ local app_id="110"
+ local app_name="karakeep 북마크 관리"
+ local app_text="데이터 수집가를 위해 설계된 인공 지능 기능을 갖춘 자체 호스팅 북마크 앱입니다."
+ local app_url="공식 웹사이트:${gh_https_url}github.com/karakeep-app/karakeep"
+ local docker_name="docker-web-1"
+ local docker_port="8110"
+ local app_size="1"
- }
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/karakeep-app/karakeep.git && cd karakeep/docker && cp .env.sample .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
- local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
- local docker_url="官网介绍: https://github.com/foamzou/melody"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker compose up -d
+ clear
+ echo "설치 완료"
+ check_docker_app_ip
+ }
+ docker_app_update() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ cd /home/docker/karakeep/
+ git pull ${gh_proxy}github.com/karakeep-app/karakeep.git main > /dev/null 2>&1
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ cd /home/docker/karakeep/docker/ && docker compose up -d
+ }
- ;;
+ docker_app_uninstall() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/karakeep
+ echo "앱이 제거되었습니다."
+ }
+ docker_app_plus
- 76)
+ ;;
- local docker_name="dosgame"
- local docker_img="oldiy/dosgame-web-docker:latest"
- local docker_port=8076
- docker_rum() {
- docker run -d \
- --name dosgame \
- --restart unless-stopped \
- -p ${docker_port}:262 \
- oldiy/dosgame-web-docker:latest
- }
+ 111|convertx)
- local docker_describe="是一个中文DOS游戏合集网站"
- local docker_url="官网介绍: https://github.com/rwv/chinese-dos-games"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ local app_id="111"
+ local docker_name="convertx"
+ local docker_img="ghcr.io/c4illin/convertx:latest"
+ local docker_port=8111
+ docker_rum() {
- ;;
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/convertx:/app/data \
+ ${docker_img}
- 77)
+ }
- local docker_name="xunlei"
- local docker_img="cnk3x/xunlei"
- local docker_port=8077
+ local docker_describe="강력한 다중 형식 파일 변환 도구입니다(문서, 이미지, 오디오 및 비디오 등 지원). 도메인 이름 액세스를 추가하는 것이 좋습니다."
+ local docker_url="프로젝트 주소:${gh_https_url}github.com/c4illin/ConvertX"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- docker_rum() {
+ ;;
- read -e -p "설정${docker_name}로그인 사용자 이름 :" app_use
- read -e -p "설정${docker_name}로그인 비밀번호 :" app_passwd
- docker run -d \
- --name xunlei \
- --restart unless-stopped \
- --privileged \
- -e XL_DASHBOARD_USERNAME=${app_use} \
- -e XL_DASHBOARD_PASSWORD=${app_passwd} \
- -v /home/docker/xunlei/data:/xunlei/data \
- -v /home/docker/xunlei/downloads:/xunlei/downloads \
- -p ${docker_port}:2345 \
- cnk3x/xunlei
+ 112|lucky)
- }
+ local app_id="112"
+ local docker_name="lucky"
+ local docker_img="gdy666/lucky:v2"
+ # Lucky는 호스트 네트워크 모드를 사용하므로 여기서 포트는 기록/설명 참조용으로만 사용되며 실제로는 애플리케이션 자체에 의해 제어됩니다(기본값 16601).
+ local docker_port=8112
- local docker_describe="迅雷你的离线高速BT磁力下载工具"
- local docker_url="官网介绍: https://github.com/cnk3x/xunlei"
- local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
- ;;
+ docker run -d --name=${docker_name} --restart=always \
+ --network host \
+ -v /home/docker/lucky/conf:/app/conf \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ ${docker_img}
+ echo "Lucky가 초기화되기를 기다리는 중..."
+ sleep 10
+ docker exec lucky /app/lucky -rSetHttpAdminPort ${docker_port}
+ }
- 78)
+ local docker_describe="Lucky는 DDNS, 역방향 프록시, WOL 및 기타 기능을 지원하는 대규모 인트라넷 침투 및 포트 전달 관리 도구입니다."
+ local docker_url="프로젝트 주소:${gh_https_url}github.com/gdy666/lucky"
+ local docker_use="echo \"기본 계정 비밀번호: 666\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local app_name="PandaWiki"
- local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
- local app_url="官方介绍: https://github.com/chaitin/PandaWiki"
- local docker_name="panda-wiki-nginx"
- local docker_port="2443"
- local app_size="2"
+ ;;
- docker_app_install() {
- bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
- }
- docker_app_update() {
- docker_app_install
- }
+ 113|firefox)
+ local app_id="113"
+ local docker_name="firefox"
+ local docker_img="jlesage/firefox:latest"
+ local docker_port=8113
- docker_app_uninstall() {
- docker_app_install
- }
+ docker_rum() {
- docker_app_plus
- ;;
+ read -e -p "로그인 비밀번호 설정:" admin_password
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:5800 \
+ -v /home/docker/firefox:/config:rw \
+ -e ENABLE_CJK_FONT=1 \
+ -e WEB_AUDIO=1 \
+ -e VNC_PASSWORD="${admin_password}" \
+ ${docker_img}
+ }
+ local docker_describe="웹 페이지를 통해 데스크톱 브라우저 인터페이스에 대한 직접 액세스를 지원하는 Docker에서 실행되는 Firefox 브라우저입니다."
+ local docker_url="프로젝트 주소:${gh_https_url}github.com/jlesage/docker-firefox"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 79)
+ ;;
- local docker_name="beszel"
- local docker_img="henrygd/beszel"
- local docker_port=8079
+ 114|Moltbot|ClawdBot|moltbot|clawdbot|openclaw|OpenClaw)
+ moltbot_menu
+ ;;
- docker_rum() {
+ 115|hermes)
+ bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/hermes_manager.sh)
+ ;;
- mkdir -p /home/docker/beszel && \
- docker run -d \
- --name beszel \
- --restart=unless-stopped \
- -v /home/docker/beszel:/beszel_data \
- -p ${docker_port}:8090 \
- henrygd/beszel
+ b)
+ clear
+ send_stats "모든 애플리케이션 백업"
- }
+ local backup_filename="app_$(date +"%Y%m%d%H%M%S").tar.gz"
+ echo -e "${gl_kjlan}백업 중$backup_filename ...${gl_bai}"
+ cd / && tar czvf "$backup_filename" home
- local docker_describe="Beszel轻量易用的服务器监控"
- local docker_url="官网介绍: https://beszel.dev/zh/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ while true; do
+ clear
+ echo "생성된 백업 파일: /$backup_filename"
+ read -e -p "백업 데이터를 원격 서버로 전송하시겠습니까? (예/아니요):" choice
+ case "$choice" in
+ [Yy])
+ kj_ssh_read_host_port "원격 서버 IP를 입력하세요:" "대상 서버 SSH 포트 [기본값 22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
+ local latest_tar=$(ls -t /app*.tar.gz | head -1)
+ if [ -n "$latest_tar" ]; then
+ ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
+ sleep 2 # 添加等待时间
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/"
+ echo "원격 서버/루트 디렉터리로 파일이 전송되었습니다."
+ else
+ echo "전송할 파일을 찾을 수 없습니다."
+ fi
+ break
+ ;;
+ *)
+ echo "참고: 현재 백업에는 docker 프로젝트만 포함되며, Pagoda, 1panel 등 웹사이트 구축 패널의 데이터 백업은 포함되지 않습니다."
+ break
+ ;;
+ esac
+ done
- ;;
+ ;;
+ r)
+ root_use
+ send_stats "모든 앱 복원"
+ echo "사용 가능한 애플리케이션 백업"
+ echo "-------------------------"
+ ls -lt /app*.gz | awk '{print $NF}'
+ echo ""
+ read -e -p "최신 백업을 복원하려면 Enter 키를 누르고, 지정된 백업을 복원하려면 백업 파일 이름을 입력하고, 종료하려면 0을 입력하세요." filename
- 0)
- kejilion
- ;;
- *)
- echo "잘못된 입력!"
- ;;
- esac
- break_end
+ if [ "$filename" == "0" ]; then
+ break_end
+ linux_panel
+ fi
+
+ # 사용자가 파일명을 입력하지 않으면 최신 압축 패키지가 사용됩니다.
+ if [ -z "$filename" ]; then
+ local filename=$(ls -t /app*.tar.gz | head -1)
+ fi
+
+ if [ -n "$filename" ]; then
+ echo -e "${gl_kjlan}압축 해제 중$filename ...${gl_bai}"
+ cd / && tar -xzf "$filename"
+ echo "애플리케이션 데이터가 복원되었습니다. 현재 해당 애플리케이션을 복원하려면 지정된 애플리케이션 메뉴에 수동으로 진입한 후 애플리케이션을 업데이트하시기 바랍니다."
+ else
+ echo "압축된 패키지를 찾을 수 없습니다."
+ fi
- done
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ cd ~
+ install git
+ if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+ else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+ fi
+ local custom_app="$HOME/apps/${sub_choice}.conf"
+ if [ -f "$custom_app" ]; then
+ . "$custom_app"
+ else
+ echo -e "${gl_hong}오류: 번호가 없습니다.${sub_choice}애플리케이션 구성${gl_bai}"
+ fi
+ ;;
+ esac
+ break_end
+ sub_choice=""
+
+done
}
+
linux_work() {
while true; do
clear
- send_stats "백엔드 작업 공간"
- echo -e "백엔드 작업 공간"
- echo -e "이 시스템은 백엔드에서 실행할 수있는 작업 공간을 제공하며 장기 작업을 수행하는 데 사용할 수 있습니다."
- echo -e "SSH를 분리하더라도 작업 공간의 작업이 중단되지 않으며 백그라운드의 작업이 거주됩니다."
- echo -e "${gl_huang}힌트:${gl_bai}작업 공간에 입력 한 후 Ctrl+B를 사용하고 D 만 눌러 작업 공간을 종료하십시오!"
+ send_stats "백엔드 작업공간"
+ echo -e "백엔드 작업공간"
+ echo -e "시스템은 장기간 작업을 수행하는 데 사용할 수 있는 백그라운드에서 영구적으로 실행될 수 있는 작업 공간을 제공합니다."
+ echo -e "SSH 연결을 끊더라도 작업 공간의 작업은 중단되지 않으며 작업은 백그라운드에 유지됩니다."
+ echo -e "${gl_huang}힌트:${gl_bai}워크스페이스 진입 후 Ctrl+b를 누른 후 d만 눌러 워크스페이스를 종료하세요!"
echo -e "${gl_kjlan}------------------------"
- echo "현재 기존 작업 공간 목록"
+ echo "현재 존재하는 작업공간 목록"
echo -e "${gl_kjlan}------------------------"
tmux list-sessions
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}작업 공간 번호 1"
- echo -e "${gl_kjlan}2. ${gl_bai}작업 공간 2 번"
- echo -e "${gl_kjlan}3. ${gl_bai}작업 공간 번호 3"
- echo -e "${gl_kjlan}4. ${gl_bai}작업 공간 No. 4"
- echo -e "${gl_kjlan}5. ${gl_bai}작업 공간 번호 5"
- echo -e "${gl_kjlan}6. ${gl_bai}작업 공간 No. 6"
- echo -e "${gl_kjlan}7. ${gl_bai}작업 공간 번호 7"
- echo -e "${gl_kjlan}8. ${gl_bai}작업 공간 번호 8"
- echo -e "${gl_kjlan}9. ${gl_bai}작업 공간 No. 9"
- echo -e "${gl_kjlan}10. ${gl_bai}작업 공간 번호 10"
+ echo -e "${gl_kjlan}1. ${gl_bai}작업 영역 1"
+ echo -e "${gl_kjlan}2. ${gl_bai}작업 영역 2"
+ echo -e "${gl_kjlan}3. ${gl_bai}작업 영역 3"
+ echo -e "${gl_kjlan}4. ${gl_bai}작업 영역 4"
+ echo -e "${gl_kjlan}5. ${gl_bai}작업 공간 5번"
+ echo -e "${gl_kjlan}6. ${gl_bai}작업 영역 6"
+ echo -e "${gl_kjlan}7. ${gl_bai}작업 영역 7"
+ echo -e "${gl_kjlan}8. ${gl_bai}작업 영역 8"
+ echo -e "${gl_kjlan}9. ${gl_bai}작업 공간 9호"
+ echo -e "${gl_kjlan}10. ${gl_bai}작업공간 10"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}SSH 거주 모드${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}22. ${gl_bai}작업 공간을 작성/입력하십시오"
- echo -e "${gl_kjlan}23. ${gl_bai}배경 작업 공간에 명령을 주입합니다"
- echo -e "${gl_kjlan}24. ${gl_bai}지정된 작업 공간을 삭제하십시오"
+ echo -e "${gl_kjlan}21. ${gl_bai}SSH 상주 모드${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}22. ${gl_bai}작업공간 생성/입력"
+ echo -e "${gl_kjlan}23. ${gl_bai}백그라운드 작업 공간에 명령 삽입"
+ echo -e "${gl_kjlan}24. ${gl_bai}지정된 작업공간 삭제"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
@@ -10676,7 +19493,7 @@ linux_work() {
clear
install tmux
local SESSION_NAME="work1"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
@@ -10684,134 +19501,624 @@ linux_work() {
clear
install tmux
local SESSION_NAME="work2"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
3)
clear
install tmux
local SESSION_NAME="work3"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
4)
clear
install tmux
local SESSION_NAME="work4"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
5)
clear
install tmux
local SESSION_NAME="work5"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
6)
clear
install tmux
local SESSION_NAME="work6"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
7)
clear
install tmux
local SESSION_NAME="work7"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
8)
clear
install tmux
local SESSION_NAME="work8"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
9)
clear
install tmux
local SESSION_NAME="work9"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
tmux_run
;;
10)
clear
install tmux
local SESSION_NAME="work10"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
+ send_stats "작업공간 시작$SESSION_NAME"
+ tmux_run
+ ;;
+
+ 21)
+ while true; do
+ clear
+ if grep -q 'tmux attach-session -t sshd || tmux new-session -s sshd' ~/.bashrc; then
+ local tmux_sshd_status="${gl_lv}켜다${gl_bai}"
+ else
+ local tmux_sshd_status="${gl_hui}폐쇄${gl_bai}"
+ fi
+ send_stats "SSH 상주 모드"
+ echo -e "SSH 상주 모드${tmux_sshd_status}"
+ echo "SSH 연결을 연 후 바로 상주 모드로 들어가고 이전 작업 상태로 바로 돌아갑니다."
+ echo "------------------------"
+ echo "1. 켜기 2. 끄기"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택사항을 입력하세요:" gongzuoqu_del
+ case "$gongzuoqu_del" in
+ 1)
+ install tmux
+ local SESSION_NAME="sshd"
+ send_stats "작업공간 시작$SESSION_NAME"
+ grep -q "tmux attach-session -t sshd" ~/.bashrc || echo -e "\n# 자동으로 tmux 세션 시작\nif [[ -z \"\$TMUX\" ]]; then\n tmux attach-session -t sshd || tmux new-session -s sshd\nfi" >> ~/.bashrc
+ source ~/.bashrc
+ tmux_run
+ ;;
+ 2)
+ sed -i '/# 자동으로 tmux 세션을 입력합니다/,+4d' ~/.bashrc
+ tmux kill-window -t sshd
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+ ;;
+
+ 22)
+ read -e -p "1001 kj001 work1과 같이 생성하거나 입력한 작업공간의 이름을 입력하세요." SESSION_NAME
tmux_run
+ send_stats "맞춤형 작업공간"
+ ;;
+
+
+ 23)
+ read -e -p "다음과 같이 백그라운드에서 실행하려는 명령을 입력하십시오. 컬 -fsSL https://get.docker.com | 쉿:" tmuxd
+ tmux_run_d
+ send_stats "백그라운드 작업 공간에 명령 삽입"
+ ;;
+
+ 24)
+ read -e -p "삭제하려는 작업공간의 이름을 입력하세요:" gongzuoqu_name
+ tmux kill-window -t $gongzuoqu_name
+ send_stats "작업공간 삭제"
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ echo "입력이 잘못되었습니다!"
;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+# 지능형 스위칭 미러 소스 기능
+switch_mirror() {
+ # 선택적 매개변수, 기본값은 false입니다.
+ local upgrade_software=${1:-false}
+ local clean_cache=${2:-false}
+
+ # 사용자 국가 가져오기
+ local country
+ country=$(curl -s ipinfo.io/country)
+
+ echo "감지된 국가:$country"
+
+ if [ "$country" = "CN" ]; then
+ echo "국내 미러 소스를 사용하세요..."
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.huaweicloud.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ echo "해외 미러 소스를 활용하세요.."
+ if [ -f /etc/os-release ] && grep -qi "oracle" /etc/os-release; then
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.xtom.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --use-official-source true \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ fi
+ fi
+}
+
+
+fail2ban_panel() {
+ root_use
+ send_stats "SSH 방어"
+ while true; do
+
+ check_f2b_status
+ echo -e "SSH 방어 프로그램$check_f2b_status"
+ echo "fall2ban은 무차별 대입 크래킹을 방지하는 SSH 도구입니다."
+ echo "공식 웹사이트 소개:${gh_proxy}github.com/fail2ban/fail2ban"
+ echo "------------------------"
+ echo "1. 방어 프로그램 설치"
+ echo "------------------------"
+ echo "2. SSH 차단 기록 보기"
+ echo "3. 실시간 로그 모니터링"
+ echo "------------------------"
+ echo "4. 기본 매개변수 구성(금지 기간/기간/재시도 횟수)"
+ echo "5. 구성 파일 편집(나노)"
+ echo "------------------------"
+ echo "9. 방어 프로그램 제거"
+ echo "------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "------------------------"
+ read -e -p "선택사항을 입력하세요:" sub_choice
+ case $sub_choice in
+ 1)
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ break_end
+ ;;
+ 2)
+ echo "------------------------"
+ f2b_sshd
+ echo "------------------------"
+ break_end
+ ;;
+ 3)
+ tail -f /var/log/fail2ban.log
+ break
+ ;;
+ 4)
+ send_stats "SSH 방어 기본 매개변수 구성"
+ f2b_basic_config
+ break_end
+ ;;
+ 5)
+ send_stats "SSH 방어 편집 구성 파일"
+ f2b_edit_config
+ break_end
+ ;;
+ 9)
+ remove fail2ban
+ rm -rf /etc/fail2ban
+ echo "Fail2Ban 방어 프로그램이 제거되었습니다."
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+}
+
+
+
+
+
+net_menu() {
+
+ send_stats "네트워크 카드 관리 도구"
+ show_nics() {
+ echo "================ 현재 네트워크 카드 정보 ================="
+ printf "%-18s %-12s %-20s %-26s\n" "네트워크 카드 이름" "상태" "IP 주소" "MAC 주소"
+ echo "------------------------------------------------"
+ for nic in $(ls /sys/class/net); do
+ state=$(cat /sys/class/net/$nic/operstate 2>/dev/null)
+ ipaddr=$(ip -4 addr show $nic | awk '/inet /{print $2}' | head -n1)
+ mac=$(cat /sys/class/net/$nic/address 2>/dev/null)
+ printf "%-15s %-10s %-18s %-20s\n" "$nic" "$state" "${ipaddr:-无}" "$mac"
+ done
+ echo "================================================"
+ }
+
+ while true; do
+ clear
+ show_nics
+ echo
+ echo "=========== 네트워크 카드 관리 메뉴 ==========="
+ echo "1. 네트워크 카드 활성화"
+ echo "2. 네트워크 카드 비활성화"
+ echo "3. 네트워크 카드 세부정보 보기"
+ echo "4. 네트워크 카드 정보 새로 고침"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "===================================="
+ read -erp "작업을 선택하십시오:" choice
+
+ case $choice in
+ 1)
+ send_stats "네트워크 카드 활성화"
+ read -erp "활성화할 네트워크 카드의 이름을 입력하십시오:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" up && echo "✔ 네트워크 카드$nic활성화됨"
+ else
+ echo "✘ 네트워크 카드가 존재하지 않습니다."
+ fi
+ read -erp "계속하려면 Enter를 누르세요..."
+ ;;
+ 2)
+ send_stats "네트워크 카드 비활성화"
+ read -erp "비활성화할 네트워크 카드의 이름을 입력하십시오:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" down && echo "✔ 네트워크 카드$nic장애가 있는"
+ else
+ echo "✘ 네트워크 카드가 존재하지 않습니다."
+ fi
+ read -erp "계속하려면 Enter를 누르세요..."
+ ;;
+ 3)
+ send_stats "네트워크 카드 세부정보 보기"
+ read -erp "보려는 네트워크 카드의 이름을 입력하십시오:" nic
+ if ip link show "$nic" &>/dev/null; then
+ echo "========== $nic세부정보 =========="
+ ip addr show "$nic"
+ ethtool "$nic" 2>/dev/null | head -n 10
+ else
+ echo "✘ 네트워크 카드가 존재하지 않습니다."
+ fi
+ read -erp "계속하려면 Enter를 누르세요..."
+ ;;
+ 4)
+ send_stats "네트워크 카드 정보 새로 고침"
+ continue
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+log_menu() {
+ send_stats "시스템 로그 관리 도구"
+
+ show_log_overview() {
+ echo "============= 시스템 로그 개요 ============="
+ echo "호스트 이름: $(호스트 이름)"
+ echo "시스템 시간: $(날짜)"
+ echo
+ echo "[/var/log 디렉토리 직업]"
+ du -sh /var/log 2>/dev/null
+ echo
+ echo "[일지 로그 직업]"
+ journalctl --disk-usage 2>/dev/null
+ echo "========================================"
+ }
+
+ while true; do
+ clear
+ show_log_overview
+ echo
+ echo "=========== 시스템 로그 관리 메뉴 ==========="
+ echo "1. 최신 시스템 로그(일지) 보기"
+ echo "2. 지정된 서비스 로그 보기"
+ echo "3. 로그인/보안 로그 보기"
+ echo "4. 실시간 추적 로그"
+ echo "5. 오래된 저널 로그 정리"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "======================================="
+ read -erp "작업을 선택하십시오:" choice
+
+ case $choice in
+ 1)
+ send_stats "최근 로그 보기"
+ read -erp "가장 최근 로그 줄을 보시겠습니까? [기본값 100]:" lines
+ lines=${lines:-100}
+ journalctl -n "$lines" --no-pager
+ read -erp "계속하려면 Enter를 누르세요..."
+ ;;
+ 2)
+ send_stats "지정된 서비스 로그 보기"
+ read -erp "서비스 이름(예: sshd, nginx)을 입력하세요." svc
+ if systemctl list-unit-files | grep -q "^$svc"; then
+ journalctl -u "$svc" -n 100 --no-pager
+ else
+ echo "✘ 서비스가 존재하지 않거나 로그가 없습니다."
+ fi
+ read -erp "계속하려면 Enter를 누르세요..."
+ ;;
+ 3)
+ send_stats "로그인/보안 로그 보기"
+ echo "====== 최근 로그인 로그 ======"
+ last -n 10
+ echo
+ echo "====== 인증 로그 ======"
+ if [ -f /var/log/secure ]; then
+ tail -n 20 /var/log/secure
+ elif [ -f /var/log/auth.log ]; then
+ tail -n 20 /var/log/auth.log
+ else
+ echo "보안 로그 파일을 찾을 수 없습니다."
+ fi
+ read -erp "계속하려면 Enter를 누르세요..."
+ ;;
+ 4)
+ send_stats "실시간 추적 로그"
+ echo "1) 시스템 로그"
+ echo "2) 서비스 로그 지정"
+ read -erp "추적 유형 선택:" t
+ if [ "$t" = "1" ]; then
+ journalctl -f
+ elif [ "$t" = "2" ]; then
+ read -erp "서비스 이름 입력:" svc
+ journalctl -u "$svc" -f
+ else
+ echo "잘못된 선택"
+ fi
+ ;;
+ 5)
+ send_stats "오래된 저널 로그 정리"
+ echo "⚠️ 일지를 청소하세요(안전한 방법)"
+ echo "1) 최근 7일을 보관"
+ echo "2) 최근 3일을 보관한다"
+ echo "3) 최대 로그 크기를 500M로 제한하십시오."
+ read -erp "청소 방법을 선택하세요:" c
+ case $c in
+ 1) journalctl --vacuum-time=7d ;;
+ 2) journalctl --vacuum-time=3d ;;
+ 3) journalctl --vacuum-size=500M ;;
+ *) echo "잘못된 옵션" ;;
+ esac
+ echo "✔ 저널 로그 정리 완료"
+ sleep 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+env_menu() {
+
+ BASHRC="$HOME/.bashrc"
+ PROFILE="$HOME/.profile"
+
+ send_stats "시스템 변수 관리 도구"
+
+ show_env_vars() {
+ clear
+ send_stats "현재 유효한 환경 변수"
+ echo "========== 현재 적용되는 환경변수(발췌) =========="
+ printf "%-20s %s\n" "변수 이름" "값"
+ echo "-----------------------------------------------"
+ for v in USER HOME SHELL LANG PWD; do
+ printf "%-20s %s\n" "$v" "${!v}"
+ done
+
+ echo
+ echo "PATH:"
+ echo "$PATH" | tr ':' '\n' | nl -ba
+
+ echo
+ echo "========== 구성 파일에 정의된 변수(파싱) =========="
+
+ parse_file_vars() {
+ local file="$1"
+ [ -f "$file" ] || return
+
+ echo
+ echo ">>> 소스 파일:$file"
+ echo "-----------------------------------------------"
+
+ # 내보내기 VAR=xxx 또는 VAR=xxx 추출
+ grep -Ev '^\s*#|^\s*$' "$file" \
+ | grep -E '^(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' \
+ | while read -r line; do
+ var=$(echo "$line" | sed -E 's/^(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*).*/\2/')
+ val=$(echo "$line" | sed -E 's/^[^=]+=//')
+ printf "%-20s %s\n" "$var" "$val"
+ done
+ }
+
+ parse_file_vars "$HOME/.bashrc"
+ parse_file_vars "$HOME/.profile"
+
+ echo
+ echo "==============================================="
+ read -erp "계속하려면 Enter를 누르세요..."
+ }
+
+
+ view_file() {
+ local file="$1"
+ send_stats "변수 파일 보기$file"
+ clear
+ if [ -f "$file" ]; then
+ echo "========== 파일 보기:$file =========="
+ cat -n "$file"
+ echo "===================================="
+ else
+ echo "파일이 존재하지 않습니다:$file"
+ fi
+ read -erp "계속하려면 Enter를 누르세요..."
+ }
+
+ edit_file() {
+ local file="$1"
+ send_stats "변수 파일 편집$file"
+ install nano
+ nano "$file"
+ }
+
+ source_files() {
+ echo "환경 변수를 다시 로드하는 중..."
+ send_stats "환경 변수 다시 로드 중"
+ source "$BASHRC"
+ source "$PROFILE"
+ echo "✔ 환경 변수가 다시 로드되었습니다."
+ read -erp "계속하려면 Enter를 누르세요..."
+ }
+
+ while true; do
+ clear
+ echo "=========== 시스템 환경 변수 관리 =========="
+ echo "현재 사용자:$USER"
+ echo "--------------------------------------"
+ echo "1. 현재 일반적으로 사용되는 환경변수를 확인하세요."
+ echo "2. ~/.bashrc 보기"
+ echo "3. ~/.profile 보기"
+ echo "4. ~/.bashrc 편집"
+ echo "5. ~/.profile 편집"
+ echo "6. 환경 변수 다시 로드(출처)"
+ echo "--------------------------------------"
+ echo "0. 이전 메뉴로 돌아가기"
+ echo "--------------------------------------"
+ read -erp "작업을 선택하십시오:" choice
+
+ case "$choice" in
+ 1)
+ show_env_vars
+ ;;
+ 2)
+ view_file "$BASHRC"
+ ;;
+ 3)
+ view_file "$PROFILE"
+ ;;
+ 4)
+ edit_file "$BASHRC"
+ ;;
+ 5)
+ edit_file "$PROFILE"
+ ;;
+ 6)
+ source_files
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "잘못된 옵션"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
- 21)
- while true; do
- clear
- if grep -q 'tmux attach-session -t sshd || tmux new-session -s sshd' ~/.bashrc; then
- local tmux_sshd_status="${gl_lv}开启${gl_bai}"
- else
- local tmux_sshd_status="${gl_hui}关闭${gl_bai}"
- fi
- send_stats "SSH 거주 모드"
- echo -e "SSH 거주 모드${tmux_sshd_status}"
- echo "SSH 연결이 활성화 된 후에는 거주 모드에 직접 입력하여 이전 작업 상태로 돌아갑니다."
- echo "------------------------"
- echo "1. 2를 켜십시오. 2를 끕니다"
- echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
- echo "------------------------"
- read -e -p "선택을 입력하십시오 :" gongzuoqu_del
- case "$gongzuoqu_del" in
- 1)
- install tmux
- local SESSION_NAME="sshd"
- send_stats "작업 공간을 시작하십시오$SESSION_NAME"
- grep -q "tmux attach-session -t sshd" ~/.bashrc || echo -e "\ n# 자동으로 tmux 세션을 입력 \ nif [[-z \"\$TMUX\" ]]; then\n tmux attach-session -t sshd || tmux new-session -s sshd\nfi" >> ~/.bashrc
- source ~/.bashrc
- tmux_run
- ;;
- 2)
- sed -i '/# 自动进入 tmux 会话/,+4d' ~/.bashrc
- tmux kill-window -t sshd
- ;;
- *)
- break
- ;;
- esac
- done
- ;;
+create_user_with_sshkey() {
+ local new_username="$1"
+ local is_sudo="${2:-false}"
+ local sshkey_vl
- 22)
- read -e -p "1001 KJ001 Work1과 같이 생성하거나 입력 한 작업 공간의 이름을 입력하십시오." SESSION_NAME
- tmux_run
- send_stats "사용자 정의 작업 공간"
- ;;
+ if [[ -z "$new_username" ]]; then
+ echo "사용법: create_user_with_sshkey <사용자 이름>"
+ return 1
+ fi
+ # 사용자 생성
+ useradd -m -s /bin/bash "$new_username" || return 1
- 23)
- read -e -p "Curl -fssl https://get.docker.com | 쉿:" tmuxd
- tmux_run_d
- send_stats "배경 작업 공간에 명령을 주입합니다"
- ;;
+ echo "공개 키 가져오기의 예:"
+ echo " - URL: ${gh_https_url}github.com/torvalds.keys"
+ echo "- 직접 붙여넣기: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+ read -e -p "수입해주세요${new_username}공개 키:" sshkey_vl
- 24)
- read -e -p "삭제하려는 작업 공간의 이름을 입력하십시오." gongzuoqu_name
- tmux kill-window -t $gongzuoqu_name
- send_stats "작업 공간을 삭제합니다"
- ;;
+ case "$sshkey_vl" in
+ http://*|https://*)
+ send_stats "URL에서 SSH 공개 키 가져오기"
+ fetch_remote_ssh_keys "$sshkey_vl" "/home/$new_username"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa*)
+ send_stats "공개 키를 직접 가져오기"
+ import_sshkey "$sshkey_vl" "/home/$new_username"
+ ;;
+ *)
+ echo "오류: 알 수 없는 매개변수 '$sshkey_vl'"
+ return 1
+ ;;
+ esac
- 0)
- kejilion
- ;;
- *)
- echo "잘못된 입력!"
- ;;
- esac
- break_end
- done
+ # 권한 수정
+ chown -R "$new_username:$new_username" "/home/$new_username/.ssh"
+
+ install sudo
+
+ # sudo 비밀번호가 필요하지 않음
+ if [[ "$is_sudo" == "true" ]]; then
+ cat >"/etc/sudoers.d/$new_username" <> /etc/ssh/sshd_config
+ passwd -l "$new_username" &>/dev/null
+ restart_ssh
+ echo "사용자$new_username생성 완료"
}
@@ -10825,57 +20132,66 @@ linux_work() {
+
+
+
linux_Settings() {
while true; do
clear
- # Send_stats "시스템 도구"
+ # send_stats "시스템 도구"
echo -e "시스템 도구"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}스크립트 스타트 업 단축키 키를 설정하십시오${gl_kjlan}2. ${gl_bai}로그인 암호를 수정하십시오"
- echo -e "${gl_kjlan}3. ${gl_bai}루트 암호 로그인 모드${gl_kjlan}4. ${gl_bai}지정된 버전의 Python을 설치하십시오"
- echo -e "${gl_kjlan}5. ${gl_bai}모든 포트를 엽니 다${gl_kjlan}6. ${gl_bai}SSH 연결 포트를 수정하십시오"
- echo -e "${gl_kjlan}7. ${gl_bai}DNS 주소를 최적화합니다${gl_kjlan}8. ${gl_bai}원 클릭 복직 시스템${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}9. ${gl_bai}루트 계정을 비활성화하여 새 계정을 생성하십시오${gl_kjlan}10. ${gl_bai}우선 순위 IPv4/IPv6을 전환하십시오"
+ echo -e "${gl_kjlan}1. ${gl_bai}스크립트 시작 단축키 설정${gl_kjlan}2. ${gl_bai}로그인 비밀번호 변경"
+ echo -e "${gl_kjlan}3. ${gl_bai}사용자 비밀번호 로그인 모드${gl_kjlan}4. ${gl_bai}지정된 버전의 Python 설치"
+ echo -e "${gl_kjlan}5. ${gl_bai}모든 포트 열기${gl_kjlan}6. ${gl_bai}SSH 연결 포트 수정"
+ echo -e "${gl_kjlan}7. ${gl_bai}DNS 주소 최적화${gl_kjlan}8. ${gl_bai}한 번의 클릭으로 시스템을 다시 설치${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}9. ${gl_bai}ROOT 계정을 비활성화하고 새 계정을 만듭니다.${gl_kjlan}10. ${gl_bai}우선순위 ipv4/ipv6 전환"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}항구 직업 상태를 확인하십시오${gl_kjlan}12. ${gl_bai}가상 메모리 크기를 수정합니다"
+ echo -e "${gl_kjlan}11. ${gl_bai}항만점유현황 확인${gl_kjlan}12. ${gl_bai}가상 메모리 크기 수정"
echo -e "${gl_kjlan}13. ${gl_bai}사용자 관리${gl_kjlan}14. ${gl_bai}사용자/비밀번호 생성기"
- echo -e "${gl_kjlan}15. ${gl_bai}시스템 시간대 조정${gl_kjlan}16. ${gl_bai}BBR3 가속도를 설정하십시오"
- echo -e "${gl_kjlan}17. ${gl_bai}방화벽 고급 관리자${gl_kjlan}18. ${gl_bai}호스트 이름을 수정하십시오"
- echo -e "${gl_kjlan}19. ${gl_bai}스위치 시스템 업데이트 소스${gl_kjlan}20. ${gl_bai}타이밍 작업 관리"
+ echo -e "${gl_kjlan}15. ${gl_bai}시스템 시간대 조정${gl_kjlan}16. ${gl_bai}BBR3 가속 설정"
+ echo -e "${gl_kjlan}17. ${gl_bai}방화벽 고급 관리자${gl_kjlan}18. ${gl_bai}호스트 이름 수정"
+ echo -e "${gl_kjlan}19. ${gl_bai}시스템 업데이트 소스 전환${gl_kjlan}20. ${gl_bai}예약된 작업 관리"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}기본 호스트 구문 분석${gl_kjlan}22. ${gl_bai}SSH 방어 프로그램"
- echo -e "${gl_kjlan}23. ${gl_bai}현재 한도의 자동 종료${gl_kjlan}24. ${gl_bai}루트 비공개 키 로그인 모드"
- echo -e "${gl_kjlan}25. ${gl_bai}TG-BOT 시스템 모니터링 및 조기 경고${gl_kjlan}26. ${gl_bai}OpenSsh 고위험 취약점 수정 (Xiuyuan)"
- echo -e "${gl_kjlan}27. ${gl_bai}Red Hat Linux 커널 업그레이드${gl_kjlan}28. ${gl_bai}Linux 시스템에서 커널 매개 변수의 최적화${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}29. ${gl_bai}바이러스 스캐닝 도구${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}파일 관리자"
+ echo -e "${gl_kjlan}21. ${gl_bai}기본 호스트 확인${gl_kjlan}22. ${gl_bai}SSH 방어 프로그램"
+ echo -e "${gl_kjlan}23. ${gl_bai}전류 제한 자동 종료${gl_kjlan}24. ${gl_bai}사용자 키 로그인 모드"
+ echo -e "${gl_kjlan}25. ${gl_bai}TG-bot 시스템 모니터링 및 조기 경보${gl_kjlan}26. ${gl_bai}OpenSSH 고위험 취약점 수정"
+ echo -e "${gl_kjlan}27. ${gl_bai}Red Hat Linux 커널 업그레이드${gl_kjlan}28. ${gl_bai}Linux 시스템 커널 매개변수 최적화${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}29. ${gl_bai}바이러스 검사 도구${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}파일 관리자"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}스위치 시스템 언어${gl_kjlan}32. ${gl_bai}명령 라인 미화 도구${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}시스템 재활용 빈을 설정하십시오${gl_kjlan}34. ${gl_bai}시스템 백업 및 복구"
+ echo -e "${gl_kjlan}31. ${gl_bai}시스템 언어 전환${gl_kjlan}32. ${gl_bai}명령줄 미화 도구${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}시스템 휴지통 설정${gl_kjlan}34. ${gl_bai}시스템 백업 및 복구"
echo -e "${gl_kjlan}35. ${gl_bai}SSH 원격 연결 도구${gl_kjlan}36. ${gl_bai}하드 디스크 파티션 관리 도구"
- echo -e "${gl_kjlan}37. ${gl_bai}명령 줄 기록${gl_kjlan}38. ${gl_bai}RSYNC 원격 동기화 도구"
+ echo -e "${gl_kjlan}37. ${gl_bai}명령줄 기록${gl_kjlan}38. ${gl_bai}rsync 원격 동기화 도구"
+ echo -e "${gl_kjlan}39. ${gl_bai}명령 즐겨찾기${gl_huang}★${gl_bai} ${gl_kjlan}40. ${gl_bai}네트워크 카드 관리 도구"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}게시판${gl_kjlan}66. ${gl_bai}원 스톱 시스템 최적화${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}99. ${gl_bai}서버를 다시 시작하십시오${gl_kjlan}100. ${gl_bai}개인 정보 및 보안"
- echo -e "${gl_kjlan}101. ${gl_bai}K 명령의 고급 사용${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}기술 라이온 스크립트를 제거하십시오"
+ echo -e "${gl_kjlan}41. ${gl_bai}시스템 로그 관리 도구${gl_huang}★${gl_bai} ${gl_kjlan}42. ${gl_bai}시스템 변수 관리 도구"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아갑니다"
+ echo -e "${gl_kjlan}61. ${gl_bai}메시지 보드${gl_kjlan}66. ${gl_bai}원스톱 시스템 튜닝${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}99. ${gl_bai}서버를 다시 시작하세요${gl_kjlan}100. ${gl_bai}개인 정보 보호 및 보안"
+ echo -e "${gl_kjlan}101. ${gl_bai}k 명령의 고급 사용법${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}기술 사자 스크립트 제거"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}메인 메뉴로 돌아가기"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
while true; do
clear
- read -e -p "바로 가기 키를 입력하십시오 (종료하려면 0을 입력하십시오) :" kuaijiejian
+ read -e -p "바로가기 키를 입력하십시오(종료하려면 0을 입력하십시오):" kuaijiejian
if [ "$kuaijiejian" == "0" ]; then
break_end
linux_Settings
fi
find /usr/local/bin/ -type l -exec bash -c 'test "$(readlink -f {})" = "/usr/local/bin/k" && rm -f {}' \;
- ln -s /usr/local/bin/k /usr/local/bin/$kuaijiejian
- echo "바로 가기 키가 설정되어 있습니다"
- send_stats "스크립트 바로 가기 키가 설정되었습니다"
+ if [ "$kuaijiejian" != "k" ]; then
+ ln -sf /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ fi
+ ln -sf /usr/local/bin/k /usr/bin/$kuaijiejian > /dev/null 2>&1
+ echo "단축키가 설정되었습니다"
+ send_stats "스크립트 단축키가 설정되었습니다"
break_end
linux_Settings
done
@@ -10883,30 +20199,29 @@ linux_Settings() {
2)
clear
- send_stats "로그인 비밀번호를 설정하십시오"
- echo "로그인 비밀번호를 설정하십시오"
+ send_stats "로그인 비밀번호를 설정하세요"
+ echo "로그인 비밀번호를 설정하세요"
passwd
;;
3)
- root_use
- send_stats "루트 비밀번호 모드"
+ clear
add_sshpasswd
;;
4)
root_use
- send_stats "PY 버전 관리"
+ send_stats "py 버전 관리"
echo "파이썬 버전 관리"
- echo "비디오 소개 : https://www.bilibili.com/video/bv1pm42157ck?t=0.1"
+ echo "영상 소개: https://www.bilibili.com/video/BV1Pm42157cK?t=0.1"
echo "---------------------------------------"
- echo "이 기능은 Python에서 공식적으로 지원되는 모든 버전을 완벽하게 설치합니다!"
+ echo "이 기능은 Python이 공식적으로 지원하는 모든 버전을 원활하게 설치할 수 있습니다!"
local VERSION=$(python3 -V 2>&1 | awk '{print $2}')
- echo -e "현재 파이썬 버전 번호 :${gl_huang}$VERSION${gl_bai}"
+ echo -e "현재 Python 버전 번호:${gl_huang}$VERSION${gl_bai}"
echo "------------"
- echo "권장 버전 : 3.12 3.11 3.10 3.9 3.8 2.7"
- echo "더 많은 버전 : https://www.python.org/downloads/"
+ echo "권장 버전: 3.12 3.11 3.10 3.9 3.8 2.7"
+ echo "더 많은 버전 확인: https://www.python.org/downloads/"
echo "------------"
- read -e -p "설치하려는 Python 버전 번호를 입력하십시오 (종료하려면 0을 입력하십시오) :" py_new_v
+ read -e -p "설치하려는 Python 버전 번호를 입력하세요(종료하려면 0 입력)." py_new_v
if [[ "$py_new_v" == "0" ]]; then
@@ -10943,7 +20258,7 @@ linux_Settings() {
apk update && apk add git
apk add --no-cache bash gcc musl-dev libffi-dev openssl-dev bzip2-dev zlib-dev readline-dev sqlite-dev libc6-compat linux-headers make xz-dev build-base ncurses-dev
else
- echo "알 수없는 패키지 관리자!"
+ echo "알 수 없는 패키지 관리자입니다!"
return
fi
@@ -10972,55 +20287,55 @@ EOF
rm -rf $(pyenv root)/cache/*
local VERSION=$(python -V 2>&1 | awk '{print $2}')
- echo -e "현재 파이썬 버전 번호 :${gl_huang}$VERSION${gl_bai}"
- send_stats "스크립트 Py 버전을 스위치하십시오"
+ echo -e "현재 Python 버전 번호:${gl_huang}$VERSION${gl_bai}"
+ send_stats "스크립트 PY 버전 전환"
;;
5)
root_use
- send_stats "포트 열기"
+ send_stats "열린 포트"
iptables_open
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
- echo "모든 포트가 열려 있습니다"
+ echo "모든 포트가 열려 있습니다."
;;
6)
root_use
- send_stats "SSH 포트를 수정하십시오"
+ send_stats "SSH 포트 수정"
while true; do
clear
- sed -i 's/#Port/Port/' /etc/ssh/sshd_config
+ sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- # 현재 SSH 포트 번호를 읽으십시오
+ # 현재 SSH 포트 번호 읽기
local current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
- # 현재 SSH 포트 번호를 인쇄하십시오
+ # 현재 SSH 포트 번호 인쇄
echo -e "현재 SSH 포트 번호는 다음과 같습니다.${gl_huang}$current_port ${gl_bai}"
echo "------------------------"
- echo "1 ~ 65535 범위의 포트 번호가있는 숫자 (종료하려면 0을 입력하십시오)"
+ echo "포트 번호 범위는 1~65535입니다. (종료하려면 0을 입력하세요.)"
- # 사용자에게 새 SSH 포트 번호를 입력하라는 메시지
- read -e -p "새로운 SSH 포트 번호를 입력하십시오 :" new_port
+ # 사용자에게 새 SSH 포트 번호를 묻는 메시지 표시
+ read -e -p "새 SSH 포트 번호를 입력하세요." new_port
# 포트 번호가 유효한 범위 내에 있는지 확인
if [[ $new_port =~ ^[0-9]+$ ]]; then # 检查输入是否为数字
if [[ $new_port -ge 1 && $new_port -le 65535 ]]; then
- send_stats "SSH 포트가 수정되었습니다"
- new_ssh_port
+ send_stats "SSH 포트가 수정되었습니다."
+ new_ssh_port $new_port
elif [[ $new_port -eq 0 ]]; then
- send_stats "SSH 포트 수정을 종료하십시오"
+ send_stats "SSH 포트 수정 종료"
break
else
- echo "포트 번호는 유효하지 않으며 1에서 65535 사이의 숫자를 입력하십시오."
- send_stats "잘못된 SSH 포트 입력"
+ echo "포트 번호가 잘못되었습니다. 1~65535 사이의 숫자를 입력하세요."
+ send_stats "잘못된 SSH 포트가 입력되었습니다."
break_end
fi
else
- echo "입력이 유효하지 않으므로 번호를 입력하십시오."
- send_stats "잘못된 SSH 포트 입력"
+ echo "입력이 잘못되었습니다. 숫자를 입력하세요."
+ send_stats "잘못된 SSH 포트가 입력되었습니다."
break_end
fi
done
@@ -11039,63 +20354,61 @@ EOF
;;
9)
root_use
- send_stats "신규 사용자는 루트를 비활성화합니다"
- read -e -p "새 사용자 이름을 입력하십시오 (종료하려면 0을 입력하십시오) :" new_username
+ send_stats "신규 사용자에 대한 루트 비활성화"
+ read -e -p "새 사용자 이름을 입력하십시오(종료하려면 0을 입력하십시오):" new_username
if [ "$new_username" == "0" ]; then
break_end
linux_Settings
fi
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
+ create_user_with_sshkey $new_username true
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- passwd -l root
+ ssh-keygen -l -f /home/$new_username/.ssh/authorized_keys &>/dev/null && {
+ passwd -l root &>/dev/null
+ sed -i 's/^[[:space:]]*#\?[[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
+ }
- echo "작업이 완료되었습니다."
;;
10)
root_use
- send_stats "V4/V6 우선 순위를 설정하십시오"
+ send_stats "v4/v6 우선순위 설정"
while true; do
clear
- echo "V4/V6 우선 순위를 설정하십시오"
+ echo "v4/v6 우선순위 설정"
echo "------------------------"
- local ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
- if [ "$ipv6_disabled" -eq 1 ]; then
- echo -e "현재 네트워크 우선 순위 설정 :${gl_huang}IPv4${gl_bai}우선 사항"
+
+ if grep -Eq '^\s*precedence\s+::ffff:0:0/96\s+100\s*$' /etc/gai.conf 2>/dev/null; then
+ echo -e "현재 네트워크 우선순위 설정:${gl_huang}IPv4${gl_bai}우선 사항"
else
- echo -e "현재 네트워크 우선 순위 설정 :${gl_huang}IPv6${gl_bai}우선 사항"
+ echo -e "현재 네트워크 우선순위 설정:${gl_huang}IPv6${gl_bai}우선 사항"
fi
+
echo ""
echo "------------------------"
- echo "1. IPv4 우선 순위 2. IPv6 우선 순위 3. IPv6 수리 도구"
+ echo "1. IPv4 우선 2. IPv6 우선 3. IPv6 복구 도구"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선호하는 네트워크를 선택하십시오." choice
+ read -e -p "선호하는 네트워크를 선택하세요:" choice
case $choice in
1)
- sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
- echo "IPv4 우선 순위로 전환되었습니다"
- send_stats "IPv4 우선 순위로 전환되었습니다"
+ prefer_ipv4
;;
2)
- sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
- echo "IPv6 우선 순위로 전환되었습니다"
- send_stats "IPv6 우선 순위로 전환되었습니다"
+ rm -f /etc/gai.conf
+ echo "IPv6 우선순위로 전환됨"
+ send_stats "IPv6 우선순위로 전환됨"
;;
3)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "이 기능은 마스터 JHB가 제공합니다."
- send_stats "IPv6 수정"
+ echo "이 기능은 jhb에서 제공합니다. 감사합니다!"
+ send_stats "IPv6 수리"
;;
*)
@@ -11113,25 +20426,25 @@ EOF
12)
root_use
- send_stats "가상 메모리를 설정합니다"
+ send_stats "가상 메모리 설정"
while true; do
clear
- echo "가상 메모리를 설정합니다"
+ echo "가상 메모리 설정"
local swap_used=$(free -m | awk 'NR==3{print $3}')
local swap_total=$(free -m | awk 'NR==3{print $2}')
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- echo -e "현재 가상 메모리 :${gl_huang}$swap_info${gl_bai}"
+ echo -e "현재 가상 메모리:${gl_huang}$swap_info${gl_bai}"
echo "------------------------"
- echo "1. 1024m 2. 할당 2048m 3. 4096m 할당 4. 사용자 정의 크기"
+ echo "1. 1024M 할당 2. 2048M 할당 3. 4096M 할당 4. 사용자 정의 크기"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택사항을 입력하세요:" choice
case "$choice" in
1)
- send_stats "1G 가상 메모리가 설정되었습니다"
+ send_stats "1G 가상 메모리가 설정되었습니다."
add_swap 1024
;;
@@ -11141,15 +20454,15 @@ EOF
;;
3)
- send_stats "4G 가상 메모리가 설정되었습니다"
+ send_stats "4G 가상 메모리가 설정되었습니다."
add_swap 4096
;;
4)
- read -e -p "가상 메모리 크기 (단위 M)를 입력하십시오." new_swap
+ read -e -p "가상 메모리 크기(단위 M)를 입력하세요." new_swap
add_swap "$new_swap"
- send_stats "사용자 정의 가상 메모리가 설정되었습니다"
+ send_stats "사용자 정의 가상 메모리 세트"
;;
*)
@@ -11165,10 +20478,15 @@ EOF
send_stats "사용자 관리"
echo "사용자 목록"
echo "----------------------------------------------------------------------------"
- printf "%-24s %-34s %-20s %-10s\n" "用户名" "用户权限" "用户组" "sudo权限"
+ printf "%-24s %-34s %-20s %-10s\n" "사용자 이름" "사용자 권한" "사용자 그룹" "sudo 권한"
while IFS=: read -r username _ userid groupid _ _ homedir shell; do
local groups=$(groups "$username" | cut -d : -f 2)
- local sudo_status=$(sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL : ALL)' && echo "Yes" || echo "No")
+ local sudo_status
+ if sudo -n -lU "$username" 2>/dev/null | grep -q "(ALL) \(NOPASSWD: \)\?ALL"; then
+ sudo_status="Yes"
+ else
+ sudo_status="No"
+ fi
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$groups" "$sudo_status"
done < /etc/passwd
@@ -11176,56 +20494,48 @@ EOF
echo ""
echo "계정 운영"
echo "------------------------"
- echo "1. 일반 계정 만들기 2. 프리미엄 계정 만들기"
+ echo "1. 일반 사용자 생성 2. 고급 사용자 생성"
echo "------------------------"
- echo "3. 최고 권한을 부여 4. 최고 권한을 취소하십시오."
+ echo "3. 가장 높은 권한을 부여합니다. 4. 가장 높은 권한을 제거합니다."
echo "------------------------"
- echo "5. 계정을 삭제하십시오"
+ echo "5. 계정 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- # 사용자에게 새 사용자 이름을 입력하도록 프롬프트하십시오
- read -e -p "새 사용자 이름을 입력하십시오 :" new_username
+ # 사용자에게 새 사용자 이름을 묻는 메시지 표시
+ read -e -p "새 사용자 이름을 입력하세요:" new_username
+ create_user_with_sshkey $new_username false
- # 새 사용자를 생성하고 비밀번호를 설정하십시오
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "작업이 완료되었습니다."
;;
2)
- # 사용자에게 새 사용자 이름을 입력하도록 프롬프트하십시오
- read -e -p "새 사용자 이름을 입력하십시오 :" new_username
-
- # 새 사용자를 생성하고 비밀번호를 설정하십시오
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- # 새로운 사용자에게 허가를 부여하십시오
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- echo "작업이 완료되었습니다."
+ # 사용자에게 새 사용자 이름을 묻는 메시지 표시
+ read -e -p "새 사용자 이름을 입력하세요:" new_username
+ create_user_with_sshkey $new_username true
;;
3)
- read -e -p "사용자 이름을 입력하십시오 :" username
- # 새로운 사용자에게 허가를 부여하십시오
- echo "$username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ read -e -p "사용자 이름을 입력하세요:" username
+ install sudo
+ cat >"/etc/sudoers.d/$username" </dev/null | xargs rm -f
+ fi
+ sed -i "/^$username\s*ALL=(ALL)/d" /etc/sudoers
;;
5)
- read -e -p "삭제하려면 사용자 이름을 입력하십시오." username
- # 사용자와 홈 디렉토리를 삭제하십시오
+ read -e -p "삭제하려는 사용자 이름을 입력하세요:" username
userdel -r "$username"
;;
@@ -11233,6 +20543,7 @@ EOF
break # 跳出循环,退出菜单
;;
esac
+
done
;;
@@ -11247,12 +20558,12 @@ EOF
done
echo ""
- echo "임의 이름"
+ echo "임의의 이름"
echo "------------------------"
local first_names=("John" "Jane" "Michael" "Emily" "David" "Sophia" "William" "Olivia" "James" "Emma" "Ava" "Liam" "Mia" "Noah" "Isabella")
local last_names=("Smith" "Johnson" "Brown" "Davis" "Wilson" "Miller" "Jones" "Garcia" "Martinez" "Williams" "Lee" "Gonzalez" "Rodriguez" "Hernandez")
- # 5 개의 임의의 사용자 이름을 생성합니다
+ # 5개의 무작위 사용자 이름 생성
for i in {1..5}; do
local first_name_index=$((RANDOM % ${#first_names[@]}))
local last_name_index=$((RANDOM % ${#last_names[@]}))
@@ -11261,27 +20572,27 @@ EOF
done
echo ""
- echo "무작위 uuid"
+ echo "무작위 UUID"
echo "------------------------"
for i in {1..5}; do
uuid=$(cat /proc/sys/kernel/random/uuid)
- echo "무작위 uuid$i: $uuid"
+ echo "무작위 UUID$i: $uuid"
done
echo ""
- echo "16 비트 랜덤 비밀번호"
+ echo "16자리 랜덤 비밀번호"
echo "------------------------"
for i in {1..5}; do
local password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
- echo "랜덤 비밀번호$i: $password"
+ echo "임의의 비밀번호$i: $password"
done
echo ""
- echo "32 비트 랜덤 비밀번호"
+ echo "32비트 임의 비밀번호"
echo "------------------------"
for i in {1..5}; do
local password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32)
- echo "랜덤 비밀번호$i: $password"
+ echo "임의의 비밀번호$i: $password"
done
echo ""
@@ -11289,46 +20600,46 @@ EOF
15)
root_use
- send_stats "시간대를 변경하십시오"
+ send_stats "시간대 변경"
while true; do
clear
echo "시스템 시간 정보"
- # 현재 시스템 시간대를 얻으십시오
+ # 현재 시스템 시간대 가져오기
local timezone=$(current_timezone)
- # 현재 시스템 시간을 얻으십시오
+ # 현재 시스템 시간 가져오기
local current_time=$(date +"%Y-%m-%d %H:%M:%S")
- # 시간대와 시간을 보여줍니다
- echo "현재 시스템 시간대 :$timezone"
- echo "현재 시스템 시간 :$current_time"
+ # 시간대 및 시간 표시
+ echo "현재 시스템 시간대:$timezone"
+ echo "현재 시스템 시간:$current_time"
echo ""
- echo "시간대 스위칭"
+ echo "시간대 스위치"
echo "------------------------"
echo "아시아"
- echo "1. 중국의 상하이 시간 2. 중국의 홍콩 시간"
- echo "3. 일본의 도쿄 시간 4. 한국의 서울 시간"
- echo "5. 싱가포르 시간 6. 인도의 콜카타 시간"
- echo "7. UAE 8의 두바이 시간. 호주 시드니 시간"
- echo "9. 태국 방콕에서의 시간"
+ echo "1. 중국 상하이 시간 2. 중국 홍콩 시간"
+ echo "3. 일본 도쿄 시간 4. 한국 서울 시간"
+ echo "5. 싱가포르 시간 6. 콜카타, 인도 시간"
+ echo "7. 아랍에미리트 두바이 시간 8. 호주 시드니 시간"
+ echo "9. 태국 방콕 시간"
echo "------------------------"
echo "유럽"
- echo "11. 영국의 런던 시간 12. 프랑스의 파리 시간"
- echo "13. 베를린 시간, 독일 14. 모스크바 시간, 러시아"
- echo "15. 네덜란드에서 우트레흐트 시간 16. 스페인의 마드리드 시간"
+ echo "11. 영국 런던 시간 12. 프랑스 파리 시간"
+ echo "13. 독일 베를린 시간 14. 러시아 모스크바 시간"
+ echo "15. 네덜란드 유트라흐트 시간 16. 스페인 마드리드 시간"
echo "------------------------"
echo "미국"
- echo "21. 서양 시간 22. 동부 시간"
+ echo "21. 미국 서부 시간 22. 미국 동부 시간"
echo "23. 캐나다 시간 24. 멕시코 시간"
echo "25. 브라질 시간 26. 아르헨티나 시간"
echo "------------------------"
- echo "31. UTC 글로벌 표준 시간"
+ echo "31. UTC 세계 표준시"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
@@ -11371,21 +20682,21 @@ EOF
18)
root_use
- send_stats "호스트 이름을 수정하십시오"
+ send_stats "호스트 이름 수정"
while true; do
clear
local current_hostname=$(uname -n)
- echo -e "현재 호스트 이름 :${gl_huang}$current_hostname${gl_bai}"
+ echo -e "현재 호스트 이름:${gl_huang}$current_hostname${gl_bai}"
echo "------------------------"
- read -e -p "새 호스트 이름을 입력하십시오 (종료하려면 0을 입력하십시오) :" new_hostname
+ read -e -p "새 호스트 이름을 입력하십시오(종료하려면 0을 입력하십시오):" new_hostname
if [ -n "$new_hostname" ] && [ "$new_hostname" != "0" ]; then
if [ -f /etc/alpine-release ]; then
# Alpine
echo "$new_hostname" > /etc/hostname
hostname "$new_hostname"
else
- # Debian, Ubuntu, Centos 등과 같은 다른 시스템
+ # Debian, Ubuntu, CentOS 등과 같은 기타 시스템
hostnamectl set-hostname "$new_hostname"
sed -i "s/$current_hostname/$new_hostname/g" /etc/hostname
systemctl restart systemd-hostnamed
@@ -11403,11 +20714,11 @@ EOF
echo "::1 $new_hostname localhost localhost.localdomain ipv6-localhost ipv6-loopback" >> /etc/hosts
fi
- echo "호스트 이름은 다음으로 변경되었습니다.$new_hostname"
- send_stats "호스트 이름이 변경되었습니다"
+ echo "호스트 이름이 다음으로 변경되었습니다.$new_hostname"
+ send_stats "호스트 이름이 변경됨"
sleep 1
else
- echo "종료, 호스트 이름이 변경되지 않았습니다."
+ echo "호스트 이름을 변경하지 않고 종료되었습니다."
break
fi
done
@@ -11415,30 +20726,35 @@ EOF
19)
root_use
- send_stats "시스템 업데이트 소스를 변경하십시오"
+ send_stats "시스템 업데이트 소스 변경"
clear
- echo "업데이트 소스 영역을 선택하십시오"
- echo "LinuxMirrors에 연결하여 시스템 업데이트 소스를 전환하십시오"
+ echo "업데이트 소스 지역 선택"
+ echo "LinuxMirrors에 액세스하여 시스템 업데이트 소스 전환"
echo "------------------------"
- echo "1. 중국 본토 [기본값] 2. 중국 본토 [교육 네트워크] 3. 해외 지역"
+ echo "1. 중국 본토 [기본값] 2. 중국 본토 [교육 네트워크] 3. 해외 지역 4. 업데이트 소스의 지능형 전환"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" choice
+ read -e -p "선택 항목을 입력하세요." choice
case $choice in
1)
- send_stats "중국 본토의 기본 소스"
+ send_stats "중국 본토 기본 소스"
bash <(curl -sSL https://linuxmirrors.cn/main.sh)
;;
2)
- send_stats "중국 본토의 교육 원"
+ send_stats "중국 본토 교육 소스"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --edu
;;
3)
- send_stats "해외 출신"
+ send_stats "해외 소스"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --abroad
;;
+ 4)
+ send_stats "업데이트 소스의 지능적인 전환"
+ switch_mirror false false
+ ;;
+
*)
echo "취소"
;;
@@ -11448,62 +20764,62 @@ EOF
;;
20)
- send_stats "타이밍 작업 관리"
+ send_stats "예약된 작업 관리"
while true; do
clear
check_crontab_installed
clear
- echo "시간이 정한 작업 목록"
+ echo "예약된 작업 목록"
crontab -l
echo ""
echo "작동하다"
echo "------------------------"
- echo "1. 타이밍 작업 추가 2. 타이밍 작업 삭제 3. 타이밍 작업 편집"
+ echo "1. 예약된 작업 추가 2. 예약된 작업 삭제 3. 예약된 작업 편집"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
- read -e -p "새 작업에 대한 실행 명령을 입력하십시오." newquest
+ read -e -p "새 작업의 실행 명령을 입력하십시오:" newquest
echo "------------------------"
echo "1. 월간 작업 2. 주간 작업"
- echo "3. 일일 작업 4. 시간당 작업"
+ echo "3. 일일 작업 4. 시간별 작업"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" dingshi
+ read -e -p "선택사항을 입력하세요:" dingshi
case $dingshi in
1)
- read -e -p "작업을 수행하려면 매월 어느 날을 선택합니까? (1-30) :" day
+ read -e -p "작업을 실행하기로 선택한 달의 날짜는 무엇입니까? (1-30):" day
(crontab -l ; echo "0 0 $day * * $newquest") | crontab - > /dev/null 2>&1
;;
2)
- read -e -p "작업을 수행 할 일주일을 선택합니까? (0-6, 0은 일요일을 나타냅니다) :" weekday
+ read -e -p "작업을 수행할 요일을 선택하시겠습니까? (0-6, 0은 일요일을 나타냄):" weekday
(crontab -l ; echo "0 0 * * $weekday $newquest") | crontab - > /dev/null 2>&1
;;
3)
- read -e -p "매일 작업을 수행 할 시간을 선택하십시오. (시간, 0-23) :" hour
+ read -e -p "매일 몇 시에 작업을 수행하기로 선택하시나요? (시간, 0-23):" hour
(crontab -l ; echo "0 $hour * * * $newquest") | crontab - > /dev/null 2>&1
;;
4)
- read -e -p "작업을 수행하기 위해 몇 분의 시간을 입력합니까? (Mins, 0-60) :" minute
+ read -e -p "작업을 실행해야 하는 시간을 입력하세요. (분, 0-60):" minute
(crontab -l ; echo "$minute * * * * $newquest") | crontab - > /dev/null 2>&1
;;
*)
break # 跳出
;;
esac
- send_stats "시간이 정한 작업을 추가하십시오"
+ send_stats "예약된 작업 추가"
;;
2)
- read -e -p "삭제 해야하는 키워드를 입력하십시오." kquest
+ read -e -p "삭제할 작업의 키워드를 입력하세요:" kquest
crontab -l | grep -v "$kquest" | crontab -
- send_stats "타이밍 작업을 삭제하십시오"
+ send_stats "예약된 작업 삭제"
;;
3)
crontab -e
- send_stats "타이밍 작업 편집"
+ send_stats "예약된 작업 편집"
;;
*)
break # 跳出循环,退出菜单
@@ -11515,32 +20831,32 @@ EOF
21)
root_use
- send_stats "지역 호스트 구문 분석"
+ send_stats "로컬 호스트 확인"
while true; do
clear
- echo "기본 호스트 구문 분석 목록"
- echo "여기에 구문 분석 일치를 추가하면 더 이상 동적 구문 분석이 사용되지 않습니다."
+ echo "기본 호스트 확인 목록"
+ echo "여기에 파싱 매칭을 추가하면 더 이상 동적 파싱이 사용되지 않습니다."
cat /etc/hosts
echo ""
echo "작동하다"
echo "------------------------"
- echo "1. 새 구문 분석 추가 2. 구문 분석 주소 삭제"
+ echo "1. 새로운 해상도 추가 2. 해상도 주소 삭제"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" host_dns
+ read -e -p "선택사항을 입력하세요:" host_dns
case $host_dns in
1)
- read -e -p "새 구문 분석 기록 형식을 입력하십시오 : 110.25.5.33 Kejilion.pro :" addhost
+ read -e -p "새로운 구문 분석 기록 형식을 입력하세요: 110.25.5.33 kejilion.pro:" addhost
echo "$addhost" >> /etc/hosts
- send_stats "로컬 호스트 구문 분석이 추가되었습니다"
+ send_stats "로컬 호스트 해상도가 추가되었습니다."
;;
2)
- read -e -p "삭제 해야하는 구문 분석 컨텐츠의 키워드를 입력하십시오." delhost
+ read -e -p "삭제해야 하는 구문 분석된 콘텐츠의 키워드를 입력하세요." delhost
sed -i "/$delhost/d" /etc/hosts
- send_stats "로컬 호스트 구문 분석 및 삭제"
+ send_stats "로컬 호스트 확인 및 삭제"
;;
*)
break # 跳出循环,退出菜单
@@ -11550,109 +20866,53 @@ EOF
;;
22)
- root_use
- send_stats "SSH 방어"
- while true; do
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
- clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "SSH 방어 프로그램$check_docker"
- echo "FAIL2BAN은 무자비한 힘을 방지하는 SSH 도구입니다"
- echo "공식 웹 사이트 소개 :${gh_proxy}github.com/fail2ban/fail2ban"
- echo "------------------------"
- echo "1. 방어 프로그램을 설치하십시오"
- echo "------------------------"
- echo "2. SSH 차단 레코드보기"
- echo "3. 실시간 로그 모니터링"
- echo "------------------------"
- echo "9. 방어 프로그램을 제거하십시오"
- echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
- echo "------------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
- case $sub_choice in
- 1)
- install_docker
- f2b_install_sshd
-
- cd ~
- f2b_status
- break_end
- ;;
- 2)
- echo "------------------------"
- f2b_sshd
- echo "------------------------"
- break_end
- ;;
- 3)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
- break
- ;;
- 9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
- echo "Fail2ban 방어 프로그램은 제거되었습니다"
- ;;
- *)
- break
- ;;
- esac
- fi
- done
+ fail2ban_panel
;;
23)
root_use
- send_stats "현재 제한 종료 기능"
+ send_stats "전류 제한 차단 기능"
while true; do
clear
- echo "현재 제한 종료 기능"
- echo "비디오 소개 : https://www.bilibili.com/video/bv1mc411j7qd?t=0.1"
+ echo "전류 제한 차단 기능"
+ echo "영상 소개: https://www.bilibili.com/video/BV1mC411j7Qd?t=0.1"
echo "------------------------------------------------"
- echo "서버 트래픽 계산을 다시 시작하는 현재 트래픽 사용이 지워집니다!"
+ echo "서버가 다시 시작되면 현재 트래픽 사용량이 지워집니다!"
output_status
- echo -e "${gl_kjlan}총 수신 :${gl_bai}$rx"
- echo -e "${gl_kjlan}총 보내기 :${gl_bai}$tx"
+ echo -e "${gl_kjlan}받은 총액:${gl_bai}$rx"
+ echo -e "${gl_kjlan}보낸 총액:${gl_bai}$tx"
- # limiting_shut_down.sh 파일이 있는지 확인하십시오
+ # Limiting_Shut_down.sh 파일이 있는지 확인하세요.
if [ -f ~/Limiting_Shut_down.sh ]; then
- # threshold_gb의 값을 얻으십시오
+ # Threshold_gb 값을 가져옵니다.
local rx_threshold_gb=$(grep -oP 'rx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
local tx_threshold_gb=$(grep -oP 'tx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
- echo -e "${gl_lv}현재 세트 입력-스테이션 전류 한계 임계 값은 다음과 같습니다.${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
- echo -e "${gl_lv}현재 아웃 바운드 전류 한계 임계 값은 다음과 같습니다.${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
+ echo -e "${gl_lv}현재 설정된 인바운드 트래픽 제한 임계값은 다음과 같습니다.${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
+ echo -e "${gl_lv}현재 설정된 아웃바운드 트래픽 제한 임계값은 다음과 같습니다.${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
else
- echo -e "${gl_hui}전류 제한 종료 기능이 활성화되지 않았습니다${gl_bai}"
+ echo -e "${gl_hui}현재 제한 종료 기능이 현재 활성화되어 있지 않습니다.${gl_bai}"
fi
echo
echo "------------------------------------------------"
- echo "시스템은 실제 트래픽이 1 분마다 임계 값에 도달하는지 여부를 감지하고 서버가 도착하면 자동으로 종료됩니다!"
+ echo "시스템은 매분마다 실제 트래픽이 임계값에 도달했는지 여부를 감지하고 임계값에 도달한 후 자동으로 서버를 종료합니다!"
echo "------------------------"
- echo "1. 현재 한계 셧다운 함수 켜기 2. 현재 한계 종료 함수 비활성화"
+ echo "1. 전류 제한 종료 기능을 활성화합니다. 2. 전류 제한 종료 기능을 비활성화합니다."
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" Limiting
+ read -e -p "선택사항을 입력하세요:" Limiting
case "$Limiting" in
1)
- # 새 가상 메모리 크기를 입력하십시오
- echo "실제 서버에 트래픽이 100g 인 경우 트래픽 오류 나 오버플로를 피하기 위해 임계 값을 95G로 설정하고 전원을 미리 차단할 수 있습니다."
- read -e -p "들어오는 트래픽 임계 값을 입력하십시오 (단위 G, 기본값은 100G) :" rx_threshold_gb
+ # 새 가상 메모리 크기 입력
+ echo "실제 서버의 트래픽이 100G만 있는 경우 임계값을 95G로 설정하고 미리 종료하여 트래픽 오류나 오버플로를 방지할 수 있습니다."
+ read -e -p "인바운드 트래픽 임계값을 입력하십시오(단위는 G, 기본값은 100G)." rx_threshold_gb
rx_threshold_gb=${rx_threshold_gb:-100}
- read -e -p "아웃 바운드 트래픽 임계 값을 입력하십시오 (단위 G, 기본값은 100g) :" tx_threshold_gb
+ read -e -p "아웃바운드 트래픽 임계값을 입력하십시오(단위는 G, 기본값은 100G)." tx_threshold_gb
tx_threshold_gb=${tx_threshold_gb:-100}
- read -e -p "트래픽 재설정 날짜를 입력하십시오 (매월 1 일에 기본 재설정) :" cz_day
+ read -e -p "트래픽 재설정 날짜를 입력하세요(기본적으로 매월 1일 재설정)." cz_day
cz_day=${cz_day:-1}
cd ~
@@ -11665,15 +20925,15 @@ EOF
(crontab -l ; echo "* * * * * ~/Limiting_Shut_down.sh") | crontab - > /dev/null 2>&1
crontab -l | grep -v 'reboot' | crontab -
(crontab -l ; echo "0 1 $cz_day * * reboot") | crontab - > /dev/null 2>&1
- echo "전류 제한 종료가 설정되었습니다"
- send_stats "전류 제한 종료가 설정되었습니다"
+ echo "전류 제한 종료가 설정되었습니다."
+ send_stats "전류 제한 종료가 설정되었습니다."
;;
2)
check_crontab_installed
crontab -l | grep -v '~/Limiting_Shut_down.sh' | crontab -
crontab -l | grep -v 'reboot' | crontab -
rm ~/Limiting_Shut_down.sh
- echo "전류 제한 종료 기능이 꺼졌습니다"
+ echo "전류 제한 차단 기능이 꺼졌습니다."
;;
*)
break
@@ -11684,69 +20944,23 @@ EOF
24)
-
- root_use
- send_stats "개인 키 로그인"
- while true; do
- clear
- echo "루트 비공개 키 로그인 모드"
- echo "비디오 소개 : https://www.bilibili.com/video/bv1q4421x78n?t=209.4"
- echo "------------------------------------------------"
- echo "키 쌍이 생성되며 SSH 로그인을위한보다 안전한 방법"
- echo "------------------------"
- echo "1. 새 키 생성 2. 기존 키 가져 오기 3. 기본 키보기"
- echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
- echo "------------------------"
- read -e -p "선택을 입력하십시오 :" host_dns
-
- case $host_dns in
- 1)
- send_stats "새로운 키를 생성하십시오"
- add_sshkey
- break_end
-
- ;;
- 2)
- send_stats "기존 공개 키를 가져옵니다"
- import_sshkey
- break_end
-
- ;;
- 3)
- send_stats "로컬 비밀 키를보십시오"
- echo "------------------------"
- echo "공개 키 정보"
- cat ~/.ssh/authorized_keys
- echo "------------------------"
- echo "개인 키 정보"
- cat ~/.ssh/sshkey
- echo "------------------------"
- break_end
-
- ;;
- *)
- break # 跳出循环,退出菜单
- ;;
- esac
- done
-
+ sshkey_panel
;;
25)
root_use
- send_stats "전보 경고"
- echo "TG-BOT 모니터링 및 조기 경고 기능"
- echo "비디오 소개 : https://youtu.be/vll-eb3z_ty"
+ send_stats "전신 경고"
+ echo "TG-bot 모니터링 및 조기경보 기능"
+ echo "영상소개: https://youtu.be/vLL-eb3Z_TY"
echo "------------------------------------------------"
- echo "기본 CPU, 메모리, 하드 디스크, 트래픽 및 SSH 로그인의 실시간 모니터링 및 조기 경고를 실현하려면 조기 경고를 받으려면 TG Robot API 및 사용자 ID를 구성해야합니다."
- echo "임계 값에 도달하면 사용자가 사용자에게 전송됩니다."
- echo -e "${gl_hui}- 트래픽과 관련하여 서버를 다시 시작하면 다시 계산됩니다.${gl_bai}"
- read -e -p "계속할거야? (Y/N) :" choice
+ echo "로컬 CPU, 메모리, 하드 디스크, 트래픽 및 SSH 로그인에 대한 실시간 모니터링 및 경고를 달성하려면 경고를 수신하도록 tg 로봇 API 및 사용자 ID를 구성해야 합니다."
+ echo "임계값에 도달하면 경고 메시지가 사용자에게 전송됩니다."
+ echo -e "${gl_hui}- 트래픽에 관해서는 서버를 다시 시작하면 다시 계산됩니다 -${gl_bai}"
+ read -e -p "계속하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
- send_stats "전보 경고가 활성화되었습니다"
+ send_stats "텔레그램 경고 활성화됨"
cd ~
install nano tmux bc jq
check_crontab_installed
@@ -11768,7 +20982,7 @@ EOF
sed -i "4i$(grep '^CHAT_ID=' ~/TG-check-notify.sh)" TG-SSH-check-notify.sh
chmod +x ~/TG-SSH-check-notify.sh
- # ~/.profile 파일에 추가하십시오
+ # ~/.profile 파일에 추가
if ! grep -q 'bash ~/TG-SSH-check-notify.sh' ~/.profile > /dev/null 2>&1; then
echo 'bash ~/TG-SSH-check-notify.sh' >> ~/.profile
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -11779,21 +20993,21 @@ EOF
source ~/.profile
clear
- echo "TG-BOT 조기 경고 시스템이 시작되었습니다"
- echo -e "${gl_hui}다른 시스템의 루트 디렉토리에 TG-Check-Notify.sh 경고 파일을 배치하고 직접 사용할 수도 있습니다!${gl_bai}"
+ echo "TG-bot 조기경보 시스템이 활성화되었습니다."
+ echo -e "${gl_hui}TG-check-notify.sh 경고 파일을 다른 컴퓨터의 루트 디렉터리에 넣고 직접 사용할 수도 있습니다!${gl_bai}"
;;
[Nn])
echo "취소"
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
26)
root_use
- send_stats "SSH에서 고위험 취약점을 수정하십시오"
+ send_stats "고위험 SSH 취약점 수정"
cd ~
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/upgrade_openssh9.8p1.sh
chmod +x ~/upgrade_openssh9.8p1.sh
@@ -11837,7 +21051,7 @@ EOF
;;
37)
clear
- send_stats "명령 줄 기록"
+ send_stats "명령줄 기록"
get_history_file() {
for file in "$HOME"/.bash_history "$HOME"/.ash_history "$HOME"/.zsh_history "$HOME"/.local/share/fish/fish_history; do
[ -f "$file" ] && { echo "$file"; return; }
@@ -11853,98 +21067,117 @@ EOF
;;
+ 39)
+ clear
+ linux_fav
+ ;;
+
+ 40)
+ clear
+ net_menu
+ ;;
+
41)
+ clear
+ log_menu
+ ;;
+
+ 42)
+ clear
+ env_menu
+ ;;
+
+
+ 61)
clear
- send_stats "게시판"
- echo "Technology Lion 게시판은 공식 커뮤니티로 옮겨졌습니다! 공식 커뮤니티에 메시지를 남겨주세요!"
- echo "https://bbs.kejilion.pro/"
+ send_stats "메시지 보드"
+ echo "Technology Lion 공식 게시판을 방문해 보세요. 스크립트에 대한 아이디어가 있으시면 교환 메시지를 남겨주세요!"
+ echo "https://board.kejilion.pro"
+ echo "공개 비밀번호: kejilion.sh"
;;
66)
root_use
- send_stats "원 스톱 튜닝"
- echo "원 스톱 시스템 최적화"
+ send_stats "원스톱 튜닝"
+ echo "원스톱 시스템 튜닝"
echo "------------------------------------------------"
- echo "다음은 작동하고 최적화됩니다"
- echo "1. 시스템을 최신으로 업데이트하십시오"
+ echo "다음 콘텐츠가 운영 및 최적화됩니다."
+ echo "1. 시스템 업데이트 소스를 최적화하고 시스템을 최신 버전으로 업데이트하세요."
echo "2. 시스템 정크 파일 정리"
- echo -e "3. 가상 메모리를 설정하십시오${gl_huang}1G${gl_bai}"
- echo -e "4. SSH 포트 번호를 설정하십시오${gl_huang}5522${gl_bai}"
- echo -e "5. 모든 포트를 엽니 다"
- echo -e "6. 켜십시오${gl_huang}BBR${gl_bai}가속"
- echo -e "7. 시간대를 설정하십시오${gl_huang}상하이${gl_bai}"
- echo -e "8. DNS 주소를 자동으로 최적화합니다${gl_huang}해외 : 1.1.1.1 8.8.8.8 국내 : 223.5.5.5${gl_bai}"
- echo -e "9. 기본 도구를 설치하십시오${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
- echo -e "10. Linux 시스템에서 커널 매개 변수 최적화로 전환하십시오${gl_huang}균형 최적화 모드${gl_bai}"
+ echo -e "3. 가상 메모리 설정${gl_huang}1G${gl_bai}"
+ echo -e "4. SSH 포트 번호를 다음으로 설정합니다.${gl_huang}5522${gl_bai}"
+ echo -e "5. SSH 무차별 대입 크래킹을 방어하기 위해 fall2ban을 시작하세요."
+ echo -e "6. 모든 포트를 엽니다"
+ echo -e "7. 켜다${gl_huang}BBR${gl_bai}가속하다"
+ echo -e "8. 시간대를 다음으로 설정합니다.${gl_huang}상하이${gl_bai}"
+ echo -e "9. DNS 주소 자동 최적화${gl_huang}해외: 1.1.1.1 8.8.8.8 국내: 223.5.5.5${gl_bai}"
+ echo -e "10. 네트워크를 다음으로 설정합니다.${gl_huang}IPv4 우선순위${gl_bai}"
+ echo -e "11. 기본 도구 설치${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "12. Linux 시스템 커널 매개변수 최적화${gl_huang}네트워크 환경에 따라 자동으로 튜닝${gl_bai}"
echo "------------------------------------------------"
- read -e -p "한 번의 클릭 유지 보수가 있습니까? (Y/N) :" choice
+ read -e -p "원클릭 유지 관리를 원하시나요? (예/아니요):" choice
case "$choice" in
[Yy])
clear
- send_stats "원 스톱 튜닝 시작"
+ send_stats "원스톱 튜닝 시작"
echo "------------------------------------------------"
+ switch_mirror false false
linux_update
- echo -e "[${gl_lv}OK${gl_bai}] 1/10. 시스템을 최신으로 업데이트하십시오"
+ echo -e "[${gl_lv}OK${gl_bai}] 1/12. 시스템을 최신으로 업데이트하세요"
echo "------------------------------------------------"
linux_clean
- echo -e "[${gl_lv}OK${gl_bai}] 2/10. 시스템 정크 파일 정리"
+ echo -e "[${gl_lv}OK${gl_bai}] 2/12. 시스템 정크 파일 정리"
echo "------------------------------------------------"
add_swap 1024
- echo -e "[${gl_lv}OK${gl_bai}] 3/10. 가상 메모리를 설정합니다${gl_huang}1G${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 3/12. 가상 메모리 설정${gl_huang}1G${gl_bai}"
echo "------------------------------------------------"
- local new_port=5522
- new_ssh_port
- echo -e "[${gl_lv}OK${gl_bai}] 4/10. SSH 포트 번호를 설정하십시오${gl_huang}5522${gl_bai}"
+ new_ssh_port 5522
+ echo -e "[${gl_lv}OK${gl_bai}] 4/12. SSH 포트 번호를 다음으로 설정합니다.${gl_huang}5522${gl_bai}"
+ echo "------------------------------------------------"
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ echo -e "[${gl_lv}OK${gl_bai}] 5/12. SSH 무차별 대입 크래킹을 방어하려면 Fail2ban을 시작하세요."
+
echo "------------------------------------------------"
- echo -e "[${gl_lv}OK${gl_bai}] 5/10. 모든 포트를 엽니 다"
+ echo -e "[${gl_lv}OK${gl_bai}] 6/12. 모든 포트 열기"
echo "------------------------------------------------"
bbr_on
- echo -e "[${gl_lv}OK${gl_bai}] 6/10. 열려 있는${gl_huang}BBR${gl_bai}가속"
+ echo -e "[${gl_lv}OK${gl_bai}] 7/12. 열려 있는${gl_huang}BBR${gl_bai}가속하다"
echo "------------------------------------------------"
set_timedate Asia/Shanghai
- echo -e "[${gl_lv}OK${gl_bai}] 7/10. 시간대를 설정하십시오${gl_huang}상하이${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 8/12. 시간대를 다음으로 설정하세요.${gl_huang}상하이${gl_bai}"
echo "------------------------------------------------"
- local country=$(curl -s ipinfo.io/country)
- if [ "$country" = "CN" ]; then
- local dns1_ipv4="223.5.5.5"
- local dns2_ipv4="183.60.83.19"
- local dns1_ipv6="2400:3200::1"
- local dns2_ipv6="2400:da00::6666"
- else
- local dns1_ipv4="1.1.1.1"
- local dns2_ipv4="8.8.8.8"
- local dns1_ipv6="2606:4700:4700::1111"
- local dns2_ipv6="2001:4860:4860::8888"
- fi
-
- set_dns
- echo -e "[${gl_lv}OK${gl_bai}] 8/10. DNS 주소를 자동으로 최적화합니다${gl_huang}${gl_bai}"
+ auto_optimize_dns
+ echo -e "[${gl_lv}OK${gl_bai}] 9/12. DNS 주소 자동 최적화${gl_huang}${gl_bai}"
+ echo "------------------------------------------------"
+ prefer_ipv4
+ echo -e "[${gl_lv}OK${gl_bai}] 10/12. 네트워크를 다음으로 설정하세요.${gl_huang}IPv4 우선순위${gl_bai}}"
echo "------------------------------------------------"
install_docker
install wget sudo tar unzip socat btop nano vim
- echo -e "[${gl_lv}OK${gl_bai}] 9/10. 기본 도구를 설치하십시오${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 11/12. 기본 도구 설치${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
echo "------------------------------------------------"
- echo "------------------------------------------------"
- optimize_balanced
- echo -e "[${gl_lv}OK${gl_bai}] 10/10. Linux 시스템의 커널 매개 변수 최적화"
- echo -e "${gl_lv}원 스톱 시스템 튜닝이 완료되었습니다${gl_bai}"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ echo -e "[${gl_lv}OK${gl_bai}] 12/12. Linux 시스템 커널 매개변수 최적화"
+ echo -e "${gl_lv}원스톱 시스템 튜닝이 완료되었습니다${gl_bai}"
;;
[Nn])
echo "취소"
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
@@ -11952,7 +21185,7 @@ EOF
99)
clear
- send_stats "시스템을 다시 시작하십시오"
+ send_stats "시스템을 다시 시작하세요"
server_reboot
;;
100)
@@ -11961,39 +21194,39 @@ EOF
while true; do
clear
if grep -q '^ENABLE_STATS="true"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_lv}正在采集数据${gl_bai}"
+ local status_message="${gl_lv}데이터 수집${gl_bai}"
elif grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_hui}采集已关闭${gl_bai}"
+ local status_message="${gl_hui}컬렉션이 닫혔습니다.${gl_bai}"
else
- local status_message="无法确定的状态"
+ local status_message="불확실한 상태"
fi
- echo "개인 정보 및 보안"
- echo "스크립트는 사용자 기능에 대한 데이터를 수집하고 스크립트 경험을 최적화하며보다 재미 있고 유용한 기능을 만듭니다."
- echo "스크립트 버전 번호, 사용 시간, 시스템 버전, CPU 아키텍처, 기계 국가 및 사용 된 기능의 이름을 수집합니다."
+ echo "개인 정보 보호 및 보안"
+ echo "스크립트는 사용자의 기능 사용에 대한 데이터를 수집하고 스크립트 경험을 최적화하며 더 재미 있고 유용한 기능을 만듭니다."
+ echo "스크립트 버전 번호, 사용 시간, 시스템 버전, CPU 아키텍처, 시스템 국가 및 사용된 기능 이름이 수집됩니다."
echo "------------------------------------------------"
- echo -e "현재 상태 :$status_message"
+ echo -e "현재 상태:$status_message"
echo "--------------------"
- echo "1. 수집을 켭니다"
- echo "2. 컬렉션을 닫습니다"
+ echo "1. 수집 시작"
+ echo "2. 수집 종료"
echo "--------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "--------------------"
- read -e -p "선택을 입력하십시오 :" sub_choice
+ read -e -p "선택사항을 입력하세요:" sub_choice
case $sub_choice in
1)
cd ~
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' ~/kejilion.sh
- echo "수집이 활성화되었습니다"
- send_stats "개인 정보 보호 및 보안 컬렉션이 활성화되었습니다"
+ echo "수집이 시작되었습니다"
+ send_stats "개인정보 보호 및 보안 수집이 사용 설정되었습니다."
;;
2)
cd ~
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
- echo "컬렉션이 닫혔습니다"
- send_stats "개인 정보 보호 및 보안이 컬렉션을 위해 마감되었습니다"
+ echo "컬렉션이 닫혔습니다."
+ send_stats "개인정보 보호 및 보안 수집이 사용 중지되었습니다."
;;
*)
break
@@ -12009,11 +21242,11 @@ EOF
102)
clear
- send_stats "기술 라이온 스크립트를 제거하십시오"
- echo "기술 라이온 스크립트를 제거하십시오"
+ send_stats "기술 사자 스크립트 제거"
+ echo "기술 사자 스크립트 제거"
echo "------------------------------------------------"
- echo "Kejilion 스크립트를 완전히 제거하고 다른 기능에 영향을 미치지 않습니다."
- read -e -p "계속할거야? (Y/N) :" choice
+ echo "kejilion 스크립트는 다른 기능에 영향을 주지 않고 완전히 제거됩니다."
+ read -e -p "계속하시겠습니까? (예/아니요):" choice
case "$choice" in
[Yy])
@@ -12021,7 +21254,7 @@ EOF
(crontab -l | grep -v "kejilion.sh") | crontab -
rm -f /usr/local/bin/k
rm ~/kejilion.sh
- echo "대본은 제거되었습니다."
+ echo "스크립트가 제거되었습니다. 안녕!"
break_end
clear
exit
@@ -12030,7 +21263,7 @@ EOF
echo "취소"
;;
*)
- echo "잘못된 선택, y 또는 N을 입력하십시오."
+ echo "선택이 잘못되었습니다. Y 또는 N을 입력하세요."
;;
esac
;;
@@ -12040,7 +21273,7 @@ EOF
;;
*)
- echo "잘못된 입력!"
+ echo "입력이 잘못되었습니다!"
;;
esac
break_end
@@ -12068,177 +21301,164 @@ linux_file() {
echo "------------------------"
ls --color=auto -x
echo "------------------------"
- echo "1. 디렉토리를 입력합니다. 2. 디렉토리 만들기 3. 디렉토리 권한 수정 4. 디렉토리 이름 바꾸기"
- echo "5. 디렉토리 삭제 6. 이전 메뉴 디렉토리로 돌아갑니다."
+ echo "1. 디렉터리 입력 2. 디렉터리 생성 3. 디렉터리 권한 수정 4. 디렉터리 이름 바꾸기"
+ echo "5. 디렉토리 삭제 6. 이전 메뉴 디렉토리로 복귀"
echo "------------------------"
- echo "11. 파일 만들기 12. 파일 편집 13. 파일 권한 수정 14. 파일 이름 바꾸기"
- echo "15. 파일을 삭제하십시오"
+ echo "11. 파일 생성 12. 파일 편집 13. 파일 권한 수정 14. 파일 이름 바꾸기"
+ echo "15. 파일 삭제"
echo "------------------------"
- echo "21. 파일 디렉토리 압축 22. zip 파일 디렉토리 23. 파일 디렉토리 이동 24. 파일 디렉토리 복사"
- echo "25. 파일을 다른 서버로 전달합니다"
+ echo "21. 파일 디렉터리 압축 22. 파일 디렉터리 압축 풀기 23. 파일 디렉터리 이동 24. 파일 디렉터리 복사"
+ echo "25. 다른 서버로 파일 전송"
echo "------------------------"
- echo "0. 이전 메뉴로 돌아갑니다"
+ echo "0. 이전 메뉴로 돌아가기"
echo "------------------------"
- read -e -p "선택을 입력하십시오 :" Limiting
+ read -e -p "선택사항을 입력하세요:" Limiting
case "$Limiting" in
1) # 进入目录
- read -e -p "디렉토리 이름을 입력하십시오 :" dirname
- cd "$dirname" 2>/dev/null || echo "디렉토리에 입력 할 수 없습니다"
- send_stats "디렉토리로 이동하십시오"
+ read -e -p "디렉토리 이름을 입력하십시오:" dirname
+ cd "$dirname" 2>/dev/null || echo "디렉토리에 들어갈 수 없습니다"
+ send_stats "디렉토리 입력"
;;
2) # 创建目录
- read -e -p "작성하려면 디렉토리 이름을 입력하십시오." dirname
- mkdir -p "$dirname" && echo "디렉토리가 생성되었습니다" || echo "창조가 실패했습니다"
- send_stats "디렉토리를 만듭니다"
+ read -e -p "생성할 디렉터리 이름을 입력하세요." dirname
+ mkdir -p "$dirname" && echo "디렉터리가 생성되었습니다." || echo "생성 실패"
+ send_stats "디렉터리 생성"
;;
3) # 修改目录权限
- read -e -p "디렉토리 이름을 입력하십시오 :" dirname
- read -e -p "권한을 입력하십시오 (예 : 755) :" perm
- chmod "$perm" "$dirname" && echo "권한이 수정되었습니다" || echo "수정이 실패했습니다"
- send_stats "디렉토리 권한을 수정하십시오"
+ read -e -p "디렉토리 이름을 입력하십시오:" dirname
+ read -e -p "권한을 입력하세요(예: 755):" perm
+ chmod "$perm" "$dirname" && echo "권한이 수정되었습니다." || echo "수정 실패"
+ send_stats "디렉터리 권한 수정"
;;
4) # 重命名目录
- read -e -p "현재 디렉토리 이름을 입력하십시오 :" current_name
- read -e -p "새 디렉토리 이름을 입력하십시오 :" new_name
- mv "$current_name" "$new_name" && echo "디렉토리의 이름이 바뀌 었습니다" || echo "이름 바꾸지 실패했습니다"
- send_stats "디렉토리의 이름을 바꿉니다"
+ read -e -p "현재 디렉터리 이름을 입력하세요." current_name
+ read -e -p "새 디렉터리 이름을 입력하세요." new_name
+ mv "$current_name" "$new_name" && echo "디렉터리 이름이 변경되었습니다." || echo "이름 바꾸기 실패"
+ send_stats "디렉터리 이름 바꾸기"
;;
5) # 删除目录
- read -e -p "삭제하려면 디렉토리 이름을 입력하십시오." dirname
- rm -rf "$dirname" && echo "디렉토리가 삭제되었습니다" || echo "삭제가 실패했습니다"
+ read -e -p "삭제할 디렉터리 이름을 입력하세요:" dirname
+ rm -rf "$dirname" && echo "디렉터리가 삭제되었습니다." || echo "삭제 실패"
send_stats "디렉토리 삭제"
;;
6) # 返回上一级选单目录
cd ..
- send_stats "이전 메뉴 디렉토리로 돌아갑니다"
+ send_stats "이전 메뉴 디렉토리로 돌아가기"
;;
11) # 创建文件
- read -e -p "작성하려면 파일 이름을 입력하십시오." filename
- touch "$filename" && echo "생성 된 파일" || echo "창조가 실패했습니다"
- send_stats "파일을 만듭니다"
+ read -e -p "생성할 파일 이름을 입력하세요:" filename
+ touch "$filename" && echo "파일이 생성되었습니다." || echo "생성 실패"
+ send_stats "파일 생성"
;;
12) # 编辑文件
- read -e -p "편집 할 파일 이름을 입력하십시오." filename
+ read -e -p "편집할 파일 이름을 입력하십시오:" filename
install nano
nano "$filename"
send_stats "파일 편집"
;;
13) # 修改文件权限
- read -e -p "파일 이름을 입력하십시오 :" filename
- read -e -p "권한을 입력하십시오 (예 : 755) :" perm
- chmod "$perm" "$filename" && echo "권한이 수정되었습니다" || echo "수정이 실패했습니다"
- send_stats "파일 권한을 수정하십시오"
+ read -e -p "파일 이름을 입력하세요:" filename
+ read -e -p "권한을 입력하세요(예: 755):" perm
+ chmod "$perm" "$filename" && echo "권한이 수정되었습니다." || echo "수정 실패"
+ send_stats "파일 권한 수정"
;;
14) # 重命名文件
- read -e -p "현재 파일 이름을 입력하십시오 :" current_name
- read -e -p "새 파일 이름을 입력하십시오 :" new_name
- mv "$current_name" "$new_name" && echo "이름이 바뀌 었습니다" || echo "이름 바꾸지 실패했습니다"
- send_stats "파일의 이름을 바꿉니다"
+ read -e -p "현재 파일 이름을 입력하십시오:" current_name
+ read -e -p "새 파일 이름을 입력하세요:" new_name
+ mv "$current_name" "$new_name" && echo "파일 이름이 변경되었습니다." || echo "이름 바꾸기 실패"
+ send_stats "파일 이름 바꾸기"
;;
15) # 删除文件
- read -e -p "삭제하려면 파일 이름을 입력하십시오." filename
- rm -f "$filename" && echo "파일이 삭제되었습니다" || echo "삭제가 실패했습니다"
+ read -e -p "삭제할 파일 이름을 입력하세요:" filename
+ rm -f "$filename" && echo "파일이 삭제되었습니다." || echo "삭제 실패"
send_stats "파일 삭제"
;;
21) # 压缩文件/目录
- read -e -p "압축 할 파일/디렉토리 이름을 입력하십시오." name
+ read -e -p "압축할 파일/디렉터리 이름을 입력하십시오:" name
install tar
- tar -czvf "$name.tar.gz" "$name" && echo "압축$name.tar.gz" || echo "압축이 실패했습니다"
- send_stats "압축 파일/디렉토리"
+ tar -czvf "$name.tar.gz" "$name" && echo "압축$name.tar.gz" || echo "압축 실패"
+ send_stats "압축된 파일/디렉토리"
;;
22) # 解压文件/目录
- read -e -p "파일 이름 (.tar.gz)을 입력하십시오." filename
+ read -e -p "추출할 파일 이름(.tar.gz)을 입력하십시오." filename
install tar
- tar -xzvf "$filename" && echo "압축 압축$filename" || echo "감압이 실패했습니다"
- send_stats "압축 파일/디렉토리 해제"
+ tar -xzvf "$filename" && echo "압축이 풀렸습니다.$filename" || echo "압축 해제 실패"
+ send_stats "파일/디렉토리 압축 풀기"
;;
23) # 移动文件或目录
- read -e -p "이동하려면 파일 또는 디렉토리 경로를 입력하십시오." src_path
+ read -e -p "이동할 파일 또는 디렉터리 경로를 입력하세요." src_path
if [ ! -e "$src_path" ]; then
- echo "오류 : 파일 또는 디렉토리가 존재하지 않습니다."
- send_stats "파일 또는 디렉토리를 이동하지 못했습니다 : 파일 또는 디렉토리가 존재하지 않습니다."
+ echo "오류: 파일 또는 디렉터리가 존재하지 않습니다."
+ send_stats "파일 또는 디렉터리 이동 실패: 파일 또는 디렉터리가 존재하지 않습니다."
continue
fi
- read -e -p "대상 경로를 입력하십시오 (새 파일 이름 또는 디렉토리 이름 포함) :" dest_path
+ read -e -p "대상 경로(새 파일 또는 디렉터리 이름 포함)를 입력하세요." dest_path
if [ -z "$dest_path" ]; then
- echo "오류 : 대상 경로를 입력하십시오."
- send_stats "움직이는 파일 또는 디렉토리 실패 : 대상 경로가 지정되지 않았습니다."
+ echo "오류: 대상 경로를 입력하십시오."
+ send_stats "파일 또는 디렉터리 이동 실패: 대상 경로가 지정되지 않았습니다."
continue
fi
- mv "$src_path" "$dest_path" && echo "파일 또는 디렉토리가 이동했습니다$dest_path" || echo "파일이나 디렉토리를 이동하지 못했습니다"
- send_stats "파일 또는 디렉토리를 이동하십시오"
+ mv "$src_path" "$dest_path" && echo "파일 또는 디렉토리가 다음으로 이동되었습니다.$dest_path" || echo "파일 또는 디렉터리를 이동하지 못했습니다."
+ send_stats "파일 또는 디렉터리 이동"
;;
24) # 复制文件目录
- read -e -p "복사 할 파일 또는 디렉토리 경로를 입력하십시오." src_path
+ read -e -p "복사할 파일 또는 디렉터리 경로를 입력하세요." src_path
if [ ! -e "$src_path" ]; then
- echo "오류 : 파일 또는 디렉토리가 존재하지 않습니다."
- send_stats "파일 또는 디렉토리를 복사하지 못했습니다 : 파일 또는 디렉토리가 존재하지 않습니다."
+ echo "오류: 파일 또는 디렉터리가 존재하지 않습니다."
+ send_stats "파일 또는 디렉터리 복사 실패: 파일 또는 디렉터리가 존재하지 않습니다."
continue
fi
- read -e -p "대상 경로를 입력하십시오 (새 파일 이름 또는 디렉토리 이름 포함) :" dest_path
+ read -e -p "대상 경로(새 파일 또는 디렉터리 이름 포함)를 입력하세요." dest_path
if [ -z "$dest_path" ]; then
- echo "오류 : 대상 경로를 입력하십시오."
- send_stats "파일 또는 디렉토리 복사에 실패 : 지정되지 않은 대상 경로"
+ echo "오류: 대상 경로를 입력하십시오."
+ send_stats "파일 또는 디렉터리 복사 실패: 대상 경로가 지정되지 않았습니다."
continue
fi
- # -r 옵션을 사용하여 디렉토리를 재귀 적으로 복사하십시오
- cp -r "$src_path" "$dest_path" && echo "파일 또는 디렉토리가 복사되었습니다$dest_path" || echo "파일이나 디렉토리를 복사하지 못했습니다"
- send_stats "파일 또는 디렉토리를 복사하십시오"
+ # 디렉토리를 반복적으로 복사하려면 -r 옵션을 사용하십시오.
+ cp -r "$src_path" "$dest_path" && echo "복사된 파일 또는 디렉터리$dest_path" || echo "파일 또는 디렉터리를 복사하지 못했습니다."
+ send_stats "파일 또는 디렉터리 복사"
;;
25) # 传送文件至远端服务器
- read -e -p "전송할 파일 경로를 입력하십시오." file_to_transfer
+ read -e -p "전송할 파일 경로를 입력하십시오:" file_to_transfer
if [ ! -f "$file_to_transfer" ]; then
- echo "오류 : 파일이 존재하지 않습니다."
- send_stats "파일을 전송하지 못했습니다 : 파일이 존재하지 않습니다."
+ echo "오류: 파일이 존재하지 않습니다."
+ send_stats "파일 전송 실패: 파일이 존재하지 않습니다."
continue
fi
- read -e -p "원격 서버 IP를 입력하십시오 :" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "오류 : 원격 서버 IP를 입력하십시오."
- send_stats "파일 전송 실패 : 원격 서버 IP가 입력되지 않았습니다"
- continue
- fi
-
- read -e -p "원격 서버 사용자 이름 (기본 루트)을 입력하십시오." remote_user
- remote_user=${remote_user:-root}
-
- read -e -p "원격 서버 비밀번호를 입력하십시오 :" -s remote_password
- echo
- if [ -z "$remote_password" ]; then
- echo "오류 : 원격 서버 비밀번호를 입력하십시오."
- send_stats "파일 전송 실패 : 원격 서버 비밀번호를 입력하지 않았습니다"
- continue
- fi
+ kj_ssh_read_host_user_port "원격 서버 IP를 입력하세요:" "원격 서버 사용자 이름(기본 루트)을 입력하십시오:" "로그인 포트(기본값 22)를 입력하세요." "root" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local remote_user="$KJ_SSH_USER"
+ local remote_port="$KJ_SSH_PORT"
- read -e -p "로그인 포트 (기본값 22)를 입력하십시오 :" remote_port
- remote_port=${remote_port:-22}
+ kj_ssh_read_password "원격 서버 비밀번호를 입력하세요:"
+ local remote_password="$KJ_SSH_PASSWORD"
- # 알려진 호스트를위한 오래된 항목을 명확하게합니다
+ # 알려진 호스트에 대한 이전 항목 지우기
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 等待时间
- # SCP를 사용하여 파일을 전송합니다
+ # scp를 사용하여 파일 전송
scp -P "$remote_port" -o StrictHostKeyChecking=no "$file_to_transfer" "$remote_user@$remote_ip:/home/" </dev/null
+
+ # 임시 파일로 다운로드 후 확인 후 교체
+ local tmp_file=$(mktemp ~/kejilion_tmp.XXXXXX)
+ if curl -sS --max-time 60 --fail -o "$tmp_file" "$download_url" && \
+ [ -s "$tmp_file" ] && \
+ head -1 "$tmp_file" | grep -q '^#!/bin/bash'; then
+ chmod +x "$tmp_file"
+ mv -f "$tmp_file" ~/kejilion.sh
+ canshu_v6
+ CheckFirstRun_true
+ yinsiyuanquan2
+ cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
+ echo -e "${gl_lv}스크립트가 최신 버전으로 업데이트되었습니다!${gl_huang}v$sh_v_new${gl_bai}"
+ send_stats "스크립트가 최신 상태입니다.$sh_v_new"
else
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh
+ rm -f "$tmp_file"
+ # 백업 복원
+ if [ -f ~/kejilion.sh.bak ]; then
+ mv -f ~/kejilion.sh.bak ~/kejilion.sh
+ fi
+ echo -e "${gl_hong}업데이트에 실패했습니다! 다운로드 중 오류가 발생했거나 파일 확인에 실패했습니다. 원본 버전이 복원되었습니다.${gl_bai}"
+ send_stats "스크립트 업데이트 실패"
fi
- canshu_v6
- CheckFirstRun_true
- yinsiyuanquan2
- cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
- echo -e "${gl_lv}스크립트는 최신 버전으로 업데이트되었습니다!${gl_huang}v$sh_v_new${gl_bai}"
- send_stats "스크립트는 최신입니다$sh_v_new"
break_end
~/kejilion.sh
exit
;;
2)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
local ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
+ local cron_proxy cron_sed_cmd
if [ "$country" = "CN" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ~/kejilion.sh"
elif [ -n "$ipv6_address" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ~/kejilion.sh"
else
- SH_Update_task="curl -sS -O https://raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh"
+ cron_proxy="https://"
+ cron_sed_cmd=""
+ fi
+
+ # 강력한 자동 업데이트 명령 구축: 임시 파일로 다운로드 → 확인 → 백업 → 교체 → 로컬 설정 복원 → 배포
+ SH_Update_task="cd ~ && tmp=\$(mktemp ~/kejilion_tmp.XXXXXX) && curl -sS --max-time 60 --fail -o \"\$tmp\" ${cron_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && [ -s \"\$tmp\" ] && head -1 \"\$tmp\" | grep -q '^#!/bin/bash' && cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null && chmod +x \"\$tmp\" && mv -f \"\$tmp\" ~/kejilion.sh"
+ # 추가 설정 복구
+ if [ -n "$cron_sed_cmd" ]; then
+ SH_Update_task="$SH_Update_task && $cron_sed_cmd"
fi
+ # 이전 스크립트에서Permission_granted 및 ENABLE_STATS 설정을 복원합니다.
+ SH_Update_task="$SH_Update_task && grep -q 'permission_granted=\"true\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/permission_granted=\"false\"/permission_granted=\"true\"/' ~/kejilion.sh; grep -q 'ENABLE_STATS=\"false\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/ENABLE_STATS=\"true\"/ENABLE_STATS=\"false\"/' ~/kejilion.sh"
+ # /usr/local/bin/k 및 /usr/bin/k에 배포
+ SH_Update_task="$SH_Update_task; cp -f ~/kejilion.sh /usr/local/bin/k 2>/dev/null; ln -sf /usr/local/bin/k /usr/bin/k 2>/dev/null"
+ # 다운로드 실패 시 임시 파일 정리
+ SH_Update_task="$SH_Update_task || rm -f \"\$tmp\" 2>/dev/null"
+
check_crontab_installed
(crontab -l | grep -v "kejilion.sh") | crontab -
- # (crontab -l 2>/dev/null; echo "0 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- echo -e "${gl_lv}자동 업데이트가 활성화되고 스크립트는 매일 오전 2시에 자동으로 업데이트됩니다!${gl_bai}"
- send_stats "자동 스크립트 업데이트를 켜십시오"
+ (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c '$SH_Update_task'") | crontab -
+ echo -e "${gl_lv}자동 업데이트가 켜져 있고 매일 새벽 2시에 스크립트가 자동으로 업데이트됩니다!${gl_bai}"
+ send_stats "자동 스크립트 업데이트 활성화"
break_end
;;
3)
clear
(crontab -l | grep -v "kejilion.sh") | crontab -
- echo -e "${gl_lv}자동 업데이트가 닫힙니다${gl_bai}"
- send_stats "스크립트 자동 업데이트를 닫습니다"
+ echo -e "${gl_lv}자동 업데이트가 꺼졌습니다${gl_bai}"
+ send_stats "자동 스크립트 업데이트 끄기"
break_end
;;
*)
@@ -12566,8 +21886,8 @@ echo -e "${gl_kjlan}"
echo "╦╔═╔═╗ ╦╦╦ ╦╔═╗╔╗╔ ╔═╗╦ ╦"
echo "╠╩╗║╣ ║║║ ║║ ║║║║ ╚═╗╠═╣"
echo "╩ ╩╚═╝╚╝╩╩═╝╩╚═╝╝╚╝o╚═╝╩ ╩"
-echo -e "기술 라이온 스크립트 도구 상자 v$sh_v"
-echo -e "명령 줄 입력${gl_huang}k${gl_kjlan}스크립트를 신속하게 시작하십시오${gl_bai}"
+echo -e "기술 사자 스크립트 도구 상자 v$sh_v"
+echo -e "명령줄 입력${gl_huang}k${gl_kjlan}빠른 시작 스크립트${gl_bai}"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}1. ${gl_bai}시스템 정보 쿼리"
echo -e "${gl_kjlan}2. ${gl_bai}시스템 업데이트"
@@ -12576,25 +21896,24 @@ echo -e "${gl_kjlan}4. ${gl_bai}기본 도구"
echo -e "${gl_kjlan}5. ${gl_bai}BBR 관리"
echo -e "${gl_kjlan}6. ${gl_bai}도커 관리"
echo -e "${gl_kjlan}7. ${gl_bai}워프 관리"
-echo -e "${gl_kjlan}8. ${gl_bai}스크립트 수집 테스트"
+echo -e "${gl_kjlan}8. ${gl_bai}테스트 스크립트 수집"
echo -e "${gl_kjlan}9. ${gl_bai}Oracle Cloud 스크립트 컬렉션"
-echo -e "${gl_huang}10. ${gl_bai}LDNMP 웹 사이트 구축"
-echo -e "${gl_kjlan}11. ${gl_bai}응용 프로그램 시장"
-echo -e "${gl_kjlan}12. ${gl_bai}백엔드 작업 공간"
+echo -e "${gl_huang}10. ${gl_bai}LDNMP 웹사이트 구축"
+echo -e "${gl_kjlan}11. ${gl_bai}응용 시장"
+echo -e "${gl_kjlan}12. ${gl_bai}백엔드 작업공간"
echo -e "${gl_kjlan}13. ${gl_bai}시스템 도구"
echo -e "${gl_kjlan}14. ${gl_bai}서버 클러스터 제어"
-echo -e "${gl_kjlan}15. ${gl_bai}광고 열"
-echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}p. ${gl_bai}Phantom Beast Palu 서버 오프닝 스크립트"
+echo -e "${gl_kjlan}15. ${gl_bai}광고 칼럼"
+echo -e "${gl_kjlan}16. ${gl_bai}게임 서버 오프닝 스크립트 모음"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}00. ${gl_bai}스크립트 업데이트"
echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}0. ${gl_bai}종료 스크립트"
+echo -e "${gl_kjlan}0. ${gl_bai}스크립트 종료"
echo -e "${gl_kjlan}------------------------${gl_bai}"
-read -e -p "선택을 입력하십시오 :" choice
+read -e -p "선택사항을 입력하세요:" choice
case $choice in
- 1) linux_ps ;;
+ 1) linux_info ;;
2) clear ; send_stats "시스템 업데이트" ; linux_update ;;
3) clear ; send_stats "시스템 정리" ; linux_clean ;;
4) linux_tools ;;
@@ -12611,13 +21930,10 @@ case $choice in
13) linux_Settings ;;
14) linux_cluster ;;
15) kejilion_Affiliates ;;
- p) send_stats "Phantom Beast Palu 서버 오프닝 스크립트" ; cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
- exit
- ;;
+ 16) games_server_tools ;;
00) kejilion_update ;;
0) clear ; exit ;;
- *) echo "잘못된 입력!" ;;
+ *) echo "입력이 잘못되었습니다!" ;;
esac
break_end
done
@@ -12625,67 +21941,76 @@ done
k_info() {
-send_stats "K 명령 참조 사용 사례"
+send_stats "k 명령 참조 예"
echo "-------------------"
-echo "비디오 소개 : https://www.bilibili.com/video/bv1ib421e7it?t=0.1"
-echo "다음은 K 명령 참조 유스 케이스입니다."
-echo "스크립트 시작 k"
-echo "소프트웨어 패키지 설치 K 설치 나노 wget | K 추가 나노 wget | K Nano wget을 설치하십시오"
-echo "패키지 k 제거 나노 wget | K del nano wget | k 제거 나노 wget | K를 제거하십시오"
-echo "업데이트 시스템 K 업데이트 | K 업데이트"
-echo "깨끗한 시스템 쓰레기 K Clean | K 청소"
-echo "시스템 패널 k dd |를 다시 설치하십시오 K 재설치"
-echo "BBR3 제어판 K BBR3 | K bbrv3"
-echo "커널 튜닝 패널 K nhyh | K 커널 최적화"
-echo "가상 메모리 K 스왑 2048을 설정하십시오"
-echo "가상 시간대 k 시간 아시아/상하이 설정 | k 시내 아시아/상하이"
-echo "시스템 재활용 빈 K 쓰레기 | K hsz | K 재활용 빈"
-echo "시스템 백업 기능 K 백업 | K bf | K 백업"
-echo "SSH 원격 연결 도구 K SSH | K 원격 연결"
-echo "rsync 원격 동기화 도구 K rsync | K 원격 동기화"
-echo "하드 디스크 관리 도구 K 디스크 | K 하드 디스크 관리"
-echo "인트라넷 침투 (서버 측) K frps"
-echo "인트라넷 침투 (클라이언트) K frpc"
-echo "소프트웨어 시작 K 시작 SSHD | K 시작 SSHD"
-echo "소프트웨어 중지 K 중지 SSHD | K 중지 SSHD"
-echo "소프트웨어 재시작 K 재시작 SSHD | K는 sshd를 다시 시작합니다"
-echo "소프트웨어 상태보기 K 상태 SSHD | K 상태 SSHD"
-echo "소프트웨어 부트 K 활성화 Docker | K autostart docke | K 스타트 업 Docker"
-echo "도메인 이름 인증서 응용 프로그램 K SSL"
-echo "도메인 이름 인증서 만료 쿼리 K SSL PS"
-echo "도커 환경 설치 K 도커 설치 | K 도커 설치"
-echo "도커 컨테이너 관리 K 도커 PS | K 도커 컨테이너"
-echo "Docker Image Management K Docker img | K Docker Image"
-echo "LDNMP 사이트 관리 K 웹"
-echo "LDNMP 캐시 정리 K 웹 캐시"
-echo "WordPress k wp | K WordPress | k wp xxx.com을 설치하십시오"
-echo "리버스 프록시 k fd | k rp | k an-generation | k fd xxx.com을 설치하십시오."
-echo "로드 밸런싱 k loadbalance | k로드 밸런싱을 설치하십시오"
-echo "방화벽 패널 K FHQ | K 방화벽"
-echo "포트 k dkdk 8080 | k 오픈 포트 8080"
-echo "포트 K GBDK 7800 | K 닫기 포트 7800"
-echo "IP K FXIP 127.0.0.0/8 | K 릴리스 IP 127.0.0.0/8 릴리스"
-echo "블록 IP K ZZIP 177.5.25.36 | K 블록 IP 177.5.25.36"
-
+echo "영상 소개: https://www.bilibili.com/video/BV1ib421E7it?t=0.1"
+echo "다음은 k 명령의 참조 사용 사례입니다."
+echo "시작 스크립트 k"
+echo "패키지 설치 k install nano wget | k 나노 wget 추가 | k는 나노 wget을 설치합니다."
+echo "패키지 제거 k 제거 nano wget | k 델 나노 wget | k 나노 wget 제거 | k 나노 wget 제거"
+echo "시스템 k 업데이트 업데이트 | k 업데이트"
+echo "클린 시스템 정크 k 클린 | 케이 깨끗하다"
+echo "시스템 패널을 다시 설치하십시오. k dd | k 다시 설치하다"
+echo "bbr3 제어판 k bbr3 | kbbrv3"
+echo "커널 튜닝 패널 k nhyh | k 커널 최적화"
+echo "가상 메모리 k 스왑 2048 설정"
+echo "가상 시간대 설정 k 시간 아시아/상하이 | k 시간대 아시아/상하이"
+echo "시스템 휴지통 k 쓰레기 | khsz | k 휴지통"
+echo "시스템 백업 기능 k 백업 | k bf | k 백업"
+echo "SSH 원격 연결 도구 k SSH | k 원격 연결"
+echo "rsync 원격 동기화 도구 k rsync | k 원격 동기화"
+echo "하드 디스크 관리 도구 k 디스크 | k 하드 디스크 관리"
+echo "인트라넷 침투(서버) k frps"
+echo "인트라넷 침투(클라이언트) k frpc"
+echo "소프트웨어 시작 k start sshd | k sshd를 시작하세요"
+echo "소프트웨어 중지 k 중지 sshd | k sshd 중지"
+echo "소프트웨어 재시작 k 재시작 sshd | k sshd를 다시 시작하세요"
+echo "소프트웨어 상태 확인 k status sshd | k 상태 sshd"
+echo "k 도커 활성화 | k 자동 시작 도커 | k 소프트웨어를 부팅할 때 도커를 활성화합니다."
+echo "도메인 이름 인증서 신청 k SSL"
+echo "도메인 이름 인증서 만료 쿼리 k SSL ps"
+echo "도커 관리 플레인 k 도커"
+echo "docker 환경 설치 k docker 설치 |k docker 설치"
+echo "도커 컨테이너 관리 k 도커 ps |k 도커 컨테이너"
+echo "도커 이미지 관리 k docker img |k 도커 이미지"
+echo "LDNMP 사이트 관리 k web"
+echo "LDNMP 캐시 정리 k 웹 캐시"
+echo "WordPress k wp 설치 | k 워드프레스 | k wp xxx.com"
+echo "역방향 프록시 설치 k fd |k rp |k 역방향 프록시 |k fd xxx.com"
+echo "로드 밸런싱 설치 k loadbalance |k 로드 밸런싱"
+echo "L4 로드 밸런싱 설치 k 스트림 |k L4 로드 밸런싱"
+echo "방화벽 패널 k fhq |k 방화벽"
+echo "포트 k 열기 DKdk 8080 |k 포트 8080 열기"
+echo "k 포트 닫기 gbdk 7800 |k 포트 7800 닫기"
+echo "릴리스 IP k fxip 127.0.0.0/8 |k 릴리스 IP 127.0.0.0/8"
+echo "IP 차단 k zzip 177.5.25.36 |k IP 177.5.25.36 차단"
+echo "명령 즐겨찾기 k 즐겨찾기 | k 명령 즐겨찾기"
+echo "애플리케이션 시장 관리 k app"
+echo "신청번호의 빠른 관리 k app 26 | k 앱 1패널 | k 앱 npm"
+echo "Fail2ban 관리 k Fail2ban | 케이 F2B"
+echo "시스템 정보 표시 k 정보"
+echo "ROOT 키 관리 k sshkey"
+echo "SSH 공개 키 가져오기(URL) k sshkey "
+echo "SSH 공개 키 가져오기(GitHub) k sshkey github "
}
if [ "$#" -eq 0 ]; then
- # 매개 변수가없는 경우 대화식 로직을 실행하십시오
+ # 인수 없이 대화형 논리를 실행합니다.
kejilion_sh
else
- # 매개 변수가있는 경우 해당 함수를 실행하십시오
+ # 매개변수가 있으면 해당 함수를 실행합니다.
case $1 in
install|add|安装)
shift
- send_stats "소프트웨어를 설치하십시오"
+ send_stats "소프트웨어 설치"
install "$@"
;;
remove|del|uninstall|卸载)
shift
- send_stats "소프트웨어를 제거하십시오"
+ send_stats "소프트웨어 제거"
remove "$@"
;;
update|更新)
@@ -12719,7 +22044,7 @@ else
rsync_run)
shift
- send_stats "시간이 정한 RSYNC 동기화"
+ send_stats "예약된 rsync 동기화"
run_task "$@"
;;
@@ -12738,9 +22063,10 @@ else
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "IP+ 포트는 서비스에 액세스하는 것이 차단되었습니다"
+ echo "IP+포트가 서비스 접근을 차단했습니다."
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
;;
@@ -12749,15 +22075,20 @@ else
ldnmp_Proxy_backend
;;
+
+ stream|L4负载均衡)
+ ldnmp_Proxy_backend_stream
+ ;;
+
swap)
shift
- send_stats "가상 메모리를 신속하게 설정했습니다"
+ send_stats "가상 메모리를 빠르게 설정하세요"
add_swap "$@"
;;
time|时区)
shift
- send_stats "시간대를 빠르게 설정하십시오"
+ send_stats "시간대를 빠르게 설정"
set_timedate "$@"
;;
@@ -12799,9 +22130,13 @@ else
iptables_panel
;;
+ 命令收藏夹|fav)
+ linux_fav
+ ;;
+
status|状态)
shift
- send_stats "소프트웨어 상태보기"
+ send_stats "소프트웨어 상태 확인"
status "$@"
;;
start|启动)
@@ -12811,7 +22146,7 @@ else
;;
stop|停止)
shift
- send_stats "소프트웨어 일시 정지"
+ send_stats "소프트웨어 일시 중지"
stop "$@"
;;
restart|重启)
@@ -12822,21 +22157,21 @@ else
enable|autostart|开机启动)
shift
- send_stats "소프트웨어 부츠"
+ send_stats "부팅 시 소프트웨어가 자동으로 시작됩니다."
enable "$@"
;;
ssl)
shift
if [ "$1" = "ps" ]; then
- send_stats "인증서 상태를 확인하십시오"
+ send_stats "인증서 상태 보기"
ssl_ps
elif [ -z "$1" ]; then
add_ssl
- send_stats "인증서를 신속하게 신청하십시오"
+ send_stats "빨리 자격증 신청하세요"
elif [ -n "$1" ]; then
add_ssl "$1"
- send_stats "인증서를 신속하게 신청하십시오"
+ send_stats "빨리 자격증 신청하세요"
else
k_info
fi
@@ -12846,7 +22181,7 @@ else
shift
case $1 in
install|安装)
- send_stats "Docker를 신속하게 설치하십시오"
+ send_stats "도커를 빠르게 설치하세요"
install_docker
;;
ps|容器)
@@ -12854,11 +22189,11 @@ else
docker_ps
;;
img|镜像)
- send_stats "빠른 미러 관리"
+ send_stats "빠른 이미지 관리"
docker_image
;;
*)
- k_info
+ linux_docker
;;
esac
;;
@@ -12878,6 +22213,59 @@ else
fi
;;
+
+ app)
+ shift
+ send_stats "신청$@"
+ linux_panel "$@"
+ ;;
+
+ claw|oc|OpenClaw)
+ moltbot_menu
+ ;;
+
+ info)
+ linux_info
+ ;;
+
+ fail2ban|f2b)
+ fail2ban_panel
+ ;;
+
+
+ sshkey)
+
+ shift
+ case "$1" in
+ "" )
+ # sshkey → 대화형 메뉴
+ send_stats "SSHKey 대화형 메뉴"
+ sshkey_panel
+ ;;
+ github )
+ shift
+ send_stats "GitHub에서 SSH 공개 키 가져오기"
+ fetch_github_ssh_keys "$1"
+ ;;
+ http://*|https://* )
+ send_stats "URL에서 SSH 공개 키 가져오기"
+ fetch_remote_ssh_keys "$1"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa* )
+ send_stats "공개 키를 직접 가져오기"
+ import_sshkey "$1"
+ ;;
+ * )
+ echo "오류: 알 수 없는 매개변수 '$1'"
+ echo "용법:"
+ echo "k sshkey가 대화형 메뉴로 들어갑니다."
+ echo "k sshkey \"\" SSH 공개 키 직접 가져오기"
+ echo "k sshkey URL에서 SSH 공개 키 가져오기"
+ echo "k sshkey github GitHub에서 SSH 공개 키 가져오기"
+ ;;
+ esac
+
+ ;;
*)
k_info
;;
diff --git a/mc.sh b/mc.sh
new file mode 100644
index 000000000..b622b4b6f
--- /dev/null
+++ b/mc.sh
@@ -0,0 +1,442 @@
+#!/bin/bash
+ln -sf ~/mc.sh /usr/local/bin/mcs
+
+ip_address() {
+ # 检测 IPv4 地址
+ ipv4_address=$(curl -s --connect-timeout 5 ipv4.ip.sb 2>/dev/null || echo "")
+ # 检测 IPv6 地址
+ ipv6_address=$(curl -s --connect-timeout 5 ipv6.ip.sb 2>/dev/null || echo "")
+
+ # 设置显示变量
+ if [ -n "$ipv4_address" ] && [ -n "$ipv6_address" ]; then
+ ip_display="\033[93m IPv4: $ipv4_address:25565 IPv6: $ipv6_address:25565 \033[0m"
+ elif [ -n "$ipv4_address" ]; then
+ ip_display="\033[93m IPv4: $ipv4_address:25565 \033[0m"
+ elif [ -n "$ipv6_address" ]; then
+ ip_display="\033[93m IPv6: $ipv6_address:25565 \033[0m"
+ else
+ ip_display="\033[93m 无法获取IP地址 \033[0m"
+ fi
+}
+
+
+install() {
+ if [ $# -eq 0 ]; then
+ echo "未提供软件包参数!"
+ return 1
+ fi
+
+ for package in "$@"; do
+ if ! command -v "$package" &>/dev/null; then
+ if command -v apt &>/dev/null; then
+ apt update -y && apt install -y "$package"
+ elif command -v yum &>/dev/null; then
+ yum -y update && yum -y install "$package"
+ elif command -v apk &>/dev/null; then
+ apk update && apk add "$package"
+ else
+ echo "未知的包管理器!"
+ return 1
+ fi
+ fi
+ done
+
+ return 0
+}
+
+
+remove() {
+ if [ $# -eq 0 ]; then
+ echo "未提供软件包参数!"
+ return 1
+ fi
+
+ for package in "$@"; do
+ if command -v apt &>/dev/null; then
+ apt purge -y "$package"
+ elif command -v yum &>/dev/null; then
+ yum remove -y "$package"
+ elif command -v apk &>/dev/null; then
+ apk del "$package"
+ else
+ echo "未知的包管理器!"
+ return 1
+ fi
+ done
+
+ return 0
+}
+
+
+break_end() {
+ echo -e "\033[0;32m操作完成\033[0m"
+ echo "按任意键继续..."
+ read -n 1 -s -r -p ""
+ echo ""
+ clear
+}
+
+mc() {
+ p
+ exit
+}
+
+
+install_add_docker() {
+ if [ -f "/etc/alpine-release" ]; then
+ apk update
+ apk add docker docker-compose
+ rc-update add docker default
+ service docker start
+ else
+ curl -fsSL https://get.docker.com | sh && ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin
+ systemctl start docker
+ systemctl enable docker
+ fi
+}
+
+install_docker() {
+ if ! command -v docker &>/dev/null; then
+ install_add_docker
+ else
+ echo "Docker 已经安装"
+ fi
+}
+
+mc_start() {
+ ip_address
+ docker start mcserver > /dev/null 2>&1
+ echo -e "\033[0;32mMinecraft服务启动啦!\033[0m"
+ echo -e "\033[0;32m游戏下载地址: https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj\033[0m"
+ echo -e "\033[0;32m进入游戏连接:$ip_display\033[0;32m开始冒险吧!\033[0m"
+
+}
+
+mc_backup() {
+ cd ~
+ curl -sS -O https://kejilion.pro/mc_backup.sh && chmod +x mc_backup.sh
+}
+
+mc_install_status() {
+ CONTAINER_NAME="mcserver"
+
+ # 检查容器是否已安装
+ if [ "$(docker ps -a -q -f name=$CONTAINER_NAME 2>/dev/null)" ]; then
+ container_status="\e[32mMinecraft服务已安装\e[0m" # 绿色
+ else
+ container_status="\e[90mMinecraft服务未安装\e[0m" # 灰色
+ fi
+
+ ip_address
+ # 检查 Docker 容器是否正在运行
+ if docker ps --format "table {{.Names}}" | grep -q "^$CONTAINER_NAME$"; then
+ tmux_status="\e[32m已开服:$ip_display\e[0m" # 绿色
+ else
+ tmux_status="\e[90m未开服\e[0m" # 灰色
+ fi
+
+}
+
+while true; do
+clear
+mc_install_status
+echo -e "\033[92m███╗ ███╗██╗███╗ ██╗███████╗ ██████╗██████╗ █████╗ ███████╗████████╗\033[0m"
+echo -e "\033[92m████╗ ████║██║████╗ ██║██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝╚══██╔══╝\033[0m"
+echo -e "\033[92m██╔████╔██║██║██╔██╗ ██║█████╗ ██║ ██████╔╝███████║█████╗ ██║ \033[0m"
+echo -e "\033[92m██║╚██╔╝██║██║██║╚██╗██║██╔══╝ ██║ ██╔══██╗██╔══██║██╔══╝ ██║ \033[0m"
+echo -e "\033[92m██║ ╚═╝ ██║██║██║ ╚████║███████╗╚██████╗██║ ██║██║ ██║██║ ██║ \033[0m"
+echo -e "\033[92m╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ \033[0m"
+echo -e "\033[92mMinecraft开服一键脚本工具v1.0.1 by AkarinLiu\033[0m"
+echo -e "\033[92m-输入\033[92mmcs\033[92m可快速启动此脚本-\033[0m"
+echo -e "$container_status $tmux_status"
+echo "------------------------"
+echo "1. 安装Minecraft服务"
+echo "2. 开启Minecraft服务"
+echo "3. 关闭Minecraft服务"
+echo "4. 重启Minecraft服务"
+echo "------------------------"
+echo "5. 查看服务器状态"
+echo "6. 设置虚拟内存"
+echo "------------------------"
+echo "7. 导出游戏存档"
+echo "8. 导入游戏存档"
+echo "9. 定时备份游戏存档"
+echo "------------------------"
+echo "10. 修改游戏配置"
+echo "o. 添加管理员权限"
+echo "p. 删除管理员权限"
+echo "------------------------"
+echo "11. 更新Minecraft服务"
+echo "12. 卸载Minecraft服务"
+echo "------------------------"
+echo "k. 科技lion脚本工具箱"
+echo "------------------------"
+echo "00. 脚本更新"
+echo "------------------------"
+echo "0. 退出脚本"
+echo "------------------------"
+read -p "请输入你的选择: " choice
+
+case $choice in
+ 1)
+ clear
+ install_docker
+ docker run -d --name mcserver -p 25565:25565/tcp --restart=always -e EULA=true -e CREATE_CONSOLE_IN_PIPE=true -v mcserver:/data:rw itzg/minecraft-server
+ clear
+ mc_start
+ ;;
+
+ 2)
+ clear
+ docker start $CONTAINER_NAME > /dev/null 2>&1
+ mc_start
+ ;;
+
+ 3)
+ clear
+ docker stop $CONTAINER_NAME > /dev/null 2>&1
+ echo -e "\033[0;32mMinecraft服务已关闭\033[0m"
+ ;;
+
+ 4)
+ clear
+ docker restart $CONTAINER_NAME > /dev/null 2>&1
+ mc_start
+ ;;
+
+ 5)
+ clear
+ install btop
+ clear
+ btop
+ ;;
+
+ 6)
+ clear
+ swap_used=$(free -m | awk 'NR==3{print $3}')
+ swap_total=$(free -m | awk 'NR==3{print $2}')
+
+ if [ "$swap_total" -eq 0 ]; then
+ swap_percentage=0
+ else
+ swap_percentage=$((swap_used * 100 / swap_total))
+ fi
+
+ swap_info="${swap_used}MB/${swap_total}MB (${swap_percentage}%)"
+
+ echo "当前虚拟内存: $swap_info"
+
+ read -p "是否调整大小?(Y/N): " choice
+
+ case "$choice" in
+ [Yy])
+ # 输入新的虚拟内存大小
+ read -p "请输入虚拟内存大小MB: " new_swap
+
+ # 获取当前系统中所有的 swap 分区
+ swap_partitions=$(grep -E '^/dev/' /proc/swaps | awk '{print $1}')
+
+ # 遍历并删除所有的 swap 分区
+ for partition in $swap_partitions; do
+ swapoff "$partition"
+ wipefs -a "$partition" # 清除文件系统标识符
+ mkswap -f "$partition"
+ echo "已删除并重新创建 swap 分区: $partition"
+ done
+
+ # 确保 /swapfile 不再被使用
+ swapoff /swapfile
+
+ # 删除旧的 /swapfile
+ rm -f /swapfile
+
+ # 创建新的 swap 分区
+ dd if=/dev/zero of=/swapfile bs=1M count=$new_swap
+ chmod 600 /swapfile
+ mkswap /swapfile
+ swapon /swapfile
+
+ if [ -f /etc/alpine-release ]; then
+ echo "/swapfile swap swap defaults 0 0" >> /etc/fstab
+ echo "nohup swapon /swapfile" >> /etc/local.d/swap.start
+ chmod +x /etc/local.d/swap.start
+ rc-update add local
+ else
+ echo "/swapfile swap swap defaults 0 0" >> /etc/fstab
+ fi
+
+ echo "虚拟内存大小已调整为${new_swap}MB"
+ ;;
+ [Nn])
+ echo "已取消"
+ ;;
+ *)
+ echo "无效的选择,请输入 Y 或 N。"
+ ;;
+ esac
+ ;;
+
+ 7)
+ clear
+ mkdir -p /home/game
+ docker cp $CONTAINER_NAME:/data/world /home/game/mc/ > /dev/null 2>&1
+ cd /home/game && tar czvf mcsave_$(date +"%Y%m%d%H%M%S").tar.gz mc > /dev/null 2>&1
+ rm -rf /home/game/mc/
+ echo -e "\033[0;32m游戏存档已导出存放在: /home/game/mc/\033[0m"
+ ;;
+ 8)
+ clear
+ docker stop mcserver > /dev/null 2>&1
+ docker exec -it mcserver bash -c "rm -rf /data/world/*"
+ cd /home/game/ && ls -t /home/game/mc/*.tar.gz | head -1 | xargs -I {} tar -xzf {}
+ docker cp /home/game/mc/world/* mcserver:/data/world
+ rm -rf /home/game/mc/
+ echo -e "\033[0;32m游戏存档已导入\033[0m"
+ docker restart mcserver > /dev/null 2>&1
+ mc_start
+ ;;
+
+ 9)
+ clear
+ echo "Minecraft游戏存档定时备份"
+ echo "------------------------"
+ echo "1. 每周备份 2. 每天备份 3. 每小时备份"
+ echo "------------------------"
+ read -p "请输入你的选择: " dingshi
+ case $dingshi in
+ 1)
+ mc_backup
+ (crontab -l ; echo "0 0 * * 1 ./mc_backup.sh") | crontab - > /dev/null 2>&1
+ echo "每周一备份,已设置"
+
+ ;;
+ 2)
+ mc_backup
+ (crontab -l ; echo "0 3 * * * ./mc_backup.sh") | crontab - > /dev/null 2>&1
+ echo "每天凌晨3点备份,已设置"
+
+ ;;
+ 3)
+ mc_backup
+ (crontab -l ; echo "0 * * * * ./mc_backup.sh") | crontab - > /dev/null 2>&1
+ echo "每小时整点备份,已设置"
+
+ ;;
+ *)
+ echo "已取消"
+ ;;
+ esac
+ ;;
+
+ 10)
+ clear
+ echo "配置游戏参数"
+ echo "------------------------"
+ read -p "设置游戏难度: (0.和平 1. 简单 2. 普通 3. 困难):" Difficulty
+ case $Difficulty in
+ 0)
+ docker exec --user 1000 mcserver mc-send-to-console difficulty peaceful
+ ;;
+ 1)
+ docker exec --user 1000 mcserver mc-send-to-console difficulty easy
+ ;;
+
+ 2)
+ docker exec --user 1000 mcserver mc-send-to-console difficulty normal
+ ;;
+ 3)
+ docker exec --user 1000 mcserver mc-send-to-console difficulty hard
+ ;;
+ *)
+ echo "-默认设置为普通难度"
+ docker exec --user 1000 mcserver mc-send-to-console difficulty normal
+ ;;
+ esac
+
+ read -p "死亡后掉落设置: (1. 掉落 2. 不掉落):" DeathPenalty
+ case $DeathPenalty in
+ 1)
+ docker exec --user 1000 mcserver mc-send-to-console gamerule KeepInventoy false
+ ;;
+
+ 2)
+ docker exec --user 1000 mcserver mc-send-to-console gamerule KeepInventoy true
+ ;;
+ *)
+ docker exec --user 1000 mcserver mc-send-to-console gamerule KeepInventoy false
+ echo "-默认设置为掉落"
+ ;;
+ esac
+
+ read -p "设置pvp模式: (1. 开启 2. 关闭):" mc_pvp
+
+ case $mc_pvp in
+ 1)
+ docker exec --user 1000 mcserver mc-send-to-console gamerule pvp true
+ ;;
+ 2)
+ docker exec --user 1000 mcserver mc-send-to-console gamerule pvp false
+ ;;
+ *)
+ docker exec --user 1000 mcserver mc-send-to-console gamerule pvp false
+ echo "-默认关闭pvp模式"
+ ;;
+ esac
+
+ # 更新配置
+ echo -e "\033[0;32m游戏配置已更改\033[0m"
+ ;;
+
+
+ 11)
+ clear
+ docker stop mcserver > /dev/null 2>&1
+ docker restart mcserver > /dev/null 2>&1
+ docker exec -it mcserver bash -c "/home/steam/mcserver/mcserver.sh +login anonymous +app_update 2394010 validate +quit"
+ clear
+ echo -e "\033[0;32mMinecraft已更新\033[0m"
+ mc_start
+ ;;
+
+ 12)
+ clear
+ docker rm -f mcserver
+ docker rmi -f itzg/minecraft-server
+ ;;
+ o)
+ read -p "请输入 Minecraft Java 版档案名称:" mc_op
+ docker exec --user 1000 mcserver mc-send-to-console op $mc_op
+ ;;
+ p)
+ read -p "请输入 Minecraft Java 版档案名称:" mc_deop
+ docker exec --user 1000 mcserver mc-send-to-console deop $mc_deop
+ ;;
+ k)
+ cd ~
+ curl -sS -O https://kejilion.pro/kejilion.sh && chmod +x kejilion.sh && ./kejilion.sh
+ exit
+ ;;
+
+ 00)
+ cd ~
+ curl -sS -O https://kejilion.pro/mc_log.sh && chmod +x mc_log.sh && ./mc_log.sh
+ rm mc_log.sh
+ echo ""
+ curl -sS -O https://kejilion.pro/mc.sh && chmod +x mc.sh
+ echo "脚本已更新到最新版本!"
+ break_end
+ mc
+ ;;
+
+
+ 0)
+ clear
+ exit
+ ;;
+
+ *)
+ echo "无效的输入!"
+ ;;
+esac
+ break_end
+done
diff --git a/mc_backup.sh b/mc_backup.sh
new file mode 100644
index 000000000..a996cc18d
--- /dev/null
+++ b/mc_backup.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+clear
+mkdir -p /home/game
+docker cp mcserver:/data /home/game/mc
+cd /home/game/mc && tar czvf mc_$(date +"%Y%m%d%H%M%S").tar.gz mc
+rm -rf /home/game/mc/
+echo -e "\033[0;32m游戏存档已导出存放在: /home/game/mc/\033[0m"
diff --git a/mc_log.sh b/mc_log.sh
new file mode 100644
index 000000000..0023e118d
--- /dev/null
+++ b/mc_log.sh
@@ -0,0 +1,7 @@
+ clear
+ echo "脚本更新日志"
+ echo "------------------------"
+ echo "2025-11-16 v1.0.1"
+ echo "Minecraft 开服脚本魔改自 幻兽帕鲁开服脚本"
+ echo "2025-11-17 v1.0.1"
+ ech "对 Minecraft 的难度等细节进行了适配"
diff --git a/network-optimize.sh b/network-optimize.sh
new file mode 100644
index 000000000..51fe8caff
--- /dev/null
+++ b/network-optimize.sh
@@ -0,0 +1,608 @@
+#!/bin/bash
+# ============================================================================
+# 自动网络性能检测与内核优化脚本
+# 适配:Debian/Ubuntu, CentOS/RHEL/Alma/Rocky, Alpine, Arch, Fedora
+# 作者:kejilion
+# ============================================================================
+
+# ======================== 颜色定义 ========================
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+WHITE='\033[1;37m'
+NC='\033[0m'
+
+# ======================== 工具函数 ========================
+info() { echo -e "${CYAN}[检测]${NC} $*"; }
+ok() { echo -e "${GREEN}[✓]${NC} $*"; }
+warn() { echo -e "${YELLOW}[!]${NC} $*"; }
+fail() { echo -e "${RED}[✗]${NC} $*"; }
+title() { echo -e "\n${WHITE}━━━ $* ━━━${NC}"; }
+
+# 获取内核主版本号用于兼容性判断
+kernel_version() {
+ local ver
+ ver=$(uname -r | grep -oP '^\d+\.\d+')
+ echo "$ver"
+}
+
+# 比较版本号: version_ge 5.4 4.9 => true
+version_ge() {
+ printf '%s\n%s' "$2" "$1" | sort -V -C
+}
+
+# 安全读取 sysctl 值
+sysctl_get() {
+ sysctl -n "$1" 2>/dev/null || echo "N/A"
+}
+
+# 获取发行版
+detect_distro() {
+ if [ -f /etc/os-release ]; then
+ . /etc/os-release
+ case "$ID" in
+ debian|ubuntu|linuxmint|pop|kali) echo "debian" ;;
+ centos|rhel|almalinux|rocky|ol|amzn|fedora) echo "rhel" ;;
+ alpine) echo "alpine" ;;
+ arch|manjaro|endeavouros) echo "arch" ;;
+ opensuse*|sles) echo "suse" ;;
+ *) echo "unknown" ;;
+ esac
+ elif [ -f /etc/debian_version ]; then
+ echo "debian"
+ elif [ -f /etc/redhat-release ]; then
+ echo "rhel"
+ else
+ echo "unknown"
+ fi
+}
+
+# ======================== 网络检测 ========================
+
+# 检测网卡速率(Mbps)
+detect_link_speed() {
+ local speed=0
+ local iface
+
+ # 获取默认路由网卡
+ iface=$(ip route show default 2>/dev/null | awk '/default/{print $5}' | head -1)
+ [ -z "$iface" ] && iface=$(ls /sys/class/net/ | grep -v lo | head -1)
+
+ if [ -n "$iface" ]; then
+ # 尝试 ethtool
+ if command -v ethtool &>/dev/null; then
+ speed=$(ethtool "$iface" 2>/dev/null | awk '/Speed:/{gsub(/[^0-9]/,"",$2); print $2}')
+ fi
+
+ # 尝试 sysfs
+ if [ -z "$speed" ] || [ "$speed" = "0" ]; then
+ speed=$(cat "/sys/class/net/$iface/speed" 2>/dev/null)
+ fi
+ fi
+
+ # VPS 虚拟网卡可能读不到速率,默认 1000
+ [ -z "$speed" ] || [ "$speed" -le 0 ] 2>/dev/null && speed=1000
+
+ echo "$speed"
+}
+
+# 检测网络延迟(ms,ping 多个公共 DNS)
+detect_latency() {
+ local total=0 count=0 avg
+ local targets=("8.8.8.8" "1.1.1.1" "9.9.9.9" "208.67.222.222")
+
+ for t in "${targets[@]}"; do
+ local rtt
+ rtt=$(ping -c 3 -W 2 "$t" 2>/dev/null | awk -F'/' '/avg/{print $5}')
+ if [ -n "$rtt" ]; then
+ total=$(awk "BEGIN{print $total + $rtt}")
+ count=$((count + 1))
+ fi
+ done
+
+ if [ "$count" -gt 0 ]; then
+ avg=$(awk "BEGIN{printf \"%.1f\", $total / $count}")
+ else
+ avg="50"
+ fi
+
+ echo "$avg"
+}
+
+# 检测丢包率(%)
+detect_packet_loss() {
+ local loss
+ loss=$(ping -c 10 -W 2 8.8.8.8 2>/dev/null | awk -F',' '/loss/{gsub(/%/,"",$3); gsub(/ packet/,"",$3); print $3+0}')
+ [ -z "$loss" ] && loss=0
+ echo "$loss"
+}
+
+# 获取当前 TCP 拥塞算法
+detect_congestion() {
+ sysctl_get net.ipv4.tcp_congestion_control
+}
+
+# 检测总物理内存(MB)
+detect_memory_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
+}
+
+# ======================== 带宽分级 ========================
+# 返回: low / medium / high / ultra
+classify_bandwidth() {
+ local speed=$1
+ if [ "$speed" -lt 100 ]; then
+ echo "low"
+ elif [ "$speed" -lt 1000 ]; then
+ echo "medium"
+ elif [ "$speed" -lt 10000 ]; then
+ echo "high"
+ else
+ echo "ultra"
+ fi
+}
+
+# ======================== 核心优化函数 ========================
+
+auto_optimize_network() {
+ local CONF="/etc/sysctl.d/99-network-optimize.conf"
+ local BACKUP="/etc/sysctl.d/99-network-optimize.conf.bak.$(date +%s)"
+ local KVER
+ KVER=$(kernel_version)
+ local DISTRO
+ DISTRO=$(detect_distro)
+
+ echo -e "${WHITE}"
+ echo "╔══════════════════════════════════════════════════════════════╗"
+ echo "║ 自动网络性能检测与内核优化 ║"
+ echo "╚══════════════════════════════════════════════════════════════╝"
+ echo -e "${NC}"
+
+ # ── 权限检查 ──
+ if [ "$(id -u)" -ne 0 ]; then
+ fail "请以 root 权限运行"
+ return 1
+ fi
+
+ # ── 环境信息 ──
+ title "环境信息"
+ info "发行版: $(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME" || echo "$DISTRO")"
+ info "内核版本: $(uname -r)"
+ info "架构: $(uname -m)"
+ info "内存: $(detect_memory_mb) MB"
+
+ # ── 网络检测 ──
+ title "网络性能检测"
+
+ local SPEED LATENCY LOSS CONGESTION MEM_MB BW_CLASS
+
+ info "检测网卡速率..."
+ SPEED=$(detect_link_speed)
+ ok "链路速率: ${SPEED} Mbps"
+
+ info "检测网络延迟..."
+ LATENCY=$(detect_latency)
+ ok "平均延迟: ${LATENCY} ms"
+
+ info "检测丢包率..."
+ LOSS=$(detect_packet_loss)
+ ok "丢包率: ${LOSS}%"
+
+ CONGESTION=$(detect_congestion)
+ ok "当前拥塞算法: ${CONGESTION}"
+
+ MEM_MB=$(detect_memory_mb)
+ BW_CLASS=$(classify_bandwidth "$SPEED")
+
+ title "检测结论"
+ case "$BW_CLASS" in
+ low) info "带宽等级: ${YELLOW}低带宽 (<100M)${NC} → 保守优化" ;;
+ medium) info "带宽等级: ${CYAN}中带宽 (100M-1G)${NC} → 标准优化" ;;
+ high) info "带宽等级: ${GREEN}高带宽 (1G-10G)${NC} → 激进优化" ;;
+ ultra) info "带宽等级: ${WHITE}超高带宽 (>10G)${NC} → 极致优化" ;;
+ esac
+
+ local HIGH_LATENCY=false
+ if [ "$(awk "BEGIN{print ($LATENCY > 100)}")" = "1" ]; then
+ HIGH_LATENCY=true
+ warn "高延迟网络 (${LATENCY}ms > 100ms),启用额外优化"
+ fi
+
+ # ── 备份现有配置 ──
+ title "备份与准备"
+ if [ -f "$CONF" ]; then
+ cp "$CONF" "$BACKUP"
+ ok "已备份: $BACKUP"
+ else
+ ok "首次优化,无需备份"
+ fi
+
+ # ── 根据带宽等级设定参数 ──
+ local TCP_RMEM_MAX TCP_WMEM_MAX TCP_RMEM TCP_WMEM
+ local NETDEV_BUDGET NETDEV_BUDGET_USECS
+ local SOMAXCONN NETDEV_BACKLOG
+ local CONNTRACK_MAX
+
+ case "$BW_CLASS" in
+ low)
+ TCP_RMEM="4096 65536 2097152"
+ TCP_WMEM="4096 65536 2097152"
+ TCP_RMEM_MAX=2097152
+ TCP_WMEM_MAX=2097152
+ SOMAXCONN=1024
+ NETDEV_BACKLOG=1000
+ NETDEV_BUDGET=300
+ NETDEV_BUDGET_USECS=2000
+ CONNTRACK_MAX=65536
+ ;;
+ medium)
+ TCP_RMEM="4096 131072 16777216"
+ TCP_WMEM="4096 131072 16777216"
+ TCP_RMEM_MAX=16777216
+ TCP_WMEM_MAX=16777216
+ SOMAXCONN=4096
+ NETDEV_BACKLOG=5000
+ NETDEV_BUDGET=600
+ NETDEV_BUDGET_USECS=4000
+ CONNTRACK_MAX=262144
+ ;;
+ high)
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ TCP_RMEM_MAX=67108864
+ TCP_WMEM_MAX=67108864
+ SOMAXCONN=8192
+ NETDEV_BACKLOG=10000
+ NETDEV_BUDGET=1200
+ NETDEV_BUDGET_USECS=6000
+ CONNTRACK_MAX=524288
+ ;;
+ ultra)
+ TCP_RMEM="4096 524288 134217728"
+ TCP_WMEM="4096 524288 134217728"
+ TCP_RMEM_MAX=134217728
+ TCP_WMEM_MAX=134217728
+ SOMAXCONN=16384
+ NETDEV_BACKLOG=20000
+ NETDEV_BUDGET=2400
+ NETDEV_BUDGET_USECS=8000
+ CONNTRACK_MAX=1048576
+ ;;
+ esac
+
+ # 高延迟追加:加大初始窗口
+ local HIGH_LAT_EXTRA=""
+ if $HIGH_LATENCY; then
+ # 高延迟网络使用更大的初始拥塞窗口提升吞吐
+ HIGH_LAT_EXTRA="
+# ── 高延迟网络额外优化 ──
+net.ipv4.tcp_slow_start_after_idle = 0
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
+
+ # 内存不足时缩小缓冲区
+ if [ "$MEM_MB" -lt 512 ]; then
+ warn "内存不足 512MB,缩小 TCP 缓冲区"
+ TCP_RMEM="4096 32768 1048576"
+ TCP_WMEM="4096 32768 1048576"
+ TCP_RMEM_MAX=1048576
+ TCP_WMEM_MAX=1048576
+ CONNTRACK_MAX=32768
+ fi
+
+ # ── 根据内存大小设定 VM 参数 ──
+ local SWAPPINESS MIN_FREE_KB
+ if [ "$MEM_MB" -ge 16384 ]; then
+ # 16G+ 大内存:激进
+ SWAPPINESS=5
+ MIN_FREE_KB=131072
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ # 4G-16G:标准
+ SWAPPINESS=10
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ # 1G-4G:保守
+ SWAPPINESS=20
+ MIN_FREE_KB=32768
+ else
+ # <1G:极度保守
+ SWAPPINESS=30
+ MIN_FREE_KB=16384
+ fi
+
+ # ── BBR 检测与加载 ──
+ title "TCP 拥塞算法优化"
+ local USE_BBR=false
+ local TARGET_CC="cubic"
+
+ if version_ge "$KVER" "4.9"; then
+ # 内核 >= 4.9 支持 BBR
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ if lsmod 2>/dev/null | grep -q tcp_bbr; then
+ ok "BBR 模块已加载"
+ else
+ warn "BBR 模块加载失败,使用 cubic"
+ fi
+ fi
+
+ if sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ USE_BBR=true
+ TARGET_CC="bbr"
+ ok "拥塞算法: cubic → ${GREEN}BBR${NC}"
+ fi
+ else
+ warn "内核 $KVER < 4.9,不支持 BBR,保持 cubic"
+ fi
+
+ # BBR 配合 fq 队列调度
+ local QDISC="fq"
+ if ! $USE_BBR; then
+ QDISC="fq_codel"
+ fi
+
+ # ── 生成优化配置 ──
+ title "写入优化配置"
+
+ cat > "$CONF" << SYSCTL
+# ============================================================================
+# 自动网络优化配置
+# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
+# 带宽等级: $BW_CLASS (${SPEED}Mbps)
+# 平均延迟: ${LATENCY}ms | 丢包率: ${LOSS}%
+# 内核: $(uname -r) | 发行版: $DISTRO | 内存: ${MEM_MB}MB
+# ============================================================================
+
+# ── TCP 拥塞控制 ──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $TARGET_CC
+
+# ── TCP 缓冲区 ──
+net.core.rmem_max = $TCP_RMEM_MAX
+net.core.wmem_max = $TCP_WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+net.ipv4.udp_rmem_min = 8192
+net.ipv4.udp_wmem_min = 8192
+
+# ── 连接队列 ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $NETDEV_BACKLOG
+net.core.netdev_budget = $NETDEV_BUDGET
+$(sysctl -n net.core.netdev_budget_usecs &>/dev/null && echo "net.core.netdev_budget_usecs = $NETDEV_BUDGET_USECS" || echo "# netdev_budget_usecs 不支持,跳过")
+
+# ── TCP 连接优化 ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = 15
+net.ipv4.tcp_keepalive_time = 300
+net.ipv4.tcp_keepalive_intvl = 30
+net.ipv4.tcp_keepalive_probes = 5
+net.ipv4.tcp_max_syn_backlog = $SOMAXCONN
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── 内存与端口 ──
+net.ipv4.ip_local_port_range = 1024 65535
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── 安全与防护 ──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+
+# ── IPv6 优化 ──
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── 虚拟内存优化 ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = 15
+vm.dirty_background_ratio = 5
+vm.overcommit_memory = 1
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = 50
+
+# ── CPU/内核调度优化 ──
+kernel.sched_autogroup_enabled = 0
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = 0" || echo "# numa_balancing 不支持,跳过")
+
+# ── 文件描述符 ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── 连接跟踪 ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+ echo "net.netfilter.nf_conntrack_max = $CONNTRACK_MAX"
+ echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+ echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+ echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+ echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+ echo "# conntrack 未启用,跳过"
+fi)
+$HIGH_LAT_EXTRA
+SYSCTL
+
+ ok "配置已写入: $CONF"
+
+ # ── 应用配置 ──
+ title "应用优化"
+ local apply_errors
+ apply_errors=$(sysctl -p "$CONF" 2>&1 | grep -i "error\|invalid\|cannot" || true)
+
+ if [ -n "$apply_errors" ]; then
+ warn "部分参数不支持(已跳过):"
+ echo "$apply_errors" | while read -r line; do
+ echo -e " ${YELLOW}$line${NC}"
+ done
+ fi
+ ok "sysctl 参数已应用"
+
+ # ── 禁用透明大页面(减少延迟抖动) ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo never > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null && \
+ ok "透明大页面已禁用" || warn "透明大页面禁用失败"
+ fi
+
+ # ── 设置文件描述符限制 ──
+ if ! grep -q "# network-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
+
+# network-optimize - 自动网络优化添加
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ ok "文件描述符限制已更新"
+ fi
+
+ # ── 持久化 BBR 模块 ──
+ if $USE_BBR; then
+ if [ ! -f /etc/modules-load.d/bbr.conf ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ ok "BBR 模块持久化"
+ fi
+ fi
+
+ # ── 优化摘要 ──
+ title "优化摘要"
+ echo -e "${WHITE}┌──────────────────────────────────────────────────┐${NC}"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "项目" "值"
+ echo -e "${WHITE}├──────────────────────────────────────────────────┤${NC}"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "带宽等级" "$BW_CLASS (${SPEED}Mbps)"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "拥塞算法" "$TARGET_CC"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "队列调度" "$QDISC"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "TCP 缓冲区(max)" "$(numfmt --to=iec $TCP_RMEM_MAX 2>/dev/null || echo ${TCP_RMEM_MAX})"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "连接队列" "$SOMAXCONN"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "网络延迟" "${LATENCY}ms"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "高延迟优化" "$(if $HIGH_LATENCY; then echo '已启用'; else echo '未需要'; fi)"
+ printf "${WHITE}│${NC} %-20s ${WHITE}│${NC} %-25s ${WHITE}│${NC}\n" "配置文件" "$CONF"
+ echo -e "${WHITE}└──────────────────────────────────────────────────┘${NC}"
+
+ echo -e "\n${GREEN}网络优化完成!${NC}"
+ echo -e "回滚命令: ${CYAN}restore_network_defaults${NC}"
+}
+
+# ======================== 回滚函数 ========================
+
+restore_network_defaults() {
+ local CONF="/etc/sysctl.d/99-network-optimize.conf"
+
+ title "回滚网络优化"
+
+ if [ "$(id -u)" -ne 0 ]; then
+ fail "请以 root 权限运行"
+ return 1
+ fi
+
+ # 查找最近的备份
+ local latest_bak
+ latest_bak=$(ls -t /etc/sysctl.d/99-network-optimize.conf.bak.* 2>/dev/null | head -1)
+
+ if [ -n "$latest_bak" ]; then
+ cp "$latest_bak" "$CONF"
+ sysctl -p "$CONF" 2>/dev/null
+ ok "已从备份恢复: $latest_bak"
+ elif [ -f "$CONF" ]; then
+ rm -f "$CONF"
+ sysctl --system 2>/dev/null
+ ok "已删除优化配置,恢复系统默认"
+ else
+ warn "没有优化配置需要回滚"
+ return 0
+ fi
+
+ # 清理 limits.conf 添加的部分
+ if grep -q "# network-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# network-optimize/,+4d' /etc/security/limits.conf
+ ok "文件描述符限制已恢复"
+ fi
+
+ # 清理 BBR 持久化
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+
+ ok "网络配置已回滚"
+}
+
+# ======================== 查看当前状态 ========================
+
+show_network_status() {
+ title "当前网络内核参数"
+
+ local params=(
+ "net.ipv4.tcp_congestion_control"
+ "net.core.default_qdisc"
+ "net.core.rmem_max"
+ "net.core.wmem_max"
+ "net.ipv4.tcp_rmem"
+ "net.ipv4.tcp_wmem"
+ "net.core.somaxconn"
+ "net.core.netdev_max_backlog"
+ "net.ipv4.tcp_fastopen"
+ "net.ipv4.tcp_tw_reuse"
+ "net.ipv4.tcp_fin_timeout"
+ "net.ipv4.ip_local_port_range"
+ "net.ipv4.tcp_mtu_probing"
+ "fs.file-max"
+ )
+
+ for p in "${params[@]}"; do
+ local val
+ val=$(sysctl_get "$p")
+ printf " %-45s = %s\n" "$p" "$val"
+ done
+
+ if [ -f /etc/sysctl.d/99-network-optimize.conf ]; then
+ echo ""
+ ok "优化配置已安装: /etc/sysctl.d/99-network-optimize.conf"
+ else
+ echo ""
+ warn "未检测到优化配置"
+ fi
+}
+
+# ======================== 入口 ========================
+# 用法:
+# source network-optimize.sh
+# auto_optimize_network # 自动检测并优化
+# show_network_status # 查看当前状态
+# restore_network_defaults # 回滚到默认
+#
+# 或直接运行:
+# bash network-optimize.sh
+# ========================
+
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]] || [[ -z "${BASH_SOURCE[0]}" ]]; then
+ # 支持参数: bash xxx.sh restore / bash xxx.sh status
+ # 支持环境变量: ACTION=restore curl ... | bash
+ _action="${1:-${ACTION:-optimize}}"
+ case "$_action" in
+ restore|rollback|回滚)
+ restore_network_defaults
+ ;;
+ status|状态)
+ show_network_status
+ ;;
+ *)
+ auto_optimize_network
+ ;;
+ esac
+fi
diff --git a/optimized_php.ini b/optimized_php.ini
index e1a3531da..dfb2e0a75 100644
--- a/optimized_php.ini
+++ b/optimized_php.ini
@@ -5,7 +5,7 @@ error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING & ~E_DEPRECATED
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off
-disable_functions = exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source
+disable_functions = passthru,system,proc_open,popen,parse_ini_file,show_source
default_charset = "UTF-8"
session.cookie_httponly = 1
@@ -13,6 +13,7 @@ session.cookie_secure = 1
session.use_strict_mode = 1
session.use_only_cookies = 1
+
; Opcache配置
opcache.enable=1
opcache.enable_cli=1
@@ -28,6 +29,7 @@ opcache.max_wasted_percentage=5
opcache.jit=tracing
opcache.jit_buffer_size=64M
+
; Realpath Cache配置
realpath_cache_size=4096k
realpath_cache_ttl=3600
diff --git a/ru/kejilion.sh b/ru/kejilion.sh
index 4be495e0f..aa8505e4f 100644
--- a/ru/kejilion.sh
+++ b/ru/kejilion.sh
@@ -3790,8 +3790,8 @@ new_ssh_port() {
# Резервное копирование файлов конфигурации SSH
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
@@ -4155,7 +4155,7 @@ dd_xitong() {
43)
send_stats "Переустановите Windows 7"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='windows 7 professional'
reboot
exit
;;
diff --git a/run_openclaw_manager_matrix.sh b/run_openclaw_manager_matrix.sh
new file mode 100755
index 000000000..95f34521c
--- /dev/null
+++ b/run_openclaw_manager_matrix.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+set -euo pipefail
+cd "$(dirname "$0")"
+
+IMAGES=(
+ "debian:12"
+ "debian:13"
+ "ubuntu:22.04"
+ "ubuntu:24.04"
+ "rockylinux:9"
+ "almalinux:9"
+ "fedora:41"
+)
+
+install_cmd() {
+ case "$1" in
+ debian:*|ubuntu:*)
+ echo 'apt-get update -y >/dev/null && DEBIAN_FRONTEND=noninteractive apt-get install -y bash jq grep sed coreutils >/dev/null'
+ ;;
+ rockylinux:*|almalinux:*|fedora:*)
+ echo 'dnf install -y bash jq grep sed coreutils >/dev/null'
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+for img in "${IMAGES[@]}"; do
+ echo "===== $img ====="
+ cmd=$(install_cmd "$img")
+ if docker run --rm -v "$PWD":/src -w /src "$img" bash -lc "$cmd && bash -n kejilion.sh && ./tests_openclaw_manager_smoke.sh"; then
+ echo "RESULT $img OK"
+ else
+ echo "RESULT $img FAIL"
+ fi
+ echo
+ done
diff --git a/tests/openclaw/README.md b/tests/openclaw/README.md
new file mode 100644
index 000000000..d5f22e511
--- /dev/null
+++ b/tests/openclaw/README.md
@@ -0,0 +1,36 @@
+# OpenClaw tests
+
+这里的脚本用于对 `kejilion.sh` 中的 OpenClaw 相关功能做最小回归与冒烟测试,主要覆盖:
+
+- API 模型同步/协议探测
+- 记忆菜单
+- 插件/技能菜单
+- API 列表颜色/对齐展示(最小验证)
+
+## 运行方式
+
+在仓库根目录执行(推荐):
+
+```bash
+bash tests/openclaw/tests_openclaw_api_protocol_detect_smoke.sh
+bash tests/openclaw/tests_openclaw_api_sync_diff_smoke.sh
+bash tests/openclaw/tests_openclaw_memory_menu_smoke.sh
+bash tests/openclaw/tests_openclaw_plugin_skill_menu_smoke.sh
+bash tests/openclaw/tests_openclaw_api_color_align_min.sh
+```
+
+也可在任意工作目录执行:
+
+```bash
+/path/to/repo/tests/openclaw/tests_openclaw_memory_menu_smoke.sh
+```
+
+## 安全说明
+
+- 脚本会创建临时工作目录与临时 `HOME`(位于 `/tmp`),并在结束后自动清理(除非设置 `KEEP_WORKDIR=true`)。
+- 运行过程使用 stub/模拟命令,不会改动真实 OpenClaw 配置或系统状态。
+- 如需保留临时目录用于排查,请执行:
+
+```bash
+KEEP_WORKDIR=true bash tests/openclaw/tests_openclaw_memory_menu_smoke.sh
+```
diff --git a/tests/openclaw/tests_change_model_dual_probe_smoke.sh b/tests/openclaw/tests_change_model_dual_probe_smoke.sh
new file mode 100755
index 000000000..877da234d
--- /dev/null
+++ b/tests/openclaw/tests_change_model_dual_probe_smoke.sh
@@ -0,0 +1,130 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-change-model-dual-probe-$$"
+mkdir -p "$WORKDIR/home/.openclaw"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+python3 - "$SCRIPT" "$WORKDIR" <<'PY'
+import json
+import re
+import sys
+from pathlib import Path
+
+script_path = Path(sys.argv[1])
+workdir = Path(sys.argv[2])
+text = script_path.read_text(encoding='utf-8')
+match = re.search(r'\n\t\topenclaw_model_probe\(\) \{.*?\n\t\t\}', text, re.S)
+if not match:
+ raise SystemExit('openclaw_model_probe not found')
+func = match.group(0).strip('\n')
+
+config = {
+ "models": {
+ "providers": {
+ "demo": {
+ "baseUrl": "https://api.example.com/v1",
+ "apiKey": "sk-test"
+ }
+ }
+ }
+}
+(workdir / 'home' / '.openclaw').mkdir(parents=True, exist_ok=True)
+(workdir / 'home' / '.openclaw' / 'openclaw.json').write_text(json.dumps(config, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
+
+harness = f'''#!/usr/bin/env bash
+set -euo pipefail
+HOME="{workdir / 'home'}"
+OPENCLAW_PROBE_STATUS=""
+OPENCLAW_PROBE_MESSAGE=""
+OPENCLAW_PROBE_LATENCY=""
+OPENCLAW_PROBE_REPLY=""
+{func}
+'''
+(workdir / 'harness.sh').write_text(harness, encoding='utf-8')
+PY
+
+run_case() {
+ local case_name="$1"
+ CASE_NAME="$case_name" WORKDIR="$WORKDIR" bash <<'EOF_CASE'
+set -euo pipefail
+source "$WORKDIR/harness.sh"
+
+python3() {
+ if [ "$1" != "-" ]; then
+ command python3 "$@"
+ return
+ fi
+
+ local script
+ script=$(cat)
+ shift
+
+ CASE_NAME="$CASE_NAME" command python3 - "$script" "$@" <<'PYWRAP'
+import io
+import json
+import os
+import sys
+import urllib.error
+import urllib.request
+from pathlib import Path
+
+script = sys.argv[1]
+args = sys.argv[2:]
+case = os.environ['CASE_NAME']
+
+class Resp(io.BytesIO):
+ def __init__(self, data, code=200):
+ super().__init__(data)
+ self.status = code
+ def __enter__(self):
+ return self
+ def __exit__(self, exc_type, exc, tb):
+ return False
+
+real_urlopen = urllib.request.urlopen
+
+def fake_urlopen(req, timeout=0):
+ url = getattr(req, 'full_url', '')
+ if case == 'fallback-success':
+ if url.endswith('/chat/completions'):
+ raise urllib.error.HTTPError(url, 404, 'Not Found', {}, io.BytesIO(b'{"error":"chat disabled"}'))
+ if url.endswith('/responses'):
+ body = json.dumps({"output": [{"content": [{"text": "pong from responses"}]}]}).encode('utf-8')
+ return Resp(body, 200)
+ elif case == 'all-fail':
+ if url.endswith('/chat/completions'):
+ raise urllib.error.HTTPError(url, 404, 'Not Found', {}, io.BytesIO(b'{"error":"chat disabled"}'))
+ if url.endswith('/responses'):
+ raise urllib.error.HTTPError(url, 404, 'Not Found', {}, io.BytesIO(b'{"error":"responses disabled"}'))
+ return real_urlopen(req, timeout=timeout)
+
+urllib.request.urlopen = fake_urlopen
+sys.argv = ['python3'] + args
+ns = {'__name__': '__main__'}
+exec(compile(script, '', 'exec'), ns, ns)
+PYWRAP
+}
+
+set +e
+openclaw_model_probe "demo/test-model"
+probe_rc=$?
+set -e
+printf 'RC=%s\nSTATUS=%s\nMESSAGE=%s\nLATENCY=%s\nREPLY=%s\n' "$probe_rc" "$OPENCLAW_PROBE_STATUS" "$OPENCLAW_PROBE_MESSAGE" "$OPENCLAW_PROBE_LATENCY" "$OPENCLAW_PROBE_REPLY"
+EOF_CASE
+}
+
+out1=$(run_case fallback-success)
+printf '%s\n' "$out1" | grep -q 'STATUS=OK'
+printf '%s\n' "$out1" | grep -q 'MESSAGE=/responses -> HTTP 200'
+printf '%s\n' "$out1" | grep -q 'REPLY=pong from responses'
+
+out2=$(run_case all-fail || true)
+printf '%s\n' "$out2" | grep -q 'RC=1'
+printf '%s\n' "$out2" | grep -q 'STATUS=FAIL'
+printf '%s\n' "$out2" | grep -q '/responses -> HTTP 404 / exit 22;/chat/completions -> HTTP 404 / exit 22'
+
+printf '%s\n' 'SMOKE_OK'
diff --git a/tests/openclaw/tests_openclaw_api_color_align_min.sh b/tests/openclaw/tests_openclaw_api_color_align_min.sh
new file mode 100755
index 000000000..5005d0927
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_api_color_align_min.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# 模拟脚本原生颜色变量
+# shellcheck disable=SC2034
+gl_hong='\033[31m'
+# shellcheck disable=SC2034
+gl_lv='\033[32m'
+# shellcheck disable=SC2034
+gl_huang='\033[33m'
+# shellcheck disable=SC2034
+gl_bai='\033[0m'
+
+printf '%s\n' '--- OpenClaw API 列表(最小颜色/对齐验证) ---'
+printf '%-4s %-18s %-44s %-8s %-12s\n' '序号' '名称' 'API地址' '模型数量' '延迟/状态'
+printf '%s\n' '----------------------------------------------------------------------------------------------'
+
+# 模拟 Python 计算后的字段(纯数据,不含颜色码)
+while IFS=$'\t' read -r rec_type idx name base_url model_count latency_txt latency_level; do
+ [[ "$rec_type" == "ROW" ]] || continue
+
+ latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
+
+ # 颜色在 Shell 层拼接并输出
+ printf '%-4s %-18s %-44s %b%-8s%b %b%-12s%b\n' \
+ "$idx" "$name" "$base_url" \
+ "$gl_huang" "$model_count" "$gl_bai" \
+ "$latency_color" "$latency_txt" "$gl_bai"
+done <<'EOF'
+ROW 1. alpha-openai https://api.alpha.example/v1 128 120ms low
+ROW 2. beta-proxy https://api.beta.example/v1 64 1300ms medium
+ROW 3. gamma-down https://api.gamma.example/v1 0 不可用 unavailable
+ROW 4. delta-custom - 12 未检测 unchecked
+EOF
diff --git a/tests/openclaw/tests_openclaw_api_protocol_detect_smoke.sh b/tests/openclaw/tests_openclaw_api_protocol_detect_smoke.sh
new file mode 100755
index 000000000..3f615f728
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_api_protocol_detect_smoke.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-api-protocol-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+# 抽取 OpenClaw API 添加逻辑(只验证:不再做协议探测/自动纠正)
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+start_gateway() { return 0; }
+install() { return 0; }
+EOF_INNER
+
+awk 'BEGIN{p=0}
+ /add-all-models-from-provider\(\) \{/{p=1}
+ /openclaw_api_manage_list\(\) \{/{p=0}
+ p{print}
+' "$SCRIPT" >> "$WORKDIR/harness.sh"
+
+awk 'BEGIN{p=0}
+ /openclaw_detect_api_protocol_by_provider\(\) \{/{p=1}
+ /fix-openclaw-provider-protocol-interactive\(\) \{/{p=0}
+ p{print}
+' "$SCRIPT" >> "$WORKDIR/harness.sh"
+
+chmod +x "$WORKDIR/harness.sh"
+
+# stub: curl
+cat > "$WORKDIR/bin/curl" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -e
+args="$*"
+if [[ "$args" == *"/models"* ]]; then
+ cat <<'JSON'
+{"data": [{"id": "gpt-4o"}, {"id": "gpt-4o-mini"}]}
+JSON
+ exit 0
+fi
+printf ""
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/curl"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{
+ "models": {
+ "mode": "merge",
+ "providers": {}
+ },
+ "agents": {
+ "defaults": {
+ "models": {}
+ }
+ }
+}
+JSON
+
+source "$WORKDIR/harness.sh"
+
+# 添加 provider,不应依赖协议探测;若脚本内部仍调用已删除函数会报错
+add-all-models-from-provider "demo" "https://api.example.com/v1" "sk-test"
+
+echo "SMOKE_OK: no protocol probing required during add"
+
+# 协议修复函数现在不再修改配置,只输出提示
+openclaw_detect_api_protocol_by_provider "$HOME/.openclaw/openclaw.json" "demo" >/dev/null 2>&1 || true
+
+echo "SMOKE_OK: protocol switch is available"
diff --git a/tests/openclaw/tests_openclaw_api_sync_diff_smoke.sh b/tests/openclaw/tests_openclaw_api_sync_diff_smoke.sh
new file mode 100755
index 000000000..327f879bc
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_api_sync_diff_smoke.sh
@@ -0,0 +1,133 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-api-sync-diff-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+start_gateway() { return 0; }
+install() { return 0; }
+EOF_INNER
+
+awk 'BEGIN{p=0}
+ /sync_openclaw_api_models\(\) \{/{p=1}
+ /install_moltbot\(\) \{/{p=0}
+ p{print}
+' "$SCRIPT" >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+
+cat > "$WORKDIR/bin/python3" <<'EOF_INNER'
+#!/usr/bin/env bash
+if [[ "$1" != "-" ]]; then
+ exec /usr/bin/python3 "$@"
+fi
+SCRIPT=$(cat)
+export SCRIPT
+/usr/bin/python3 - "${@:2}" <<"PY_STDIN"
+import io
+import json
+import os
+import urllib.request
+import urllib.error
+
+class _Resp(io.BytesIO):
+ def __init__(self, data, code=200):
+ super().__init__(data)
+ self._code = code
+ def getcode(self):
+ return self._code
+ def __enter__(self):
+ return self
+ def __exit__(self, exc_type, exc, tb):
+ return False
+
+_real = urllib.request.urlopen
+
+def urlopen(req, timeout=0):
+ url = getattr(req, "full_url", "") or getattr(req, "get_full_url", lambda: "")()
+ if url.endswith("/models"):
+ data = json.dumps({"data": [{"id": "alpha"}, {"id": "beta"}]}).encode("utf-8")
+ return _Resp(data, 200)
+ if url.endswith("/responses") or url.endswith("/chat/completions"):
+ raise urllib.error.HTTPError(url, 404, "Not Found", {}, None)
+ return _real(req, timeout=timeout)
+
+urllib.request.urlopen = urlopen
+script = os.environ.get("SCRIPT", "")
+exec(compile(script, "", "exec"))
+PY_STDIN
+exit $?
+EOF_INNER
+chmod +x "$WORKDIR/bin/python3"
+
+cat > "$WORKDIR/bin/curl" <<'EOF_INNER'
+#!/usr/bin/env bash
+# fake curl for /models, /responses, /chat/completions (for shell probes)
+set -e
+args="$*"
+if [[ "$args" == *"/responses"* ]]; then
+ printf "404"
+ exit 0
+fi
+if [[ "$args" == *"/chat/completions"* ]]; then
+ printf "404"
+ exit 0
+fi
+printf "000"
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/curl"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+export ENABLE_STATS=false
+export sh_v=testing
+
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "demo": {
+ "api": "openai-completions",
+ "baseUrl": "https://api.example.com/v1",
+ "apiKey": "sk-test",
+ "models": [
+ {"id": "alpha", "name": "demo / alpha"},
+ {"id": "gamma", "name": "demo / gamma"}
+ ]
+ }
+ }
+ },
+ "agents": {
+ "defaults": {
+ "models": {
+ "demo/alpha": {},
+ "demo/gamma": {}
+ },
+ "model": "demo/alpha"
+ }
+ }
+}
+JSON
+
+source "$WORKDIR/harness.sh"
+
+output=$(sync_openclaw_api_models 2>&1 || true)
+
+printf '%s\n' "$output" | grep -q "➕ 新增模型(1):"
+printf '%s\n' "$output" | grep -q "^ + beta$"
+printf '%s\n' "$output" | grep -q "➖ 删除模型(1):"
+printf '%s\n' "$output" | grep -q "^ - gamma$"
+
+printf '%s\n' "SMOKE_OK"
diff --git a/tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh b/tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh
new file mode 100644
index 000000000..f4036af28
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_memory_auto_setup_smoke.sh
@@ -0,0 +1,237 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-memory-auto-setup-test-$$"
+mkdir -p "$WORKDIR/bin"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+# 抽取 OpenClaw 记忆菜单实现
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+EOF_INNER
+
+awk 'BEGIN{p=0} /openclaw_memory_config_file\(\) \{/{p=1} /openclaw_memory_menu\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+# stub: openclaw
+cat > "$WORKDIR/bin/openclaw" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+cmd="$*"
+config_dir="$HOME/.openclaw/config"
+mkdir -p "$config_dir"
+key_file() {
+ local key="$1"
+ echo "$config_dir/${key//./_}"
+}
+if [[ "$cmd" == "config get"* ]]; then
+ key="$3"
+ file=$(key_file "$key")
+ if [ -f "$file" ]; then
+ cat "$file"
+ fi
+ exit 0
+fi
+if [[ "$cmd" == "config set"* ]]; then
+ key="$3"
+ val="$4"
+ file=$(key_file "$key")
+ echo "$val" > "$file"
+ exit 0
+fi
+if [[ "$1" == "memory" && "$2" == "status" ]]; then
+ agent="main"
+ while [ $# -gt 0 ]; do
+ if [ "$1" = "--agent" ] && [ $# -ge 2 ]; then
+ agent="$2"
+ shift 2
+ continue
+ fi
+ shift
+ done
+ cat <> "${HOME}/.openclaw/index_calls"
+ echo "index ok"
+ exit 0
+fi
+echo "mock openclaw $*"
+EOF_INNER
+chmod +x "$WORKDIR/bin/openclaw"
+
+# stub: curl
+cat > "$WORKDIR/bin/curl" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+args=("$@")
+
+is_head=0
+for arg in "${args[@]}"; do
+ if [ "$arg" = "-I" ]; then
+ is_head=1
+ fi
+done
+url="${args[$((${#args[@]}-1))]}"
+
+for arg in "${args[@]}"; do
+ if [[ "$arg" == *"ipinfo.io/country"* ]]; then
+ echo "${FAKE_COUNTRY:-US}"
+ exit 0
+ fi
+ if [[ "$arg" == *"bun.sh/install"* ]]; then
+ cat <<'SCRIPT'
+mkdir -p "$HOME/.bun/bin"
+cat > "$HOME/.bun/bin/bun" <<'BUN'
+#!/usr/bin/env bash
+set -euo pipefail
+if [[ "$1" == "install" && "$2" == "-g" ]]; then
+ mkdir -p "$HOME/.bun/bin"
+ cat > "$HOME/.bun/bin/qmd" <<'QMD'
+#!/usr/bin/env bash
+exit 0
+QMD
+ chmod +x "$HOME/.bun/bin/qmd"
+fi
+echo "$*" >> "$HOME/.openclaw/bun_calls"
+exit 0
+BUN
+chmod +x "$HOME/.bun/bin/bun"
+SCRIPT
+ exit 0
+ fi
+done
+
+if [ "$is_head" = "1" ]; then
+ if [[ "$url" == *"huggingface.co"* ]]; then
+ exit ${FAKE_HF_OK:-0}
+ fi
+ if [[ "$url" == *"hf-mirror.com"* ]]; then
+ exit ${FAKE_MIRROR_OK:-0}
+ fi
+ exit 0
+fi
+
+dest=""
+for ((i=0;i<${#args[@]};i++)); do
+ if [ "${args[$i]}" = "-o" ] && [ $((i+1)) -lt ${#args[@]} ]; then
+ dest="${args[$((i+1))]}"
+ fi
+done
+
+if [ -n "$dest" ] && [[ "$url" == *"embeddinggemma"* ]]; then
+ mkdir -p "$(dirname "$dest")"
+ echo "model" > "$dest"
+ echo "$url" >> "$HOME/.openclaw/download_calls"
+ exit 0
+fi
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/curl"
+
+# stub: wget (fallback, not used in test)
+cat > "$WORKDIR/bin/wget" <<'EOF_INNER'
+#!/usr/bin/env bash
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/wget"
+
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+
+source "$WORKDIR/harness.sh"
+
+make_home() {
+ local name="$1"
+ local home="$WORKDIR/$name/home"
+ mkdir -p "$home/.openclaw/workspace/memory" "$home/.openclaw"
+ echo "$home"
+}
+
+run_auto() {
+ local input="$1"
+ local scheme="$2"
+ local outfile="$3"
+ printf "%b" "$input" | openclaw_memory_auto_setup_run "$scheme" >"$outfile"
+}
+
+# Case 1: qmd 不存在 -> 安装 -> 写入绝对路径 -> index
+HOME_QMD=$(make_home "qmd")
+export HOME="$HOME_QMD"
+FAKE_COUNTRY=US FAKE_HF_OK=0 FAKE_MIRROR_OK=1 \
+ run_auto "yes\n" "qmd" "$WORKDIR/out_qmd.txt"
+
+# Case 2: local 模型不存在 -> 下载 -> 写入 modelPath -> index (CN)
+HOME_LOCAL=$(make_home "local")
+export HOME="$HOME_LOCAL"
+FAKE_COUNTRY=CN FAKE_HF_OK=1 FAKE_MIRROR_OK=0 \
+ run_auto "yes\n" "local" "$WORKDIR/out_local.txt"
+
+# Case 3: 已存在 -> 跳过下载/安装
+HOME_SKIP=$(make_home "skip")
+export HOME="$HOME_SKIP"
+mkdir -p "$HOME/.openclaw/models/embedding" "$HOME/.openclaw/config"
+MODEL_EXIST="$HOME/.openclaw/models/embedding/embeddinggemma-300M-Q8_0.gguf"
+
+echo "local" > "$HOME/.openclaw/config/memory_backend"
+echo "local" > "$HOME/.openclaw/config/agents_defaults_memorySearch_provider"
+echo "$MODEL_EXIST" > "$HOME/.openclaw/config/agents_defaults_memorySearch_local_modelPath"
+
+touch "$MODEL_EXIST"
+
+# 预置 qmd
+mkdir -p "$HOME/.bun/bin"
+cat > "$HOME/.bun/bin/qmd" <<'QMD'
+#!/usr/bin/env bash
+exit 0
+QMD
+chmod +x "$HOME/.bun/bin/qmd"
+export PATH="$HOME/.bun/bin:$PATH"
+
+FAKE_COUNTRY=US FAKE_HF_OK=0 FAKE_MIRROR_OK=0 \
+ run_auto "yes\n" "local" "$WORKDIR/out_skip.txt"
+
+export WORKDIR
+python3 - <<'PY'
+import os
+from pathlib import Path
+
+workdir = os.environ['WORKDIR']
+
+# Case1: qmd command absolute + index called
+home_qmd = Path(workdir) / 'qmd' / 'home'
+qmd_cmd = (home_qmd / '.openclaw/config/memory_qmd_command').read_text().strip()
+if not qmd_cmd.startswith('/'):
+ raise SystemExit('qmd command not absolute')
+index_calls = (home_qmd / '.openclaw/index_calls').read_text()
+if 'memory index' not in index_calls or '--force' not in index_calls:
+ raise SystemExit('qmd preheat index not called')
+
+# Case2: local model downloaded + modelPath written
+home_local = Path(workdir) / 'local' / 'home'
+model_path = (home_local / '.openclaw/config/agents_defaults_memorySearch_local_modelPath').read_text().strip()
+if not Path(model_path).exists():
+ raise SystemExit('model file not downloaded')
+if 'hf-mirror.com' not in (Path(workdir) / 'out_local.txt').read_text():
+ raise SystemExit('mirror not used for CN')
+
+# Case3: skip download
+home_skip = Path(workdir) / 'skip' / 'home'
+download_calls = home_skip / '.openclaw/download_calls'
+if download_calls.exists():
+ raise SystemExit('download should be skipped when model exists')
+print('SMOKE_OK')
+PY
diff --git a/tests/openclaw/tests_openclaw_memory_menu_smoke.sh b/tests/openclaw/tests_openclaw_memory_menu_smoke.sh
new file mode 100755
index 000000000..74fa768a1
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_memory_menu_smoke.sh
@@ -0,0 +1,230 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-memory-menu-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw/workspace/memory"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+# 抽取 OpenClaw 记忆菜单实现
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+EOF_INNER
+
+awk 'BEGIN{p=0} /openclaw_memory_config_file\(\) \{/{p=1} /openclaw_memory_menu\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+awk 'BEGIN{p=0} /openclaw_memory_menu\(\) \{/{p=1} /openclaw_backup_restore_menu\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+# stub: openclaw
+cat > "$WORKDIR/bin/openclaw" <<'EOF_INNER'
+#!/usr/bin/env bash
+cmd="$*"
+if [[ "$cmd" == "config get"* ]]; then
+ key="$3"
+ case "$key" in
+ memory.backend) echo "qmd" ;;
+ memory.qmd.includeDefaultMemory)
+ if [ -f "${HOME}/.openclaw/includeDefaultMemory" ]; then
+ cat "${HOME}/.openclaw/includeDefaultMemory"
+ else
+ echo "true"
+ fi
+ ;;
+ memory.qmd.command) echo "qmd" ;;
+ agents.defaults.memorySearch.local.modelPath) echo "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf" ;;
+ agents.defaults.memorySearch.provider) echo "local" ;;
+ *) echo "" ;;
+ esac
+ exit 0
+fi
+if [[ "$cmd" == "config set"* ]]; then
+ key="$3"
+ val="$4"
+ if [[ "$key" == "memory.qmd.includeDefaultMemory" ]]; then
+ echo "$val" > "${HOME}/.openclaw/includeDefaultMemory"
+ fi
+ if [[ "$key" == "memory.backend" ]]; then
+ echo "$val" > "${HOME}/.openclaw/backend"
+ fi
+ if [[ "$key" == "memory.qmd.command" ]]; then
+ echo "$val" > "${HOME}/.openclaw/qmd_command"
+ fi
+ if [[ "$key" == "agents.defaults.memorySearch.provider" ]]; then
+ echo "$val" > "${HOME}/.openclaw/provider"
+ fi
+ exit 0
+fi
+if [[ "$1" == "memory" && "$2" == "status" ]]; then
+ agent="main"
+ while [ $# -gt 0 ]; do
+ if [ "$1" = "--agent" ] && [ $# -ge 2 ]; then
+ agent="$2"
+ shift 2
+ continue
+ fi
+ shift
+ done
+ cat <> "${HOME}/.openclaw/index_calls"
+ echo "index ok"
+ exit 0
+fi
+if [[ "$cmd" == "gateway restart" ]]; then
+ echo "$cmd" >> "${HOME}/.openclaw/gateway_calls"
+ echo "gateway restarted"
+ exit 0
+fi
+if [[ "$cmd" == "gateway stop" ]]; then
+ echo "gateway stopped"
+ exit 0
+fi
+if [[ "$cmd" == "gateway start" ]]; then
+ echo "gateway started"
+ exit 0
+fi
+echo "mock openclaw $*"
+EOF_INNER
+chmod +x "$WORKDIR/bin/openclaw"
+
+# stub: qmd
+cat > "$WORKDIR/bin/qmd" <<'EOF_INNER'
+#!/usr/bin/env bash
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/qmd"
+
+# stub: curl (用于网络探测)
+cat > "$WORKDIR/bin/curl" <<'EOF_INNER'
+#!/usr/bin/env bash
+if [[ "$*" == *"huggingface.co"* ]]; then
+ exit 1
+fi
+if [[ "$*" == *"hf-mirror.com"* ]]; then
+ exit 0
+fi
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/curl"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+
+# minimal openclaw.json
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{
+ "memory": {
+ "backend": "qmd",
+ "qmd": {"command": "qmd", "includeDefaultMemory": true}
+ },
+ "agents": {
+ "defaults": {
+ "memorySearch": {
+ "provider": "local",
+ "local": {
+ "modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
+ }
+ }
+ }
+ }
+}
+JSON
+
+# mock index sqlite
+mkdir -p "$HOME/.openclaw/workspace/memory"
+touch "$HOME/.openclaw/workspace/memory/index.sqlite"
+
+# mock memory files
+cat > "$HOME/.openclaw/workspace/MEMORY.md" <<'TXT'
+# MEMORY
+line1
+line2
+line3
+TXT
+cat > "$HOME/.openclaw/workspace/memory/2026-03-11.md" <<'TXT'
+# 2026-03-11
+foo
+bar
+TXT
+
+source "$WORKDIR/harness.sh"
+
+run_menu() {
+ local input="$1"
+ local title="$2"
+ local outfile="$3"
+ echo "[TEST] $title"
+ printf "%b" "$input" | openclaw_memory_menu >"$outfile"
+}
+
+# 1) 状态展示(直接返回)
+run_menu "0\n" "status" "$WORKDIR/out_status.txt"
+# 2) 更新索引(增量)
+run_menu "1\nyes\n\n\n0\n" "index" "$WORKDIR/out_index.txt"
+# 3) 查看记忆文件(列表+查看)
+run_menu "2\n1\n\n\n\n\n0\n0\n" "files" "$WORKDIR/out_files.txt"
+# 4) 索引修复(检测到 includeDefaultMemory=false → 恢复为 true 并重建)
+echo "false" > "$HOME/.openclaw/includeDefaultMemory"
+run_menu "3\ny\n\n\n0\n" "fix" "$WORKDIR/out_fix.txt"
+# 5) 记忆方案(自动推荐并取消)
+run_menu "4\n1\n\n0\n0\n" "scheme" "$WORKDIR/out_scheme.txt"
+
+# 6) Store 缺失时仍执行 rebuild
+: > "$HOME/.openclaw/index_calls"
+OPENCLAW_STORE="~/.openclaw/workspace/memory/missing.sqlite" \
+ openclaw_memory_rebuild_index_safe >"$WORKDIR/out_missing.txt"
+
+export WORKDIR
+python3 - <<'PY'
+import glob
+import os
+
+flag_path = os.path.expanduser('~/.openclaw/includeDefaultMemory')
+if not os.path.exists(flag_path):
+ raise SystemExit('includeDefaultMemory not updated')
+flag = open(flag_path, 'r', encoding='utf-8').read().strip()
+if flag != 'true':
+ raise SystemExit('includeDefaultMemory not restored to true')
+# ensure backup created
+baks = glob.glob(os.path.expanduser('~/.openclaw/workspace/memory/index.sqlite.bak.*'))
+if not baks:
+ raise SystemExit('index sqlite backup not created')
+# ensure missing store still triggers force index
+calls_path = os.path.expanduser('~/.openclaw/index_calls')
+if not os.path.exists(calls_path):
+ raise SystemExit('index calls not recorded')
+with open(calls_path, 'r', encoding='utf-8') as fh:
+ calls = fh.read()
+if 'memory index' not in calls or '--force' not in calls:
+ raise SystemExit('force index not called for missing store')
+# ensure gateway restart called after rebuild
+gw_calls_path = os.path.expanduser('~/.openclaw/gateway_calls')
+if not os.path.exists(gw_calls_path):
+ raise SystemExit('gateway restart not recorded')
+with open(gw_calls_path, 'r', encoding='utf-8') as fh:
+ gw_calls = fh.read()
+if 'gateway restart' not in gw_calls:
+ raise SystemExit('gateway restart not called after rebuild')
+# ensure warning emitted
+with open(os.path.join(os.environ['WORKDIR'], 'out_missing.txt'), 'r', encoding='utf-8') as fh:
+ out = fh.read()
+if 'Store 原始值' not in out:
+ raise SystemExit('missing store warning not found')
+if '索引已重建并自动重启网关' not in out:
+ raise SystemExit('auto restart message not found')
+print('SMOKE_OK')
+PY
diff --git a/tests/openclaw/tests_openclaw_memory_multi_agent_smoke.sh b/tests/openclaw/tests_openclaw_memory_multi_agent_smoke.sh
new file mode 100644
index 000000000..3ce983b2a
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_memory_multi_agent_smoke.sh
@@ -0,0 +1,150 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-memory-multi-agent-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw/workspace/memory"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+EOF_INNER
+
+awk 'BEGIN{p=0} /openclaw_memory_config_file\(\) \{/{p=1} /openclaw_permission_config_file\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+cat > "$WORKDIR/bin/openclaw" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+cmd="$*"
+if [[ "$cmd" == "agents list --json" ]]; then
+ cat <<'JSON'
+[
+ {"id":"main","workspace":"~/.openclaw/workspace"},
+ {"id":"work","workspace":"~/.openclaw/workspace-work"}
+]
+JSON
+ exit 0
+fi
+if [[ "$cmd" == "config get"* ]]; then
+ key="$3"
+ case "$key" in
+ memory.backend) echo "qmd" ;;
+ memory.qmd.command) echo "qmd" ;;
+ agents.defaults.memorySearch.provider) echo "local" ;;
+ agents.defaults.memorySearch.local.modelPath) echo "$HOME/.openclaw/models/embedding/model.gguf" ;;
+ *) echo "" ;;
+ esac
+ exit 0
+fi
+if [[ "$cmd" == "config set"* || "$cmd" == "config unset"* ]]; then
+ exit 0
+fi
+if [[ "$1" == "memory" && "$2" == "status" ]]; then
+ agent="main"
+ while [ $# -gt 0 ]; do
+ if [ "$1" = "--agent" ] && [ $# -ge 2 ]; then
+ agent="$2"
+ shift 2
+ continue
+ fi
+ shift
+ done
+ if [ "$agent" = "work" ]; then
+ cat <> "$HOME/.openclaw/index_calls"
+ exit 0
+fi
+if [[ "$1" == "gateway" && "$2" == "restart" ]]; then
+ echo "$cmd" >> "$HOME/.openclaw/gateway_calls"
+ exit 0
+fi
+echo "mock openclaw $*"
+EOF_INNER
+chmod +x "$WORKDIR/bin/openclaw"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+
+mkdir -p "$HOME/.openclaw/workspace/memory" "$HOME/.openclaw/workspace-work/memory"
+touch "$HOME/.openclaw/workspace/memory/index.sqlite" "$HOME/.openclaw/workspace-work/memory/index.sqlite"
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{
+ "agents": {
+ "list": [
+ {"id": "work", "workspace": "~/.openclaw/workspace-work"}
+ ]
+ }
+}
+JSON
+cat > "$HOME/.openclaw/workspace/MEMORY.md" <<'TXT'
+# main memory
+TXT
+cat > "$HOME/.openclaw/workspace-work/MEMORY.md" <<'TXT'
+# work memory
+TXT
+cat > "$HOME/.openclaw/workspace-work/memory/2026-03-23.md" <<'TXT'
+# work note
+TXT
+
+source "$WORKDIR/harness.sh"
+
+openclaw_memory_render_status > "$WORKDIR/out_status.txt"
+openclaw_memory_file_render_list > "$WORKDIR/out_files.txt"
+openclaw_memory_prepare_workspace_all > "$WORKDIR/out_prepare.txt"
+openclaw_memory_rebuild_index_all > "$WORKDIR/out_rebuild.txt"
+
+export WORKDIR
+python3 - <<'PY'
+import os
+from pathlib import Path
+
+workdir = Path(os.environ['WORKDIR'])
+home = workdir / 'home'
+status_out = (workdir / 'out_status.txt').read_text(encoding='utf-8')
+files_out = (workdir / 'out_files.txt').read_text(encoding='utf-8')
+prepare_out = (workdir / 'out_prepare.txt').read_text(encoding='utf-8')
+rebuild_out = (workdir / 'out_rebuild.txt').read_text(encoding='utf-8')
+index_calls = (home / '.openclaw' / 'index_calls').read_text(encoding='utf-8')
+gateway_calls = (home / '.openclaw' / 'gateway_calls').read_text(encoding='utf-8')
+
+if 'Agent: main' not in status_out or 'Agent: work' not in status_out:
+ raise SystemExit('multi-agent status missing agent sections')
+if 'main/MEMORY.md' not in files_out or 'work/MEMORY.md' not in files_out:
+ raise SystemExit('multi-agent file list missing memory files')
+if 'work/memory/2026-03-23.md' not in files_out:
+ raise SystemExit('work daily memory file missing')
+if '检查并准备 2 个智能体工作区' not in prepare_out:
+ raise SystemExit('prepare summary missing agent count')
+if '--agent main --force' not in index_calls or '--agent work --force' not in index_calls:
+ raise SystemExit('force index not called for all agents')
+if 'gateway restart' not in gateway_calls:
+ raise SystemExit('gateway restart not called after multi-agent rebuild')
+if '已为 2 个智能体重建索引' not in rebuild_out:
+ raise SystemExit('multi-agent rebuild summary missing')
+print('SMOKE_OK')
+PY
diff --git a/tests/openclaw/tests_openclaw_plugin_skill_menu_smoke.sh b/tests/openclaw/tests_openclaw_plugin_skill_menu_smoke.sh
new file mode 100755
index 000000000..e6ea8d8d5
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_plugin_skill_menu_smoke.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+SCRIPT="$REPO_ROOT/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-plugin-skill-menu-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw/workspace/skills" "$WORKDIR/home/.openclaw"
+KEEP_WORKDIR=${KEEP_WORKDIR:-false}
+trap '[ "$KEEP_WORKDIR" = "true" ] || rm -rf "$WORKDIR"' EXIT
+
+cat > "$WORKDIR/harness.sh" <<'EOF_INNER'
+#!/usr/bin/env bash
+set -euo pipefail
+break_end() { return 0; }
+send_stats() { return 0; }
+start_gateway() { openclaw gateway stop; openclaw gateway start; }
+EOF_INNER
+
+# 抽取插件/技能管理实现
+awk 'BEGIN{p=0} /resolve_openclaw_plugin_id\(\) \{/{p=1} /openclaw_json_get_bool\(\) \{/{p=0} p{print}' "$SCRIPT" >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+# stub: openclaw
+cat > "$WORKDIR/bin/openclaw" <<'EOF_INNER'
+#!/usr/bin/env bash
+cmd="$*"
+if [[ "$cmd" == "plugins list" ]]; then
+ cat <> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+if [[ "$cmd" == "plugins install"* ]]; then
+ echo "install $*" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+if [[ "$cmd" == "plugins disable"* ]]; then
+ echo "disable $*" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+if [[ "$cmd" == "plugins uninstall"* ]]; then
+ # 模拟预装卸载失败
+ if [[ "$cmd" == *"feishu"* ]]; then
+ exit 1
+ fi
+ echo "uninstall $*" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+if [[ "$cmd" == "skills list" ]]; then
+ echo "skill1"
+ exit 0
+fi
+if [[ "$cmd" == "gateway stop" ]]; then
+ echo "gateway stop" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+if [[ "$cmd" == "gateway start" ]]; then
+ echo "gateway start" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+echo "mock openclaw $*" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+EOF_INNER
+chmod +x "$WORKDIR/bin/openclaw"
+
+# stub: npx
+cat > "$WORKDIR/bin/npx" <<'EOF_INNER'
+#!/usr/bin/env bash
+if [[ "$*" == *"clawhub uninstall"* ]]; then
+ echo "uninstall $*" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+if [[ "$*" == *"clawhub install"* ]]; then
+ echo "install $*" >> "${TEST_LOG:-/tmp/openclaw-plugin.log}"
+ exit 0
+fi
+exit 0
+EOF_INNER
+chmod +x "$WORKDIR/bin/npx"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+export TERM=xterm
+export TEST_LOG="$WORKDIR/test.log"
+
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{"plugins":{"allow":["telegram","feishu"]}}
+JSON
+
+# 模拟用户技能目录
+mkdir -p "$HOME/.openclaw/workspace/skills/skillA"
+
+source "$WORKDIR/harness.sh"
+
+# 插件安装:多输入,确保只重启一次
+printf "1\nfeishu telegram\n0\n" | install_plugin > "$WORKDIR/plugin_install.out"
+
+# 插件删除:多输入,确保删除流程与 allowlist 移除
+printf "2\nfeishu telegram\n0\n" | install_plugin > "$WORKDIR/plugin_delete.out"
+
+# 技能删除:二次确认 + 多输入,删除目录
+printf "2\nskillA skillB\ny\n0\n" | install_skill > "$WORKDIR/skill_delete.out"
+
+# 断言:start_gateway 只调用一次/每次菜单
+start_count=$(grep -c "gateway start" "$TEST_LOG" || true)
+if [ "$start_count" -ne 3 ]; then
+ echo "Expected 3 gateway starts (plugin install/delete + skill delete), got $start_count"
+ exit 1
+fi
+
+# 断言:用户技能目录已删除
+if [ -d "$HOME/.openclaw/workspace/skills/skillA" ]; then
+ echo "Skill directory was not removed"
+ exit 1
+fi
+
+# 断言:plugins.allow 移除
+if grep -q '"feishu"' "$HOME/.openclaw/openclaw.json"; then
+ echo "feishu still in plugins.allow"
+ exit 1
+fi
+
+# 断言:输出汇总
+grep -q "操作汇总" "$WORKDIR/plugin_install.out"
+grep -q "操作汇总" "$WORKDIR/plugin_delete.out"
+grep -q "操作汇总" "$WORKDIR/skill_delete.out"
+
+echo "SMOKE_OK"
diff --git a/tests/openclaw/tests_openclaw_sync_sessions_model_smoke.sh b/tests/openclaw/tests_openclaw_sync_sessions_model_smoke.sh
new file mode 100755
index 000000000..9e922b8c5
--- /dev/null
+++ b/tests/openclaw/tests_openclaw_sync_sessions_model_smoke.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repo_root=$(cd "$(dirname "$0")/../.." && pwd)
+script="$repo_root/kejilion.sh"
+
+workdir=$(mktemp -d)
+trap 'rm -rf "$workdir"' EXIT
+export HOME="$workdir/home"
+mkdir -p "$HOME/.openclaw/agents/main/sessions"
+mkdir -p "$HOME/.openclaw/agents/work/sessions"
+
+funcs_file="$workdir/funcs.sh"
+{
+ awk '/^[[:space:]]*openclaw_get_agents_dir\(\)/,/^[[:space:]]*\}$/' "$script"
+ echo
+ awk '/^[[:space:]]*openclaw_sync_sessions_model\(\)/,/^[[:space:]]*\}$/' "$script"
+} > "$funcs_file"
+
+cat > "$HOME/.openclaw/agents/main/sessions/sessions.json" <<'JSON'
+{
+ "agent:main:telegram:direct:123": {
+ "sessionId": "abc-123",
+ "modelOverride": "gpt-4",
+ "providerOverride": "openai"
+ },
+ "agent:main:main": {
+ "sessionId": "def-456"
+ }
+}
+JSON
+
+cat > "$HOME/.openclaw/agents/work/sessions/sessions.json" <<'JSON'
+{
+ "agent:work:slack:channel:C789": {
+ "sessionId": "ghi-789",
+ "modelOverride": "claude-3",
+ "providerOverride": "anthropic"
+ }
+}
+JSON
+
+source "$funcs_file"
+openclaw_sync_sessions_model "cli-api/gpt-5-pro"
+
+echo "=== VERIFICATION ==="
+
+main_file="$HOME/.openclaw/agents/main/sessions/sessions.json"
+work_file="$HOME/.openclaw/agents/work/sessions/sessions.json"
+
+main_model=$(jq -r '."agent:main:telegram:direct:123".modelOverride' "$main_file")
+main_provider=$(jq -r '."agent:main:telegram:direct:123".providerOverride' "$main_file")
+main2_model=$(jq -r '."agent:main:main".modelOverride' "$main_file")
+main2_provider=$(jq -r '."agent:main:main".providerOverride // "null"' "$main_file")
+
+work_model=$(jq -r '."agent:work:slack:channel:C789".modelOverride' "$work_file")
+work_provider=$(jq -r '."agent:work:slack:channel:C789".providerOverride' "$work_file")
+
+[ "$main_model" = "gpt-5-pro" ] || { echo "FAIL: main model (got: $main_model)"; exit 1; }
+[ "$main_provider" = "cli-api" ] || { echo "FAIL: main provider (got: $main_provider)"; exit 1; }
+[ "$main2_model" = "gpt-5-pro" ] || { echo "FAIL: main2 model (got: $main2_model)"; exit 1; }
+[ "$main2_provider" = "cli-api" ] || { echo "FAIL: main2 provider (got: $main2_provider)"; exit 1; }
+[ "$work_model" = "gpt-5-pro" ] || { echo "FAIL: work model (got: $work_model)"; exit 1; }
+[ "$work_provider" = "cli-api" ] || { echo "FAIL: work provider (got: $work_provider)"; exit 1; }
+
+echo "PASS: openclaw_sync_sessions_model smoke"
diff --git a/tests/tests_openclaw_config_path_resolution_smoke.sh b/tests/tests_openclaw_config_path_resolution_smoke.sh
new file mode 100755
index 000000000..eeb671014
--- /dev/null
+++ b/tests/tests_openclaw_config_path_resolution_smoke.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repo_root=$(cd "$(dirname "$0")/.." && pwd)
+script="$repo_root/kejilion.sh"
+
+workdir=$(mktemp -d)
+trap 'rm -rf "$workdir"' EXIT
+export HOME="$workdir/home"
+mkdir -p "$HOME/.openclaw"
+
+python_extract() {
+python3 - "$script" <<'PY'
+import re, sys
+text = open(sys.argv[1], encoding='utf-8').read()
+patterns = [
+ r'openclaw_get_config_file\(\) \{.*?\n\t\t\}',
+ r'openclaw_memory_config_file\(\) \{.*?\n\t\}',
+ r'openclaw_permission_config_file\(\) \{.*?\n\t\}',
+]
+for pat in patterns:
+ m = re.search(pat, text, re.S)
+ if not m:
+ raise SystemExit(f'missing pattern: {pat}')
+ print(m.group(0))
+ print()
+PY
+}
+
+snippet="$workdir/snippet.sh"
+{
+ echo '#!/usr/bin/env bash'
+ echo 'set -euo pipefail'
+ python_extract
+ cat <<'SH'
+main() {
+ echo "CFG=$(openclaw_get_config_file)"
+ echo "MEM=$(openclaw_memory_config_file)"
+ echo "PERM=$(openclaw_permission_config_file)"
+}
+main "$@"
+SH
+} > "$snippet"
+chmod +x "$snippet"
+
+assert_eq() {
+ local got="$1" expected="$2" msg="$3"
+ if [ "$got" != "$expected" ]; then
+ echo "ASSERT FAIL: $msg"
+ echo "got: $got"
+ echo "exp: $expected"
+ exit 1
+ fi
+}
+
+user_cfg="$HOME/.openclaw/openclaw.json"
+root_cfg="$workdir/root/.openclaw/openclaw.json"
+mkdir -p "$(dirname "$root_cfg")"
+
+out=$("$snippet")
+assert_eq "$(printf '%s\n' "$out" | sed -n '1p')" "CFG=$user_cfg" 'default config path should be user path'
+assert_eq "$(printf '%s\n' "$out" | sed -n '2p')" "MEM=$user_cfg" 'memory config path should follow helper'
+assert_eq "$(printf '%s\n' "$out" | sed -n '3p')" "PERM=$user_cfg" 'permission config path should follow helper'
+
+printf '{}\n' > "$root_cfg"
+rm -f "$user_cfg"
+out=$("$snippet")
+assert_eq "$(printf '%s\n' "$out" | sed -n '1p')" "CFG=$user_cfg" 'non-root should not silently fallback to root config'
+assert_eq "$(printf '%s\n' "$out" | sed -n '2p')" "MEM=$user_cfg" 'memory config should stay in user path for non-root'
+assert_eq "$(printf '%s\n' "$out" | sed -n '3p')" "PERM=$user_cfg" 'permission config should stay in user path for non-root'
+
+mkdir -p "$(dirname "$user_cfg")"
+printf '{"user":true}\n' > "$user_cfg"
+out=$("$snippet")
+assert_eq "$(printf '%s\n' "$out" | sed -n '1p')" "CFG=$user_cfg" 'user config should take precedence'
+
+echo 'PASS: openclaw config path resolution smoke'
diff --git a/tests_openclaw_manager_smoke.sh b/tests_openclaw_manager_smoke.sh
new file mode 100755
index 000000000..f2e5cf046
--- /dev/null
+++ b/tests_openclaw_manager_smoke.sh
@@ -0,0 +1,163 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
+SCRIPT="$REPO_DIR/kejilion.sh"
+WORKDIR="${TMPDIR:-/tmp}/openclaw-manager-test-$$"
+mkdir -p "$WORKDIR/bin" "$WORKDIR/home/.openclaw"
+trap 'rm -rf "$WORKDIR"' EXIT
+
+cat > "$WORKDIR/harness.sh" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+install() { return 0; }
+break_end() { :; }
+send_stats() { :; }
+EOF
+sed -n '10597,10766p' "$SCRIPT" >> "$WORKDIR/harness.sh"
+printf '\n' >> "$WORKDIR/harness.sh"
+sed -n '12815,13534p' "$SCRIPT" >> "$WORKDIR/harness.sh"
+printf '\n' >> "$WORKDIR/harness.sh"
+sed -n '13781,13825p' "$SCRIPT" >> "$WORKDIR/harness.sh"
+printf '\n' >> "$WORKDIR/harness.sh"
+chmod +x "$WORKDIR/harness.sh"
+
+cat > "$WORKDIR/bin/curl" <<'EOF'
+#!/usr/bin/env bash
+for arg in "$@"; do
+ if [ "$arg" = "-I" ]; then
+ exit 0
+ fi
+done
+if [[ "$*" == *"/models"* ]]; then
+ cat < "$WORKDIR/bin/openclaw" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+cmd="${1:-}"
+shift || true
+log_file="$HOME/.openclaw/mock_openclaw.log"
+echo "openclaw $cmd $*" >> "$log_file"
+case "$cmd" in
+ dashboard)
+ echo "Dashboard: http://127.0.0.1:18789/#token=deadbeef"
+ ;;
+ config)
+ sub="${1:-}"
+ shift || true
+ config_file="$HOME/.openclaw/mock_config.env"
+ touch "$config_file"
+ case "$sub" in
+ set)
+ key="$1"
+ shift || true
+ value="$*"
+ grep -v "^${key}=" "$config_file" > "${config_file}.tmp" || true
+ echo "${key}=${value}" >> "${config_file}.tmp"
+ mv "${config_file}.tmp" "$config_file"
+ ;;
+ get)
+ key="$1"
+ if grep -q "^${key}=" "$config_file"; then
+ awk -F'=' -v k="$key" '$1==k {print substr($0, index($0, "=")+1); exit}' "$config_file"
+ fi
+ ;;
+ unset)
+ key="$1"
+ grep -v "^${key}=" "$config_file" > "${config_file}.tmp" || true
+ mv "${config_file}.tmp" "$config_file"
+ ;;
+ *)
+ echo "mock openclaw config $sub $*"
+ ;;
+ esac
+ ;;
+ memory)
+ sub="${1:-}"
+ shift || true
+ case "$sub" in
+ status)
+ echo "Provider: builtin"
+ echo "Vector: ok"
+ echo "Indexed: 0/0"
+ echo "Workspace: $HOME/.openclaw/workspace"
+ ;;
+ index)
+ echo "mock memory index $*"
+ ;;
+ *)
+ echo "mock openclaw memory $sub $*"
+ ;;
+ esac
+ ;;
+ gateway)
+ sub="${1:-}"
+ shift || true
+ if [ "$sub" = "restart" ]; then
+ echo "mock gateway restart"
+ else
+ echo "mock openclaw gateway $sub $*"
+ fi
+ ;;
+ *)
+ echo "mock openclaw $cmd $*"
+ ;;
+esac
+EOF
+chmod +x "$WORKDIR/bin/openclaw"
+
+export HOME="$WORKDIR/home"
+export PATH="$WORKDIR/bin:$PATH"
+cat > "$HOME/.openclaw/openclaw.json" <<'JSON'
+{"models":{"mode":"merge","providers":{}}}
+JSON
+
+mkdir -p /home/web/conf.d
+cat > /home/web/conf.d/test-openclaw.conf <<'EOF'
+server {
+ listen 443 ssl;
+ server_name claw.example.com;
+ location / {
+ proxy_pass http://127.0.0.1:18789;
+ }
+}
+EOF
+
+source "$WORKDIR/harness.sh"
+
+echo '[TEST] add-all-models-from-provider'
+add-all-models-from-provider "cli-api" "https://example.com/v1" "dummy-token" >/tmp/add-models.out
+jq -e '.models.providers["cli-api"].models | length == 3' "$HOME/.openclaw/openclaw.json" >/dev/null
+jq -r '.models.providers["cli-api"].models[].id' "$HOME/.openclaw/openclaw.json"
+
+echo '[TEST] openclaw_find_webui_domain'
+openclaw_find_webui_domain
+
+echo '[TEST] openclaw_show_webui_addr'
+openclaw_show_webui_addr
+
+echo '[TEST] openclaw_memory_auto_setup_run local'
+mkdir -p "$HOME/.openclaw/models/embedding"
+touch "$HOME/.openclaw/models/embedding/embeddinggemma-300M-Q8_0.gguf"
+echo "memory.local=legacy" > "$HOME/.openclaw/mock_config.env"
+printf "yes\n" | openclaw_memory_auto_setup_run "local" >/tmp/memory-auto.out
+
+grep -q '^memory.backend=builtin' "$HOME/.openclaw/mock_config.env"
+grep -q '^agents.defaults.memorySearch.provider=local' "$HOME/.openclaw/mock_config.env"
+if grep -q '^memory.local=' "$HOME/.openclaw/mock_config.env"; then
+ echo "memory.local should be removed"
+ exit 1
+fi
+
+grep -q 'openclaw memory index --force' "$HOME/.openclaw/mock_openclaw.log"
+grep -q 'openclaw gateway restart' "$HOME/.openclaw/mock_openclaw.log"
+
+echo 'SMOKE_OK'
diff --git a/translate.py b/translate.py
index 08fa7e575..96c5ae508 100644
--- a/translate.py
+++ b/translate.py
@@ -9,94 +9,84 @@ def is_chinese(text):
return bool(re.search(r'[\u4e00-\u9fff]', text))
def translate_text(text, target_lang):
+ if not text.strip() or not is_chinese(text):
+ return text
try:
- return GoogleTranslator(source='zh-CN', target=target_lang).translate(text)
+ # 过滤掉一些不该翻译的特殊符号
+ clean_text = text.strip()
+ result = GoogleTranslator(source='zh-CN', target=target_lang).translate(clean_text)
+ return result
except Exception as e:
- print(f"\nTranslation error: {e}")
+ print(f"\n[Error] {e}")
return text
-def translate_line_preserving_variables(line, target_lang):
+def process_content_with_vars(content, target_lang):
"""
- Translate only Chinese parts in echo/read/send_stats commands, excluding shell variables
+ 核心逻辑:保护 ${var} 和 $var,翻译其中的中文部分
"""
- # Match double or single quoted strings
- def repl(match):
- full_string = match.group(0)
- quote = full_string[0]
- content = full_string[1:-1]
- # Split by variable expressions
- parts = re.split(r'(\$\{?\w+\}?)', content)
- translated_parts = [
- translate_text(p, target_lang) if is_chinese(p) else p
- for p in parts
- ]
- return quote + ''.join(translated_parts) + quote
-
- return re.sub(r'(?:\'[^\']*\'|"[^"]*")', repl, line)
+ # 匹配 ${var} 或 $var (字母数字下划线)
+ parts = re.split(r'(\$\{\w+\}|\$\w+)', content)
+ translated_parts = []
+ for p in parts:
+ if p.startswith('$'): # 变量部分,保持原样
+ translated_parts.append(p)
+ elif is_chinese(p): # 中文部分,翻译
+ translated_parts.append(translate_text(p, target_lang))
+ else: # 其他英文/符号,保持原样
+ translated_parts.append(p)
+ return "".join(translated_parts)
+
+def universal_translator(line, target_lang):
+ """
+ 通用翻译引擎:识别行内所有引号内容并翻译
+ """
+ # 1. 保护注释行
+ leading_space = re.match(r'^(\s*)', line).group(1)
+ stripped = line.strip()
+ if stripped.startswith('#'):
+ comment_content = stripped[1:].strip()
+ if is_chinese(comment_content):
+ return f"{leading_space}# {translate_text(comment_content, target_lang)}\n"
+ return line
+
+ # 2. 识别所有引号内的内容 (双引号或单引号)
+ # 使用正则匹配引号对,同时处理转义引号 \"
+ def replacer(match):
+ quote_type = match.group(1) # ' 或 "
+ content = match.group(2) # 引号内的文本内容
+ if is_chinese(content):
+ # 翻译内容,但保护里面的变量
+ translated = process_content_with_vars(content, target_lang)
+ return f"{quote_type}{translated}{quote_type}"
+ return match.group(0)
+
+ # 匹配 "content" 或 'content'
+ new_line = re.sub(r'([\'"])(.*?)(? Google翻译语言代码
- languages = {
- 'en': 'en', # 英语
- 'tw': 'zh-TW', # 繁体中文
- 'kr': 'ko', # 韩语
- 'jp': 'ja' # 日语
- }
-
- success_count = 0
-
- for dir_name, lang_code in languages.items():
- output_file = f'{dir_name}/kejilion.sh'
- if translate_file(input_file, output_file, lang_code):
- success_count += 1
- print(f"✓ Successfully translated to {dir_name}")
- else:
- print(f"✗ Failed to translate to {dir_name}")
- print("-" * 50)
-
- print(f"\nTranslation summary: {success_count}/{len(languages)} languages completed")
-
- if success_count == 0:
- sys.exit(1)
+ langs = {'en': 'en', 'tw': 'zh-TW', 'kr': 'ko', 'jp': 'ja'}
+ for dir_name, lang_code in langs.items():
+ translate_file(input_file, f'{dir_name}/kejilion.sh', lang_code)
diff --git a/tw/kejilion.sh b/tw/kejilion.sh
index 6d28b8d24..c3e430e7b 100644
--- a/tw/kejilion.sh
+++ b/tw/kejilion.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-sh_v="4.0.3"
+sh_v="4.5.2"
gl_hui='\e[37m'
@@ -29,6 +29,8 @@ else
gh_proxy="https://"
fi
+gh_https_url="https://"
+
}
quanju_canshu
@@ -45,6 +47,8 @@ run_command() {
canshu_v6() {
if grep -q '^canshu="V6"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
+ elif grep -q '^canshu="V6"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^canshu="default"/canshu="V6"/' ~/kejilion.sh
fi
}
@@ -52,14 +56,16 @@ canshu_v6() {
CheckFirstRun_true() {
if grep -q '^permission_granted="true"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
+ elif grep -q '^permission_granted="true"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^permission_granted="false"/permission_granted="true"/' ~/kejilion.sh
fi
}
-# 收集功能埋點信息的函數,記錄當前腳本版本號,使用時間,系統版本,CPU架構,機器所在國家和用戶使用的功能名稱,絕對不涉及任何敏感信息,請放心!請相信我!
-# 為什麼要設計這個功能,目的更好的了解用戶喜歡使用的功能,進一步優化功能推出更多符合用戶需求的功能。
-# 全文可搜搜 send_stats 函數調用位置,透明開源,如有顧慮可拒絕使用。
+# 收集功能埋藏資訊的函數,記錄當前腳本版本號,使用時間,系統版本,CPU架構,機器所在國家和用戶使用的功能名稱,絕對不涉及任何敏感信息,請放心!請相信我!
+# 為什麼要設計這個功能,目的更好的了解使用者喜歡使用的功能,進一步優化功能推出更多符合使用者需求的功能。
+# 全文可搜尋 send_stats 函數呼叫位置,透明開源,如有顧慮可拒絕使用。
@@ -86,6 +92,8 @@ yinsiyuanquan2() {
if grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
+elif grep -q '^ENABLE_STATS="false"' ~/kejilion.sh.bak > /dev/null 2>&1; then
+ sed -i 's/^ENABLE_STATS="true"/ENABLE_STATS="false"/' ~/kejilion.sh
fi
}
@@ -102,6 +110,7 @@ sed -i '/^alias k=/d' ~/.profile > /dev/null 2>&1
sed -i '/^alias k=/d' ~/.bash_profile > /dev/null 2>&1
cp -f ./kejilion.sh ~/kejilion.sh > /dev/null 2>&1
cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
@@ -111,14 +120,14 @@ CheckFirstRun_false() {
fi
}
-# 提示用戶同意條款
+# 提示使用者同意條款
UserLicenseAgreement() {
clear
echo -e "${gl_kjlan}歡迎使用科技lion腳本工具箱${gl_bai}"
- echo "首次使用腳本,請先閱讀並同意用戶許可協議。"
- echo "用戶許可協議: https://blog.kejilion.pro/user-license-agreement/"
+ echo "首次使用腳本,請先閱讀並同意使用者授權協議。"
+ echo "使用者授權協議: https://blog.kejilion.pro/user-license-agreement/"
echo -e "----------------------"
- read -r -p "是否同意以上條款? (y/n):" user_input
+ read -e -p "是否同意以上條款? (y/n):" user_input
if [ "$user_input" = "y" ] || [ "$user_input" = "Y" ]; then
@@ -154,7 +163,7 @@ public_ip=$(get_public_ip)
isp_info=$(curl -s --max-time 3 http://ipinfo.io/org)
-if echo "$isp_info" | grep -Eiq 'china|mobile|unicom|telecom'; then
+if echo "$isp_info" | grep -Eiq 'CHINANET|mobile|unicom|telecom'; then
ipv4_address=$(get_local_ip)
else
ipv4_address="$public_ip"
@@ -170,13 +179,13 @@ ipv6_address=$(curl -s --max-time 1 https://v6.ipinfo.io/ip && echo)
install() {
if [ $# -eq 0 ]; then
- echo "未提供軟件包參數!"
+ echo "未提供软件包参数!"
return 1
fi
for package in "$@"; do
if ! command -v "$package" &>/dev/null; then
- echo -e "${gl_huang}正在安裝$package...${gl_bai}"
+ echo -e "${gl_kjlan}正在安裝$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
dnf install -y epel-release
@@ -204,7 +213,7 @@ install() {
pkg update
pkg install -y "$package"
else
- echo "未知的包管理器!"
+ echo "未知的套件管理器!"
return 1
fi
fi
@@ -213,35 +222,45 @@ install() {
check_disk_space() {
+ local required_gb=$1
+ local path=${2:-/}
- required_gb=$1
- required_space_mb=$((required_gb * 1024))
- available_space_mb=$(df -m / | awk 'NR==2 {print $4}')
+ mkdir -p "$path"
- if [ $available_space_mb -lt $required_space_mb ]; then
- echo -e "${gl_huang}提示:${gl_bai}磁盤空間不足!"
- echo "當前可用空間: $((available_space_mb/1024))G"
+ local required_space_mb=$((required_gb * 1024))
+ local available_space_mb=$(df -m "$path" | awk 'NR==2 {print $4}')
+
+ if [ "$available_space_mb" -lt "$required_space_mb" ]; then
+ echo -e "${gl_huang}提示:${gl_bai}磁碟空間不足!"
+ echo "目前可用空間: $((available_space_mb/1024))G"
echo "最小需求空間:${required_gb}G"
- echo "無法繼續安裝,請清理磁盤空間後重試。"
- send_stats "磁盤空間不足"
+ echo "無法繼續安裝,請清理磁碟空間後重試。"
+ send_stats "磁碟空間不足"
break_end
kejilion
fi
}
+
install_dependency() {
+ switch_mirror false false
+ check_port
+ check_swap
+ prefer_ipv4
+ auto_optimize_dns
install wget unzip tar jq grep
+
}
remove() {
if [ $# -eq 0 ]; then
- echo "未提供軟件包參數!"
+ echo "未提供軟體包參數!"
return 1
fi
for package in "$@"; do
- echo -e "${gl_huang}正在卸載$package...${gl_bai}"
+ echo -e "${gl_kjlan}正在卸載$package...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf remove -y "$package"
elif command -v yum &>/dev/null; then
@@ -259,7 +278,7 @@ remove() {
elif command -v pkg &>/dev/null; then
pkg delete -y "$package"
else
- echo "未知的包管理器!"
+ echo "未知的套件管理器!"
return 1
fi
done
@@ -328,7 +347,7 @@ enable() {
/bin/systemctl enable "$SERVICE_NAME"
fi
- echo "$SERVICE_NAME已設置為開機自啟。"
+ echo "$SERVICE_NAME已設定為開機自啟動。"
}
@@ -349,22 +368,22 @@ kejilion() {
-check_port() {
- install lsof
+stop_containers_or_kill_process() {
+ local port=$1
+ local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
- stop_containers_or_kill_process() {
- local port=$1
- local containers=$(docker ps --filter "publish=$port" --format "{{.ID}}" 2>/dev/null)
+ if [ -n "$containers" ]; then
+ docker stop $containers
+ else
+ install lsof
+ for pid in $(lsof -t -i:$port); do
+ kill -9 $pid
+ done
+ fi
+}
- if [ -n "$containers" ]; then
- docker stop $containers
- else
- for pid in $(lsof -t -i:$port); do
- kill -9 $pid
- done
- fi
- }
+check_port() {
stop_containers_or_kill_process 80
stop_containers_or_kill_process 443
}
@@ -377,23 +396,23 @@ if [ "$country" = "CN" ]; then
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
- "https://docker-0.unsee.tech",
- "https://docker.1panel.live",
- "https://registry.dockermirror.com",
- "https://docker.imgdb.de",
- "https://docker.m.daocloud.io",
- "https://hub.firefly.store",
- "https://hub.littlediary.cn",
+ "https://docker.1ms.run",
+ "https://docker.m.ixdev.cn",
"https://hub.rat.dev",
- "https://dhub.kubesre.xyz",
- "https://cjie.eu.org",
- "https://docker.1panelproxy.com",
+ "https://dockerproxy.net",
+ "https://docker-registry.nmqu.com",
+ "https://docker.amingg.com",
"https://docker.hlmirror.com",
- "https://hub.fast360.xyz",
- "https://dockerpull.cn",
- "https://cr.laoyou.ip-ddns.com",
- "https://docker.melikeme.cn",
- "https://docker.kejilion.pro"
+ "https://hub1.nat.tf",
+ "https://hub2.nat.tf",
+ "https://hub3.nat.tf",
+ "https://docker.m.daocloud.io",
+ "https://docker.kejilion.pro",
+ "https://docker.367231.xyz",
+ "https://hub.1panel.dev",
+ "https://dockerproxy.cool",
+ "https://docker.apiba.cn",
+ "https://proxy.vvvv.ee"
]
}
EOF
@@ -407,80 +426,40 @@ restart docker
}
-install_add_docker_guanfang() {
+
+linuxmirrors_install_docker() {
+
local country=$(curl -s ipinfo.io/country)
if [ "$country" = "CN" ]; then
- cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/install && chmod +x install
- sh install --mirror Aliyun
- rm -f install
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source mirrors.huaweicloud.com/docker-ce \
+ --source-registry docker.1ms.run \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
else
- curl -fsSL https://get.docker.com | sh
+ bash <(curl -sSL https://linuxmirrors.cn/docker.sh) \
+ --source download.docker.com \
+ --source-registry registry.hub.docker.com \
+ --protocol https \
+ --use-intranet-source false \
+ --install-latest true \
+ --close-firewall false \
+ --ignore-backup-tips
fi
-install_add_docker_cn
+install_add_docker_cn
}
install_add_docker() {
- echo -e "${gl_huang}正在安裝docker環境...${gl_bai}"
- if [ -f /etc/os-release ] && grep -q "Fedora" /etc/os-release; then
- install_add_docker_guanfang
- elif command -v dnf &>/dev/null; then
- dnf update -y
- dnf install -y yum-utils device-mapper-persistent-data lvm2
- rm -f /etc/yum.repos.d/docker*.repo > /dev/null
- country=$(curl -s ipinfo.io/country)
- arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo | tee /etc/yum.repos.d/docker-ce.repo > /dev/null
- else
- yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo > /dev/null
- fi
- dnf install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
- elif [ -f /etc/os-release ] && grep -q "Kali" /etc/os-release; then
- apt update
- apt upgrade -y
- apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
- rm -f /usr/share/keyrings/docker-archive-keyring.gpg
- local country=$(curl -s ipinfo.io/country)
- local arch=$(uname -m)
- if [ "$country" = "CN" ]; then
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/etc\/apt\/keyrings\/docker-archive-keyring.gpg\] https:\/\/mirrors.aliyun.com\/docker-ce\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- else
- if [ "$arch" = "x86_64" ]; then
- sed -i '/^deb \[arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- elif [ "$arch" = "aarch64" ]; then
- sed -i '/^deb \[arch=arm64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg\] https:\/\/download.docker.com\/linux\/debian bullseye stable/d' /etc/apt/sources.list.d/docker.list > /dev/null
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker-archive-keyring.gpg > /dev/null
- echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- fi
- fi
- apt update
- apt install -y docker-ce docker-ce-cli containerd.io
- install_add_docker_cn
-
-
- elif command -v apt &>/dev/null || command -v yum &>/dev/null; then
- install_add_docker_guanfang
+ echo -e "${gl_kjlan}正在安裝docker環境...${gl_bai}"
+ if command -v apt &>/dev/null || command -v yum &>/dev/null || command -v dnf &>/dev/null; then
+ linuxmirrors_install_docker
else
install docker docker-compose
install_add_docker_cn
@@ -506,45 +485,45 @@ while true; do
echo ""
echo "容器操作"
echo "------------------------"
- echo "1. 創建新的容器"
+ echo "1. 建立新的容器"
echo "------------------------"
- echo "2. 啟動指定容器 6. 啟動所有容器"
- echo "3. 停止指定容器 7. 停止所有容器"
- echo "4. 刪除指定容器 8. 刪除所有容器"
- echo "5. 重啟指定容器 9. 重啟所有容器"
+ echo "2. 啟動指定容器 6. 啟動所有容器"
+ echo "3. 停止指定容器 7. 停止所有容器"
+ echo "4. 刪除指定容器 8. 刪除所有容器"
+ echo "5. 重啟指定容器 9. 重新啟動所有容器"
echo "------------------------"
- echo "11. 進入指定容器 12. 查看容器日誌"
- echo "13. 查看容器網絡 14. 查看容器佔用"
+ echo "11. 進入指定容器 12. 查看容器日誌"
+ echo "13. 查看容器網路 14. 查看容器佔用"
echo "------------------------"
- echo "15. 開啟容器端口訪問 16. 關閉容器端口訪問"
+ echo "15. 開啟容器連接埠存取 16. 關閉容器連接埠訪問"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
- send_stats "新建容器"
- read -e -p "請輸入創建命令:" dockername
+ send_stats "新容器"
+ read -e -p "請輸入建立命令:" dockername
$dockername
;;
2)
send_stats "啟動指定容器"
- read -e -p "請輸入容器名(多個容器名請用空格分隔):" dockername
+ read -e -p "請輸入容器名稱(多個容器名稱請以空格分隔):" dockername
docker start $dockername
;;
3)
send_stats "停止指定容器"
- read -e -p "請輸入容器名(多個容器名請用空格分隔):" dockername
+ read -e -p "請輸入容器名稱(多個容器名稱請以空格分隔):" dockername
docker stop $dockername
;;
4)
send_stats "刪除指定容器"
- read -e -p "請輸入容器名(多個容器名請用空格分隔):" dockername
+ read -e -p "請輸入容器名稱(多個容器名稱請以空格分隔):" dockername
docker rm -f $dockername
;;
5)
send_stats "重啟指定容器"
- read -e -p "請輸入容器名(多個容器名請用空格分隔):" dockername
+ read -e -p "請輸入容器名稱(多個容器名稱請以空格分隔):" dockername
docker restart $dockername
;;
6)
@@ -575,13 +554,13 @@ while true; do
;;
11)
send_stats "進入容器"
- read -e -p "請輸入容器名:" dockername
+ read -e -p "請輸入容器名稱:" dockername
docker exec -it $dockername /bin/sh
break_end
;;
12)
send_stats "查看容器日誌"
- read -e -p "請輸入容器名:" dockername
+ read -e -p "請輸入容器名稱:" dockername
docker logs $dockername
break_end
;;
@@ -590,7 +569,7 @@ while true; do
echo ""
container_ids=$(docker ps -q)
echo "------------------------------------------------------------"
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "容器名稱" "網路名稱" "IP位址"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
local container_name=$(echo "$container_info" | awk '{print $1}')
@@ -610,8 +589,8 @@ while true; do
;;
15)
- send_stats "允許容器端口訪問"
- read -e -p "請輸入容器名:" docker_name
+ send_stats "允許容器連接埠訪問"
+ read -e -p "請輸入容器名稱:" docker_name
ip_address
clear_container_rules "$docker_name" "$ipv4_address"
local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
@@ -620,8 +599,8 @@ while true; do
;;
16)
- send_stats "阻止容器端口訪問"
- read -e -p "請輸入容器名:" docker_name
+ send_stats "阻止容器連接埠訪問"
+ read -e -p "請輸入容器名稱:" docker_name
ip_address
block_container_port "$docker_name" "$ipv4_address"
local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
@@ -640,14 +619,14 @@ done
docker_image() {
while true; do
clear
- send_stats "Docker鏡像管理"
- echo "Docker鏡像列表"
+ send_stats "Docker映像管理"
+ echo "Docker镜像列表"
docker image ls
echo ""
echo "鏡像操作"
echo "------------------------"
- echo "1. 獲取指定鏡像 3. 刪除指定鏡像"
- echo "2. 更新指定鏡像 4. 刪除所有鏡像"
+ echo "1. 取得指定鏡像 3. 刪除指定鏡像"
+ echo "2. 更新指定鏡像 4. 刪除所有鏡像"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -655,23 +634,23 @@ while true; do
case $sub_choice in
1)
send_stats "拉取鏡像"
- read -e -p "請輸入鏡像名(多個鏡像名請用空格分隔):" imagenames
+ read -e -p "請輸入鏡像名稱(多個鏡像名稱請以空格分隔):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}正在獲取鏡像:$name${gl_bai}"
+ echo -e "${gl_kjlan}正在取得鏡像:$name${gl_bai}"
docker pull $name
done
;;
2)
send_stats "更新鏡像"
- read -e -p "請輸入鏡像名(多個鏡像名請用空格分隔):" imagenames
+ read -e -p "請輸入鏡像名稱(多個鏡像名稱請以空格分隔):" imagenames
for name in $imagenames; do
- echo -e "${gl_huang}正在更新鏡像:$name${gl_bai}"
+ echo -e "${gl_kjlan}正在更新鏡像:$name${gl_bai}"
docker pull $name
done
;;
3)
send_stats "刪除鏡像"
- read -e -p "請輸入鏡像名(多個鏡像名請用空格分隔):" imagenames
+ read -e -p "請輸入鏡像名稱(多個鏡像名稱請以空格分隔):" imagenames
for name in $imagenames; do
docker rmi -f $name
done
@@ -754,16 +733,16 @@ install_crontab() {
service cron start
;;
*)
- echo "不支持的發行版:$ID"
+ echo "不支援的發行版:$ID"
return
;;
esac
else
- echo "無法確定操作系統。"
+ echo "無法確定作業系統。"
return
fi
- echo -e "${gl_lv}crontab 已安裝且 cron 服務正在運行。${gl_bai}"
+ echo -e "${gl_lv}crontab 已安裝且 cron 服務正在執行。${gl_bai}"
}
@@ -775,15 +754,15 @@ docker_ipv6_on() {
local CONFIG_FILE="/etc/docker/daemon.json"
local REQUIRED_IPV6_CONFIG='{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64"}'
- # 檢查配置文件是否存在,如果不存在則創建文件並寫入默認設置
+ # 檢查設定檔是否存在,如果不存在則建立檔案並寫入預設設定
if [ ! -f "$CONFIG_FILE" ]; then
echo "$REQUIRED_IPV6_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
else
- # 使用jq處理配置文件的更新
+ # 使用jq處理設定檔的更新
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # 檢查當前配置是否已經有 ipv6 設置
+ # 檢查目前配置是否已經有 ipv6 設定
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq '.ipv6 // false')
# 更新配置,開啟 IPv6
@@ -793,9 +772,9 @@ docker_ipv6_on() {
UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq '. + {"fixed-cidr-v6": "2001:db8:1::/64"}')
fi
- # 對比原始配置與新配置
+ # 比較原始配置與新配置
if [[ "$ORIGINAL_CONFIG" == "$UPDATED_CONFIG" ]]; then
- echo -e "${gl_huang}當前已開啟ipv6訪問${gl_bai}"
+ echo -e "${gl_huang}目前已開啟ipv6訪問${gl_bai}"
else
echo "$UPDATED_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
@@ -810,24 +789,24 @@ docker_ipv6_off() {
local CONFIG_FILE="/etc/docker/daemon.json"
- # 檢查配置文件是否存在
+ # 檢查設定檔是否存在
if [ ! -f "$CONFIG_FILE" ]; then
- echo -e "${gl_hong}配置文件不存在${gl_bai}"
+ echo -e "${gl_hong}設定檔不存在${gl_bai}"
return
fi
- # 讀取當前配置
+ # 讀取目前配置
local ORIGINAL_CONFIG=$(<"$CONFIG_FILE")
- # 使用jq處理配置文件的更新
+ # 使用jq處理設定檔的更新
local UPDATED_CONFIG=$(echo "$ORIGINAL_CONFIG" | jq 'del(.["fixed-cidr-v6"]) | .ipv6 = false')
- # 檢查當前的 ipv6 狀態
+ # 檢查目前的 ipv6 狀態
local CURRENT_IPV6=$(echo "$ORIGINAL_CONFIG" | jq -r '.ipv6 // false')
- # 對比原始配置與新配置
+ # 比較原始配置與新配置
if [[ "$CURRENT_IPV6" == "false" ]]; then
- echo -e "${gl_huang}當前已關閉ipv6訪問${gl_bai}"
+ echo -e "${gl_huang}目前已關閉ipv6訪問${gl_bai}"
else
echo "$UPDATED_CONFIG" | jq . > "$CONFIG_FILE"
restart docker
@@ -870,7 +849,7 @@ iptables_open() {
open_port() {
local ports=($@) # 将传入的参数转换为数组
if [ ${#ports[@]} -eq 0 ]; then
- echo "請提供至少一個端口號"
+ echo "請提供至少一個連接埠號"
return 1
fi
@@ -881,44 +860,44 @@ open_port() {
iptables -D INPUT -p tcp --dport $port -j DROP 2>/dev/null
iptables -D INPUT -p udp --dport $port -j DROP 2>/dev/null
- # 添加打開規則
+ # 新增開啟規則
if ! iptables -C INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j ACCEPT
fi
if ! iptables -C INPUT -p udp --dport $port -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j ACCEPT
- echo "已打開端口$port"
+ echo "已開啟連接埠$port"
fi
done
save_iptables_rules
- send_stats "已打開端口"
+ send_stats "已開啟連接埠"
}
close_port() {
local ports=($@) # 将传入的参数转换为数组
if [ ${#ports[@]} -eq 0 ]; then
- echo "請提供至少一個端口號"
+ echo "請提供至少一個連接埠號"
return 1
fi
install iptables
for port in "${ports[@]}"; do
- # 刪除已存在的打開規則
+ # 刪除已存在的開啟規則
iptables -D INPUT -p tcp --dport $port -j ACCEPT 2>/dev/null
iptables -D INPUT -p udp --dport $port -j ACCEPT 2>/dev/null
- # 添加關閉規則
+ # 新增關閉規則
if ! iptables -C INPUT -p tcp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p tcp --dport $port -j DROP
fi
if ! iptables -C INPUT -p udp --dport $port -j DROP 2>/dev/null; then
iptables -I INPUT 1 -p udp --dport $port -j DROP
- echo "已關閉端口$port"
+ echo "已關閉連接埠$port"
fi
done
@@ -931,14 +910,14 @@ close_port() {
iptables -I FORWARD 1 -i lo -j ACCEPT
save_iptables_rules
- send_stats "已關閉端口"
+ send_stats "已關閉連接埠"
}
allow_ip() {
local ips=($@) # 将传入的参数转换为数组
if [ ${#ips[@]} -eq 0 ]; then
- echo "請提供至少一個IP地址或IP段"
+ echo "請提供至少一個IP位址或IP段"
return 1
fi
@@ -948,7 +927,7 @@ allow_ip() {
# 刪除已存在的阻止規則
iptables -D INPUT -s $ip -j DROP 2>/dev/null
- # 添加允許規則
+ # 新增允許規則
if ! iptables -C INPUT -s $ip -j ACCEPT 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j ACCEPT
echo "已放行IP$ip"
@@ -962,7 +941,7 @@ allow_ip() {
block_ip() {
local ips=($@) # 将传入的参数转换为数组
if [ ${#ips[@]} -eq 0 ]; then
- echo "請提供至少一個IP地址或IP段"
+ echo "請提供至少一個IP位址或IP段"
return 1
fi
@@ -972,7 +951,7 @@ block_ip() {
# 刪除已存在的允許規則
iptables -D INPUT -s $ip -j ACCEPT 2>/dev/null
- # 添加阻止規則
+ # 新增阻止規則
if ! iptables -C INPUT -s $ip -j DROP 2>/dev/null; then
iptables -I INPUT 1 -s $ip -j DROP
echo "已阻止IP$ip"
@@ -1025,115 +1004,108 @@ disable_ddos_defense() {
# 管理國家IP規則的函數
manage_country_rules() {
local action="$1"
- local country_code="$2"
- local ipset_name="${country_code,,}_block"
- local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
+ shift # 去掉第一个参数,剩下的全是国家代码
install ipset
- case "$action" in
- block)
- # 如果 ipset 不存在則創建
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ for country_code in "$@"; do
+ local ipset_name="${country_code,,}_block"
+ local download_url="http://www.ipdeny.com/ipblocks/data/countries/${country_code,,}.zone"
- # 下載 IP 區域文件
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "錯誤:下載$country_code的 IP 區域文件失敗"
- exit 1
- fi
+ case "$action" in
+ block)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
- # 將 IP 添加到 ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "錯誤:下載$country_code的 IP 區域檔案失敗"
+ continue
+ fi
- # 使用 iptables 阻止 IP
- iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- iptables -I OUTPUT -m set --match-set "$ipset_name" dst -j DROP
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- echo "已成功阻止$country_code的 IP 地址"
- rm "${country_code,,}.zone"
- ;;
+ iptables -I INPUT -m set --match-set "$ipset_name" src -j DROP
- allow)
- # 為允許的國家創建 ipset(如果不存在)
- if ! ipset list "$ipset_name" &> /dev/null; then
- ipset create "$ipset_name" hash:net
- fi
+ echo "已成功阻止$country_code的 IP 位址"
+ rm "${country_code,,}.zone"
+ ;;
- # 下載 IP 區域文件
- if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
- echo "錯誤:下載$country_code的 IP 區域文件失敗"
- exit 1
- fi
+ allow)
+ if ! ipset list "$ipset_name" &> /dev/null; then
+ ipset create "$ipset_name" hash:net
+ fi
+
+ if ! wget -q "$download_url" -O "${country_code,,}.zone"; then
+ echo "錯誤:下載$country_code的 IP 區域檔案失敗"
+ continue
+ fi
- # 刪除現有的國家規則
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
- ipset flush "$ipset_name"
+ ipset flush "$ipset_name"
+ while IFS= read -r ip; do
+ ipset add "$ipset_name" "$ip" 2>/dev/null
+ done < "${country_code,,}.zone"
- # 將 IP 添加到 ipset
- while IFS= read -r ip; do
- ipset add "$ipset_name" "$ip"
- done < "${country_code,,}.zone"
- # 僅允許指定國家的 IP
- iptables -P INPUT DROP
- iptables -P OUTPUT DROP
- iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- iptables -A OUTPUT -m set --match-set "$ipset_name" dst -j ACCEPT
+ iptables -P INPUT DROP
+ iptables -A INPUT -m set --match-set "$ipset_name" src -j ACCEPT
- echo "已成功僅允許$country_code的 IP 地址"
- rm "${country_code,,}.zone"
- ;;
+ echo "已成功允許$country_code的 IP 位址"
+ rm "${country_code,,}.zone"
+ ;;
- unblock)
- # 刪除國家的 iptables 規則
- iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- iptables -D OUTPUT -m set --match-set "$ipset_name" dst -j DROP 2>/dev/null
+ unblock)
+ iptables -D INPUT -m set --match-set "$ipset_name" src -j DROP 2>/dev/null
- # 銷毀 ipset
- if ipset list "$ipset_name" &> /dev/null; then
- ipset destroy "$ipset_name"
- fi
+ if ipset list "$ipset_name" &> /dev/null; then
+ ipset destroy "$ipset_name"
+ fi
- echo "已成功解除$country_code的 IP 地址限制"
- ;;
+ echo "已成功解除$country_code的 IP 位址限制"
+ ;;
- *)
- ;;
- esac
+ *)
+ echo "用法: manage_country_rules {block|allow|unblock} "
+ ;;
+ esac
+ done
}
+
+
+
+
+
+
iptables_panel() {
root_use
install iptables
save_iptables_rules
while true; do
clear
- echo "高級防火牆管理"
- send_stats "高級防火牆管理"
+ echo "進階防火牆管理"
+ send_stats "進階防火牆管理"
echo "------------------------"
iptables -L INPUT
echo ""
echo "防火牆管理"
echo "------------------------"
- echo "1. 開放指定端口 2. 關閉指定端口"
- echo "3. 開放所有端口 4. 關閉所有端口"
+ echo "1. 開放指定連接埠 2. 關閉指定連接埠"
+ echo "3. 開放所有連接埠 4. 關閉所有連接埠"
echo "------------------------"
- echo "5. IP白名單 6. IP黑名單"
- echo "7. 清除指定IP"
+ echo "5. IP白名單 6. IP黑名單"
+ echo "7. 清除指定IP"
echo "------------------------"
- echo "11. 允許PING 12. 禁止PING"
+ echo "11. 允許PING 12. 禁止PING"
echo "------------------------"
- echo "13. 啟動DDOS防禦 14. 關閉DDOS防禦"
+ echo "13. 啟動DDOS防禦 14. 關閉DDOS防禦"
echo "------------------------"
- echo "15. 阻止指定國家IP 16. 僅允許指定國家IP"
+ echo "15. 阻止指定國家IP 16. 僅允許指定國家IP"
echo "17. 解除指定國家IP限制"
echo "------------------------"
echo "0. 返回上一級選單"
@@ -1141,17 +1113,17 @@ iptables_panel() {
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
- read -e -p "請輸入開放的端口號:" o_port
+ read -e -p "請輸入開放的連接埠號碼:" o_port
open_port $o_port
- send_stats "開放指定端口"
+ send_stats "開放指定連接埠"
;;
2)
- read -e -p "請輸入關閉的端口號:" c_port
+ read -e -p "請輸入關閉的連接埠號碼:" c_port
close_port $c_port
- send_stats "關閉指定端口"
+ send_stats "關閉指定連接埠"
;;
3)
- # 開放所有端口
+ # 開放所有連接埠
current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
iptables -F
iptables -X
@@ -1164,10 +1136,10 @@ iptables_panel() {
iptables -A FORWARD -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport $current_port -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "開放所有端口"
+ send_stats "開放所有連接埠"
;;
4)
- # 關閉所有端口
+ # 關閉所有連接埠
current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
iptables -F
iptables -X
@@ -1180,7 +1152,7 @@ iptables_panel() {
iptables -A FORWARD -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport $current_port -j ACCEPT
iptables-save > /etc/iptables/rules.v4
- send_stats "關閉所有端口"
+ send_stats "關閉所有連接埠"
;;
5)
@@ -1223,18 +1195,18 @@ iptables_panel() {
;;
15)
- read -e -p "請輸入阻止的國家代碼(如 CN, US, JP):" country_code
+ read -e -p "請輸入阻止的國家代碼(多個國家代碼可用空格隔開如 CN US JP):" country_code
manage_country_rules block $country_code
send_stats "允許國家$country_code的IP"
;;
16)
- read -e -p "請輸入允許的國家代碼(如 CN, US, JP):" country_code
+ read -e -p "請輸入允許的國家代碼(多個國家代碼可用空格隔開如 CN US JP):" country_code
manage_country_rules allow $country_code
send_stats "阻止國家$country_code的IP"
;;
17)
- read -e -p "請輸入清除的國家代碼(如 CN, US, JP):" country_code
+ read -e -p "請輸入清除的國家代碼(多個國家代碼可用空格隔開如 CN US JP):" country_code
manage_country_rules unblock $country_code
send_stats "清除國家$country_code的IP"
;;
@@ -1252,15 +1224,13 @@ iptables_panel() {
-
-
add_swap() {
local new_swap=$1 # 获取传入的参数
- # 獲取當前系統中所有的 swap 分區
+ # 取得目前系統中所有的 swap 分區
local swap_partitions=$(grep -E '^/dev/' /proc/swaps | awk '{print $1}')
- # 遍歷並刪除所有的 swap 分區
+ # 遍歷並刪除所有的 swap 分割區
for partition in $swap_partitions; do
swapoff "$partition"
wipefs -a "$partition"
@@ -1273,7 +1243,7 @@ add_swap() {
# 刪除舊的 /swapfile
rm -f /swapfile
- # 創建新的 swap 分區
+ # 建立新的 swap 分割區
fallocate -l ${new_swap}M /swapfile
chmod 600 /swapfile
mkswap /swapfile
@@ -1288,7 +1258,7 @@ add_swap() {
rc-update add local
fi
- echo -e "虛擬內存大小已調整為${gl_huang}${new_swap}${gl_bai}M"
+ echo -e "虛擬記憶體大小已調整為${gl_huang}${new_swap}${gl_bai}M"
}
@@ -1298,7 +1268,7 @@ check_swap() {
local swap_total=$(free -m | awk 'NR==3{print $2}')
-# 判斷是否需要創建虛擬內存
+# 判斷是否需要建立虛擬記憶體
[ "$swap_total" -gt 0 ] || add_swap 1024
@@ -1314,21 +1284,21 @@ local swap_total=$(free -m | awk 'NR==3{print $2}')
ldnmp_v() {
- # 獲取nginx版本
+ # 取得nginx版本
local nginx_version=$(docker exec nginx nginx -v 2>&1)
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e "nginx : ${gl_huang}v$nginx_version${gl_bai}"
- # 獲取mysql版本
+ # 取得mysql版本
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local mysql_version=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SELECT VERSION();" 2>/dev/null | tail -n 1)
echo -n -e " mysql : ${gl_huang}v$mysql_version${gl_bai}"
- # 獲取php版本
+ # 取得php版本
local php_version=$(docker exec php php -v 2>/dev/null | grep -oP "PHP \K[0-9]+\.[0-9]+\.[0-9]+")
echo -n -e " php : ${gl_huang}v$php_version${gl_bai}"
- # 獲取redis版本
+ # 取得redis版本
local redis_version=$(docker exec redis redis-server -v 2>&1 | grep -oP "v=+\K[0-9]+\.[0-9]+")
echo -e " redis : ${gl_huang}v$redis_version${gl_bai}"
@@ -1341,20 +1311,18 @@ ldnmp_v() {
install_ldnmp_conf() {
- # 創建必要的目錄和文件
- cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/redis web/log/nginx && touch web/docker-compose.yml
+ # 建立必要的目錄和文件
+ cd /home && mkdir -p web/html web/mysql web/certs web/conf.d web/stream.d web/redis web/log/nginx web/letsencrypt && touch web/docker-compose.yml
wget -O /home/web/nginx.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default10.conf
- wget -O /home/web/redis/valkey.conf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/valkey.conf
-
default_server_ssl
- # 下載 docker-compose.yml 文件並進行替換
+ # 下載 docker-compose.yml 檔案並進行替換
wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
dbrootpasswd=$(openssl rand -base64 16) ; dbuse=$(openssl rand -hex 4) ; dbusepasswd=$(openssl rand -base64 8)
- # 在 docker-compose.yml 文件中進行替換
+ # 在 docker-compose.yml 檔案中進行替換
sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
@@ -1362,31 +1330,69 @@ install_ldnmp_conf() {
}
+update_docker_compose_with_db_creds() {
+ cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
+ if ! grep -q "letsencrypt" /home/web/docker-compose.yml; then
+ wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
-install_ldnmp() {
+ dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+ dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
+
+ sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
+ sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
+ fi
+
+ if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
+ sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
+ fi
- check_swap
+}
- cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
- if ! grep -q "network_mode" /home/web/docker-compose.yml; then
- wget -O /home/web/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/LNMP-docker-compose-10.yml
- dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbuse=$(grep -oP 'MYSQL_USER:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- dbusepasswd=$(grep -oP 'MYSQL_PASSWORD:\s*\K.*' /home/web/docker-compose1.yml | tr -d '[:space:]')
- sed -i "s#webroot#$dbrootpasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilionYYDS#$dbusepasswd#g" /home/web/docker-compose.yml
- sed -i "s#kejilion#$dbuse#g" /home/web/docker-compose.yml
- fi
- if grep -q "kjlion/nginx:alpine" /home/web/docker-compose1.yml; then
- sed -i 's|kjlion/nginx:alpine|nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- sed -i 's|nginx:alpine|kjlion/nginx:alpine|g' /home/web/docker-compose.yml > /dev/null 2>&1
- fi
+auto_optimize_dns() {
+ # 取得國家代碼(如 CN、US 等)
+ local country=$(curl -s ipinfo.io/country)
+
+ # 根據國家設定 DNS
+ if [ "$country" = "CN" ]; then
+ local dns1_ipv4="223.5.5.5"
+ local dns2_ipv4="183.60.83.19"
+ local dns1_ipv6="2400:3200::1"
+ local dns2_ipv6="2400:da00::6666"
+ else
+ local dns1_ipv4="1.1.1.1"
+ local dns2_ipv4="8.8.8.8"
+ local dns1_ipv6="2606:4700:4700::1111"
+ local dns2_ipv6="2001:4860:4860::8888"
+ fi
+
+ set_dns
+
+
+}
+
+
+prefer_ipv4() {
+grep -q '^precedence ::ffff:0:0/96 100' /etc/gai.conf 2>/dev/null \
+ || echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
+echo "已切換為 IPv4 優先"
+send_stats "已切換為 IPv4 優先"
+}
+
+
+
+
+install_ldnmp() {
+
+ update_docker_compose_with_db_creds
cd /home/web && docker compose up -d
sleep 1
@@ -1395,9 +1401,17 @@ install_ldnmp() {
fix_phpfpm_conf php
fix_phpfpm_conf php74
- restart_ldnmp
+
+ # mysql調優
+ wget -O /home/custom_mysql_config.cnf ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/custom_mysql_config-1.cnf
+ docker cp /home/custom_mysql_config.cnf mysql:/etc/mysql/conf.d/
+ rm -rf /home/custom_mysql_config.cnf
+
+ restart_ldnmp
+ sleep 2
+
clear
echo "LDNMP環境安裝完畢"
echo "------------------------"
@@ -1422,13 +1436,12 @@ install_certbot() {
install_ssltls() {
docker stop nginx > /dev/null 2>&1
- check_port > /dev/null 2>&1
cd ~
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ ! -f "$file_path" ]; then
local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
- local ipv6_pattern='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?))))$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
if [[ ($yuming =~ $ipv4_pattern || $yuming =~ $ipv6_pattern) ]]; then
mkdir -p /etc/letsencrypt/live/$yuming/
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -1438,7 +1451,7 @@ install_ssltls() {
openssl req -x509 -key /etc/letsencrypt/live/$yuming/privkey.pem -out /etc/letsencrypt/live/$yuming/fullchain.pem -days 5475 -subj "/C=US/ST=State/L=City/O=Organization/OU=Organizational Unit/CN=Common Name"
fi
else
- docker run -it --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
+ docker run --rm -p 80:80 -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot certonly --standalone -d "$yuming" --email your@email.com --agree-tos --no-eff-email --force-renewal --key-type ecdsa
fi
fi
mkdir -p /home/web/certs/
@@ -1451,10 +1464,10 @@ install_ssltls() {
install_ssltls_text() {
- echo -e "${gl_huang}$yuming公鑰信息${gl_bai}"
+ echo -e "${gl_huang}$yuming公鑰資訊${gl_bai}"
cat /etc/letsencrypt/live/$yuming/fullchain.pem
echo ""
- echo -e "${gl_huang}$yuming私鑰信息${gl_bai}"
+ echo -e "${gl_huang}$yuming私鑰資訊${gl_bai}"
cat /etc/letsencrypt/live/$yuming/privkey.pem
echo ""
echo -e "${gl_huang}證書存放路徑${gl_bai}"
@@ -1468,14 +1481,14 @@ install_ssltls_text() {
add_ssl() {
-echo -e "${gl_huang}快速申請SSL證書,過期前自動續簽${gl_bai}"
+echo -e "${gl_huang}快速申請SSL證書,過期前自動續約${gl_bai}"
yuming="${1:-}"
if [ -z "$yuming" ]; then
add_yuming
fi
install_docker
install_certbot
-docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
install_ssltls_text
@@ -1485,7 +1498,7 @@ ssl_ps
ssl_ps() {
echo -e "${gl_huang}已申請的證書到期情況${gl_bai}"
- echo "站點信息 證書到期時間"
+ echo "網站資訊 證書到期時間"
echo "------------------------"
for cert_dir in /etc/letsencrypt/live/*; do
local cert_file="$cert_dir/fullchain.pem"
@@ -1524,22 +1537,79 @@ certs_status() {
local file_path="/etc/letsencrypt/live/$yuming/fullchain.pem"
if [ -f "$file_path" ]; then
- send_stats "域名證書申請成功"
+ send_stats "網域證書申請成功"
else
- send_stats "域名證書申請失敗"
+ send_stats "網域證書申請失敗"
echo -e "${gl_hong}注意:${gl_bai}證書申請失敗,請檢查以下可能原因並重試:"
- echo -e "1. 域名拼寫錯誤 ➠ 請檢查域名輸入是否正確"
- echo -e "2. DNS解析問題 ➠ 確認域名已正確解析到本服務器IP"
- echo -e "3. 網絡配置問題 ➠ 如使用Cloudflare Warp等虛擬網絡請暫時關閉"
- echo -e "4. 防火牆限制 ➠ 檢查80/443端口是否開放,確保驗證可訪問"
- echo -e "5. 申請次數超限 ➠ Let's Encrypt有每週限額(5次/域名/週)"
- echo -e "6. 國內備案限制 ➠ 中國大陸環境請確認域名是否備案"
- break_end
- clear
- echo "請再次嘗試部署$webname"
- add_yuming
- install_ssltls
- certs_status
+ echo -e "1. 網域拼字錯誤 ➠ 請檢查網域名稱輸入是否正確"
+ echo -e "2. DNS解析问题 ➠ 确认域名已正确解析到本服务器IP"
+ echo -e "3. 網路設定問題 ➠ 如使用Cloudflare Warp等虛擬網路請暫時關閉"
+ echo -e "4. 防火牆限制 ➠ 檢查80/443連接埠是否開放,確保驗證可存取"
+ echo -e "5. 申請次數超限 ➠ Let's Encrypt有每週限額(5次/網域/週)"
+ echo -e "6. 國內備案限制 ➠ 中國大陸環境請確認網域是否備案"
+ echo "------------------------"
+ echo "1. 重新申請 2. 匯入已有憑證 0. 退出"
+ echo "------------------------"
+ read -e -p "請輸入你的選擇:" sub_choice
+ case $sub_choice in
+ 1)
+ send_stats "重新申請"
+ echo "請再次嘗試部署$webname"
+ add_yuming
+ install_ssltls
+ certs_status
+
+ ;;
+ 2)
+ send_stats "導入已有證書"
+
+ # 定義檔案路徑
+ local cert_file="/home/web/certs/${yuming}_cert.pem"
+ local key_file="/home/web/certs/${yuming}_key.pem"
+
+ mkdir -p /home/web/certs
+
+ # 1. 輸入憑證 (ECC 和 RSA 憑證開頭都是 BEGIN CERTIFICATE)
+ echo "請貼上 證書 (CRT/PEM) 內容 (以兩次回車結束):"
+ local cert_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$cert_content" == *"-----BEGIN"* ]] && break
+ cert_content+="${line}"$'\n'
+ done
+
+ # 2. 輸入私鑰 (相容 RSA, ECC, PKCS#8)
+ echo "請貼上 憑證私鑰 (Private Key) 內容 (按兩次回車結束):"
+ local key_content=""
+ while IFS= read -r line; do
+ [[ -z "$line" && "$key_content" == *"-----BEGIN"* ]] && break
+ key_content+="${line}"$'\n'
+ done
+
+ # 3. 智能校驗
+ # 只要包含 "BEGIN CERTIFICATE" 和 "PRIVATE KEY" 即可透過
+ if [[ "$cert_content" == *"-----BEGIN CERTIFICATE-----"* && "$key_content" == *"PRIVATE KEY-----"* ]]; then
+ echo -n "$cert_content" > "$cert_file"
+ echo -n "$key_content" > "$key_file"
+
+ chmod 644 "$cert_file"
+ chmod 600 "$key_file"
+
+ # 識別目前證書類型並顯示
+ if [[ "$key_content" == *"EC PRIVATE KEY"* ]]; then
+ echo "偵測到 ECC 憑證已成功儲存。"
+ else
+ echo "偵測到 RSA 憑證已成功儲存。"
+ fi
+ auth_method="ssl_imported"
+ else
+ echo "錯誤:無效的憑證或私鑰格式!"
+ certs_status
+ fi
+ ;;
+ *)
+ exit
+ ;;
+ esac
fi
}
@@ -1547,7 +1617,7 @@ certs_status() {
repeat_add_yuming() {
if [ -e /home/web/conf.d/$yuming.conf ]; then
- send_stats "域名重複使用"
+ send_stats "網域重複使用"
web_del "${yuming}" > /dev/null 2>&1
fi
@@ -1556,11 +1626,45 @@ fi
add_yuming() {
ip_address
- echo -e "先將域名解析到本機IP:${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
- read -e -p "請輸入你的IP或者解析過的域名:" yuming
+ echo -e "先將網域名稱解析到本機IP:${gl_huang}$ipv4_address $ipv6_address${gl_bai}"
+ read -e -p "請輸入你的IP或解析過的網域名稱:" yuming
+}
+
+
+check_ip_and_get_access_port() {
+ local yuming="$1"
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ read -e -p "請輸入訪問/監聽端口,回車預設使用 80:" access_port
+ access_port=${access_port:-80}
+ fi
+}
+
+
+
+update_nginx_listen_port() {
+ local yuming="$1"
+ local access_port="$2"
+ local conf="/home/web/conf.d/${yuming}.conf"
+
+ # 如果 access_port 為空,則跳過
+ [ -z "$access_port" ] && return 0
+
+ # 刪除所有 listen 行
+ sed -i '/^[[:space:]]*listen[[:space:]]\+/d' "$conf"
+
+ # 在 server { 後插入新的 listen
+ sed -i "/server {/a\\
+ listen ${access_port};\\
+ listen [::]:${access_port};
+" "$conf"
}
+
add_db() {
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbname="${dbname}"
@@ -1571,28 +1675,8 @@ add_db() {
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "CREATE DATABASE $dbname; GRANT ALL PRIVILEGES ON $dbname.* TO \"$dbuse\"@\"%\";"
}
-reverse_proxy() {
- ip_address
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
- sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$ipv4_address/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$duankou|g" /home/web/conf.d/$yuming.conf
- nginx_http_on
- docker exec nginx nginx -s reload
-}
-
-
-restart_redis() {
- rm -rf /home/web/redis/*
- docker exec redis redis-cli FLUSHALL > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory 1gb > /dev/null 2>&1
- # docker exec -it redis redis-cli CONFIG SET maxmemory-policy allkeys-lru > /dev/null 2>&1
-}
-
-
restart_ldnmp() {
- restart_redis
docker exec nginx chown -R nginx:nginx /var/www/html > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/proxy > /dev/null 2>&1
docker exec nginx mkdir -p /var/cache/nginx/fastcgi > /dev/null 2>&1
@@ -1600,7 +1684,8 @@ restart_ldnmp() {
docker exec nginx chown -R nginx:nginx /var/cache/nginx/fastcgi > /dev/null 2>&1
docker exec php chown -R www-data:www-data /var/www/html > /dev/null 2>&1
docker exec php74 chown -R www-data:www-data /var/www/html > /dev/null 2>&1
- cd /home/web && docker compose restart nginx php php74
+ cd /home/web && docker compose restart
+
}
@@ -1635,13 +1720,13 @@ phpmyadmin_upgrade() {
cd /home/web/
docker rm -f $ldnmp_pods > /dev/null 2>&1
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
- curl -sS -O https://raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/docker/refs/heads/main/docker-compose.phpmyadmin.yml
docker compose -f docker-compose.phpmyadmin.yml up -d
clear
ip_address
check_docker_app_ip
- echo "登錄信息:"
+ echo "登入資訊:"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
echo
@@ -1655,29 +1740,29 @@ cf_purge_cache() {
local EMAIL
local ZONE_IDS
- # 檢查配置文件是否存在
+ # 檢查設定檔是否存在
if [ -f "$CONFIG_FILE" ]; then
- # 從配置文件讀取 API_TOKEN 和 zone_id
+ # 從設定檔讀取 API_TOKEN 和 zone_id
read API_TOKEN EMAIL ZONE_IDS < "$CONFIG_FILE"
# 將 ZONE_IDS 轉換為數組
ZONE_IDS=($ZONE_IDS)
else
- # 提示用戶是否清理緩存
- read -e -p "需要清理 Cloudflare 的緩存嗎? (y/n):" answer
+ # 提示使用者是否清理快取
+ read -e -p "需要清理 Cloudflare 的快取嗎? (y/n):" answer
if [[ "$answer" == "y" ]]; then
- echo "CF信息保存在$CONFIG_FILE,可以後期修改CF信息"
+ echo "CF資訊保存在$CONFIG_FILE,可以後期修改CF訊息"
read -e -p "請輸入你的 API_TOKEN:" API_TOKEN
read -e -p "請輸入你的CF用戶名:" EMAIL
- read -e -p "請輸入 zone_id(多個用空格分隔):" -a ZONE_IDS
+ read -e -p "请输入 zone_id(多个用空格分隔): " -a ZONE_IDS
mkdir -p /home/web/config/
echo "$API_TOKEN $EMAIL ${ZONE_IDS[*]}" > "$CONFIG_FILE"
fi
fi
- # 循環遍歷每個 zone_id 並執行清除緩存命令
+ # 循環遍歷每個 zone_id 並執行清除快取命令
for ZONE_ID in "${ZONE_IDS[@]}"; do
- echo "正在清除緩存 for zone_id:$ZONE_ID"
+ echo "正在清除快取 for zone_id:$ZONE_ID"
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $API_TOKEN" \
@@ -1685,16 +1770,15 @@ cf_purge_cache() {
--data '{"purge_everything":true}'
done
- echo "緩存清除請求已發送完畢。"
+ echo "快取清除請求已發送完畢。"
}
web_cache() {
- send_stats "清理站點緩存"
+ send_stats "清理網站快取"
cf_purge_cache
cd /home/web && docker compose restart
- restart_redis
}
@@ -1704,25 +1788,25 @@ web_del() {
send_stats "刪除站點數據"
yuming_list="${1:-}"
if [ -z "$yuming_list" ]; then
- read -e -p "刪除站點數據,請輸入你的域名(多個域名用空格隔開):" yuming_list
+ read -e -p "刪除站點數據,請輸入你的網域名稱(多個網域以空格隔開):" yuming_list
if [[ -z "$yuming_list" ]]; then
return
fi
fi
for yuming in $yuming_list; do
- echo "正在刪除域名:$yuming"
+ echo "正在刪除網域名稱:$yuming"
rm -r /home/web/html/$yuming > /dev/null 2>&1
rm /home/web/conf.d/$yuming.conf > /dev/null 2>&1
rm /home/web/certs/${yuming}_key.pem > /dev/null 2>&1
rm /home/web/certs/${yuming}_cert.pem > /dev/null 2>&1
- # 將域名轉換為數據庫名
+ # 將網域名稱轉換為資料庫名
dbname=$(echo "$yuming" | sed -e 's/[^A-Za-z0-9]/_/g')
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
- # 刪除數據庫前檢查是否存在,避免報錯
- echo "正在刪除數據庫:$dbname"
+ # 刪除資料庫前檢查是否存在,避免報錯
+ echo "正在刪除資料庫:$dbname"
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE ${dbname};" > /dev/null 2>&1
done
@@ -1738,7 +1822,7 @@ nginx_waf() {
wget -O /home/web/nginx.conf "${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/nginx10.conf"
fi
- # 根據 mode 參數來決定開啟或關閉 WAF
+ # 根據 mode 參數決定開啟或關閉 WAF
if [ "$mode" == "on" ]; then
# 開啟 WAF:去掉註釋
sed -i 's|# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;|' /home/web/nginx.conf > /dev/null 2>&1
@@ -1768,7 +1852,7 @@ check_waf_status() {
if grep -q "^\s*#\s*modsecurity on;" /home/web/nginx.conf; then
waf_status=""
elif grep -q "modsecurity on;" /home/web/nginx.conf; then
- waf_status=" WAF已开启"
+ waf_status="WAF已開啟"
else
waf_status=""
fi
@@ -1776,8 +1860,8 @@ check_waf_status() {
check_cf_mode() {
- if [ -f "/path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf" ]; then
- CFmessage=" cf模式已开启"
+ if [ -f "/etc/fail2ban/action.d/cloudflare-docker.conf" ]; then
+ CFmessage="cf模式已開啟"
else
CFmessage=""
fi
@@ -1847,6 +1931,43 @@ patch_wp_debug() {
}
+
+
+patch_wp_url() {
+ local HOME_URL="$1"
+ local SITE_URL="$2"
+ local TARGET_DIR="/home/web/html"
+
+ find "$TARGET_DIR" -type f -name "wp-config-sample.php" | while read -r FILE; do
+ # 刪除舊定義
+ sed -i "/define(['\"]WP_HOME['\"].*/d" "$FILE"
+ sed -i "/define(['\"]WP_SITEURL['\"].*/d" "$FILE"
+
+ # 產生插入內容
+ INSERT="
+define('WP_HOME', '$HOME_URL');
+define('WP_SITEURL', '$SITE_URL');
+"
+
+ # 插入到 “Happy publishing” 之前
+ awk -v insert="$INSERT" '
+ /Happy publishing/ {
+ print insert
+ }
+ { print }
+ ' "$FILE" > "$FILE.tmp" && mv -f "$FILE.tmp" "$FILE"
+
+ echo "[+] Updated WP_HOME and WP_SITEURL in $FILE"
+ done
+}
+
+
+
+
+
+
+
+
nginx_br() {
local mode=$1
@@ -1990,31 +2111,23 @@ nginx_gzip() {
web_security() {
send_stats "LDNMP環境防禦"
while true; do
+ check_f2b_status
check_waf_status
check_cf_mode
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "服務器網站防禦程序${check_docker}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
+ echo -e "伺服器網站防禦程序${check_f2b_status}${gl_lv}${CFmessage}${waf_status}${gl_bai}"
echo "------------------------"
echo "1. 安裝防禦程序"
echo "------------------------"
- echo "5. 查看SSH攔截記錄 6. 查看網站攔截記錄"
- echo "7. 查看防禦規則列表 8. 查看日誌實時監控"
+ echo "5. 查看SSH攔截記錄 6. 查看網站攔截記錄"
+ echo "7. 檢視防禦規則清單 8. 查看日誌即時監控"
echo "------------------------"
- echo "11. 配置攔截參數 12. 清除所有拉黑的IP"
+ echo "11. 設定攔截參數 12. 清除所有拉黑的IP"
echo "------------------------"
- echo "21. cloudflare模式 22. 高負載開啟5秒盾"
+ echo "21. cloudflare模式 22. 高負載開啟5秒盾"
echo "------------------------"
- echo "31. 開啟WAF 32. 關閉WAF"
- echo "33. 開啟DDOS防禦 34. 關閉DDOS防禦"
+ echo "31. 開啟WAF 32. 關閉WAF"
+ echo "33. 開啟DDOS防禦 34. 關閉DDOS防禦"
echo "------------------------"
echo "9. 卸載防禦程序"
echo "------------------------"
@@ -2024,11 +2137,16 @@ web_security() {
case $sub_choice in
1)
f2b_install_sshd
- cd /path/to/fail2ban/config/fail2ban/filter.d
+ cd /etc/fail2ban/filter.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/fail2ban-nginx-cc.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-418.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-deny.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-unauthorized.conf
+ wget ${gh_proxy}raw.githubusercontent.com/linuxserver/fail2ban-confs/master/filter.d/nginx-bad-request.conf
+
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- sed -i "/cloudflare/d" /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ sed -i "/cloudflare/d" /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
;;
5)
@@ -2042,76 +2160,77 @@ web_security() {
local xxx="fail2ban-nginx-cc"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-418"
+ local xxx="nginx-418"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-bad-request"
+ local xxx="nginx-bad-request"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-badbots"
+ local xxx="nginx-badbots"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-botsearch"
+ local xxx="nginx-botsearch"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-deny"
+ local xxx="nginx-deny"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-http-auth"
+ local xxx="nginx-http-auth"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-nginx-unauthorized"
+ local xxx="nginx-unauthorized"
f2b_status_xxx
echo "------------------------"
- local xxx="docker-php-url-fopen"
+ local xxx="php-url-fopen"
f2b_status_xxx
echo "------------------------"
;;
7)
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
;;
8)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
+ tail -f /var/log/fail2ban.log
;;
9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
+ remove fail2ban
+ rm -rf /etc/fail2ban
crontab -l | grep -v "CF-Under-Attack.sh" | crontab - 2>/dev/null
echo "Fail2Ban防禦程序已卸載"
+ break
;;
11)
install nano
- nano /path/to/fail2ban/config/fail2ban/jail.d/nginx-docker-cc.conf
+ nano /etc/fail2ban/jail.d/nginx-docker-cc.conf
f2b_status
break
;;
12)
- docker exec -it fail2ban fail2ban-client unban --all
+ fail2ban-client unban --all
;;
21)
send_stats "cloudflare模式"
- echo "到cf後台右上角我的個人資料,選擇左側API令牌,獲取Global API Key"
+ echo "到cf後台右上角我的個人資料,選擇左側API令牌,取得Global API Key"
echo "https://dash.cloudflare.com/login"
- read -e -p "輸入CF的賬號:" cfuser
+ read -e -p "輸入CF的帳號:" cfuser
read -e -p "輸入CF的Global API Key:" cftoken
wget -O /home/web/conf.d/default.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/default11.conf
docker exec nginx nginx -s reload
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/nginx-docker-cc.conf
- cd /path/to/fail2ban/config/fail2ban/action.d
+ cd /etc/fail2ban/action.d
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/cloudflare-docker.conf
- sed -i "s/kejilion@outlook.com/$cfuser/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
- sed -i "s/APIKEY00000/$cftoken/g" /path/to/fail2ban/config/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/kejilion@outlook.com/$cfuser/g" /etc/fail2ban/action.d/cloudflare-docker.conf
+ sed -i "s/APIKEY00000/$cftoken/g" /etc/fail2ban/action.d/cloudflare-docker.conf
f2b_status
echo "已配置cloudflare模式,可在cf後台,站點-安全性-事件中查看攔截記錄"
@@ -2119,16 +2238,16 @@ web_security() {
22)
send_stats "高負載開啟5秒盾"
- echo -e "${gl_huang}網站每5分鐘自動檢測,當達檢測到高負載會自動開盾,低負載也會自動關閉5秒盾。${gl_bai}"
+ echo -e "${gl_huang}網站每5分鐘自動偵測,當達偵測到高負載會自動開盾,低負載也會自動關閉5秒盾。${gl_bai}"
echo "--------------"
- echo "獲取CF參數:"
- echo -e "到cf後台右上角我的個人資料,選擇左側API令牌,獲取${gl_huang}Global API Key${gl_bai}"
- echo -e "到cf後台域名概要頁面右下方獲取${gl_huang}區域ID${gl_bai}"
+ echo "取得CF參數:"
+ echo -e "到cf後台右上角我的個人資料,選擇左側API令牌,取得${gl_huang}Global API Key${gl_bai}"
+ echo -e "到cf后台域名概要页面右下方获取${gl_huang}區域ID${gl_bai}"
echo "https://dash.cloudflare.com/login"
echo "--------------"
- read -e -p "輸入CF的賬號:" cfuser
+ read -e -p "輸入CF的帳號:" cfuser
read -e -p "輸入CF的Global API Key:" cftoken
- read -e -p "輸入CF中域名的區域ID:" cfzonID
+ read -e -p "輸入CF中網域名稱的區域ID:" cfzonID
cd ~
install jq bc
@@ -2145,7 +2264,7 @@ web_security() {
if [ -z "$existing_cron" ]; then
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "高負載自動開盾腳本已添加"
+ echo "高負載自動開盾腳本已新增"
else
echo "自動開盾腳本已存在,無需添加"
fi
@@ -2176,26 +2295,23 @@ web_security() {
break
;;
esac
- fi
break_end
done
}
-check_nginx_mode() {
+check_ldnmp_mode() {
-CONFIG_FILE="/home/web/nginx.conf"
+ local MYSQL_CONTAINER="mysql"
+ local MYSQL_CONF="/etc/mysql/conf.d/custom_mysql_config.cnf"
-# 獲取當前的 worker_processes 設置值
-current_value=$(grep -E '^\s*worker_processes\s+[0-9]+;' "$CONFIG_FILE" | awk '{print $2}' | tr -d ';')
-
-# 根據值設置模式信息
-if [ "$current_value" = "8" ]; then
- mode_info=" 高性能模式"
-else
- mode_info=" 标准模式"
-fi
+ # 檢查 MySQL 設定檔中是否包含 4096M
+ if docker exec "$MYSQL_CONTAINER" grep -q "4096M" "$MYSQL_CONF" 2>/dev/null; then
+ mode_info="高性能模式"
+ else
+ mode_info="標準模式"
+ fi
@@ -2204,25 +2320,25 @@ fi
check_nginx_compression() {
- CONFIG_FILE="/home/web/nginx.conf"
+ local CONFIG_FILE="/home/web/nginx.conf"
- # 檢查 zstd 是否開啟且未被註釋(整行以 zstd on; 開頭)
+ # 檢查 zstd 是否開啟且未被註解(整行以 zstd on; 開頭)
if grep -qE '^\s*zstd\s+on;' "$CONFIG_FILE"; then
- zstd_status=" zstd压缩已开启"
+ zstd_status="zstd壓縮已開啟"
else
zstd_status=""
fi
# 檢查 brotli 是否開啟且未被註釋
if grep -qE '^\s*brotli\s+on;' "$CONFIG_FILE"; then
- br_status=" br压缩已开启"
+ br_status="br壓縮已開啟"
else
br_status=""
fi
- # 檢查 gzip 是否開啟且未被註釋
+ # 检查 gzip 是否开启且未被注释
if grep -qE '^\s*gzip\s+on;' "$CONFIG_FILE"; then
- gzip_status=" gzip压缩已开启"
+ gzip_status="gzip壓縮已開啟"
else
gzip_status=""
fi
@@ -2233,17 +2349,17 @@ check_nginx_compression() {
web_optimization() {
while true; do
- check_nginx_mode
+ check_ldnmp_mode
check_nginx_compression
clear
send_stats "優化LDNMP環境"
echo -e "優化LDNMP環境${gl_lv}${mode_info}${gzip_status}${br_status}${zstd_status}${gl_bai}"
echo "------------------------"
- echo "1. 標準模式 2. 高性能模式 (推薦2H4G以上)"
+ echo "1. 標準模式 2. 高效能模式 (建議2H4G以上)"
echo "------------------------"
- echo "3. 開啟gzip壓縮 4. 關閉gzip壓縮"
- echo "5. 開啟br壓縮 6. 關閉br壓縮"
- echo "7. 開啟zstd壓縮 8. 關閉zstd壓縮"
+ echo "3. 開啟gzip壓縮 4. 關閉gzip壓縮"
+ echo "5. 開啟br壓縮 6. 關閉br壓縮"
+ echo "7. 開啟zstd壓縮 8. 關閉zstd壓縮"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -2252,9 +2368,11 @@ web_optimization() {
1)
send_stats "站點標準模式"
- # nginx調優
- sed -i 's/worker_connections.*/worker_connections 10240;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 4;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((1024 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
+
# php調優
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2282,19 +2400,20 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_balanced
- echo "LDNMP環境已設置成 標準模式"
+ echo "LDNMP環境已設定成 標準模式"
;;
2)
- send_stats "站點高性能模式"
+ send_stats "站點高效能模式"
# nginx調優
- sed -i 's/worker_connections.*/worker_connections 20480;/' /home/web/nginx.conf
- sed -i 's/worker_processes.*/worker_processes 8;/' /home/web/nginx.conf
+ local cpu_cores=$(nproc)
+ local connections=$((2048 * ${cpu_cores}))
+ sed -i "s/worker_processes.*/worker_processes ${cpu_cores};/" /home/web/nginx.conf
+ sed -i "s/worker_connections.*/worker_connections ${connections};/" /home/web/nginx.conf
# php調優
wget -O /home/optimized_php.ini ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/optimized_php.ini
@@ -2321,10 +2440,9 @@ web_optimization() {
cd /home/web && docker compose restart
- restart_redis
optimize_web_server
- echo "LDNMP環境已設置成 高性能模式"
+ echo "LDNMP環境已設定成 高效能模式"
;;
3)
@@ -2371,18 +2489,25 @@ web_optimization() {
+check_docker_app() {
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name" ; then
+ check_docker="${gl_lv}已安裝${gl_bai}"
+ else
+ check_docker="${gl_hui}未安裝${gl_bai}"
+ fi
+}
-check_docker_app() {
+# check_docker_app() {
-if docker inspect "$docker_name" &>/dev/null; then
- check_docker="${gl_lv}已安装${gl_bai}"
-else
- check_docker="${gl_hui}未安装${gl_bai}"
-fi
+# if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+# check_docker="${gl_lv}已安裝${gl_bai}"
+# else
+# check_docker="${gl_hui}未安裝${gl_bai}"
+# fi
-}
+# }
check_docker_app_ip() {
@@ -2416,59 +2541,66 @@ done
check_docker_image_update() {
-
local container_name=$1
+ update_status=""
- local country=$(curl -s ipinfo.io/country)
- if [[ "$country" == "CN" ]]; then
- update_status=""
- return
- fi
+ # 1. 區域檢查
+ local country=$(curl -s --max-time 2 ipinfo.io/country)
+ [[ "$country" == "CN" ]] && return
- # 獲取容器的創建時間和鏡像名稱
+ # 2. 取得本地鏡像訊息
local container_info=$(docker inspect --format='{{.Created}},{{.Config.Image}}' "$container_name" 2>/dev/null)
- local container_created=$(echo "$container_info" | cut -d',' -f1)
- local image_name=$(echo "$container_info" | cut -d',' -f2)
-
- # 提取鏡像倉庫和標籤
- local image_repo=${image_name%%:*}
- local image_tag=${image_name##*:}
-
- # 默認標籤為 latest
- [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
-
- # 添加對官方鏡像的支持
- [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
-
- # 從 Docker Hub API 獲取鏡像發佈時間
- local hub_info=$(curl -s "https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag")
- local last_updated=$(echo "$hub_info" | jq -r '.last_updated' 2>/dev/null)
+ [[ -z "$container_info" ]] && return
- # 驗證獲取的時間
- if [[ -n "$last_updated" && "$last_updated" != "null" ]]; then
- local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
- local last_updated_ts=$(date -d "$last_updated" +%s 2>/dev/null)
+ local container_created=$(echo "$container_info" | cut -d',' -f1)
+ local full_image_name=$(echo "$container_info" | cut -d',' -f2)
+ local container_created_ts=$(date -d "$container_created" +%s 2>/dev/null)
+
+ # 3. 智慧路由判斷
+ if [[ "$full_image_name" == ghcr.io* ]]; then
+ # --- 場景 A: 鏡像在 GitHub (ghcr.io) ---
+ # 提取倉庫路徑,例如 ghcr.io/onexru/oneimg -> onexru/oneimg
+ local repo_path=$(echo "$full_image_name" | sed 's/ghcr.io\///' | cut -d':' -f1)
+ # 註:ghcr.io 的 API 比較複雜,通常最快的方法是查 GitHub Repo 的 Release
+ local api_url="https://api.github.com/repos/$repo_path/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
+
+ elif [[ "$full_image_name" == *"oneimg"* ]]; then
+ # --- 場景 B: 特殊指定 (即便在 Docker Hub,也想透過 GitHub Release 判斷) ---
+ local api_url="https://api.github.com/repos/onexru/oneimg/releases/latest"
+ local remote_date=$(curl -s "$api_url" | jq -r '.published_at' 2>/dev/null)
- # 比較時間戳
- if [[ $container_created_ts -lt $last_updated_ts ]]; then
- update_status="${gl_huang}发现新版本!${gl_bai}"
- else
- update_status=""
- fi
else
- update_status=""
+ # --- 場景 C: 標準 Docker Hub ---
+ local image_repo=${full_image_name%%:*}
+ local image_tag=${full_image_name##*:}
+ [[ "$image_repo" == "$image_tag" ]] && image_tag="latest"
+ [[ "$image_repo" != */* ]] && image_repo="library/$image_repo"
+
+ local api_url="https://hub.docker.com/v2/repositories/$image_repo/tags/$image_tag"
+ local remote_date=$(curl -s "$api_url" | jq -r '.last_updated' 2>/dev/null)
fi
+ # 4. 時間戳對比
+ if [[ -n "$remote_date" && "$remote_date" != "null" ]]; then
+ local remote_ts=$(date -d "$remote_date" +%s 2>/dev/null)
+ if [[ $container_created_ts -lt $remote_ts ]]; then
+ update_status="${gl_huang}發現新版本!${gl_bai}"
+ fi
+ fi
}
+
+
+
block_container_port() {
local container_name_or_id=$1
local allowed_ip=$2
- # 獲取容器的 IP 地址
+ # 取得容器的 IP 位址
local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name_or_id")
if [ -z "$container_ip" ]; then
@@ -2478,7 +2610,7 @@ block_container_port() {
install iptables
- # 檢查並封禁其他所有 IP
+ # 檢查並封鎖其他所有 IP
if ! iptables -C DOCKER-USER -p tcp -d "$container_ip" -j DROP &>/dev/null; then
iptables -I DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
@@ -2488,14 +2620,14 @@ block_container_port() {
iptables -I DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 檢查並放行本地網絡 127.0.0.0/8
+ # 檢查並放行本地網路 127.0.0.0/8
if ! iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
- # 檢查並封禁其他所有 IP
+ # 檢查並封鎖其他所有 IP
if ! iptables -C DOCKER-USER -p udp -d "$container_ip" -j DROP &>/dev/null; then
iptables -I DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
@@ -2505,7 +2637,7 @@ block_container_port() {
iptables -I DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 檢查並放行本地網絡 127.0.0.0/8
+ # 檢查並放行本地網路 127.0.0.0/8
if ! iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -I DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2515,7 +2647,7 @@ block_container_port() {
fi
- echo "已阻止IP+端口訪問該服務"
+ echo "已阻止IP+連接埠存取該服務"
save_iptables_rules
}
@@ -2526,7 +2658,7 @@ clear_container_rules() {
local container_name_or_id=$1
local allowed_ip=$2
- # 獲取容器的 IP 地址
+ # 取得容器的 IP 位址
local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name_or_id")
if [ -z "$container_ip" ]; then
@@ -2536,7 +2668,7 @@ clear_container_rules() {
install iptables
- # 清除封禁其他所有 IP 的規則
+ # 清除封鎖其他所有 IP 的規則
if iptables -C DOCKER-USER -p tcp -d "$container_ip" -j DROP &>/dev/null; then
iptables -D DOCKER-USER -p tcp -d "$container_ip" -j DROP
fi
@@ -2546,7 +2678,7 @@ clear_container_rules() {
iptables -D DOCKER-USER -p tcp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 清除放行本地網絡 127.0.0.0/8 的規則
+ # 清除放行本地網路 127.0.0.0/8 的規則
if iptables -C DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p tcp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2555,7 +2687,7 @@ clear_container_rules() {
- # 清除封禁其他所有 IP 的規則
+ # 清除封鎖其他所有 IP 的規則
if iptables -C DOCKER-USER -p udp -d "$container_ip" -j DROP &>/dev/null; then
iptables -D DOCKER-USER -p udp -d "$container_ip" -j DROP
fi
@@ -2565,7 +2697,7 @@ clear_container_rules() {
iptables -D DOCKER-USER -p udp -s "$allowed_ip" -d "$container_ip" -j ACCEPT
fi
- # 清除放行本地網絡 127.0.0.0/8 的規則
+ # 清除放行本地網路 127.0.0.0/8 的規則
if iptables -C DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT &>/dev/null; then
iptables -D DOCKER-USER -p udp -s 127.0.0.0/8 -d "$container_ip" -j ACCEPT
fi
@@ -2576,7 +2708,7 @@ clear_container_rules() {
fi
- echo "已允許IP+端口訪問該服務"
+ echo "已允許IP+連接埠存取該服務"
save_iptables_rules
}
@@ -2590,8 +2722,8 @@ block_host_port() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "錯誤:請提供端口號和允許訪問的 IP。"
- echo "用法: block_host_port <端口號> <允許的IP>"
+ echo "錯誤:請提供連接埠號碼和允許存取的 IP。"
+ echo "用法: block_host_port <連接埠號碼> <允許的IP>"
return 1
fi
@@ -2603,7 +2735,7 @@ block_host_port() {
iptables -I INPUT -p tcp --dport "$port" -j DROP
fi
- # 允許指定 IP 訪問
+ # 允許指定 IP 存取
if ! iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
@@ -2622,7 +2754,7 @@ block_host_port() {
iptables -I INPUT -p udp --dport "$port" -j DROP
fi
- # 允許指定 IP 訪問
+ # 允許指定 IP 存取
if ! iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -I INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
@@ -2632,12 +2764,12 @@ block_host_port() {
iptables -I INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 允許已建立和相關連接的流量
+ # 允許已建立和相關連線的流量
if ! iptables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT &>/dev/null; then
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
fi
- echo "已阻止IP+端口訪問該服務"
+ echo "已阻止IP+連接埠存取該服務"
save_iptables_rules
}
@@ -2649,47 +2781,47 @@ clear_host_port_rules() {
local allowed_ip=$2
if [[ -z "$port" || -z "$allowed_ip" ]]; then
- echo "錯誤:請提供端口號和允許訪問的 IP。"
- echo "用法: clear_host_port_rules <端口號> <允許的IP>"
+ echo "錯誤:請提供連接埠號碼和允許存取的 IP。"
+ echo "用法: clear_host_port_rules <連接埠號碼> <允許的IP>"
return 1
fi
install iptables
- # 清除封禁所有其他 IP 訪問的規則
+ # 清除封鎖所有其他 IP 存取的規則
if iptables -C INPUT -p tcp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -j DROP
fi
- # 清除允許本機訪問的規則
+ # 清除允許本機存取的規則
if iptables -C INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 清除允許指定 IP 訪問的規則
+ # 清除允許指定 IP 存取的規則
if iptables -C INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p tcp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- # 清除封禁所有其他 IP 訪問的規則
+ # 清除封鎖所有其他 IP 存取的規則
if iptables -C INPUT -p udp --dport "$port" -j DROP &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -j DROP
fi
- # 清除允許本機訪問的規則
+ # 清除允許本機存取的規則
if iptables -C INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s 127.0.0.0/8 -j ACCEPT
fi
- # 清除允許指定 IP 訪問的規則
+ # 清除允許指定 IP 存取的規則
if iptables -C INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT &>/dev/null; then
iptables -D INPUT -p udp --dport "$port" -s "$allowed_ip" -j ACCEPT
fi
- echo "已允許IP+端口訪問該服務"
+ echo "已允許IP+連接埠存取該服務"
save_iptables_rules
}
@@ -2698,16 +2830,33 @@ clear_host_port_rules() {
setup_docker_dir() {
- mkdir -p /home/docker/ 2>/dev/null
+ mkdir -p /home /home/docker 2>/dev/null
+
if [ -d "/vol1/1000/" ] && [ ! -d "/vol1/1000/docker" ]; then
cp -f /home/docker /home/docker1 2>/dev/null
rm -rf /home/docker 2>/dev/null
mkdir -p /vol1/1000/docker 2>/dev/null
ln -s /vol1/1000/docker /home/docker 2>/dev/null
fi
+
+ if [ -d "/volume1/" ] && [ ! -d "/volume1/docker" ]; then
+ cp -f /home/docker /home/docker1 2>/dev/null
+ rm -rf /home/docker 2>/dev/null
+ mkdir -p /volume1/docker 2>/dev/null
+ ln -s /volume1/docker /home/docker 2>/dev/null
+ fi
+
+
}
+add_app_id() {
+mkdir -p /home/docker
+touch /home/docker/appno.txt
+grep -qxF "${app_id}" /home/docker/appno.txt || echo "${app_id}" >> /home/docker/appno.txt
+
+}
+
docker_app() {
@@ -2716,11 +2865,11 @@ send_stats "${docker_name}管理"
while true; do
clear
check_docker_app
- check_docker_image_update $docker_name
+ check_docker_image_update "${docker_app_service:-$docker_name}"
echo -e "$docker_name $check_docker $update_status"
echo "$docker_describe"
echo "$docker_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
@@ -2731,27 +2880,38 @@ while true; do
fi
echo ""
echo "------------------------"
- echo "1. 安裝 2. 更新 3. 卸載"
+ echo "1. 安裝 2. 更新 3. 卸載"
echo "------------------------"
- echo "5. 添加域名訪問 6. 刪除域名訪問"
- echo "7. 允許IP+端口訪問 8. 阻止IP+端口訪問"
+ echo "5. 新增網域存取 6. 刪除網域存取"
+ echo "7. 允許IP+連接埠存取 8. 阻止IP+連接埠訪問"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
read -e -p "請輸入你的選擇:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "輸入應用對外服務端口,回車默認使用${docker_port}端口:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+ while true; do
+ read -e -p "輸入應用程式對外服務端口,回車預設使用${docker_port}連接埠:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}錯誤:${gl_bai}連接埠$app_port已被佔用,請更換一個端口"
+ send_stats "應用程式連接埠已被佔用"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
install jq
install_docker
docker_rum
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+ add_app_id
+
clear
echo "$docker_name已經安裝完成"
check_docker_app_ip
@@ -2764,6 +2924,9 @@ while true; do
docker rm -f "$docker_name"
docker rmi -f "$docker_img"
docker_rum
+
+ add_app_id
+
clear
echo "$docker_name已經安裝完成"
check_docker_app_ip
@@ -2777,30 +2940,32 @@ while true; do
docker rmi -f "$docker_img"
rm -rf "/home/docker/$docker_name"
rm -f /home/docker/${docker_name}_port.conf
- echo "應用已卸載"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "應用程式已解除安裝"
send_stats "解除安裝$docker_name"
;;
5)
- echo "${docker_name}域名訪問設置"
- send_stats "${docker_name}域名訪問設置"
+ echo "${docker_name}域名存取設定"
+ send_stats "${docker_name}域名存取設定"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
block_container_port "$docker_name" "$ipv4_address"
;;
6)
- echo "域名格式 example.com 不帶https://"
+ echo "網域格式 example.com 不含https://"
web_del
;;
7)
- send_stats "允許IP訪問${docker_name}"
+ send_stats "允許IP存取${docker_name}"
clear_container_rules "$docker_name" "$ipv4_address"
;;
8)
- send_stats "阻止IP訪問${docker_name}"
+ send_stats "阻止IP访问 ${docker_name}"
block_container_port "$docker_name" "$ipv4_address"
;;
@@ -2817,7 +2982,6 @@ done
-
docker_app_plus() {
send_stats "$app_name"
while true; do
@@ -2827,9 +2991,10 @@ docker_app_plus() {
echo -e "$app_name $check_docker $update_status"
echo "$app_text"
echo "$app_url"
- if docker inspect "$docker_name" &>/dev/null; then
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_check_name"; then
if [ ! -f "/home/docker/${docker_name}_port.conf" ]; then
- local docker_port=$(docker port "$docker_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
+ local docker_port=$(docker port "$docker_check_name" | head -n1 | awk -F'[:]' '/->/ {print $NF; exit}')
docker_port=${docker_port:-0000}
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
fi
@@ -2838,51 +3003,76 @@ docker_app_plus() {
fi
echo ""
echo "------------------------"
- echo "1. 安裝 2. 更新 3. 卸載"
+ echo "1. 安裝 2. 更新 3. 卸載"
echo "------------------------"
- echo "5. 添加域名訪問 6. 刪除域名訪問"
- echo "7. 允許IP+端口訪問 8. 阻止IP+端口訪問"
+ echo "5. 新增網域存取 6. 刪除網域存取"
+ echo "7. 允許IP+連接埠存取 8. 阻止IP+連接埠訪問"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
read -e -p "輸入你的選擇:" choice
case $choice in
1)
- check_disk_space $app_size
- read -e -p "輸入應用對外服務端口,回車默認使用${docker_port}端口:" app_port
- local app_port=${app_port:-${docker_port}}
- local docker_port=$app_port
+ setup_docker_dir
+ check_disk_space $app_size /home/docker
+
+ while true; do
+ read -e -p "輸入應用程式對外服務端口,回車預設使用${docker_port}連接埠:" app_port
+ local app_port=${app_port:-${docker_port}}
+
+ if ss -tuln | grep -q ":$app_port "; then
+ echo -e "${gl_hong}錯誤:${gl_bai}連接埠$app_port已被佔用,請更換一個端口"
+ send_stats "應用程式連接埠已被佔用"
+ else
+ local docker_port=$app_port
+ break
+ fi
+ done
+
install jq
install_docker
docker_app_install
- setup_docker_dir
echo "$docker_port" > "/home/docker/${docker_name}_port.conf"
+
+ add_app_id
+ send_stats "$app_name安裝"
;;
+
2)
docker_app_update
+ add_app_id
+ send_stats "$app_name更新"
;;
+
3)
docker_app_uninstall
rm -f /home/docker/${docker_name}_port.conf
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "$app_name解除安裝"
;;
+
5)
- echo "${docker_name}域名訪問設置"
- send_stats "${docker_name}域名訪問設置"
+ echo "${docker_name}域名存取設定"
+ send_stats "${docker_name}域名存取設定"
add_yuming
ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
+
;;
6)
- echo "域名格式 example.com 不帶https://"
+ echo "網域格式 example.com 不含https://"
web_del
;;
7)
- send_stats "允許IP訪問${docker_name}"
+ send_stats "允許IP存取${docker_name}"
clear_container_rules "$docker_name" "$ipv4_address"
;;
8)
send_stats "阻止IP訪問${docker_name}"
- block_container_port "$docker_name" "$ipv4_address"
+ local docker_check_name="${docker_app_service:-$docker_name}"
+ block_container_port "$docker_check_name" "$ipv4_address"
;;
*)
break
@@ -2920,7 +3110,7 @@ docker network create $NETWORK_NAME
docker run -d \
--name=node-exporter \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
prom/node-exporter
# Run Prometheus container
@@ -2929,7 +3119,7 @@ docker run -d \
-v $PROMETHEUS_DIR/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $PROMETHEUS_DIR/data:/prometheus \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
--user 0:0 \
prom/prometheus:latest
@@ -2939,7 +3129,7 @@ docker run -d \
-p ${docker_port}:3000 \
-v $GRAFANA_DIR:/var/lib/grafana \
--network $NETWORK_NAME \
- --restart unless-stopped \
+ --restart=always \
grafana/grafana:latest
}
@@ -2976,7 +3166,7 @@ while session_exists "$base_name-$tmuxd_ID"; do
local tmuxd_ID=$((tmuxd_ID + 1))
done
-# 創建新的 tmux 會話
+# 建立新的 tmux 會話
tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
@@ -2985,52 +3175,41 @@ tmux new -d -s "$base_name-$tmuxd_ID" "$tmuxd"
f2b_status() {
- docker exec -it fail2ban fail2ban-client reload
+ fail2ban-client reload
sleep 3
- docker exec -it fail2ban fail2ban-client status
+ fail2ban-client status
}
f2b_status_xxx() {
- docker exec -it fail2ban fail2ban-client status $xxx
+ fail2ban-client status $xxx
+}
+
+check_f2b_status() {
+ if command -v fail2ban-client >/dev/null 2>&1; then
+ check_f2b_status="${gl_lv}已安裝${gl_bai}"
+ else
+ check_f2b_status="${gl_hui}未安裝${gl_bai}"
+ fi
}
f2b_install_sshd() {
- docker run -d \
- --name=fail2ban \
- --net=host \
- --cap-add=NET_ADMIN \
- --cap-add=NET_RAW \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e VERBOSITY=-vv \
- -v /path/to/fail2ban/config:/config \
- -v /var/log:/var/log:ro \
- -v /home/web/log/nginx/:/remotelogs/nginx:ro \
- --restart unless-stopped \
- lscr.io/linuxserver/fail2ban:latest
-
- sleep 3
- if grep -q 'Alpine' /etc/issue; then
- cd /path/to/fail2ban/config/fail2ban/filter.d
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd.conf
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-sshd-ddos.conf
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/alpine-ssh.conf
- elif command -v dnf &>/dev/null; then
- cd /path/to/fail2ban/config/fail2ban/jail.d/
+ docker rm -f fail2ban >/dev/null 2>&1
+ install fail2ban
+ start fail2ban
+ enable fail2ban
+
+ if command -v dnf &>/dev/null; then
+ cd /etc/fail2ban/jail.d/
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/centos-ssh.conf
- else
+ fi
+
+ if command -v apt &>/dev/null; then
install rsyslog
systemctl start rsyslog
systemctl enable rsyslog
- cd /path/to/fail2ban/config/fail2ban/jail.d/
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/config/main/fail2ban/linux-ssh.conf
- systemctl restart rsyslog
fi
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf
}
f2b_sshd() {
@@ -3043,6 +3222,80 @@ f2b_sshd() {
fi
}
+# 基礎參數配置:封禁時長(bantime)、時間視窗(findtime)、重試次數(maxretry)
+# 說明:
+# - 優先寫入 /etc/fail2ban/jail.d/sshd.local(覆蓋預設 jail 配置,升級不易丟)
+# - 若是 Alpine 且 jail 名稱不同,仍寫 sshd.local;Fail2Ban 會以 jail 名稱配對
+f2b_basic_config() {
+ root_use
+ install nano
+
+ if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo -e "${gl_hui}未偵測到 fail2ban-client,請先安裝 fail2ban。${gl_bai}"
+ return
+ fi
+
+ local jail_name="sshd"
+ if grep -qi 'Alpine' /etc/issue 2>/dev/null; then
+ # Alpine 默认 jail 通常为 sshd;仅当检测到自定义 alpine-sshd 规则时才切换
+ if [ -f /etc/fail2ban/filter.d/alpine-sshd.conf ] || [ -f /etc/fail2ban/jail.d/alpine-ssh.conf ] || [ -f /etc/fail2ban/jail.d/alpine-sshd.local ]; then
+ jail_name="alpine-sshd"
+ fi
+ fi
+
+ echo "即將配置 SSH jail:$jail_name"
+ read -e -p "封禁時長 bantime (秒/分鐘/小時,如 3600 或 1h) [預設 1h]:" bantime
+ read -e -p "時間窗口 findtime (秒/分鐘/小時,如 600 或 10m) [預設 10m]:" findtime
+ read -e -p "重試次數 maxretry (整數) [預設 5]:" maxretry
+
+ bantime=${bantime:-1h}
+ findtime=${findtime:-10m}
+ maxretry=${maxretry:-5}
+
+ mkdir -p /etc/fail2ban/jail.d
+ cat > /etc/fail2ban/jail.d/sshd.local <> /etc/fail2ban/jail.d/sshd.local
+ fi
+ fi
+
+ echo -e "${gl_lv}已寫入配置${gl_bai}: /etc/fail2ban/jail.d/sshd.local"
+ fail2ban-client reload >/dev/null 2>&1 || true
+ sleep 2
+ fail2ban-client status $jail_name || true
+}
+
+# 直接開啟主配置/覆蓋配置編輯(nano)
+# 優先編輯 /etc/fail2ban/jail.d/sshd.local(更安全),若不存在則創建
+f2b_edit_config() {
+ root_use
+ install nano
+
+ if [ ! -d /etc/fail2ban ]; then
+ echo -e "${gl_hui}/etc/fail2ban 不存在,請先安裝 fail2ban。${gl_bai}"
+ return
+ fi
+
+ mkdir -p /etc/fail2ban/jail.d
+ local cfg="/etc/fail2ban/jail.d/sshd.local"
+ [ -f "$cfg" ] || printf "[sshd]\n# bantime/findtime/maxretry\n" > "$cfg"
+
+ nano "$cfg"
+ echo -e "${gl_lv}已儲存${gl_bai},正在 reload fail2ban..."
+ fail2ban-client reload >/dev/null 2>&1 || true
+}
@@ -3113,8 +3366,7 @@ send_stats "安裝LDNMP環境"
root_use
clear
echo -e "${gl_huang}LDNMP環境未安裝,開始安裝LDNMP環境...${gl_bai}"
-check_disk_space 3
-check_port
+check_disk_space 3 /home
install_dependency
install_docker
install_certbot
@@ -3130,8 +3382,6 @@ send_stats "安裝nginx環境"
root_use
clear
echo -e "${gl_huang}nginx未安裝,開始安裝nginx環境...${gl_bai}"
-check_disk_space 1
-check_port
install_dependency
install_docker
install_certbot
@@ -3141,7 +3391,7 @@ clear
local nginx_version=$(docker exec nginx nginx -v 2>&1)
local nginx_version=$(echo "$nginx_version" | grep -oP "nginx/\K[0-9]+\.[0-9]+\.[0-9]+")
echo "nginx已安裝完成"
-echo -e "當前版本:${gl_huang}v$nginx_version${gl_bai}"
+echo -e "目前版本:${gl_huang}v$nginx_version${gl_bai}"
echo ""
}
@@ -3176,15 +3426,26 @@ ldnmp_web_on() {
echo "您的$webname搭建好了!"
echo "https://$yuming"
echo "------------------------"
- echo "$webname安裝信息如下:"
+ echo "$webname安裝資訊如下:"
}
nginx_web_on() {
- clear
- echo "您的$webname搭建好了!"
- echo "https://$yuming"
+ clear
+
+ local ipv4_pattern='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
+ local ipv6_pattern='^(([0-9A-Fa-f]{1,4}:){1,7}:|([0-9A-Fa-f]{1,4}:){7,7}[0-9A-Fa-f]{1,4}|::1)$'
+ echo "您的$webname搭建好了!"
+
+ if [[ "$yuming" =~ $ipv4_pattern || "$yuming" =~ $ipv6_pattern ]]; then
+ mv /home/web/conf.d/"$yuming".conf /home/web/conf.d/"${yuming}_${access_port}".conf
+ echo "http://$yuming:$access_port"
+ elif grep -q '^[[:space:]]*#.*if (\$scheme = http)' "/home/web/conf.d/"$yuming".conf"; then
+ echo "http://$yuming"
+ else
+ echo "https://$yuming"
+ fi
}
@@ -3201,43 +3462,43 @@ ldnmp_wp() {
fi
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/wordpress.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
nginx_http_on
+
cd /home/web/html
mkdir $yuming
cd $yuming
wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/refs/heads/main/wp-latest.zip
- # wget -O latest.zip https://cn.wordpress.org/latest-zh_CN.zip
- # wget -O latest.zip https://wordpress.org/latest.zip
unzip latest.zip
rm latest.zip
- echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
+ echo "define('FS_METHOD', 'direct'); define('WP_REDIS_HOST', 'redis'); define('WP_REDIS_PORT', '6379'); define('WP_REDIS_MAXTTL', 86400); define('WP_CACHE_KEY_SALT', '${yuming}_');" >> /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|database_name_here|$dbname|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|username_here|$dbuse|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|password_here|$dbusepasswd|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
sed -i "s|localhost|mysql|g" /home/web/html/$yuming/wordpress/wp-config-sample.php
+ patch_wp_url "https://$yuming" "https://$yuming"
cp /home/web/html/$yuming/wordpress/wp-config-sample.php /home/web/html/$yuming/wordpress/wp-config.php
+
restart_ldnmp
nginx_web_on
-# echo "數據庫名: $dbname"
-# echo "用戶名: $dbuse"
-# echo "密碼: $dbusepasswd"
-# echo "數據庫地址: mysql"
-# echo "表前綴: wp_"
}
+
ldnmp_Proxy() {
clear
- webname="反向代理-IP+端口"
+ webname="反向代理-IP+埠"
yuming="${1:-}"
reverseproxy="${2:-}"
port="${3:-}"
@@ -3247,21 +3508,43 @@ ldnmp_Proxy() {
if [ -z "$yuming" ]; then
add_yuming
fi
+
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy" ]; then
- read -e -p "請輸入你的反代IP:" reverseproxy
+ read -e -p "請輸入你的反代IP (回車預設本機IP 127.0.0.1):" reverseproxy
+ reverseproxy=${reverseproxy:-127.0.0.1}
fi
if [ -z "$port" ]; then
read -e -p "請輸入你的反代端口:" port
fi
nginx_install_status
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
- wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy.conf
+ wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/backend_$backend/g" /home/web/conf.d/"$yuming".conf
+
+
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- sed -i "s/0.0.0.0/$reverseproxy/g" /home/web/conf.d/$yuming.conf
- sed -i "s|0000|$port|g" /home/web/conf.d/$yuming.conf
+
+ reverseproxy_port="$reverseproxy:$port"
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 動態添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i '/remote_addr/d' /home/web/conf.d/$yuming.conf
+
+ update_nginx_listen_port "$yuming" "$access_port"
+
nginx_http_on
docker exec nginx nginx -s reload
nginx_web_on
@@ -3271,9 +3554,7 @@ ldnmp_Proxy() {
ldnmp_Proxy_backend() {
clear
- webname="反向代理-负载均衡"
- yuming="${1:-}"
- reverseproxy_port="${2:-}"
+ webname="反向代理-負載平衡"
send_stats "安裝$webname"
echo "開始部署$webname"
@@ -3281,14 +3562,17 @@ ldnmp_Proxy_backend() {
add_yuming
fi
- # 獲取用戶輸入的多個IP:端口(用空格分隔)
+ check_ip_and_get_access_port "$yuming"
+
if [ -z "$reverseproxy_port" ]; then
- read -e -p "請輸入你的多個反代IP+端口用空格隔開(例如 127.0.0.1:3000 127.0.0.1:3002):" reverseproxy_port
+ read -e -p "請輸入你的多個反代IP+埠以空格隔開(例如 127.0.0.1:3000 127.0.0.1:3002):" reverseproxy_port
fi
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend.conf
@@ -3298,14 +3582,15 @@ ldnmp_Proxy_backend() {
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
- # 動態生成 upstream 配置
upstream_servers=""
for server in $reverseproxy_port; do
upstream_servers="$upstream_servers server $server;\n"
done
- # 替換模板中的佔位符
- sed -i "s/# 动态添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+ sed -i "s/# 動態添加/$upstream_servers/g" /home/web/conf.d/$yuming.conf
+
+
+ update_nginx_listen_port "$yuming" "$access_port"
nginx_http_on
docker exec nginx nginx -s reload
@@ -3314,6 +3599,200 @@ ldnmp_Proxy_backend() {
+
+
+
+list_stream_services() {
+
+ STREAM_DIR="/home/web/stream.d"
+ printf "%-25s %-18s %-25s %-20s\n" "服務名" "通訊類型" "本機地址" "後端地址"
+
+ if [ -z "$(ls -A "$STREAM_DIR")" ]; then
+ return
+ fi
+
+ for conf in "$STREAM_DIR"/*; do
+ # 服務名取檔名
+ service_name=$(basename "$conf" .conf)
+
+ # 取得 upstream 區塊中的 server 後端 IP:端口
+ backend=$(grep -Po '(?<=server )[^;]+' "$conf" | head -n1)
+
+ # 取得 listen 端口
+ listen_port=$(grep -Po '(?<=listen )[^;]+' "$conf" | head -n1)
+
+ # 預設本地 IP
+ ip_address
+ local_ip="$ipv4_address"
+
+ # 取得通訊類型,優先從檔案名稱後綴或內容判斷
+ if grep -qi 'udp;' "$conf"; then
+ proto="udp"
+ else
+ proto="tcp"
+ fi
+
+ # 拼接監聽 IP:端口
+ local_addr="$local_ip:$listen_port"
+
+ printf "%-22s %-14s %-21s %-20s\n" "$service_name" "$proto" "$local_addr" "$backend"
+ done
+}
+
+
+
+
+
+
+
+
+
+stream_panel() {
+ send_stats "Stream四層代理"
+ local app_id="104"
+ local docker_name="nginx"
+
+ while true; do
+ clear
+ check_docker_app
+ check_docker_image_update $docker_name
+ echo -e "Stream四層代理轉送工具$check_docker $update_status"
+ echo "NGINX Stream 是 NGINX 的 TCP/UDP 代理模組,用於實現高效能的 傳輸層流量轉送和負載平衡。"
+ echo "------------------------"
+ if [ -d "/home/web/stream.d" ]; then
+ list_stream_services
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 安裝 2. 更新 3. 卸載"
+ echo "------------------------"
+ echo "4. 新增轉送服務 5. 修改轉送服務 6. 刪除轉送服務"
+ echo "------------------------"
+ echo "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "輸入你的選擇:" choice
+ case $choice in
+ 1)
+ nginx_install_status
+ add_app_id
+ send_stats "安裝Stream四層代理"
+ ;;
+ 2)
+ update_docker_compose_with_db_creds
+ nginx_upgrade
+ add_app_id
+ send_stats "更新Stream四層代理"
+ ;;
+ 3)
+ read -e -p "確定要刪除 nginx 容器嗎?這可能會影響網站功能! (y/N):" confirm
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ docker rm -f nginx
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ send_stats "更新Stream四層代理"
+ echo "nginx 容器已刪除。"
+ else
+ echo "操作已取消。"
+ fi
+
+ ;;
+
+ 4)
+ ldnmp_Proxy_backend_stream
+ add_app_id
+ send_stats "新增四層代理"
+ ;;
+ 5)
+ send_stats "編輯轉送配置"
+ read -e -p "請輸入你要編輯的服務名稱:" stream_name
+ install nano
+ nano /home/web/stream.d/$stream_name.conf
+ docker restart nginx
+ send_stats "修改四層代理"
+ ;;
+ 6)
+ send_stats "刪除轉送配置"
+ read -e -p "請輸入你要刪除的服務名稱:" stream_name
+ rm /home/web/stream.d/$stream_name.conf > /dev/null 2>&1
+ docker restart nginx
+ send_stats "刪除四層代理"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ break_end
+ done
+}
+
+
+
+ldnmp_Proxy_backend_stream() {
+ clear
+ webname="Stream四層代理-負載平衡"
+
+ send_stats "安裝$webname"
+ echo "開始部署$webname"
+
+ # 取得代理名稱
+ read -erp "請輸入代理轉發名稱 (如 mysql_proxy):" proxy_name
+ if [ -z "$proxy_name" ]; then
+ echo "名稱不能為空"; return 1
+ fi
+
+ # 取得監聽埠
+ read -erp "請輸入本機監聽埠 (如 3306):" listen_port
+ if ! [[ "$listen_port" =~ ^[0-9]+$ ]]; then
+ echo "連接埠必須是數字"; return 1
+ fi
+
+ echo "請選擇協議類型:"
+ echo "1. TCP 2. UDP"
+ read -erp "請輸入序號 [1-2]:" proto_choice
+
+ case "$proto_choice" in
+ 1) proto="tcp"; listen_suffix="" ;;
+ 2) proto="udp"; listen_suffix=" udp" ;;
+ *) echo "無效選擇"; return 1 ;;
+ esac
+
+ read -e -p "請輸入你的一個或多個後端IP+埠以空格隔開(例如 10.13.0.2:3306 10.13.0.3:3306):" reverseproxy_port
+
+ nginx_install_status
+ cd /home && mkdir -p web/stream.d
+ grep -q '^[[:space:]]*stream[[:space:]]*{' /home/web/nginx.conf || echo -e '\nstream {\n include /etc/nginx/stream.d/*.conf;\n}' | tee -a /home/web/nginx.conf
+ wget -O /home/web/stream.d/$proxy_name.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-backend-stream.conf
+
+ backend=$(tr -dc 'A-Za-z' < /dev/urandom | head -c 8)
+ sed -i "s/backend_yuming_com/${proxy_name}_${backend}/g" /home/web/stream.d/"$proxy_name".conf
+ sed -i "s|listen 80|listen $listen_port $listen_suffix|g" /home/web/stream.d/$proxy_name.conf
+ sed -i "s|listen \[::\]:|listen [::]:${listen_port} ${listen_suffix}|g" "/home/web/stream.d/${proxy_name}.conf"
+
+ upstream_servers=""
+ for server in $reverseproxy_port; do
+ upstream_servers="$upstream_servers server $server;\n"
+ done
+
+ sed -i "s/# 動態添加/$upstream_servers/g" /home/web/stream.d/$proxy_name.conf
+
+ docker exec nginx nginx -s reload
+ clear
+ echo "您的$webname搭建好了!"
+ echo "------------------------"
+ echo "訪問地址:"
+ ip_address
+ if [ -n "$ipv4_address" ]; then
+ echo "$ipv4_address:${listen_port}"
+ fi
+ if [ -n "$ipv6_address" ]; then
+ echo "$ipv6_address:${listen_port}"
+ fi
+ echo ""
+}
+
+
+
+
+
find_container_by_host_port() {
port="$1"
docker_name=$(docker ps --format '{{.ID}} {{.Names}}' | while read id name; do
@@ -3331,20 +3810,19 @@ ldnmp_web_status() {
root_use
while true; do
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
- local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+ local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
- local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+ local db_output="${gl_lv}${db_count}${gl_bai}"
clear
- send_stats "LDNMP站點管理"
+ send_stats "LDNMP站台管理"
echo "LDNMP環境"
echo "------------------------"
ldnmp_v
- # ls -t /home/web/conf.d | sed 's/\.[^.]*$//'
- echo -e "${output}證書到期時間"
+ echo -e "站點:${output}證書到期時間"
echo -e "------------------------"
for cert_file in /home/web/certs/*_cert.pem; do
local domain=$(basename "$cert_file" | sed 's/_cert.pem//')
@@ -3355,27 +3833,46 @@ ldnmp_web_status() {
fi
done
+ for conf_file in /home/web/conf.d/*_*.conf; do
+ [ -e "$conf_file" ] || continue
+ basename "$conf_file" .conf
+ done
+
+ for conf_file in /home/web/conf.d/*.conf; do
+ [ -e "$conf_file" ] || continue
+
+ filename=$(basename "$conf_file")
+
+ if [ "$filename" = "map.conf" ] || [ "$filename" = "default.conf" ]; then
+ continue
+ fi
+
+ if ! grep -q "ssl_certificate" "$conf_file"; then
+ basename "$conf_file" .conf
+ fi
+ done
+
echo "------------------------"
echo ""
- echo -e "${db_output}"
+ echo -e "資料庫:${db_output}"
echo -e "------------------------"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2> /dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys"
echo "------------------------"
echo ""
- echo "站點目錄"
+ echo "網站目錄"
echo "------------------------"
echo -e "數據${gl_hui}/home/web/html${gl_bai}證書${gl_hui}/home/web/certs${gl_bai}配置${gl_hui}/home/web/conf.d${gl_bai}"
echo "------------------------"
echo ""
echo "操作"
echo "------------------------"
- echo "1. 申請/更新域名證書 2. 更換站點域名"
- echo "3. 清理站點緩存 4. 創建關聯站點"
- echo "5. 查看訪問日誌 6. 查看錯誤日誌"
- echo "7. 編輯全局配置 8. 編輯站點配置"
- echo "9. 管理站點數據庫 10. 查看站點分析報告"
+ echo "1. 申請/更新網域憑證 2. 克隆站點域名"
+ echo "3. 清理站點快取 4. 建立關聯站點"
+ echo "5. 查看訪問日誌 6. 查看錯誤日誌"
+ echo "7. 編輯全域配置 8. 編輯站點配置"
+ echo "9. 管理站點資料庫 10. 查看站點分析報告"
echo "------------------------"
echo "20. 刪除指定站點數據"
echo "------------------------"
@@ -3384,33 +3881,29 @@ ldnmp_web_status() {
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
- send_stats "申請域名證書"
- read -e -p "請輸入你的域名:" yuming
+ send_stats "申請網域證書"
+ read -e -p "請輸入你的網域名稱:" yuming
install_certbot
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
install_ssltls
certs_status
;;
2)
- send_stats "更換站點域名"
- echo -e "${gl_hong}強烈建議:${gl_bai}先備份好全站數據再更換站點域名!"
- read -e -p "請輸入舊域名:" oddyuming
- read -e -p "請輸入新域名:" yuming
+ send_stats "克隆站點域名"
+ read -e -p "請輸入舊網域名稱:" oddyuming
+ read -e -p "請輸入新網域:" yuming
install_certbot
install_ssltls
certs_status
- # mysql替換
- add_db
+ add_db
local odd_dbname=$(echo "$oddyuming" | sed -e 's/[^A-Za-z0-9]/_/g')
local odd_dbname="${odd_dbname}"
docker exec mysql mysqldump -u root -p"$dbrootpasswd" $odd_dbname | docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname
- docker exec mysql mysql -u root -p"$dbrootpasswd" -e "DROP DATABASE $odd_dbname;"
-
local tables=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -D $dbname -e "SHOW TABLES;" | awk '{ if (NR>1) print $1 }')
for table in $tables; do
@@ -3421,18 +3914,15 @@ ldnmp_web_status() {
done
# 網站目錄替換
- mv /home/web/html/$oddyuming /home/web/html/$yuming
+ cp -r /home/web/html/$oddyuming /home/web/html/$yuming
find /home/web/html/$yuming -type f -exec sed -i "s/$odd_dbname/$dbname/g" {} +
find /home/web/html/$yuming -type f -exec sed -i "s/$oddyuming/$yuming/g" {} +
- mv /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
+ cp /home/web/conf.d/$oddyuming.conf /home/web/conf.d/$yuming.conf
sed -i "s/$oddyuming/$yuming/g" /home/web/conf.d/$yuming.conf
- rm /home/web/certs/${oddyuming}_key.pem
- rm /home/web/certs/${oddyuming}_cert.pem
-
- docker exec nginx nginx -s reload
+ cd /home/web && docker compose restart
;;
@@ -3441,10 +3931,10 @@ ldnmp_web_status() {
web_cache
;;
4)
- send_stats "創建關聯站點"
- echo -e "為現有的站點再關聯一個新域名用於訪問"
- read -e -p "請輸入現有的域名:" oddyuming
- read -e -p "請輸入新域名:" yuming
+ send_stats "建立關聯站點"
+ echo -e "为现有的站点再关联一个新域名用于访问"
+ read -e -p "請輸入現有的網域名稱:" oddyuming
+ read -e -p "請輸入新網域:" yuming
install_certbot
install_ssltls
certs_status
@@ -3468,15 +3958,15 @@ ldnmp_web_status() {
break_end
;;
7)
- send_stats "編輯全局配置"
+ send_stats "編輯全域配置"
install nano
nano /home/web/nginx.conf
docker exec nginx nginx -s reload
;;
8)
- send_stats "編輯站點配置"
- read -e -p "編輯站點配置,請輸入你要編輯的域名:" yuming
+ send_stats "編輯網站配置"
+ read -e -p "編輯網站配置,請輸入你要編輯的網域:" yuming
install nano
nano /home/web/conf.d/$yuming.conf
docker exec nginx nginx -s reload
@@ -3486,14 +3976,14 @@ ldnmp_web_status() {
break_end
;;
10)
- send_stats "查看站點數據"
+ send_stats "查看網站數據"
install goaccess
goaccess --log-format=COMBINED /home/web/log/nginx/access.log
;;
20)
web_del
- docker run -it --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
+ docker run --rm -v /etc/letsencrypt/:/etc/letsencrypt certbot/certbot delete --cert-name "$yuming" -n 2>/dev/null
;;
*)
@@ -3508,7 +3998,7 @@ ldnmp_web_status() {
check_panel_app() {
if $lujing > /dev/null 2>&1; then
- check_panel="${gl_lv}已安装${gl_bai}"
+ check_panel="${gl_lv}已安裝${gl_bai}"
else
check_panel=""
fi
@@ -3522,12 +4012,12 @@ while true; do
clear
check_panel_app
echo -e "$panelname $check_panel"
- echo "${panelname}是一款時下流行且強大的運維管理面板。"
+ echo "${panelname}是一款時下流行且強大的維運管理面板。"
echo "官網介紹:$panelurl "
echo ""
echo "------------------------"
- echo "1. 安裝 2. 管理 3. 卸載"
+ echo "1. 安裝 2. 管理 3. 卸載"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -3538,15 +4028,21 @@ while true; do
install wget
iptables_open
panel_app_install
+
+ add_app_id
send_stats "${panelname}安裝"
;;
2)
panel_app_manage
+
+ add_app_id
send_stats "${panelname}控制"
;;
3)
panel_app_uninstall
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
send_stats "${panelname}解除安裝"
;;
*)
@@ -3563,9 +4059,9 @@ done
check_frp_app() {
if [ -d "/home/frp/" ]; then
- check_frp="${gl_lv}已安装${gl_bai}"
+ check_frp="${gl_lv}已安裝${gl_bai}"
else
- check_frp="${gl_hui}未安装${gl_bai}"
+ check_frp="${gl_hui}未安裝${gl_bai}"
fi
}
@@ -3592,7 +4088,7 @@ donlond_frp() {
generate_frps_config() {
send_stats "安裝frp服務端"
- # 生成隨機端口和憑證
+ # 產生隨機連接埠和憑證
local bind_port=8055
local dashboard_port=8056
local token=$(openssl rand -hex 16)
@@ -3613,16 +4109,16 @@ EOF
donlond_frp frps
- # 輸出生成的信息
+ # 輸出產生的信息
ip_address
echo "------------------------"
- echo "客戶端部署時需要用的參數"
+ echo "客戶端部署時所需使用的參數"
echo "服務IP:$ipv4_address"
echo "token: $token"
echo
- echo "FRP面板信息"
- echo "FRP面板地址: http://$ipv4_address:$dashboard_port"
- echo "FRP面板用戶名:$dashboard_user"
+ echo "FRP面板資訊"
+ echo "FRP面板位址: http://$ipv4_address:$dashboard_port"
+ echo "FRP面板使用者名稱:$dashboard_user"
echo "FRP面板密碼:$dashboard_pwd"
echo
@@ -3655,17 +4151,17 @@ EOF
}
add_forwarding_service() {
- send_stats "添加frp內網服務"
- # 提示用戶輸入服務名稱和轉發信息
+ send_stats "新增frp內網服務"
+ # 提示使用者輸入服務名稱和轉發訊息
read -e -p "請輸入服務名稱:" service_name
- read -e -p "請輸入轉發類型 (tcp/udp) [回車默認tcp]:" service_type
+ read -e -p "請輸入轉送類型 (tcp/udp) [回車預設tcp]:" service_type
local service_type=${service_type:-tcp}
- read -e -p "請輸入內網IP [回車默認127.0.0.1]:" local_ip
+ read -e -p "請輸入內網IP [回車預設127.0.0.1]:" local_ip
local local_ip=${local_ip:-127.0.0.1}
- read -e -p "請輸入內網端口:" local_port
- read -e -p "請輸入外網端口:" remote_port
+ read -e -p "請輸入內部網路連接埠:" local_port
+ read -e -p "請輸入外網埠:" remote_port
- # 將用戶輸入寫入配置文件
+ # 將使用者輸入寫入設定檔
cat <> /home/frp/frpc.toml
[$service_name]
type = ${service_type}
@@ -3675,8 +4171,8 @@ remote_port = ${remote_port}
EOF
- # 輸出生成的信息
- echo "服務$service_name已成功添加到 frpc.toml"
+ # 輸出產生的信息
+ echo "服務$service_name已成功加入 frpc.toml"
docker restart frpc
@@ -3688,7 +4184,7 @@ EOF
delete_forwarding_service() {
send_stats "刪除frp內網服務"
- # 提示用戶輸入需要刪除的服務名稱
+ # 提示使用者輸入需要刪除的服務名稱
read -e -p "請輸入需要刪除的服務名稱:" service_name
# 使用 sed 刪除該服務及其相關配置
sed -i "/\[$service_name\]/,/^$/d" /home/frp/frpc.toml
@@ -3702,8 +4198,8 @@ delete_forwarding_service() {
list_forwarding_services() {
local config_file="$1"
- # 打印表頭
- printf "%-20s %-25s %-30s %-10s\n" "服务名称" "内网地址" "外网地址" "协议"
+ # 列印表頭
+ printf "%-20s %-25s %-30s %-10s\n" "服務名稱" "內部網路位址" "外網位址" "協定"
awk '
BEGIN {
@@ -3723,7 +4219,7 @@ list_forwarding_services() {
}
/^\[.*\]/ {
- # 如果已有服務信息,在處理新服務之前打印當前服務
+ # 如果已有服務訊息,在處理新服務之前列印目前服務
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3732,7 +4228,7 @@ list_forwarding_services() {
type
}
- # 更新當前服務名稱
+ # 更新目前服務名稱
if ($1 != "[common]") {
gsub(/[\[\]]/, "", $1)
current_service=$1
@@ -3765,7 +4261,7 @@ list_forwarding_services() {
}
END {
- # 打印最後一個服務的信息
+ # 列印最後一個服務的訊息
if (current_service != "" && current_service != "common" && local_ip != "" && local_port != "") {
printf "%-16s %-21s %-26s %-10s\n", \
current_service, \
@@ -3778,14 +4274,14 @@ list_forwarding_services() {
-# 獲取 FRP 服務端端口
+# 取得 FRP 服務端端口
get_frp_ports() {
mapfile -t ports < <(ss -tulnape | grep frps | awk '{print $5}' | awk -F':' '{print $NF}' | sort -u)
}
-# 生成訪問地址
+# 產生訪問地址
generate_access_urls() {
- # 首先獲取所有端口
+ # 首先獲取所有連接埠
get_frp_ports
# 檢查是否有非 8055/8056 的端口
@@ -3797,18 +4293,18 @@ generate_access_urls() {
fi
done
- # 只在有有效端口時顯示標題和內容
+ # 只在有有效連接埠時顯示標題和內容
if [ "$has_valid_ports" = true ]; then
- echo "FRP服務對外訪問地址:"
+ echo "FRP服務對外存取位址:"
- # 處理 IPv4 地址
+ # 處理 IPv4 位址
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
echo "http://${ipv4_address}:${port}"
fi
done
- # 處理 IPv6 地址(如果存在)
+ # 處理 IPv6 位址(如果存在)
if [ -n "$ipv6_address" ]; then
for port in "${ports[@]}"; do
if [[ $port != "8055" && $port != "8056" ]]; then
@@ -3845,6 +4341,7 @@ frps_main_ports() {
frps_panel() {
send_stats "FRP服務端"
+ local app_id="55"
local docker_name="frps"
local docker_port=8056
while true; do
@@ -3852,22 +4349,22 @@ frps_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRP服務端$check_frp $update_status"
- echo "構建FRP內網穿透服務環境,將無公網IP的設備暴露到互聯網"
- echo "官網介紹: https://github.com/fatedier/frp/"
- echo "視頻教學: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
+ echo "建構FRP內網穿透服務環境,將無公網IP的設備暴露到互聯網"
+ echo "官網介紹:${gh_https_url}github.com/fatedier/frp/"
+ echo "影片教學: https://www.bilibili.com/video/BV1yMw6e2EwL?t=124.0"
if [ -d "/home/frp/" ]; then
check_docker_app_ip
frps_main_ports
fi
echo ""
echo "------------------------"
- echo "1. 安裝 2. 更新 3. 卸載"
+ echo "1. 安裝 2. 更新 3. 卸載"
echo "------------------------"
- echo "5. 內網服務域名訪問 6. 刪除域名訪問"
+ echo "5. 內部網路服務網域存取 6. 刪除網域名稱訪問"
echo "------------------------"
- echo "7. 允許IP+端口訪問 8. 阻止IP+端口訪問"
+ echo "7. 允許IP+連接埠存取 8. 阻止IP+連接埠訪問"
echo "------------------------"
- echo "00. 刷新服務狀態 0. 返回上一級選單"
+ echo "00. 刷新服務狀態 0. 返回上一級選單"
echo "------------------------"
read -e -p "輸入你的選擇:" choice
case $choice in
@@ -3875,6 +4372,8 @@ frps_panel() {
install jq grep ss
install_docker
generate_frps_config
+
+ add_app_id
echo "FRP服務端已經安裝完成"
;;
2)
@@ -3883,6 +4382,8 @@ frps_panel() {
docker rm -f frps && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frps.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frps.toml /home/frp/frps.toml
donlond_frp frps
+
+ add_app_id
echo "FRP服務端已經更新完成"
;;
3)
@@ -3893,36 +4394,37 @@ frps_panel() {
close_port 8055 8056
- echo "應用已卸載"
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "應用程式已解除安裝"
;;
5)
echo "將內網穿透服務反代成域名訪問"
send_stats "FRP對外域名訪問"
add_yuming
- read -e -p "請輸入你的內網穿透服務端口:" frps_port
+ read -e -p "請輸入你的內部網路穿透服務埠:" frps_port
ldnmp_Proxy ${yuming} 127.0.0.1 ${frps_port}
block_host_port "$frps_port" "$ipv4_address"
;;
6)
- echo "域名格式 example.com 不帶https://"
+ echo "網域格式 example.com 不含https://"
web_del
;;
7)
- send_stats "允許IP訪問"
- read -e -p "請輸入需要放行的端口:" frps_port
+ send_stats "允許IP存取"
+ read -e -p "請輸入需要放行的連接埠:" frps_port
clear_host_port_rules "$frps_port" "$ipv4_address"
;;
8)
send_stats "阻止IP訪問"
echo "如果你已經反代域名訪問了,可用此功能阻止IP+端口訪問,這樣更安全。"
- read -e -p "請輸入需要阻止的端口:" frps_port
+ read -e -p "請輸入需要阻止的連接埠:" frps_port
block_host_port "$frps_port" "$ipv4_address"
;;
00)
- send_stats "刷新FRP服務狀態"
+ send_stats "重新整理FRP服務狀態"
echo "已經刷新FRP服務狀態"
;;
@@ -3937,6 +4439,7 @@ frps_panel() {
frpc_panel() {
send_stats "FRP客戶端"
+ local app_id="56"
local docker_name="frpc"
local docker_port=8055
while true; do
@@ -3944,9 +4447,9 @@ frpc_panel() {
check_frp_app
check_docker_image_update $docker_name
echo -e "FRP客戶端$check_frp $update_status"
- echo "與服務端對接,對接後可創建內網穿透服務到互聯網訪問"
- echo "官網介紹: https://github.com/fatedier/frp/"
- echo "視頻教學: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
+ echo "與服務端對接,對接後可建立內網穿透服務到網際網路存取"
+ echo "官網介紹:${gh_https_url}github.com/fatedier/frp/"
+ echo "影片教學: https://www.bilibili.com/video/BV1yMw6e2EwL?t=173.9"
echo "------------------------"
if [ -d "/home/frp/" ]; then
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
@@ -3954,9 +4457,9 @@ frpc_panel() {
fi
echo ""
echo "------------------------"
- echo "1. 安裝 2. 更新 3. 卸載"
+ echo "1. 安裝 2. 更新 3. 卸載"
echo "------------------------"
- echo "4. 添加對外服務 5. 刪除對外服務 6. 手動配置服務"
+ echo "4. 新增對外服務 5. 刪除對外服務 6. 手動設定服務"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -3966,7 +4469,9 @@ frpc_panel() {
install jq grep ss
install_docker
configure_frpc
- echo "FRP客戶端已經安裝完成"
+
+ add_app_id
+ echo "FRP客户端已经安装完成"
;;
2)
crontab -l | grep -v 'frpc' | crontab - > /dev/null 2>&1
@@ -3974,6 +4479,8 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine >/dev/null 2>&1
[ -f /home/frp/frpc.toml ] || cp /home/frp/frp_0.61.0_linux_amd64/frpc.toml /home/frp/frpc.toml
donlond_frp frpc
+
+ add_app_id
echo "FRP客戶端已經更新完成"
;;
@@ -3983,7 +4490,9 @@ frpc_panel() {
docker rm -f frpc && docker rmi kjlion/frp:alpine
rm -rf /home/frp
close_port 8055
- echo "應用已卸載"
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "應用程式已解除安裝"
;;
4)
@@ -4013,6 +4522,7 @@ frpc_panel() {
yt_menu_pro() {
+ local app_id="66"
local VIDEO_DIR="/home/yt-dlp"
local URL_FILE="$VIDEO_DIR/urls.txt"
local ARCHIVE_FILE="$VIDEO_DIR/archive.txt"
@@ -4022,24 +4532,24 @@ yt_menu_pro() {
while true; do
if [ -x "/usr/local/bin/yt-dlp" ]; then
- local YTDLP_STATUS="${gl_lv}已安装${gl_bai}"
+ local YTDLP_STATUS="${gl_lv}已安裝${gl_bai}"
else
- local YTDLP_STATUS="${gl_hui}未安装${gl_bai}"
+ local YTDLP_STATUS="${gl_hui}未安裝${gl_bai}"
fi
clear
send_stats "yt-dlp 下載工具"
echo -e "yt-dlp $YTDLP_STATUS"
- echo -e "yt-dlp 是一個功能強大的視頻下載工具,支持 YouTube、Bilibili、Twitter 等數千站點。"
- echo -e "官網地址:https://github.com/yt-dlp/yt-dlp"
+ echo -e "yt-dlp 是一個功能強大的影片下載工具,支援 YouTube、Bilibili、Twitter 等數千網站。"
+ echo -e "官網位址:${gh_https_url}github.com/yt-dlp/yt-dlp"
echo "-------------------------"
- echo "已下載視頻列表:"
- ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(暫無)"
+ echo "已下載影片清單:"
+ ls -td "$VIDEO_DIR"/*/ 2>/dev/null || echo "(暂无)"
echo "-------------------------"
- echo "1. 安裝 2. 更新 3. 卸載"
+ echo "1. 安裝 2. 更新 3. 卸載"
echo "-------------------------"
- echo "5. 單個視頻下載 6. 批量視頻下載 7. 自定義參數下載"
- echo "8. 下載為MP3音頻 9. 刪除視頻目錄 10. Cookie管理(開發中)"
+ echo "5. 單一影片下載 6. 大量影片下載 7. 自訂參數下載"
+ echo "8. 下載為MP3音訊 9.刪除影片目錄 10. Cookie管理(開發中)"
echo "-------------------------"
echo "0. 返回上一級選單"
echo "-------------------------"
@@ -4050,37 +4560,43 @@ yt_menu_pro() {
send_stats "正在安裝 yt-dlp..."
echo "正在安裝 yt-dlp..."
install ffmpeg
- sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
- sudo chmod a+rx /usr/local/bin/yt-dlp
+ curl -L ${gh_https_url}github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ chmod a+rx /usr/local/bin/yt-dlp
+
+ add_app_id
echo "安裝完成。按任意鍵繼續..."
read ;;
2)
send_stats "正在更新 yt-dlp..."
echo "正在更新 yt-dlp..."
- sudo yt-dlp -U
+ yt-dlp -U
+
+ add_app_id
echo "更新完成。按任意鍵繼續..."
read ;;
3)
send_stats "正在卸載 yt-dlp..."
echo "正在卸載 yt-dlp..."
- sudo rm -f /usr/local/bin/yt-dlp
+ rm -f /usr/local/bin/yt-dlp
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
echo "卸載完成。按任意鍵繼續..."
read ;;
5)
- send_stats "單個視頻下載"
- read -e -p "請輸入視頻鏈接:" url
+ send_stats "單一影片下載"
+ read -e -p "請輸入影片連結:" url
yt-dlp -P "$VIDEO_DIR" -f "bv*+ba/b" --merge-output-format mp4 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "下載完成,按任意鍵繼續..." ;;
+ read -e -p "下載完成,按任何鍵繼續..." ;;
6)
- send_stats "批量視頻下載"
+ send_stats "大量影片下載"
install nano
if [ ! -f "$URL_FILE" ]; then
- echo -e "# 輸入多個視頻鏈接地址\n# https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.episode.0" > "$URL_FILE"
+ echo -e "# 輸入多個視訊連結位址\n# https://www.bilibili.com/bangumi/play/ep733316?spm_id_from=333.337.0.0&from_spmid=666.25.episode.0" > "$URL_FILE"
fi
nano $URL_FILE
echo "現在開始批量下載..."
@@ -4091,9 +4607,9 @@ yt_menu_pro() {
-a "$URL_FILE" \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites
- read -e -p "批量下載完成,按任意鍵繼續..." ;;
+ read -e -p "批次下載完成,按任何鍵繼續..." ;;
7)
- send_stats "自定義視頻下載"
+ send_stats "自訂影片下載"
read -e -p "請輸入完整 yt-dlp 參數(不含 yt-dlp):" custom
yt-dlp -P "$VIDEO_DIR" $custom \
--write-subs --sub-langs all \
@@ -4104,18 +4620,18 @@ yt_menu_pro() {
read -e -p "執行完成,按任意鍵繼續..." ;;
8)
send_stats "MP3下載"
- read -e -p "請輸入視頻鏈接:" url
+ read -e -p "請輸入影片連結:" url
yt-dlp -P "$VIDEO_DIR" -x --audio-format mp3 \
--write-subs --sub-langs all \
--write-thumbnail --embed-thumbnail \
--write-info-json \
-o "$VIDEO_DIR/%(title)s/%(title)s.%(ext)s" \
--no-overwrites --no-post-overwrites "$url"
- read -e -p "音頻下載完成,按任意鍵繼續..." ;;
+ read -e -p "音訊下載完成,按任意鍵繼續..." ;;
9)
- send_stats "刪除視頻"
- read -e -p "請輸入刪除視頻名稱:" rmdir
+ send_stats "刪除影片"
+ read -e -p "請輸入刪除影片名稱:" rmdir
rm -rf "$VIDEO_DIR/$rmdir"
;;
*)
@@ -4160,7 +4676,7 @@ fix_dpkg() {
linux_update() {
- echo -e "${gl_huang}正在系統更新...${gl_bai}"
+ echo -e "${gl_kjlan}正在系統更新...${gl_bai}"
if command -v dnf &>/dev/null; then
dnf -y update
elif command -v yum &>/dev/null; then
@@ -4179,7 +4695,7 @@ linux_update() {
elif command -v opkg &>/dev/null; then
opkg update
else
- echo "未知的包管理器!"
+ echo "未知的套件管理器!"
return
fi
}
@@ -4187,7 +4703,7 @@ linux_update() {
linux_clean() {
- echo -e "${gl_huang}正在系統清理...${gl_bai}"
+ echo -e "${gl_kjlan}正在系統清理...${gl_bai}"
if command -v dnf &>/dev/null; then
rpm --rebuilddb
dnf autoremove -y
@@ -4216,13 +4732,13 @@ linux_clean() {
journalctl --vacuum-size=500M
elif command -v apk &>/dev/null; then
- echo "清理包管理器緩存..."
+ echo "清理包管理器快取..."
apk cache clean
echo "刪除系統日誌..."
rm -rf /var/log/*
- echo "刪除APK緩存..."
+ echo "刪除APK快取..."
rm -rf /var/cache/apk/*
- echo "刪除臨時文件..."
+ echo "刪除臨時檔案..."
rm -rf /tmp/*
elif command -v pacman &>/dev/null; then
@@ -4242,21 +4758,21 @@ linux_clean() {
elif command -v opkg &>/dev/null; then
echo "刪除系統日誌..."
rm -rf /var/log/*
- echo "刪除臨時文件..."
+ echo "刪除臨時檔案..."
rm -rf /tmp/*
elif command -v pkg &>/dev/null; then
echo "清理未使用的依賴..."
pkg autoremove -y
- echo "清理包管理器緩存..."
+ echo "清理包管理器快取..."
pkg clean -y
echo "刪除系統日誌..."
rm -rf /var/log/*
- echo "刪除臨時文件..."
+ echo "刪除臨時檔案..."
rm -rf /tmp/*
else
- echo "未知的包管理器!"
+ echo "未知的套件管理器!"
return
fi
return
@@ -4266,10 +4782,17 @@ linux_clean() {
bbr_on() {
-cat > /etc/sysctl.conf << EOF
-net.ipv4.tcp_congestion_control=bbr
-EOF
-sysctl -p
+# 統一寫入到 sysctl.d 以防與核心調優模組打架
+local CONF="/etc/sysctl.d/99-kejilion-bbr.conf"
+mkdir -p /etc/sysctl.d
+echo "net.core.default_qdisc=fq" > "$CONF"
+echo "net.ipv4.tcp_congestion_control=bbr" >> "$CONF"
+
+# 清理可能導致衝突的舊版 sysctl.conf 殘留
+sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf 2>/dev/null
+
+sysctl -p "$CONF" >/dev/null 2>&1 || sysctl --system >/dev/null 2>&1
}
@@ -4278,8 +4801,8 @@ set_dns() {
ip_address
-rm /etc/resolv.conf
-touch /etc/resolv.conf
+chattr -i /etc/resolv.conf
+> /etc/resolv.conf
if [ -n "$ipv4_address" ]; then
echo "nameserver $dns1_ipv4" >> /etc/resolv.conf
@@ -4291,6 +4814,13 @@ if [ -n "$ipv6_address" ]; then
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
fi
+if [ ! -s /etc/resolv.conf ]; then
+ echo "nameserver 223.5.5.5" >> /etc/resolv.conf
+ echo "nameserver 8.8.8.8" >> /etc/resolv.conf
+fi
+
+chattr +i /etc/resolv.conf
+
}
@@ -4299,9 +4829,9 @@ root_use
send_stats "優化DNS"
while true; do
clear
- echo "優化DNS地址"
+ echo "優化DNS位址"
echo "------------------------"
- echo "當前DNS地址"
+ echo "目前DNS地址"
cat /etc/resolv.conf
echo "------------------------"
echo ""
@@ -4335,7 +4865,9 @@ while true; do
;;
3)
install nano
+ chattr -i /etc/resolv.conf
nano /etc/resolv.conf
+ chattr +i /etc/resolv.conf
send_stats "手動編輯DNS配置"
;;
*)
@@ -4359,45 +4891,38 @@ correct_ssh_config() {
local sshd_config="/etc/ssh/sshd_config"
- # 如果找到 PasswordAuthentication 設置為 yes
- if grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
- # 如果找到 PubkeyAuthentication 設置為 yes
- if grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
+ if grep -Eq "^\s*PasswordAuthentication\s+no" "$sshd_config"; then
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
-e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' "$sshd_config"
+ else
+ sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin yes/' \
+ -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication yes/' \
+ -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' "$sshd_config"
fi
- # 如果 PasswordAuthentication 和 PubkeyAuthentication 都沒有匹配,則設置默認值
- if ! grep -Eq "^PasswordAuthentication\s+yes" "$sshd_config" && ! grep -Eq "^PubkeyAuthentication\s+yes" "$sshd_config"; then
- sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' "$sshd_config"
- sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' "$sshd_config"
- fi
-
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
}
new_ssh_port() {
- # 備份 SSH 配置文件
+ local new_port=$1
+
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- sed -i "s/Port [0-9]\+/Port $new_port/g" /etc/ssh/sshd_config
+ sed -i '/^\s*#\?\s*Port\s\+/d' /etc/ssh/sshd_config
+ sed -i "1i Port $new_port" /etc/ssh/sshd_config
correct_ssh_config
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
open_port $new_port
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
- echo "SSH 端口已修改為:$new_port"
+ echo "SSH 連接埠已修改為:$new_port"
sleep 1
@@ -4405,21 +4930,7 @@ new_ssh_port() {
-add_sshkey() {
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f /root/.ssh/sshkey -N ""
- cat ~/.ssh/sshkey.pub >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
-
- ip_address
- echo -e "私鑰信息已生成,務必復制保存,可保存成${gl_huang}${ipv4_address}_ssh.key${gl_bai}文件,用於以後的SSH登錄"
-
- echo "--------------------------------"
- cat ~/.ssh/sshkey
- echo "--------------------------------"
+sshkey_on() {
sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
-e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
@@ -4427,61 +4938,335 @@ add_sshkey() {
-e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
restart_ssh
- echo -e "${gl_lv}ROOT私鑰登錄已開啟,已關閉ROOT密碼登錄,重連將會生效${gl_bai}"
+ echo -e "${gl_lv}使用者金鑰登入模式已開啟,已關閉密碼登入模式,重連將會生效${gl_bai}"
+
+}
+
+
+
+add_sshkey() {
+ chmod 700 "${HOME}"
+ mkdir -p "${HOME}/.ssh"
+ chmod 700 "${HOME}/.ssh"
+ touch "${HOME}/.ssh/authorized_keys"
+
+ ssh-keygen -t ed25519 -C "xxxx@gmail.com" -f "${HOME}/.ssh/sshkey" -N ""
+
+ cat "${HOME}/.ssh/sshkey.pub" >> "${HOME}/.ssh/authorized_keys"
+ chmod 600 "${HOME}/.ssh/authorized_keys"
+
+ ip_address
+ echo -e "私鑰資訊已生成,務必複製保存,可保存成${gl_huang}${ipv4_address}_ssh.key${gl_bai}文件,用於以後的SSH登錄"
+
+ echo "--------------------------------"
+ cat "${HOME}/.ssh/sshkey"
+ echo "--------------------------------"
+ sshkey_on
}
+
+
+
import_sshkey() {
- read -e -p "請輸入您的SSH公鑰內容(通常以 'ssh-rsa' 或 'ssh-ed25519' 開頭):" public_key
+ local public_key="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local auth_keys="${ssh_dir}/authorized_keys"
+
+ if [[ -z "$public_key" ]]; then
+ read -e -p "請輸入您的SSH公鑰內容(通常以 'ssh-rsa' 或 'ssh-ed25519' 開頭):" public_key
+ fi
if [[ -z "$public_key" ]]; then
echo -e "${gl_hong}錯誤:未輸入公鑰內容。${gl_bai}"
return 1
fi
- chmod 700 ~/
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- touch ~/.ssh/authorized_keys
- echo "$public_key" >> ~/.ssh/authorized_keys
- chmod 600 ~/.ssh/authorized_keys
+ if [[ ! "$public_key" =~ ^ssh-(rsa|ed25519|ecdsa) ]]; then
+ echo -e "${gl_hong}錯誤:看起來不像合法的 SSH 公鑰。${gl_bai}"
+ return 1
+ fi
- sed -i -e 's/^\s*#\?\s*PermitRootLogin .*/PermitRootLogin prohibit-password/' \
- -e 's/^\s*#\?\s*PasswordAuthentication .*/PasswordAuthentication no/' \
- -e 's/^\s*#\?\s*PubkeyAuthentication .*/PubkeyAuthentication yes/' \
- -e 's/^\s*#\?\s*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
+ if grep -Fxq "$public_key" "$auth_keys" 2>/dev/null; then
+ echo "該公鑰已存在,無需重複添加"
+ return 0
+ fi
- rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
- restart_ssh
- echo -e "${gl_lv}公鑰已成功導入,ROOT私鑰登錄已開啟,已關閉ROOT密碼登錄,重連將會生效${gl_bai}"
+ mkdir -p "$ssh_dir"
+ chmod 700 "$ssh_dir"
+ touch "$auth_keys"
+ echo "$public_key" >> "$auth_keys"
+ chmod 600 "$auth_keys"
+ sshkey_on
}
+fetch_remote_ssh_keys() {
+
+ local keys_url="$1"
+ local base_dir="${2:-$HOME}"
+ local ssh_dir="${base_dir}/.ssh"
+ local authorized_keys="${ssh_dir}/authorized_keys"
+ local temp_file
+
+ if [[ -z "${keys_url}" ]]; then
+ read -e -p "請輸入您的遠端公鑰URL:" keys_url
+ fi
+
+ echo "此腳本將從遠端 URL 拉取 SSH 公鑰,並添加到${authorized_keys}"
+ echo ""
+ echo "遠端公鑰地址:"
+ echo " ${keys_url}"
+ echo ""
+
+ # 建立臨時文件
+ temp_file=$(mktemp)
+
+ # 下載公鑰
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL --connect-timeout 10 "${keys_url}" -o "${temp_file}" || {
+ echo "错误:无法从 URL 下载公钥(网络问题或地址无效)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ elif command -v wget >/dev/null 2>&1; then
+ wget -q --timeout=10 -O "${temp_file}" "${keys_url}" || {
+ echo "錯誤:無法從 URL 下載公鑰(網路問題或位址無效)" >&2
+ rm -f "${temp_file}"
+ return 1
+ }
+ else
+ echo "錯誤:系統中未找到 curl 或 wget,無法下載公鑰" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ # 檢查內容是否有效
+ if [[ ! -s "${temp_file}" ]]; then
+ echo "錯誤:下載到的檔案為空,URL 可能不包含任何公鑰" >&2
+ rm -f "${temp_file}"
+ return 1
+ fi
+
+ mkdir -p "${ssh_dir}"
+ chmod 700 "${ssh_dir}"
+ touch "${authorized_keys}"
+ chmod 600 "${authorized_keys}"
+
+ # 備份原有 authorized_keys
+ if [[ -f "${authorized_keys}" ]]; then
+ cp "${authorized_keys}" "${authorized_keys}.bak.$(date +%Y%m%d-%H%M%S)"
+ echo "已備份原有 authorized_keys 文件"
+ fi
+
+ # 追加公鑰(避免重複)
+ local added=0
+ while IFS= read -r line; do
+ [[ -z "${line}" || "${line}" =~ ^# ]] && continue
+
+ if ! grep -Fxq "${line}" "${authorized_keys}" 2>/dev/null; then
+ echo "${line}" >> "${authorized_keys}"
+ ((added++))
+ fi
+ done < "${temp_file}"
+
+ rm -f "${temp_file}"
+
+ echo ""
+ if (( added > 0 )); then
+ echo "成功添加${added}條新的公鑰到${authorized_keys}"
+ sshkey_on
+ else
+ echo "沒有新的公鑰需要添加(可能已全部存在)"
+ fi
+
+ echo ""
+}
+
+
+
+
+fetch_github_ssh_keys() {
+
+ local username="$1"
+ local base_dir="${2:-$HOME}"
+
+ echo "操作前,請確保您已在 GitHub 帳戶中新增了 SSH 公鑰:"
+ echo "1. 登入${gh_https_url}github.com/settings/keys"
+ echo "2. 點選 New SSH key 或 Add SSH key"
+ echo "3. Title 可隨意填寫(例如:Home Laptop 2026)"
+ echo "4. 將本機公鑰內容(通常是 ~/.ssh/id_ed25519.pub 或 id_rsa.pub 的全部內容)貼到 Key 字段"
+ echo "5. 點選 Add SSH key 完成新增"
+ echo ""
+ echo "新增完成後,GitHub 會公開提供您的所有公鑰,位址為:"
+ echo " ${gh_https_url}github.com/您的使用者名稱.keys"
+ echo ""
+
+
+ if [[ -z "${username}" ]]; then
+ read -e -p "請輸入您的 GitHub 使用者名稱(username,不含 @):" username
+ fi
+
+ if [[ -z "${username}" ]]; then
+ echo "錯誤:GitHub 使用者名稱不能為空" >&2
+ return 1
+ fi
+
+ keys_url="${gh_https_url}github.com/${username}.keys"
+
+ fetch_remote_ssh_keys "${keys_url}" "${base_dir}"
+
+}
+
+
+sshkey_panel() {
+ root_use
+ send_stats "使用者密鑰登入"
+ while true; do
+ clear
+ local REAL_STATUS=$(grep -i "^PubkeyAuthentication" /etc/ssh/sshd_config | tr '[:upper:]' '[:lower:]')
+ if [[ "$REAL_STATUS" =~ "yes" ]]; then
+ IS_KEY_ENABLED="${gl_lv}已啟用${gl_bai}"
+ else
+ IS_KEY_ENABLED="${gl_hui}未啟用${gl_bai}"
+ fi
+ echo -e "使用者密鑰登入模式${IS_KEY_ENABLED}"
+ echo "進階玩法: https://blog.kejilion.pro/ssh-key"
+ echo "------------------------------------------------"
+ echo "將會產生金鑰對,更安全的方式SSH登錄"
+ echo "------------------------"
+ echo "1. 產生新密鑰對 2. 手動輸入已有公鑰"
+ echo "3. 從GitHub導入已有公鑰 4. 從網址導入已有公鑰"
+ echo "5. 編輯公鑰檔案 6. 查看本機金鑰"
+ echo "------------------------"
+ echo "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "請輸入你的選擇:" host_dns
+ case $host_dns in
+ 1)
+ send_stats "產生新密鑰"
+ add_sshkey
+ break_end
+ ;;
+ 2)
+ send_stats "导入已有公钥"
+ import_sshkey
+ break_end
+ ;;
+ 3)
+ send_stats "導入GitHub遠端公鑰"
+ fetch_github_ssh_keys
+ break_end
+ ;;
+ 4)
+ send_stats "導入URL遠端公鑰"
+ read -e -p "請輸入您的遠端公鑰URL:" keys_url
+ fetch_remote_ssh_keys "${keys_url}"
+ break_end
+ ;;
+
+ 5)
+ send_stats "編輯公鑰文件"
+ install nano
+ nano ${HOME}/.ssh/authorized_keys
+ break_end
+ ;;
+
+ 6)
+ send_stats "查看本機密鑰"
+ echo "------------------------"
+ echo "公鑰資訊"
+ cat ${HOME}/.ssh/authorized_keys
+ echo "------------------------"
+ echo "私鑰資訊"
+ cat ${HOME}/.ssh/sshkey
+ echo "------------------------"
+ break_end
+ ;;
+ *)
+ break # 跳出循环,退出菜单
+ ;;
+ esac
+ done
+
+
+}
+
+
+
+
+
+
+add_sshpasswd() {
+
+ root_use
+ send_stats "設定密碼登入模式"
+ echo "設定密碼登入模式"
+
+ local target_user="$1"
+
+ # 如果沒有透過參數傳入,則交互輸入
+ if [[ -z "$target_user" ]]; then
+ read -e -p "請輸入要修改密碼的使用者名稱(預設 root):" target_user
+ fi
+
+ # 回車不輸入,預設 root
+ target_user=${target_user:-root}
+
+ # 校驗用戶是否存在
+ if ! id "$target_user" >/dev/null 2>&1; then
+ echo "錯誤:用戶$target_user不存在"
+ return 1
+ fi
+
+ passwd "$target_user"
+
+ if [[ "$target_user" == "root" ]]; then
+ sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
+ fi
+
+ sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+ rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
+
+ restart_ssh
+
+ echo -e "${gl_lv}密碼設定完畢,已更改為密碼登入模式!${gl_bai}"
+}
+
+
+
+
+
+
+
+
+
-add_sshpasswd() {
-echo "設置你的ROOT密碼"
-passwd
-sed -i 's/^\s*#\?\s*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
-sed -i 's/^\s*#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
-rm -rf /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
-restart_ssh
-echo -e "${gl_lv}ROOT登錄設置完畢!${gl_bai}"
-}
root_use() {
clear
-[ "$EUID" -ne 0 ] && echo -e "${gl_huang}提示:${gl_bai}該功能需要root用戶才能運行!" && break_end && kejilion
+[ "$EUID" -ne 0 ] && echo -e "${gl_huang}提示:${gl_bai}該功能需要root用戶才能運作!" && break_end && kejilion
}
+
+
+
+
+
+
+
+
+
dd_xitong() {
send_stats "重裝系統"
dd_xitong_MollyLau() {
@@ -4494,7 +5279,8 @@ dd_xitong() {
}
dd_xitong_1() {
- echo -e "重裝後初始用戶名:${gl_huang}root${gl_bai}初始密碼:${gl_huang}LeitboGi0ro${gl_bai}初始端口:${gl_huang}22${gl_bai}"
+ echo -e "重裝後初始使用者名稱:${gl_huang}root${gl_bai}初始密碼:${gl_huang}LeitboGi0ro${gl_bai}初始連接埠:${gl_huang}22${gl_bai}"
+ echo -e "${gl_huang}重裝後請及時修改初始密碼,以防止暴力入侵。命令列輸入passwd修改密碼${gl_bai}"
echo -e "按任意鍵繼續..."
read -n 1 -s -r -p ""
install wget
@@ -4502,7 +5288,7 @@ dd_xitong() {
}
dd_xitong_2() {
- echo -e "重裝後初始用戶名:${gl_huang}Administrator${gl_bai}初始密碼:${gl_huang}Teddysun.com${gl_bai}初始端口:${gl_huang}3389${gl_bai}"
+ echo -e "重裝後初始使用者名稱:${gl_huang}Administrator${gl_bai}初始密碼:${gl_huang}Teddysun.com${gl_bai}初始連接埠:${gl_huang}3389${gl_bai}"
echo -e "按任意鍵繼續..."
read -n 1 -s -r -p ""
install wget
@@ -4510,14 +5296,14 @@ dd_xitong() {
}
dd_xitong_3() {
- echo -e "重裝後初始用戶名:${gl_huang}root${gl_bai}初始密碼:${gl_huang}123@@@${gl_bai}初始端口:${gl_huang}22${gl_bai}"
+ echo -e "重裝後初始使用者名稱:${gl_huang}root${gl_bai}初始密碼:${gl_huang}123@@@${gl_bai}初始連接埠:${gl_huang}22${gl_bai}"
echo -e "按任意鍵繼續..."
read -n 1 -s -r -p ""
dd_xitong_bin456789
}
dd_xitong_4() {
- echo -e "重裝後初始用戶名:${gl_huang}Administrator${gl_bai}初始密碼:${gl_huang}123@@@${gl_bai}初始端口:${gl_huang}3389${gl_bai}"
+ echo -e "重裝後初始使用者名稱:${gl_huang}Administrator${gl_bai}初始密碼:${gl_huang}123@@@${gl_bai}初始連接埠:${gl_huang}3389${gl_bai}"
echo -e "按任意鍵繼續..."
read -n 1 -s -r -p ""
dd_xitong_bin456789
@@ -4527,92 +5313,96 @@ dd_xitong() {
root_use
echo "重裝系統"
echo "--------------------------------"
- echo -e "${gl_hong}注意:${gl_bai}重裝有風險失聯,不放心者慎用。重裝預計花費15分鐘,請提前備份數據。"
- echo -e "${gl_hui}感謝MollyLau大佬和bin456789大佬的腳本支持!${gl_bai} "
+ echo -e "${gl_hong}注意:${gl_bai}重裝有風險失聯,不放心者慎用。重裝預計花費15分鐘,請提前備份資料。"
+ echo -e "${gl_hui}感謝bin456789大佬和leitbogioro大佬的腳本支持!${gl_bai} "
+ echo -e "${gl_hui}bin456789項目地址:${gh_https_url}github.com/bin456789/reinstall${gl_bai}"
+ echo -e "${gl_hui}leitbogioro專案地址:${gh_https_url}github.com/leitbogioro/Tools${gl_bai}"
echo "------------------------"
- echo "1. Debian 12 2. Debian 11"
- echo "3. Debian 10 4. Debian 9"
+ echo "1. Debian 13 2. Debian 12"
+ echo "3. Debian 11 4. Debian 10"
echo "------------------------"
- echo "11. Ubuntu 24.04 12. Ubuntu 22.04"
- echo "13. Ubuntu 20.04 14. Ubuntu 18.04"
+ echo "11. Ubuntu 26.04 12. Ubuntu 24.04"
+ echo "13. Ubuntu 22.04 14. Ubuntu 20.04"
echo "------------------------"
echo "21. Rocky Linux 10 22. Rocky Linux 9"
echo "23. Alma Linux 10 24. Alma Linux 9"
echo "25. oracle Linux 10 26. oracle Linux 9"
- echo "27. Fedora Linux 42 28. Fedora Linux 41"
+ echo "27. Fedora Linux 44 28. Fedora Linux 43"
echo "29. CentOS 10 30. CentOS 9"
echo "------------------------"
echo "31. Alpine Linux 32. Arch Linux"
echo "33. Kali Linux 34. openEuler"
- echo "35. openSUSE Tumbleweed 36. fnos飛牛公測版"
+ echo "35. openSUSE Tumbleweed 36. fnos飛牛公測版"
echo "------------------------"
echo "41. Windows 11 42. Windows 10"
- echo "43. Windows 7 44. Windows Server 2022"
- echo "45. Windows Server 2019 46. Windows Server 2016"
+ echo "43. Windows 7 44. Windows Server 2025"
+ echo "45. Windows Server 2022 46. Windows Server 2019"
echo "47. Windows 11 ARM"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
read -e -p "請選擇要重裝的系統:" sys_choice
case "$sys_choice" in
+
+
1)
- send_stats "重裝debian 12"
- dd_xitong_1
- bash InstallNET.sh -debian 12
+ send_stats "重裝debian 13"
+ dd_xitong_3
+ bash reinstall.sh debian 13
reboot
exit
;;
+
2)
- send_stats "重裝debian 11"
- dd_xitong_1
- bash InstallNET.sh -debian 11
+ send_stats "重裝debian 12"
+ dd_xitong_3
+ bash reinstall.sh debian 12
reboot
exit
;;
3)
- send_stats "重裝debian 10"
- dd_xitong_1
- bash InstallNET.sh -debian 10
+ send_stats "重裝debian 11"
+ dd_xitong_3
+ bash reinstall.sh debian 11
reboot
exit
;;
4)
- send_stats "重裝debian 9"
- dd_xitong_1
- bash InstallNET.sh -debian 9
+ send_stats "重裝debian 10"
+ dd_xitong_3
+ bash reinstall.sh debian 10
reboot
exit
;;
11)
- send_stats "重裝ubuntu 24.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 24.04
+ send_stats "重裝ubuntu 26.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 26.04
reboot
exit
;;
12)
- send_stats "重裝ubuntu 22.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 22.04
+ send_stats "重裝ubuntu 24.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 24.04
reboot
exit
;;
13)
- send_stats "重裝ubuntu 20.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 20.04
+ send_stats "重裝ubuntu 22.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 22.04
reboot
exit
;;
14)
- send_stats "重裝ubuntu 18.04"
- dd_xitong_1
- bash InstallNET.sh -ubuntu 18.04
+ send_stats "重裝ubuntu 20.04"
+ dd_xitong_3
+ bash reinstall.sh ubuntu 20.04
reboot
exit
;;
-
21)
send_stats "重裝rockylinux10"
dd_xitong_3
@@ -4662,17 +5452,17 @@ dd_xitong() {
;;
27)
- send_stats "重裝fedora42"
+ send_stats "重裝fedora44"
dd_xitong_3
- bash reinstall.sh fedora
+ bash reinstall.sh fedora 44
reboot
exit
;;
28)
- send_stats "重裝fedora41"
+ send_stats "重裝fedora43"
dd_xitong_3
- bash reinstall.sh fedora 41
+ bash reinstall.sh fedora 43
reboot
exit
;;
@@ -4741,7 +5531,6 @@ dd_xitong() {
exit
;;
-
41)
send_stats "重裝windows11"
dd_xitong_2
@@ -4749,6 +5538,7 @@ dd_xitong() {
reboot
exit
;;
+
42)
dd_xitong_2
send_stats "重裝windows10"
@@ -4756,32 +5546,35 @@ dd_xitong() {
reboot
exit
;;
+
43)
send_stats "重裝windows7"
dd_xitong_4
- bash reinstall.sh windows --iso="https://drive.massgrave.dev/cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso" --image-name='Windows 7 PROFESSIONAL'
+ bash reinstall.sh windows --iso="https://archive.org/download/en_windows_7_professional_with_sp1_x64_dvd_u_676939_201906/en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso" --image-name='windows 7 professional'
reboot
exit
;;
44)
- send_stats "重裝windows server 22"
+ send_stats "重裝windows server 25"
dd_xitong_2
- bash InstallNET.sh -windows 2022 -lang "cn"
+ bash InstallNET.sh -windows 2025 -lang "cn"
reboot
exit
;;
+
45)
- send_stats "重裝windows server 19"
+ send_stats "重裝windows server 22"
dd_xitong_2
- bash InstallNET.sh -windows 2019 -lang "cn"
+ bash InstallNET.sh -windows 2022 -lang "cn"
reboot
exit
;;
+
46)
- send_stats "重裝windows server 16"
+ send_stats "重裝windows server 19"
dd_xitong_2
- bash InstallNET.sh -windows 2016 -lang "cn"
+ bash InstallNET.sh -windows 2019 -lang "cn"
reboot
exit
;;
@@ -4806,6 +5599,146 @@ bbrv3() {
root_use
send_stats "bbrv3管理"
+ xanmod_add_repo() {
+ local keyring="/usr/share/keyrings/xanmod-archive-keyring.gpg"
+ local list_file="/etc/apt/sources.list.d/xanmod-release.list"
+ local key_url="https://dl.xanmod.org/archive.key"
+ local fallback_key_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key"
+ local os_codename=""
+
+ if command -v lsb_release >/dev/null 2>&1; then
+ os_codename=$(lsb_release -sc)
+ elif [ -r /etc/os-release ]; then
+ os_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
+ fi
+
+ # 相容官方已移除的老系統代號(回退使用 releases 嘗試舊包庫)
+ if ! echo "bookworm trixie forky sid noble plucky questing resolute faye gigi wilma xia zara zena" | grep -qw "$os_codename"; then
+ os_codename="releases"
+ fi
+
+ # 官方已徹底移除對 jammy, focal, bullseye 等舊系統的 apt 支持
+ if echo "jammy focal bullseye buster" | grep -qw "$os_codename" || [ "$os_codename" = "releases" ]; then
+ echo -e "${gl_hong}XanMod 官方已停止對目前系統($os_codename)的 APT 來源支持,請升級至 Debian12 / Ubuntu24 或更高版本。${gl_bai}"
+ return 1
+ fi
+
+ if [ -z "$os_codename" ]; then
+ echo "無法取得系統代號,無法設定XanMod來源"
+ return 1
+ fi
+
+ install wget gnupg ca-certificates
+ mkdir -p /usr/share/keyrings /etc/apt/sources.list.d
+ if ! wget -qO - "$key_url" | gpg --dearmor -o "$keyring" --yes; then
+ echo "官方密钥下载失败,尝试备用下载源..."
+ wget -qO - "$fallback_key_url" | gpg --dearmor -o "$keyring" --yes || return 1
+ fi
+ chmod 644 "$keyring"
+ echo "deb [signed-by=$keyring] http://deb.xanmod.org $os_codename main" > "$list_file"
+ }
+
+ xanmod_detect_psabi_level() {
+ local psabi_output=""
+ psabi_output=$(awk 'BEGIN {
+ while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
+ if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
+ if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
+ if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
+ if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
+ if (level > 0) { print level; exit }
+ exit 1
+ }' /proc/cpuinfo 2>/dev/null) || return 1
+ printf '%s' "$psabi_output" | tr -dc '0-9' | head -c 1
+ }
+
+ xanmod_package_available() {
+ local package="$1"
+ apt-cache policy "$package" 2>/dev/null | grep -q 'Candidate: [^ ]'
+ }
+
+ xanmod_detect_package() {
+ local psabi_level=""
+ local level=""
+ local package=""
+ local prefix_list="linux-xanmod linux-xanmod-lts"
+
+ psabi_level=$(xanmod_detect_psabi_level) || return 1
+ [ -n "$psabi_level" ] || return 1
+ [ "$psabi_level" -gt 3 ] && psabi_level=3
+
+ apt update -y >/dev/null 2>&1
+
+ for prefix in $prefix_list; do
+ level="$psabi_level"
+ while [ "$level" -ge 1 ]; do
+ package="${prefix}-x64v${level}"
+ if xanmod_package_available "$package"; then
+ if [ "$level" != "$psabi_level" ] || [ "$prefix" = "linux-xanmod-lts" ]; then
+ echo "已自動配對合適安裝包:$package" >&2
+ fi
+ printf '%s\n' "$package"
+ return 0
+ fi
+ level=$((level - 1))
+ done
+ done
+
+ echo "軟體來源中找不到適合此CPU的XanMod核心包" >&2
+ return 1
+ }
+
+ xanmod_installed() {
+ dpkg-query -W -f='${Package}\n' 'linux-*xanmod*' 2>/dev/null | grep -q '^linux-.*xanmod'
+ }
+
+ xanmod_install_or_update() {
+ local action="$1"
+ local package=""
+
+ check_disk_space 3
+ check_swap
+ xanmod_add_repo || {
+ echo "XanMod官方倉庫配置失敗,請稍後重試"
+ return 1
+ }
+
+ package=$(xanmod_detect_package) || {
+ echo "無法辨識目前CPU或找不到匹配核心包,已取消安裝"
+ return 1
+ }
+
+ apt update -y
+ if [ "$action" = "update" ]; then
+ apt install -y --only-upgrade "$package" || apt install -y "$package" || {
+ echo "XanMod核心更新失敗,請檢查軟體來源或稍後重試"
+ return 1
+ }
+ else
+ apt install -y "$package" || {
+ echo "XanMod核心安裝失敗,請檢查軟體來源或稍後重試"
+ return 1
+ }
+ fi
+
+ bbr_on || {
+ echo "BBR3參數寫入失敗,請檢查系統配置"
+ return 1
+ }
+ echo "XanMod BBRv3內核處理完成。重啟後生效"
+ server_reboot
+ }
+
+ xanmod_uninstall() {
+ apt purge -y 'linux-*xanmod*'
+ apt autoremove -y
+ update-grub 2>/dev/null || true
+ rm -f /etc/apt/sources.list.d/xanmod-release.list
+ rm -f /usr/share/keyrings/xanmod-archive-keyring.gpg
+ echo "XanMod核心已卸載。重啟後生效"
+ server_reboot
+ }
+
local cpu_arch=$(uname -m)
if [ "$cpu_arch" = "aarch64" ]; then
bash <(curl -sL jhb.ovh/jb/bbrv3arm.sh)
@@ -4813,17 +5746,30 @@ bbrv3() {
linux_Settings
fi
- if dpkg -l | grep -q 'linux-xanmod'; then
+ if [ -r /etc/os-release ]; then
+ . /etc/os-release
+ if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
+ echo "目前環境不支持,僅支援Debian和Ubuntu系統"
+ break_end
+ linux_Settings
+ fi
+ else
+ echo "無法確定作業系統類型"
+ break_end
+ linux_Settings
+ fi
+
+ if xanmod_installed; then
while true; do
clear
local kernel_version=$(uname -r)
echo "您已安裝xanmod的BBRv3內核"
- echo "當前內核版本:$kernel_version"
+ echo "當前核心版本:$kernel_version"
echo ""
- echo "內核管理"
+ echo "核心管理"
echo "------------------------"
- echo "1. 更新BBRv3內核 2. 卸載BBRv3內核"
+ echo "1. 更新BBRv3內核 2. 卸載BBRv3內核"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -4831,91 +5777,31 @@ bbrv3() {
case $sub_choice in
1)
- apt purge -y 'linux-*xanmod1*'
- update-grub
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 步驟3:添加存儲庫
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- echo "XanMod內核已更新。重啟後生效"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
-
- server_reboot
-
- ;;
+ xanmod_install_or_update update
+ ;;
2)
- apt purge -y 'linux-*xanmod1*'
- update-grub
- echo "XanMod內核已卸載。重啟後生效"
- server_reboot
- ;;
-
+ xanmod_uninstall
+ ;;
*)
- break # 跳出循环,退出菜单
- ;;
+ break
+ ;;
esac
done
else
clear
- echo "設置BBR3加速"
- echo "視頻介紹: https://www.bilibili.com/video/BV14K421x7BS?t=0.1"
+ echo "設定BBR3加速"
+ echo "影片介紹: https://www.bilibili.com/video/BV14K421x7BS?t=0.1"
echo "------------------------------------------------"
- echo "僅支持Debian/Ubuntu"
- echo "請備份數據,將為你升級Linux內核開啟BBR3"
- echo "VPS是512M內存的,請提前添加1G虛擬內存,防止因內存不足失聯!"
+ echo "僅支援Debian/Ubuntu"
+ echo "請備份數據,將為你升級Linux核心開啟BBR3"
echo "------------------------------------------------"
read -e -p "確定繼續嗎? (Y/N):" choice
case "$choice" in
[Yy])
- check_disk_space 3
- if [ -r /etc/os-release ]; then
- . /etc/os-release
- if [ "$ID" != "debian" ] && [ "$ID" != "ubuntu" ]; then
- echo "當前環境不支持,僅支持Debian和Ubuntu系統"
- break_end
- linux_Settings
- fi
- else
- echo "無法確定操作系統類型"
- break_end
- linux_Settings
- fi
-
- check_swap
- install wget gnupg
-
- # wget -qO - https://dl.xanmod.org/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
- wget -qO - ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/archive.key | gpg --dearmor -o /usr/share/keyrings/xanmod-archive-keyring.gpg --yes
-
- # 步驟3:添加存儲庫
- echo 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org releases main' | tee /etc/apt/sources.list.d/xanmod-release.list
-
- # version=$(wget -q https://dl.xanmod.org/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
- local version=$(wget -q ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/check_x86-64_psabi.sh && chmod +x check_x86-64_psabi.sh && ./check_x86-64_psabi.sh | grep -oP 'x86-64-v\K\d+|x86-64-v\d+')
-
- apt update -y
- apt install -y linux-xanmod-x64v$version
-
- bbr_on
-
- echo "XanMod內核安裝並BBR3啟用成功。重啟後生效"
- rm -f /etc/apt/sources.list.d/xanmod-release.list
- rm -f check_x86-64_psabi.sh*
- server_reboot
-
+ xanmod_install_or_update install
;;
[Nn])
echo "已取消"
@@ -4928,7 +5814,6 @@ bbrv3() {
}
-
elrepo_install() {
# 導入 ELRepo GPG 公鑰
echo "導入 ELRepo GPG 公鑰..."
@@ -4936,34 +5821,34 @@ elrepo_install() {
# 檢測系統版本
local os_version=$(rpm -q --qf "%{VERSION}" $(rpm -qf /etc/os-release) 2>/dev/null | awk -F '.' '{print $1}')
local os_name=$(awk -F= '/^NAME/{print $2}' /etc/os-release)
- # 確保我們在一個支持的操作系統上運行
+ # 確保我們在一個支援的作業系統上運行
if [[ "$os_name" != *"Red Hat"* && "$os_name" != *"AlmaLinux"* && "$os_name" != *"Rocky"* && "$os_name" != *"Oracle"* && "$os_name" != *"CentOS"* ]]; then
- echo "不支持的操作系統:$os_name"
+ echo "不支援的作業系統:$os_name"
break_end
linux_Settings
fi
- # 打印檢測到的操作系統信息
- echo "檢測到的操作系統:$os_name $os_version"
+ # 列印偵測到的作業系統訊息
+ echo "偵測到的作業系統:$os_name $os_version"
# 根據系統版本安裝對應的 ELRepo 倉庫配置
if [[ "$os_version" == 8 ]]; then
- echo "安裝 ELRepo 倉庫配置 (版本 8)..."
+ echo "安裝 ELRepo 倉庫設定 (版本 8)..."
yum -y install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm
elif [[ "$os_version" == 9 ]]; then
- echo "安裝 ELRepo 倉庫配置 (版本 9)..."
+ echo "安裝 ELRepo 倉庫設定 (版本 9)..."
yum -y install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm
elif [[ "$os_version" == 10 ]]; then
- echo "安裝 ELRepo 倉庫配置 (版本 10)..."
+ echo "安裝 ELRepo 倉庫設定 (版本 10)..."
yum -y install https://www.elrepo.org/elrepo-release-10.el10.elrepo.noarch.rpm
else
- echo "不支持的系統版本:$os_version"
+ echo "不支援的系統版本:$os_version"
break_end
linux_Settings
fi
# 啟用 ELRepo 內核倉庫並安裝最新的主線內核
- echo "啟用 ELRepo 內核倉庫並安裝最新的主線內核..."
+ echo "啟用 ELRepo 核心倉庫並安裝最新的主線核心..."
# yum -y --enablerepo=elrepo-kernel install kernel-ml
yum --nogpgcheck -y --enablerepo=elrepo-kernel install kernel-ml
- echo "已安裝 ELRepo 倉庫配置並更新到最新主線內核。"
+ echo "已安裝 ELRepo 倉庫配置並更新至最新主線核心。"
server_reboot
}
@@ -4977,12 +5862,12 @@ elrepo() {
clear
kernel_version=$(uname -r)
echo "您已安裝elrepo內核"
- echo "當前內核版本:$kernel_version"
+ echo "當前核心版本:$kernel_version"
echo ""
- echo "內核管理"
+ echo "核心管理"
echo "------------------------"
- echo "1. 更新elrepo內核 2. 卸載elrepo內核"
+ echo "1. 更新elrepo內核 2. 卸載elrepo內核"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -5000,8 +5885,8 @@ elrepo() {
2)
dnf remove -y elrepo-release
rpm -qa | grep elrepo | grep kernel | xargs rpm -e --nodeps
- echo "elrepo內核已卸載。重啟後生效"
- send_stats "卸載紅帽內核"
+ echo "elrepo核心已卸載。重啟後生效"
+ send_stats "解除安裝紅帽內核"
server_reboot
;;
@@ -5015,10 +5900,10 @@ elrepo() {
clear
echo "請備份數據,將為你升級Linux內核"
- echo "視頻介紹: https://www.bilibili.com/video/BV1mH4y1w7qA?t=529.2"
+ echo "影片介紹: https://www.bilibili.com/video/BV1mH4y1w7qA?t=529.2"
echo "------------------------------------------------"
- echo "僅支持紅帽系列發行版 CentOS/RedHat/Alma/Rocky/oracle"
- echo "升級Linux內核可提升系統性能和安全,建議有條件的嘗試,生產環境謹慎升級!"
+ echo "僅支援紅帽系列發行 CentOS/RedHat/Alma/Rocky/oracle"
+ echo "升級Linux核心可提升系統效能與安全,建議有條件的嘗試,生產環境謹慎升級!"
echo "------------------------------------------------"
read -e -p "確定繼續嗎? (Y/N):" choice
@@ -5044,7 +5929,7 @@ elrepo() {
clamav_freshclam() {
- echo -e "${gl_huang}正在更新病毒庫...${gl_bai}"
+ echo -e "${gl_kjlan}正在更新病毒庫...${gl_bai}"
docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
@@ -5058,15 +5943,15 @@ clamav_scan() {
return
fi
- echo -e "${gl_huang}正在掃描目錄$@...${gl_bai}"
+ echo -e "${gl_kjlan}正在扫描目录$@... ${gl_bai}"
- # 構建 mount 參數
+ # 建構 mount 參數
local MOUNT_PARAMS=""
for dir in "$@"; do
MOUNT_PARAMS+="--mount type=bind,source=${dir},target=/mnt/host${dir} "
done
- # 構建 clamscan 命令參數
+ # 建構 clamscan 指令參數
local SCAN_PARAMS=""
for dir in "$@"; do
SCAN_PARAMS+="/mnt/host${dir} "
@@ -5076,7 +5961,7 @@ clamav_scan() {
> /home/docker/clamav/log/scan.log > /dev/null 2>&1
# 執行 Docker 命令
- docker run -it --rm \
+ docker run --rm \
--name clamav \
--mount source=clam_db,target=/var/lib/clamav \
$MOUNT_PARAMS \
@@ -5084,8 +5969,8 @@ clamav_scan() {
clamav/clamav-debian:latest \
clamscan -r --log=/var/log/clamav/scan.log $SCAN_PARAMS
- echo -e "${gl_lv}$@ 掃描完成,病毒報告存放在${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
- echo -e "${gl_lv}如果有病毒請在${gl_huang}scan.log${gl_lv}文件中搜索FOUND關鍵字確認病毒位置${gl_bai}"
+ echo -e "${gl_lv}$@ 扫描完成,病毒报告存放在${gl_huang}/home/docker/clamav/log/scan.log${gl_bai}"
+ echo -e "${gl_lv}如果有病毒請在${gl_huang}scan.log${gl_lv}文件中搜尋FOUND關鍵字確認病毒位置${gl_bai}"
}
@@ -5101,19 +5986,19 @@ clamav() {
while true; do
clear
echo "clamav病毒掃描工具"
- echo "視頻介紹: https://www.bilibili.com/video/BV1TqvZe4EQm?t=0.1"
+ echo "影片介紹: https://www.bilibili.com/video/BV1TqvZe4EQm?t=0.1"
echo "------------------------"
- echo "是一個開源的防病毒軟件工具,主要用於檢測和刪除各種類型的惡意軟件。"
- echo "包括病毒、特洛伊木馬、間諜軟件、惡意腳本和其他有害軟件。"
+ echo "是一個開源的防毒軟體工具,主要用於偵測和刪除各種類型的惡意軟體。"
+ echo "包括病毒、木馬、間諜軟體、惡意腳本和其他有害軟體。"
echo "------------------------"
- echo -e "${gl_lv}1. 全盤掃描${gl_bai} ${gl_huang}2. 重要目錄掃描${gl_bai} ${gl_kjlan}3. 自定義目錄掃描${gl_bai}"
+ echo -e "${gl_lv}1. 全盤掃描${gl_bai} ${gl_huang}2. 重要目錄掃描${gl_bai} ${gl_kjlan}3. 自訂目錄掃描${gl_bai}"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
- send_stats "全盤掃描"
+ send_stats "全碟掃描"
install_docker
docker volume create clam_db > /dev/null 2>&1
clamav_freshclam
@@ -5130,7 +6015,7 @@ clamav() {
break_end
;;
3)
- send_stats "自定義目錄掃描"
+ send_stats "自訂目錄掃描"
read -e -p "請輸入要掃描的目錄,用空格分隔(例如:/etc /var /usr /home /root):" directories
install_docker
clamav_freshclam
@@ -5146,171 +6031,348 @@ clamav() {
}
+# ============================================================================
+# Linux 核心調優模組(重構版)
+# 統一核心函數 + 場景差異化參數 + 持久化到設定檔 + 硬體自適應
+# 取代原 optimize_high_performance / optimize_balanced / optimize_web_server / restore_defaults
+# ============================================================================
+# 取得記憶體大小(MB)
+_get_mem_mb() {
+ awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
+}
-# 高性能模式優化函數
-optimize_high_performance() {
- echo -e "${gl_lv}切換到${tiaoyou_moshi}...${gl_bai}"
+# 統一內核調優核心函數
+# 參數: $1 = 模式名稱, $2 = 場景 (high/balanced/web/stream/game)
+_kernel_optimize_core() {
+ local mode_name="$1"
+ local scene="${2:-high}"
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
+ local MEM_MB=$(_get_mem_mb)
+
+ echo -e "${gl_lv}切換到${mode_name}...${gl_bai}"
+
+ # ── 根據場景設定參數 ──
+ local SWAPPINESS DIRTY_RATIO DIRTY_BG_RATIO OVERCOMMIT MIN_FREE_KB VFS_PRESSURE
+ local RMEM_MAX WMEM_MAX TCP_RMEM TCP_WMEM
+ local SOMAXCONN BACKLOG SYN_BACKLOG
+ local PORT_RANGE SCHED_AUTOGROUP THP NUMA FIN_TIMEOUT
+ local KEEPALIVE_TIME KEEPALIVE_INTVL KEEPALIVE_PROBES
+
+ case "$scene" in
+ high|stream|game)
+ # 高效能/直播/遊戲:激進參數
+ SWAPPINESS=10
+ DIRTY_RATIO=15
+ DIRTY_BG_RATIO=5
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=67108864
+ WMEM_MAX=67108864
+ TCP_RMEM="4096 262144 67108864"
+ TCP_WMEM="4096 262144 67108864"
+ SOMAXCONN=8192
+ BACKLOG=250000
+ SYN_BACKLOG=8192
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=10
+ KEEPALIVE_TIME=300
+ KEEPALIVE_INTVL=30
+ KEEPALIVE_PROBES=5
+ ;;
+ web)
+ # 網站伺服器:高並發優先
+ SWAPPINESS=10
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=1
+ VFS_PRESSURE=50
+ RMEM_MAX=33554432
+ WMEM_MAX=33554432
+ TCP_RMEM="4096 131072 33554432"
+ TCP_WMEM="4096 131072 33554432"
+ SOMAXCONN=16384
+ BACKLOG=10000
+ SYN_BACKLOG=16384
+ PORT_RANGE="1024 65535"
+ SCHED_AUTOGROUP=0
+ THP="never"
+ NUMA=0
+ FIN_TIMEOUT=15
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ balanced)
+ # 均衡模式:適度優化
+ SWAPPINESS=30
+ DIRTY_RATIO=20
+ DIRTY_BG_RATIO=10
+ OVERCOMMIT=0
+ VFS_PRESSURE=75
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ SOMAXCONN=4096
+ BACKLOG=5000
+ SYN_BACKLOG=4096
+ PORT_RANGE="1024 49151"
+ SCHED_AUTOGROUP=1
+ THP="always"
+ NUMA=1
+ FIN_TIMEOUT=30
+ KEEPALIVE_TIME=600
+ KEEPALIVE_INTVL=60
+ KEEPALIVE_PROBES=5
+ ;;
+ esac
- echo -e "${gl_lv}優化文件描述符...${gl_bai}"
- ulimit -n 65535
+ # ── 根據記憶體大小自適應調整 ──
+ if [ "$MEM_MB" -ge 16384 ]; then
+ MIN_FREE_KB=131072
+ [ "$scene" != "balanced" ] && SWAPPINESS=5
+ elif [ "$MEM_MB" -ge 4096 ]; then
+ MIN_FREE_KB=65536
+ elif [ "$MEM_MB" -ge 1024 ]; then
+ MIN_FREE_KB=32768
+ # 小記憶體縮小緩衝區
+ if [ "$scene" != "balanced" ]; then
+ RMEM_MAX=16777216
+ WMEM_MAX=16777216
+ TCP_RMEM="4096 87380 16777216"
+ TCP_WMEM="4096 65536 16777216"
+ fi
+ else
+ MIN_FREE_KB=16384
+ SWAPPINESS=30
+ OVERCOMMIT=0
+ RMEM_MAX=4194304
+ WMEM_MAX=4194304
+ TCP_RMEM="4096 32768 4194304"
+ TCP_WMEM="4096 32768 4194304"
+ SOMAXCONN=1024
+ BACKLOG=1000
+ fi
- echo -e "${gl_lv}優化虛擬內存...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=15 2>/dev/null
- sysctl -w vm.dirty_background_ratio=5 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # ── 直播場景額外:UDP 緩衝區加大 ──
+ local STREAM_EXTRA=""
+ if [ "$scene" = "stream" ]; then
+ STREAM_EXTRA="
+# 直播推流 UDP 優化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384"
+ fi
- echo -e "${gl_lv}優化網絡設置...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=250000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # ── 游戏服场景额外:低延迟优先 ──
+ local GAME_EXTRA=""
+ if [ "$scene" = "game" ]; then
+ GAME_EXTRA="
+# 遊戲服低延遲優化
+net.ipv4.udp_rmem_min = 16384
+net.ipv4.udp_wmem_min = 16384
+net.ipv4.tcp_notsent_lowat = 16384
+net.ipv4.tcp_slow_start_after_idle = 0"
+ fi
- echo -e "${gl_lv}優化緩存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # ── 載入 BBR 模組 ──
+ local CC="bbr"
+ local QDISC="fq"
+ local KVER
+ KVER=$(uname -r | grep -oP '^\d+\.\d+')
+ if printf '%s\n%s' "4.9" "$KVER" | sort -V -C; then
+ if ! lsmod 2>/dev/null | grep -q tcp_bbr; then
+ modprobe tcp_bbr 2>/dev/null
+ fi
+ if ! sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | grep -q bbr; then
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
+ else
+ CC="cubic"
+ QDISC="fq_codel"
+ fi
- echo -e "${gl_lv}優化CPU設置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
-
- echo -e "${gl_lv}其他優化...${gl_bai}"
- # 禁用透明大頁面,減少延遲
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # ── 備份已有設定 ──
+ [ -f "$CONF" ] && cp "$CONF" "${CONF}.bak.$(date +%s)"
+
+ # ── 寫入設定檔(持久化) ──
+ echo -e "${gl_lv}寫入優化配置...${gl_bai}"
+ cat > "$CONF" << SYSCTL
+# kejilion 内核调优配置
+# 模式: $mode_name | 場景: $scene
+# 記憶體: ${MEM_MB}MB | 產生時間: $(date '+%Y-%m-%d %H:%M:%S')
+
+# ── TCP 擁塞控制 ──
+net.core.default_qdisc = $QDISC
+net.ipv4.tcp_congestion_control = $CC
+
+# ── TCP 緩衝區 ──
+net.core.rmem_max = $RMEM_MAX
+net.core.wmem_max = $WMEM_MAX
+net.core.rmem_default = $(echo "$TCP_RMEM" | awk '{print $2}')
+net.core.wmem_default = $(echo "$TCP_WMEM" | awk '{print $2}')
+net.ipv4.tcp_rmem = $TCP_RMEM
+net.ipv4.tcp_wmem = $TCP_WMEM
+
+# ── 連接隊列 ──
+net.core.somaxconn = $SOMAXCONN
+net.core.netdev_max_backlog = $BACKLOG
+net.ipv4.tcp_max_syn_backlog = $SYN_BACKLOG
+
+# ── TCP 連線最佳化 ──
+net.ipv4.tcp_fastopen = 3
+net.ipv4.tcp_tw_reuse = 1
+net.ipv4.tcp_fin_timeout = $FIN_TIMEOUT
+net.ipv4.tcp_keepalive_time = $KEEPALIVE_TIME
+net.ipv4.tcp_keepalive_intvl = $KEEPALIVE_INTVL
+net.ipv4.tcp_keepalive_probes = $KEEPALIVE_PROBES
+net.ipv4.tcp_max_tw_buckets = 65536
+net.ipv4.tcp_syncookies = 1
+net.ipv4.tcp_synack_retries = 2
+net.ipv4.tcp_syn_retries = 3
+net.ipv4.tcp_mtu_probing = 1
+net.ipv4.tcp_sack = 1
+net.ipv4.tcp_timestamps = 1
+net.ipv4.tcp_window_scaling = 1
+
+# ── 埠與記憶體 ──
+net.ipv4.ip_local_port_range = $PORT_RANGE
+net.ipv4.tcp_mem = $((MEM_MB * 1024 / 8)) $((MEM_MB * 1024 / 4)) $((MEM_MB * 1024 / 2))
+net.ipv4.tcp_max_orphans = 32768
+
+# ── 虛擬記憶體 ──
+vm.swappiness = $SWAPPINESS
+vm.dirty_ratio = $DIRTY_RATIO
+vm.dirty_background_ratio = $DIRTY_BG_RATIO
+vm.overcommit_memory = $OVERCOMMIT
+vm.min_free_kbytes = $MIN_FREE_KB
+vm.vfs_cache_pressure = $VFS_PRESSURE
+
+# ── CPU/核心調度 ──
+kernel.sched_autogroup_enabled = $SCHED_AUTOGROUP
+$([ -f /proc/sys/kernel/numa_balancing ] && echo "kernel.numa_balancing = $NUMA" || echo "# numa_balancing 不支持")
+
+# ── 安全防護 ──
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+net.ipv4.icmp_ignore_bogus_error_responses = 1
+net.ipv4.conf.all.accept_redirects = 0
+net.ipv4.conf.default.accept_redirects = 0
+net.ipv4.conf.all.send_redirects = 0
+net.ipv4.conf.default.send_redirects = 0
+net.ipv6.conf.all.accept_redirects = 0
+net.ipv6.conf.default.accept_redirects = 0
+
+# ── 文件描述符 ──
+fs.file-max = 1048576
+fs.nr_open = 1048576
+
+# ── 連結追蹤 ──
+$(if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
+echo "net.netfilter.nf_conntrack_max = $((SOMAXCONN * 32))"
+echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200"
+echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30"
+echo "net.netfilter.nf_conntrack_tcp_timeout_close_wait = 15"
+echo "net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 15"
+else
+echo "# conntrack 未啟用"
+fi)
+$STREAM_EXTRA
+$GAME_EXTRA
+SYSCTL
+
+ # ── 應用配置(逐行,跳過不支援的參數) ──
+ echo -e "${gl_lv}應用優化參數...${gl_bai}"
+ local applied=0 skipped=0
+ while IFS= read -r line; do
+ # 跳過註解和空行
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
+ [[ -z "${line// /}" ]] && continue
+ if sysctl -w "$line" >/dev/null 2>&1; then
+ applied=$((applied + 1))
+ else
+ skipped=$((skipped + 1))
+ fi
+ done < "$CONF"
+ echo -e "${gl_lv}已应用 ${applied}項參數${skipped:+,跳過${skipped}項不支援的參數}${gl_bai}"
+ # ── 透明大页面 ──
+ if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
+ echo "$THP" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
+ fi
-}
+ # ── 文件描述符限制 ──
+ if ! grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ cat >> /etc/security/limits.conf << 'LIMITS'
-# 均衡模式優化函數
-optimize_balanced() {
- echo -e "${gl_lv}切換到均衡模式...${gl_bai}"
-
- echo -e "${gl_lv}優化文件描述符...${gl_bai}"
- ulimit -n 32768
-
- echo -e "${gl_lv}優化虛擬內存...${gl_bai}"
- sysctl -w vm.swappiness=30 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=32768 2>/dev/null
-
- echo -e "${gl_lv}優化網絡設置...${gl_bai}"
- sysctl -w net.core.rmem_max=8388608 2>/dev/null
- sysctl -w net.core.wmem_max=8388608 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=125000 2>/dev/null
- sysctl -w net.core.somaxconn=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 32768 8388608' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 49151' 2>/dev/null
-
- echo -e "${gl_lv}優化緩存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=75 2>/dev/null
-
- echo -e "${gl_lv}優化CPU設置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}其他優化...${gl_bai}"
- # 還原透明大頁面
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # 還原 NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# kejilion-optimize
+* soft nofile 1048576
+* hard nofile 1048576
+root soft nofile 1048576
+root hard nofile 1048576
+LIMITS
+ fi
+ # ── BBR 持久化 ──
+ if [ "$CC" = "bbr" ]; then
+ echo "tcp_bbr" > /etc/modules-load.d/bbr.conf 2>/dev/null
+ # 清理舊的 sysctl.conf 裡的 bbr 設定(避免衝突)
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
+ fi
+ echo -e "${gl_lv}${mode_name}優化完成!配置已持久化到${CONF}${gl_bai}"
+ echo -e "${gl_lv}記憶體:${MEM_MB}MB | 壅塞演算法:${CC}| 隊列:${QDISC}${gl_bai}"
}
-# 還原默認設置函數
-restore_defaults() {
- echo -e "${gl_lv}還原到默認設置...${gl_bai}"
-
- echo -e "${gl_lv}還原文件描述符...${gl_bai}"
- ulimit -n 1024
-
- echo -e "${gl_lv}還原虛擬內存...${gl_bai}"
- sysctl -w vm.swappiness=60 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=0 2>/dev/null
- sysctl -w vm.min_free_kbytes=16384 2>/dev/null
-
- echo -e "${gl_lv}還原網絡設置...${gl_bai}"
- sysctl -w net.core.rmem_max=212992 2>/dev/null
- sysctl -w net.core.wmem_max=212992 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=1000 2>/dev/null
- sysctl -w net.core.somaxconn=128 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=2048 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=0 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='32768 60999' 2>/dev/null
-
- echo -e "${gl_lv}還原緩存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=100 2>/dev/null
-
- echo -e "${gl_lv}還原CPU設置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=1 2>/dev/null
-
- echo -e "${gl_lv}還原其他優化...${gl_bai}"
- # 還原透明大頁面
- echo always > /sys/kernel/mm/transparent_hugepage/enabled
- # 還原 NUMA balancing
- sysctl -w kernel.numa_balancing=1 2>/dev/null
+# ── 各模式入口函數(保持原有呼叫介面不變) ──
+optimize_high_performance() {
+ _kernel_optimize_core "${tiaoyou_moshi:-高性能优化模式}" "high"
}
+optimize_balanced() {
+ _kernel_optimize_core "均衡最佳化模式" "balanced"
+}
-
-# 網站搭建優化函數
optimize_web_server() {
- echo -e "${gl_lv}切換到網站搭建優化模式...${gl_bai}"
+ _kernel_optimize_core "網站建置優化模式" "web"
+}
+
+# ── 還原預設設定(完全清理) ──
+restore_defaults() {
+ echo -e "${gl_lv}還原到預設值...${gl_bai}"
- echo -e "${gl_lv}優化文件描述符...${gl_bai}"
- ulimit -n 65535
+ local CONF="/etc/sysctl.d/99-kejilion-optimize.conf"
- echo -e "${gl_lv}優化虛擬內存...${gl_bai}"
- sysctl -w vm.swappiness=10 2>/dev/null
- sysctl -w vm.dirty_ratio=20 2>/dev/null
- sysctl -w vm.dirty_background_ratio=10 2>/dev/null
- sysctl -w vm.overcommit_memory=1 2>/dev/null
- sysctl -w vm.min_free_kbytes=65536 2>/dev/null
+ # 刪除最佳化設定檔(含外鏈自動調優配置)
+ rm -f "$CONF"
+ rm -f /etc/sysctl.d/99-network-optimize.conf
- echo -e "${gl_lv}優化網絡設置...${gl_bai}"
- sysctl -w net.core.rmem_max=16777216 2>/dev/null
- sysctl -w net.core.wmem_max=16777216 2>/dev/null
- sysctl -w net.core.netdev_max_backlog=5000 2>/dev/null
- sysctl -w net.core.somaxconn=4096 2>/dev/null
- sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_wmem='4096 65536 16777216' 2>/dev/null
- sysctl -w net.ipv4.tcp_congestion_control=bbr 2>/dev/null
- sysctl -w net.ipv4.tcp_max_syn_backlog=8192 2>/dev/null
- sysctl -w net.ipv4.tcp_tw_reuse=1 2>/dev/null
- sysctl -w net.ipv4.ip_local_port_range='1024 65535' 2>/dev/null
+ # 清理 sysctl.conf 裡可能殘留的 bbr 配置
+ sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf 2>/dev/null
- echo -e "${gl_lv}優化緩存管理...${gl_bai}"
- sysctl -w vm.vfs_cache_pressure=50 2>/dev/null
+ # 重新載入系統預設配置
+ sysctl --system 2>/dev/null | tail -1
- echo -e "${gl_lv}優化CPU設置...${gl_bai}"
- sysctl -w kernel.sched_autogroup_enabled=0 2>/dev/null
+ # 還原透明大頁面
+ [ -f /sys/kernel/mm/transparent_hugepage/enabled ] && \
+ echo always > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null
- echo -e "${gl_lv}其他優化...${gl_bai}"
- # 禁用透明大頁面,減少延遲
- echo never > /sys/kernel/mm/transparent_hugepage/enabled
- # 禁用 NUMA balancing
- sysctl -w kernel.numa_balancing=0 2>/dev/null
+ # 清理檔案描述符配置
+ if grep -q "# kejilion-optimize" /etc/security/limits.conf 2>/dev/null; then
+ sed -i '/# kejilion-optimize/,+4d' /etc/security/limits.conf
+ fi
+ # 清理 BBR 持久化
+ rm -f /etc/modules-load.d/bbr.conf 2>/dev/null
+ echo -e "${gl_lv}系統已還原到預設設定${gl_bai}"
}
@@ -5318,19 +6380,27 @@ Kernel_optimize() {
root_use
while true; do
clear
- send_stats "Linux內核調優管理"
- echo "Linux系統內核參數優化"
- echo "視頻介紹: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
+ send_stats "Linux核心調優管理"
+ local current_mode=$(grep "^# 模式:" /etc/sysctl.d/99-kejilion-optimize.conf 2>/dev/null | sed 's/# 模式: //' | awk -F'|' '{print $1}' | xargs)
+ [ -z "$current_mode" ] && [ -f /etc/sysctl.d/99-network-optimize.conf ] && current_mode="自動調優模式"
+ echo "Linux系統核心參數優化"
+ if [ -n "$current_mode" ]; then
+ echo -e "當前模式:${gl_lv}${current_mode}${gl_bai}"
+ else
+ echo -e "當前模式:${gl_hui}未最佳化${gl_bai}"
+ fi
+ echo "影片介紹: https://www.bilibili.com/video/BV1Kb421J7yg?t=0.1"
echo "------------------------------------------------"
- echo "提供多種系統參數調優模式,用戶可以根據自身使用場景進行選擇切換。"
+ echo "提供多種系統參數調優模式,使用者可依自身使用場景進行選擇切換。"
echo -e "${gl_huang}提示:${gl_bai}生產環境請謹慎使用!"
- echo "--------------------"
- echo "1. 高性能優化模式: 最大化系統性能,優化文件描述符、虛擬內存、網絡設置、緩存管理和CPU設置。"
- echo "2. 均衡優化模式: 在性能與資源消耗之間取得平衡,適合日常使用。"
- echo "3. 網站優化模式: 針對網站服務器進行優化,提高並發連接處理能力、響應速度和整體性能。"
- echo "4. 直播優化模式: 針對直播推流的特殊需求進行優化,減少延遲,提高傳輸性能。"
- echo "5. 遊戲服優化模式: 針對遊戲服務器進行優化,提高並發處理能力和響應速度。"
- echo "6. 還原默認設置: 將系統設置還原為默認配置。"
+ echo -e "--------------------"
+ echo -e "1. 高效能最佳化模式: 最大化系統效能,激進的記憶體和網路參數。"
+ echo -e "2. 均衡最佳化模式: 在效能與資源消耗之間取得平衡,適合日常使用。"
+ echo -e "3. 網站最佳化模式: 針對網站伺服器最佳化,超高並發連線佇列。"
+ echo -e "4. 直播最佳化模式: 針對直播推流優化,UDP 緩衝區加大,減少延遲。"
+ echo -e "5. 遊戲服最佳化模式: 針對遊戲伺服器最佳化,低延遲優先。"
+ echo -e "6. 還原預設設定: 將系統設定還原為預設配置。"
+ echo -e "7. 自動調優: 依測試資料自動調優核心參數。${gl_huang}★${gl_bai}"
echo "--------------------"
echo "0. 返回上一級選單"
echo "--------------------"
@@ -5339,15 +6409,15 @@ Kernel_optimize() {
1)
cd ~
clear
- local tiaoyou_moshi="高性能优化模式"
+ local tiaoyou_moshi="高效能最佳化模式"
optimize_high_performance
- send_stats "高性能模式優化"
+ send_stats "高性能模式最佳化"
;;
2)
cd ~
clear
optimize_balanced
- send_stats "均衡模式優化"
+ send_stats "均衡模式最佳化"
;;
3)
cd ~
@@ -5358,23 +6428,30 @@ Kernel_optimize() {
4)
cd ~
clear
- local tiaoyou_moshi="直播优化模式"
- optimize_high_performance
+ _kernel_optimize_core "直播優化模式" "stream"
send_stats "直播推流優化"
;;
5)
cd ~
clear
- local tiaoyou_moshi="游戏服优化模式"
- optimize_high_performance
+ _kernel_optimize_core "遊戲服優化模式" "game"
send_stats "遊戲服優化"
;;
6)
cd ~
clear
restore_defaults
- send_stats "還原默認設置"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh -o /tmp/network-optimize.sh && source /tmp/network-optimize.sh && restore_network_defaults
+ send_stats "還原預設設定"
+ ;;
+
+ 7)
+ cd ~
+ clear
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ send_stats "核心自動調優"
;;
+
*)
break
;;
@@ -5387,6 +6464,8 @@ Kernel_optimize() {
+
+
update_locale() {
local lang=$1
local locale_file=$2
@@ -5400,7 +6479,7 @@ update_locale() {
locale-gen
echo "LANG=${lang}" > /etc/default/locale
export LANG=${lang}
- echo -e "${gl_lv}系統語言已經修改為:$lang重新連接SSH生效。${gl_bai}"
+ echo -e "${gl_lv}系統語言已經修改為:$lang重新連線SSH生效。${gl_bai}"
hash -r
break_end
@@ -5409,17 +6488,17 @@ update_locale() {
install glibc-langpack-zh
localectl set-locale LANG=${lang}
echo "LANG=${lang}" | tee /etc/locale.conf
- echo -e "${gl_lv}系統語言已經修改為:$lang重新連接SSH生效。${gl_bai}"
+ echo -e "${gl_lv}系統語言已經修改為:$lang重新連線SSH生效。${gl_bai}"
hash -r
break_end
;;
*)
- echo "不支持的系統:$ID"
+ echo "不支援的系統:$ID"
break_end
;;
esac
else
- echo "不支持的系統,無法識別系統類型。"
+ echo "不支援的系統,無法辨識系統類型。"
break_end
fi
}
@@ -5434,7 +6513,7 @@ while true; do
clear
echo "當前系統語言:$LANG"
echo "------------------------"
- echo "1. 英文 2. 簡體中文 3. 繁體中文"
+ echo "1. 英文 2. 簡體中文 3. 繁體中文"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -5484,10 +6563,10 @@ break_end
shell_bianse() {
root_use
- send_stats "命令行美化工具"
+ send_stats "命令列美化工具"
while true; do
clear
- echo "命令行美化工具"
+ echo "命令列美化工具"
echo "------------------------"
echo -e "1. \033[1;32mroot \033[1;34mlocalhost \033[1;31m~ \033[0m${gl_bai}#"
echo -e "2. \033[1;35mroot \033[1;36mlocalhost \033[1;33m~ \033[0m${gl_bai}#"
@@ -5553,19 +6632,19 @@ linux_trash() {
local trash_status
if ! grep -q "trash-put" "$bashrc_profile"; then
- trash_status="${gl_hui}未启用${gl_bai}"
+ trash_status="${gl_hui}未啟用${gl_bai}"
else
- trash_status="${gl_lv}已启用${gl_bai}"
+ trash_status="${gl_lv}已啟用${gl_bai}"
fi
clear
- echo -e "當前回收站${trash_status}"
- echo -e "啟用後rm刪除的文件先進入回收站,防止誤刪重要文件!"
+ echo -e "目前回收站${trash_status}"
+ echo -e "啟用後rm刪除的檔案先進入回收站,防止誤刪重要檔案!"
echo "------------------------------------------------"
ls -l --color=auto "$TRASH_DIR" 2>/dev/null || echo "回收站為空"
echo "------------------------"
- echo "1. 啟用回收站 2. 關閉回收站"
- echo "3. 還原內容 4. 清空回收站"
+ echo "1. 啟用回收站 2. 關閉回收站"
+ echo "3. 還原內容 4. 清空回收站"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -5577,7 +6656,7 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='trash-put'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "回收站已啟用,刪除的文件將移至回收站。"
+ echo "回收站已啟用,刪除的檔案將移至回收站。"
sleep 2
;;
2)
@@ -5585,11 +6664,11 @@ linux_trash() {
sed -i '/alias rm/d' "$bashrc_profile"
echo "alias rm='rm -i'" >> "$bashrc_profile"
source "$bashrc_profile"
- echo "回收站已關閉,文件將直接刪除。"
+ echo "回收站已關閉,檔案將直接刪除。"
sleep 2
;;
3)
- read -e -p "輸入要還原的文件名:" file_to_restore
+ read -e -p "輸入要還原的檔名:" file_to_restore
if [ -e "$TRASH_DIR/$file_to_restore" ]; then
mv "$TRASH_DIR/$file_to_restore" "$HOME/"
echo "$file_to_restore已還原到主目錄。"
@@ -5611,21 +6690,24 @@ linux_trash() {
done
}
+linux_fav() {
+send_stats "命令收藏夾"
+bash <(curl -l -s ${gh_proxy}raw.githubusercontent.com/byJoey/cmdbox/refs/heads/main/install.sh)
+}
-
-# 創建備份
+# 建立備份
create_backup() {
- send_stats "創建備份"
+ send_stats "建立備份"
local TIMESTAMP=$(date +"%Y%m%d%H%M%S")
- # 提示用戶輸入備份目錄
- echo "創建備份示例:"
- echo "- 備份單個目錄: /var/www"
+ # 提示使用者輸入備份目錄
+ echo "建立備份範例:"
+ echo "- 備份單一目錄: /var/www"
echo "- 備份多個目錄: /etc /home /var/log"
- echo "- 直接回車將使用默認目錄 (/etc /usr /home)"
- read -r -p "請輸入要備份的目錄(多個目錄用空格分隔,直接回車則使用默認目錄):" input
+ echo "- 直接回車將使用預設目錄 (/etc /usr /home)"
+ read -e -p "請輸入要備份的目錄(多個目錄以空格分隔,直接回車則使用預設目錄):" input
- # 如果用戶沒有輸入目錄,則使用默認目錄
+ # 如果使用者沒有輸入目錄,則使用預設目錄
if [ -z "$input" ]; then
BACKUP_PATHS=(
"/etc" # 配置文件和软件包配置
@@ -5633,11 +6715,11 @@ create_backup() {
"/home" # 用户数据
)
else
- # 將用戶輸入的目錄按空格分隔成數組
+ # 將使用者輸入的目錄以空格分隔成數組
IFS=' ' read -r -a BACKUP_PATHS <<< "$input"
fi
- # 生成備份文件前綴
+ # 產生備份檔案前綴
local PREFIX=""
for path in "${BACKUP_PATHS[@]}"; do
# 提取目錄名稱並去除斜杠
@@ -5645,28 +6727,28 @@ create_backup() {
PREFIX+="${dir_name}_"
done
- # 去除最後一個下劃線
+ # 去除最後一條底線
local PREFIX=${PREFIX%_}
- # 生成備份文件名
+ # 產生備份檔名
local BACKUP_NAME="${PREFIX}_$TIMESTAMP.tar.gz"
- # 打印用戶選擇的目錄
- echo "您選擇的備份目錄為:"
+ # 列印使用者選擇的目錄
+ echo "您选择的备份目录为:"
for path in "${BACKUP_PATHS[@]}"; do
echo "- $path"
done
- # 創建備份
- echo "正在創建備份$BACKUP_NAME..."
+ # 建立備份
+ echo "正在建立備份$BACKUP_NAME..."
install tar
tar -czvf "$BACKUP_DIR/$BACKUP_NAME" "${BACKUP_PATHS[@]}"
# 檢查命令是否成功
if [ $? -eq 0 ]; then
- echo "備份創建成功:$BACKUP_DIR/$BACKUP_NAME"
+ echo "備份建立成功:$BACKUP_DIR/$BACKUP_NAME"
else
- echo "備份創建失敗!"
+ echo "備份建立失敗!"
exit 1
fi
}
@@ -5674,12 +6756,12 @@ create_backup() {
# 恢復備份
restore_backup() {
send_stats "恢復備份"
- # 選擇要恢復的備份
- read -e -p "請輸入要恢復的備份文件名:" BACKUP_NAME
+ # 選擇要還原的備份
+ read -e -p "請輸入要還原的備份檔名:" BACKUP_NAME
- # 檢查備份文件是否存在
+ # 檢查備份檔案是否存在
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
- echo "備份文件不存在!"
+ echo "備份檔案不存在!"
exit 1
fi
@@ -5689,7 +6771,7 @@ restore_backup() {
if [ $? -eq 0 ]; then
echo "備份恢復成功!"
else
- echo "備份恢復失敗!"
+ echo "备份恢复失败!"
exit 1
fi
}
@@ -5704,11 +6786,11 @@ list_backups() {
delete_backup() {
send_stats "刪除備份"
- read -e -p "請輸入要刪除的備份文件名:" BACKUP_NAME
+ read -e -p "請輸入要刪除的備份檔名:" BACKUP_NAME
- # 檢查備份文件是否存在
+ # 檢查備份檔案是否存在
if [ ! -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
- echo "備份文件不存在!"
+ echo "備份檔案不存在!"
exit 1
fi
@@ -5723,7 +6805,7 @@ delete_backup() {
fi
}
-# 備份主菜單
+# 備份主選單
linux_backup() {
BACKUP_DIR="/backups"
mkdir -p "$BACKUP_DIR"
@@ -5734,7 +6816,7 @@ linux_backup() {
echo "------------------------"
list_backups
echo "------------------------"
- echo "1. 創建備份 2. 恢復備份 3. 刪除備份"
+ echo "1. 建立備份 2. 恢復備份 3. 刪除備份"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -5745,7 +6827,7 @@ linux_backup() {
3) delete_backup ;;
*) break ;;
esac
- read -e -p "按回車鍵繼續..."
+ read -e -p "按下回車鍵繼續..."
done
}
@@ -5757,30 +6839,95 @@ linux_backup() {
-# 顯示連接列表
-list_connections() {
- echo "已保存的連接:"
- echo "------------------------"
- cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
- echo "------------------------"
+# SSH 輸入標準化函數
+kj_ssh_validate_host() {
+ local host="$1"
+ [[ -n "$host" && ! "$host" =~ [[:space:]] && "$host" =~ ^[A-Za-z0-9._:-]+$ ]]
}
+kj_ssh_validate_port() {
+ local port="$1"
+ [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
+}
-# 添加新連接
-add_connection() {
- send_stats "添加新連接"
- echo "創建新連接示例:"
- echo "- 連接名稱: my_server"
- echo "- IP地址: 192.168.1.100"
- echo "- 用戶名: root"
- echo "- 端口: 22"
- echo "------------------------"
- read -e -p "請輸入連接名稱:" name
- read -e -p "請輸入IP地址:" ip
- read -e -p "請輸入用戶名 (默認: root):" user
- local user=${user:-root} # 如果用户未输入,则使用默认值 root
- read -e -p "請輸入端口號 (默認: 22):" port
- local port=${port:-22} # 如果用户未输入,则使用默认值 22
+kj_ssh_validate_user() {
+ local user="$1"
+ [[ -n "$user" && "$user" =~ ^[A-Za-z_][A-Za-z0-9._-]*$ ]]
+}
+
+kj_ssh_read_host_port() {
+ local host_prompt="$1"
+ local port_prompt="$2"
+ local default_port="${3:-22}"
+
+ while true; do
+ read -e -p "$host_prompt" KJ_SSH_HOST
+ if kj_ssh_validate_host "$KJ_SSH_HOST"; then
+ break
+ fi
+ echo "錯誤: 請輸入有效的伺服器位址。"
+ done
+
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ break
+ fi
+ echo "錯誤: 連接埠必須是 1-65535 之間的數字。"
+ done
+}
+
+kj_ssh_read_host_user_port() {
+ local host_prompt="$1"
+ local user_prompt="$2"
+ local port_prompt="$3"
+ local default_user="${4:-root}"
+ local default_port="${5:-22}"
+
+ kj_ssh_read_host_port "$host_prompt" "$port_prompt" "$default_port"
+
+ while true; do
+ read -e -p "$user_prompt" KJ_SSH_USER
+ KJ_SSH_USER=${KJ_SSH_USER:-$default_user}
+ if kj_ssh_validate_user "$KJ_SSH_USER"; then
+ break
+ fi
+ echo "錯誤: 使用者名稱格式不正確。"
+ done
+}
+
+kj_ssh_parse_remote() {
+ local remote_raw="$1"
+ local default_user="${2:-root}"
+ local remote_user remote_host
+
+ if [[ "$remote_raw" == *@* ]]; then
+ remote_user="${remote_raw%@*}"
+ remote_host="${remote_raw#*@}"
+ else
+ remote_user="$default_user"
+ remote_host="$remote_raw"
+ fi
+
+ if ! kj_ssh_validate_user "$remote_user"; then
+ echo "错误: SSH 用户名格式不正确。"
+ return 1
+ fi
+
+ if ! kj_ssh_validate_host "$remote_host"; then
+ echo "錯誤: SSH 主機位址格式不正確。"
+ return 1
+ fi
+
+ KJ_SSH_USER="$remote_user"
+ KJ_SSH_HOST="$remote_host"
+ KJ_SSH_REMOTE="$remote_user@$remote_host"
+}
+
+kj_ssh_read_auth() {
+ local key_file="$1"
+ local password_or_key=""
echo "請選擇身份驗證方式:"
echo "1. 密碼"
@@ -5790,38 +6937,93 @@ add_connection() {
case $auth_choice in
1)
read -s -p "請輸入密碼:" password_or_key
- echo # 换行
+ echo
+ if [ -z "$password_or_key" ]; then
+ echo "錯誤: 密碼不能為空。"
+ return 1
+ fi
+ KJ_SSH_AUTH_METHOD="password"
+ KJ_SSH_AUTH_SECRET="$password_or_key"
;;
2)
- echo "請粘貼密鑰內容 (粘貼完成後按兩次回車):"
- local password_or_key=""
+ echo "請貼上金鑰內容 (貼上完成後按兩次回車):"
while IFS= read -r line; do
- # 如果輸入為空行且密鑰內容已經包含了開頭,則結束輸入
if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
break
fi
- # 如果是第一行或已經開始輸入密鑰內容,則繼續添加
if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- local password_or_key+="${line}"$'\n'
+ password_or_key+="${line}"$'\n'
fi
done
- # 檢查是否是密鑰內容
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/$name.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- local password_or_key="$key_file"
+ if [[ "$password_or_key" != *"-----BEGIN"* || "$password_or_key" != *"PRIVATE KEY-----"* ]]; then
+ echo "無效的密鑰內容!"
+ return 1
fi
+
+ mkdir -p "$(dirname "$key_file")"
+ echo -n "$password_or_key" > "$key_file"
+ chmod 600 "$key_file"
+ KJ_SSH_AUTH_METHOD="key"
+ KJ_SSH_AUTH_SECRET="$key_file"
;;
*)
echo "無效的選擇!"
- return
+ return 1
;;
esac
+}
+
+kj_ssh_read_password() {
+ local prompt="${1:-请输入密码: }"
+ while true; do
+ read -e -s -p "$prompt" KJ_SSH_PASSWORD
+ echo
+ [ -n "$KJ_SSH_PASSWORD" ] && break
+ echo "錯誤: 密碼不能為空。"
+ done
+}
+
+kj_ssh_read_port() {
+ local port_prompt="$1"
+ local default_port="${2:-22}"
+ while true; do
+ read -e -p "$port_prompt" KJ_SSH_PORT
+ KJ_SSH_PORT=${KJ_SSH_PORT:-$default_port}
+ if kj_ssh_validate_port "$KJ_SSH_PORT"; then
+ return 0
+ fi
+ echo "錯誤: 連接埠必須是 1-65535 之間的數字。"
+ done
+}
+
+# 顯示連線清單
+list_connections() {
+ echo "已儲存的連線:"
+ echo "------------------------"
+ cat "$CONFIG_FILE" | awk -F'|' '{print NR " - " $1 " (" $2 ")"}'
+ echo "------------------------"
+}
+
- echo "$name|$ip|$user|$port|$password_or_key" >> "$CONFIG_FILE"
- echo "連接已保存!"
+# 新增連接
+add_connection() {
+ send_stats "新增連接"
+ echo "建立新連接範例:"
+ echo "- 連線名稱: my_server"
+ echo "- IP位址: 192.168.1.100"
+ echo "- 使用者名稱: root"
+ echo "- 連接埠: 22"
+ echo "------------------------"
+ read -e -p "請輸入連線名稱:" name
+
+ kj_ssh_read_host_user_port "請輸入IP位址:" "請輸入使用者名稱 (預設: root):" "請輸入連接埠號碼 (預設: 22):" "root" "22"
+ if ! kj_ssh_read_auth "$KEY_DIR/$name.key"; then
+ return
+ fi
+
+ echo "$name|$KJ_SSH_HOST|$KJ_SSH_USER|$KJ_SSH_PORT|$KJ_SSH_AUTH_SECRET" >> "$CONFIG_FILE"
+ echo "連線已儲存!"
}
@@ -5833,19 +7035,19 @@ delete_connection() {
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "錯誤:未找到對應的連接。"
+ echo "錯誤:未找到對應的連線。"
return
fi
IFS='|' read -r name ip user port password_or_key <<< "$connection"
- # 如果連接使用的是密鑰文件,則刪除該密鑰文件
+ # 如果连接使用的是密钥文件,则删除该密钥文件
if [[ "$password_or_key" == "$KEY_DIR"* ]]; then
rm -f "$password_or_key"
fi
sed -i "${num}d" "$CONFIG_FILE"
- echo "連接已刪除!"
+ echo "連線已刪除!"
}
# 使用連接
@@ -5855,7 +7057,7 @@ use_connection() {
local connection=$(sed -n "${num}p" "$CONFIG_FILE")
if [[ -z "$connection" ]]; then
- echo "錯誤:未找到對應的連接。"
+ echo "錯誤:未找到對應的連線。"
return
fi
@@ -5866,10 +7068,10 @@ use_connection() {
# 使用密鑰連接
ssh -o StrictHostKeyChecking=no -i "$password_or_key" -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
- echo "連接失敗!請檢查以下內容:"
- echo "1. 密鑰文件路徑是否正確:$password_or_key"
- echo "2. 密鑰文件權限是否正確(應為 600)。"
- echo "3. 目標服務器是否允許使用密鑰登錄。"
+ echo "連線失敗!請檢查以下內容:"
+ echo "1. 密鑰檔案路徑是否正確:$password_or_key"
+ echo "2. 密鑰檔案權限是否正確(應為 600)。"
+ echo "3. 目標伺服器是否允許使用金鑰登入。"
fi
else
# 使用密碼連接
@@ -5882,22 +7084,22 @@ use_connection() {
fi
sshpass -p "$password_or_key" ssh -o StrictHostKeyChecking=no -p "$port" "$user@$ip"
if [[ $? -ne 0 ]]; then
- echo "連接失敗!請檢查以下內容:"
- echo "1. 用戶名和密碼是否正確。"
- echo "2. 目標服務器是否允許密碼登錄。"
- echo "3. 目標服務器的 SSH 服務是否正常運行。"
+ echo "連線失敗!請檢查以下內容:"
+ echo "1. 使用者名稱和密碼是否正確。"
+ echo "2. 目標伺服器是否允許密碼登入。"
+ echo "3. 目標伺服器的 SSH 服務是否正常運作。"
fi
fi
}
ssh_manager() {
- send_stats "ssh遠程連接工具"
+ send_stats "ssh遠端連線工具"
CONFIG_FILE="$HOME/.ssh_connections"
KEY_DIR="$HOME/.ssh/ssh_manager_keys"
- # 檢查配置文件和密鑰目錄是否存在,如果不存在則創建
+ # 檢查設定檔和金鑰目錄是否存在,如果不存在則創建
if [[ ! -f "$CONFIG_FILE" ]]; then
touch "$CONFIG_FILE"
fi
@@ -5909,11 +7111,11 @@ ssh_manager() {
while true; do
clear
- echo "SSH 遠程連接工具"
- echo "可以通過SSH連接到其他Linux系統上"
+ echo "SSH 遠端連線工具"
+ echo "可以透過SSH連接到其他Linux系統上"
echo "------------------------"
list_connections
- echo "1. 創建新連接 2. 使用連接 3. 刪除連接"
+ echo "1. 建立新連接 2. 使用連接 3. 刪除連接"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -5939,92 +7141,120 @@ ssh_manager() {
-# 列出可用的硬盤分區
+# 列出可用的硬碟分割區
list_partitions() {
- echo "可用的硬盤分區:"
+ echo "可用的硬碟分割區:"
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT | grep -v "sr\|loop"
}
-# 掛載分區
+
+# 持久化掛載分區
mount_partition() {
- send_stats "掛載分區"
- read -e -p "請輸入要掛載的分區名稱(例如 sda1):" PARTITION
+ send_stats "掛載分割區"
+ read -e -p "請輸入要掛載的分割區名稱(例如 sda1):" PARTITION
- # 檢查分區是否存在
- if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
+ DEVICE="/dev/$PARTITION"
+ MOUNT_POINT="/mnt/$PARTITION"
+
+ # 檢查分割區是否存在
+ if ! lsblk -no NAME | grep -qw "$PARTITION"; then
echo "分區不存在!"
- return
+ return 1
fi
- # 檢查分區是否已經掛載
- if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
+ # 檢查是否已掛載
+ if mount | grep -qw "$DEVICE"; then
echo "分區已經掛載!"
- return
+ return 1
fi
- # 創建掛載點
- MOUNT_POINT="/mnt/$PARTITION"
- mkdir -p "$MOUNT_POINT"
+ # 取得 UUID
+ UUID=$(blkid -s UUID -o value "$DEVICE")
+ if [ -z "$UUID" ]; then
+ echo "無法取得 UUID!"
+ return 1
+ fi
- # 掛載分區
- mount "/dev/$PARTITION" "$MOUNT_POINT"
+ # 取得檔案系統類型
+ FSTYPE=$(blkid -s TYPE -o value "$DEVICE")
+ if [ -z "$FSTYPE" ]; then
+ echo "無法取得檔案系統類型!"
+ return 1
+ fi
- if [ $? -eq 0 ]; then
- echo "分區掛載成功:$MOUNT_POINT"
- else
+ # 建立掛載點
+ mkdir -p "$MOUNT_POINT"
+
+ # 掛載
+ if ! mount "$DEVICE" "$MOUNT_POINT"; then
echo "分區掛載失敗!"
rmdir "$MOUNT_POINT"
+ return 1
+ fi
+
+ echo "分區已成功掛載到$MOUNT_POINT"
+
+ # 檢查 /etc/fstab 是否已經存在 UUID 或掛載點
+ if grep -qE "UUID=$UUID|[[:space:]]$MOUNT_POINT[[:space:]]" /etc/fstab; then
+ echo "/etc/fstab 中已存在該分區記錄,跳過寫入"
+ return 0
fi
+
+ # 寫入 /etc/fstab
+ echo "UUID=$UUID $MOUNT_POINT $FSTYPE defaults,nofail 0 2" >> /etc/fstab
+
+ echo "已寫入 /etc/fstab,實現持久化掛載"
}
-# 卸載分區
+
+# 解除安裝分割區
unmount_partition() {
- send_stats "卸載分區"
- read -e -p "請輸入要卸載的分區名稱(例如 sda1):" PARTITION
+ send_stats "解除安裝分割區"
+ read -e -p "請輸入要卸載的分割區名稱(例如 sda1):" PARTITION
- # 檢查分區是否已經掛載
+ # 檢查分割區是否已經掛載
MOUNT_POINT=$(lsblk -o MOUNTPOINT | grep -w "$PARTITION")
if [ -z "$MOUNT_POINT" ]; then
echo "分區未掛載!"
return
fi
- # 卸載分區
+ # 解除安裝分割區
umount "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "分區卸載成功:$MOUNT_POINT"
+ echo "分割區卸載成功:$MOUNT_POINT"
rmdir "$MOUNT_POINT"
else
echo "分區卸載失敗!"
fi
}
-# 列出已掛載的分區
+# 列出已掛載的分割區
list_mounted_partitions() {
- echo "已掛載的分區:"
+ echo "已掛載的分割區:"
df -h | grep -v "tmpfs\|udev\|overlay"
}
-# 格式化分區
+# 格式化分割區
format_partition() {
- send_stats "格式化分區"
- read -e -p "請輸入要格式化的分區名稱(例如 sda1):" PARTITION
+ send_stats "格式化分割區"
+ read -e -p "請輸入要格式化的分割區名稱(例如 sda1):" PARTITION
- # 檢查分區是否存在
+ # 檢查分割區是否存在
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
echo "分區不存在!"
return
fi
- # 檢查分區是否已經掛載
+ # 檢查分割區是否已經掛載
if lsblk -o MOUNTPOINT | grep -w "$PARTITION" > /dev/null; then
- echo "分區已經掛載,請先卸載!"
+ echo "分割區已經掛載,請先卸載!"
return
fi
- # 選擇文件系統類型
- echo "請選擇文件系統類型:"
+ # 選擇檔案系統類型
+ echo "請選擇檔案系統類型:"
echo "1. ext4"
echo "2. xfs"
echo "3. ntfs"
@@ -6040,51 +7270,51 @@ format_partition() {
esac
# 確認格式化
- read -e -p "確認格式化分區 /dev/$PARTITION為$FS_TYPE嗎? (y/n):" CONFIRM
+ read -e -p "確認格式化分割區 /dev/$PARTITION為$FS_TYPE嗎? (y/n):" CONFIRM
if [ "$CONFIRM" != "y" ]; then
echo "操作已取消。"
return
fi
- # 格式化分區
- echo "正在格式化分區 /dev/$PARTITION為$FS_TYPE ..."
+ # 格式化分割區
+ echo "正在格式化分割區 /dev/$PARTITION為$FS_TYPE ..."
mkfs.$FS_TYPE "/dev/$PARTITION"
if [ $? -eq 0 ]; then
- echo "分區格式化成功!"
+ echo "分割區格式化成功!"
else
- echo "分區格式化失敗!"
+ echo "分割區格式化失敗!"
fi
}
# 檢查分區狀態
check_partition() {
send_stats "檢查分區狀態"
- read -e -p "請輸入要檢查的分區名稱(例如 sda1):" PARTITION
+ read -e -p "請輸入要檢查的分割區名稱(例如 sda1):" PARTITION
- # 檢查分區是否存在
+ # 檢查分割區是否存在
if ! lsblk -o NAME | grep -w "$PARTITION" > /dev/null; then
echo "分區不存在!"
return
fi
# 檢查分區狀態
- echo "檢查分區 /dev/$PARTITION的狀態:"
+ echo "檢查分割區 /dev/$PARTITION的狀態:"
fsck "/dev/$PARTITION"
}
-# 主菜單
+# 主選單
disk_manager() {
- send_stats "硬盤管理功能"
+ send_stats "硬碟管理功能"
while true; do
clear
- echo "硬盤分區管理"
- echo -e "${gl_huang}該功能內部測試階段,請勿在生產環境使用。${gl_bai}"
+ echo "硬碟分割管理"
+ echo -e "${gl_huang}此功能內部測試階段,請勿在生產環境使用。${gl_bai}"
echo "------------------------"
list_partitions
echo "------------------------"
- echo "1. 掛載分區 2. 卸載分區 3. 查看已掛載分區"
- echo "4. 格式化分區 5. 檢查分區狀態"
+ echo "1. 掛載分割區 2. 卸載分割區 3. 檢視已掛載分割區"
+ echo "4. 格式化分区 5. 检查分区状态"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -6097,7 +7327,7 @@ disk_manager() {
5) check_partition ;;
*) break ;;
esac
- read -e -p "按回車鍵繼續..."
+ read -e -p "按下回車鍵繼續..."
done
}
@@ -6106,89 +7336,61 @@ disk_manager() {
# 顯示任務列表
list_tasks() {
- echo "已保存的同步任務:"
+ echo "已儲存的同步任務:"
echo "---------------------------------"
awk -F'|' '{print NR " - " $1 " ( " $2 " -> " $3":"$4 " )"}' "$CONFIG_FILE"
echo "---------------------------------"
}
-# 添加新任務
+# 新增任務
add_task() {
- send_stats "添加新同步任務"
- echo "創建新同步任務示例:"
+ send_stats "新增同步任務"
+ echo "建立新同步任務範例:"
echo "- 任務名稱: backup_www"
- echo "- 本地目錄: /var/www"
- echo "- 遠程地址: user@192.168.1.100"
- echo "- 遠程目錄: /backup/www"
- echo "- 端口號 (默認 22)"
+ echo "- 本機目錄: /var/www"
+ echo "- 遠端位址: user@192.168.1.100"
+ echo "- 遠端目錄: /backup/www"
+ echo "- 連接埠號碼 (預設 22)"
echo "---------------------------------"
read -e -p "請輸入任務名稱:" name
read -e -p "請輸入本地目錄:" local_path
- read -e -p "請輸入遠程目錄:" remote_path
- read -e -p "請輸入遠程用戶@IP:" remote
- read -e -p "請輸入 SSH 端口 (默認 22):" port
- port=${port:-22}
+ read -e -p "請輸入遠端目錄:" remote_path
- echo "請選擇身份驗證方式:"
- echo "1. 密碼"
- echo "2. 密鑰"
- read -e -p "請選擇 (1/2):" auth_choice
+ while true; do
+ read -e -p "請輸入遠端使用者@IP:" remote
+ if kj_ssh_parse_remote "$remote" "root"; then
+ remote="$KJ_SSH_REMOTE"
+ break
+ fi
+ done
- case $auth_choice in
- 1)
- read -s -p "請輸入密碼:" password_or_key
- echo # 换行
- auth_method="password"
- ;;
- 2)
- echo "請粘貼密鑰內容 (粘貼完成後按兩次回車):"
- local password_or_key=""
- while IFS= read -r line; do
- # 如果輸入為空行且密鑰內容已經包含了開頭,則結束輸入
- if [[ -z "$line" && "$password_or_key" == *"-----BEGIN"* ]]; then
- break
- fi
- # 如果是第一行或已經開始輸入密鑰內容,則繼續添加
- if [[ -n "$line" || "$password_or_key" == *"-----BEGIN"* ]]; then
- password_or_key+="${line}"$'\n'
- fi
- done
+ kj_ssh_read_port "請輸入 SSH 連接埠 (預設 22):" "22"
+ port="$KJ_SSH_PORT"
- # 檢查是否是密鑰內容
- if [[ "$password_or_key" == *"-----BEGIN"* && "$password_or_key" == *"PRIVATE KEY-----"* ]]; then
- local key_file="$KEY_DIR/${name}_sync.key"
- echo -n "$password_or_key" > "$key_file"
- chmod 600 "$key_file"
- password_or_key="$key_file"
- auth_method="key"
- else
- echo "無效的密鑰內容!"
- return
- fi
- ;;
- *)
- echo "無效的選擇!"
- return
- ;;
- esac
+ if ! kj_ssh_read_auth "$KEY_DIR/${name}_sync.key"; then
+ return
+ fi
+ auth_method="$KJ_SSH_AUTH_METHOD"
+ password_or_key="$KJ_SSH_AUTH_SECRET"
echo "請選擇同步模式:"
echo "1. 標準模式 (-avz)"
- echo "2. 刪除目標文件 (-avz --delete)"
+ echo "2. 刪除目標檔 (-avz --delete)"
read -e -p "請選擇 (1/2):" mode
case $mode in
1) options="-avz" ;;
2) options="-avz --delete" ;;
- *) echo "無效選擇,使用默認 -avz"; options="-avz" ;;
+ *) echo "無效選擇,使用預設 -avz"; options="-avz" ;;
esac
echo "$name|$local_path|$remote|$remote_path|$port|$options|$auth_method|$password_or_key" >> "$CONFIG_FILE"
install rsync rsync
- echo "任務已保存!"
+ echo "任務已儲存!"
}
+
# 刪除任務
delete_task() {
send_stats "刪除同步任務"
@@ -6202,7 +7404,7 @@ delete_task() {
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # 如果任務使用的是密鑰文件,則刪除該密鑰文件
+ # 如果任務使用的是金鑰文件,則刪除該金鑰文件
if [[ "$auth_method" == "key" && "$password_or_key" == "$KEY_DIR"* ]]; then
rm -f "$password_or_key"
fi
@@ -6229,7 +7431,7 @@ run_task() {
num="$1"
fi
- # 如果沒有傳入任務編號,提示用戶輸入
+ # 如果沒有傳入任務編號,提示使用者輸入
if [[ -z "$num" ]]; then
read -e -p "請輸入要執行的任務編號:" num
fi
@@ -6242,9 +7444,9 @@ run_task() {
IFS='|' read -r name local_path remote remote_path port options auth_method password_or_key <<< "$task"
- # 根據同步方向調整源和目標路徑
+ # 根據同步方向調整來源和目標路徑
if [[ "$direction" == "pull" ]]; then
- echo "正在拉取同步到本地:$remote:$local_path -> $remote_path"
+ echo "正在拉取同步到本地: $remote:$local_path -> $remote_path"
source="$remote:$local_path"
destination="$remote_path"
else
@@ -6253,7 +7455,7 @@ run_task() {
destination="$remote:$remote_path"
fi
- # 添加 SSH 連接通用參數
+ # 新增 SSH 連線通用參數
local ssh_options="-p $port -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
if [[ "$auth_method" == "password" ]]; then
@@ -6266,14 +7468,14 @@ run_task() {
fi
sshpass -p "$password_or_key" rsync $options -e "ssh $ssh_options" "$source" "$destination"
else
- # 檢查密鑰文件是否存在和權限是否正確
+ # 檢查密鑰檔案是否存在和權限是否正確
if [[ ! -f "$password_or_key" ]]; then
- echo "錯誤:密鑰文件不存在:$password_or_key"
+ echo "錯誤:密鑰檔案不存在:$password_or_key"
return
fi
if [[ "$(stat -c %a "$password_or_key")" != "600" ]]; then
- echo "警告:密鑰文件權限不正確,正在修復..."
+ echo "警告:密鑰檔案權限不正確,正在修復..."
chmod 600 "$password_or_key"
fi
@@ -6284,17 +7486,17 @@ run_task() {
echo "同步完成!"
else
echo "同步失敗! 請檢查以下內容:"
- echo "1. 網絡連接是否正常"
- echo "2. 遠程主機是否可訪問"
- echo "3. 認證信息是否正確"
- echo "4. 本地和遠程目錄是否有正確的訪問權限"
+ echo "1. 網路連線是否正常"
+ echo "2. 遠端主機是否可存取"
+ echo "3. 認證資訊是否正確"
+ echo "4. 本機和遠端目錄是否有正確的存取權限"
fi
}
-# 創建定時任務
+# 建立定時任務
schedule_task() {
- send_stats "添加同步定時任務"
+ send_stats "新增同步定時任務"
read -e -p "請輸入要定時同步的任務編號:" num
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
@@ -6326,14 +7528,14 @@ schedule_task() {
return
fi
- # 創建到用戶的 crontab
+ # 建立到使用者的 crontab
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
- echo "定時任務已創建:$cron_job"
+ echo "定時任務已建立:$cron_job"
}
# 查看定時任務
view_tasks() {
- echo "當前的定時任務:"
+ echo "目前的定時任務:"
echo "---------------------------------"
crontab -l | grep "k rsync_run"
echo "---------------------------------"
@@ -6353,23 +7555,23 @@ delete_task_schedule() {
}
-# 任務管理主菜單
+# 工作管理員主選單
rsync_manager() {
CONFIG_FILE="$HOME/.rsync_tasks"
CRON_FILE="$HOME/.rsync_cron"
while true; do
clear
- echo "Rsync 遠程同步工具"
- echo "遠程目錄之間同步,支持增量同步,高效穩定。"
+ echo "Rsync 遠端同步工具"
+ echo "遠端目錄之間同步,支援增量同步,高效穩定。"
echo "---------------------------------"
list_tasks
echo
view_tasks
echo
- echo "1. 創建新任務 2. 刪除任務"
- echo "3. 執行本地同步到遠端 4. 執行遠端同步到本地"
- echo "5. 創建定時任務 6. 刪除定時任務"
+ echo "1. 建立新任務 2. 刪除任務"
+ echo "3. 执行本地同步到远端 4. 执行远端同步到本地"
+ echo "5. 建立定時任務 6. 刪除定時任務"
echo "---------------------------------"
echo "0. 返回上一級選單"
echo "---------------------------------"
@@ -6384,7 +7586,7 @@ rsync_manager() {
0) break ;;
*) echo "無效的選擇,請重試。" ;;
esac
- read -e -p "按回車鍵繼續..."
+ read -e -p "按下回車鍵繼續..."
done
}
@@ -6396,10 +7598,13 @@ rsync_manager() {
-linux_ps() {
+linux_info() {
+
+
clear
- send_stats "系統信息查詢"
+ echo -e "${gl_kjlan}正在查詢系統資訊…${gl_bai}"
+ send_stats "系統資訊查詢"
ip_address
@@ -6443,15 +7648,17 @@ linux_ps() {
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1 % 3600) / 60); if (run_days > 0) printf("%d天 ", run_days); if (run_hours > 0) printf("%d时 ", run_hours); printf("%d分\n", run_minutes)}')
+ local runtime=$(cat /proc/uptime | awk -F. '{run_days=int($1 / 86400);run_hours=int(($1 % 86400) / 3600);run_minutes=int(($1% 3600) / 60); if (run_days > 0) printf("%d天 ", run_days); if (run_hours > 0) printf("%d時 ", run_hours); printf("%d分\n", run_minutes)}')
local timezone=$(current_timezone)
+ local tcp_count=$(ss -t | wc -l)
+ local udp_count=$(ss -u | wc -l)
- echo ""
- echo -e "系統信息查詢"
+ clear
+ echo -e "系統資訊查詢"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}主機名:${gl_bai}$hostname"
+ echo -e "${gl_kjlan}主機名稱:${gl_bai}$hostname"
echo -e "${gl_kjlan}系統版本:${gl_bai}$os_info"
echo -e "${gl_kjlan}Linux版本:${gl_bai}$kernel_version"
echo -e "${gl_kjlan}-------------"
@@ -6462,24 +7669,25 @@ linux_ps() {
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}CPU佔用:${gl_bai}$cpu_usage_percent%"
echo -e "${gl_kjlan}系統負載:${gl_bai}$load"
- echo -e "${gl_kjlan}物理內存:${gl_bai}$mem_info"
- echo -e "${gl_kjlan}虛擬內存:${gl_bai}$swap_info"
- echo -e "${gl_kjlan}硬盤佔用:${gl_bai}$disk_info"
+ echo -e "${gl_kjlan}TCP|UDP連線數:${gl_bai}$tcp_count|$udp_count"
+ echo -e "${gl_kjlan}實體記憶體:${gl_bai}$mem_info"
+ echo -e "${gl_kjlan}虛擬記憶體:${gl_bai}$swap_info"
+ echo -e "${gl_kjlan}硬碟佔用:${gl_bai}$disk_info"
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}總接收:${gl_bai}$rx"
echo -e "${gl_kjlan}總發送:${gl_bai}$tx"
echo -e "${gl_kjlan}-------------"
- echo -e "${gl_kjlan}網絡算法:${gl_bai}$congestion_algorithm $queue_algorithm"
+ echo -e "${gl_kjlan}網路演算法:${gl_bai}$congestion_algorithm $queue_algorithm"
echo -e "${gl_kjlan}-------------"
echo -e "${gl_kjlan}運營商:${gl_bai}$isp_info"
if [ -n "$ipv4_address" ]; then
- echo -e "${gl_kjlan}IPv4地址:${gl_bai}$ipv4_address"
+ echo -e "${gl_kjlan}IPv4位址:${gl_bai}$ipv4_address"
fi
if [ -n "$ipv6_address" ]; then
- echo -e "${gl_kjlan}IPv6地址:${gl_bai}$ipv6_address"
+ echo -e "${gl_kjlan}IPv6位址:${gl_bai}$ipv6_address"
fi
- echo -e "${gl_kjlan}DNS地址:${gl_bai}$dns_addresses"
+ echo -e "${gl_kjlan}DNS位址:${gl_bai}$dns_addresses"
echo -e "${gl_kjlan}地理位置:${gl_bai}$country $city"
echo -e "${gl_kjlan}系統時間:${gl_bai}$timezone $current_time"
echo -e "${gl_kjlan}-------------"
@@ -6498,28 +7706,80 @@ linux_tools() {
clear
# send_stats "基礎工具"
echo -e "基礎工具"
+
+ tools=(
+ curl wget sudo socat htop iftop unzip tar tmux ffmpeg
+ btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders
+ vim nano git
+ )
+
+ if command -v apt >/dev/null 2>&1; then
+ PM="apt"
+ elif command -v dnf >/dev/null 2>&1; then
+ PM="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PM="yum"
+ elif command -v pacman >/dev/null 2>&1; then
+ PM="pacman"
+ elif command -v apk >/dev/null 2>&1; then
+ PM="apk"
+ elif command -v zypper >/dev/null 2>&1; then
+ PM="zypper"
+ elif command -v opkg >/dev/null 2>&1; then
+ PM="opkg"
+ elif command -v pkg >/dev/null 2>&1; then
+ PM="pkg"
+ else
+ echo "❌ 未識別的套件管理器"
+ exit 1
+ fi
+
+ echo "📦 使用套件管理器:$PM"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+
+ for ((i=0; i<${#tools[@]}; i+=2)); do
+ # 左列
+ if command -v "${tools[i]}" >/dev/null 2>&1; then
+ left=$(printf "✅ %-12s 已安裝" "${tools[i]}")
+ else
+ left=$(printf "❌ %-12s 未安裝" "${tools[i]}")
+ fi
+
+ # 右列(防止數組越界)
+ if [[ -n "${tools[i+1]}" ]]; then
+ if command -v "${tools[i+1]}" >/dev/null 2>&1; then
+ right=$(printf "✅ %-12s 已安裝" "${tools[i+1]}")
+ else
+ right=$(printf "❌ %-12s 未安裝" "${tools[i+1]}")
+ fi
+ printf "%-42s %s\n" "$left" "$right"
+ else
+ printf "%s\n" "$left"
+ fi
+ done
+
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}curl 下載工具${gl_huang}★${gl_bai} ${gl_kjlan}2. ${gl_bai}wget 下載工具${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}3. ${gl_bai}sudo 超級管理權限工具${gl_kjlan}4. ${gl_bai}socat 通信連接工具"
- echo -e "${gl_kjlan}5. ${gl_bai}htop 系統監控工具${gl_kjlan}6. ${gl_bai}iftop 網絡流量監控工具"
- echo -e "${gl_kjlan}7. ${gl_bai}unzip ZIP壓縮解壓工具${gl_kjlan}8. ${gl_bai}tar GZ壓縮解壓工具"
- echo -e "${gl_kjlan}9. ${gl_bai}tmux 多路後台運行工具${gl_kjlan}10. ${gl_bai}ffmpeg 視頻編碼直播推流工具"
+ echo -e "${gl_kjlan}3. ${gl_bai}sudo 超級管理權限工具${gl_kjlan}4. ${gl_bai}socat 通訊連接工具"
+ echo -e "${gl_kjlan}5. ${gl_bai}htop 系統監控工具${gl_kjlan}6. ${gl_bai}iftop 網路流量監控工具"
+ echo -e "${gl_kjlan}7. ${gl_bai}unzip ZIP壓縮解壓縮工具${gl_kjlan}8. ${gl_bai}tar GZ壓縮解壓縮工具"
+ echo -e "${gl_kjlan}9. ${gl_bai}tmux 多路後台運行工具${gl_kjlan}10. ${gl_bai}ffmpeg 視訊編碼直播推流工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}btop 現代化監控工具${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}ranger 文件管理工具"
- echo -e "${gl_kjlan}13. ${gl_bai}ncdu 磁盤佔用查看工具${gl_kjlan}14. ${gl_bai}fzf 全局搜索工具"
- echo -e "${gl_kjlan}15. ${gl_bai}vim 文本編輯器${gl_kjlan}16. ${gl_bai}nano 文本編輯器${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}17. ${gl_bai}git 版本控制系統"
+ echo -e "${gl_kjlan}11. ${gl_bai}btop 現代化監控工具${gl_huang}★${gl_bai} ${gl_kjlan}12. ${gl_bai}ranger 檔案管理工具"
+ echo -e "${gl_kjlan}13. ${gl_bai}ncdu 磁碟佔用檢視工具${gl_kjlan}14. ${gl_bai}fzf 全域搜尋工具"
+ echo -e "${gl_kjlan}15. ${gl_bai}vim 文字編輯器${gl_kjlan}16. ${gl_bai}nano 文字編輯器${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}17. ${gl_bai}git 版本控制系統${gl_kjlan}18. ${gl_bai}opencode AI程式設計助手${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}黑客帝國屏保${gl_kjlan}22. ${gl_bai}跑火車屏保"
+ echo -e "${gl_kjlan}21. ${gl_bai}駭客任務螢幕保${gl_kjlan}22. ${gl_bai}跑火車屏保"
echo -e "${gl_kjlan}26. ${gl_bai}俄羅斯方塊小遊戲${gl_kjlan}27. ${gl_bai}貪吃蛇小遊戲"
echo -e "${gl_kjlan}28. ${gl_bai}太空入侵者小遊戲"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}全部安裝${gl_kjlan}32. ${gl_bai}全部安裝(不含屏保和遊戲)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}全部卸載"
+ echo -e "${gl_kjlan}31. ${gl_bai}全部安裝${gl_kjlan}32. ${gl_bai}全部安裝(不含螢幕保護程式和遊戲)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}全部解除安裝"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}安裝指定工具${gl_kjlan}42. ${gl_bai}卸載指定工具"
+ echo -e "${gl_kjlan}41. ${gl_bai}安裝指定工具${gl_kjlan}42. ${gl_bai}解除安裝指定工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
@@ -6667,6 +7927,17 @@ linux_tools() {
send_stats "安裝git"
;;
+ 18)
+ clear
+ cd ~
+ curl -fsSL https://opencode.ai/install | bash
+ source ~/.bashrc
+ source ~/.profile
+ opencode
+ send_stats "安裝opencode"
+ ;;
+
+
21)
clear
install cmatrix
@@ -6695,6 +7966,7 @@ linux_tools() {
nsnake
send_stats "安裝nsnake"
;;
+
28)
clear
install ninvaders
@@ -6711,28 +7983,30 @@ linux_tools() {
32)
clear
- send_stats "全部安裝(不含遊戲和屏保)"
+ send_stats "全部安裝(不含遊戲和螢幕保護程式)"
install curl wget sudo socat htop iftop unzip tar tmux ffmpeg btop ranger ncdu fzf vim nano git
;;
33)
clear
- send_stats "全部卸載"
+ send_stats "全部解除安裝"
remove htop iftop tmux ffmpeg btop ranger ncdu fzf cmatrix sl bastet nsnake ninvaders vim nano git
+ opencode uninstall
+ rm -rf ~/.opencode
;;
41)
clear
- read -e -p "請輸入安裝的工具名(wget curl sudo htop):" installname
+ read -e -p "請輸入已安裝的工具名稱(wget curl sudo htop):" installname
install $installname
- send_stats "安裝指定軟件"
+ send_stats "安裝指定軟體"
;;
42)
clear
- read -e -p "請輸入卸載的工具名(htop ufw tmux cmatrix):" removename
+ read -e -p "請輸入卸載的工具名稱(htop ufw tmux cmatrix):" removename
remove $removename
- send_stats "卸載指定軟件"
+ send_stats "解除安裝指定軟體"
;;
0)
@@ -6760,12 +8034,12 @@ linux_bbr() {
clear
local congestion_algorithm=$(sysctl -n net.ipv4.tcp_congestion_control)
local queue_algorithm=$(sysctl -n net.core.default_qdisc)
- echo "當前TCP阻塞算法:$congestion_algorithm $queue_algorithm"
+ echo "當前TCP阻塞演算法:$congestion_algorithm $queue_algorithm"
echo ""
echo "BBR管理"
echo "------------------------"
- echo "1. 開啟BBRv3 2. 關閉BBRv3(會重啟)"
+ echo "1. 開啟BBRv3 2. 關閉BBRv3(會重新啟動)"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -6777,7 +8051,7 @@ linux_bbr() {
send_stats "alpine開啟bbr3"
;;
2)
- sed -i '/net.ipv4.tcp_congestion_control=bbr/d' /etc/sysctl.conf
+ sed -i '/net.ipv4.tcp_congestion_control=/d' /etc/sysctl.conf
sysctl -p
server_reboot
;;
@@ -6801,90 +8075,412 @@ linux_bbr() {
-linux_docker() {
+docker_ssh_migration() {
- while true; do
- clear
- # send_stats "docker管理"
- echo -e "Docker管理"
- docker_tato
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}安裝更新Docker環境${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}2. ${gl_bai}查看Docker全局狀態${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}3. ${gl_bai}Docker容器管理${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}4. ${gl_bai}Docker鏡像管理"
- echo -e "${gl_kjlan}5. ${gl_bai}Docker網絡管理"
- echo -e "${gl_kjlan}6. ${gl_bai}Docker捲管理"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}7. ${gl_bai}清理無用的docker容器和鏡像網絡數據卷"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}8. ${gl_bai}更換Docker源"
- echo -e "${gl_kjlan}9. ${gl_bai}編輯daemon.json文件"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}開啟Docker-ipv6訪問"
- echo -e "${gl_kjlan}12. ${gl_bai}關閉Docker-ipv6訪問"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}20. ${gl_bai}卸載Docker環境"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
- echo -e "${gl_kjlan}------------------------${gl_bai}"
- read -e -p "請輸入你的選擇:" sub_choice
+ is_compose_container() {
+ local container=$1
+ docker inspect "$container" | jq -e '.[0].Config.Labels["com.docker.compose.project"]' >/dev/null 2>&1
+ }
- case $sub_choice in
- 1)
- clear
- send_stats "安裝docker環境"
- install_add_docker
+ list_backups() {
+ local BACKUP_ROOT="/tmp"
+ echo -e "${gl_kjlan}目前備份清單:${gl_bai}"
+ ls -1dt ${BACKUP_ROOT}/docker_backup_* 2>/dev/null || echo "無備份"
+ }
- ;;
- 2)
- clear
- local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
- local image_count=$(docker images -q 2>/dev/null | wc -l)
- local network_count=$(docker network ls -q 2>/dev/null | wc -l)
- local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
- send_stats "docker全局狀態"
- echo "Docker版本"
- docker -v
- docker compose version
- echo ""
- echo -e "Docker鏡像:${gl_lv}$image_count${gl_bai} "
- docker image ls
- echo ""
- echo -e "Docker容器:${gl_lv}$container_count${gl_bai}"
- docker ps -a
- echo ""
- echo -e "Docker卷:${gl_lv}$volume_count${gl_bai}"
- docker volume ls
- echo ""
- echo -e "Docker網絡:${gl_lv}$network_count${gl_bai}"
- docker network ls
- echo ""
+ # ----------------------------
+ # 備份
+ # ----------------------------
+ backup_docker() {
+ send_stats "Docker備份"
- ;;
- 3)
- docker_ps
- ;;
- 4)
- docker_image
- ;;
+ echo -e "${gl_kjlan}正在備份 Docker 容器...${gl_bai}"
+ docker ps --format '{{.Names}}'
+ read -e -p "請輸入要備份的容器名稱(多個空格分隔,回車備份全部運行中容器):" containers
- 5)
- while true; do
- clear
- send_stats "Docker網絡管理"
- echo "Docker網絡列表"
- echo "------------------------------------------------------------"
- docker network ls
+ install tar jq gzip
+ install_docker
+
+ local BACKUP_ROOT="/tmp"
+ local DATE_STR=$(date +%Y%m%d_%H%M%S)
+ local TARGET_CONTAINERS=()
+ if [ -z "$containers" ]; then
+ mapfile -t TARGET_CONTAINERS < <(docker ps --format '{{.Names}}')
+ else
+ read -ra TARGET_CONTAINERS <<< "$containers"
+ fi
+ [[ ${#TARGET_CONTAINERS[@]} -eq 0 ]] && { echo -e "${gl_hong}沒有找到容器${gl_bai}"; return; }
+
+ local BACKUP_DIR="${BACKUP_ROOT}/docker_backup_${DATE_STR}"
+ mkdir -p "$BACKUP_DIR"
+
+ local RESTORE_SCRIPT="${BACKUP_DIR}/docker_restore.sh"
+ echo "#!/bin/bash" > "$RESTORE_SCRIPT"
+ echo "set -e" >> "$RESTORE_SCRIPT"
+ echo "# 自動產生的還原腳本" >> "$RESTORE_SCRIPT"
+
+ # 記錄已打包過的 Compose 專案路徑,避免重複打包
+ declare -A PACKED_COMPOSE_PATHS=()
+
+ for c in "${TARGET_CONTAINERS[@]}"; do
+ echo -e "${gl_lv}備份容器:$c${gl_bai}"
+ local inspect_file="${BACKUP_DIR}/${c}_inspect.json"
+ docker inspect "$c" > "$inspect_file"
+
+ if is_compose_container "$c"; then
+ echo -e "${gl_kjlan}檢測到$c是 docker-compose 容器${gl_bai}"
+ local project_dir=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty')
+ local project_name=$(docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty')
+
+ if [ -z "$project_dir" ]; then
+ read -e -p "未偵測到 compose 目錄,請手動輸入路徑:" project_dir
+ fi
+
+ # 如果該 Compose 項目已經打包過,跳過
+ if [[ -n "${PACKED_COMPOSE_PATHS[$project_dir]}" ]]; then
+ echo -e "${gl_huang}Compose 項目 [$project_name] 已備份過,跳過重複打包...${gl_bai}"
+ continue
+ fi
+
+ if [ -f "$project_dir/docker-compose.yml" ]; then
+ echo "compose" > "${BACKUP_DIR}/backup_type_${project_name}"
+ echo "$project_dir" > "${BACKUP_DIR}/compose_path_${project_name}.txt"
+ tar -czf "${BACKUP_DIR}/compose_project_${project_name}.tar.gz" -C "$project_dir" .
+ echo "# docker-compose 恢復:$project_name" >> "$RESTORE_SCRIPT"
+ echo "cd \"$project_dir\" && docker compose up -d" >> "$RESTORE_SCRIPT"
+ PACKED_COMPOSE_PATHS["$project_dir"]=1
+ echo -e "${gl_lv}Compose 項目 [$project_name] 已打包:${project_dir}${gl_bai}"
+ else
+ echo -e "${gl_hong}未找到 docker-compose.yml,跳過此容器...${gl_bai}"
+ fi
+ else
+ # 普通容器備份卷
+ local VOL_PATHS
+ VOL_PATHS=$(docker inspect "$c" --format '{{range .Mounts}}{{.Source}} {{end}}')
+ for path in $VOL_PATHS; do
+ echo "打包卷:$path"
+ tar -czpf "${BACKUP_DIR}/${c}_$(basename $path).tar.gz" -C / "$(echo $path | sed 's/^\///')"
+ done
+
+ # 連接埠
+ local PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[] | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$inspect_file" 2>/dev/null)
+ for p in "${PORTS[@]}"; do PORT_ARGS+="-p $p "; done
+
+ # 環境變數
+ local ENV_VARS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[] | @sh' "$inspect_file")
+ for e in "${ENVS[@]}"; do ENV_VARS+="-e $e "; done
+
+ # 卷映射
+ local VOL_ARGS=""
+ for path in $VOL_PATHS; do VOL_ARGS+="-v $path:$path "; done
+
+ # 鏡像
+ local IMAGE
+ IMAGE=$(jq -r '.[0].Config.Image' "$inspect_file")
+
+ echo -e "\n# 還原容器:$c" >> "$RESTORE_SCRIPT"
+ echo "docker run -d --name $c $PORT_ARGS $VOL_ARGS $ENV_VARS $IMAGE" >> "$RESTORE_SCRIPT"
+ fi
+ done
+
+
+ # 備份 /home/docker 下的所有檔案(不含子目錄)
+ if [ -d "/home/docker" ]; then
+ echo -e "${gl_kjlan}備份 /home/docker 下的檔案...${gl_bai}"
+ find /home/docker -maxdepth 1 -type f | tar -czf "${BACKUP_DIR}/home_docker_files.tar.gz" -T -
+ echo -e "${gl_lv}/home/docker 下的檔案已打包到:${BACKUP_DIR}/home_docker_files.tar.gz${gl_bai}"
+ fi
+
+ chmod +x "$RESTORE_SCRIPT"
+ echo -e "${gl_lv}備份完成:${BACKUP_DIR}${gl_bai}"
+ echo -e "${gl_lv}可用還原腳本:${RESTORE_SCRIPT}${gl_bai}"
+
+
+ }
+
+ # ----------------------------
+ # 還原
+ # ----------------------------
+ restore_docker() {
+
+ send_stats "Docker還原"
+ read -e -p "請輸入要還原的備份目錄:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}備份目錄不存在${gl_bai}"; return; }
+
+ echo -e "${gl_kjlan}開始執行還原操作...${gl_bai}"
+
+ install tar jq gzip
+ install_docker
+
+ # --------- 優先還原 Compose 專案 ---------
+ for f in "$BACKUP_DIR"/backup_type_*; do
+ [[ ! -f "$f" ]] && continue
+ if grep -q "compose" "$f"; then
+ project_name=$(basename "$f" | sed 's/backup_type_//')
+ path_file="$BACKUP_DIR/compose_path_${project_name}.txt"
+ [[ -f "$path_file" ]] && original_path=$(cat "$path_file") || original_path=""
+ [[ -z "$original_path" ]] && read -e -p "未找到原始路徑,請輸入還原目錄路徑:" original_path
+
+ # 檢查該 compose 項目的容器是否已在運作
+ running_count=$(docker ps --filter "label=com.docker.compose.project=$project_name" --format '{{.Names}}' | wc -l)
+ if [[ "$running_count" -gt 0 ]]; then
+ echo -e "${gl_huang}Compose 項目 [$project_name] 已有容器在運行,跳過還原...${gl_bai}"
+ continue
+ fi
+
+ read -e -p "確認還原 Compose 項目 [$project_name] 到路徑 [$original_path] ? (y/n): " confirm
+ [[ "$confirm" != "y" ]] && read -e -p "請輸入新的還原路徑:" original_path
+
+ mkdir -p "$original_path"
+ tar -xzf "$BACKUP_DIR/compose_project_${project_name}.tar.gz" -C "$original_path"
+ echo -e "${gl_lv}Compose 項目 [$project_name] 已解壓縮到:$original_path${gl_bai}"
+
+ cd "$original_path" || return
+ docker compose down || true
+ docker compose up -d
+ echo -e "${gl_lv}Compose 項目 [$project_name] 還原完成!${gl_bai}"
+ fi
+ done
+
+ # --------- 繼續還原一般容器 ---------
+ echo -e "${gl_kjlan}檢查並還原普通 Docker 容器...${gl_bai}"
+ local has_container=false
+ for json in "$BACKUP_DIR"/*_inspect.json; do
+ [[ ! -f "$json" ]] && continue
+ has_container=true
+ container=$(basename "$json" | sed 's/_inspect.json//')
+ echo -e "${gl_lv}處理容器:$container${gl_bai}"
+
+ # 檢查容器是否已經存在且正在運行
+ if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] 已在運行,跳過還原...${gl_bai}"
+ continue
+ fi
+
+ IMAGE=$(jq -r '.[0].Config.Image' "$json")
+ [[ -z "$IMAGE" || "$IMAGE" == "null" ]] && { echo -e "${gl_hong}未找到鏡像訊息,跳過:$container${gl_bai}"; continue; }
+
+ # 連接埠映射
+ PORT_ARGS=""
+ mapfile -t PORTS < <(jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.value[0].HostPort):\(.key | split("/")[0])"' "$json")
+ for p in "${PORTS[@]}"; do
+ [[ -n "$p" ]] && PORT_ARGS="$PORT_ARGS -p $p"
+ done
+
+ # 環境變數
+ ENV_ARGS=""
+ mapfile -t ENVS < <(jq -r '.[0].Config.Env[]' "$json")
+ for e in "${ENVS[@]}"; do
+ ENV_ARGS="$ENV_ARGS -e \"$e\""
+ done
+
+ # 卷映射 + 卷資料恢復
+ VOL_ARGS=""
+ mapfile -t VOLS < <(jq -r '.[0].Mounts[] | "\(.Source):\(.Destination)"' "$json")
+ for v in "${VOLS[@]}"; do
+ VOL_SRC=$(echo "$v" | cut -d':' -f1)
+ VOL_DST=$(echo "$v" | cut -d':' -f2)
+ mkdir -p "$VOL_SRC"
+ VOL_ARGS="$VOL_ARGS -v $VOL_SRC:$VOL_DST"
+
+ VOL_FILE="$BACKUP_DIR/${container}_$(basename $VOL_SRC).tar.gz"
+ if [[ -f "$VOL_FILE" ]]; then
+ echo "恢復卷宗資料:$VOL_SRC"
+ tar -xzf "$VOL_FILE" -C /
+ fi
+ done
+
+ # 刪除已存在但未運行的容器
+ if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
+ echo -e "${gl_huang}容器 [$container] 存在但未運行,刪除舊容器...${gl_bai}"
+ docker rm -f "$container"
+ fi
+
+ # 啟動容器
+ echo "執行還原指令: docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ eval "docker run -d --name \"$container\" $PORT_ARGS $VOL_ARGS $ENV_ARGS \"$IMAGE\""
+ done
+
+ [[ "$has_container" == false ]] && echo -e "${gl_huang}未找到普通容器的備份訊息${gl_bai}"
+
+ # 還原 /home/docker 下的文件
+ if [ -f "$BACKUP_DIR/home_docker_files.tar.gz" ]; then
+ echo -e "${gl_kjlan}正在還原 /home/docker 下的檔案...${gl_bai}"
+ mkdir -p /home/docker
+ tar -xzf "$BACKUP_DIR/home_docker_files.tar.gz" -C /
+ echo -e "${gl_lv}/home/docker 下的檔案已還原完成${gl_bai}"
+ else
+ echo -e "${gl_huang}未找到 /home/docker 下檔案的備份,跳過...${gl_bai}"
+ fi
+
+
+ }
+
+
+ # ----------------------------
+ # 遷移
+ # ----------------------------
+ migrate_docker() {
+ send_stats "Docker遷移"
+ install jq
+ read -e -p "請輸入要遷移的備份目錄:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}備份目錄不存在${gl_bai}"; return; }
+
+ kj_ssh_read_host_user_port "目標伺服器IP:" "目標伺服器SSH用戶名 [預設root]:" "目標伺服器SSH連接埠 [預設22]:" "root" "22"
+ local TARGET_IP="$KJ_SSH_HOST"
+ local TARGET_USER="$KJ_SSH_USER"
+ local TARGET_PORT="$KJ_SSH_PORT"
+
+ local LATEST_TAR="$BACKUP_DIR"
+
+ echo -e "${gl_huang}傳輸備份中...${gl_bai}"
+ if [[ -z "$TARGET_PASS" ]]; then
+ # 使用密鑰登入
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no -r "$LATEST_TAR" "$TARGET_USER@$TARGET_IP:/tmp/"
+ fi
+
+ }
+
+ # ----------------------------
+ # 刪除備份
+ # ----------------------------
+ delete_backup() {
+ send_stats "Docker備份檔案刪除"
+ read -e -p "請輸入要刪除的備份目錄:" BACKUP_DIR
+ [[ ! -d "$BACKUP_DIR" ]] && { echo -e "${gl_hong}備份目錄不存在${gl_bai}"; return; }
+ rm -rf "$BACKUP_DIR"
+ echo -e "${gl_lv}已刪除備份:${BACKUP_DIR}${gl_bai}"
+ }
+
+ # ----------------------------
+ # 主選單
+ # ----------------------------
+ main_menu() {
+ send_stats "Docker備份遷移還原"
+ while true; do
+ clear
+ echo "------------------------"
+ echo -e "Docker備份/遷移/還原工具"
+ echo "------------------------"
+ list_backups
+ echo -e ""
+ echo "------------------------"
+ echo -e "1. 備份docker項目"
+ echo -e "2. 遷移docker項目"
+ echo -e "3. 還原docker項目"
+ echo -e "4. 刪除docker專案的備份文件"
+ echo "------------------------"
+ echo -e "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "請選擇:" choice
+ case $choice in
+ 1) backup_docker ;;
+ 2) migrate_docker ;;
+ 3) restore_docker ;;
+ 4) delete_backup ;;
+ 0) return ;;
+ *) echo -e "${gl_hong}無效選項${gl_bai}" ;;
+ esac
+ break_end
+ done
+ }
+
+ main_menu
+}
+
+
+
+
+
+linux_docker() {
+
+ while true; do
+ clear
+ # send_stats "docker管理"
+ echo -e "Docker管理"
+ docker_tato
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}安裝更新Docker環境${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}2. ${gl_bai}查看Docker全域狀態${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}3. ${gl_bai}Docker容器管理${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}4. ${gl_bai}Docker映像管理"
+ echo -e "${gl_kjlan}5. ${gl_bai}Docker網路管理"
+ echo -e "${gl_kjlan}6. ${gl_bai}Docker磁碟區管理"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}7. ${gl_bai}清理無用的docker容器和映像網路資料卷"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}8. ${gl_bai}更換Docker源"
+ echo -e "${gl_kjlan}9. ${gl_bai}編輯daemon.json文件"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}11. ${gl_bai}開啟Docker-ipv6訪問"
+ echo -e "${gl_kjlan}12. ${gl_bai}關閉Docker-ipv6訪問"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}19. ${gl_bai}備份/遷移/還原Docker環境"
+ echo -e "${gl_kjlan}20. ${gl_bai}解除安裝Docker環境"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "請輸入你的選擇:" sub_choice
+
+ case $sub_choice in
+ 1)
+ clear
+ send_stats "安裝docker環境"
+ install_add_docker
+
+ ;;
+ 2)
+ clear
+ local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
+ local image_count=$(docker images -q 2>/dev/null | wc -l)
+ local network_count=$(docker network ls -q 2>/dev/null | wc -l)
+ local volume_count=$(docker volume ls -q 2>/dev/null | wc -l)
+
+ send_stats "docker全域狀態"
+ echo "Docker版本"
+ docker -v
+ docker compose version
+
+ echo ""
+ echo -e "Docker映像:${gl_lv}$image_count${gl_bai} "
+ docker image ls
+ echo ""
+ echo -e "Docker容器:${gl_lv}$container_count${gl_bai}"
+ docker ps -a
+ echo ""
+ echo -e "Docker磁碟區:${gl_lv}$volume_count${gl_bai}"
+ docker volume ls
+ echo ""
+ echo -e "Docker網路:${gl_lv}$network_count${gl_bai}"
+ docker network ls
+ echo ""
+
+ ;;
+ 3)
+ docker_ps
+ ;;
+ 4)
+ docker_image
+ ;;
+
+ 5)
+ while true; do
+ clear
+ send_stats "Docker網路管理"
+ echo "Docker網路列表"
+ echo "------------------------------------------------------------"
+ docker network ls
echo ""
echo "------------------------------------------------------------"
container_ids=$(docker ps -q)
- printf "%-25s %-25s %-25s\n" "容器名称" "网络名称" "IP地址"
+ printf "%-25s %-25s %-25s\n" "容器名稱" "網路名稱" "IP位址"
for container_id in $container_ids; do
local container_info=$(docker inspect --format '{{ .Name }}{{ range $network, $config := .NetworkSettings.Networks }} {{ $network }} {{ $config.IPAddress }}{{ end }}' "$container_id")
@@ -6901,9 +8497,9 @@ linux_docker() {
done
echo ""
- echo "網絡操作"
+ echo "網路操作"
echo "------------------------"
- echo "1. 創建網絡"
+ echo "1. 建立網絡"
echo "2. 加入網絡"
echo "3. 退出網絡"
echo "4. 刪除網絡"
@@ -6914,23 +8510,23 @@ linux_docker() {
case $sub_choice in
1)
- send_stats "創建網絡"
- read -e -p "設置新網絡名:" dockernetwork
+ send_stats "創建網路"
+ read -e -p "設定新網路名稱:" dockernetwork
docker network create $dockernetwork
;;
2)
- send_stats "加入網絡"
- read -e -p "加入網絡名:" dockernetwork
- read -e -p "那些容器加入該網絡(多個容器名請用空格分隔):" dockernames
+ send_stats "加入網路"
+ read -e -p "加入網路名稱:" dockernetwork
+ read -e -p "那些容器加入該網路(多個容器名稱請以空格分隔):" dockernames
for dockername in $dockernames; do
docker network connect $dockernetwork $dockername
done
;;
3)
- send_stats "加入網絡"
- read -e -p "退出網絡名:" dockernetwork
- read -e -p "那些容器退出該網絡(多個容器名請用空格分隔):" dockernames
+ send_stats "加入網路"
+ read -e -p "退出網路名稱:" dockernetwork
+ read -e -p "那些容器退出該網路(多個容器名稱請以空格分隔):" dockernames
for dockername in $dockernames; do
docker network disconnect $dockernetwork $dockername
@@ -6940,7 +8536,7 @@ linux_docker() {
4)
send_stats "刪除網絡"
- read -e -p "請輸入要刪除的網絡名:" dockernetwork
+ read -e -p "請輸入要刪除的網路名稱:" dockernetwork
docker network rm $dockernetwork
;;
@@ -6954,13 +8550,13 @@ linux_docker() {
6)
while true; do
clear
- send_stats "Docker捲管理"
+ send_stats "Docker磁碟區管理"
echo "Docker卷列表"
docker volume ls
echo ""
echo "卷操作"
echo "------------------------"
- echo "1. 創建新卷"
+ echo "1. 建立新卷"
echo "2. 刪除指定卷"
echo "3. 刪除所有捲"
echo "------------------------"
@@ -6970,13 +8566,13 @@ linux_docker() {
case $sub_choice in
1)
- send_stats "新建卷"
- read -e -p "設置新卷名:" dockerjuan
+ send_stats "新磁碟區"
+ read -e -p "設定新卷名:" dockerjuan
docker volume create $dockerjuan
;;
2)
- read -e -p "輸入刪除卷名(多個卷名請用空格分隔):" dockerjuans
+ read -e -p "輸入刪除卷名(多個卷名請以空格分隔):" dockerjuans
for dockerjuan in $dockerjuans; do
docker volume rm $dockerjuan
@@ -7022,7 +8618,7 @@ linux_docker() {
;;
8)
clear
- send_stats "Docker源"
+ send_stats "Docker來源"
bash <(curl -sSL https://linuxmirrors.cn/docker.sh)
;;
@@ -7033,6 +8629,9 @@ linux_docker() {
restart docker
;;
+
+
+
11)
clear
send_stats "Docker v6 開"
@@ -7045,9 +8644,14 @@ linux_docker() {
docker_ipv6_off
;;
+ 19)
+ docker_ssh_migration
+ ;;
+
+
20)
clear
- send_stats "Docker卸載"
+ send_stats "Docker解除安裝"
read -e -p "$(echo -e "${gl_hong}注意: ${gl_bai}确定卸载docker环境吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -7085,60 +8689,61 @@ linux_test() {
while true; do
clear
- # send_stats "測試腳本合集"
+ # send_stats "測試腳本集合"
echo -e "測試腳本合集"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}IP及解鎖狀態檢測"
- echo -e "${gl_kjlan}1. ${gl_bai}ChatGPT 解鎖狀態檢測"
- echo -e "${gl_kjlan}2. ${gl_bai}Region 流媒體解鎖測試"
- echo -e "${gl_kjlan}3. ${gl_bai}yeahwu 流媒體解鎖檢測"
- echo -e "${gl_kjlan}4. ${gl_bai}xykt IP質量體檢腳本${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}IP及解鎖狀態偵測"
+ echo -e "${gl_kjlan}1. ${gl_bai}ChatGPT 解鎖狀態偵測"
+ echo -e "${gl_kjlan}2. ${gl_bai}Region 串流解鎖測試"
+ echo -e "${gl_kjlan}3. ${gl_bai}yeahwu 串流媒體解鎖偵測"
+ echo -e "${gl_kjlan}4. ${gl_bai}xykt IP品質體檢腳本${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}網絡線路測速"
+ echo -e "${gl_kjlan}網路線路測速"
echo -e "${gl_kjlan}11. ${gl_bai}besttrace 三網回程延遲路由測試"
echo -e "${gl_kjlan}12. ${gl_bai}mtr_trace 三網回程線路測試"
echo -e "${gl_kjlan}13. ${gl_bai}Superspeed 三網測速"
echo -e "${gl_kjlan}14. ${gl_bai}nxtrace 快速回程測試腳本"
echo -e "${gl_kjlan}15. ${gl_bai}nxtrace 指定IP回程測試腳本"
- echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 三網線路測試"
+ echo -e "${gl_kjlan}16. ${gl_bai}ludashi2020 三線線路測試"
echo -e "${gl_kjlan}17. ${gl_bai}i-abc 多功能測速腳本"
- echo -e "${gl_kjlan}18. ${gl_bai}NetQuality 網絡質量體檢腳本${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}18. ${gl_bai}NetQuality 網路品質體檢腳本${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}硬件性能測試"
- echo -e "${gl_kjlan}21. ${gl_bai}yabs 性能測試"
- echo -e "${gl_kjlan}22. ${gl_bai}icu/gb5 CPU性能測試腳本"
+ echo -e "${gl_kjlan}硬體效能測試"
+ echo -e "${gl_kjlan}21. ${gl_bai}yabs 效能測試"
+ echo -e "${gl_kjlan}22. ${gl_bai}icu/gb5 CPU效能測試腳本"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}綜合性測試"
- echo -e "${gl_kjlan}31. ${gl_bai}bench 性能測試"
+ echo -e "${gl_kjlan}31. ${gl_bai}bench 效能測試"
echo -e "${gl_kjlan}32. ${gl_bai}spiritysdx 融合怪測評${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}nodequality 融合怪測評${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
clear
- send_stats "ChatGPT解鎖狀態檢測"
+ send_stats "ChatGPT解鎖狀態偵測"
bash <(curl -Ls https://cdn.jsdelivr.net/gh/missuo/OpenAI-Checker/openai.sh)
;;
2)
clear
- send_stats "Region流媒體解鎖測試"
+ send_stats "Region串流解鎖測試"
bash <(curl -L -s check.unlock.media)
;;
3)
clear
- send_stats "yeahwu流媒體解鎖檢測"
+ send_stats "yeahwu串流解鎖偵測"
install wget
wget -qO- ${gh_proxy}github.com/yeahwu/check/raw/main/check.sh | bash
;;
4)
clear
- send_stats "xykt_IP質量體檢腳本"
+ send_stats "xykt_IP品質體檢腳本"
bash <(curl -Ls IP.Check.Place)
;;
@@ -7194,7 +8799,7 @@ linux_test() {
16)
clear
- send_stats "ludashi2020三網線路測試"
+ send_stats "ludashi2020三線線路測試"
curl ${gh_proxy}raw.githubusercontent.com/ludashi2020/backtrace/main/install.sh -sSf | sh
;;
@@ -7206,34 +8811,42 @@ linux_test() {
18)
clear
- send_stats "網絡質量測試腳本"
+ send_stats "網路品質測試腳本"
bash <(curl -sL Net.Check.Place)
;;
21)
clear
- send_stats "yabs性能測試"
+ send_stats "yabs效能測試"
check_swap
curl -sL yabs.sh | bash -s -- -i -5
;;
22)
clear
- send_stats "icu/gb5 CPU性能測試腳本"
+ send_stats "icu/gb5 CPU效能測試腳本"
check_swap
bash <(curl -sL bash.icu/gb5)
;;
31)
clear
- send_stats "bench性能測試"
+ send_stats "bench效能測試"
curl -Lso- bench.sh | bash
;;
32)
send_stats "spiritysdx融合怪測評"
clear
- curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ curl -L ${gh_proxy}github.com/spiritLHLS/ecs/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
+ ;;
+
+ 33)
+ send_stats "nodequality融合怪測評"
+ clear
+ bash <(curl -sL https://run.NodeQuality.com)
;;
+
+
0)
kejilion
@@ -7255,59 +8868,59 @@ linux_Oracle() {
while true; do
clear
- send_stats "甲骨文云腳本合集"
- echo -e "甲骨文云腳本合集"
+ send_stats "甲骨文雲腳本合集"
+ echo -e "甲骨文雲腳本合集"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}1. ${gl_bai}安裝閒置機器活躍腳本"
echo -e "${gl_kjlan}2. ${gl_bai}卸載閒置機器活躍腳本"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}3. ${gl_bai}DD重裝系統腳本"
echo -e "${gl_kjlan}4. ${gl_bai}R探長開機腳本"
- echo -e "${gl_kjlan}5. ${gl_bai}開啟ROOT密碼登錄模式"
+ echo -e "${gl_kjlan}5. ${gl_bai}開啟ROOT密碼登入模式"
echo -e "${gl_kjlan}6. ${gl_bai}IPV6恢復工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
clear
- echo "活躍腳本: CPU佔用10-20% 內存佔用20%"
+ echo "活躍腳本: CPU佔用10-20% 記憶體佔用20%"
read -e -p "確定安裝嗎? (Y/N):" choice
case "$choice" in
[Yy])
install_docker
- # 設置默認值
+ # 設定預設值
local DEFAULT_CPU_CORE=1
local DEFAULT_CPU_UTIL="10-20"
local DEFAULT_MEM_UTIL=20
local DEFAULT_SPEEDTEST_INTERVAL=120
- # 提示用戶輸入CPU核心數和占用百分比,如果回車則使用默認值
- read -e -p "請輸入CPU核心數 [默認:$DEFAULT_CPU_CORE]: " cpu_core
+ # 提示使用者輸入CPU核心數和占用百分比,如果回車則使用預設值
+ read -e -p "請輸入CPU核心數 [預設:$DEFAULT_CPU_CORE]: " cpu_core
local cpu_core=${cpu_core:-$DEFAULT_CPU_CORE}
- read -e -p "請輸入CPU佔用百分比範圍(例如10-20) [默認:$DEFAULT_CPU_UTIL]: " cpu_util
+ read -e -p "請輸入CPU佔用百分比範圍(例如10-20) [預設:$DEFAULT_CPU_UTIL]: " cpu_util
local cpu_util=${cpu_util:-$DEFAULT_CPU_UTIL}
- read -e -p "請輸入內存佔用百分比 [默認:$DEFAULT_MEM_UTIL]: " mem_util
+ read -e -p "請輸入記憶體佔用百分比 [預設:$DEFAULT_MEM_UTIL]: " mem_util
local mem_util=${mem_util:-$DEFAULT_MEM_UTIL}
- read -e -p "請輸入Speedtest間隔時間(秒) [默認:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
+ read -e -p "請輸入Speedtest間隔時間(秒) [預設:$DEFAULT_SPEEDTEST_INTERVAL]: " speedtest_interval
local speedtest_interval=${speedtest_interval:-$DEFAULT_SPEEDTEST_INTERVAL}
# 運行Docker容器
- docker run -itd --name=lookbusy --restart=always \
+ docker run -d --name=lookbusy --restart=always \
-e TZ=Asia/Shanghai \
-e CPU_UTIL="$cpu_util" \
-e CPU_CORE="$cpu_core" \
-e MEM_UTIL="$mem_util" \
-e SPEEDTEST_INTERVAL="$speedtest_interval" \
fogforest/lookbusy
- send_stats "甲骨文云安裝活躍腳本"
+ send_stats "甲骨文雲安裝活躍腳本"
;;
[Nn])
@@ -7322,20 +8935,20 @@ linux_Oracle() {
clear
docker rm -f lookbusy
docker rmi fogforest/lookbusy
- send_stats "甲骨文云卸載活躍腳本"
+ send_stats "甲骨文雲端卸載活躍腳本"
;;
3)
clear
echo "重裝系統"
echo "--------------------------------"
- echo -e "${gl_hong}注意:${gl_bai}重裝有風險失聯,不放心者慎用。重裝預計花費15分鐘,請提前備份數據。"
+ echo -e "${gl_hong}注意:${gl_bai}重裝有風險失聯,不放心者慎用。重裝預計花費15分鐘,請提前備份資料。"
read -e -p "確定繼續嗎? (Y/N):" choice
case "$choice" in
[Yy])
while true; do
- read -e -p "請選擇要重裝的系統: 1. Debian12 | 2. Ubuntu20.04 :" sys_choice
+ read -e -p "請選擇要重裝的系統: 1. Debian12 | 2. Ubuntu20.04 :" sys_choice
case "$sys_choice" in
1)
@@ -7355,7 +8968,7 @@ linux_Oracle() {
read -e -p "請輸入你重裝後的密碼:" vpspasswd
install wget
bash <(wget --no-check-certificate -qO- "${gh_proxy}raw.githubusercontent.com/MoeClub/Note/master/InstallNET.sh") $xitong -v 64 -p $vpspasswd -port 22
- send_stats "甲骨文云重裝系統腳本"
+ send_stats "甲骨文雲端重裝系統腳本"
;;
[Nn])
echo "已取消"
@@ -7368,17 +8981,17 @@ linux_Oracle() {
4)
clear
- echo "該功能處於開發階段,敬請期待!"
+ send_stats "R探長開機腳本"
+ bash <(wget -qO- ${gh_proxy}github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh)
;;
5)
clear
add_sshpasswd
-
;;
6)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "該功能由jhb大神提供,感謝他!"
+ echo "此功能由jhb大神提供,感謝他!"
send_stats "ipv6修復"
;;
0)
@@ -7398,6 +9011,9 @@ linux_Oracle() {
}
+
+
+
docker_tato() {
local container_count=$(docker ps -a -q 2>/dev/null | wc -l)
@@ -7407,7 +9023,7 @@ docker_tato() {
if command -v docker &> /dev/null; then
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_lv}環境已經安裝${gl_bai}容器:${gl_lv}$container_count${gl_bai}鏡像:${gl_lv}$image_count${gl_bai}網絡:${gl_lv}$network_count${gl_bai}卷:${gl_lv}$volume_count${gl_bai}"
+ echo -e "${gl_lv}環境已經安裝${gl_bai}容器:${gl_lv}$container_count${gl_bai}鏡像:${gl_lv}$image_count${gl_bai}網路:${gl_lv}$network_count${gl_bai}卷:${gl_lv}$volume_count${gl_bai}"
fi
}
@@ -7415,20 +9031,20 @@ docker_tato() {
ldnmp_tato() {
local cert_count=$(ls /home/web/certs/*_cert.pem 2>/dev/null | wc -l)
-local output="站点: ${gl_lv}${cert_count}${gl_bai}"
+local output="${gl_lv}${cert_count}${gl_bai}"
local dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml 2>/dev/null | tr -d '[:space:]')
if [ -n "$dbrootpasswd" ]; then
local db_count=$(docker exec mysql mysql -u root -p"$dbrootpasswd" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "Database|information_schema|mysql|performance_schema|sys" | wc -l)
fi
-local db_output="数据库: ${gl_lv}${db_count}${gl_bai}"
+local db_output="${gl_lv}${db_count}${gl_bai}"
if command -v docker &>/dev/null; then
if docker ps --filter "name=nginx" --filter "status=running" | grep -q nginx; then
echo -e "${gl_huang}------------------------"
- echo -e "${gl_lv}環境已安裝${gl_bai} $output $db_output"
+ echo -e "${gl_lv}環境已安裝${gl_bai}站點:$output資料庫:$db_output"
fi
fi
@@ -7456,29 +9072,29 @@ linux_ldnmp() {
while true; do
clear
- # send_stats "LDNMP建站"
+ # send_stats "LDNMP建置站"
echo -e "${gl_huang}LDNMP建站"
ldnmp_tato
echo -e "${gl_huang}------------------------"
echo -e "${gl_huang}1. ${gl_bai}安裝LDNMP環境${gl_huang}★${gl_bai} ${gl_huang}2. ${gl_bai}安裝WordPress${gl_huang}★${gl_bai}"
- echo -e "${gl_huang}3. ${gl_bai}安裝Discuz論壇${gl_huang}4. ${gl_bai}安裝可道云桌面"
+ echo -e "${gl_huang}3. ${gl_bai}安裝Discuz論壇${gl_huang}4. ${gl_bai}安裝可道雲桌面"
echo -e "${gl_huang}5. ${gl_bai}安裝蘋果CMS影視站${gl_huang}6. ${gl_bai}安裝獨角數發卡網"
- echo -e "${gl_huang}7. ${gl_bai}安裝flarum論壇網站${gl_huang}8. ${gl_bai}安裝typecho輕量博客網站"
- echo -e "${gl_huang}9. ${gl_bai}安裝LinkStack共享鏈接平台${gl_huang}20. ${gl_bai}自定義動態站點"
+ echo -e "${gl_huang}7. ${gl_bai}安裝flarum論壇網站${gl_huang}8. ${gl_bai}安裝typecho輕量部落格網站"
+ echo -e "${gl_huang}9. ${gl_bai}安裝LinkStack分享連結平台${gl_huang}20. ${gl_bai}自訂動態站點"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}21. ${gl_bai}僅安裝nginx${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}站點重定向"
+ echo -e "${gl_huang}21. ${gl_bai}僅安裝nginx${gl_huang}★${gl_bai} ${gl_huang}22. ${gl_bai}網站重定向"
echo -e "${gl_huang}23. ${gl_bai}站點反向代理-IP+端口${gl_huang}★${gl_bai} ${gl_huang}24. ${gl_bai}站點反向代理-域名"
- echo -e "${gl_huang}25. ${gl_bai}安裝Bitwarden密碼管理平台${gl_huang}26. ${gl_bai}安裝Halo博客網站"
- echo -e "${gl_huang}27. ${gl_bai}安裝AI繪畫提示詞生成器${gl_huang}28. ${gl_bai}站點反向代理-負載均衡"
- echo -e "${gl_huang}30. ${gl_bai}自定義靜態站點"
+ echo -e "${gl_huang}25. ${gl_bai}安裝Bitwarden密碼管理平台${gl_huang}26. ${gl_bai}安裝Halo部落格網站"
+ echo -e "${gl_huang}27. ${gl_bai}安裝AI繪畫提示詞產生器${gl_huang}28. ${gl_bai}站點反向代理-負載平衡"
+ echo -e "${gl_huang}29. ${gl_bai}Stream四層代理轉發${gl_huang}30. ${gl_bai}自訂靜態站點"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}31. ${gl_bai}站點數據管理${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}備份全站數據"
- echo -e "${gl_huang}33. ${gl_bai}定時遠程備份${gl_huang}34. ${gl_bai}還原全站數據"
+ echo -e "${gl_huang}31. ${gl_bai}站點資料管理${gl_huang}★${gl_bai} ${gl_huang}32. ${gl_bai}備份全站數據"
+ echo -e "${gl_huang}33. ${gl_bai}定時遠端備份${gl_huang}34. ${gl_bai}還原全站數據"
echo -e "${gl_huang}------------------------"
echo -e "${gl_huang}35. ${gl_bai}防護LDNMP環境${gl_huang}36. ${gl_bai}優化LDNMP環境"
- echo -e "${gl_huang}37. ${gl_bai}更新LDNMP環境${gl_huang}38. ${gl_bai}卸載LDNMP環境"
+ echo -e "${gl_huang}37. ${gl_bai}更新LDNMP環境${gl_huang}38. ${gl_bai}解除安裝LDNMP環境"
echo -e "${gl_huang}------------------------"
- echo -e "${gl_huang}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_huang}0. ${gl_bai}返回主選單"
echo -e "${gl_huang}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
@@ -7495,33 +9111,41 @@ linux_ldnmp() {
3)
clear
# Discuz論壇
- webname="Discuz论坛"
+ webname="Discuz論壇"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/discuz.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kejilion/Website_source_code/raw/main/Discuz_X3.5_SC_UTF8_20240520.zip
- unzip latest.zip
+ LATEST_URL=$(curl -s https://api.gitee.com/api/v5/repos/Discuz/DiscuzX/releases/latest | grep -o 'https://[^"]*Discuz_X[^"]*SC_UTF8[^"]*\.zip' | head -n 1)
+ wget -O latest.zip ${LATEST_URL}
+ unzip -q latest.zip
+ mv upload/* .
+ rm -rf upload readme readme.html utility.html LICENSE qqqun.png
rm latest.zip
restart_ldnmp
ldnmp_web_on
- echo "數據庫地址: mysql"
- echo "數據庫名:$dbname"
+ echo "資料庫位址: mysql"
+ echo "資料庫名稱:$dbname"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
echo "表前綴: discuz_"
@@ -7531,35 +9155,39 @@ linux_ldnmp() {
4)
clear
- # 可道云桌面
- webname="可道云桌面"
+ # 可道雲桌面
+ webname="可道雲桌面"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/kdy.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
mkdir $yuming
cd $yuming
- wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/1.50.02.zip
+ LATEST_VERSION=$(curl -s https://api.github.com/repos/kalcaddle/kodbox/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ wget -O latest.zip ${gh_proxy}github.com/kalcaddle/kodbox/archive/refs/tags/${LATEST_VERSION}.zip
unzip -o latest.zip
rm latest.zip
mv /home/web/html/$yuming/kodbox* /home/web/html/$yuming/kodbox
restart_ldnmp
ldnmp_web_on
- echo "數據庫地址: mysql"
+ echo "資料庫位址: mysql"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
- echo "數據庫名:$dbname"
+ echo "資料庫名稱:$dbname"
echo "redis主機: redis"
;;
@@ -7567,18 +9195,23 @@ linux_ldnmp() {
5)
clear
# 蘋果CMS
- webname="苹果CMS"
+ webname="蘋果CMS"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/maccms.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7595,14 +9228,14 @@ linux_ldnmp() {
ldnmp_web_on
- echo "數據庫地址: mysql"
- echo "數據庫端口: 3306"
- echo "數據庫名:$dbname"
+ echo "資料庫位址: mysql"
+ echo "資料庫連接埠: 3306"
+ echo "資料庫名稱:$dbname"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
- echo "數據庫前綴: mac_"
+ echo "資料庫前綴: mac_"
echo "------------------------"
- echo "安裝成功後登錄後台地址"
+ echo "安裝成功後登入後台位址"
echo "https://$yuming/vip.php"
;;
@@ -7610,18 +9243,24 @@ linux_ldnmp() {
6)
clear
# 獨腳數卡
- webname="独脚数卡"
+ webname="獨腳數卡"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/dujiaoka.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7633,23 +9272,23 @@ linux_ldnmp() {
ldnmp_web_on
- echo "數據庫地址: mysql"
- echo "數據庫端口: 3306"
- echo "數據庫名:$dbname"
+ echo "資料庫位址: mysql"
+ echo "資料庫連接埠: 3306"
+ echo "資料庫名稱:$dbname"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
echo ""
echo "redis地址: redis"
- echo "redis密碼: 默認不填寫"
- echo "redis端口: 6379"
+ echo "redis密碼: 預設不填寫"
+ echo "redis 埠: 6379"
echo ""
- echo "網站url: https://$yuming"
- echo "後台登錄路徑: /admin"
+ echo "網址url: https://$yuming"
+ echo "後台登入路徑: /admin"
echo "------------------------"
- echo "用戶名: admin"
+ echo "使用者名稱: admin"
echo "密碼: admin"
echo "------------------------"
- echo "登錄時右上角如果出現紅色error0請使用如下命令:"
+ echo "登入時右上角如果出現紅色error0請使用下列指令:"
echo "我也很氣憤獨角數卡為啥這麼麻煩,會有這樣的問題!"
echo "sed -i 's/ADMIN_HTTPS=false/ADMIN_HTTPS=true/g' /home/web/html/$yuming/dujiaoka/.env"
@@ -7658,18 +9297,24 @@ linux_ldnmp() {
7)
clear
# flarum論壇
- webname="flarum论坛"
+ webname="flarum論壇"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/flarum.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec php rm -f /usr/local/etc/php/conf.d/optimized_php.ini
@@ -7690,19 +9335,23 @@ linux_ldnmp() {
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/sitemap"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/oauth"
docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/best-answer:*"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/upload"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/gamification"
+ docker exec php sh -c "cd /var/www/html/$yuming && composer require fof/byobu:*"
docker exec php sh -c "cd /var/www/html/$yuming && composer require v17development/flarum-seo"
docker exec php sh -c "cd /var/www/html/$yuming && composer require clarkwinkelmann/flarum-ext-emojionearea"
+
restart_ldnmp
ldnmp_web_on
- echo "數據庫地址: mysql"
- echo "數據庫名:$dbname"
+ echo "資料庫位址: mysql"
+ echo "資料庫名稱:$dbname"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
- echo "表前綴: flarum_"
- echo "管理員信息自行設置"
+ echo "表字首: flarum_"
+ echo "管理員資訊自行設定"
;;
@@ -7715,12 +9364,18 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/typecho.com.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7735,11 +9390,11 @@ linux_ldnmp() {
clear
ldnmp_web_on
- echo "數據庫前綴: typecho_"
- echo "數據庫地址: mysql"
+ echo "数据库前缀: typecho_"
+ echo "資料庫位址: mysql"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
- echo "數據庫名:$dbname"
+ echo "資料庫名稱:$dbname"
;;
@@ -7753,13 +9408,17 @@ linux_ldnmp() {
add_yuming
repeat_add_yuming
ldnmp_install_status
+
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/refs/heads/main/index_php.conf
sed -i "s|/var/www/html/yuming.com/|/var/www/html/yuming.com/linkstack|g" /home/web/conf.d/$yuming.conf
sed -i "s|yuming.com|$yuming|g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7774,27 +9433,30 @@ linux_ldnmp() {
clear
ldnmp_web_on
- echo "數據庫地址: mysql"
- echo "數據庫端口: 3306"
- echo "數據庫名:$dbname"
+ echo "資料庫位址: mysql"
+ echo "資料庫連接埠: 3306"
+ echo "資料庫名稱:$dbname"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
;;
20)
clear
- webname="PHP动态站点"
+ webname="PHP動態站點"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
ldnmp_install_status
+
install_ssltls
certs_status
add_db
+
wget -O /home/web/conf.d/map.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/map.conf
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/index_php.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -7802,10 +9464,10 @@ linux_ldnmp() {
cd $yuming
clear
- echo -e "[${gl_huang}1/6${gl_bai}] 上傳PHP源碼"
+ echo -e "[${gl_huang}1/6${gl_bai}] 上傳PHP原始碼"
echo "-------------"
echo "目前只允許上傳zip格式的源碼包,請將源碼包放到/home/web/html/${yuming}目錄下"
- read -e -p "也可以輸入下載鏈接,遠程下載源碼包,直接回車將跳過遠程下載:" url_download
+ read -e -p "也可以輸入下載鏈接,遠端下載源碼包,直接回車將跳過遠端下載:" url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -7845,9 +9507,9 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}4/6${gl_bai}] 安裝指定擴展"
+ echo -e "[${gl_huang}4/6${gl_bai}] 安裝指定擴充"
echo "-------------"
- echo "已經安裝的擴展"
+ echo "已經安裝的擴充"
docker exec php php -m
read -e -p "$(echo -e "输入需要安装的扩展名称,如 ${gl_huang}SourceGuardian imap ftp${gl_bai} 等等。直接回车将跳过安装 : ")" php_extensions
@@ -7857,25 +9519,25 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}5/6${gl_bai}] 編輯站點配置"
+ echo -e "[${gl_huang}5/6${gl_bai}] 編輯網站配置"
echo "-------------"
- echo "按任意鍵繼續,可以詳細設置站點配置,如偽靜態等內容"
+ echo "按任一鍵繼續,可詳細設定網站配置,如偽靜態等內容"
read -n 1 -s -r -p ""
install nano
nano /home/web/conf.d/$yuming.conf
clear
- echo -e "[${gl_huang}6/6${gl_bai}] 數據庫管理"
+ echo -e "[${gl_huang}6/6${gl_bai}] 資料庫管理"
echo "-------------"
- read -e -p "1. 我搭建新站 2. 我搭建老站有數據庫備份:" use_db
+ read -e -p "1. 我搭建新站 2. 我搭建老站有資料庫備份:" use_db
case $use_db in
1)
echo
;;
2)
- echo "數據庫備份必須是.gz結尾的壓縮包。請放到/home/目錄下,支持寶塔/1panel備份數據導入。"
- read -e -p "也可以輸入下載鏈接,遠程下載備份數據,直接回車將跳過遠程下載:" url_download_db
+ echo "資料庫備份必須是.gz結尾的壓縮包。請放到/home/目錄下,支援寶塔/1panel備份資料導入。"
+ read -e -p "也可以輸入下載鏈接,遠端下載備份數據,直接回車將跳過遠端下載:" url_download_db
cd /home/
if [ -n "$url_download_db" ]; then
@@ -7885,10 +9547,10 @@ linux_ldnmp() {
latest_sql=$(ls -t *.sql | head -n 1)
dbrootpasswd=$(grep -oP 'MYSQL_ROOT_PASSWORD:\s*\K.*' /home/web/docker-compose.yml | tr -d '[:space:]')
docker exec -i mysql mysql -u root -p"$dbrootpasswd" $dbname < "/home/$latest_sql"
- echo "數據庫導入的表數據"
+ echo "資料庫匯入的表數據"
docker exec -i mysql mysql -u root -p"$dbrootpasswd" -e "USE $dbname; SHOW TABLES;"
rm -f *.sql
- echo "數據庫導入完成"
+ echo "資料庫導入完成"
;;
*)
echo
@@ -7900,12 +9562,12 @@ linux_ldnmp() {
restart_ldnmp
ldnmp_web_on
prefix="web$(shuf -i 10-99 -n 1)_"
- echo "數據庫地址: mysql"
- echo "數據庫名:$dbname"
+ echo "資料庫位址: mysql"
+ echo "資料庫名稱:$dbname"
echo "使用者名稱:$dbuse"
echo "密碼:$dbusepasswd"
echo "表前綴:$prefix"
- echo "管理員登錄信息自行設置"
+ echo "管理員登入資訊自行設定"
;;
@@ -7917,18 +9579,23 @@ linux_ldnmp() {
22)
clear
- webname="站点重定向"
+ webname="網站重定向"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
read -e -p "請輸入跳轉域名:" reverseproxy
nginx_install_status
+
+
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/rewrite.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s/baidu.com/$reverseproxy/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7943,9 +9610,10 @@ linux_ldnmp() {
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "已阻止IP+端口訪問該服務"
+ echo "已阻止IP+連接埠存取該服務"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
@@ -7958,14 +9626,18 @@ linux_ldnmp() {
echo "開始部署$webname"
add_yuming
echo -e "域名格式:${gl_huang}google.com${gl_bai}"
- read -e -p "請輸入你的反代域名:" fandai_yuming
+ read -e -p "請輸入你的反代網域:" fandai_yuming
nginx_install_status
+
install_ssltls
certs_status
+
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/reverse-proxy-domain.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
sed -i "s|fandaicom|$fandai_yuming|g" /home/web/conf.d/$yuming.conf
+
+
nginx_http_on
docker exec nginx nginx -s reload
@@ -7981,20 +9653,17 @@ linux_ldnmp() {
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
docker run -d \
--name bitwarden \
- --restart always \
+ --restart=always \
-p 3280:80 \
-v /home/web/html/$yuming/bitwarden/data:/data \
vaultwarden/server
+
duankou=3280
- reverse_proxy
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
- nginx_web_on
;;
@@ -8004,30 +9673,29 @@ linux_ldnmp() {
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
- nginx_install_status
- install_ssltls
- certs_status
- docker run -d --name halo --restart always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- duankou=8010
- reverse_proxy
+ docker run -d --name halo --restart=always -p 8010:8090 -v /home/web/html/$yuming/.halo2:/root/.halo2 halohub/halo:2
- nginx_web_on
+ duankou=8010
+ ldnmp_Proxy ${yuming} 127.0.0.1 $duankou
;;
27)
clear
- webname="AI绘画提示词生成器"
+ webname="AI繪畫提示詞產生器"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8050,19 +9718,26 @@ linux_ldnmp() {
;;
+ 29)
+ stream_panel
+ ;;
+
30)
clear
- webname="静态站点"
+ webname="靜態站點"
send_stats "安裝$webname"
echo "開始部署$webname"
add_yuming
repeat_add_yuming
nginx_install_status
+
+
install_ssltls
certs_status
wget -O /home/web/conf.d/$yuming.conf ${gh_proxy}raw.githubusercontent.com/kejilion/nginx/main/html.conf
sed -i "s/yuming.com/$yuming/g" /home/web/conf.d/$yuming.conf
+
nginx_http_on
cd /home/web/html
@@ -8071,10 +9746,10 @@ linux_ldnmp() {
clear
- echo -e "[${gl_huang}1/2${gl_bai}] 上傳靜態源碼"
+ echo -e "[${gl_huang}1/2${gl_bai}] 上傳靜態原始碼"
echo "-------------"
echo "目前只允許上傳zip格式的源碼包,請將源碼包放到/home/web/html/${yuming}目錄下"
- read -e -p "也可以輸入下載鏈接,遠程下載源碼包,直接回車將跳過遠程下載:" url_download
+ read -e -p "也可以輸入下載鏈接,遠端下載源碼包,直接回車將跳過遠端下載:" url_download
if [ -n "$url_download" ]; then
wget "$url_download"
@@ -8117,26 +9792,24 @@ linux_ldnmp() {
send_stats "LDNMP環境備份"
local backup_filename="web_$(date +"%Y%m%d%H%M%S").tar.gz"
- echo -e "${gl_huang}正在備份$backup_filename ...${gl_bai}"
+ echo -e "${gl_kjlan}正在備份$backup_filename ...${gl_bai}"
cd /home/ && tar czvf "$backup_filename" web
while true; do
clear
- echo "備份文件已創建: /home/$backup_filename"
- read -e -p "要傳送備份數據到遠程服務器嗎? (Y/N):" choice
+ echo "備份檔案已建立: /home/$backup_filename"
+ read -e -p "要傳送備份資料到遠端伺服器嗎? (Y/N):" choice
case "$choice" in
[Yy])
- read -e -p "請輸入遠端服務器IP:" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "錯誤: 請輸入遠端服務器IP。"
- continue
- fi
+ kj_ssh_read_host_port "請輸入遠端伺服器IP:" "目標伺服器SSH連接埠 [預設22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
local latest_tar=$(ls -t /home/*.tar.gz | head -1)
if [ -n "$latest_tar" ]; then
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
sleep 2 # 添加等待时间
- scp -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
- echo "文件已傳送至遠程服務器home目錄。"
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/home/"
+ echo "檔案已傳送至遠端伺服器home目錄。"
else
echo "未找到要傳送的文件。"
fi
@@ -8154,9 +9827,9 @@ linux_ldnmp() {
33)
clear
- send_stats "定時遠程備份"
- read -e -p "輸入遠程服務器IP:" useip
- read -e -p "輸入遠程服務器密碼:" usepasswd
+ send_stats "定時遠端備份"
+ read -e -p "輸入遠端伺服器IP:" useip
+ read -e -p "輸入遠端伺服器密碼:" usepasswd
cd ~
wget -O ${useip}_beifen.sh ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/beifen.sh > /dev/null 2>&1
@@ -8166,13 +9839,13 @@ linux_ldnmp() {
sed -i "s/123456/$usepasswd/g" ${useip}_beifen.sh
echo "------------------------"
- echo "1. 每周備份 2. 每天備份"
+ echo "1. 每週備份 2. 每天備份"
read -e -p "請輸入你的選擇:" dingshi
case $dingshi in
1)
check_crontab_installed
- read -e -p "選擇每周備份的星期幾 (0-6,0代表星期日):" weekday
+ read -e -p "選擇每週備份的星期幾 (0-6,0代表星期日):" weekday
(crontab -l ; echo "0 0 * * $weekday ./${useip}_beifen.sh") | crontab - > /dev/null 2>&1
;;
2)
@@ -8196,14 +9869,14 @@ linux_ldnmp() {
echo "-------------------------"
ls -lt /home/*.gz | awk '{print $NF}'
echo ""
- read -e -p "回車鍵還原最新的備份,輸入備份文件名還原指定的備份,輸入0退出:" filename
+ read -e -p "回車鍵還原最新的備份,輸入備份檔案名稱還原指定的備份,輸入0退出:" filename
if [ "$filename" == "0" ]; then
break_end
linux_ldnmp
fi
- # 如果用戶沒有輸入文件名,使用最新的壓縮包
+ # 如果使用者沒有輸入檔名,使用最新的壓縮包
if [ -z "$filename" ]; then
local filename=$(ls -t /home/*.tar.gz | head -1)
fi
@@ -8213,10 +9886,9 @@ linux_ldnmp() {
docker compose down > /dev/null 2>&1
rm -rf /home/web > /dev/null 2>&1
- echo -e "${gl_huang}正在解壓$filename ...${gl_bai}"
+ echo -e "${gl_kjlan}正在解壓縮$filename ...${gl_bai}"
cd /home/ && tar -xzf "$filename"
- check_port
install_dependency
install_docker
install_certbot
@@ -8244,7 +9916,7 @@ linux_ldnmp() {
echo "更新LDNMP環境"
echo "------------------------"
ldnmp_v
- echo "發現新版本的組件"
+ echo "發現新版本的元件"
echo "------------------------"
check_docker_image_update nginx
if [ -n "$update_status" ]; then
@@ -8264,7 +9936,7 @@ linux_ldnmp() {
fi
echo "------------------------"
echo
- echo "1. 更新nginx 2. 更新mysql 3. 更新php 4. 更新redis"
+ echo "1. 更新nginx 2. 更新mysql 3. 更新php 4. 更新redis"
echo "------------------------"
echo "5. 更新完整環境"
echo "------------------------"
@@ -8279,7 +9951,7 @@ linux_ldnmp() {
2)
local ldnmp_pods="mysql"
- read -e -p "請輸入${ldnmp_pods}版本號 (如: 8.0 8.3 8.4 9.0)(回車獲取最新版):" version
+ read -e -p "請輸入${ldnmp_pods}版本號碼 (如: 8.0 8.3 8.4 9.0)(回車取得最新版):" version
local version=${version:-latest}
cd /home/web/
@@ -8296,7 +9968,7 @@ linux_ldnmp() {
;;
3)
local ldnmp_pods="php"
- read -e -p "請輸入${ldnmp_pods}版本號 (如: 7.4 8.0 8.1 8.2 8.3)(回車獲取最新版):" version
+ read -e -p "請輸入${ldnmp_pods}版本號 (如: 7.4 8.0 8.1 8.2 8.3)(回車取得最新版):" version
local version=${version:-8.3}
cd /home/web/
cp /home/web/docker-compose.yml /home/web/docker-compose1.yml
@@ -8340,7 +10012,6 @@ linux_ldnmp() {
docker images --filter=reference="$ldnmp_pods*" -q | xargs docker rmi > /dev/null 2>&1
docker compose up -d --force-recreate $ldnmp_pods
docker restart $ldnmp_pods > /dev/null 2>&1
- restart_redis
send_stats "更新$ldnmp_pods"
echo "更新${ldnmp_pods}完成"
@@ -8353,7 +10024,6 @@ linux_ldnmp() {
cd /home/web/
docker compose down --rmi all
- check_port
install_dependency
install_docker
install_certbot
@@ -8375,7 +10045,7 @@ linux_ldnmp() {
38)
root_use
- send_stats "卸載LDNMP環境"
+ send_stats "解除安裝LDNMP環境"
read -e -p "$(echo -e "${gl_hong}强烈建议:${gl_bai}先备份全部网站数据,再卸载LDNMP环境。确定删除所有网站数据吗?(Y/N): ")" choice
case "$choice" in
[Yy])
@@ -8409,2244 +10079,9391 @@ linux_ldnmp() {
-linux_panel() {
- while true; do
- clear
- # send_stats "應用市場"
- echo -e "應用市場"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}寶塔面板官方版${gl_kjlan}2. ${gl_bai}aaPanel寶塔國際版"
- echo -e "${gl_kjlan}3. ${gl_bai}1Panel新一代管理面板${gl_kjlan}4. ${gl_bai}NginxProxyManager可視化面板"
- echo -e "${gl_kjlan}5. ${gl_bai}OpenList多存儲文件列表程序${gl_kjlan}6. ${gl_bai}Ubuntu遠程桌面網頁版"
- echo -e "${gl_kjlan}7. ${gl_bai}哪吒探針VPS監控面板${gl_kjlan}8. ${gl_bai}QB離線BT磁力下載面板"
- echo -e "${gl_kjlan}9. ${gl_bai}Poste.io郵件服務器程序${gl_kjlan}10. ${gl_bai}RocketChat多人在線聊天系統"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}禪道項目管理軟件${gl_kjlan}12. ${gl_bai}青龍面板定時任務管理平台"
- echo -e "${gl_kjlan}13. ${gl_bai}Cloudreve網盤${gl_huang}★${gl_bai} ${gl_kjlan}14. ${gl_bai}簡單圖床圖片管理程序"
- echo -e "${gl_kjlan}15. ${gl_bai}emby多媒體管理系統${gl_kjlan}16. ${gl_bai}Speedtest測速面板"
- echo -e "${gl_kjlan}17. ${gl_bai}AdGuardHome去廣告軟件${gl_kjlan}18. ${gl_bai}onlyoffice在線辦公OFFICE"
- echo -e "${gl_kjlan}19. ${gl_bai}雷池WAF防火牆面板${gl_kjlan}20. ${gl_bai}portainer容器管理面板"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}21. ${gl_bai}VScode網頁版${gl_kjlan}22. ${gl_bai}UptimeKuma監控工具"
- echo -e "${gl_kjlan}23. ${gl_bai}Memos網頁備忘錄${gl_kjlan}24. ${gl_bai}Webtop遠程桌面網頁版${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}25. ${gl_bai}Nextcloud網盤${gl_kjlan}26. ${gl_bai}QD-Today定時任務管理框架"
- echo -e "${gl_kjlan}27. ${gl_bai}Dockge容器堆棧管理面板${gl_kjlan}28. ${gl_bai}LibreSpeed測速工具"
- echo -e "${gl_kjlan}29. ${gl_bai}searxng聚合搜索站${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}PhotoPrism私有相冊系統"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}StirlingPDF工具大全${gl_kjlan}32. ${gl_bai}drawio免費的在線圖表軟件${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}Sun-Panel導航面板${gl_kjlan}34. ${gl_bai}Pingvin-Share文件分享平台"
- echo -e "${gl_kjlan}35. ${gl_bai}極簡朋友圈${gl_kjlan}36. ${gl_bai}LobeChatAI聊天聚合網站"
- echo -e "${gl_kjlan}37. ${gl_bai}MyIP工具箱${gl_huang}★${gl_bai} ${gl_kjlan}38. ${gl_bai}小雅alist全家桶"
- echo -e "${gl_kjlan}39. ${gl_bai}Bililive直播錄製工具${gl_kjlan}40. ${gl_bai}webssh網頁版SSH連接工具"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}耗子管理面板${gl_kjlan}42. ${gl_bai}Nexterm遠程連接工具"
- echo -e "${gl_kjlan}43. ${gl_bai}RustDesk遠程桌面(服務端)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${gl_bai}RustDesk遠程桌面(中繼端)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}45. ${gl_bai}Docker加速站${gl_kjlan}46. ${gl_bai}GitHub加速站${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}47. ${gl_bai}普羅米修斯監控${gl_kjlan}48. ${gl_bai}普羅米修斯(主機監控)"
- echo -e "${gl_kjlan}49. ${gl_bai}普羅米修斯(容器監控)${gl_kjlan}50. ${gl_bai}補貨監控工具"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}51. ${gl_bai}PVE開小雞面板${gl_kjlan}52. ${gl_bai}DPanel容器管理面板"
- echo -e "${gl_kjlan}53. ${gl_bai}llama3聊天AI大模型${gl_kjlan}54. ${gl_bai}AMH主機建站管理面板"
- echo -e "${gl_kjlan}55. ${gl_bai}FRP內網穿透(服務端)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${gl_bai}FRP內網穿透(客戶端)${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}57. ${gl_bai}Deepseek聊天AI大模型${gl_kjlan}58. ${gl_bai}Dify大模型知識庫${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}59. ${gl_bai}NewAPI大模型資產管理${gl_kjlan}60. ${gl_bai}JumpServer開源堡壘機"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}61. ${gl_bai}在線翻譯服務器${gl_kjlan}62. ${gl_bai}RAGFlow大模型知識庫"
- echo -e "${gl_kjlan}63. ${gl_bai}OpenWebUI自託管AI平台${gl_huang}★${gl_bai} ${gl_kjlan}64. ${gl_bai}it-tools工具箱"
- echo -e "${gl_kjlan}65. ${gl_bai}n8n自動化工作流平台${gl_huang}★${gl_bai} ${gl_kjlan}66. ${gl_bai}yt-dlp視頻下載工具"
- echo -e "${gl_kjlan}67. ${gl_bai}ddns-go動態DNS管理工具${gl_huang}★${gl_bai} ${gl_kjlan}68. ${gl_bai}AllinSSL證書管理平台"
- echo -e "${gl_kjlan}69. ${gl_bai}SFTPGo文件傳輸工具${gl_kjlan}70. ${gl_bai}AstrBot聊天機器人框架"
- echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}71. ${gl_bai}Navidrome私有音樂服務器${gl_kjlan}72. ${gl_bai}bitwarden密碼管理器${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}73. ${gl_bai}LibreTV私有影視${gl_kjlan}74. ${gl_bai}MoonTV私有影視"
- echo -e "${gl_kjlan}75. ${gl_bai}Melody音樂精靈${gl_kjlan}76. ${gl_bai}在線DOS老遊戲"
- echo -e "${gl_kjlan}77. ${gl_bai}迅雷離線下載工具${gl_kjlan}78. ${gl_bai}PandaWiki智能文檔管理系統"
- echo -e "${gl_kjlan}79. ${gl_bai}Beszel服務器監控"
+
+
+moltbot_menu() {
+ local app_id="114"
+
+ send_stats "clawdbot/moltbot管理"
+
+ check_openclaw_update() {
+ if ! command -v npm >/dev/null 2>&1; then
+ return 1
+ fi
+
+ # 加上 --no-update-notifier,並確保錯誤重定向位置正確
+ local_version=$(npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null | grep openclaw | awk '{print $NF}' | sed 's/^.*@//')
+
+ if [ -z "$local_version" ]; then
+ return 1
+ fi
+
+ remote_version=$(npm view openclaw version --no-update-notifier 2>/dev/null)
+
+ if [ -z "$remote_version" ]; then
+ return 1
+ fi
+
+ if [ "$local_version" != "$remote_version" ]; then
+ echo "${gl_huang}偵測到新版本:$remote_version${gl_bai}"
+ else
+ echo "${gl_lv}目前版本已是最新:$local_version${gl_bai}"
+ fi
+ }
+
+
+ get_install_status() {
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "${gl_lv}已安裝${gl_bai}"
+ else
+ echo "${gl_hui}未安裝${gl_bai}"
+ fi
+ }
+
+ get_running_status() {
+ if pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
+ echo "${gl_lv}運作中${gl_bai}"
+ else
+ echo "${gl_hui}未運行${gl_bai}"
+ fi
+ }
+
+
+ show_menu() {
+
+
+ clear
+
+ local install_status=$(get_install_status)
+ local running_status=$(get_running_status)
+ local update_message=$(check_openclaw_update)
+
+ echo "======================================="
+ echo -e "🦞 OPENCLAW 管理工具 by KEJILION 🦞"
+ echo -e "💡 終端執行 \033[1;33mk claw\033[0m 快速進入選單"
+ echo -e "$install_status $running_status $update_message"
+ echo "======================================="
+ echo "1. 安裝"
+ echo "2. 啟動"
+ echo "3. 停止"
+ echo "--------------------"
+ echo "4. 狀態日誌查看"
+ echo "5. 換模型"
+ echo "6. API管理"
+ echo "7. 機器人連線對接"
+ echo "8. 插件管理(安裝/刪除)"
+ echo "9. 技能管理(安裝/刪除)"
+ echo "10. 編輯主設定文件"
+ echo "11. 配置精靈"
+ echo "12. 健康檢測與修復"
+ echo "13. WebUI存取與設置"
+ echo "14. TUI命令列對話窗口"
+ echo "15. 記憶/Memory"
+ echo "16. 權限管理"
+ echo "17. 多智能體管理"
+ echo "--------------------"
+ echo "18. 備份與還原"
+ echo "19. 更新"
+ echo "20. 卸載"
+ echo "--------------------"
+ echo "0. 返回上一級選單"
+ echo "--------------------"
+ printf "請輸入選項並回車:"
+ }
+
+
+ start_gateway() {
+ openclaw gateway stop
+ openclaw gateway start
+ sleep 3
+ }
+
+
+ install_node_and_tools() {
+ if command -v dnf &>/dev/null; then
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
+ dnf update -y
+ dnf group install -y "Development Tools" "Development Libraries"
+ dnf install -y cmake libatomic nodejs
+ fi
+
+ if command -v apt &>/dev/null; then
+ curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
+ apt update -y
+ apt install build-essential python3 libatomic1 nodejs -y
+ fi
+ }
+
+ sync_openclaw_api_models() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ [ ! -f "$config_file" ] && return 0
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$ENABLE_STATS" "$sh_v" <<'PY'
+import copy
+import json
+import os
+import platform
+import sys
+import time
+import urllib.request
+from datetime import datetime, timezone
+
+path = sys.argv[1]
+stats_enabled = (sys.argv[2].lower() == "true") if len(sys.argv) > 2 else True
+script_version = sys.argv[3] if len(sys.argv) > 3 else ""
+
+def send_stat(action):
+ if not stats_enabled:
+ return
+ payload = {
+ "action": action,
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
+ "country": "",
+ "os_info": platform.platform(),
+ "cpu_arch": platform.machine(),
+ "version": script_version,
+ }
+ try:
+ req = urllib.request.Request(
+ "https://api.kejilion.pro/api/log",
+ data=json.dumps(payload).encode("utf-8"),
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=3):
+ pass
+ except Exception:
+ pass
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('ℹ️ 未检测到 API providers,跳过模型同步')
+ raise SystemExit(0)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+changed = False
+fatal_errors = []
+summary = []
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+def prompt_delete_provider(name):
+ prompt = f"⚠️ {name} /models 偵測連續失敗 3 次。是否刪除該 API 供應商及其全部相關模型? [y/N]:"
+ try:
+ ans = input(prompt).strip().lower()
+ except EOFError:
+ return False
+ return ans in ('y', 'yes')
+
+
+def rebind_defaults_before_delete(name):
+ global changed
+
+ replacement = None
+
+ def get_replacement():
+ nonlocal replacement
+ if replacement is None:
+ candidates = collect_available_refs(exclude_provider=name)
+ replacement = candidates[0] if candidates else None
+ return replacement
+
+ primary_ref = get_primary_ref(defaults)
+ if ref_provider(primary_ref) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: 預設主模型指向該 provider,但無可用替代模型,已中止刪除')
+ return False
+ set_primary_ref(defaults, repl)
+ changed = True
+ summary.append(f'🔁 刪除前已切換預設主模型: {primary_ref} -> {repl}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ repl = get_replacement()
+ if not repl:
+ summary.append(f'❌ {name}: {fk} 指向该 provider,但无可用替代模型,已中止删除')
+ return False
+ defaults[fk] = repl
+ changed = True
+ summary.append(f'🔁 删除前已切换 {fk}: {val} -> {repl}')
+
+ return True
+
+
+def delete_provider_and_refs(name):
+ global changed
+
+ if not rebind_defaults_before_delete(name):
+ return False
+
+ removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+ for r in removed_refs:
+ defaults_models.pop(r, None)
+ if removed_refs:
+ changed = True
+
+ if name in providers:
+ providers.pop(name, None)
+ changed = True
+
+ summary.append(f'🗑️ 已刪除 provider {name},並移除 defaults.models 下 {len(removed_refs)} 個模型引用')
+ return True
+
+
+def fetch_remote_models_with_retry(name, base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ data = json.loads(payload)
+ return data, None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+for name, provider in list(providers.items()):
+ if not isinstance(provider, dict):
+ summary.append(f'ℹ️ 跳過 {name}: provider 結構非法')
+ continue
+
+ api = provider.get('api', '')
+ base_url = provider.get('baseUrl')
+ api_key = provider.get('apiKey')
+ model_list = provider.get('models', [])
+
+ if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ summary.append(f'ℹ️ 跳過 {name}: 無 baseUrl/apiKey/models')
+ continue
+
+ if api not in SUPPORTED_APIS:
+ summary.append(f'🔁 {name}: 发现非法协议 {api or "(unset)"},将重新探测')
+ provider['api'] = ''
+ api = ''
+ changed = True
+
+ data, err, attempts = fetch_remote_models_with_retry(name, base_url, api_key, retries=3)
+ if err is not None:
+ summary.append(f'⚠️ {name}: /models 探测失败,已重试 {attempts} 次 ({type(err).__name__}: {err})')
+ send_stat('OpenClaw API確認介入')
+ if prompt_delete_provider(name):
+ deleted = delete_provider_and_refs(name)
+ if deleted:
+ send_stat('OpenClaw API刪失敗Provider-確認')
+ summary.append(f'✅ {name}: 使用者已確認刪除該 provider 及全部相關模型引用')
+ else:
+ send_stat('OpenClaw API刪失敗Provider-拒絕')
+ summary.append(f'ℹ️ {name}: 使用者未確認刪除,保留現有 provider 配置')
+ continue
+
+ if attempts > 1:
+ summary.append(f'🔁 {name}: /models 第 {attempts} 次重試後成功')
+
+ if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ summary.append(f'⚠️ 跳過 {name}: /models 回傳結構不可識別')
+ continue
+
+ remote_ids = []
+ for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+ remote_set = set(remote_ids)
+
+ if not remote_set:
+ fatal_errors.append(f'❌ {name} 上游 /models 為空,無法為該 provider 提供兜底模型')
+ continue
+
+ local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+ local_ids = [str(m['id']) for m in local_models]
+ local_set = set(local_ids)
+
+ template = None
+ for m in local_models:
+ template = copy.deepcopy(m)
+ break
+ if template is None:
+ summary.append(f'⚠️ 跳過 {name}: 本地 models 無有效模板模型')
+ continue
+
+ removed_ids = [mid for mid in local_ids if mid not in remote_set]
+ added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+ kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+ new_models = kept_models[:]
+
+ for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{name} / {mid}'
+ new_models.append(nm)
+
+ if not new_models:
+ fatal_errors.append(f'❌ {name} 同步後無可用模型,無法保障預設模型/回退模型兜底')
+ continue
+
+ expected_refs = {model_ref(name, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+ local_refs = {model_ref(name, mid) for mid in local_ids}
+
+ first_ref = model_ref(name, str(new_models[0]['id']))
+
+ primary_ref = get_primary_ref(defaults)
+ if isinstance(primary_ref, str) and primary_ref in (local_refs - expected_refs):
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ summary.append(f'🔁 預設模型已兜底替換: {primary_ref} -> {first_ref}')
+
+ for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in (local_refs - expected_refs):
+ defaults[fk] = first_ref
+ changed = True
+ summary.append(f'🔁 {fk} 已兜底替換: {val} -> {first_ref}')
+
+ stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/') and r not in expected_refs]
+ for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+ for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+ if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+ summary.append(f'✅ {name}: 新增 {len(added_ids)} 個,刪除 {len(removed_ids)} 個,目前 {len(new_models)} 個')
+
+ if added_ids:
+ summary.append(f'➕ 新增模型({len(added_ids)}):')
+ for mid in added_ids:
+ summary.append(f' + {mid}')
+ if removed_ids:
+ summary.append(f'➖ 刪除模型({len(removed_ids)}):')
+ for mid in removed_ids:
+ summary.append(f' - {mid}')
+
+
+if fatal_errors:
+ for line in summary:
+ print(line)
+ for err in fatal_errors:
+ print(err)
+ print('❌ 模型同步失敗:存在 provider 同步後無可用模型,已中止寫入')
+ raise SystemExit(2)
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+ for line in summary:
+ print(line)
+ print('✅ OpenClaw API 模型一致性同步完成并已写入配置')
+else:
+ for line in summary:
+ print(line)
+ print('ℹ️ 無需同步:配置已與上游 /models 保持一致')
+PY
+ }
+
+
+
+ install_moltbot() {
+ echo "開始安裝 OpenClaw..."
+ send_stats "開始安裝 OpenClaw..."
+ install git jq
+
+ install_node_and_tools
+
+ country=$(curl -s ipinfo.io/country)
+ if [[ "$country" == "CN" || "$country" == "HK" ]]; then
+ npm config set registry https://registry.npmmirror.com
+ fi
+
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+
+ npm install -g openclaw@latest
+ openclaw onboard --install-daemon
+ start_gateway
+ add_app_id
+ break_end
+
+ }
+
+
+ start_bot() {
+ echo "啟動 OpenClaw..."
+ send_stats "啟動 OpenClaw..."
+ start_gateway
+ break_end
+ }
+
+ stop_bot() {
+ echo "停止 OpenClaw..."
+ send_stats "停止 OpenClaw..."
+ tmux kill-session -t gateway > /dev/null 2>&1
+ openclaw gateway stop
+ break_end
+ }
+
+ view_logs() {
+ echo "查看 OpenClaw 狀態日誌"
+ send_stats "查看 OpenClaw 日誌"
+ openclaw status
+ openclaw gateway status
+ openclaw logs
+ break_end
+ }
+
+
+
+
+
+ # OpenClaw API 協定偵測邏輯已移除:不再自動偵測/判定 API 類型。
+ # 说明:API 类型由用户显式配置(models.providers..api),脚本不再尝试调用 /responses 做推断。
+
+ # 建構模型配置 JSON
+ build-openclaw-provider-models-json() {
+ local provider_name="$1"
+ local model_ids="$2"
+ local models_array="["
+ local first=true
+
+ while read -r model_id; do
+ [ -z "$model_id" ] && continue
+ [[ $first == false ]] && models_array+=","
+ first=false
+
+ local context_window=1048576
+ local max_tokens=128000
+ local input_cost=0.15
+ local output_cost=0.60
+
+ case "$model_id" in
+ *opus*|*pro*|*preview*|*thinking*|*sonnet*)
+ input_cost=2.00
+ output_cost=12.00
+ ;;
+ *gpt-5*|*codex*)
+ input_cost=1.25
+ output_cost=10.00
+ ;;
+ *flash*|*lite*|*haiku*|*mini*|*nano*)
+ input_cost=0.10
+ output_cost=0.40
+ ;;
+ esac
+
+ models_array+=$(cat < 0 then
+ .["\($prov)/\($mid)"] //= {}
+ else
+ .
+ end
+ )
+ )
+ ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
+ }
+
+ # 核心函數:取得並加入所有模型
+ add-all-models-from-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+
+ echo "🔍 正在獲取$provider_name的所有可用模型..."
+
+ local models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -z "$models_json" ]]; then
+ echo "❌ 無法取得模型列表"
+ return 1
+ fi
+
+ local model_ids=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+')
+
+ if [[ -z "$model_ids" ]]; then
+ echo "❌ 未找到任何模型"
+ return 1
+ fi
+
+ local model_count=$(echo "$model_ids" | wc -l)
+ echo "✅ 發現$model_count個模型"
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$model_ids")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 成功添加$model_count個模型到$provider_name"
+ echo "📦 模型引用格式:$provider_name/"
+ return 0
+ else
+ echo "❌ 配置注入失敗"
+ return 1
+ fi
+ }
+
+ # 僅添加預設模型並保留 provider
+ add-default-model-only-to-provider() {
+ local provider_name="$1"
+ local base_url="$2"
+ local api_key="$3"
+ local default_model="$4"
+
+ if [[ -z "$default_model" ]]; then
+ echo "❌ 默认模型不能为空"
+ return 1
+ fi
+
+ local models_array
+ models_array=$(build-openclaw-provider-models-json "$provider_name" "$default_model")
+
+ write-openclaw-provider-models "$provider_name" "$base_url" "$api_key" "$models_array"
+
+ if [[ $? -eq 0 ]]; then
+ echo "✅ 已新增 provider:$provider_name"
+ echo "✅ 僅寫入預設模型:$default_model"
+ return 0
+ else
+ echo "❌ 配置注入失敗"
+ return 1
+ fi
+ }
+
+ add-openclaw-provider-interactive() {
+ send_stats "OpenClaw API新增"
+ echo "=== 互動式加入 OpenClaw Provider (全量模型) ==="
+
+ # 1. Provider 名稱
+ read -erp "請輸入 Provider 名稱 (如: deepseek):" provider_name
+ while [[ -z "$provider_name" ]]; do
+ echo "❌ Provider 名稱不能為空"
+ read -erp "請輸入 Provider 名稱:" provider_name
+ done
+
+ # 2. Base URL
+ read -erp "請輸入 Base URL (如: https://api.xxx.com/v1):" base_url
+ while [[ -z "$base_url" ]]; do
+ echo "❌ Base URL 不能為空"
+ read -erp "請輸入 Base URL:" base_url
+ done
+ base_url="${base_url%/}"
+
+ # 3. API Key
+ read -rsp "請輸入 API Key (輸入不顯示):" api_key
+ echo
+ while [[ -z "$api_key" ]]; do
+ echo "❌ API Key 不能為空"
+ read -rsp "請輸入 API Key:" api_key
+ echo
+ done
+
+ # 4. 不再偵測/判斷 API 類型;協定由使用者自行選擇與維護
+
+ # 5. 取得模型列表
+ echo "🔍 正在取得可用模型清單..."
+ models_json=$(curl -s -m 10 \
+ -H "Authorization: Bearer $api_key" \
+ "${base_url}/models")
+
+ if [[ -n "$models_json" ]]; then
+ available_models=$(echo "$models_json" | grep -oP '"id":\s*"\K[^"]+' | sort)
+
+ if [[ -n "$available_models" ]]; then
+ model_count=$(echo "$available_models" | wc -l)
+ echo "✅ 發現$model_count個可用模型:"
+ echo "--------------------------------"
+ # 全部顯示,附序號
+ i=1
+ model_list=()
+ while read -r model; do
+ echo "[$i] $model"
+ model_list+=("$model")
+ ((i++))
+ done <<< "$available_models"
+ echo "--------------------------------"
+ fi
+ fi
+
+ # 5. 選擇預設模型
+ echo
+ read -erp "請輸入預設 Model ID (或序號,留空則使用第一個):" input_model
+
+ if [[ -z "$input_model" && -n "$available_models" ]]; then
+ default_model=$(echo "$available_models" | head -1)
+ echo "🎯 使用第一個模型:$default_model"
+ elif [[ "$input_model" =~ ^[0-9]+$ ]] && [ "${#model_list[@]}" -gt 0 ] && [ "$input_model" -ge 1 ] && [ "$input_model" -le "${#model_list[@]}" ]; then
+ default_model="${model_list[$((input_model-1))]}"
+ echo "🎯 已選擇模型:$default_model"
+ else
+ default_model="$input_model"
+ fi
+
+ # 6. 確認訊息
+ echo
+ echo "====== 確認訊息 ======"
+ echo "Provider : $provider_name"
+ echo "Base URL : $base_url"
+ echo "API Key : ${api_key:0:8}****"
+ echo "預設模型 :$default_model"
+ echo "模型總數 :$model_count"
+ echo "======================"
+
+ read -erp "是否同時新增其他所有可用模型? (y/N):" confirm
+
+ install jq
+ if [[ "$confirm" =~ ^[Yy]$ ]]; then
+ add-all-models-from-provider "$provider_name" "$base_url" "$api_key"
+ add_result=$?
+ finish_msg="✅ 完成!所有$model_count個模型已載入"
+ else
+ add-default-model-only-to-provider "$provider_name" "$base_url" "$api_key" "$default_model"
+ add_result=$?
+ finish_msg="✅ 完成!已保留 provider,並僅載入預設模型:$default_model"
+ fi
+
+ if [[ $add_result -eq 0 ]]; then
+ echo
+ echo "🔄 設定預設模型並重新啟動網關..."
+ openclaw models set "$provider_name/$default_model"
+ openclaw_sync_sessions_model "$provider_name/$default_model"
+ start_gateway
+ echo "$finish_msg"
+ echo "✅ 當前 API 協定類型:$DETECTED_API"
+ fi
+
+ break_end
+ }
+
+
+
+openclaw_api_manage_list() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API列表"
+
+ while IFS=$'\t' read -r rec_type idx name base_url model_count api_type latency_txt latency_level; do
+ case "$rec_type" in
+ MSG)
+ echo "$idx"
+ ;;
+ ROW)
+ local latency_color="$gl_bai"
+ case "$latency_level" in
+ low) latency_color="$gl_lv" ;;
+ medium) latency_color="$gl_huang" ;;
+ high|unavailable) latency_color="$gl_hong" ;;
+ unchecked) latency_color="$gl_bai" ;;
+ esac
+
+ printf '%b\n' "[$idx] ${name} | API: ${base_url}| 協議:${api_type}| 模型數量:${gl_huang}${model_count}${gl_bai}| 延遲/狀態:${latency_color}${latency_txt}${gl_bai}"
+ ;;
+ esac
+ done < <(python3 - "$config_file" <<-'PY'
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+
+def ping_models(base_url, api_key):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'OpenClaw-API-Manage/1.0',
+ },
+ )
+ start = time.perf_counter()
+ with urllib.request.urlopen(req, timeout=4) as resp:
+ resp.read(2048)
+ return int((time.perf_counter() - start) * 1000)
+
+
+def classify_latency(latency):
+ if latency == '不可用':
+ return '不可用', 'unavailable'
+ if latency == '未檢測':
+ return '未檢測', 'unchecked'
+ if isinstance(latency, int):
+ if latency <= 800:
+ level = 'low'
+ elif latency <= 2000:
+ level = 'medium'
+ else:
+ level = 'high'
+ return f'{latency}ms', level
+ return str(latency), 'unchecked'
+
+
+try:
+ with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+except FileNotFoundError:
+ print('MSG\tℹ️ 未找到 openclaw.json,請先完成安裝/初始化。')
+ raise SystemExit(0)
+except Exception as e:
+ print(f'MSG\t❌ 讀取設定失敗: {type(e).__name__}: {e}')
+ raise SystemExit(0)
+
+providers = ((obj.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or not providers:
+ print('MSG\tℹ️ 目前未配置任何 API provider。')
+ raise SystemExit(0)
+
+print('MSG\t--- 已設定 API 清單 ---')
+
+for idx, name in enumerate(sorted(providers.keys()), start=1):
+ provider = providers.get(name)
+ if not isinstance(provider, dict):
+ base_url = '-'
+ model_count = 0
+ latency_raw = '不可用'
+ else:
+ base_url = provider.get('baseUrl') or provider.get('url') or provider.get('endpoint') or '-'
+ models = provider.get('models') if isinstance(provider.get('models'), list) else []
+ model_count = sum(1 for m in models if isinstance(m, dict) and m.get('id'))
+ api = provider.get('api', '')
+ api_key = provider.get('apiKey')
+
+ latency_raw = '未檢測'
+ if api in SUPPORTED_APIS:
+ if isinstance(base_url, str) and base_url != '-' and isinstance(api_key, str) and api_key:
+ try:
+ latency_raw = ping_models(base_url, api_key)
+ except Exception:
+ latency_raw = '不可用'
+ else:
+ latency_raw = '不可用'
+
+ latency_text, latency_level = classify_latency(latency_raw)
+ api_label = api if api in SUPPORTED_APIS else '-'
+ print(
+ 'ROW\t' + '\t'.join([
+ str(idx),
+ str(name),
+ str(base_url),
+ str(model_count),
+ str(api_label),
+ str(latency_text),
+ str(latency_level),
+ ])
+ )
+PY
+)
+}
+sync-openclaw-provider-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API按Provider同步"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到設定檔:$config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "請輸入要同步的 API 名稱(provider),直接回車同步全部:" provider_name
+ if [ -z "$provider_name" ]; then
+ if sync_openclaw_api_models; then
+ start_gateway
+ else
+ echo "❌ API 模型同步失敗,已中止重新啟動網關。請檢查 provider /models 返回後重試。"
+ return 1
+ fi
+ break_end
+ return 0
+ fi
+
+ install jq curl >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" <<'PY2'
+import copy
+import json
+import sys
+import time
+import urllib.request
+
+path = sys.argv[1]
+target = sys.argv[2]
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or not providers:
+ print('❌ 未偵測到 API providers,無法同步')
+ raise SystemExit(2)
+
+provider = providers.get(target)
+if not isinstance(provider, dict):
+ print(f'❌ 未找到 provider: {target}')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def fetch_remote_models_with_retry(base_url, api_key, retries=3):
+ last_error = None
+ for attempt in range(1, retries + 1):
+ req = urllib.request.Request(
+ base_url.rstrip('/') + '/models',
+ headers={
+ 'Authorization': f'Bearer {api_key}',
+ 'User-Agent': 'Mozilla/5.0',
+ },
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=12) as resp:
+ payload = resp.read().decode('utf-8', 'ignore')
+ return json.loads(payload), None, attempt
+ except Exception as e:
+ last_error = e
+ if attempt < retries:
+ time.sleep(1)
+ return None, last_error, retries
+
+
+api = provider.get('api', '')
+base_url = provider.get('baseUrl')
+api_key = provider.get('apiKey')
+model_list = provider.get('models', [])
+
+if not base_url or not api_key or not isinstance(model_list, list) or not model_list:
+ print(f'❌ provider {target} 缺少 baseUrl/apiKey/models,無法執行同步')
+ raise SystemExit(3)
+
+if api not in SUPPORTED_APIS:
+ print(f'ℹ️ provider {target} 目前 api={api},但腳本已不再探測/修正協定;請手動設定為 openai-completions 或 openai-responses')
+
+protocol_msg = None
+
+data, err, attempts = fetch_remote_models_with_retry(base_url, api_key, retries=3)
+if err is not None:
+ print(f'❌ {target}: /models 探測失敗,已重試 {attempts} 次 ({type(err).__name__}: {err})')
+ raise SystemExit(4)
+
+if not (isinstance(data, dict) and isinstance(data.get('data'), list)):
+ print(f'❌ {target}: /models 回傳結構不可識別')
+ raise SystemExit(4)
+
+remote_ids = []
+for item in data['data']:
+ if isinstance(item, dict) and item.get('id'):
+ remote_ids.append(str(item['id']))
+remote_set = set(remote_ids)
+if not remote_set:
+ print(f'❌ {target}: 上游 /models 为空,已中止同步')
+ raise SystemExit(5)
+
+local_models = [m for m in model_list if isinstance(m, dict) and m.get('id')]
+local_ids = [str(m['id']) for m in local_models]
+local_set = set(local_ids)
+
+template = copy.deepcopy(local_models[0]) if local_models else None
+if template is None:
+ print(f'❌ {target}: 本地 models 無有效模板模型,無法補全新增模型')
+ raise SystemExit(3)
+
+removed_ids = [mid for mid in local_ids if mid not in remote_set]
+added_ids = [mid for mid in remote_ids if mid not in local_set]
+
+kept_models = [copy.deepcopy(m) for m in local_models if str(m['id']) in remote_set]
+new_models = kept_models[:]
+for mid in added_ids:
+ nm = copy.deepcopy(template)
+ nm['id'] = mid
+ if isinstance(nm.get('name'), str):
+ nm['name'] = f'{target} / {mid}'
+ new_models.append(nm)
+
+if not new_models:
+ print(f'❌ {target}: 同步後無可用模型,已中止寫入')
+ raise SystemExit(5)
+
+expected_refs = {model_ref(target, str(m['id'])) for m in new_models if isinstance(m, dict) and m.get('id')}
+local_refs = {model_ref(target, mid) for mid in local_ids}
+removed_refs = local_refs - expected_refs
+first_ref = model_ref(target, str(new_models[0]['id']))
+
+changed = False
+primary_ref = get_primary_ref(defaults)
+if isinstance(primary_ref, str) and primary_ref in removed_refs:
+ set_primary_ref(defaults, first_ref)
+ changed = True
+ print(f'🔁 預設模型已兜底替換: {primary_ref} -> {first_ref}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if isinstance(val, str) and val in removed_refs:
+ defaults[fk] = first_ref
+ changed = True
+ print(f'🔁 {fk} 已兜底替換: {val} -> {first_ref}')
+
+stale_refs = [r for r in list(defaults_models.keys()) if r.startswith(target + '/') and r not in expected_refs]
+for r in stale_refs:
+ defaults_models.pop(r, None)
+ changed = True
+
+for r in sorted(expected_refs):
+ if r not in defaults_models:
+ defaults_models[r] = {}
+ changed = True
+
+if removed_ids or added_ids or len(local_models) != len(new_models):
+ provider['models'] = new_models
+ changed = True
+
+
+if changed:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ {target}: 新增 {len(added_ids)} 個,刪除 {len(removed_ids)} 個,目前 {len(new_models)} 個')
+
+if added_ids:
+ print(f'➕ 新增模型({len(added_ids)}):')
+ for mid in added_ids:
+ print(f' + {mid}')
+if removed_ids:
+ print(f'➖ 刪除模型({len(removed_ids)}):')
+ for mid in removed_ids:
+ print(f' - {mid}')
+
+if changed:
+ print('✅ 指定 provider 模型一致性同步完成並已寫入配置')
+else:
+ print('ℹ️ 無需同步:此 provider 配置已與上游 /models 保持一致')
+PY2
+ local rc=$?
+ case "$rc" in
+ 0)
+ echo "✅ 同步执行完成"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 同步失敗:provider 不存在或未配置"
+ ;;
+ 3)
+ echo "❌ 同步失败:provider 配置不完整或类型不支持"
+ ;;
+ 4)
+ echo "❌ 同步失敗:上游 /models 請求失敗"
+ ;;
+ 5)
+ echo "❌ 同步失败:上游模型为空或同步后无可用模型"
+ ;;
+ *)
+ echo "❌ 同步失敗:請檢查設定檔結構或日誌輸出"
+ ;;
+ esac
+
+ break_end
+}
+
+openclaw_detect_api_protocol_by_provider() {
+ # 協定探測邏輯已移除:腳本不再自動偵測/判定 API 類型。
+ # 保留函數以相容選單調用,但不做任何改寫。
+ echo "ℹ️ 已关闭协议探测:请手动在 ${HOME}/.openclaw/openclaw.json 中設定 provider.api 為 openai-completions 或 openai-responses"
+ return 0
+}
+
+fix-openclaw-provider-protocol-interactive() {
+ local config_file="${HOME}/.openclaw/openclaw.json"
+ send_stats "OpenClaw API協定切換"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到設定檔:$config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "請輸入要切換協定的 API 名稱(provider):" provider_name
+ if [ -z "$provider_name" ]; then
+ echo "❌ provider 名稱不能為空"
+ break_end
+ return 1
+ fi
+
+ echo "请选择要设置的 API 类型:"
+ echo "1. openai-completions"
+ echo "2. openai-responses"
+ read -erp "請輸入你的選擇 (1/2):" proto_choice
+
+ local new_api=""
+ case "$proto_choice" in
+ 1) new_api="openai-completions" ;;
+ 2) new_api="openai-responses" ;;
+ *)
+ echo "❌ 无效选择"
+ break_end
+ return 1
+ ;;
+ esac
+
+ install python3 >/dev/null 2>&1
+
+ python3 - "$config_file" "$provider_name" "$new_api" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+new_api = sys.argv[3]
+
+SUPPORTED_APIS = {'openai-completions', 'openai-responses'}
+if new_api not in SUPPORTED_APIS:
+ print('❌ 非法協議值')
+ raise SystemExit(3)
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+providers = ((work.get('models') or {}).get('providers') or {})
+if not isinstance(providers, dict) or name not in providers or not isinstance(providers.get(name), dict):
+ print(f'❌ 未找到 provider: {name}')
+ raise SystemExit(2)
+
+providers[name]['api'] = new_api
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'✅ 已更新 provider {name} 協定為: {new_api}')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 切换失败:provider 不存在或未配置"
+ ;;
+ 3)
+ echo "❌ 切換失敗:協定值非法"
+ ;;
+ *)
+ echo "❌ 切換失敗:請檢查設定檔結構或日誌輸出"
+ ;;
+ esac
+
+ break_end
+}
+
+ delete-openclaw-provider-interactive() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ send_stats "OpenClaw API刪除入口"
+
+ if [ ! -f "$config_file" ]; then
+ echo "❌ 未找到設定檔:$config_file"
+ break_end
+ return 1
+ fi
+
+ read -erp "請輸入要刪除的 API 名稱(provider):" provider_name
+ if [ -z "$provider_name" ]; then
+ send_stats "OpenClaw API刪除取消"
+ echo "❌ provider 名稱不能為空"
+ break_end
+ return 1
+ fi
+
+ python3 - "$config_file" "$provider_name" <<'PY'
+import copy
+import json
+import sys
+
+path = sys.argv[1]
+name = sys.argv[2]
+
+with open(path, 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+
+work = copy.deepcopy(obj)
+models_cfg = work.setdefault('models', {})
+providers = models_cfg.get('providers', {})
+if not isinstance(providers, dict) or name not in providers:
+ print(f'❌ 未找到 provider: {name}')
+ raise SystemExit(2)
+
+agents = work.setdefault('agents', {})
+defaults = agents.setdefault('defaults', {})
+defaults_models_raw = defaults.get('models')
+if isinstance(defaults_models_raw, dict):
+ defaults_models = defaults_models_raw
+elif isinstance(defaults_models_raw, list):
+ defaults_models = {str(x): {} for x in defaults_models_raw if isinstance(x, str)}
+else:
+ defaults_models = {}
+defaults['models'] = defaults_models
+
+
+def model_ref(provider_name, model_id):
+ return f"{provider_name}/{model_id}"
+
+
+def ref_provider(ref):
+ if not isinstance(ref, str) or '/' not in ref:
+ return None
+ return ref.split('/', 1)[0]
+
+
+def get_primary_ref(defaults_obj):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ return model_obj
+ if isinstance(model_obj, dict):
+ primary = model_obj.get('primary')
+ if isinstance(primary, str):
+ return primary
+ return None
+
+
+def set_primary_ref(defaults_obj, new_ref):
+ model_obj = defaults_obj.get('model')
+ if isinstance(model_obj, str):
+ defaults_obj['model'] = new_ref
+ elif isinstance(model_obj, dict):
+ model_obj['primary'] = new_ref
+ else:
+ defaults_obj['model'] = {'primary': new_ref}
+
+
+def collect_available_refs(exclude_provider=None):
+ refs = []
+ if not isinstance(providers, dict):
+ return refs
+ for pname, p in providers.items():
+ if exclude_provider and pname == exclude_provider:
+ continue
+ if not isinstance(p, dict):
+ continue
+ for m in p.get('models', []) or []:
+ if isinstance(m, dict) and m.get('id'):
+ refs.append(model_ref(pname, str(m['id'])))
+ return refs
+
+
+replacement_candidates = collect_available_refs(exclude_provider=name)
+replacement = replacement_candidates[0] if replacement_candidates else None
+
+primary_ref = get_primary_ref(defaults)
+if ref_provider(primary_ref) == name:
+ if not replacement:
+ print('❌ 刪除中止:預設主模型指向該 provider,且無可用替代模型')
+ raise SystemExit(3)
+ set_primary_ref(defaults, replacement)
+ print(f'🔁 預設主模型切換: {primary_ref} -> {replacement}')
+
+for fk in ('modelFallback', 'imageModelFallback'):
+ val = defaults.get(fk)
+ if ref_provider(val) == name:
+ if not replacement:
+ print(f'❌ 刪除中止:{fk} 指向該 provider,且無可用替代模型')
+ raise SystemExit(3)
+ defaults[fk] = replacement
+ print(f'🔁 {fk} 切換: {val} -> {replacement}')
+
+removed_refs = [r for r in list(defaults_models.keys()) if r.startswith(name + '/')]
+for r in removed_refs:
+ defaults_models.pop(r, None)
+
+providers.pop(name, None)
+
+with open(path, 'w', encoding='utf-8') as f:
+ json.dump(work, f, ensure_ascii=False, indent=2)
+ f.write('\n')
+
+print(f'🗑️ 已刪除 provider: {name}')
+print(f'🧹 已清理 defaults.models 中 {len(removed_refs)} 個關聯模型引用')
+PY
+ local rc=$?
+ case "$rc" in
+ 0)
+ send_stats "OpenClaw API刪除確認"
+ echo "✅ 刪除完成"
+ start_gateway
+ ;;
+ 2)
+ echo "❌ 刪除失敗:provider 不存在"
+ ;;
+ 3)
+ send_stats "OpenClaw API刪除取消"
+ echo "❌ 刪除失敗:無可用替代模型,已保持原始配置"
+ ;;
+ *)
+ echo "❌ 刪除失敗:請檢查設定檔結構或日誌輸出"
+ ;;
+ esac
+
+ break_end
+ }
+
+ openclaw_api_providers_showcase() {
+ send_stats "OpenClaw API廠商推薦"
+
+ clear
+ echo ""
+ echo -e "${gl_kjlan}╔════════════════════════════════════════════════════════════╗${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_huang}🌟 API 廠商推薦列表${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}║${gl_bai} ${gl_zi}部分入口含 AFF${gl_bai} ${gl_kjlan}║${gl_bai}"
+ echo -e "${gl_kjlan}╚════════════════════════════════════════════════════════════╝${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● DeepSeek${gl_bai}"
+ echo -e " ${gl_kjlan}https://api-docs.deepseek.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● OpenRouter${gl_bai}"
+ echo -e " ${gl_kjlan}https://openrouter.ai/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Kimi${gl_bai}"
+ echo -e " ${gl_kjlan}https://platform.moonshot.cn/docs/guide/start-using-kimi-api${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 超算互聯網${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.scnet.cn/${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 優雲智算${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://passport.compshare.cn/register?referral_code=4mscFZXfutfFi8swMVsPuf${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 矽基流動${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://cloud.siliconflow.cn/i/irWVdPic${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 智譜 GLM${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.bigmodel.cn/glm-coding?ic=HYOTDOAJMR${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● PackyAPI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.packyapi.com/register?aff=wHri${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}● 雲霧 API${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://yunwu.ai/register?aff=ZuyK${gl_bai}"
+ echo ""
+ echo -e " ${gl_huang}●柏拉圖AI${gl_bai} ${gl_zi}[AFF]${gl_bai}"
+ echo -e " ${gl_kjlan}https://api.bltcy.ai/register?aff=TBzb114019${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● MiniMax${gl_bai}"
+ echo -e " ${gl_kjlan}https://www.minimaxi.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● NVIDIA${gl_bai}"
+ echo -e " ${gl_kjlan}https://build.nvidia.com/settings/api-keys${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● Ollama${gl_bai}"
+ echo -e " ${gl_kjlan}https://ollama.com/${gl_bai}"
+ echo ""
+ echo -e " ${gl_lv}● 白山雲${gl_bai}"
+ echo -e " ${gl_kjlan}https://ai.baishan.com/${gl_bai}"
+ echo ""
+ echo -e "${gl_kjlan}────────────────────────────────────────────────────────────${gl_bai}"
+ echo -e " ${gl_zi}圖例:${gl_lv}● 官方入口${gl_bai} ${gl_huang}● AFF 推薦入口${gl_bai}"
+ echo ""
+ echo -e "${gl_huang}提示:複製連結到瀏覽器開啟即可訪問${gl_bai}"
+ echo ""
+ read -erp "按回車鍵返回..." dummy
+ }
+
+ openclaw_api_manage_menu() {
+ send_stats "OpenClaw API入口"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw API 管理"
+ echo "======================================="
+ openclaw_api_manage_list
+ echo "---------------------------------------"
+ echo "1. 新增API"
+ echo "2. 同步API供應商模型列表"
+ echo "3. 切換 API 類型(completions / responses)"
+ echo "4. 刪除API"
+ echo "5. API 廠商推薦"
+ echo "0. 退出"
+ echo "---------------------------------------"
+ read -erp "請輸入你的選擇:" api_choice
+
+ case "$api_choice" in
+ 1)
+ add-openclaw-provider-interactive
+ ;;
+ 2)
+ sync-openclaw-provider-interactive
+ ;;
+ 3)
+ fix-openclaw-provider-protocol-interactive
+ ;;
+ 4)
+ delete-openclaw-provider-interactive
+ ;;
+ 5)
+ openclaw_api_providers_showcase
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "無效的選擇,請重試。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ install_gum() {
+ if command -v gum >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if command -v apt >/dev/null 2>&1; then
+ mkdir -p /etc/apt/keyrings
+ curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg
+ echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | tee /etc/apt/sources.list.d/charm.list > /dev/null
+ apt update && apt install -y gum
+ elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
+ cat > /etc/yum.repos.d/charm.repo <<'REPO'
+[charm]
+name=Charm
+baseurl=https://repo.charm.sh/yum/
+enabled=1
+gpgcheck=1
+gpgkey=https://repo.charm.sh/yum/gpg.key
+REPO
+ rpm --import https://repo.charm.sh/yum/gpg.key
+ if command -v dnf >/dev/null 2>&1; then
+ dnf install -y gum
+ else
+ yum install -y gum
+ fi
+ elif command -v zypper >/dev/null 2>&1; then
+ zypper --non-interactive refresh
+ zypper --non-interactive install gum
+ fi
+ }
+
+
+
+ change_model() {
+ send_stats "換模型"
+
+ local orange="#FF8C00"
+
+ openclaw_probe_status_line() {
+ local status_text="$1"
+ local status_color_ok='[32m'
+ local status_color_fail='[31m'
+ local status_color_reset='[0m'
+ if [ "$status_text" = "可用" ]; then
+ printf "%b最小检测结果:%s%b
+" "$status_color_ok" "$status_text" "$status_color_reset"
+ else
+ printf "%b最小检测结果:%s%b
+" "$status_color_fail" "$status_text" "$status_color_reset"
+ fi
+ }
+
+ openclaw_model_probe() {
+ local target_model="$1"
+ local probe_timeout=25
+ local tmp_payload tmp_response probe_result probe_status reply_preview reply_trimmed
+ local oc_config provider_name base_url api_key request_model
+ local first_endpoint second_endpoint
+ local first_exit first_http first_latency second_exit second_http second_latency
+ local first_reply second_reply
+
+ oc_config=$(openclaw_get_config_file)
+ [ ! -f "$oc_config" ] && {
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="未找到 openclaw 設定文件"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ }
+
+ provider_name="${target_model%%/*}"
+ request_model="${target_model#*/}"
+ base_url=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].baseUrl // empty' "$oc_config" 2>/dev/null)
+ api_key=$(jq -r --arg provider "$provider_name" '.models.providers[$provider].apiKey // empty' "$oc_config" 2>/dev/null)
+ if [ -z "$provider_name" ] || [ -z "$base_url" ] || [ -z "$api_key" ]; then
+ OPENCLAW_PROBE_STATUS="ERROR"
+ OPENCLAW_PROBE_MESSAGE="未讀取到 provider/baseUrl/apiKey"
+ OPENCLAW_PROBE_LATENCY="-"
+ OPENCLAW_PROBE_REPLY="-"
+ return 1
+ fi
+
+ base_url="${base_url%/}"
+ first_endpoint="/responses"
+ second_endpoint="/chat/completions"
+
+ openclaw_extract_probe_reply() {
+ python3 - "$1" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+path = Path(sys.argv[1])
+raw = path.read_text(encoding='utf-8', errors='replace').strip()
+reply = ''
+if raw:
+ try:
+ data = json.loads(raw)
+ if isinstance(data, dict):
+ choices = data.get('choices') or []
+ if choices and isinstance(choices[0], dict):
+ message = choices[0].get('message') or {}
+ if isinstance(message, dict):
+ reply = message.get('content') or ''
+ if not reply:
+ output = data.get('output') or []
+ if isinstance(output, list):
+ texts = []
+ for item in output:
+ if not isinstance(item, dict):
+ continue
+ for content in item.get('content') or []:
+ if not isinstance(content, dict):
+ continue
+ text = content.get('text')
+ if isinstance(text, str) and text.strip():
+ texts.append(text.strip())
+ if texts:
+ break
+ if texts:
+ reply = ' '.join(texts)
+ if not reply:
+ for key in ('error', 'message', 'detail'):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ reply = value.strip()
+ break
+ if isinstance(value, dict):
+ nested = value.get('message')
+ if isinstance(nested, str) and nested.strip():
+ reply = nested.strip()
+ break
+ except Exception:
+ reply = raw
+reply = ' '.join(str(reply).split())
+print(reply)
+PYTHON_EOF
+ }
+
+ openclaw_run_probe() {
+ local endpoint="$1"
+ tmp_payload=$(mktemp)
+ tmp_response=$(mktemp)
+ if [ "$endpoint" = "/responses" ]; then
+ printf '{"model":"%s","input":"hi","temperature":0,"max_output_tokens":16}' "$request_model" > "$tmp_payload"
+ else
+ printf '{"model":"%s","messages":[{"role":"user","content":"hi"}],"temperature":0,"max_tokens":16}' "$request_model" > "$tmp_payload"
+ fi
+
+ probe_result=$(python3 - "$base_url" "$api_key" "$tmp_payload" "$tmp_response" "$probe_timeout" "$endpoint" <<'PYTHON_EOF'
+import sys
+import time
+import urllib.error
+import urllib.request
+
+base_url, api_key, payload_path, response_path, timeout, endpoint = sys.argv[1:7]
+timeout = int(timeout)
+url = base_url + endpoint
+payload = open(payload_path, 'rb').read()
+req = urllib.request.Request(
+ url,
+ data=payload,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'Bearer {api_key}',
+ },
+ method='POST',
+)
+start = time.time()
+body = b''
+status = 0
+exit_code = 0
+try:
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
+ status = getattr(resp, 'status', 200)
+ body = resp.read()
+except urllib.error.HTTPError as e:
+ status = getattr(e, 'code', 0) or 0
+ body = e.read()
+ exit_code = 22
+except Exception as e:
+ body = str(e).encode('utf-8', errors='replace')
+ exit_code = 1
+elapsed = int((time.time() - start) * 1000)
+with open(response_path, 'wb') as f:
+ f.write(body)
+print(f"{exit_code}|{status}|{elapsed}")
+PYTHON_EOF
+)
+ probe_status=$?
+ reply_preview=$(openclaw_extract_probe_reply "$tmp_response")
+ rm -f "$tmp_payload" "$tmp_response"
+ return $probe_status
+ }
+
+ openclaw_run_probe "$first_endpoint"
+ first_exit=${probe_result%%|*}
+ first_http=${probe_result#*|}
+ first_http=${first_http%%|*}
+ first_latency=${probe_result##*|}
+ first_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ if [ "$first_exit" = "0" ] && [ "$first_http" -ge 200 ] && [ "$first_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http}"
+ OPENCLAW_PROBE_LATENCY="${first_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ openclaw_run_probe "$second_endpoint"
+ second_exit=${probe_result%%|*}
+ second_http=${probe_result#*|}
+ second_http=${second_http%%|*}
+ second_latency=${probe_result##*|}
+ second_reply="$reply_preview"
+
+ reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ if [ "$second_exit" = "0" ] && [ "$second_http" -ge 200 ] && [ "$second_http" -lt 300 ]; then
+ OPENCLAW_PROBE_STATUS="OK"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint}-> HTTP ${first_http:-0},切換${second_endpoint} -> HTTP ${second_http}"
+ OPENCLAW_PROBE_LATENCY="${second_latency}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 0
+ fi
+
+ reply_trimmed=$(printf '%s' "$first_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed=$(printf '%s' "$second_reply" | cut -c1-120)
+ [ -z "$reply_trimmed" ] && reply_trimmed="(空返回)"
+
+ OPENCLAW_PROBE_STATUS="FAIL"
+ OPENCLAW_PROBE_MESSAGE="${first_endpoint} -> HTTP ${first_http:-0} / exit ${first_exit:-1};${second_endpoint} -> HTTP ${second_http:-0} / exit ${second_exit:-1}"
+ OPENCLAW_PROBE_LATENCY="${first_latency:-?}ms -> ${second_latency:-?}ms"
+ OPENCLAW_PROBE_REPLY="$reply_trimmed"
+ return 1
+ }
+
+ clear
+
+ while true; do
+ local models_raw models_list default_model model_count selected_model confirm_switch
+
+ # 從設定檔讀取模型鍵(不呼叫 openclaw models list)
+ local oc_config
+ oc_config=$(openclaw_get_config_file)
+
+ models_raw=$(jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d')
+ if [ -z "$models_raw" ]; then
+ echo "取得模型清單失敗:設定檔中未找到 agents.defaults.models。"
+ break_end
+ return 1
+ fi
+
+ # 為每個模型加編號,方便快速定位(例如:"(10) or-api/...:free")
+ models_list=$(echo "$models_raw" | awk '{print "(" NR ") " $0}')
+ model_count=$(echo "$models_list" | sed '/^\s*$/d' | wc -l | tr -d ' ')
+
+ # 從設定檔讀取預設模型(更快);失敗再回退到 openclaw 指令
+ default_model=$(jq -r '.agents.defaults.model.primary // empty' "$oc_config" 2>/dev/null)
+ [ -z "$default_model" ] && default_model="(unknown)"
+
+ clear
+
+ install_gum
+ install gum
+
+ # 若 gum 不存在,降級為原始手動輸入流程
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "--- 模型管理 ---"
+ echo "目前可用模型:"
+ jq -r '.agents.defaults.models | if type == "object" then keys[] else .[] end' "$oc_config" 2>/dev/null | sed '/^\s*$/d'
+ echo "----------------"
+ read -e -p "請輸入要設定的模型名稱 (例如 openrouter/openai/gpt-4o)(輸入 0 退出):" selected_model
+
+ if [ "$selected_model" = "0" ]; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+
+ if [ -z "$selected_model" ]; then
+ echo "錯誤:模型名稱不能為空。請重試。"
+ echo ""
+ continue
+ fi
+
+ echo "正在切換模型為:$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切換失敗:openclaw models set 回傳錯誤。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ return 0
+ else
+ if ! command -v gum >/dev/null 2>&1 || ! gum --version >/dev/null 2>&1; then
+ echo "gum 不可用,傳回舊版輸入模式。"
+ sleep 1
+ continue
+ fi
+ gum style --foreground "$orange" --bold "模型管理"
+ gum style --foreground "$orange" "可用模型(Auth=yes):${model_count}"
+ gum style --foreground "$orange" "目前預設:${default_model}"
+ echo ""
+ gum style --faint "↑↓ 選擇 / Enter 測試 / Esc 退出"
+ echo ""
+
+ selected_model=$(echo "$models_list" | gum filter --placeholder "搜尋模型(如 cli-api/gpt-5.2)" --prompt "選擇模型 >" --indicator "➜ " --prompt.foreground "$orange" --indicator.foreground "$orange" --cursor-text.foreground "$orange" --match.foreground "$orange" --header "" --height 35)
+
+ if [ -z "$selected_model" ] || echo "$selected_model" | head -n 1 | grep -iqE '^(error|usage|gum:)'; then
+ echo "操作已取消,正在退出..."
+ break
+ fi
+ fi
+
+ selected_model=$(echo "$selected_model" | sed -E 's/^\([0-9]+\)[[:space:]]+//')
+
+ echo ""
+ echo "正在檢測模型:$selected_model"
+ if openclaw_model_probe "$selected_model"; then
+ openclaw_probe_status_line "可用"
+ else
+ openclaw_probe_status_line "不可用"
+ fi
+ echo "狀態:$OPENCLAW_PROBE_MESSAGE"
+ echo "延遲:$OPENCLAW_PROBE_LATENCY"
+ echo "摘要:$OPENCLAW_PROBE_REPLY"
+ echo ""
+
+ printf "是否切換到該模型? [y/N,Esc 返回列表]:"
+ IFS= read -rsn1 confirm_switch
+ echo ""
+ if [ "$confirm_switch" = $'' ]; then
+ confirm_switch="no"
+ else
+ case "$confirm_switch" in
+ [yY])
+ IFS= read -rsn1 -t 5 _enter_key
+ confirm_switch="yes"
+ ;;
+ [nN]|"") confirm_switch="no" ;;
+ *) confirm_switch="no" ;;
+ esac
+ fi
+
+ if [ "$confirm_switch" != "yes" ]; then
+ echo "已返回模型選擇清單。"
+ sleep 1
+ continue
+ fi
+
+ echo "正在切換模型為:$selected_model ..."
+ if ! openclaw models set "$selected_model"; then
+ echo "切換失敗:openclaw models set 回傳錯誤。"
+ break_end
+ return 1
+ fi
+ openclaw_sync_sessions_model "$selected_model"
+ start_gateway
+
+ break_end
+ done
+ }
+
+
+ openclaw_get_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_get_agents_dir() {
+ local user_agents="${HOME}/.openclaw/agents"
+ local root_agents="/root/.openclaw/agents"
+ if [ -d "$user_agents" ]; then
+ echo "$user_agents"
+ elif [ "$HOME" = "/root" ] && [ -d "$root_agents" ]; then
+ echo "$root_agents"
+ else
+ echo "$user_agents"
+ fi
+ }
+
+ openclaw_sync_sessions_model() {
+ local model_ref="$1"
+ [ -z "$model_ref" ] && return 1
+
+ local agents_dir
+ agents_dir=$(openclaw_get_agents_dir)
+ [ ! -d "$agents_dir" ] && return 0
+
+ local provider="${model_ref%%/*}"
+ local model="${model_ref#*/}"
+ [ "$provider" = "$model_ref" ] && { provider=""; model="$model_ref"; }
+
+ local count=0
+ local agent_dir sessions_file backup_file
+
+ for agent_dir in "$agents_dir"/*/; do
+ [ ! -d "$agent_dir" ] && continue
+ sessions_file="$agent_dir/sessions/sessions.json"
+ [ ! -f "$sessions_file" ] && continue
+
+ backup_file="${sessions_file}.bak"
+ cp "$sessions_file" "$backup_file" 2>/dev/null || continue
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if [ -n "$provider" ]; then
+ jq --arg model "$model" --arg provider "$provider" \
+ 'to_entries | map(.value.modelOverride = $model | .value.providerOverride = $provider) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ else
+ jq --arg model "$model" \
+ 'to_entries | map(.value.modelOverride = $model | del(.value.providerOverride)) | from_entries' \
+ "$sessions_file" > "$tmp_json" 2>/dev/null && \
+ mv "$tmp_json" "$sessions_file" && \
+ count=$((count + 1))
+ fi
+ fi
+ done
+
+ [ "$count" -gt 0 ] && echo "✅ 已同步$count個 agent 的會話模型為$model_ref"
+ return 0
+ }
+
+ resolve_openclaw_plugin_id() {
+ local raw_input="$1"
+ local plugin_id="$raw_input"
+
+ plugin_id="${plugin_id#@openclaw/}"
+ if [[ "$plugin_id" == @*/* ]]; then
+ plugin_id="${plugin_id##*/}"
+ fi
+ plugin_id="${plugin_id%%@*}"
+ echo "$plugin_id"
+ }
+
+ sync_openclaw_plugin_allowlist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | if (.plugins.allow | index($pid)) == null then .plugins.allow += [$pid] else . end
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 已同步 plugins.allow 白名單:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+if plugin_id not in a:
+ a.append(plugin_id)
+
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 已同步 plugins.allow 白名單:$plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ 已安裝插件,但同步 plugins.allow 失敗,請手動檢查:$config_file"
+ return 1
+ }
+
+ sync_openclaw_plugin_denylist() {
+ local plugin_id="$1"
+ [ -z "$plugin_id" ] && return 1
+
+ local config_file
+ config_file=$(openclaw_get_config_file)
+
+ mkdir -p "$(dirname "$config_file")"
+ if [ ! -s "$config_file" ]; then
+ echo '{}' > "$config_file"
+ fi
+
+ if command -v jq >/dev/null 2>&1; then
+ local tmp_json
+ tmp_json=$(mktemp)
+ if jq --arg pid "$plugin_id" '
+ .plugins = (if (.plugins | type) == "object" then .plugins else {} end)
+ | .plugins.allow = (if (.plugins.allow | type) == "array" then .plugins.allow else [] end)
+ | .plugins.allow = (.plugins.allow | map(select(. != $pid)))
+ ' "$config_file" > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$config_file"; then
+ echo "✅ 已從 plugins.allow 移除:$plugin_id"
+ return 0
+ fi
+ rm -f "$tmp_json"
+ fi
+
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 - "$config_file" "$plugin_id" <<'PYTHON_EOF'
+import json
+import sys
+from pathlib import Path
+
+config_file = Path(sys.argv[1])
+plugin_id = sys.argv[2]
+
+try:
+ data = json.loads(config_file.read_text(encoding='utf-8')) if config_file.exists() else {}
+ if not isinstance(data, dict):
+ data = {}
+except Exception:
+ data = {}
+
+plugins = data.get('plugins')
+if not isinstance(plugins, dict):
+ plugins = {}
+
+a = plugins.get('allow')
+if not isinstance(a, list):
+ a = []
+
+a = [x for x in a if x != plugin_id]
+plugins['allow'] = a
+data['plugins'] = plugins
+config_file.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding='utf-8')
+PYTHON_EOF
+ then
+ echo "✅ 已從 plugins.allow 移除:$plugin_id"
+ return 0
+ fi
+ fi
+
+ echo "⚠️ plugins.allow 移除失敗,請手動檢查:$config_file"
+ return 1
+ }
+
+
+
+
+
+
+ install_plugin() {
+ send_stats "外掛管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo "外掛程式管理 (安裝/刪除)"
+ echo "========================================"
+ echo "當前插件列表:"
+ openclaw plugins list
+ echo "--------------------------------------------------------"
+ echo "推薦的常用外掛 ID (直接複製括號內的 ID 即可):"
+ echo "--------------------------------------------------------"
+ echo "📱 通訊頻道:"
+ echo "- [feishu] # 飛書/Lark 集成"
+ echo "- [telegram] # Telegram 機器人"
+ echo "- [slack] # Slack 企業通訊"
+ echo " - [msteams] # Microsoft Teams"
+ echo "- [discord] # Discord 社群管理"
+ echo "- [whatsapp] # WhatsApp 自動化"
+ echo ""
+ echo "🧠 記憶與 AI:"
+ echo "- [memory-core] # 基礎記憶 (文件檢索)"
+ echo "- [memory-lancedb] # 增強記憶 (向量資料庫)"
+ echo "- [copilot-proxy] # Copilot 介面轉發"
+ echo ""
+ echo "⚙️ 功能擴充:"
+ echo "- [lobster] # 審批流 (附人工確認)"
+ echo "- [voice-call] # 語音通話能力"
+ echo "- [nostr] # 加密隱私聊天"
+ echo "--------------------------------------------------------"
+
+ echo "1) 安裝/啟用插件"
+ echo "2) 刪除/停用插件"
+ echo "0) 返回"
+ read -e -p "請選擇操作:" plugin_action
+
+ [ "$plugin_action" = "0" ] && break
+ [ -z "$plugin_action" ] && continue
+
+ read -e -p "請輸入插件 ID(空格分隔,輸入 0 退出):" raw_input
+ [ "$raw_input" = "0" ] && break
+ [ -z "$raw_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ for token in $raw_input; do
+ local plugin_id
+ local plugin_full
+ plugin_id=$(resolve_openclaw_plugin_id "$token")
+ plugin_full="$token"
+ [ -z "$plugin_id" ] && continue
+
+ if [ "$plugin_action" = "1" ]; then
+ echo "🔍 正在檢查插件狀態:$plugin_id"
+ local plugin_list
+ plugin_list=$(openclaw plugins list 2>/dev/null)
+
+ if echo "$plugin_list" | grep -qw "$plugin_id" && echo "$plugin_list" | grep "$plugin_id" | grep -q "disabled"; then
+ echo "💡 插件 [$plugin_id] 已預先安裝,正在啟動..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ if [ -d "/usr/lib/node_modules/openclaw/extensions/$plugin_id" ]; then
+ echo "💡 發現系統內建目錄存在該插件,嘗試直接啟用..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ continue
+ fi
+
+ echo "📥 本機未發現,嘗試下載安裝:$plugin_full"
+ rm -rf "${HOME}/.openclaw/extensions/$plugin_id"
+ [ "$HOME" != "/root" ] && rm -rf "/root/.openclaw/extensions/$plugin_id"
+ if openclaw plugins install "$plugin_full"; then
+ echo "✅ 下載成功,正在啟用..."
+ if openclaw plugins enable "$plugin_id"; then
+ sync_openclaw_plugin_allowlist "$plugin_id"
+ success_list="$success_list $plugin_id"
+ changed=true
+ else
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "❌ 安裝失敗:$plugin_full"
+ failed_list="$failed_list $plugin_id"
+ fi
+ else
+ echo "🗑️ 正在删除/禁用插件: $plugin_id"
+ openclaw plugins disable "$plugin_id" >/dev/null 2>&1
+ if openclaw plugins uninstall "$plugin_id"; then
+ echo "✅ 已卸載:$plugin_id"
+ else
+ echo "⚠️ 卸載失敗,可能為預先安裝插件,僅停用:$plugin_id"
+ fi
+ sync_openclaw_plugin_denylist "$plugin_id" >/dev/null 2>&1
+ success_list="$success_list $plugin_id"
+ changed=true
+ fi
+ done
+
+ echo ""
+ echo "====== 操作匯總 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失敗:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 跳過:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 正在重啟 OpenClaw 服務以載入變更..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+
+ install_skill() {
+ send_stats "技能管理"
+ while true; do
+ clear
+ echo "========================================"
+ echo "技能管理 (安裝/刪除)"
+ echo "========================================"
+ echo "目前已安裝技能:"
+ openclaw skills list
+ echo "----------------------------------------"
+
+ # 輸出建議的實用技能列表
+ echo "建議的實用技能(可直接複製名稱輸入):"
+ echo "github # 管理 GitHub Issues/PR/CI (gh CLI)"
+ echo "notion # 操作 Notion 頁面、資料庫和區塊"
+ echo "apple-notes # macOS 原生筆記管理 (建立/編輯/搜尋)"
+ echo "apple-reminders # macOS 提醒事項管理 (待辦事項清單)"
+ echo "1password # 自動化讀取和注入 1Password 金鑰"
+ echo "gog # Google Workspace (Gmail/雲端盤/文件) 全能助手"
+ echo "things-mac # 深度整合 Things 3 任務管理"
+ echo "bluebubbles # 透過 BlueBubbles 完美收發 iMessage"
+ echo "himalaya # 終端郵件管理 (IMAP/SMTP 強力工具)"
+ echo "summarize # 網頁/播客/YouTube 影片內容一鍵總結"
+ echo "openhue # 控制 Philips Hue 智慧燈光場景"
+ echo "video-frames # 視訊抽幀與短片剪輯 (ffmpeg 驅動)"
+ echo "openai-whisper # 本地音訊轉文字 (離線隱私保護)"
+ echo "coding-agent # 自動運行 Claude Code/Codex 等程式設計助手"
+ echo "----------------------------------------"
+
+ echo "1) 安裝技能"
+ echo "2) 刪除技能"
+ echo "0) 返回"
+ read -e -p "請選擇操作:" skill_action
+
+ [ "$skill_action" = "0" ] && break
+ [ -z "$skill_action" ] && continue
+
+ read -e -p "請輸入技能名稱(空格分隔,輸入 0 退出):" skill_input
+ [ "$skill_input" = "0" ] && break
+ [ -z "$skill_input" ] && continue
+
+ local success_list=""
+ local failed_list=""
+ local skipped_list=""
+ local changed=false
+ local token
+
+ if [ "$skill_action" = "2" ]; then
+ read -e -p "二次確認:刪除僅影響使用者目錄 ~/.openclaw/workspace/skills,確認繼續? (y/N):" confirm_del
+ if [[ ! "$confirm_del" =~ ^[Yy]$ ]]; then
+ echo "已取消刪除。"
+ break_end
+ continue
+ fi
+ fi
+
+ for token in $skill_input; do
+ local skill_name
+ skill_name="$token"
+ [ -z "$skill_name" ] && continue
+
+ if [ "$skill_action" = "1" ]; then
+ local skill_found=false
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ echo "💡 技能 [$skill_name] 已在使用者目錄安裝。"
+ skill_found=true
+ elif [ -d "/usr/lib/node_modules/openclaw/skills/${skill_name}" ]; then
+ echo "💡 技能 [$skill_name] 已在系統目錄安裝。"
+ skill_found=true
+ fi
+
+ if [ "$skill_found" = true ]; then
+ read -e -p "技能 [$skill_name] 已安裝,是否重新安裝? (y/N):" reinstall
+ if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
+ skipped_list="$skipped_list $skill_name"
+ continue
+ fi
+ fi
+
+ echo "正在安裝技能:$skill_name ..."
+ if npx clawhub install "$skill_name" --yes --no-input 2>/dev/null || npx clawhub install "$skill_name"; then
+ echo "✅ 技能$skill_name安裝成功。"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "❌ 安裝失敗:$skill_name"
+ failed_list="$failed_list $skill_name"
+ fi
+ else
+ echo "🗑️ 正在刪除技能:$skill_name"
+ npx clawhub uninstall "$skill_name" --yes --no-input 2>/dev/null || npx clawhub uninstall "$skill_name" >/dev/null 2>&1
+ if [ -d "${HOME}/.openclaw/workspace/skills/${skill_name}" ]; then
+ rm -rf "${HOME}/.openclaw/workspace/skills/${skill_name}"
+ echo "✅ 已刪除使用者技能目錄:$skill_name"
+ success_list="$success_list $skill_name"
+ changed=true
+ else
+ echo "⏭️ 未發現使用者技能目錄:$skill_name"
+ skipped_list="$skipped_list $skill_name"
+ fi
+ fi
+ done
+
+ echo ""
+ echo "====== 操作匯總 ======"
+ echo "✅ 成功:$success_list"
+ [ -n "$failed_list" ] && echo "❌ 失敗:$failed_list"
+ [ -n "$skipped_list" ] && echo "⏭️ 跳過:$skipped_list"
+
+ if [ "$changed" = true ]; then
+ echo "🔄 正在重啟 OpenClaw 服務以載入變更..."
+ start_gateway
+ fi
+ break_end
+ done
+ }
+
+openclaw_json_get_bool() {
+ local expr="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r "$expr" "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_channel_has_cfg() {
+ local channel="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ ! -s "$config_file" ]; then
+ echo "false"
+ return
+ fi
+ jq -r --arg c "$channel" '
+ (.channels[$c] // null) as $v
+ | if ($v | type) != "object" then
+ false
+ else
+ ([ $v
+ | to_entries[]
+ | select((.key == "enabled" or .key == "dmPolicy" or .key == "groupPolicy" or .key == "streaming") | not)
+ | .value
+ | select(. != null and . != "" and . != false)
+ ] | length) > 0
+ end
+ ' "$config_file" 2>/dev/null || echo "false"
+ }
+
+ openclaw_dir_has_files() {
+ local dir="$1"
+ [ -d "$dir" ] && find "$dir" -type f -print -quit 2>/dev/null | grep -q .
+ }
+
+ openclaw_plugin_local_installed() {
+ local plugin="$1"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -s "$config_file" ] && jq -e --arg p "$plugin" '.plugins.installs[$p]' "$config_file" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ # 相容於兩種常見目錄命名:
+ # - ~/.openclaw/extensions/qqbot
+ # - ~/.openclaw/extensions/openclaw-qqbot
+ # 避免無腦 substring,優先精確匹配與 openclaw- 前綴相符。
+ [ -d "${HOME}/.openclaw/extensions/${plugin}" ] \
+ || [ -d "${HOME}/.openclaw/extensions/openclaw-${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/${plugin}" ] \
+ || [ -d "/usr/lib/node_modules/openclaw/extensions/openclaw-${plugin}" ]
+ }
+
+ openclaw_bot_status_text() {
+ local enabled="$1"
+ local configured="$2"
+ local connected="$3"
+ local abnormal="$4"
+ if [ "$abnormal" = "true" ]; then
+ echo "例外"
+ elif [ "$enabled" != "true" ]; then
+ echo "未啟用"
+ elif [ "$connected" = "true" ]; then
+ echo "已連接"
+ elif [ "$configured" = "true" ]; then
+ echo "已配置"
+ else
+ echo "未配置"
+ fi
+ }
+
+ openclaw_colorize_bot_status() {
+ local status="$1"
+ case "$status" in
+ 已连接) echo -e "${gl_lv}${status}${gl_bai}" ;;
+ 已配置) echo -e "${gl_huang}${status}${gl_bai}" ;;
+ 异常) echo -e "${gl_hong}${status}${gl_bai}" ;;
+ *) echo "$status" ;;
+ esac
+ }
+
+ openclaw_print_bot_status_line() {
+ local label="$1"
+ local status="$2"
+ echo -e "- ${label}: $(openclaw_colorize_bot_status "$status")"
+ }
+
+ openclaw_show_bot_local_status_block() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local json_ok="false"
+ if [ -s "$config_file" ] && jq empty "$config_file" >/dev/null 2>&1; then
+ json_ok="true"
+ fi
+
+ local tg_enabled tg_cfg tg_connected tg_abnormal tg_status
+ tg_enabled=$(openclaw_json_get_bool '.channels.telegram.enabled // .plugins.entries.telegram.enabled // false')
+ tg_cfg=$(openclaw_channel_has_cfg "telegram")
+ tg_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/telegram"; then
+ tg_connected="true"
+ fi
+ tg_abnormal="false"
+ if [ "$tg_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ tg_abnormal="true"
+ fi
+ tg_status=$(openclaw_bot_status_text "$tg_enabled" "$tg_cfg" "$tg_connected" "$tg_abnormal")
+
+ local feishu_enabled feishu_cfg feishu_connected feishu_abnormal feishu_status
+ feishu_enabled=$(openclaw_json_get_bool '.plugins.entries.feishu.enabled // .plugins.entries["openclaw-lark"].enabled // .channels.feishu.enabled // .channels.lark.enabled // false')
+ feishu_cfg=$(openclaw_channel_has_cfg "feishu")
+ if [ "$feishu_cfg" != "true" ]; then
+ feishu_cfg=$(openclaw_channel_has_cfg "lark")
+ fi
+ feishu_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/feishu" || openclaw_dir_has_files "${HOME}/.openclaw/lark" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-lark"; then
+ feishu_connected="true"
+ fi
+ feishu_abnormal="false"
+ if [ "$feishu_enabled" = "true" ] && ! openclaw_plugin_local_installed "feishu" && ! openclaw_plugin_local_installed "lark" && ! openclaw_plugin_local_installed "openclaw-lark"; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ feishu_abnormal="true"
+ fi
+ if [ "$feishu_connected" != "true" ] && [ "$feishu_enabled" = "true" ] && [ "$feishu_cfg" = "true" ] && { openclaw_plugin_local_installed "feishu" || openclaw_plugin_local_installed "lark" || openclaw_plugin_local_installed "openclaw-lark"; }; then
+ feishu_connected="true"
+ fi
+ feishu_status=$(openclaw_bot_status_text "$feishu_enabled" "$feishu_cfg" "$feishu_connected" "$feishu_abnormal")
+
+ local wa_enabled wa_cfg wa_connected wa_abnormal wa_status
+ wa_enabled=$(openclaw_json_get_bool '.plugins.entries.whatsapp.enabled // .channels.whatsapp.enabled // false')
+ wa_cfg=$(openclaw_channel_has_cfg "whatsapp")
+ wa_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/whatsapp"; then
+ wa_connected="true"
+ fi
+ wa_abnormal="false"
+ if [ "$wa_enabled" = "true" ] && ! openclaw_plugin_local_installed "whatsapp"; then
+ wa_abnormal="true"
+ fi
+ if [ "$wa_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wa_abnormal="true"
+ fi
+ wa_status=$(openclaw_bot_status_text "$wa_enabled" "$wa_cfg" "$wa_connected" "$wa_abnormal")
+
+ local dc_enabled dc_cfg dc_connected dc_abnormal dc_status
+ dc_enabled=$(openclaw_json_get_bool '.channels.discord.enabled // .plugins.entries.discord.enabled // false')
+ dc_cfg=$(openclaw_channel_has_cfg "discord")
+ dc_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/discord"; then
+ dc_connected="true"
+ fi
+ dc_abnormal="false"
+ if [ "$dc_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ dc_abnormal="true"
+ fi
+ dc_status=$(openclaw_bot_status_text "$dc_enabled" "$dc_cfg" "$dc_connected" "$dc_abnormal")
+
+ local slack_enabled slack_cfg slack_connected slack_abnormal slack_status
+ slack_enabled=$(openclaw_json_get_bool '.plugins.entries.slack.enabled // .channels.slack.enabled // false')
+ slack_cfg=$(openclaw_channel_has_cfg "slack")
+ slack_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/slack"; then
+ slack_connected="true"
+ fi
+ slack_abnormal="false"
+ if [ "$slack_enabled" = "true" ] && ! openclaw_plugin_local_installed "slack"; then
+ slack_abnormal="true"
+ fi
+ if [ "$slack_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ slack_abnormal="true"
+ fi
+ slack_status=$(openclaw_bot_status_text "$slack_enabled" "$slack_cfg" "$slack_connected" "$slack_abnormal")
+
+ local qq_enabled qq_cfg qq_connected qq_abnormal qq_status
+ qq_enabled=$(openclaw_json_get_bool '.plugins.entries.qqbot.enabled // .channels.qqbot.enabled // false')
+ qq_cfg=$(openclaw_channel_has_cfg "qqbot")
+ qq_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/qqbot/sessions" || openclaw_dir_has_files "${HOME}/.openclaw/qqbot/data"; then
+ qq_connected="true"
+ fi
+ qq_abnormal="false"
+ if [ "$qq_enabled" = "true" ] && ! openclaw_plugin_local_installed "qqbot"; then
+ qq_abnormal="true"
+ fi
+ if [ "$qq_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ qq_abnormal="true"
+ fi
+ qq_status=$(openclaw_bot_status_text "$qq_enabled" "$qq_cfg" "$qq_connected" "$qq_abnormal")
+
+ local wx_enabled wx_cfg wx_connected wx_abnormal wx_status
+ wx_enabled=$(openclaw_json_get_bool '.plugins.entries.weixin.enabled // .plugins.entries["openclaw-weixin"].enabled // .channels.weixin.enabled // .channels["openclaw-weixin"].enabled // false')
+ wx_cfg=$(openclaw_channel_has_cfg "weixin")
+ if [ "$wx_cfg" != "true" ]; then
+ wx_cfg=$(openclaw_channel_has_cfg "openclaw-weixin")
+ fi
+ wx_connected="false"
+ if openclaw_dir_has_files "${HOME}/.openclaw/weixin" || openclaw_dir_has_files "${HOME}/.openclaw/openclaw-weixin"; then
+ wx_connected="true"
+ fi
+ wx_abnormal="false"
+ if [ "$wx_enabled" = "true" ] && ! openclaw_plugin_local_installed "weixin" && ! openclaw_plugin_local_installed "openclaw-weixin"; then
+ wx_abnormal="true"
+ fi
+ if [ "$wx_enabled" = "true" ] && [ "$json_ok" != "true" ]; then
+ wx_abnormal="true"
+ fi
+ wx_status=$(openclaw_bot_status_text "$wx_enabled" "$wx_cfg" "$wx_connected" "$wx_abnormal")
+
+ echo "本機狀態(僅本機配置/緩存,不做網路探測):"
+ openclaw_print_bot_status_line "Telegram" "$tg_status"
+ openclaw_print_bot_status_line "飛書(Lark)" "$feishu_status"
+ openclaw_print_bot_status_line "WhatsApp" "$wa_status"
+ openclaw_print_bot_status_line "Discord" "$dc_status"
+ openclaw_print_bot_status_line "Slack" "$slack_status"
+ openclaw_print_bot_status_line "QQ Bot" "$qq_status"
+ openclaw_print_bot_status_line "微信 (Weixin)" "$wx_status"
+ }
+
+ change_tg_bot_code() {
+ send_stats "機器人對接"
+ while true; do
+ clear
+ echo "========================================"
+ echo "機器人連線對接"
+ echo "========================================"
+ openclaw_show_bot_local_status_block
+ echo "----------------------------------------"
+ echo "1. Telegram 機器人對接"
+ echo "2. 飛書 (Lark) 機器人對接"
+ echo "3. WhatsApp 機器人對接"
+ echo "4. QQ 機器人對接"
+ echo "5. 微信機器人對接"
+ echo "----------------------------------------"
+ echo "0. 返回上一級選單"
+ echo "----------------------------------------"
+ read -e -p "請輸入你的選擇:" bot_choice
+
+ case $bot_choice in
+ 1)
+ read -e -p "請輸入TG機器人收到的連線碼 (例如 NYA99R2F)(輸入 0 退出):" code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "錯誤:連接碼不能為空。"; sleep 1; continue; fi
+ openclaw pairing approve telegram "$code"
+ break_end
+ ;;
+ 2)
+ npx -y @larksuite/openclaw-lark install
+ openclaw config set channels.feishu.streaming true
+ openclaw config set channels.feishu.requireMention true --json
+ break_end
+ ;;
+ 3)
+ read -e -p "請輸入WhatsApp收到的連接碼 (例如 NYA99R2F)(輸入 0 退出):" code
+ if [ "$code" = "0" ]; then continue; fi
+ if [ -z "$code" ]; then echo "錯誤:連接碼不能為空。"; sleep 1; continue; fi
+ openclaw pairing approve whatsapp "$code"
+ break_end
+ ;;
+ 4)
+ echo "QQ 官方對接位址:"
+ echo "https://q.qq.com/qqbot/openclaw/login.html"
+ break_end
+ ;;
+ 5)
+ npx -y @tencent-weixin/openclaw-weixin-cli@latest install
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "無效的選擇,請重試。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ openclaw_backup_root() {
+ echo "${HOME}/.openclaw/backups"
+ }
+
+ openclaw_is_interactive_terminal() {
+ [ -t 0 ] && [ -t 1 ]
+ }
+
+ openclaw_has_command() {
+ command -v "$1" >/dev/null 2>&1
+ }
+
+
+ openclaw_is_safe_relpath() {
+ local rel="$1"
+ [ -z "$rel" ] && return 1
+ [[ "$rel" = /* ]] && return 1
+ [[ "$rel" == *"//"* ]] && return 1
+ [[ "$rel" == *$'\n'* ]] && return 1
+ [[ "$rel" == *$'\r'* ]] && return 1
+ case "$rel" in
+ ../*|*/../*|*/..|..)
+ return 1
+ ;;
+ esac
+ return 0
+ }
+
+ openclaw_restore_path_allowed() {
+ local mode="$1"
+ local rel="$2"
+ case "$mode" in
+ memory)
+ case "$rel" in
+ MEMORY.md|AGENTS.md|USER.md|SOUL.md|TOOLS.md|memory/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ project)
+ case "$rel" in
+ openclaw.json|workspace/*|extensions/*|skills/*|prompts/*|tools/*|telegram/*|feishu/*|whatsapp/*|discord/*|slack/*|qqbot/*|logs/*) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ }
+
+ openclaw_pack_backup_archive() {
+ local backup_type="$1"
+ local export_mode="$2"
+ local payload_dir="$3"
+ local output_file="$4"
+
+ local tmp_root
+ tmp_root=$(mktemp -d) || return 1
+ local pack_dir="$tmp_root/package"
+ mkdir -p "$pack_dir"
+
+ cp -a "$payload_dir" "$pack_dir/payload"
+
+ (
+ cd "$pack_dir/payload" || exit 1
+ find . -type f | sed 's|^\./||' | sort > "$pack_dir/manifest.files"
+ : > "$pack_dir/manifest.sha256"
+ while IFS= read -r f; do
+ [ -z "$f" ] && continue
+ sha256sum "$f" >> "$pack_dir/manifest.sha256"
+ done < "$pack_dir/manifest.files"
+ ) || { rm -rf "$tmp_root"; return 1; }
+
+ cat > "$pack_dir/backup.meta" </dev/null
+ ) || { echo "❌ sha256 校驗失敗,拒絕還原"; return 1; }
+
+ echo "$pkg_dir"
+ return 0
+ }
+
+ openclaw_get_all_agent_workspaces() {
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json, sys, os
+try:
+ with open(sys.argv[1]) as f: data = json.load(f)
+ agents = data.get("agents", {}).get("list", [])
+ results = [{"id": "main", "ws": os.path.expanduser("~/.openclaw/workspace")}]
+ for a in agents:
+ aid = a.get("id"); ws = a.get("workspace")
+ if aid and ws and aid != "main": results.append({"id": aid, "ws": os.path.expanduser(ws)})
+ print(json.dumps(results))
+except: print("[]")
+PY
+ else
+ echo '[{"id": "main", "ws": "'"${HOME}"'/.openclaw/workspace"}]'
+ fi
+ }
+
+ openclaw_memory_backup_export() {
+ send_stats "OpenClaw記憶全量備份"
+ local backup_root=$(openclaw_backup_root)
+ local ts=$(date +%Y%m%d-%H%M%S)
+ local out_file="$backup_root/openclaw-memory-full-${ts}.tar.gz"
+ mkdir -p "$backup_root"
+ local tmp_payload=$(mktemp -d) || return 1
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c "import json, sys, os, shutil;
+workspaces = json.loads(sys.argv[1]); tmp_payload = sys.argv[2]
+for item in workspaces:
+ aid = item['id']; ws = item['ws']
+ if not os.path.isdir(ws): continue
+ target_dir = os.path.join(tmp_payload, 'agents', aid)
+ os.makedirs(target_dir, exist_ok=True)
+ for f in ['MEMORY.md', 'memory']:
+ src = os.path.join(ws, f)
+ if os.path.exists(src):
+ if os.path.isfile(src): shutil.copy2(src, target_dir)
+ else: shutil.copytree(src, os.path.join(target_dir, f), dirs_exist_ok=True)
+" "$workspaces_json" "$tmp_payload"
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 未找到可備份的記憶文件"; rm -rf "$tmp_payload"; break_end; return 1
+ fi
+ if openclaw_pack_backup_archive "memory-full" "multi-agent" "$tmp_payload" "$out_file"; then
+ echo "✅ 記憶全量備份完成 (含多智能體):$out_file"; openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ 記憶全量備份失敗"
+ fi
+ rm -rf "$tmp_payload"; break_end
+ }
+
+ openclaw_memory_backup_import() {
+ send_stats "OpenClaw記憶全量還原"
+ local archive_path=$(openclaw_read_import_path "還原記憶全量 (支援多智能體)")
+ [ -z "$archive_path" ] && { echo "❌ 未輸入路徑"; break_end; return 1; }
+ local tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir=$(openclaw_prepare_import_archive "memory-full" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+ local workspaces_json=$(openclaw_get_all_agent_workspaces)
+ python3 -c 'import json, sys, os, shutil;
+workspaces = {item["id"]: item["ws"] for item in json.loads(sys.argv[1])};
+payload_dir = sys.argv[2]; agents_root = os.path.join(payload_dir, "agents")
+if os.path.isdir(agents_root):
+ for aid in os.listdir(agents_root):
+ if aid in workspaces:
+ src_agent_dir = os.path.join(agents_root, aid); dest_ws = workspaces[aid]
+ os.makedirs(dest_ws, exist_ok=True)
+ for f in os.listdir(src_agent_dir):
+ src = os.path.join(src_agent_dir, f); dest = os.path.join(dest_ws, f)
+ if os.path.isfile(src): shutil.copy2(src, dest)
+ else: shutil.copytree(src, dest, dirs_exist_ok=True)
+ print(f"✅ 已還原智能體記憶: {aid}")' "$workspaces_json" "$pkg_dir/payload"
+ rm -rf "$tmp_unpack"; echo "✅ 記憶全量還原完成"; break_end
+ }
+
+
+ openclaw_project_backup_export() {
+ send_stats "OpenClaw專案備份"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ if [ ! -d "$openclaw_root" ]; then
+ echo "❌ 未找到 OpenClaw 根目錄:$openclaw_root"
+ break_end
+ return 1
+ fi
+
+ echo "備份模式:"
+ echo "1. 安全模式(默認,建議):workspace + openclaw.json + extensions/skills/prompts/tools(如存在)"
+ echo "2. 完整模式(含更多狀態,敏感風險較高)"
+ read -e -p "請選擇備份模式(預設 1):" export_mode
+ [ -z "$export_mode" ] && export_mode="1"
+
+ local mode_label="safe"
+ local tmp_payload
+ tmp_payload=$(mktemp -d) || return 1
+
+ if [ "$export_mode" = "2" ]; then
+ mode_label="full"
+ for d in workspace extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in telegram feishu whatsapp discord slack qqbot logs; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ else
+ [ -d "$openclaw_root/workspace" ] && cp -a "$openclaw_root/workspace" "$tmp_payload/"
+ [ -f "$openclaw_root/openclaw.json" ] && cp -a "$openclaw_root/openclaw.json" "$tmp_payload/"
+ for d in extensions skills prompts tools; do
+ [ -e "$openclaw_root/$d" ] && cp -a "$openclaw_root/$d" "$tmp_payload/"
+ done
+ fi
+
+ if ! find "$tmp_payload" -mindepth 1 -print -quit | grep -q .; then
+ echo "❌ 未找到可備份的 OpenClaw 專案內容"
+ rm -rf "$tmp_payload"
+ break_end
+ return 1
+ fi
+
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ local out_file="$backup_root/openclaw-project-${mode_label}-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ if openclaw_pack_backup_archive "openclaw-project" "$mode_label" "$tmp_payload" "$out_file"; then
+ echo "✅ OpenClaw 專案備份完成 (${mode_label}): $out_file"
+ openclaw_offer_transfer_hint "$out_file"
+ else
+ echo "❌ OpenClaw 專案備份失敗"
+ fi
+
+ rm -rf "$tmp_payload"
+ break_end
+ }
+
+ openclaw_project_backup_import() {
+ send_stats "OpenClaw專案還原"
+ local config_file
+ config_file=$(openclaw_get_config_file)
+ local openclaw_root
+ openclaw_root=$(dirname "$config_file")
+ mkdir -p "$openclaw_root"
+
+ echo "⚠️ 高風險操作:專案還原會涵蓋 OpenClaw 設定與工作區內容。"
+ echo "⚠️ 還原前將執行 manifest/sha256 校驗、白名單恢復、gateway 停啟與健康檢查。"
+ read -e -p "請輸入確認詞【我已知高風險並繼續還原】後繼續:" confirm_text
+ if [ "$confirm_text" != "我已知高風險並持續還原" ]; then
+ echo "❌ 確認詞不匹配,已取消還原"
+ break_end
+ return 1
+ fi
+
+ local archive_path
+ archive_path=$(openclaw_read_import_path "請輸入 OpenClaw 專案備份包路徑")
+ [ -z "$archive_path" ] && { echo "❌ 未輸入備份路徑"; break_end; return 1; }
+
+ local tmp_unpack
+ tmp_unpack=$(mktemp -d) || return 1
+ local pkg_dir
+ pkg_dir=$(openclaw_prepare_import_archive "openclaw-project" "$archive_path" "$tmp_unpack") || { rm -rf "$tmp_unpack"; break_end; return 1; }
+
+ local invalid=0
+ local valid_list
+ valid_list=$(mktemp)
+ while IFS= read -r rel; do
+ [ -z "$rel" ] && continue
+ if ! openclaw_is_safe_relpath "$rel" || ! openclaw_restore_path_allowed project "$rel"; then
+ echo "❌ 偵測到非法或越權路徑:$rel"
+ invalid=1
+ break
+ fi
+ echo "$rel" >> "$valid_list"
+ done < "$pkg_dir/manifest.files"
+
+ if [ "$invalid" -ne 0 ]; then
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "❌ 還原中止:存在不安全路徑"
+ break_end
+ return 1
+ fi
+
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "⏸️ 還原前停止 OpenClaw gateway..."
+ openclaw gateway stop >/dev/null 2>&1
+ fi
+
+ while IFS= read -r rel; do
+ mkdir -p "$openclaw_root/$(dirname "$rel")"
+ cp -a "$pkg_dir/payload/$rel" "$openclaw_root/$rel"
+ done < "$valid_list"
+
+ if command -v openclaw >/dev/null 2>&1; then
+ echo "▶️ 還原後啟動 OpenClaw gateway..."
+ openclaw gateway start >/dev/null 2>&1
+ sleep 2
+ echo "🩺 gateway 健康檢查:"
+ openclaw gateway status || true
+ fi
+
+ rm -f "$valid_list"
+ rm -rf "$tmp_unpack"
+ echo "✅ OpenClaw 專案還原完成"
+ break_end
+ }
+
+ openclaw_backup_detect_type() {
+ local file_name="$1"
+ if [[ "$file_name" == openclaw-memory-full-*.tar.gz ]]; then
+ echo "記憶備份文件"
+ elif [[ "$file_name" == openclaw-project-*.tar.gz ]]; then
+ echo "專案備份文件"
+ else
+ echo "其他備份文件"
+ fi
+ }
+
+ openclaw_backup_collect_files() {
+ local backup_root
+ backup_root=$(openclaw_backup_root)
+ mkdir -p "$backup_root"
+ mapfile -t OPENCLAW_BACKUP_FILES < <(find "$backup_root" -maxdepth 1 -type f -name '*.tar.gz' -printf '%f\n' | sort -r)
+ }
+
+
+ openclaw_backup_render_file_list() {
+ local backup_root i file_name file_path file_type file_size file_time
+ local has_memory=0 has_project=0 has_other=0
+ backup_root=$(openclaw_backup_root)
+ openclaw_backup_collect_files
+
+ echo "備份目錄:$backup_root"
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ echo "暫無備份文件"
+ return 0
+ fi
+
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_type=$(openclaw_backup_detect_type "${OPENCLAW_BACKUP_FILES[$i]}")
+ case "$file_type" in
+ "記憶備份文件") has_memory=1 ;;
+ "專案備份文件") has_project=1 ;;
+ "其他備份文件") has_other=1 ;;
+ esac
+ done
+
+ if [ "$has_memory" -eq 1 ]; then
+ echo "記憶備份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "記憶備份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_project" -eq 1 ]; then
+ echo "專案備份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "專案備份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+
+ if [ "$has_other" -eq 1 ]; then
+ echo "其他備份文件"
+ for i in "${!OPENCLAW_BACKUP_FILES[@]}"; do
+ file_name="${OPENCLAW_BACKUP_FILES[$i]}"
+ file_type=$(openclaw_backup_detect_type "$file_name")
+ [ "$file_type" != "其他備份文件" ] && continue
+ file_path="$backup_root/$file_name"
+ file_size=$(ls -lh "$file_path" | awk '{print $5}')
+ file_time=$(date -d "$(stat -c %y "$file_path")" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file_path" | awk '{print $1" "$2}')
+ printf "%s | %s | %s\n" "$file_name" "$file_size" "$file_time"
+ done
+ fi
+ }
+
+ openclaw_backup_file_exists_in_list() {
+ local target_file="$1"
+ local item
+ for item in "${OPENCLAW_BACKUP_FILES[@]}"; do
+ [ "$item" = "$target_file" ] && return 0
+ done
+ return 1
+ }
+
+ openclaw_backup_delete_file() {
+ send_stats "OpenClaw刪除備份文件"
+ local backup_root backup_root_real user_input target_file target_path target_type
+ backup_root=$(openclaw_backup_root)
+
+ openclaw_backup_render_file_list
+ if [ ${#OPENCLAW_BACKUP_FILES[@]} -eq 0 ]; then
+ break_end
+ return 0
+ fi
+
+ read -e -p "請輸入要刪除的檔案名稱或完整路徑(0 取消):" user_input
+ if [ "$user_input" = "0" ]; then
+ echo "已取消刪除。"
+ break_end
+ return 0
+ fi
+ if [ -z "$user_input" ]; then
+ echo "❌ 輸入不能為空。"
+ break_end
+ return 1
+ fi
+
+ backup_root_real=$(realpath -m "$backup_root")
+ if [[ "$user_input" == /* ]]; then
+ target_path=$(realpath -m "$user_input")
+ case "$target_path" in
+ "$backup_root_real"/*) ;;
+ *)
+ echo "❌ 路徑越界:僅允許刪除備份根目錄內的檔案。"
+ break_end
+ return 1
+ ;;
+ esac
+ target_file=$(basename "$target_path")
+ else
+ target_file=$(basename -- "$user_input")
+ target_path="$backup_root/$target_file"
+ fi
+
+ if [ ! -f "$target_path" ]; then
+ echo "❌ 目標檔案不存在:$target_path"
+ break_end
+ return 1
+ fi
+
+ if ! openclaw_backup_file_exists_in_list "$target_file"; then
+ echo "❌ 目標檔案不在目前備份清單中。"
+ break_end
+ return 1
+ fi
+
+ target_type=$(openclaw_backup_detect_type "$target_file")
+
+ echo "即將刪除: [$target_type] $target_path"
+ read -e -p "第一次確認:輸入 yes 確認繼續:" confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "已取消刪除。"
+ break_end
+ return 0
+ fi
+ read -e -p "二次確認:輸入 DELETE 執行刪除:" confirm_step2
+ if [ "$confirm_step2" != "DELETE" ]; then
+ echo "已取消刪除。"
+ break_end
+ return 0
+ fi
+
+ if rm -f -- "$target_path"; then
+ echo "✅ 刪除成功:$target_file"
+ else
+ echo "❌ 刪除失敗:$target_file"
+ fi
+ break_end
+ }
+
+ openclaw_backup_list_files() {
+ openclaw_backup_render_file_list
+ break_end
+ }
+
+ openclaw_memory_config_file() {
+ local user_config="${HOME}/.openclaw/openclaw.json"
+ local root_config="/root/.openclaw/openclaw.json"
+ if [ -f "$user_config" ]; then
+ echo "$user_config"
+ elif [ "$HOME" = "/root" ] && [ -f "$root_config" ]; then
+ echo "$root_config"
+ else
+ echo "$user_config"
+ fi
+ }
+
+ openclaw_memory_config_get() {
+ local key="$1"
+ local default_value="${2:-}"
+ local value
+ value=$(openclaw config get "$key" 2>/dev/null | head -n 1 | sed -e 's/^"//' -e 's/"$//')
+ if [ -z "$value" ] || [ "$value" = "null" ] || [ "$value" = "undefined" ]; then
+ echo "$default_value"
+ return 0
+ fi
+ echo "$value"
+ }
+
+ openclaw_memory_config_set() {
+ local key="$1"
+ shift
+ openclaw config set "$key" "$@" >/dev/null 2>&1
+ }
+
+ openclaw_memory_config_unset() {
+ local key="$1"
+ openclaw config unset "$key" >/dev/null 2>&1
+ }
+
+ openclaw_memory_cleanup_legacy_keys() {
+ openclaw_memory_config_unset "memory.local"
+ }
+
+ openclaw_memory_list_agents() {
+ if command -v openclaw >/dev/null 2>&1; then
+ local agents_json
+ agents_json=$(openclaw agents list --json 2>/dev/null || true)
+ if [ -n "$agents_json" ]; then
+ python3 - "$agents_json" <<'PY'
+import json, os, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ data = None
+seen = set()
+results = []
+if isinstance(data, list):
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ if not aid or aid in seen:
+ continue
+ ws = item.get('workspace') or ("~/.openclaw/workspace" if aid == 'main' else f"~/.openclaw/workspace-{aid}")
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+if results:
+ for aid, ws in results:
+ print(f"{aid}\t{ws}")
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+ [ $? -eq 0 ] && return 0
+ fi
+ fi
+ local config_path
+ config_path=$(openclaw_memory_config_file)
+ python3 - "$config_path" <<'PY'
+import json, os, sys
+config_path = sys.argv[1]
+results = [("main", os.path.expanduser("~/.openclaw/workspace"))]
+seen = {"main"}
+try:
+ if os.path.exists(config_path):
+ with open(config_path, encoding='utf-8') as f:
+ data = json.load(f)
+ agents = data.get('agents', {}).get('list', [])
+ if isinstance(agents, list):
+ for item in agents:
+ if not isinstance(item, dict):
+ continue
+ aid = item.get('id')
+ ws = item.get('workspace')
+ if not aid or aid in seen:
+ continue
+ if not ws:
+ ws = f"~/.openclaw/workspace-{aid}"
+ results.append((aid, os.path.expanduser(ws)))
+ seen.add(aid)
+except Exception:
+ pass
+for aid, ws in results:
+ print(f"{aid}\t{ws}")
+PY
+ }
+
+ openclaw_memory_status_value() {
+ local key="$1"
+ local agent_id="${2:-}"
+ if [ -n "$agent_id" ]; then
+ openclaw memory status --agent "$agent_id" 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ else
+ openclaw memory status 2>/dev/null | awk -F': ' -v k="$key" '$1==k {print $2; exit}'
+ fi
+ }
+
+ openclaw_memory_expand_path() {
+ local raw_path="$1"
+ if [ -z "$raw_path" ]; then
+ echo ""
+ return 0
+ fi
+ raw_path=$(echo "$raw_path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [[ "$raw_path" == ~* ]]; then
+ echo "${raw_path/#\~/$HOME}"
+ else
+ echo "$raw_path"
+ fi
+ }
+
+ openclaw_memory_rebuild_index_single() {
+ local agent_id="${1:-main}"
+ local store_raw store_file ts backup_file
+ store_raw=$(openclaw_memory_status_value "Store" "$agent_id")
+ store_file=$(openclaw_memory_expand_path "$store_raw")
+ if [ -z "$store_file" ] || [ ! -f "$store_file" ]; then
+ echo "⚠️ [$agent_id] 未找到索引庫文件,可能為空或不存在。"
+ echo "Store 原始值: ${store_raw:-<空>}"
+ echo "仍將執行重建索引。"
+ else
+ ts=$(date +%Y%m%d_%H%M%S)
+ backup_file="${store_file}.bak.${ts}"
+ if mv "$store_file" "$backup_file"; then
+ echo "✅ [$agent_id] 已備份索引:$backup_file"
+ else
+ echo "⚠️ [$agent_id] 索引備份失敗,繼續重建。"
+ fi
+ fi
+ openclaw memory index --agent "$agent_id" --force
+ }
+
+ openclaw_memory_rebuild_index_safe() {
+ local agent_id="${1:-main}"
+ openclaw_memory_rebuild_index_single "$agent_id"
+ openclaw gateway restart
+ echo "✅ 索引已重建並自動重新啟動網關"
+ echo ""
+ openclaw_memory_render_status
+ }
+
+ openclaw_memory_rebuild_index_all() {
+ local count=0
+ local agent_lines agent_id workspace
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id workspace; do
+ [ -z "$agent_id" ] && continue
+ openclaw_memory_rebuild_index_single "$agent_id"
+ count=$((count+1))
+ done </dev/null)
+ if [ -z "$json_output" ]; then
+ echo "取得記憶狀態失敗(openclaw memory status --json 無輸出)"
+ return 1
+ fi
+ python3 - "$json_output" <<'PY'
+import json, sys
+raw = sys.argv[1]
+try:
+ data = json.loads(raw)
+except Exception:
+ print("取得記憶狀態失敗(JSON 解析錯誤)")
+ raise SystemExit(1)
+if not isinstance(data, list) or len(data) == 0:
+ print("未檢測到任何智能體記憶狀態。")
+ raise SystemExit(0)
+first = True
+for entry in data:
+ if not isinstance(entry, dict):
+ continue
+ agent_id = entry.get("agentId", "?")
+ s = entry.get("status", {})
+ if not isinstance(s, dict):
+ s = {}
+ if not first:
+ print("")
+ first = False
+ print("Agent: %s" % agent_id)
+ backend = s.get("backend") or s.get("provider") or "-"
+ print("底層方案: %s" % backend)
+ files = s.get("files", 0)
+ chunks = s.get("chunks", 0)
+ print("已收錄: %s 檔案 / %s 區塊" % (files, chunks))
+ dirty = s.get("dirty")
+ dirty_str = "是" if dirty else "否"
+ print("待刷新: %s" % dirty_str)
+ vec = s.get("vector", {})
+ if isinstance(vec, dict) and vec.get("enabled"):
+ vec_str = "就緒" if vec.get("available") else "已啟用(不可用)"
+ else:
+ vec_str = "未啟用"
+ print("向量庫: %s" % vec_str)
+ ws = s.get("workspaceDir") or "-"
+ print("工作區: %s" % ws)
+ db = s.get("dbPath") or "-"
+ print("索引庫: %s" % db)
+ scan = entry.get("scan", {})
+ if isinstance(scan, dict):
+ issues = scan.get("issues", [])
+ if issues:
+ for issue in issues[:3]:
+ print(" ⚠️ %s" % issue)
+PY
+ }
+
+ openclaw_memory_get_backend() {
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "local" ]; then
+ echo "builtin"
+ else
+ echo "$backend"
+ fi
+ }
+
+ openclaw_memory_get_local_model_path() {
+ openclaw_memory_config_get "agents.defaults.memorySearch.local.modelPath"
+ }
+
+ openclaw_memory_local_model_status() {
+ local model_path="$1"
+ if [ -z "$model_path" ]; then
+ echo "missing"
+ return
+ fi
+ if [[ "$model_path" == hf:* ]]; then
+ echo "hf"
+ return
+ fi
+ if [ -f "$model_path" ]; then
+ echo "ok"
+ else
+ echo "missing"
+ fi
+ }
+
+ openclaw_memory_qmd_available() {
+ if command -v qmd >/dev/null 2>&1; then
+ echo "true"
+ return
+ fi
+ local backend
+ backend=$(openclaw_memory_config_get "memory.backend")
+ if [ "$backend" = "qmd" ]; then
+ echo "true"
+ return
+ fi
+ echo "false"
+ }
+
+ openclaw_memory_probe_url() {
+ local url="$1"
+ if ! command -v curl >/dev/null 2>&1; then
+ echo "unknown"
+ return
+ fi
+ if [ -z "$url" ]; then
+ echo "unknown"
+ return
+ fi
+ if curl -I -m 2 -s "$url" >/dev/null 2>&1; then
+ echo "ok"
+ else
+ echo "fail"
+ fi
+ }
+
+ openclaw_memory_recommend() {
+ local qmd_ok model_path model_status hf_ok mirror_ok
+ qmd_ok=$(openclaw_memory_qmd_available)
+ model_path=$(openclaw_memory_get_local_model_path)
+ model_status=$(openclaw_memory_local_model_status "$model_path")
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+
+ OPENCLAW_MEMORY_RECOMMEND_REASON=()
+ if [ "$qmd_ok" = "true" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("QMD 可用")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("未檢測到 QMD")
+ fi
+ if [ -n "$model_path" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型路徑:$model_path")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("未配置本地模型路徑")
+ fi
+ case "$model_status" in
+ ok) OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型檔案存在") ;;
+ hf) OPENCLAW_MEMORY_RECOMMEND_REASON+=("模型来自 HF 下载源(国内可能慢/失败)") ;;
+ *) OPENCLAW_MEMORY_RECOMMEND_REASON+=("本地模型檔案不存在或不可用") ;;
+ esac
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co 可訪問")
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("hf-mirror.com 可訪問")
+ else
+ OPENCLAW_MEMORY_RECOMMEND_REASON+=("huggingface.co / hf-mirror.com 可能不可達(疑似國內/受限網路)")
+ fi
+
+ if [ "$qmd_ok" = "true" ]; then
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && { [ "$hf_ok" = "ok" ] || [ "$mirror_ok" = "ok" ]; }; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ elif [ "$model_status" = "hf" ] && [ "$hf_ok" = "fail" ] && [ "$mirror_ok" = "fail" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ else
+ if [ "$model_status" = "ok" ]; then
+ OPENCLAW_MEMORY_RECOMMEND="local"
+ else
+ OPENCLAW_MEMORY_RECOMMEND="qmd"
+ fi
+ fi
+ }
+
+
+ openclaw_memory_detect_region() {
+ OPENCLAW_MEMORY_COUNTRY="unknown"
+ OPENCLAW_MEMORY_USE_MIRROR="false"
+ if command -v curl >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_COUNTRY=$(curl -s -m 2 ipinfo.io/country | tr -d '
+' | tr -d '
+')
+ fi
+ case "$OPENCLAW_MEMORY_COUNTRY" in
+ CN|HK)
+ OPENCLAW_MEMORY_USE_MIRROR="true"
+ ;;
+ esac
+ }
+
+ openclaw_memory_select_sources() {
+ local hf_ok mirror_ok
+ hf_ok=$(openclaw_memory_probe_url "https://huggingface.co")
+ mirror_ok=$(openclaw_memory_probe_url "https://hf-mirror.com")
+ OPENCLAW_MEMORY_HF_OK="$hf_ok"
+ OPENCLAW_MEMORY_MIRROR_OK="$mirror_ok"
+ if [ "$OPENCLAW_MEMORY_USE_MIRROR" = "true" ]; then
+ if [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ elif [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://gh.kejilion.pro/"
+ else
+ if [ "$hf_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ elif [ "$mirror_ok" = "ok" ]; then
+ OPENCLAW_MEMORY_HF_BASE="https://hf-mirror.com"
+ else
+ OPENCLAW_MEMORY_HF_BASE="https://huggingface.co"
+ fi
+ OPENCLAW_MEMORY_GH_PROXY="https://"
+ fi
+ }
+
+ openclaw_memory_download_file() {
+ local url="$1"
+ local dest="$2"
+ mkdir -p "$(dirname "$dest")"
+ if command -v curl >/dev/null 2>&1; then
+ curl -L --fail --retry 2 -o "$dest" "$url"
+ return $?
+ fi
+ if command -v wget >/dev/null 2>&1; then
+ wget -O "$dest" "$url"
+ return $?
+ fi
+ echo "❌ 未偵測到 curl 或 wget,無法下載。"
+ return 1
+ }
+
+ openclaw_memory_check_sqlite() {
+ if ! command -v sqlite3 >/dev/null 2>&1; then
+ echo "⚠️ 未偵測到 sqlite3,QMD 可能無法正常運作。"
+ return 1
+ fi
+ local ver
+ ver=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
+ echo "✅ sqlite3 可用: ${ver:-unknown}"
+ echo "ℹ️ sqlite 擴充支援無法可靠檢測,將持續。"
+ return 0
+ }
+
+ openclaw_memory_ensure_bun() {
+ if [ -x "$HOME/.bun/bin/bun" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun 已存在"
+ return 0
+ fi
+ echo "⬇️ 安裝 bun..."
+ if command -v curl >/dev/null 2>&1; then
+ curl -fsSL https://bun.sh/install | bash
+ elif command -v wget >/dev/null 2>&1; then
+ wget -qO- https://bun.sh/install | bash
+ else
+ echo "❌ 未偵測到 curl 或 wget,無法安裝 bun。"
+ return 1
+ fi
+ if [ -d "$HOME/.bun/bin" ]; then
+ export PATH="$HOME/.bun/bin:$PATH"
+ fi
+ if command -v bun >/dev/null 2>&1; then
+ echo "✅ bun 安裝完成"
+ return 0
+ fi
+ echo "❌ bun 安裝失敗"
+ return 1
+ }
+
+ openclaw_memory_ensure_qmd() {
+ local qmd_path
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -n "$qmd_path" ]; then
+ if qmd --version >/dev/null 2>&1; then
+ echo "✅ qmd 已存在且可用:$qmd_path"
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ return 0
+ else
+ echo "⚠️ qmd 指令存在但模組損壞,重新安裝..."
+ fi
+ fi
+ echo "⬇️ 透過 npm 安裝 qmd: @tobilu/qmd"
+ npm install -g @tobilu/qmd
+ qmd_path=$(command -v qmd 2>/dev/null || true)
+ if [ -z "$qmd_path" ]; then
+ echo "❌ qmd 安裝失敗"
+ return 1
+ fi
+ if ! qmd --version >/dev/null 2>&1; then
+ echo "❌ qmd 安裝後仍無法運作"
+ return 1
+ fi
+ OPENCLAW_MEMORY_QMD_PATH="$qmd_path"
+ echo "✅ qmd 安裝完成:$qmd_path"
+ return 0
+ }
+
+ openclaw_memory_render_auto_summary() {
+ echo "---------------------------------------"
+ echo "✅ 環境就緒"
+ echo "方案: ${OPENCLAW_MEMORY_AUTO_SCHEME:-unknown}"
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "模式: 僅寫入配置(未安裝/未下載)"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "索引: 已執行"
+ else
+ echo "索引: 已跳過"
+ fi
+ if [ "$OPENCLAW_MEMORY_RESTARTED" = "true" ]; then
+ echo "重啟: 已執行"
+ else
+ echo "重啟: 已跳過"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ echo "qmd: $OPENCLAW_MEMORY_QMD_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_MODEL_PATH" ]; then
+ echo "模型:$OPENCLAW_MEMORY_MODEL_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_COUNTRY" ]; then
+ echo "地區:$OPENCLAW_MEMORY_COUNTRY"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_HF_BASE" ]; then
+ echo "下載來源:$OPENCLAW_MEMORY_HF_BASE"
+ fi
+ echo "最終狀態:"
+ openclaw_memory_render_status
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_auto_confirm() {
+ local scheme_label="$1"
+ OPENCLAW_MEMORY_PREHEAT="true"
+ OPENCLAW_MEMORY_RESTARTED="false"
+ OPENCLAW_MEMORY_CONFIG_ONLY="false"
+ echo "即將執行自動部署(詳細模式)"
+ echo "目標方案:$scheme_label"
+ echo "地區: ${OPENCLAW_MEMORY_COUNTRY:-unknown}"
+ echo "鏡像來源探測: huggingface.co=${OPENCLAW_MEMORY_HF_OK:-unknown} hf-mirror.com=${OPENCLAW_MEMORY_MIRROR_OK:-unknown}"
+ echo "下載來源: ${OPENCLAW_MEMORY_HF_BASE:-unknown}"
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_PATH" ]; then
+ echo "預計下載路徑:$OPENCLAW_MEMORY_EXPECT_PATH"
+ fi
+ if [ -n "$OPENCLAW_MEMORY_EXPECT_SIZE" ]; then
+ echo "可能流量/磁碟佔用:$OPENCLAW_MEMORY_EXPECT_SIZE"
+ else
+ echo "可能流量/磁碟佔用: 視實際情況而定"
+ fi
+ echo "確認後將自動安裝/下載、寫入設定、建置索引並重新啟動網關"
+ echo "進階選項: 輸入 config 僅寫入設定(不安裝不下載、不索引、不重新啟動)"
+ read -e -p "輸入 yes 確認繼續(預設 N):" confirm_step
+ case "$confirm_step" in
+ yes|YES)
+ OPENCLAW_MEMORY_PREHEAT="true"
+ ;;
+ config|CONFIG)
+ OPENCLAW_MEMORY_CONFIG_ONLY="true"
+ OPENCLAW_MEMORY_PREHEAT="false"
+ ;;
+ *)
+ echo "已取消自動部署。"
+ return 1
+ ;;
+ esac
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ echo "⚠️ 已選擇僅寫入配置,不安裝不下載"
+ else
+ echo "✅ 將自動建立索引並重新啟動網關"
+ fi
+ return 0
+ }
+
+ openclaw_memory_auto_setup_qmd() {
+ echo "🔍 檢測 QMD 環境"
+ openclaw_memory_cleanup_legacy_keys
+ openclaw_memory_check_sqlite || true
+ if [ "$OPENCLAW_MEMORY_CONFIG_ONLY" = "true" ]; then
+ if command -v qmd >/dev/null 2>&1; then
+ OPENCLAW_MEMORY_QMD_PATH=$(command -v qmd)
+ else
+ OPENCLAW_MEMORY_QMD_PATH="qmd"
+ fi
+ else
+ openclaw_memory_ensure_qmd || return 1
+ fi
+ local backend
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ]; then
+ echo "✅ memory.backend 已是 qmd"
+ else
+ openclaw_memory_config_set "memory.backend" "qmd"
+ echo "✅ 已設定 memory.backend=qmd"
+ fi
+ local qmd_cmd
+ qmd_cmd=$(openclaw_memory_config_get "memory.qmd.command")
+ if [ -z "$qmd_cmd" ] || [[ "$qmd_cmd" != /* ]] || [ "$qmd_cmd" != "$OPENCLAW_MEMORY_QMD_PATH" ]; then
+ openclaw_memory_config_set "memory.qmd.command" "$OPENCLAW_MEMORY_QMD_PATH"
+ echo "✅ 已寫入 memory.qmd.command:$OPENCLAW_MEMORY_QMD_PATH"
+ else
+ echo "✅ memory.qmd.command 已正確"
+ fi
+ if [ "$OPENCLAW_MEMORY_PREHEAT" = "true" ]; then
+ echo "🔥 預熱索引(可能下載模型)"
+ openclaw_memory_prepare_workspace_all
+ local preh_agent_lines preh_agent_id preh_workspace
+ preh_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r preh_agent_id preh_workspace; do
+ [ -z "$preh_agent_id" ] && continue
+ openclaw memory index --agent "$preh_agent_id" --force
+ done </dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ OPENCLAW_MEMORY_RESTARTED="true"
+ openclaw_memory_render_auto_summary
+ return 0
+ }
+
+ openclaw_memory_auto_setup_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "记忆方案自动部署"
+ echo "======================================="
+ echo "1. QMD"
+ echo "2. Local"
+ echo "3. Auto(自動選擇)"
+ echo "0. 返回上一級"
+ echo "---------------------------------------"
+ read -e -p "請輸入你的選擇:" auto_choice
+ case "$auto_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "無效的選擇,請重試。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_apply_scheme() {
+ local scheme="$1"
+ openclaw_memory_cleanup_legacy_keys
+ case "$scheme" in
+ qmd)
+ openclaw_memory_config_set "memory.backend" "qmd"
+ if [ $? -ne 0 ]; then
+ echo "❌ 寫入配置失敗"
+ return 1
+ fi
+ openclaw_memory_config_set "memory.qmd.command" "qmd" >/dev/null 2>&1
+ ;;
+ local)
+ openclaw_memory_config_set "memory.backend" "builtin"
+ if [ $? -ne 0 ]; then
+ echo "❌ 寫入配置失敗"
+ return 1
+ fi
+ openclaw_memory_config_set "agents.defaults.memorySearch.provider" "local" >/dev/null 2>&1
+ ;;
+ *)
+ echo "❌ 未知方案:$scheme"
+ return 1
+ esac
+ echo "✅ 已更新記憶方案配置"
+ return 0
+ }
+
+ openclaw_memory_offer_restart() {
+ echo "配置已寫入,需要重新啟動 OpenClaw 閘道後生效。"
+ read -e -p "是否立即重新啟動 OpenClaw 網關? (Y/n):" restart_choice
+ if [[ "$restart_choice" =~ ^[Nn]$ ]]; then
+ echo "已跳過重啟,可稍後執行: openclaw gateway restart"
+ return 0
+ fi
+ if declare -F start_gateway >/dev/null 2>&1; then
+ start_gateway
+ else
+ openclaw gateway restart
+ fi
+ }
+
+ openclaw_memory_fix_index() {
+ local backend include_dm
+ backend=$(openclaw_memory_get_backend)
+ if [ "$backend" = "qmd" ] && ! command -v qmd >/dev/null 2>&1; then
+ echo "⚠️ 偵測到目前方案為 QMD,但未安裝 qmd 指令。"
+ echo "可切換 Local,或安裝 bun + qmd 後再試。"
+ fi
+ include_dm=$(openclaw config get memory.qmd.includeDefaultMemory 2>/dev/null)
+ echo "======================================="
+ echo "索引修復診斷"
+ echo "======================================="
+ echo "目前 includeDefaultMemory: ${include_dm:-未設定}"
+ echo ""
+ if [ "$include_dm" = "false" ]; then
+ echo "⚠️ 偵測到 includeDefaultMemory=false"
+ echo "這會導致預設記憶檔案(MEMORY.md + memory/*.md)不被索引"
+ echo "所以 Indexed 會一直顯示 0/N"
+ echo ""
+ read -e -p "是否恢復為 true 並重建索引? (Y/n):" fix_choice
+ if [[ ! "$fix_choice" =~ ^[Nn]$ ]]; then
+ openclaw_memory_config_set "memory.qmd.includeDefaultMemory" true
+ if [ $? -ne 0 ]; then
+ echo "❌ 寫入配置失敗"
+ break_end
+ return 1
+ fi
+ echo "✅ 已恢復 includeDefaultMemory=true"
+ openclaw_memory_rebuild_index_all
+ else
+ echo "已取消。"
+ fi
+ else
+ echo "includeDefaultMemory 配置正常。"
+ echo "將執行:清理舊索引 → 全量重建所有智能體索引"
+ echo ""
+ read -e -p "確認執行? (Y/n):" confirm_fix
+ if [[ ! "$confirm_fix" =~ ^[Nn]$ ]]; then
+ openclaw_memory_rebuild_index_all
+ else
+ echo "已取消。"
+ fi
+ fi
+ break_end
+ }
+
+ openclaw_memory_scheme_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 記憶方案"
+ echo "======================================="
+ local backend current_label
+ backend=$(openclaw_memory_get_backend)
+ case "$backend" in
+ qmd) current_label="QMD" ;;
+ builtin|local) current_label="Local" ;;
+ *) current_label="未配置" ;;
+ esac
+ echo "當前方案:$current_label"
+ echo ""
+ echo "QMD : 輕量索引,依賴 qmd 指令(適合網路受限)"
+ echo "Local: 本機向量檢索,依賴 embedding 模型文件"
+ echo "Auto : 自動推薦(基於可用性 + 網路探測)"
+ echo "---------------------------------------"
+ echo "1. 切換 QMD(自動部署/已安裝則跳過)"
+ echo "2. 切換 Local(自動部署/已安裝則跳過)"
+ echo "3. Auto(自動推薦並自動部署)"
+ echo "0. 返回上一級"
+ echo "---------------------------------------"
+ read -e -p "請輸入你的選擇:" scheme_choice
+ case "$scheme_choice" in
+ 1)
+ openclaw_memory_auto_setup_run "qmd"
+ break_end
+ ;;
+ 2)
+ openclaw_memory_auto_setup_run "local"
+ break_end
+ ;;
+ 3)
+ openclaw_memory_auto_setup_run "auto"
+ break_end
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "無效的選擇,請重試。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_memory_file_collect() {
+ OPENCLAW_MEMORY_FILES=()
+ OPENCLAW_MEMORY_FILE_LABELS=()
+ local agent_lines agent_id base_dir memory_dir memory_file rel
+ agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r agent_id base_dir; do
+ [ -z "$agent_id" ] && continue
+ memory_dir="$base_dir/memory"
+ memory_file="$base_dir/MEMORY.md"
+ if [ -f "$memory_file" ]; then
+ OPENCLAW_MEMORY_FILES+=("$memory_file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/MEMORY.md")
+ fi
+ if [ -d "$memory_dir" ]; then
+ while IFS= read -r file; do
+ [ -f "$file" ] || continue
+ rel="${file#$base_dir/}"
+ OPENCLAW_MEMORY_FILES+=("$file")
+ OPENCLAW_MEMORY_FILE_LABELS+=("$agent_id/$rel")
+ done < <(find "$memory_dir" -type f -name '*.md' | sort)
+ fi
+ done </dev/null || stat -c %y "$file" | awk '{print $1" "$2}')
+ printf "%s | %s | %s | %s\\n" "$((i+1))" "$rel" "$size" "$mtime"
+ done
+ }
+
+ openclaw_memory_view_file() {
+ local file="$1"
+ [ -f "$file" ] || {
+ echo "❌ 文件不存在:$file"
+ return 1
+ }
+ local total_lines
+ total_lines=$(wc -l < "$file" 2>/dev/null || echo 0)
+ local default_lines=120
+ local start_line count
+ echo "文件:$file"
+ echo "總行數:$total_lines"
+ read -e -p "請輸入起始行(回車預設末尾$default_lines行):" start_line
+ read -e -p "請輸入顯示行數(回車預設$default_lines): " count
+ [ -z "$count" ] && count=$default_lines
+ if [ -z "$start_line" ]; then
+ if [ "$total_lines" -le "$count" ]; then
+ start_line=1
+ else
+ start_line=$((total_lines - count + 1))
+ fi
+ fi
+ if ! [[ "$start_line" =~ ^[0-9]+$ ]] || ! [[ "$count" =~ ^[0-9]+$ ]]; then
+ echo "❌ 請輸入有效的數字。"
+ return 1
+ fi
+ if [ "$start_line" -lt 1 ]; then
+ start_line=1
+ fi
+ if [ "$count" -le 0 ]; then
+ echo "❌ 行數必須大於 0。"
+ return 1
+ fi
+ local end_line=$((start_line + count - 1))
+ if [ "$end_line" -gt "$total_lines" ]; then
+ end_line=$total_lines
+ fi
+ if [ "$total_lines" -eq 0 ]; then
+ echo "(空白文件)"
+ return 0
+ fi
+ echo "---------------------------------------"
+ sed -n "${start_line},${end_line}p" "$file"
+ echo "---------------------------------------"
+ }
+
+ openclaw_memory_files_menu() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 記憶文件"
+ echo "======================================="
+ openclaw_memory_file_render_list
+ echo "---------------------------------------"
+ read -e -p "請輸入文件編號查看(0 返回):" file_choice
+ if [ "$file_choice" = "0" ]; then
+ return 0
+ fi
+ if ! [[ "$file_choice" =~ ^[0-9]+$ ]]; then
+ echo "無效的選擇,請重試。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_file_collect
+ if [ ${#OPENCLAW_MEMORY_FILES[@]} -eq 0 ]; then
+ read -p "未找到記憶文件,按回車返回..."
+ return 0
+ fi
+ local idx=$((file_choice-1))
+ if [ "$idx" -lt 0 ] || [ "$idx" -ge ${#OPENCLAW_MEMORY_FILES[@]} ]; then
+ echo "無效的編號,請重試。"
+ sleep 1
+ continue
+ fi
+ openclaw_memory_view_file "${OPENCLAW_MEMORY_FILES[$idx]}"
+ read -p "按回車返回清單..."
+ done
+ }
+
+
+ openclaw_memory_search_test() {
+ read -e -p "輸入搜尋關鍵字:" query
+ if [ -z "$query" ]; then
+ echo "關鍵字不能為空。"
+ return 1
+ fi
+ echo "正在搜尋記憶..."
+ openclaw memory search "$query" --max-results 5
+ }
+
+ openclaw_memory_deep_status() {
+ echo "正在探測嵌入模型就緒狀態..."
+ openclaw memory status --deep
+ }
+
+ openclaw_memory_menu() {
+ send_stats "OpenClaw記憶管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 記憶管理"
+ echo "======================================="
+ openclaw_memory_render_status
+ echo "1. 更新記憶索引"
+ echo "2. 檢視記憶文件"
+ echo "3. 索引修復(Indexed 異常)"
+ echo "4. 記憶方案(QMD/Local/Auto)"
+ echo "5. 搜索测试(验证索引是否工作)"
+ echo "6. 深度狀態探測(檢查嵌入模型)"
+ echo "0. 返回上一級"
+ echo "---------------------------------------"
+ read -e -p "請輸入你的選擇:" memory_choice
+ case "$memory_choice" in
+ 1)
+ echo "即將更新記憶索引。"
+ read -e -p "第一次確認:輸入 yes 繼續:" confirm_step1
+ if [ "$confirm_step1" != "yes" ]; then
+ echo "已取消。"
+ break_end
+ continue
+ fi
+ openclaw_memory_prepare_workspace_all
+ read -e -p "二次確認:輸入 force 使用全量(留空為增量):" confirm_step2
+ if [ "$confirm_step2" = "force" ]; then
+ echo "⚠️ 全量重建更徹底,但耗時更長。"
+ echo "建議:輸入 rebuild 進行安全重建(先備份索引庫)。"
+ read -e -p "第三次確認:輸入 rebuild 執行安全重建;直接回車繼續普通 force:" confirm_step3
+ if [ "$confirm_step3" = "rebuild" ]; then
+ openclaw_memory_rebuild_index_all
+ else
+ local fl_agent_lines fl_agent_id fl_workspace
+ fl_agent_lines=$(openclaw_memory_list_agents)
+ while IFS=$'\t' read -r fl_agent_id fl_workspace; do
+ [ -z "$fl_agent_id" ] && continue
+ openclaw memory index --agent "$fl_agent_id" --force
+ done </dev/null 2>&1 || {
+ echo "⚠️ 權限備份失敗:$backup_file"
+ return 1
+ }
+ echo "✅ 已備份目前權限配置:$backup_file"
+ return 0
+ }
+
+ openclaw_permission_restore_backup() {
+ local config_file backup_file
+ config_file=$(openclaw_permission_config_file)
+ backup_file=$(openclaw_permission_backup_file)
+ if [ ! -s "$backup_file" ]; then
+ echo "❌ 未找到可還原的權限備份檔。"
+ return 1
+ fi
+ cp -f "$backup_file" "$config_file" >/dev/null 2>&1 || {
+ echo "❌ 權限恢復失敗:$backup_file"
+ return 1
+ }
+ echo "✅ 已恢復切換前權限配置"
+ openclaw_permission_restart_gateway || true
+ return 0
+ }
+
+ openclaw_permission_restart_gateway() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ 未偵測到 openclaw,無法重新啟動 OpenClaw Gateway。"
+ return 1
+ fi
+ echo "正在重啟 OpenClaw Gateway..."
+ openclaw gateway restart >/dev/null 2>&1 || {
+ openclaw gateway stop >/dev/null 2>&1
+ openclaw gateway start >/dev/null 2>&1
+ }
+ }
+
+ openclaw_permission_get_value() {
+ local path="$1"
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+
+ if openclaw_has_command openclaw; then
+ local value
+ value=$(openclaw config get "$path" 2>&1 | head -n 1)
+ if [ -n "$value" ]; then
+ if echo "$value" | grep -qi "config path not found"; then
+ echo "(unset)"
+ return 0
+ fi
+ if [ "$value" = "null" ]; then
+ echo "(unset)"
+ else
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ fi
+ return 0
+ fi
+ fi
+
+ [ -f "$config_file" ] || { echo "(unset)"; return 0; }
+
+ if openclaw_has_command jq; then
+ local jq_value
+ jq_value=$(jq -r --arg p "$path" 'getpath($p|split(".")) // "(unset)"' "$config_file" 2>/dev/null) || jq_value="(unset)"
+ [ "$jq_value" = "null" ] && jq_value="(unset)"
+ echo "$jq_value"
+ return 0
+ fi
+
+ if openclaw_has_command python3; then
+ python3 - "$config_file" "$path" <<'PY'
+import json, sys
+path = sys.argv[2]
+with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ obj = json.load(f)
+cur = obj
+for part in path.split('.'):
+ if isinstance(cur, dict) and part in cur:
+ cur = cur[part]
+ else:
+ print('(unset)')
+ raise SystemExit(0)
+if isinstance(cur, bool):
+ print('true' if cur else 'false')
+elif cur is None:
+ print('(unset)')
+else:
+ print(json.dumps(cur, ensure_ascii=False) if isinstance(cur, (dict, list)) else str(cur))
+PY
+ return 0
+ fi
+
+ echo "(unset)"
+ return 0
+ }
+
+ openclaw_permission_unset_optional() {
+ local key="$1"
+ local probe
+ if ! openclaw_has_command openclaw; then
+ return 1
+ fi
+ if openclaw config unset "$key" >/dev/null 2>&1; then
+ return 0
+ fi
+ probe=$(openclaw config get "$key" 2>&1 | head -n 1)
+ if [ -z "$probe" ] || [ "$probe" = "null" ] || [ "$probe" = "(unset)" ] || echo "$probe" | grep -qi "config path not found"; then
+ return 0
+ fi
+ return 1
+ }
+
+ openclaw_permission_detect_mode() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ [ ! -f "$config_file" ] && { echo "未知模式"; return; }
+
+ python3 - "$config_file" <<'PY'
+import json, sys
+
+def get_v(o, p):
+ for k in p.split('.'):
+ if isinstance(o, dict) and k in o:
+ o = o[k]
+ else:
+ return "(unset)"
+ return str(o).lower()
+
+try:
+ with open(sys.argv[1], 'r', encoding='utf-8') as f:
+ d = json.load(f)
+ p = get_v(d, "tools.profile")
+ s = get_v(d, "tools.exec.security")
+ a = get_v(d, "tools.exec.ask")
+ e = get_v(d, "tools.elevated.enabled")
+ b = get_v(d, "commands.bash")
+ ap = get_v(d, "tools.exec.applyPatch.enabled")
+ w = get_v(d, "tools.exec.applyPatch.workspaceOnly")
+
+ if p == "coding" and s == "allowlist" and a == "on-miss" and e == "false" and b == "false" and ap == "false":
+ print("標準安全模式")
+ elif p == "coding" and s == "allowlist" and a == "on-miss" and e == "true" and b == "true" and ap == "true" and w == "true":
+ print("開發增強模式")
+ elif (p == "full" or p == "(unset)") and s == "full" and a == "off" and e == "true" and b == "true" and ap == "true":
+ print("完全開放模式")
+ else:
+ print("自訂模式")
+except Exception:
+ print("自訂模式")
+PY
+ }
+
+ openclaw_permission_update_exec_approvals() {
+ local sec="$1"
+ local ask="$2"
+ local fallback="$3"
+ local approvals_file="$HOME/.openclaw/exec-approvals.json"
+
+ mkdir -p "$HOME/.openclaw"
+
+ # 產生 JSON 並透過 openclaw approvals set --stdin 寫入(優先)
+ # 若 CLI 不支援則回退直接寫文件
+ local json_payload
+ json_payload=$(python3 -c '
+import json, sys, os
+path = sys.argv[1]
+try:
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ data = json.load(f)
+ else:
+ data = {"version": 1, "defaults": {}}
+except Exception:
+ data = {"version": 1, "defaults": {}}
+if "defaults" not in data:
+ data["defaults"] = {}
+data["defaults"]["security"] = sys.argv[2]
+data["defaults"]["ask"] = sys.argv[3]
+data["defaults"]["askFallback"] = sys.argv[4]
+data["defaults"]["autoAllowSkills"] = True
+print(json.dumps(data, indent=2))
+' "$approvals_file" "$sec" "$ask" "$fallback")
+
+ if openclaw_has_command openclaw && echo "$json_payload" | openclaw approvals set --stdin >/dev/null 2>&1; then
+ return 0
+ fi
+ # 回退:直接寫文件
+ echo "$json_payload" > "$approvals_file"
+ }
+
+ openclaw_permission_render_status() {
+ echo "應用層配置: ~/.openclaw/openclaw.json"
+ echo "宿主機核准: ~/.openclaw/exec-approvals.json"
+ echo "---------------------------------------"
+ local current_profile current_sec current_ask current_elevated
+ current_profile=$(openclaw config get tools.profile 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_sec=$(openclaw config get tools.exec.security 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_ask=$(openclaw config get tools.exec.ask 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ current_elevated=$(openclaw config get tools.elevated.enabled 2>/dev/null | head -n 1 | sed 's/^"//;s/"$//')
+ # 清理空值
+ [ -z "$current_profile" ] || echo "$current_profile" | grep -qi "config path not found" && current_profile=""
+ [ -z "$current_sec" ] || echo "$current_sec" | grep -qi "config path not found" && current_sec=""
+ [ -z "$current_ask" ] || echo "$current_ask" | grep -qi "config path not found" && current_ask=""
+ [ -z "$current_elevated" ] || echo "$current_elevated" | grep -qi "config path not found" && current_elevated=""
+
+ local current_mode="未知 / 自訂"
+ if [ "$current_profile" = "full" ] && [ "$current_sec" = "full" ] && [ "$current_ask" = "off" ]; then
+ current_mode="\033[1;31m完全開放模式\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" = "true" ]; then
+ current_mode="\033[1;33m開發增強模式\033[0m"
+ elif [ "$current_profile" = "coding" ] && [ "$current_sec" = "allowlist" ] && [ "$current_ask" = "on-miss" ] && [ "$current_elevated" != "true" ]; then
+ current_mode="\033[1;32m標準安全模式\033[0m"
+ elif [ -z "$current_profile" ] && [ -z "$current_sec" ]; then
+ current_mode="\033[1;36m官方沙盒兜底\033[0m"
+ fi
+ echo -e "當前綜合安全等級:${current_mode}"
+ echo "---------------------------------------"
+ echo -e "${gl_huang}[應用層 Tool Policy 狀態]${gl_bai}"
+ echo "Profile (預設): ${current_profile:-(unset)}"
+ echo "Exec 限制: ${current_sec:-(unset)}"
+ echo "審核提示: ${current_ask:-(unset)}"
+ echo "提權開關: ${current_elevated:-(unset)}"
+
+ echo -e "\n${gl_huang}[底層 Exec Approvals 狀態]${gl_bai}"
+ if openclaw_has_command openclaw; then
+ local approvals_json
+ approvals_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$approvals_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ defaults = d.get("file", {}).get("defaults", {})
+ if not defaults:
+ defaults = d.get("defaults", {})
+ sec = defaults.get("security", "(unset)")
+ ask = defaults.get("ask", "(unset)")
+ fb = defaults.get("askFallback", "(unset)")
+ auto = defaults.get("autoAllowSkills", False)
+ print("攔截策略 (Security):" + str(sec))
+ print("提示策略 (Ask):" + str(ask))
+ print("無UI兜底 (AskFallback):" + str(fb))
+ print("自動放行技能 (autoAllowSkills):" + ("on" if auto else "off"))
+ exists = d.get("exists", True)
+ if not exists:
+ print("(審核文件不存在,使用系統內建安全兜底)")
+except Exception as e:
+ print("(解析失敗:" + str(e) + ")")
+' "$approvals_json"
+ else
+ echo "(openclaw approvals get --json 無輸出)"
+ fi
+ elif [ -f "$HOME/.openclaw/exec-approvals.json" ]; then
+ python3 -c '
+import json, os
+path = os.path.expanduser("~/.openclaw/exec-approvals.json")
+try:
+ with open(path) as f:
+ d = json.load(f).get("defaults", {})
+ print("攔截策略 (Security):" + str(d.get("security", "(unset)")))
+ print("提示策略 (Ask):" + str(d.get("ask", "(unset)")))
+ print("無UI兜底 (AskFallback):" + str(d.get("askFallback", "(unset)")))
+except Exception:
+ print("(設定檔解析失敗)")
+'
+ else
+ echo "(未配置,強制使用系統內建安全兜底策略)"
+ fi
+ }
+
+ openclaw_permission_apply_standard() {
+ send_stats "OpenClaw權限-標準安全模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在設定應用層策略..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled false >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval true >/dev/null 2>&1 # 拦截危险的内联代码
+ openclaw config unset commands.bash >/dev/null 2>&1 # 废弃旧版参数
+
+ echo "正在設定宿主機審批攔截..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切換為標準安全模式 (所有危險命令將透過UI/TG請求你的批准)${gl_bai}"
+ }
+
+ openclaw_permission_apply_developer() {
+ send_stats "OpenClaw權限-開發增強模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在設定應用層策略..."
+ openclaw config set tools.profile coding >/dev/null 2>&1
+ openclaw config set tools.exec.security allowlist >/dev/null 2>&1
+ openclaw config set tools.exec.ask on-miss >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1 # 允许智能体申请提权
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "正在設定宿主機審批攔截..."
+ openclaw_permission_update_exec_approvals "allowlist" "on-miss" "deny"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切換為開發增強模式 (允許提權,但常規危險命令仍需審批)${gl_bai}"
+ }
+
+ openclaw_permission_apply_full() {
+ send_stats "OpenClaw權限-完全開放模式"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "正在設定應用層策略..."
+ openclaw config set tools.profile full >/dev/null 2>&1
+ openclaw config set tools.exec.security full >/dev/null 2>&1
+ openclaw config set tools.exec.ask off >/dev/null 2>&1
+ openclaw config set tools.elevated.enabled true >/dev/null 2>&1
+ openclaw config set tools.exec.strictInlineEval false >/dev/null 2>&1
+
+ echo "正在瓦解宿主機攔截防禦..."
+ # 這裡的 full 和 off 將徹底繞過底層宿主機的 exec 審批系統
+ openclaw_permission_update_exec_approvals "full" "off" "full"
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已切換為完全開放模式 (警告:所有宿主機指令攔截已失效,智能體具有最高權限)${gl_bai}"
+ }
+
+ openclaw_permission_restore_official_defaults() {
+ send_stats "OpenClaw權限-恢復官方默認"
+ openclaw_permission_require_openclaw || return 1
+
+ echo "清理應用層強制覆蓋..."
+ openclaw config unset tools.profile >/dev/null 2>&1
+ openclaw config unset tools.exec.security >/dev/null 2>&1
+ openclaw config unset tools.exec.ask >/dev/null 2>&1
+ openclaw config unset tools.elevated.enabled >/dev/null 2>&1
+ openclaw config unset tools.exec.strictInlineEval >/dev/null 2>&1
+
+ echo "清理宿主機攔截配置..."
+ # 優先透過 CLI 清空審核配置,回退直接刪文件
+ if echo '{"version":1,"defaults":{}}' | openclaw approvals set --stdin >/dev/null 2>&1; then
+ true
+ else
+ rm -f "$HOME/.openclaw/exec-approvals.json"
+ fi
+
+ openclaw_permission_restart_gateway
+ echo -e "${gl_lv}✅ 已恢復到 OpenClaw 官方安全沙盒防禦機制${gl_bai}"
+ }
+
+ openclaw_permission_run_audit() {
+ echo "======================================="
+ echo "運行 OpenClaw 官方安全審計與體檢..."
+ echo "======================================="
+ openclaw security audit
+ echo "---------------------------------------"
+ read -e -p "是否嘗試自動修復發現的安全隱患? (y/n):" fix_choice
+ if [[ "$fix_choice" == "y" || "$fix_choice" == "Y" || "$fix_choice" == "yes" ]]; then
+ openclaw security audit --fix
+ echo -e "${gl_lv}✅ 自動修復完成。${gl_bai}"
+ fi
+ echo "按任意鍵返回..."
+ read -n 1 -s
+ }
+
+
+ openclaw_permission_manage_allowlist() {
+ while true; do
+ clear
+ echo "======================================="
+ echo "Exec 指令白名單管理"
+ echo "======================================="
+ echo "目前白名單:"
+ local allowlist_json
+ allowlist_json=$(openclaw approvals get --json 2>/dev/null)
+ if [ -n "$allowlist_json" ]; then
+ python3 -c '
+import json, sys
+try:
+ d = json.loads(sys.argv[1])
+ f = d.get("file", {})
+ agents = f.get("agents", {})
+ found = False
+ for agent_id, agent_data in agents.items():
+ al = agent_data.get("allowlist", [])
+ if al:
+ found = True
+ print("智能體 [%s]:" % agent_id)
+ for item in al:
+ print(" - %s" % item)
+ if not found:
+ print("(空,未配置任何白名單規則)")
+except Exception as e:
+ print("(解析失敗:" + str(e) + ")")
+' "$allowlist_json"
+ else
+ echo "(無法取得)"
+ fi
+ echo "---------------------------------------"
+ echo "1. 新增白名單規則"
+ echo "2. 移除白名單規則"
+ echo "0. 返回"
+ echo "---------------------------------------"
+ read -e -p "請選擇:" al_choice
+ case "$al_choice" in
+ 1)
+ read -e -p "輸入要放行的命令路徑 (支援 glob,如 /usr/bin/git):" pattern
+ [ -z "$pattern" ] && { echo "不能為空"; break_end; continue; }
+ read -e -p "指定智能體ID (留空=所有智能體 *):" agent_id
+ agent_id="${agent_id:-*}"
+ openclaw approvals allowlist add --agent "$agent_id" "$pattern"
+ break_end
+ ;;
+ 2)
+ read -e -p "輸入要移除的命令路徑:" pattern
+ [ -z "$pattern" ] && { echo "不能為空"; break_end; continue; }
+ openclaw approvals allowlist remove "$pattern"
+ break_end
+ ;;
+ 0) return 0 ;;
+ *) echo "無效選擇"; sleep 1 ;;
+ esac
+ done
+ }
+
+ openclaw_permission_menu() {
+ send_stats "OpenClaw權限管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 權限管理 (雙層架構深度適配)"
+ echo "======================================="
+ openclaw_permission_render_status
+ echo "---------------------------------------"
+ echo -e "${gl_kjlan}1.${gl_bai}切換為標準安全模式(日常推薦,彈卡核准)"
+ echo -e "${gl_kjlan}2.${gl_bai}切換為開發增強模式(允許智能體申請提權)"
+ echo -e "${gl_kjlan}3.${gl_bai}切換為完全開放模式(${gl_hong}高風險!徹底解除所有宿主機攔截${gl_bai})"
+ echo -e "${gl_kjlan}4.${gl_bai}恢復官方預設沙盒防禦策略"
+ echo -e "${gl_kjlan}5.${gl_bai}運行底層安全審計與自動修復"
+ echo -e "${gl_kjlan}6.${gl_bai}管理 Exec 指令白名單"
+ echo -e "${gl_kjlan}0.${gl_bai}回上一級"
+ echo "---------------------------------------"
+ read -e -p "請輸入你的選擇:" perm_choice
+ case "$perm_choice" in
+ 1)
+ echo "準備應用:標準安全模式"
+ read -e -p "輸入 yes 確認:" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_standard; else echo "已取消"; fi
+ break_end
+ ;;
+ 2)
+ echo "准备应用:开发增强模式"
+ read -e -p "輸入 yes 確認:" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_apply_developer; else echo "已取消"; fi
+ break_end
+ ;;
+ 3)
+ echo -e "${gl_hong}⚠️ 完全開放模式會徹底瓦解 exec 審核並自動放行高風險程式碼。${gl_bai}"
+ read -e -p "輸入 FULL 確認繼續:" confirm
+ if [ "$confirm" = "FULL" ]; then openclaw_permission_apply_full; else echo "已取消"; fi
+ break_end
+ ;;
+ 4)
+ echo "將清除所有自訂覆蓋,恢復 OpenClaw 剛安裝時的嚴格沙盒狀態。"
+ read -e -p "輸入 yes 確認:" confirm
+ if [ "$confirm" = "yes" ]; then openclaw_permission_restore_official_defaults; else echo "已取消"; fi
+ break_end
+ ;;
+ 5)
+ openclaw_permission_run_audit
+ ;;
+ 6)
+ openclaw_permission_manage_allowlist
+ ;;
+ 0)
+ return 0
+ ;;
+ *)
+ echo "無效的選擇,請重試。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+ openclaw_multiagent_config_file() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ echo "$config_file"
+ return 0
+ fi
+ openclaw config file 2>/dev/null | tail -n 1
+ }
+
+ openclaw_multiagent_default_agent() {
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+value="(unset)"
+try:
+ with open(path) as f:
+ data=json.load(f)
+ defaults=data.get("agents",{}).get("defaults",{}) if isinstance(data,dict) else {}
+ value=defaults.get("agent") or None
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and (item.get("isDefault") or item.get("default")):
+ value=item.get("id")
+ break
+ if not value:
+ for item in data.get("agents",{}).get("list",[]) or []:
+ if isinstance(item,dict) and item.get("id"):
+ value=item.get("id")
+ break
+except Exception:
+ value="(unset)"
+print(value or "(unset)")
+PY
+ return 0
+ fi
+ local value
+ value=$(openclaw config get agents.defaults.agent 2>&1 | head -n 1)
+ if [ -z "$value" ] || echo "$value" | grep -qi "config path not found"; then
+ value=$(openclaw agents list --json 2>/dev/null | python3 -c 'import json,sys
+try:
+ data=json.load(sys.stdin)
+ print(next((x.get("id","(unset)") for x in data if x.get("isDefault")), "(unset)"))
+except Exception:
+ print("(unset)")' 2>/dev/null)
+ fi
+ [ -z "$value" ] && value="(unset)"
+ if echo "$value" | grep -q '^".*"$'; then
+ value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//')
+ fi
+ echo "$value"
+ }
+
+ openclaw_multiagent_require_openclaw() {
+ if ! openclaw_has_command openclaw; then
+ echo "❌ 未偵測到 openclaw 指令,請先安裝或初始化 OpenClaw。"
+ return 1
+ fi
+ return 0
+ }
+
+ openclaw_multiagent_agents_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents list --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:從設定檔讀取
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys,os
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ agents=data.get("agents",{}).get("list",[])
+ if not isinstance(agents,list):
+ agents=[]
+ print(json.dumps(agents, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_bindings_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw agents bindings --json 2>/dev/null)
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:從設定檔讀取
+ local config_file
+ config_file=$(openclaw_permission_config_file)
+ if [ -s "$config_file" ]; then
+ python3 - "$config_file" <<'PY'
+import json,sys
+path=sys.argv[1]
+try:
+ with open(path) as f:
+ data=json.load(f)
+ bindings=data.get("agents",{}).get("bindings",[])
+ if not isinstance(bindings,list):
+ bindings=[]
+ results=[]
+ for item in bindings:
+ if not isinstance(item,dict):
+ continue
+ results.append({"agentId": item.get("agentId") or item.get("agent") or "?", "description": item.get("description") or "-"})
+ print(json.dumps(results, ensure_ascii=False))
+except Exception:
+ print("[]")
+PY
+ return 0
+ fi
+ echo '[]'
+ }
+
+ openclaw_multiagent_sessions_json() {
+ local result
+ if openclaw_has_command openclaw; then
+ result=$(openclaw sessions --json 2>/dev/null | grep -v '^\[')
+ if [ -n "$result" ] && python3 -c "import json,sys; json.loads(sys.argv[1])" "$result" 2>/dev/null; then
+ echo "$result"
+ return 0
+ fi
+ fi
+ # 回退:從檔案系統讀取
+ python3 <<'PY'
+import json,os
+base=os.path.expanduser("~/.openclaw/agents")
+sessions=[]
+try:
+ agent_dirs=[d for d in os.listdir(base) if os.path.isdir(os.path.join(base,d))]
+except Exception:
+ agent_dirs=[]
+for agent_id in agent_dirs:
+ path=os.path.join(base,agent_id,"sessions","sessions.json")
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path) as f:
+ data=json.load(f)
+ except Exception:
+ continue
+ if isinstance(data,dict):
+ items=data.items()
+ elif isinstance(data,list):
+ items=[(item.get("key") or "?", item) for item in data if isinstance(item,dict)]
+ else:
+ continue
+ for key,item in items:
+ if not isinstance(item,dict):
+ continue
+ model=item.get("model") or "-"
+ sessions.append({"agentId": agent_id, "key": key, "model": model})
+print(json.dumps({"path":"(filesystem)","count":len(sessions),"sessions":sessions}, ensure_ascii=False))
+PY
+ }
+
+ openclaw_multiagent_render_status() {
+ local config_file default_agent
+ config_file=$(openclaw_multiagent_config_file)
+ default_agent=$(openclaw_multiagent_default_agent)
+ echo "設定檔: ${config_file:-$(openclaw_permission_config_file)}"
+ echo "預設智能體:$default_agent"
+ python3 -c '
+import json,sys
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+sess_obj=json.loads(sys.argv[3] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+print("已配置智能體數: %s" % len(agents))
+print("路由綁定數: %s" % len(bindings))
+print("會話總數: %s" % len(sessions))
+print("---------------------------------------")
+if not agents:
+ print("目前未配置任何多智能體。")
+else:
+ for item in agents[:8]:
+ aid = item.get("id","?")
+ identity = item.get("identityName") or item.get("name") or "-"
+ emoji = item.get("identityEmoji") or ""
+ ws = item.get("workspace") or "-"
+ model = item.get("model") or "-"
+ is_default = item.get("isDefault", False)
+ bcount = item.get("bindings", 0)
+ default_tag = "[預設]" if is_default else ""
+ print("- 智能體ID: \033[1;36m%s\033[0m%s" % (aid, default_tag))
+ print("身份名稱: %s %s" % (identity, emoji))
+ print("模型: %s" % model)
+ print("工作目錄: %s" % ws)
+ print("綁定數: %s" % bcount)
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)" "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_list_agents() {
+ send_stats "OpenClaw多智能體-列出Agent"
+ python3 -c 'import json,sys; agents=json.loads(sys.argv[1] or "[]");
+if not agents: print("暫無已配置 Agent。"); raise SystemExit(0)
+for idx,item in enumerate(agents,1):
+ print("%s. %s" % (idx, item.get("id","?"))); print(" workspace : %s" % item.get("workspace","-")); ident=(item.get("identityName") or "-") + ((" " + item.get("identityEmoji")) if item.get("identityEmoji") else ""); print(" identity : %s" % ident.strip()); print(" model : %s" % (item.get("model") or "-")); print(" bindings : %s" % item.get("bindings",0)); print(" default : %s" % ("yes" if item.get("isDefault") else "no"))' "$(openclaw_multiagent_agents_json)"
+ }
+
+ openclaw_multiagent_add_agent() {
+ send_stats "OpenClaw多智能體-新增Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id workspace confirm
+ read -e -p "請輸入新的 Agent ID:" agent_id
+ [ -z "$agent_id" ] && echo "已取消:Agent ID 不能為空。" && return 1
+ read -e -p "請輸入 workspace 路徑(預設為 ~/.openclaw/workspace-${agent_id}): " workspace
+ [ -z "$workspace" ] && workspace="~/.openclaw/workspace-${agent_id}"
+ echo "將創建智能體:$agent_id"
+ echo "工作目錄:$workspace"
+ read -e -p "輸入 yes 確認繼續:" confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents add "$agent_id" --workspace "$workspace"; then
+ echo "✅ 智能體創建成功:$agent_id"
+ local name theme
+ read -e -p "請輸入智能體身分名稱 (如: 代碼專家):" name
+ [ -z "$name" ] && name="$agent_id"
+ read -e -p "請輸入智能體性格主題 (如: 嚴謹、有效率):" theme
+ [ -z "$theme" ] && theme="助理"
+ echo "正在配置智能體身份..."
+ openclaw agents set-identity --agent "$agent_id" --name "$name" --theme "$theme"
+ else
+ echo "❌ 智能體建立失敗"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_delete_agent() {
+ send_stats "OpenClaw多智能體-刪除Agent"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id confirm
+ read -e -p "請輸入要刪除的 Agent ID:" agent_id
+ [ -z "$agent_id" ] && echo "已取消:Agent ID 不能為空。" && return 1
+ echo "⚠️ 刪除智能體可能會影響其工作目錄、路由綁定與會話路由。"
+ read -e -p "輸入 DELETE 確認刪除${agent_id}: " confirm
+ [ "$confirm" = "DELETE" ] || { echo "已取消"; return 1; }
+ if openclaw agents delete "$agent_id"; then
+ echo "✅ 智能體刪除成功:$agent_id"
+ else
+ echo "❌ 智能體刪除失敗"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_list_bindings() {
+ send_stats "OpenClaw多智能體-檢視路由綁定"
+ python3 -c '
+import json,sys
+bindings=json.loads(sys.argv[1] or "[]")
+if not bindings:
+ print("暫無路由綁定。")
+ raise SystemExit(0)
+for idx,item in enumerate(bindings,1):
+ desc = item.get("description") or "-"
+ print("%s. agent=%s | %s" % (idx, item.get("agentId","?"), desc))
+' "$(openclaw_multiagent_bindings_json)"
+ }
+
+ openclaw_multiagent_add_binding() {
+ send_stats "OpenClaw多智能體-新增路由綁定"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "請輸入智能體 ID:" agent_id
+ read -e -p "請輸入路由綁定值(如 telegram:ops / discord:guild-a):" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "已取消:參數不能為空。" && return 1
+ echo "將綁定智能體 [$agent_id] -> [$bind_value]"
+ read -e -p "輸入 yes 確認繼續:" confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents bind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 路由綁定新增成功"
+ else
+ echo "❌ 路由綁定新增失敗"
+ return 1
+ fi
+ }
+
+ openclaw_multiagent_remove_binding() {
+ send_stats "OpenClaw多智能體-移除路由綁定"
+ openclaw_multiagent_require_openclaw || return 1
+ local agent_id bind_value confirm
+ read -e -p "請輸入智能體 ID:" agent_id
+ read -e -p "請輸入要移除的路由綁定值:" bind_value
+ { [ -z "$agent_id" ] || [ -z "$bind_value" ]; } && echo "已取消:參數不能為空。" && return 1
+ echo "將移除智能體 [$agent_id] 的路由綁定 [$bind_value]"
+ read -e -p "輸入 yes 確認繼續:" confirm
+ [ "$confirm" = "yes" ] || { echo "已取消"; return 1; }
+ if openclaw agents unbind --agent "$agent_id" --bind "$bind_value"; then
+ echo "✅ 路由綁定移除成功"
+ else
+ echo "❌ 路由綁定移除失敗"
+ return 1
+ fi
+ }
+
+
+ openclaw_multiagent_show_sessions() {
+ send_stats "OpenClaw多智能體-會話概況"
+ python3 -c '
+import json,sys
+sess_obj=json.loads(sys.argv[1] or "{}")
+sessions=sess_obj.get("sessions",[]) if isinstance(sess_obj,dict) else []
+if not sessions:
+ print("暫無 session 資料。")
+ raise SystemExit(0)
+by_agent={}
+for item in sessions:
+ aid = item.get("agentId","?")
+ by_agent[aid] = by_agent.get(aid, 0) + 1
+print("會話匯總:")
+for agent_id,count in sorted(by_agent.items()):
+ print("- %s: %s" % (agent_id, count))
+print("---------------------------------------")
+for item in sessions[:10]:
+ key = item.get("key","-")
+ model = item.get("model") or "-"
+ aid = item.get("agentId","?")
+ tokens = ""
+ it = item.get("inputTokens")
+ ot = item.get("outputTokens")
+ if it is not None:
+ tokens = " | in=%s out=%s" % (it, ot or 0)
+ print("%s | %s | %s%s" % (aid, key, model, tokens))
+' "$(openclaw_multiagent_sessions_json)"
+ }
+
+ openclaw_multiagent_health_check() {
+ send_stats "OpenClaw多智能體-健康檢查"
+ openclaw_multiagent_require_openclaw || return 1
+ local config_file
+ config_file=$(openclaw_multiagent_config_file)
+ echo "檢查設定檔: ${config_file:-$(openclaw_permission_config_file)}"
+ openclaw config validate || echo "⚠️ 配置校驗未通過,請檢查上方輸出。"
+ python3 -c '
+import json,sys,os
+agents=json.loads(sys.argv[1] or "[]")
+bindings=json.loads(sys.argv[2] or "[]")
+print("---------------------------------------")
+if not agents:
+ print("⚠️ 未發現配置智能體。")
+else:
+ for item in agents:
+ ws = item.get("workspace") or ""
+ aid = item.get("id","?")
+ if ws and os.path.isdir(os.path.expanduser(ws)):
+ state = "OK"
+ elif aid == "main":
+ state = "OK"
+ else:
+ state = "MISSING"
+ model = item.get("model") or "-"
+ bcount = item.get("bindings", 0)
+ print("agent=%s workspace=%s model=%s bindings=%s [%s]" % (aid, ws or "-", model, bcount, state))
+print("路由綁定數=%s" % len(bindings))
+print("✅ 多智能體健康檢查完成")
+' "$(openclaw_multiagent_agents_json)" "$(openclaw_multiagent_bindings_json)"
+ echo ""
+ echo "運行安全審計..."
+ openclaw security audit 2>/dev/null || echo "⚠️ 安全審計命令不可用"
+ }
+
+
+ openclaw_multiagent_set_identity() {
+ openclaw_multiagent_require_openclaw || return 1
+ openclaw_multiagent_list_agents
+ read -e -p "輸入要修改身分的智能體ID:" agent_id
+ [ -z "$agent_id" ] && { echo "ID 不能為空"; return 1; }
+ echo "修改選項(留空跳過):"
+ read -e -p "新名稱:" new_name
+ read -e -p "新 Emoji:" new_emoji
+ local cmd="openclaw agents set-identity --agent $agent_id"
+ [ -n "$new_name" ] && cmd="$cmd --name $new_name"
+ [ -n "$new_emoji" ] && cmd="$cmd --emoji $new_emoji"
+ echo "也可以從 IDENTITY.md 自動讀取身分資訊。"
+ read -e -p "是否從 IDENTITY.md 讀取? (y/n):" from_id
+ if [ "$from_id" = "y" ]; then
+ cmd="openclaw agents set-identity --agent $agent_id --from-identity"
+ fi
+ eval "$cmd"
+ }
+
+ openclaw_multiagent_cleanup_sessions() {
+ openclaw_multiagent_require_openclaw || return 1
+ echo "即將清理過期/冗餘會話資料..."
+ read -e -p "輸入 yes 確認:" confirm
+ [ "$confirm" != "yes" ] && { echo "已取消"; return 0; }
+ openclaw sessions cleanup
+ }
+
+ openclaw_multiagent_menu() {
+ send_stats "OpenClaw多智能體管理"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 多智能體管理"
+ echo "======================================="
+ openclaw_multiagent_render_status
+ echo "---------------------------------------"
+ echo "1. 新增智能體"
+ echo "2. 刪除智能體"
+ echo "3. 查看路由綁定"
+ echo "4. 新增路由綁定"
+ echo "5. 移除路由綁定"
+ echo "6. 查看會話概況"
+ echo "7. 執行多智能體健康檢查"
+ echo "8. 修改智能體身分(名稱/Emoji)"
+ echo "9. 清理過期會話"
+ echo "0. 返回上一級"
+ echo "---------------------------------------"
+ read -e -p "請輸入你的選擇:" multi_choice
+ case "$multi_choice" in
+ 1) openclaw_multiagent_add_agent; break_end ;;
+ 2) openclaw_multiagent_delete_agent; break_end ;;
+ 3) openclaw_multiagent_list_bindings; break_end ;;
+ 4) openclaw_multiagent_add_binding; break_end ;;
+ 5) openclaw_multiagent_remove_binding; break_end ;;
+ 6) openclaw_multiagent_show_sessions; break_end ;;
+ 7) openclaw_multiagent_health_check; break_end ;;
+ 8) openclaw_multiagent_set_identity; break_end ;;
+ 9) openclaw_multiagent_cleanup_sessions; break_end ;;
+ 0) return 0 ;;
+ *) echo "無效的選擇,請重試。"; sleep 1 ;;
+ esac
+ done
+ }
+
+
+openclaw_backup_restore_menu() {
+
+ send_stats "OpenClaw備份與還原"
+ while true; do
+ clear
+ echo "======================================="
+ echo "OpenClaw 備份與還原"
+ echo "======================================="
+ openclaw_backup_render_file_list
+ echo "---------------------------------------"
+ echo "1. 備份記憶全量"
+ echo "2. 還原記憶全量"
+ echo "3. 備份 OpenClaw 專案(預設安全模式)"
+ echo "4. 還原 OpenClaw 專案(進階/高風險)"
+ echo "5. 刪除備份文件"
+ echo "0. 返回上一級"
+ echo "---------------------------------------"
+ read -e -p "請輸入你的選擇:" backup_choice
+
+ case "$backup_choice" in
+ 1) openclaw_memory_backup_export ;;
+ 2) openclaw_memory_backup_import ;;
+ 3) openclaw_project_backup_export ;;
+ 4) openclaw_project_backup_import ;;
+ 5) openclaw_backup_delete_file ;;
+ 0) return 0 ;;
+ *)
+ echo "無效的選擇,請重試。"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+ update_moltbot() {
+ echo "更新 OpenClaw..."
+ send_stats "更新 OpenClaw..."
+ install_node_and_tools
+ git config --global url."${gh_proxy}github.com/".insteadOf ssh://git@github.com/
+ git config --global url."${gh_proxy}github.com/".insteadOf git@github.com:
+ npm install -g openclaw@latest
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ start_gateway
+ hash -r
+ add_app_id
+ echo "更新完成"
+ break_end
+ }
+
+
+ uninstall_moltbot() {
+ echo "卸載 OpenClaw..."
+ send_stats "卸載 OpenClaw..."
+ openclaw uninstall
+ npm uninstall -g openclaw
+ crontab -l 2>/dev/null | grep -v "s gateway" | crontab -
+ rm -rf "$HOME/.openclaw"
+ [ "$HOME" != "/root" ] && [ -d /root/.openclaw ] && echo "⚠️ 偵測到 root 目錄下仍存在 /root/.openclaw,如需清理請手動處理"
+ hash -r
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "卸載完成"
+ break_end
+ }
+
+ nano_openclaw_json() {
+ send_stats "編輯 OpenClaw 設定檔"
+ install nano
+ nano "$(openclaw_get_config_file)"
+ start_gateway
+ }
+
+
+
+
+
+
+ openclaw_find_webui_domain() {
+ local conf domain_list
+
+ domain_list=$(
+ grep -R "18789" /home/web/conf.d/*.conf 2>/dev/null \
+ | awk -F: '{print $1}' \
+ | sort -u \
+ | while read conf; do
+ basename "$conf" .conf
+ done
+ )
+
+ if [ -n "$domain_list" ]; then
+ echo "$domain_list"
+ fi
+ }
+
+
+
+ openclaw_show_webui_addr() {
+ local local_ip token domains
+
+ echo "=================================="
+ echo "OpenClaw WebUI 存取位址"
+ local_ip="127.0.0.1"
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+ echo
+ echo "本機地址:"
+ echo "http://${local_ip}:18789/#token=${token}"
+
+ domains=$(openclaw_find_webui_domain)
+ if [ -n "$domains" ]; then
+ echo "網域名稱地址:"
+ echo "$domains" | while read d; do
+ echo "https://${d}/#token=${token}"
+ done
+ fi
+
+ echo "=================================="
+ }
+
+
+
+ # 新增網域(呼叫你給的函數)
+ openclaw_domain_webui() {
+ add_yuming
+ ldnmp_Proxy ${yuming} 127.0.0.1 18789
+
+ token=$(
+ openclaw dashboard 2>/dev/null \
+ | sed -n 's/.*:18789\/#token=\([a-f0-9]\+\).*/\1/p' \
+ | head -n 1
+ )
+
+ clear
+ echo "訪問地址:"
+ echo "https://${yuming}/#token=$token"
+ echo "先造訪URL觸發設備ID,然後回車下一步進行配對。"
+ read
+ echo -e "${gl_kjlan}正在載入設備列表…${gl_bai}"
+ # 自動新增網域到 allowedOrigins
+ config_file=$(openclaw_get_config_file)
+ if [ -f "$config_file" ]; then
+ new_origin="https://${yuming}"
+ # 使用 jq 安全性修改 JSON,確保結構存在且不重複新增域名
+ if command -v jq >/dev/null 2>&1; then
+ tmp_json=$(mktemp)
+ jq 'if .gateway.controlUi == null then .gateway.controlUi = {"allowedOrigins": ["http://127.0.0.1"]} else . end | if (.gateway.controlUi.allowedOrigins | contains([$origin]) | not) then .gateway.controlUi.allowedOrigins += [$origin] else . end' --arg origin "$new_origin" "$config_file" > "$tmp_json" && mv "$tmp_json" "$config_file"
+ echo -e "${gl_kjlan}已將域名${yuming}加入 allowedOrigins 配置${gl_bai}"
+ openclaw gateway restart >/dev/null 2>&1
+ fi
+ fi
+
+ openclaw devices list
+
+ read -e -p "請輸入 Request_Key:" Request_Key
+
+ [ -z "$Request_Key" ] && {
+ echo "Request_Key 不能為空"
+ return 1
+ }
+
+ openclaw devices approve "$Request_Key"
+
+ }
+
+ # 刪除域名
+ openclaw_remove_domain() {
+ echo "網域格式 example.com 不含https://"
+ web_del
+ }
+
+ # 主選單
+ openclaw_webui_menu() {
+
+ send_stats "WebUI存取與設定"
+ while true; do
+ clear
+ openclaw_show_webui_addr
+ echo
+ echo "1. 添加網域訪問"
+ echo "2. 刪除網域名稱訪問"
+ echo "0. 退出"
+ echo
+ read -e -p "請選擇:" choice
+
+ case "$choice" in
+ 1)
+ openclaw_domain_webui
+ echo
+ read -p "按回車返回選單..."
+ ;;
+ 2)
+ openclaw_remove_domain
+ read -p "按回車返回選單..."
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "無效選項"
+ sleep 1
+ ;;
+ esac
+ done
+ }
+
+
+
+ # 主循環
+ while true; do
+ show_menu
+ read choice
+ case $choice in
+ 1) install_moltbot ;;
+ 2) start_bot ;;
+ 3) stop_bot ;;
+ 4) view_logs ;;
+ 5) change_model ;;
+ 6) openclaw_api_manage_menu ;;
+ 7) change_tg_bot_code ;;
+ 8) install_plugin ;;
+ 9) install_skill ;;
+ 10) nano_openclaw_json ;;
+ 11) send_stats "初始化配置精靈"
+ openclaw onboard --install-daemon
+ break_end
+ ;;
+ 12) send_stats "健康檢測與修復"
+ openclaw doctor --fix
+ break_end
+ ;;
+ 13) openclaw_webui_menu ;;
+ 14) send_stats "TUI命令列對話"
+ openclaw tui
+ break_end
+ ;;
+ 15) openclaw_memory_menu ;;
+ 16) openclaw_permission_menu ;;
+ 17) openclaw_multiagent_menu ;;
+ 18) openclaw_backup_restore_menu ;;
+ 19) update_moltbot ;;
+ 20) uninstall_moltbot ;;
+ *) break ;;
+ esac
+ done
+
+}
+
+
+
+
+linux_panel() {
+
+local sub_choice="$1"
+
+clear
+cd ~
+install git
+echo -e "${gl_kjlan}正在更新應用程式清單請稍等…${gl_bai}"
+if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+fi
+
+while true; do
+
+ if [ -z "$sub_choice" ]; then
+ clear
+ echo -e "應用市場"
+ echo -e "${gl_kjlan}-------------------------"
+
+ local app_numbers=$([ -f /home/docker/appno.txt ] && cat /home/docker/appno.txt || echo "")
+
+ # 用循環設定顏色
+ for i in {1..150}; do
+ if echo "$app_numbers" | grep -q "^$i$"; then
+ declare "color$i=${gl_lv}"
+ else
+ declare "color$i=${gl_bai}"
+ fi
+ done
+
+ echo -e "${gl_kjlan}1. ${color1}寶塔面板官方版${gl_kjlan}2. ${color2}aaPanel寶塔國際版"
+ echo -e "${gl_kjlan}3. ${color3}1Panel新一代管理面板${gl_kjlan}4. ${color4}NginxProxyManager視覺化面板"
+ echo -e "${gl_kjlan}5. ${color5}OpenList多重儲存文件列表程序${gl_kjlan}6. ${color6}Ubuntu遠端桌面網頁版"
+ echo -e "${gl_kjlan}7. ${color7}哪吒探針VPS監控面板${gl_kjlan}8. ${color8}QB離線BT磁力下載面板"
+ echo -e "${gl_kjlan}9. ${color9}Poste.io郵件伺服器程式${gl_kjlan}10. ${color10}RocketChat多人線上聊天系統"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}11. ${color11}禪道專案管理軟體${gl_kjlan}12. ${color12}青龍面板定時任務管理平台"
+ echo -e "${gl_kjlan}13. ${color13}Cloudreve網盤${gl_huang}★${gl_bai} ${gl_kjlan}14. ${color14}簡單圖床圖片管理程序"
+ echo -e "${gl_kjlan}15. ${color15}emby多媒體管理系統${gl_kjlan}16. ${color16}Speedtest測速板"
+ echo -e "${gl_kjlan}17. ${color17}AdGuardHome去廣告軟體${gl_kjlan}18. ${color18}onlyoffice線上辦公OFFICE"
+ echo -e "${gl_kjlan}19. ${color19}雷池WAF防火牆面板${gl_kjlan}20. ${color20}portainer容器管理面板"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}21. ${color21}VScode網頁版${gl_kjlan}22. ${color22}UptimeKuma監控工具"
+ echo -e "${gl_kjlan}23. ${color23}Memos網頁備忘錄${gl_kjlan}24. ${color24}Webtop遠端桌面網頁版${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}25. ${color25}Nextcloud網站${gl_kjlan}26. ${color26}QD-Today定時任務管理框架"
+ echo -e "${gl_kjlan}27. ${color27}Dockge容器堆疊管理面板${gl_kjlan}28. ${color28}LibreSpeed測速工具"
+ echo -e "${gl_kjlan}29. ${color29}searxng聚合搜尋站${gl_huang}★${gl_bai} ${gl_kjlan}30. ${color30}PhotoPrism私有相簿系統"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}31. ${color31}StirlingPDF工具大全${gl_kjlan}32. ${color32}drawio免費的線上圖表軟體${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${color33}Sun-Panel導航面板${gl_kjlan}34. ${color34}Pingvin-Share文件分享平台"
+ echo -e "${gl_kjlan}35. ${color35}極簡朋友圈${gl_kjlan}36. ${color36}LobeChatAI聊天聚合網站"
+ echo -e "${gl_kjlan}37. ${color37}MyIP工具箱${gl_huang}★${gl_bai} ${gl_kjlan}38. ${color38}小雅alist全家桶"
+ echo -e "${gl_kjlan}39. ${color39}Bililive直播錄影工具${gl_kjlan}40. ${color40}webssh網頁版SSH連線工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}41. ${color41}耗子管理面板${gl_kjlan}42. ${color42}Nexterm遠端連線工具"
+ echo -e "${gl_kjlan}43. ${color43}RustDesk遠端桌面(服務端)${gl_huang}★${gl_bai} ${gl_kjlan}44. ${color44}RustDesk遠端桌面(中繼端)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}45. ${color45}Docker加速站${gl_kjlan}46. ${color46}GitHub加速站${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}47. ${color47}普羅米修斯監控${gl_kjlan}48. ${color48}普羅米修斯(主機監控)"
+ echo -e "${gl_kjlan}49. ${color49}普羅米修斯(容器監控)${gl_kjlan}50. ${color50}補貨監控工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}51. ${color51}PVE開小雞面板${gl_kjlan}52. ${color52}DPanel容器管理面板"
+ echo -e "${gl_kjlan}53. ${color53}llama3聊天AI大模型${gl_kjlan}54. ${color54}AMH主機建站管理面板"
+ echo -e "${gl_kjlan}55. ${color55}FRP內網穿透(服務端)${gl_huang}★${gl_bai} ${gl_kjlan}56. ${color56}FRP內網穿透(客戶端)${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}57. ${color57}Deepseek聊天AI大模型${gl_kjlan}58. ${color58}Dify大模型知識庫${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}59. ${color59}NewAPI大模型資產管理${gl_kjlan}60. ${color60}JumpServer開源堡壘機"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}61. ${color61}線上翻譯伺服器${gl_kjlan}62. ${color62}RAGFlow大模型知識庫"
+ echo -e "${gl_kjlan}63. ${color63}OpenWebUI自架AI平台${gl_huang}★${gl_bai} ${gl_kjlan}64. ${color64}it-tools工具箱"
+ echo -e "${gl_kjlan}65. ${color65}n8n自動化工作流程平台${gl_huang}★${gl_bai} ${gl_kjlan}66. ${color66}yt-dlp影片下載工具"
+ echo -e "${gl_kjlan}67. ${color67}ddns-go動態DNS管理工具${gl_huang}★${gl_bai} ${gl_kjlan}68. ${color68}AllinSSL憑證管理平台"
+ echo -e "${gl_kjlan}69. ${color69}SFTPGo檔案傳輸工具${gl_kjlan}70. ${color70}AstrBot聊天機器人框架"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}71. ${color71}Navidrome私有音樂伺服器${gl_kjlan}72. ${color72}bitwarden密碼管理器${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}73. ${color73}LibreTV私有影視${gl_kjlan}74. ${color74}MoonTV私有影視"
+ echo -e "${gl_kjlan}75. ${color75}Melody音樂精靈${gl_kjlan}76. ${color76}線上DOS老遊戲"
+ echo -e "${gl_kjlan}77. ${color77}迅雷離線下載工具${gl_kjlan}78. ${color78}PandaWiki智慧文件管理系統"
+ echo -e "${gl_kjlan}79. ${color79}Beszel伺服器監控${gl_kjlan}80. ${color80}linkwarden書籤管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}81. ${color81}JitsiMeet視訊會議${gl_kjlan}82. ${color82}gpt-load高性能AI透明代理"
+ echo -e "${gl_kjlan}83. ${color83}komari伺服器監控工具${gl_kjlan}84. ${color84}Wallos個人財務管理工具"
+ echo -e "${gl_kjlan}85. ${color85}immich圖片影片管理器${gl_kjlan}86. ${color86}jellyfin媒體管理系統"
+ echo -e "${gl_kjlan}87. ${color87}SyncTV一起看片神器${gl_kjlan}88. ${color88}Owncast自架直播平台"
+ echo -e "${gl_kjlan}89. ${color89}FileCodeBox檔案快遞${gl_kjlan}90. ${color90}matrix去中心化聊天協議"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}91. ${color91}gitea私有程式碼倉庫${gl_kjlan}92. ${color92}FileBrowser文件管理器"
+ echo -e "${gl_kjlan}93. ${color93}Dufs極簡靜態檔案伺服器${gl_kjlan}94. ${color94}Gopeed高速下載工具"
+ echo -e "${gl_kjlan}95. ${color95}paperless文件管理平台${gl_kjlan}96. ${color96}2FAuth自架二步驟驗證器"
+ echo -e "${gl_kjlan}97. ${color97}WireGuard組網(服務端)${gl_kjlan}98. ${color98}WireGuard組網(客戶端)"
+ echo -e "${gl_kjlan}99. ${color99}DSM群暉虛擬機${gl_kjlan}100. ${color100}Syncthing點對點檔案同步工具"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}101. ${color101}AI影片生成工具${gl_kjlan}102. ${color102}VoceChat多人線上聊天系統"
+ echo -e "${gl_kjlan}103. ${color103}Umami網站統計工具${gl_kjlan}104. ${color104}Stream四層代理轉送工具"
+ echo -e "${gl_kjlan}105. ${color105}思源筆記${gl_kjlan}106. ${color106}Drawnix開源白板工具"
+ echo -e "${gl_kjlan}107. ${color107}PanSou網盤搜尋${gl_kjlan}108. ${color108}LangBot聊天機器人"
+ echo -e "${gl_kjlan}109. ${color109}ZFile線上網路磁碟${gl_kjlan}110. ${color110}Karakeep書籤管理"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}111. ${color111}多格式檔案轉換工具${gl_kjlan}112. ${color112}Lucky大內網穿透工具"
+ echo -e "${gl_kjlan}113. ${color113}Firefox瀏覽器${gl_kjlan}114. ${color114}OpenClaw機器人管理工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}115. ${color115}Hermes機器人管理工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}第三方應用程式列表"
+ echo -e "${gl_kjlan}想要讓你的應用程式出現在這裡?查看開發者指南:${gl_huang}https://dev.kejilion.sh/${gl_bai}"
+
+ for f in "$HOME"/apps/*.conf; do
+ [ -e "$f" ] || continue
+ local base_name=$(basename "$f" .conf)
+ # 取得應用描述
+ local app_text=$(grep "app_text=" "$f" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
+
+ # 檢查安裝狀態 (匹配 appno.txt 中的 ID)
+ # 這裡假設 appno.txt 中記錄的是 base_name (即檔名)
+ if echo "$app_numbers" | grep -q "^$base_name$"; then
+ # 如果已安裝:顯示 base_name - 描述 [已安裝] (綠色)
+ echo -e "${gl_kjlan}$base_name${gl_bai} - ${gl_lv}$app_text[已安裝]${gl_bai}"
+ else
+ # 如果未安裝:正常顯示
+ echo -e "${gl_kjlan}$base_name${gl_bai} - $app_text"
+ fi
+ done
+
+
+
+ echo -e "${gl_kjlan}-------------------------"
+ echo -e "${gl_kjlan}b. ${gl_bai}備份全部應用數據${gl_kjlan}r. ${gl_bai}還原全部應用數據"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
+ fi
+
+ case $sub_choice in
+ 1|bt|baota)
+ local app_id="1"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="寶塔面板"
+ local panelurl="https://www.bt.cn/new/index.html"
+
+ panel_app_install() {
+ if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+
+
+ ;;
+ 2|aapanel)
+
+
+ local app_id="2"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="aapanel"
+ local panelurl="https://www.aapanel.com/new/index.html"
+
+ panel_app_install() {
+ URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
+ }
+
+ panel_app_manage() {
+ bt
+ }
+
+ panel_app_uninstall() {
+ curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
+ chmod +x bt-uninstall.sh
+ ./bt-uninstall.sh
+ }
+
+ install_panel
+
+ ;;
+ 3|1p|1panel)
+
+ local app_id="3"
+ local lujing="command -v 1pctl"
+ local panelname="1Panel"
+ local panelurl="https://1panel.cn/"
+
+ panel_app_install() {
+ install bash
+ bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
+ }
+
+ panel_app_manage() {
+ 1pctl user-info
+ 1pctl update password
+ }
+
+ panel_app_uninstall() {
+ 1pctl uninstall
+ }
+
+ install_panel
+
+ ;;
+ 4|npm)
+
+ local app_id="4"
+ local docker_name="npm"
+ local docker_img="jc21/nginx-proxy-manager:latest"
+ local docker_port=81
+
+ docker_rum() {
+
+ docker run -d \
+ --name=$docker_name \
+ -p ${docker_port}:81 \
+ -p 80:80 \
+ -p 443:443 \
+ -v /home/docker/npm/data:/data \
+ -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
+ --restart=always \
+ $docker_img
+
+
+ }
+
+ local docker_describe="一個Nginx反向代理工具面板,不支援新增網域存取。"
+ local docker_url="官網介紹: https://nginxproxymanager.com/"
+ local docker_use="echo \"初始使用者名稱: admin@example.com\""
+ local docker_passwd="echo \"初始密碼: changeme\""
+ local app_size="1"
+
+ docker_app
+
+ ;;
+
+ 5|openlist)
+
+ local app_id="5"
+ local docker_name="openlist"
+ local docker_img="openlistteam/openlist:latest-aria2"
+ local docker_port=5244
+
+ docker_rum() {
+
+ mkdir -p /home/docker/openlist
+ chmod -R 777 /home/docker/openlist
+
+ docker run -d \
+ --restart=always \
+ -v /home/docker/openlist:/opt/openlist/data \
+ -p ${docker_port}:5244 \
+ -e PUID=0 \
+ -e PGID=0 \
+ -e UMASK=022 \
+ --name="openlist" \
+ openlistteam/openlist:latest-aria2
+
+ }
+
+
+ local docker_describe="一個支援多種存儲,支援網頁瀏覽和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驅動"
+ local docker_url="官網介紹:${gh_https_url}github.com/OpenListTeam/OpenList"
+ local docker_use="docker exec openlist ./openlist admin random"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 6|webtop-ubuntu)
+
+ local app_id="6"
+ local docker_name="webtop-ubuntu"
+ local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
+ local docker_port=3006
+
+ docker_rum() {
+
+ read -e -p "設定登入用戶名:" admin
+ read -e -p "設定登入用戶密碼:" admin_password
+ docker run -d \
+ --name=webtop-ubuntu \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:ubuntu-kde
+
+
+ }
+
+
+ local docker_describe="webtop基於Ubuntu的容器。若IP無法訪問,請新增網域訪問。"
+ local docker_url="官網介紹: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+
+ ;;
+ 7|nezha)
+ clear
+ send_stats "搭建哪吒"
+
+ local app_id="7"
+ local docker_name="nezha-dashboard"
+ local docker_port=8008
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+ clear
+ echo -e "哪吒監控$check_docker $update_status"
+ echo "開源、輕量、易用的伺服器監控與維運工具"
+ echo "官網搭建文件: https://nezha.wiki/guide/dashboard.html"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ fi
+ echo ""
+ echo "------------------------"
+ echo "1. 使用"
+ echo "------------------------"
+ echo "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "輸入你的選擇:" choice
+
+ case $choice in
+ 1)
+ check_disk_space 1
+ install unzip jq
+ install_docker
+ curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
+ local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
+ check_docker_app_ip
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+ ;;
+
+ 8|qb|QB)
+
+ local app_id="8"
+ local docker_name="qbittorrent"
+ local docker_img="lscr.io/linuxserver/qbittorrent:latest"
+ local docker_port=8081
+
+ docker_rum() {
+
+ docker run -d \
+ --name=qbittorrent \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e WEBUI_PORT=${docker_port} \
+ -e TORRENTING_PORT=56881 \
+ -p ${docker_port}:${docker_port} \
+ -p 56881:56881 \
+ -p 56881:56881/udp \
+ -v /home/docker/qbittorrent/config:/config \
+ -v /home/docker/qbittorrent/downloads:/downloads \
+ --restart=always \
+ lscr.io/linuxserver/qbittorrent:latest
+
+ }
+
+ local docker_describe="qbittorrent離線BT磁力下載服務"
+ local docker_url="官網介紹: https://hub.docker.com/r/linuxserver/qbittorrent"
+ local docker_use="sleep 3"
+ local docker_passwd="docker logs qbittorrent"
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 9|mail)
+ send_stats "搭建郵局"
+ clear
+ install telnet
+ local app_id="9"
+ local docker_name=“mailserver”
+ while true; do
+ check_docker_app
+ check_docker_image_update $docker_name
+
+ clear
+ echo -e "郵局服務$check_docker $update_status"
+ echo "poste.io 是一個開源的郵件伺服器解決方案,"
+ echo "影片介紹: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+
+ echo ""
+ echo "連接埠偵測"
+ port=25
+ timeout=3
+ if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
+ echo -e "${gl_lv}連接埠$port目前可用${gl_bai}"
+ else
+ echo -e "${gl_hong}連接埠$port目前不可用${gl_bai}"
+ fi
+ echo ""
+
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ yuming=$(cat /home/docker/mail.txt)
+ echo "訪問地址:"
+ echo "https://$yuming"
+ fi
+
+ echo "------------------------"
+ echo "1. 安裝 2. 更新 3. 卸載"
+ echo "------------------------"
+ echo "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "輸入你的選擇:" choice
+
+ case $choice in
+ 1)
+ setup_docker_dir
+ check_disk_space 2 /home/docker
+ read -e -p "請設定郵箱網域名稱 例如 mail.yuming.com :" yuming
+ mkdir -p /home/docker
+ echo "$yuming" > /home/docker/mail.txt
+ echo "------------------------"
+ ip_address
+ echo "先解析這些DNS記錄"
+ echo "A mail $ipv4_address"
+ echo "CNAME imap $yuming"
+ echo "CNAME pop $yuming"
+ echo "CNAME smtp $yuming"
+ echo "MX @ $yuming"
+ echo "TXT @ v=spf1 mx ~all"
+ echo "TXT ? ?"
+ echo ""
+ echo "------------------------"
+ echo "按任意鍵繼續..."
+ read -n 1 -s -r -p ""
+
+ install jq
+ install_docker
+
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.io
+
+
+ add_app_id
+
+ clear
+ echo "poste.io已經安裝完成"
+ echo "------------------------"
+ echo "您可以使用以下地址存取poste.io:"
+ echo "https://$yuming"
+ echo ""
+
+ ;;
+
+ 2)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.i
+ yuming=$(cat /home/docker/mail.txt)
+ docker run \
+ --net=host \
+ -e TZ=Europe/Prague \
+ -v /home/docker/mail:/data \
+ --name "mailserver" \
+ -h "$yuming" \
+ --restart=always \
+ -d analogic/poste.i
+
+
+ add_app_id
+
+ clear
+ echo "poste.io已經安裝完成"
+ echo "------------------------"
+ echo "您可以使用以下地址存取poste.io:"
+ echo "https://$yuming"
+ echo ""
+ ;;
+ 3)
+ docker rm -f mailserver
+ docker rmi -f analogic/poste.io
+ rm /home/docker/mail.txt
+ rm -rf /home/docker/mail
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "應用程式已解除安裝"
+ ;;
+
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 10|rocketchat)
+
+ local app_id="10"
+ local app_name="Rocket.Chat聊天系統"
+ local app_text="Rocket.Chat 是一個開源的團隊通訊平台,支援即時聊天、音訊視訊通話、檔案共享等多種功能,"
+ local app_url="官方介紹: https://www.rocket.chat/"
+ local docker_name="rocketchat"
+ local docker_port="3897"
+ local app_size="2"
+
+ docker_app_install() {
+ docker run --name db -d --restart=always \
+ -v /home/docker/mongo/dump:/dump \
+ mongo:latest --replSet rs5 --oplogSize 256
+ sleep 1
+ docker exec db mongosh --eval "printjson(rs.initiate())"
+ sleep 5
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+
+ clear
+ ip_address
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat:latest
+ docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
+ clear
+ ip_address
+ echo "rocket.chat已經安裝完成"
+ check_docker_app_ip
+ }
+
+ docker_app_uninstall() {
+ docker rm -f rocketchat
+ docker rmi -f rocket.chat
+ docker rm -f db
+ docker rmi -f mongo:latest
+ rm -rf /home/docker/mongo
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+ ;;
+
+
+
+ 11|zentao)
+ local app_id="11"
+ local docker_name="zentao-server"
+ local docker_img="idoop/zentao:latest"
+ local docker_port=82
+
+
+ docker_rum() {
+
+
+ docker run -d -p ${docker_port}:80 \
+ -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
+ -e BIND_ADDRESS="false" \
+ -v /home/docker/zentao-server/:/opt/zbox/ \
+ --add-host smtp.exmail.qq.com:163.177.90.125 \
+ --name zentao-server \
+ --restart=always \
+ idoop/zentao:latest
+
+
+ }
+
+ local docker_describe="禪道是通用的專案管理軟體"
+ local docker_url="官網介紹: https://www.zentao.net/"
+ local docker_use="echo \"初始使用者名稱: admin\""
+ local docker_passwd="echo \"初始密碼: 123456\""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 12|qinglong)
+ local app_id="12"
+ local docker_name="qinglong"
+ local docker_img="whyour/qinglong:latest"
+ local docker_port=5700
+
+ docker_rum() {
+
+
+ docker run -d \
+ -v /home/docker/qinglong/data:/ql/data \
+ -p ${docker_port}:5700 \
+ --name qinglong \
+ --hostname qinglong \
+ --restart=always \
+ whyour/qinglong:latest
+
+
+ }
+
+ local docker_describe="青龍面板是一個定時任務管理平台"
+ local docker_url="官網介紹:${gh_proxy}github.com/whyour/qinglong"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 13|cloudreve)
+
+ local app_id="13"
+ local app_name="cloudreve網盤"
+ local app_text="cloudreve是一個支援多家雲端儲存的網盤系統"
+ local app_url="影片介紹: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
+ local docker_name="cloudreve"
+ local docker_port="5212"
+ local app_size="2"
+
+ docker_app_install() {
+ cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
+ curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
+ sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
+ cd /home/docker/cloud/
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
+
+
+ docker_app_update() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ cd /home/docker/cloud/ && docker compose up -d
+ }
+
+
+ docker_app_uninstall() {
+ cd /home/docker/cloud/ && docker compose down --rmi all
+ rm -rf /home/docker/cloud
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+ ;;
+
+ 14|easyimage)
+ local app_id="14"
+ local docker_name="easyimage"
+ local docker_img="ddsderek/easyimage:latest"
+ local docker_port=8014
+ docker_rum() {
+
+ docker run -d \
+ --name easyimage \
+ -p ${docker_port}:80 \
+ -e TZ=Asia/Shanghai \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -v /home/docker/easyimage/config:/app/web/config \
+ -v /home/docker/easyimage/i:/app/web/i \
+ --restart=always \
+ ddsderek/easyimage:latest
+
+ }
+
+ local docker_describe="簡單圖床是一個簡單的圖床程序"
+ local docker_url="官網介紹:${gh_proxy}github.com/icret/EasyImages2.0"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 15|emby)
+ local app_id="15"
+ local docker_name="emby"
+ local docker_img="linuxserver/emby:latest"
+ local docker_port=8015
+
+ docker_rum() {
+
+ docker run -d --name=emby --restart=always \
+ -v /home/docker/emby/config:/config \
+ -v /home/docker/emby/share1:/mnt/share1 \
+ -v /home/docker/emby/share2:/mnt/share2 \
+ -v /mnt/notify:/mnt/notify \
+ -p ${docker_port}:8096 \
+ -e UID=1000 -e GID=100 -e GIDLIST=100 \
+ linuxserver/emby:latest
+
+ }
+
+
+ local docker_describe="emby是一個主從式架構的媒體伺服器軟體,可以用來整理伺服器上的視訊和音頻,並將音頻和視訊串流傳輸到客戶端設備"
+ local docker_url="官網介紹: https://emby.media/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 16|looking)
+ local app_id="16"
+ local docker_name="looking-glass"
+ local docker_img="wikihostinc/looking-glass-server"
+ local docker_port=8016
+
+
+ docker_rum() {
+
+ docker run -d --name looking-glass --restart=always -p ${docker_port}:80 wikihostinc/looking-glass-server
+
+ }
+
+ local docker_describe="Speedtest測速面板是VPS網路速度測試工具,多項測試功能,還可以即時監控VPS進出站流量"
+ local docker_url="官網介紹:${gh_proxy}github.com/wikihost-opensource/als"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+ 17|adguardhome)
+
+ local app_id="17"
+ local docker_name="adguardhome"
+ local docker_img="adguard/adguardhome"
+ local docker_port=8017
+
+ docker_rum() {
+
+ docker run -d \
+ --name adguardhome \
+ -v /home/docker/adguardhome/work:/opt/adguardhome/work \
+ -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
+ -p 53:53/tcp \
+ -p 53:53/udp \
+ -p ${docker_port}:3000/tcp \
+ --restart=always \
+ adguard/adguardhome
+
+
+ }
+
+
+ local docker_describe="AdGuardHome是一款全網廣告攔截與反追蹤軟體,未來不只一個DNS伺服器。"
+ local docker_url="官網介紹: https://hub.docker.com/r/adguard/adguardhome"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+
+ 18|onlyoffice)
+
+ local app_id="18"
+ local docker_name="onlyoffice"
+ local docker_img="onlyoffice/documentserver"
+ local docker_port=8018
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:80 \
+ --restart=always \
+ --name onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
+ -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
+ onlyoffice/documentserver
+
+
+ }
+
+ local docker_describe="onlyoffice是一款開源的線上office工具,太強大了!"
+ local docker_url="官網介紹: https://www.onlyoffice.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+
+ ;;
+
+ 19|safeline)
+ send_stats "搭建雷池"
+
+ local app_id="19"
+ local docker_name=safeline-mgt
+ local docker_port=9443
+ while true; do
+ check_docker_app
+ clear
+ echo -e "雷池服務$check_docker"
+ echo "雷池是長亭科技開發的WAF站點防火牆程式面板,可反代站點進行自動化防禦"
+ echo "影片介紹: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "$docker_name"; then
+ check_docker_app_ip
+ fi
+ echo ""
+
+ echo "------------------------"
+ echo "1. 安裝 2. 更新 3. 重設密碼 4. 解除安裝"
+ echo "------------------------"
+ echo "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "輸入你的選擇:" choice
+
+ case $choice in
+ 1)
+ install_docker
+ check_disk_space 5
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
+
+ add_app_id
+ clear
+ echo "雷池WAF面板已經安裝完成"
+ check_docker_app_ip
+ docker exec safeline-mgt resetadmin
+
+ ;;
+
+ 2)
+ bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
+ docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
+ echo ""
+
+ add_app_id
+ clear
+ echo "雷池WAF面板已經更新完成"
+ check_docker_app_ip
+ ;;
+ 3)
+ docker exec safeline-mgt resetadmin
+ ;;
+ 4)
+ cd /data/safeline
+ docker compose down --rmi all
+
+ sed -i "/\b${app_id}\b/d" /home/docker/appno.txt
+ echo "如果你是預設安裝目錄那現在項目已經卸載。如果你是自訂安裝目錄你需要到安裝目錄下自行執行:"
+ echo "docker compose down && docker compose down --rmi all"
+ ;;
+ *)
+ break
+ ;;
+
+ esac
+ break_end
+ done
+
+ ;;
+
+ 20|portainer)
+ local app_id="20"
+ local docker_name="portainer"
+ local docker_img="portainer/portainer"
+ local docker_port=8020
+
+ docker_rum() {
+
+ docker run -d \
+ --name portainer \
+ -p ${docker_port}:9000 \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/portainer:/data \
+ --restart=always \
+ portainer/portainer
+
+ }
+
+
+ local docker_describe="portainer是一個輕量級的docker容器管理面板"
+ local docker_url="官網介紹: https://www.portainer.io/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
+
+ 21|vscode)
+ local app_id="21"
+ local docker_name="vscode-web"
+ local docker_img="codercom/code-server"
+ local docker_port=8021
+
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart=always codercom/code-server
+
+ }
+
+
+ local docker_describe="VScode是一款強大的線上程式碼編寫工具"
+ local docker_url="官網介紹:${gh_proxy}github.com/coder/code-server"
+ local docker_use="sleep 3"
+ local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 22|uptime-kuma)
+ local app_id="22"
+ local docker_name="uptime-kuma"
+ local docker_img="louislam/uptime-kuma:latest"
+ local docker_port=8022
+
+
+ docker_rum() {
+
+ docker run -d \
+ --name=uptime-kuma \
+ -p ${docker_port}:3001 \
+ -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
+ --restart=always \
+ louislam/uptime-kuma:latest
+
+ }
+
+
+ local docker_describe="Uptime Kuma 易於使用的自架監控工具"
+ local docker_url="官網介紹:${gh_proxy}github.com/louislam/uptime-kuma"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 23|memos)
+ local app_id="23"
+ local docker_name="memos"
+ local docker_img="neosmemo/memos:stable"
+ local docker_port=8023
+
+ docker_rum() {
+
+ docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart=always neosmemo/memos:stable
+
+ }
+
+ local docker_describe="Memos是一款輕量、自架的備忘錄中心"
+ local docker_url="官網介紹:${gh_proxy}github.com/usememos/memos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 24|webtop)
+ local app_id="24"
+ local docker_name="webtop"
+ local docker_img="lscr.io/linuxserver/webtop:latest"
+ local docker_port=8024
+
+ docker_rum() {
+
+ read -e -p "設定登入用戶名:" admin
+ read -e -p "設定登入用戶密碼:" admin_password
+ docker run -d \
+ --name=webtop \
+ --security-opt seccomp=unconfined \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SUBFOLDER=/ \
+ -e TITLE=Webtop \
+ -e CUSTOM_USER=${admin} \
+ -e PASSWORD=${admin_password} \
+ -e LC_ALL=zh_CN.UTF-8 \
+ -e DOCKER_MODS=linuxserver/mods:universal-package-install \
+ -e INSTALL_PACKAGES=font-noto-cjk \
+ -p ${docker_port}:3000 \
+ -v /home/docker/webtop/data:/config \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --shm-size="1gb" \
+ --restart=always \
+ lscr.io/linuxserver/webtop:latest
+
+ }
+
+
+ local docker_describe="webtop基於Alpine的中文版容器。若IP無法訪問,請新增網域訪問。"
+ local docker_url="官網介紹: https://docs.linuxserver.io/images/docker-webtop/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 25|nextcloud)
+ local app_id="25"
+ local docker_name="nextcloud"
+ local docker_img="nextcloud:latest"
+ local docker_port=8025
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+
+ }
+
+ local docker_describe="Nextcloud擁有超過 400,000 個部署,是您可以下載的最受歡迎的本地內容協作平台"
+ local docker_url="官網介紹: https://nextcloud.com/"
+ local docker_use="echo \"帳號: nextcloud 密碼:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
+
+ 26|qd)
+ local app_id="26"
+ local docker_name="qd"
+ local docker_img="qdtoday/qd:latest"
+ local docker_port=8026
+
+ docker_rum() {
+
+ docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+
+ }
+
+ local docker_describe="QD-Today是HTTP請求定時任務自動執行框架"
+ local docker_url="官網介紹: https://qd-today.github.io/qd/zh_CN/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 27|dockge)
+ local app_id="27"
+ local docker_name="dockge"
+ local docker_img="louislam/dockge:latest"
+ local docker_port=8027
+
+ docker_rum() {
+
+ docker run -d --name dockge --restart=always -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+
+ }
+
+ local docker_describe="dockge是一個可視化的docker-compose容器管理面板"
+ local docker_url="官網介紹:${gh_proxy}github.com/louislam/dockge"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 28|speedtest)
+ local app_id="28"
+ local docker_name="speedtest"
+ local docker_img="ghcr.io/librespeed/speedtest"
+ local docker_port=8028
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 --name speedtest --restart=always ghcr.io/librespeed/speedtest
+
+ }
+
+ local docker_describe="librespeed是用Javascript實現的輕量級速度測試工具,即開即用"
+ local docker_url="官網介紹:${gh_proxy}github.com/librespeed/speedtest"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 29|searxng)
+ local app_id="29"
+ local docker_name="searxng"
+ local docker_img="searxng/searxng"
+ local docker_port=8029
+
+ docker_rum() {
+
+ docker run -d \
+ --name searxng \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v "/home/docker/searxng:/etc/searxng" \
+ searxng/searxng
+
+ }
+
+ local docker_describe="searxng是一個私有且隱私的搜尋引擎站點"
+ local docker_url="官網介紹: https://hub.docker.com/r/alandoyle/searxng"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 30|photoprism)
+ local app_id="30"
+ local docker_name="photoprism"
+ local docker_img="photoprism/photoprism:latest"
+ local docker_port=8030
+ local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+
+ docker_rum() {
+
+ docker run -d \
+ --name photoprism \
+ --restart=always \
+ --security-opt seccomp=unconfined \
+ --security-opt apparmor=unconfined \
+ -p ${docker_port}:2342 \
+ -e PHOTOPRISM_UPLOAD_NSFW="true" \
+ -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
+ -v /home/docker/photoprism/storage:/photoprism/storage \
+ -v /home/docker/photoprism/Pictures:/photoprism/originals \
+ photoprism/photoprism
+
+ }
+
+
+ local docker_describe="photoprism非常強大的私有相簿系統"
+ local docker_url="官網介紹: https://www.photoprism.app/"
+ local docker_use="echo \"帳號: admin 密碼:$rootpasswd\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 31|s-pdf)
+ local app_id="31"
+ local docker_name="s-pdf"
+ local docker_img="frooodle/s-pdf:latest"
+ local docker_port=8031
+
+ docker_rum() {
+
+ docker run -d \
+ --name s-pdf \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
+ -v /home/docker/s-pdf/extraConfigs:/configs \
+ -v /home/docker/s-pdf/logs:/logs \
+ -e DOCKER_ENABLE_SECURITY=false \
+ frooodle/s-pdf:latest
+ }
+
+ local docker_describe="這是一個強大的本機託管基於 Web 的 PDF 操作工具,使用 docker,可讓您對 PDF 檔案執行各種操作,例如分割合併、轉換、重新組織、新增映像、旋轉、壓縮等。"
+ local docker_url="官網介紹:${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 32|drawio)
+ local app_id="32"
+ local docker_name="drawio"
+ local docker_img="jgraph/drawio"
+ local docker_port=8032
+
+ docker_rum() {
+
+ docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+
+ }
+
+
+ local docker_describe="這是一個強大圖表繪製軟體。心智圖,拓樸圖,流程圖,都能畫"
+ local docker_url="官網介紹: https://www.drawio.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 33|sun-panel)
+ local app_id="33"
+ local docker_name="sun-panel"
+ local docker_img="hslr/sun-panel"
+ local docker_port=8033
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:3002 \
+ -v /home/docker/sun-panel/conf:/app/conf \
+ -v /home/docker/sun-panel/uploads:/app/uploads \
+ -v /home/docker/sun-panel/database:/app/database \
+ --name sun-panel \
+ hslr/sun-panel
+
+ }
+
+ local docker_describe="Sun-Panel伺服器、NAS導覽面板、Homepage、瀏覽器首頁"
+ local docker_url="官網介紹: https://doc.sun-panel.top/zh_cn/"
+ local docker_use="echo \"帳號: admin@sun.cc 密碼: 12345678\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 34|pingvin-share)
+ local app_id="34"
+ local docker_name="pingvin-share"
+ local docker_img="stonith404/pingvin-share"
+ local docker_port=8034
+
+ docker_rum() {
+
+ docker run -d \
+ --name pingvin-share \
+ --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/pingvin-share/data:/opt/app/backend/data \
+ stonith404/pingvin-share
+ }
+
+ local docker_describe="Pingvin Share 是一個可自建的文件分享平台,是 WeTransfer 的替代品"
+ local docker_url="官網介紹:${gh_proxy}github.com/stonith404/pingvin-share"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 35|moments)
+ local app_id="35"
+ local docker_name="moments"
+ local docker_img="kingwrcy/moments:latest"
+ local docker_port=8035
+
+ docker_rum() {
+
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/moments/data:/app/data \
+ -v /etc/localtime:/etc/localtime:ro \
+ -v /etc/timezone:/etc/timezone:ro \
+ --name moments \
+ kingwrcy/moments:latest
+ }
+
+
+ local docker_describe="極簡朋友圈,高仿微信朋友圈,記錄你的美好生活"
+ local docker_url="官網介紹:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
+ local docker_use="echo \"帳號: admin 密碼: a123456\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+
+ 36|lobe-chat)
+ local app_id="36"
+ local docker_name="lobe-chat"
+ local docker_img="lobehub/lobe-chat:latest"
+ local docker_port=8036
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:3210 \
+ --name lobe-chat \
+ --restart=always \
+ lobehub/lobe-chat
+ }
+
+ local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
+ local docker_url="官網介紹:${gh_proxy}github.com/lobehub/lobe-chat"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 37|myip)
+ local app_id="37"
+ local docker_name="myip"
+ local docker_img="jason5ng32/myip:latest"
+ local docker_port=8037
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+
+ }
+
+
+ local docker_describe="是多功能IP工具箱,可以查看自己IP資訊及連結性,用網頁面板呈現"
+ local docker_url="官網介紹:${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 38|xiaoya)
+ send_stats "小雅全家桶"
+ clear
+ install_docker
+ check_disk_space 1
+ bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
+ ;;
+
+ 39|bililive)
+
+ if [ ! -d /home/docker/bililive-go/ ]; then
+ mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
+ wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
+ fi
+
+ local app_id="39"
+ local docker_name="bililive-go"
+ local docker_img="chigusa/bililive-go"
+ local docker_port=8039
+
+ docker_rum() {
+
+ docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+
+ }
+
+ local docker_describe="Bililive-go是一個支援多種直播平台的直播錄製工具"
+ local docker_url="官網介紹:${gh_proxy}github.com/hr3lxphr6j/bililive-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 40|webssh)
+ local app_id="40"
+ local docker_name="webssh"
+ local docker_img="jrohy/webssh"
+ local docker_port=8040
+ docker_rum() {
+ docker run -d -p ${docker_port}:5032 --restart=always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
+ }
+
+ local docker_describe="簡易線上ssh連線工具和sftp工具"
+ local docker_url="官網介紹:${gh_proxy}github.com/Jrohy/webssh"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 41|haozi|acepanel)
+
+ local app_id="41"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AcePanel 原耗子麵板"
+ local panelurl="官方地址:${gh_proxy}github.com/acepanel/panel"
+
+ panel_app_install() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+ }
+
+ panel_app_manage() {
+ acepanel help
+ }
+
+ panel_app_uninstall() {
+ cd ~
+ bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
+
+ }
+
+ install_panel
+
+ ;;
+
+
+ 42|nexterm)
+ local app_id="42"
+ local docker_name="nexterm"
+ local docker_img="germannewsmaker/nexterm:latest"
+ local docker_port=8042
+
+ docker_rum() {
+
+ ENCRYPTION_KEY=$(openssl rand -hex 32)
+ docker run -d \
+ --name nexterm \
+ -e ENCRYPTION_KEY=${ENCRYPTION_KEY} \
+ -p ${docker_port}:6989 \
+ -v /home/docker/nexterm:/app/data \
+ --restart=always \
+ germannewsmaker/nexterm:latest
+
+ }
+
+ local docker_describe="nexterm是一款強大的線上SSH/VNC/RDP連線工具。"
+ local docker_url="官網介紹:${gh_proxy}github.com/gnmyt/Nexterm"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 43|hbbs)
+ local app_id="43"
+ local docker_name="hbbs"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbs
+
+ }
+
+
+ local docker_describe="rustdesk開源的遠端桌面(服務端),類似自己的向日葵私服。"
+ local docker_url="官網介紹: https://rustdesk.com/zh-cn/"
+ local docker_use="docker logs hbbs"
+ local docker_passwd="echo \"把你的IP和key記錄下,會在遠端桌面客戶端中用到。去44選項裝中繼端吧!\""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 44|hbbr)
+ local app_id="44"
+ local docker_name="hbbr"
+ local docker_img="rustdesk/rustdesk-server"
+ local docker_port=0000
+
+ docker_rum() {
+
+ docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart=always rustdesk/rustdesk-server hbbr
+
+ }
+
+ local docker_describe="rustdesk開源的遠端桌面(中繼端),類似自己的向日葵私服。"
+ local docker_url="官網介紹: https://rustdesk.com/zh-cn/"
+ local docker_use="echo \"前往官網下載遠端桌面的客戶端: https://rustdesk.com/zh-cn/\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 45|registry)
+ local app_id="45"
+ local docker_name="registry"
+ local docker_img="registry:2"
+ local docker_port=8045
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name registry \
+ -v /home/docker/registry:/var/lib/registry \
+ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
+ --restart=always \
+ registry:2
+
+ }
+
+ local docker_describe="Docker Registry 是一個用於儲存和分發 Docker 映像的服務。"
+ local docker_url="官網介紹: https://hub.docker.com/_/registry"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
+ ;;
+
+ 46|ghproxy)
+ local app_id="46"
+ local docker_name="ghproxy"
+ local docker_img="wjqserver/ghproxy:latest"
+ local docker_port=8046
+
+ docker_rum() {
+
+ docker run -d --name ghproxy --restart=always -p ${docker_port}:8080 -v /home/docker/ghproxy/config:/data/ghproxy/config wjqserver/ghproxy:latest
+
+ }
+
+ local docker_describe="使用Go實現的GHProxy,用於加速部分地區Github倉庫的拉取。"
+ local docker_url="官網介紹:${gh_https_url}github.com/WJQSERVER-STUDIO/ghproxy"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 47|prometheus|grafana)
+
+ local app_id="47"
+ local app_name="普羅米修斯監控"
+ local app_text="Prometheus+Grafana企業級監控系統"
+ local app_url="官網介紹: https://prometheus.io"
+ local docker_name="grafana"
+ local docker_port="8047"
+ local app_size="2"
+
+ docker_app_install() {
+ prometheus_install
+ clear
+ ip_address
+ echo "已經安裝完成"
+ check_docker_app_ip
+ echo "初始使用者名稱密碼皆為: admin"
+ }
+
+ docker_app_update() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+ docker_app_install
+ }
+
+ docker_app_uninstall() {
+ docker rm -f node-exporter prometheus grafana
+ docker rmi -f prom/node-exporter
+ docker rmi -f prom/prometheus:latest
+ docker rmi -f grafana/grafana:latest
+
+ rm -rf /home/docker/monitoring
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+ ;;
+
+ 48|node-exporter)
+ local app_id="48"
+ local docker_name="node-exporter"
+ local docker_img="prom/node-exporter"
+ local docker_port=8048
+
+ docker_rum() {
+
+ docker run -d \
+ --name=node-exporter \
+ -p ${docker_port}:9100 \
+ --restart=always \
+ prom/node-exporter
+
+
+ }
+
+ local docker_describe="這是一個普羅米修斯的主機資料擷取元件,請部署在被監控主機上。"
+ local docker_url="官網介紹:${gh_https_url}github.com/prometheus/node_exporter"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 49|cadvisor)
+ local app_id="49"
+ local docker_name="cadvisor"
+ local docker_img="gcr.io/cadvisor/cadvisor:latest"
+ local docker_port=8049
+
+ docker_rum() {
+
+ docker run -d \
+ --name=cadvisor \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ --volume=/:/rootfs:ro \
+ --volume=/var/run:/var/run:rw \
+ --volume=/sys:/sys:ro \
+ --volume=/var/lib/docker/:/var/lib/docker:ro \
+ gcr.io/cadvisor/cadvisor:latest \
+ -housekeeping_interval=10s \
+ -docker_only=true
+
+ }
+
+ local docker_describe="這是一個普羅米修斯的容器資料擷取元件,請部署在被監控主機上。"
+ local docker_url="官網介紹:${gh_https_url}github.com/google/cadvisor"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 50|changedetection)
+ local app_id="50"
+ local docker_name="changedetection"
+ local docker_img="dgtlmoon/changedetection.io:latest"
+ local docker_port=8050
+
+ docker_rum() {
+
+ docker run -d --restart=always -p ${docker_port}:5000 \
+ -v /home/docker/datastore:/datastore \
+ --name changedetection dgtlmoon/changedetection.io:latest
+
+ }
+
+ local docker_describe="這是一個網站變更偵測、補貨監控和通知的小工具"
+ local docker_url="官網介紹:${gh_https_url}github.com/dgtlmoon/changedetection.io"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+
+ 51|pve)
+ clear
+ send_stats "PVE開小雞"
+ check_disk_space 1
+ curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
+ ;;
+
+
+ 52|dpanel)
+ local app_id="52"
+ local docker_name="dpanel"
+ local docker_img="dpanel/dpanel:lite"
+ local docker_port=8052
+
+ docker_rum() {
+
+ docker run -d --name dpanel --restart=always \
+ -p ${docker_port}:8080 -e APP_NAME=dpanel \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /home/docker/dpanel:/dpanel \
+ dpanel/dpanel:lite
+
+ }
+
+ local docker_describe="Docker視覺化面板系統,提供完善的docker管理功能。"
+ local docker_url="官網介紹:${gh_https_url}github.com/donknap/dpanel"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+
+ 53|llama3)
+ local app_id="53"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI一款大語言模型網頁框架,連結全新的llama3大語言模型"
+ local docker_url="官網介紹:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run llama3.2:1b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+ 54|amh)
+
+ local app_id="54"
+ local lujing="[ -d "/www/server/panel" ]"
+ local panelname="AMH面板"
+ local panelurl="官方地址: https://amh.sh/index.htm?amh"
+
+ panel_app_install() {
+ cd ~
+ wget https://dl.amh.sh/amh.sh && bash amh.sh
+ }
+
+ panel_app_manage() {
+ panel_app_install
+ }
+
+ panel_app_uninstall() {
+ panel_app_install
+ }
+
+ install_panel
+ ;;
+
+
+ 55|frps)
+ frps_panel
+ ;;
+
+ 56|frpc)
+ frpc_panel
+ ;;
+
+ 57|deepseek)
+ local app_id="57"
+ local docker_name="ollama"
+ local docker_img="ghcr.io/open-webui/open-webui:ollama"
+ local docker_port=8053
+
+ docker_rum() {
+
+ docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart=always ghcr.io/open-webui/open-webui:ollama
+
+ }
+
+ local docker_describe="OpenWebUI一款大語言模型網頁框架,連結全新的DeepSeek R1大語言模型"
+ local docker_url="官網介紹:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
+
+ 58|dify)
+ local app_id="58"
+ local app_name="Dify知識庫"
+ local app_text="是一款開源的大語言模型(LLM) 應用開發平台。自託管訓練資料用於AI生成"
+ local app_url="官方網站: https://docs.dify.ai/zh-hans"
+ local docker_name="docker-nginx-1"
+ local docker_port="8058"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
+ sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+
+ docker compose up -d
+
+ chown -R 1001:1001 /home/docker/dify/docker/volumes/app/storage
+ chmod -R 755 /home/docker/dify/docker/volumes/app/storage
+ docker compose down
+ docker compose up -d
+
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ cd /home/docker/dify/
+ git pull ${gh_proxy}github.com/langgenius/dify.git main > /dev/null 2>&1
+ sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
+ cd /home/docker/dify/docker/ && docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/dify/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/dify
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+
+ ;;
+
+ 59|new-api)
+ local app_id="59"
+ local app_name="NewAPI"
+ local app_text="新一代大模型閘道與AI資產管理系統"
+ local app_url="官方網站:${gh_https_url}github.com/Calcium-Ion/new-api"
+ local docker_name="new-api"
+ local docker_port="8059"
+ local app_size="3"
+
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
+
+ docker_app_update() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ cd /home/docker/new-api/
+
+ git pull ${gh_proxy}github.com/Calcium-Ion/new-api.git main > /dev/null 2>&1
+ sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
+ -e 's/container_name: redis/container_name: redis-new-api/g' \
+ -e 's/container_name: mysql/container_name: mysql-new-api/g' \
+ docker-compose.yml
+
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/new-api/ && docker compose down --rmi all
+ rm -rf /home/docker/new-api
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+
+ ;;
+
+
+ 60|jms)
+
+ local app_id="60"
+ local app_name="JumpServer開源堡壘機"
+ local app_text="是一個開源的特權存取管理 (PAM) 工具,該程式佔用80連接埠不支援新增網域存取了"
+ local app_url="官方介紹:${gh_https_url}github.com/jumpserver/jumpserver"
+ local docker_name="jms_web"
+ local docker_port="80"
+ local app_size="2"
+
+ docker_app_install() {
+ curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ echo "初始使用者名稱: admin"
+ echo "初始密碼: ChangeMe"
+ }
+
+
+ docker_app_update() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh upgrade
+ echo "應用程式已更新"
+ }
+
+
+ docker_app_uninstall() {
+ cd /opt/jumpserver-installer*/
+ ./jmsctl.sh uninstall
+ cd /opt
+ rm -rf jumpserver-installer*/
+ rm -rf jumpserver
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+ ;;
+
+ 61|libretranslate)
+ local app_id="61"
+ local docker_name="libretranslate"
+ local docker_img="libretranslate/libretranslate:latest"
+ local docker_port=8061
+
+ docker_rum() {
+
+ docker run -d \
+ -p ${docker_port}:5000 \
+ --name libretranslate \
+ libretranslate/libretranslate \
+ --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+
+ }
+
+ local docker_describe="免費開源機器翻譯 API,完全自託管,它的翻譯引擎由開源Argos Translate庫提供支援。"
+ local docker_url="官網介紹:${gh_https_url}github.com/LibreTranslate/LibreTranslate"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="5"
+ docker_app
+ ;;
+
- case $sub_choice in
- 1)
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="宝塔面板"
- local panelurl="https://www.bt.cn/new/index.html"
+ 62|ragflow)
+ local app_id="62"
+ local app_name="RAGFlow知識庫"
+ local app_text="基於深度文件理解的開源 RAG(檢索增強生成)引擎"
+ local app_url="官方網站:${gh_https_url}github.com/infiniflow/ragflow"
+ local docker_name="ragflow-server"
+ local docker_port="8062"
+ local app_size="8"
- panel_app_install() {
- if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
- }
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- panel_app_manage() {
- bt
- }
+ docker_app_update() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ cd /home/docker/ragflow/
+ git pull ${gh_proxy}github.com/infiniflow/ragflow.git main > /dev/null 2>&1
+ cd /home/docker/ragflow/docker/
+ sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
+ docker compose up -d
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ docker_app_uninstall() {
+ cd /home/docker/ragflow/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/ragflow
+ echo "應用程式已解除安裝"
+ }
- install_panel
+ docker_app_plus
+ ;;
- ;;
- 2)
+ 63|open-webui)
+ local app_id="63"
+ local docker_name="open-webui"
+ local docker_img="ghcr.io/open-webui/open-webui:main"
+ local docker_port=8063
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="aapanel"
- local panelurl="https://www.aapanel.com/new/index.html"
+ docker_rum() {
- panel_app_install() {
- URL=https://www.aapanel.com/script/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_7.0_en.sh "$URL";fi;bash install_7.0_en.sh aapanel
- }
+ docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart=always ghcr.io/open-webui/open-webui:main
- panel_app_manage() {
- bt
- }
+ }
- panel_app_uninstall() {
- curl -o bt-uninstall.sh http://download.bt.cn/install/bt-uninstall.sh > /dev/null 2>&1 && chmod +x bt-uninstall.sh && ./bt-uninstall.sh
- chmod +x bt-uninstall.sh
- ./bt-uninstall.sh
- }
+ local docker_describe="OpenWebUI一款大語言模型網頁框架,官方精簡版本,支援各大模型API接入"
+ local docker_url="官網介紹:${gh_https_url}github.com/open-webui/open-webui"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="3"
+ docker_app
+ ;;
- install_panel
+ 64|it-tools)
+ local app_id="64"
+ local docker_name="it-tools"
+ local docker_img="corentinth/it-tools:latest"
+ local docker_port=8064
- ;;
- 3)
+ docker_rum() {
+ docker run -d --name it-tools --restart=always -p ${docker_port}:80 corentinth/it-tools:latest
+ }
- local lujing="command -v 1pctl"
- local panelname="1Panel"
- local panelurl="https://1panel.cn/"
+ local docker_describe="對開發人員和 IT 工作者來說非常有用的工具"
+ local docker_url="官網介紹:${gh_https_url}github.com/CorentinTh/it-tools"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- panel_app_install() {
- install bash
- bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
- }
- panel_app_manage() {
- 1pctl user-info
- 1pctl update password
- }
+ 65|n8n)
+ local app_id="65"
+ local docker_name="n8n"
+ local docker_img="docker.n8n.io/n8nio/n8n"
+ local docker_port=8065
- panel_app_uninstall() {
- 1pctl uninstall
- }
+ docker_rum() {
- install_panel
+ add_yuming
+ mkdir -p /home/docker/n8n
+ chmod -R 777 /home/docker/n8n
+
+ docker run -d --name n8n \
+ --restart=always \
+ -p ${docker_port}:5678 \
+ -v /home/docker/n8n:/home/node/.n8n \
+ -e N8N_HOST=${yuming} \
+ -e N8N_PORT=5678 \
+ -e N8N_PROTOCOL=https \
+ -e WEBHOOK_URL=https://${yuming}/ \
+ docker.n8n.io/n8nio/n8n
- ;;
- 4)
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- local docker_name="npm"
- local docker_img="jc21/nginx-proxy-manager:latest"
- local docker_port=81
+ }
- docker_rum() {
+ local docker_describe="是一款功能強大的自動化工作流程平台"
+ local docker_url="官網介紹:${gh_https_url}github.com/n8n-io/n8n"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker run -d \
- --name=$docker_name \
- -p ${docker_port}:81 \
- -p 80:80 \
- -p 443:443 \
- -v /home/docker/npm/data:/data \
- -v /home/docker/npm/letsencrypt:/etc/letsencrypt \
- --restart=always \
- $docker_img
+ 66|yt)
+ yt_menu_pro
+ ;;
- }
+ 67|ddns)
+ local app_id="67"
+ local docker_name="ddns-go"
+ local docker_img="jeessy/ddns-go"
+ local docker_port=8067
- local docker_describe="一个Nginx反向代理工具面板,不支持添加域名访问。"
- local docker_url="官网介绍: https://nginxproxymanager.com/"
- local docker_use="echo \"初始用户名: admin@example.com\""
- local docker_passwd="echo \"初始密码: changeme\""
- local app_size="1"
+ docker_rum() {
+ docker run -d \
+ --name ddns-go \
+ --restart=always \
+ -p ${docker_port}:9876 \
+ -v /home/docker/ddns-go:/root \
+ jeessy/ddns-go
- docker_app
+ }
- ;;
+ local docker_describe="自動將你的公網 IP(IPv4/IPv6)即時更新到各大 DNS 服務商,實現動態網域解析。"
+ local docker_url="官網介紹:${gh_https_url}github.com/jeessy2/ddns-go"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 5)
+ 68|allinssl)
+ local app_id="68"
+ local docker_name="allinssl"
+ local docker_img="allinssl/allinssl:latest"
+ local docker_port=8068
- local docker_name="openlist"
- local docker_img="openlistteam/openlist:latest-aria2"
- local docker_port=5244
+ docker_rum() {
+ docker run -d --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
+ }
- docker_rum() {
+ local docker_describe="開源免費的 SSL 憑證自動化管理平台"
+ local docker_url="官網介紹: https://allinssl.com"
+ local docker_use="echo \"安全入口: /allinssl\""
+ local docker_passwd="echo \"使用者名稱: allinssl 密碼: allinssldocker\""
+ local app_size="1"
+ docker_app
+ ;;
- docker run -d \
- --restart=always \
- -v /home/docker/openlist:/opt/openlist/data \
- -p ${docker_port}:5244 \
- -e PUID=0 \
- -e PGID=0 \
- -e UMASK=022 \
- --name="openlist" \
- openlistteam/openlist:latest-aria2
- }
+ 69|sftpgo)
+ local app_id="69"
+ local docker_name="sftpgo"
+ local docker_img="drakkan/sftpgo:latest"
+ local docker_port=8069
+ docker_rum() {
- local docker_describe="一个支持多种存储,支持网页浏览和 WebDAV 的文件列表程序,由 gin 和 Solidjs 驱动"
- local docker_url="官网介绍: https://github.com/OpenListTeam/OpenList"
- local docker_use="docker exec -it openlist ./openlist admin random"
- local docker_passwd=""
- local app_size="1"
- docker_app
+ mkdir -p /home/docker/sftpgo/data
+ mkdir -p /home/docker/sftpgo/config
+ chown -R 1000:1000 /home/docker/sftpgo
- ;;
+ docker run -d \
+ --name sftpgo \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -p 22022:2022 \
+ --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
+ --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
+ drakkan/sftpgo:latest
- 6)
+ }
- local docker_name="webtop-ubuntu"
- local docker_img="lscr.io/linuxserver/webtop:ubuntu-kde"
- local docker_port=3006
+ local docker_describe="開源免費隨時隨地SFTP FTP WebDAV 檔案傳輸工具"
+ local docker_url="官網介紹: https://sftpgo.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_rum() {
+ 70|astrbot)
+ local app_id="70"
+ local docker_name="astrbot"
+ local docker_img="soulter/astrbot:latest"
+ local docker_port=8070
- docker run -d \
- --name=webtop-ubuntu \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=ubuntu-abc \
- -e PASSWORD=ubuntuABC123 \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:ubuntu-kde
+ docker_rum() {
+ mkdir -p /home/docker/astrbot/data
- }
+ docker run -d \
+ -p ${docker_port}:6185 \
+ -p 6195:6195 \
+ -p 6196:6196 \
+ -p 6199:6199 \
+ -p 11451:11451 \
+ -v /home/docker/astrbot/data:/AstrBot/data \
+ --restart=always \
+ --name astrbot \
+ soulter/astrbot:latest
+ }
- local docker_describe="webtop基于Ubuntu的容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: ubuntu-abc\""
- local docker_passwd="echo \"密码: ubuntuABC123\""
- local app_size="2"
- docker_app
+ local docker_describe="開源AI聊天機器人框架,支援微信,QQ,TG接入AI大模型"
+ local docker_url="官網介紹: https://astrbot.app/"
+ local docker_use="echo \"使用者名稱: astrbot 密碼: astrbot\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- ;;
- 7)
- clear
- send_stats "搭建哪吒"
- local docker_name="nezha-dashboard"
- local docker_port=8008
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
- clear
- echo -e "哪吒監控$check_docker $update_status"
- echo "開源、輕量、易用的服務器監控與運維工具"
- echo "官網搭建文檔: https://nezha.wiki/guide/dashboard.html"
- if docker inspect "$docker_name" &>/dev/null; then
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- fi
- echo ""
- echo "------------------------"
- echo "1. 使用"
- echo "------------------------"
- echo "0. 返回上一級選單"
- echo "------------------------"
- read -e -p "輸入你的選擇:" choice
+ 71|navidrome)
+ local app_id="71"
+ local docker_name="navidrome"
+ local docker_img="deluan/navidrome:latest"
+ local docker_port=8071
- case $choice in
- 1)
- check_disk_space 1
- install unzip jq
- install_docker
- curl -sL ${gh_proxy}raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh && chmod +x nezha.sh && ./nezha.sh
- local docker_port=$(docker port $docker_name | awk -F'[:]' '/->/ {print $NF}' | uniq)
- check_docker_app_ip
- ;;
+ docker_rum() {
- *)
- break
- ;;
+ docker run -d \
+ --name navidrome \
+ --restart=always \
+ --user $(id -u):$(id -g) \
+ -v /home/docker/navidrome/music:/music \
+ -v /home/docker/navidrome/data:/data \
+ -p ${docker_port}:4533 \
+ -e ND_LOGLEVEL=info \
+ deluan/navidrome:latest
- esac
- break_end
- done
- ;;
+ }
- 8)
+ local docker_describe="是一個輕量、高效能的音樂串流伺服器"
+ local docker_url="官網介紹: https://www.navidrome.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_name="qbittorrent"
- local docker_img="lscr.io/linuxserver/qbittorrent:latest"
- local docker_port=8081
-
- docker_rum() {
-
- docker run -d \
- --name=qbittorrent \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e WEBUI_PORT=${docker_port} \
- -e TORRENTING_PORT=56881 \
- -p ${docker_port}:${docker_port} \
- -p 56881:56881 \
- -p 56881:56881/udp \
- -v /home/docker/qbittorrent/config:/config \
- -v /home/docker/qbittorrent/downloads:/downloads \
- --restart unless-stopped \
- lscr.io/linuxserver/qbittorrent:latest
- }
+ 72|bitwarden)
- local docker_describe="qbittorrent离线BT磁力下载服务"
- local docker_url="官网介绍: https://hub.docker.com/r/linuxserver/qbittorrent"
- local docker_use="sleep 3"
- local docker_passwd="docker logs qbittorrent"
- local app_size="1"
- docker_app
+ local app_id="72"
+ local docker_name="bitwarden"
+ local docker_img="vaultwarden/server"
+ local docker_port=8072
- ;;
+ docker_rum() {
- 9)
- send_stats "搭建郵局"
- clear
- install telnet
- local docker_name=“mailserver”
- while true; do
- check_docker_app
- check_docker_image_update $docker_name
+ docker run -d \
+ --name bitwarden \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/bitwarden/data:/data \
+ vaultwarden/server
- clear
- echo -e "郵局服務$check_docker $update_status"
- echo "poste.io 是一個開源的郵件服務器解決方案,"
- echo "視頻介紹: https://www.bilibili.com/video/BV1wv421C71t?t=0.1"
+ }
- echo ""
- echo "端口檢測"
- port=25
- timeout=3
- if echo "quit" | timeout $timeout telnet smtp.qq.com $port | grep 'Connected'; then
- echo -e "${gl_lv}端口$port當前可用${gl_bai}"
- else
- echo -e "${gl_hong}端口$port當前不可用${gl_bai}"
- fi
- echo ""
+ local docker_describe="一個你可以控制資料的密碼管理器"
+ local docker_url="官網導論: https://bitwarden.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- if docker inspect "$docker_name" &>/dev/null; then
- yuming=$(cat /home/docker/mail.txt)
- echo "訪問地址:"
- echo "https://$yuming"
- fi
- echo "------------------------"
- echo "1. 安裝 2. 更新 3. 卸載"
- echo "------------------------"
- echo "0. 返回上一級選單"
- echo "------------------------"
- read -e -p "輸入你的選擇:" choice
+ ;;
- case $choice in
- 1)
- check_disk_space 2
- read -e -p "請設置郵箱域名 例如 mail.yuming.com :" yuming
- mkdir -p /home/docker
- echo "$yuming" > /home/docker/mail.txt
- echo "------------------------"
- ip_address
- echo "先解析這些DNS記錄"
- echo "A mail $ipv4_address"
- echo "CNAME imap $yuming"
- echo "CNAME pop $yuming"
- echo "CNAME smtp $yuming"
- echo "MX @ $yuming"
- echo "TXT @ v=spf1 mx ~all"
- echo "TXT ? ?"
- echo ""
- echo "------------------------"
- echo "按任意鍵繼續..."
- read -n 1 -s -r -p ""
- install jq
- install_docker
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.io
+ 73|libretv)
- clear
- echo "poste.io已經安裝完成"
- echo "------------------------"
- echo "您可以使用以下地址訪問poste.io:"
- echo "https://$yuming"
- echo ""
+ local app_id="73"
+ local docker_name="libretv"
+ local docker_img="bestzwei/libretv:latest"
+ local docker_port=8073
- ;;
+ docker_rum() {
- 2)
- docker rm -f mailserver
- docker rmi -f analogic/poste.i
- yuming=$(cat /home/docker/mail.txt)
- docker run \
- --net=host \
- -e TZ=Europe/Prague \
- -v /home/docker/mail:/data \
- --name "mailserver" \
- -h "$yuming" \
- --restart=always \
- -d analogic/poste.i
- clear
- echo "poste.io已經安裝完成"
- echo "------------------------"
- echo "您可以使用以下地址訪問poste.io:"
- echo "https://$yuming"
- echo ""
- ;;
- 3)
- docker rm -f mailserver
- docker rmi -f analogic/poste.io
- rm /home/docker/mail.txt
- rm -rf /home/docker/mail
- echo "應用已卸載"
- ;;
+ read -e -p "設定LibreTV的登入密碼:" app_passwd
- *)
- break
- ;;
+ docker run -d \
+ --name libretv \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -e PASSWORD=${app_passwd} \
+ bestzwei/libretv:latest
- esac
- break_end
- done
+ }
- ;;
+ local docker_describe="免費線上影片搜尋與觀看平台"
+ local docker_url="官網介紹:${gh_https_url}github.com/LibreSpark/LibreTV"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 10)
+ ;;
- local app_name="Rocket.Chat聊天系统"
- local app_text="Rocket.Chat 是一个开源的团队通讯平台,支持实时聊天、音视频通话、文件共享等多种功能,"
- local app_url="官方介绍: https://www.rocket.chat/"
- local docker_name="rocketchat"
- local docker_port="3897"
- local app_size="2"
-
- docker_app_install() {
- docker run --name db -d --restart=always \
- -v /home/docker/mongo/dump:/dump \
- mongo:latest --replSet rs5 --oplogSize 256
- sleep 1
- docker exec -it db mongosh --eval "printjson(rs.initiate())"
- sleep 5
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "已經安裝完成"
- check_docker_app_ip
- }
- docker_app_update() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat:latest
- docker run --name rocketchat --restart=always -p ${docker_port}:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/rs5 -d rocket.chat
- clear
- ip_address
- echo "rocket.chat已經安裝完成"
- check_docker_app_ip
- }
+ 74|moontv)
- docker_app_uninstall() {
- docker rm -f rocketchat
- docker rmi -f rocket.chat
- docker rm -f db
- docker rmi -f mongo:latest
- rm -rf /home/docker/mongo
- echo "應用已卸載"
- }
+ local app_id="74"
- docker_app_plus
- ;;
+ local app_name="moontv私有影視"
+ local app_text="免費線上影片搜尋與觀看平台"
+ local app_url="影片介紹:${gh_https_url}github.com/MoonTechLab/LunaTV"
+ local docker_name="moontv-core"
+ local docker_port="8074"
+ local app_size="2"
+ docker_app_install() {
+ read -e -p "設定登入用戶名:" admin
+ read -e -p "設定登入用戶密碼:" admin_password
+ read -e -p "輸入授權碼:" shouquanma
- 11)
- local docker_name="zentao-server"
- local docker_img="idoop/zentao:latest"
- local docker_port=82
+ mkdir -p /home/docker/moontv
+ mkdir -p /home/docker/moontv/config
+ mkdir -p /home/docker/moontv/data
+ cd /home/docker/moontv
+
+ curl -o /home/docker/moontv/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/moontv-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin_password|${admin_password}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|admin|${admin}|g" /home/docker/moontv/docker-compose.yml
+ sed -i "s|shouquanma|${shouquanma}|g" /home/docker/moontv/docker-compose.yml
+ cd /home/docker/moontv/
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- docker_rum() {
+ docker_app_update() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ cd /home/docker/moontv/ && docker compose up -d
+ }
- docker run -d -p ${docker_port}:80 \
- -e ADMINER_USER="root" -e ADMINER_PASSWD="password" \
- -e BIND_ADDRESS="false" \
- -v /home/docker/zentao-server/:/opt/zbox/ \
- --add-host smtp.exmail.qq.com:163.177.90.125 \
- --name zentao-server \
- --restart=always \
- idoop/zentao:latest
+ docker_app_uninstall() {
+ cd /home/docker/moontv/ && docker compose down --rmi all
+ rm -rf /home/docker/moontv
+ echo "應用程式已解除安裝"
+ }
+ docker_app_plus
- }
+ ;;
- local docker_describe="禅道是通用的项目管理软件"
- local docker_url="官网介绍: https://www.zentao.net/"
- local docker_use="echo \"初始用户名: admin\""
- local docker_passwd="echo \"初始密码: 123456\""
- local app_size="2"
- docker_app
- ;;
+ 75|melody)
- 12)
- local docker_name="qinglong"
- local docker_img="whyour/qinglong:latest"
- local docker_port=5700
+ local app_id="75"
+ local docker_name="melody"
+ local docker_img="foamzou/melody:latest"
+ local docker_port=8075
- docker_rum() {
+ docker_rum() {
+ docker run -d \
+ --name melody \
+ --restart=always \
+ -p ${docker_port}:5566 \
+ -v /home/docker/melody/.profile:/app/backend/.profile \
+ foamzou/melody:latest
- docker run -d \
- -v /home/docker/qinglong/data:/ql/data \
- -p ${docker_port}:5700 \
- --name qinglong \
- --hostname qinglong \
- --restart unless-stopped \
- whyour/qinglong:latest
+ }
- }
+ local docker_describe="你的音樂精靈,旨在幫助你更好地管理音樂。"
+ local docker_url="官網介紹:${gh_https_url}github.com/foamzou/melody"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="青龙面板是一个定时任务管理平台"
- local docker_url="官网介绍: ${gh_proxy}github.com/whyour/qinglong"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 13)
+ ;;
- local app_name="cloudreve网盘"
- local app_text="cloudreve是一个支持多家云存储的网盘系统"
- local app_url="视频介绍: https://www.bilibili.com/video/BV13F4m1c7h7?t=0.1"
- local docker_name="cloudreve"
- local docker_port="5212"
- local app_size="2"
-
- docker_app_install() {
- cd /home/ && mkdir -p docker/cloud && cd docker/cloud && mkdir temp_data && mkdir -vp cloudreve/{uploads,avatar} && touch cloudreve/conf.ini && touch cloudreve/cloudreve.db && mkdir -p aria2/config && mkdir -p data/aria2 && chmod -R 777 data/aria2
- curl -o /home/docker/cloud/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/cloudreve-docker-compose.yml
- sed -i "s/5212:5212/${docker_port}:5212/g" /home/docker/cloud/docker-compose.yml
- cd /home/docker/cloud/
- docker compose up -d
- clear
- echo "已經安裝完成"
- check_docker_app_ip
- }
+ 76|dosgame)
- docker_app_update() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- cd /home/docker/cloud/ && docker compose up -d
- }
+ local app_id="76"
+ local docker_name="dosgame"
+ local docker_img="oldiy/dosgame-web-docker:latest"
+ local docker_port=8076
+ docker_rum() {
+ docker run -d \
+ --name dosgame \
+ --restart=always \
+ -p ${docker_port}:262 \
+ oldiy/dosgame-web-docker:latest
- docker_app_uninstall() {
- cd /home/docker/cloud/ && docker compose down --rmi all
- rm -rf /home/docker/cloud
- echo "應用已卸載"
- }
+ }
- docker_app_plus
- ;;
+ local docker_describe="是一個中文DOS遊戲收藏網站"
+ local docker_url="官網介紹:${gh_https_url}github.com/rwv/chinese-dos-games"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- 14)
- local docker_name="easyimage"
- local docker_img="ddsderek/easyimage:latest"
- local docker_port=85
- docker_rum() {
-
- docker run -d \
- --name easyimage \
- -p ${docker_port}:80 \
- -e TZ=Asia/Shanghai \
- -e PUID=1000 \
- -e PGID=1000 \
- -v /home/docker/easyimage/config:/app/web/config \
- -v /home/docker/easyimage/i:/app/web/i \
- --restart unless-stopped \
- ddsderek/easyimage:latest
- }
+ ;;
- local docker_describe="简单图床是一个简单的图床程序"
- local docker_url="官网介绍: ${gh_proxy}github.com/icret/EasyImages2.0"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 77|xunlei)
- 15)
- local docker_name="emby"
- local docker_img="linuxserver/emby:latest"
- local docker_port=8096
+ local app_id="77"
+ local docker_name="xunlei"
+ local docker_img="cnk3x/xunlei"
+ local docker_port=8077
- docker_rum() {
+ docker_rum() {
- docker run -d --name=emby --restart=always \
- -v /home/docker/emby/config:/config \
- -v /home/docker/emby/share1:/mnt/share1 \
- -v /home/docker/emby/share2:/mnt/share2 \
- -v /mnt/notify:/mnt/notify \
- -p ${docker_port}:8096 \
- -e UID=1000 -e GID=100 -e GIDLIST=100 \
- linuxserver/emby:latest
+ read -e -p "設定登入用戶名:" app_use
+ read -e -p "設定登入密碼:" app_passwd
- }
+ docker run -d \
+ --name xunlei \
+ --restart=always \
+ --privileged \
+ -e XL_DASHBOARD_USERNAME=${app_use} \
+ -e XL_DASHBOARD_PASSWORD=${app_passwd} \
+ -v /home/docker/xunlei/data:/xunlei/data \
+ -v /home/docker/xunlei/downloads:/xunlei/downloads \
+ -p ${docker_port}:2345 \
+ cnk3x/xunlei
+ }
- local docker_describe="emby是一个主从式架构的媒体服务器软件,可以用来整理服务器上的视频和音频,并将音频和视频流式传输到客户端设备"
- local docker_url="官网介绍: https://emby.media/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="迅雷你的離線高速BT磁力下載工具"
+ local docker_url="官網介紹:${gh_https_url}github.com/cnk3x/xunlei"
+ local docker_use="echo \"手機登入迅雷,再輸入邀請碼,邀請碼: 迅雷牛通\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 16)
- local docker_name="looking-glass"
- local docker_img="wikihostinc/looking-glass-server"
- local docker_port=89
+ ;;
- docker_rum() {
- docker run -d --name looking-glass --restart always -p ${docker_port}:80 wikihostinc/looking-glass-server
+ 78|PandaWiki)
- }
+ local app_id="78"
+ local app_name="PandaWiki"
+ local app_text="PandaWiki是一款以AI大模型驅動的開源智慧文件管理系統,強烈建議不要自訂連接埠部署。"
+ local app_url="官方介紹:${gh_https_url}github.com/chaitin/PandaWiki"
+ local docker_name="panda-wiki-nginx"
+ local docker_port="2443"
+ local app_size="2"
- local docker_describe="Speedtest测速面板是一个VPS网速测试工具,多项测试功能,还可以实时监控VPS进出站流量"
- local docker_url="官网介绍: ${gh_proxy}github.com/wikihost-opensource/als"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_install() {
+ bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
+ }
- ;;
- 17)
+ docker_app_update() {
+ docker_app_install
+ }
- local docker_name="adguardhome"
- local docker_img="adguard/adguardhome"
- local docker_port=3000
- docker_rum() {
+ docker_app_uninstall() {
+ docker_app_install
+ }
- docker run -d \
- --name adguardhome \
- -v /home/docker/adguardhome/work:/opt/adguardhome/work \
- -v /home/docker/adguardhome/conf:/opt/adguardhome/conf \
- -p 53:53/tcp \
- -p 53:53/udp \
- -p ${docker_port}:3000/tcp \
- --restart always \
- adguard/adguardhome
+ docker_app_plus
+ ;;
- }
+ 79|beszel)
- local docker_describe="AdGuardHome是一款全网广告拦截与反跟踪软件,未来将不止是一个DNS服务器。"
- local docker_url="官网介绍: https://hub.docker.com/r/adguard/adguardhome"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ local app_id="79"
+ local docker_name="beszel"
+ local docker_img="henrygd/beszel"
+ local docker_port=8079
- ;;
+ docker_rum() {
+ mkdir -p /home/docker/beszel && \
+ docker run -d \
+ --name beszel \
+ --restart=always \
+ -v /home/docker/beszel:/beszel_data \
+ -p ${docker_port}:8090 \
+ henrygd/beszel
- 18)
+ }
- local docker_name="onlyoffice"
- local docker_img="onlyoffice/documentserver"
- local docker_port=8082
+ local docker_describe="Beszel輕易易用的伺服器監控"
+ local docker_url="官網介紹: https://beszel.dev/zh/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker_rum() {
+ ;;
- docker run -d -p ${docker_port}:80 \
- --restart=always \
- --name onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/logs:/var/log/onlyoffice \
- -v /home/docker/onlyoffice/DocumentServer/data:/var/www/onlyoffice/Data \
- onlyoffice/documentserver
+ 80|linkwarden)
- }
+ local app_id="80"
+ local app_name="linkwarden書籤管理"
+ local app_text="一個開源的自架書籤管理平台,支援標籤、搜尋和團隊協作。"
+ local app_url="官方網站: https://linkwarden.app/"
+ local docker_name="linkwarden-linkwarden-1"
+ local docker_port="8080"
+ local app_size="3"
- local docker_describe="onlyoffice是一款开源的在线office工具,太强大了!"
- local docker_url="官网介绍: https://www.onlyoffice.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ docker_app_install() {
+ install git openssl
+ mkdir -p /home/docker/linkwarden && cd /home/docker/linkwarden
- ;;
+ # 下載官方 docker-compose 和 env 文件
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env"
- 19)
- send_stats "搭建雷池"
+ # 產生隨機密鑰與密碼
+ local ADMIN_EMAIL="admin@example.com"
+ local ADMIN_PASSWORD=$(openssl rand -hex 8)
- local docker_name=safeline-mgt
- local docker_port=9443
- while true; do
- check_docker_app
- clear
- echo -e "雷池服務$check_docker"
- echo "雷池是長亭科技開發的WAF站點防火牆程序面板,可以反代站點進行自動化防禦"
- echo "視頻介紹: https://www.bilibili.com/video/BV1mZ421T74c?t=0.1"
- if docker inspect "$docker_name" &>/dev/null; then
- check_docker_app_ip
- fi
- echo ""
+ sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=http://localhost:${docker_port}/api/v1/auth|g" .env
+ sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$(openssl rand -hex 32)|g" .env
+ sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|g" .env
+ sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$(openssl rand -hex 32)|g" .env
- echo "------------------------"
- echo "1. 安裝 2. 更新 3. 重置密碼 4. 卸載"
- echo "------------------------"
- echo "0. 返回上一級選單"
- echo "------------------------"
- read -e -p "輸入你的選擇:" choice
+ # 追加管理員帳號資訊
+ echo "ADMIN_EMAIL=${ADMIN_EMAIL}" >> .env
+ echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env
- case $choice in
- 1)
- install_docker
- check_disk_space 5
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
- clear
- echo "雷池WAF面板已經安裝完成"
- check_docker_app_ip
- docker exec safeline-mgt resetadmin
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
- ;;
+ # 啟動容器
+ docker compose up -d
- 2)
- bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/upgrade.sh)"
- docker rmi $(docker images | grep "safeline" | grep "none" | awk '{print $3}')
- echo ""
- clear
- echo "雷池WAF面板已經更新完成"
- check_docker_app_ip
- ;;
- 3)
- docker exec safeline-mgt resetadmin
- ;;
- 4)
- cd /data/safeline
- docker compose down --rmi all
- echo "如果你是默認安裝目錄那現在項目已經卸載。如果你是自定義安裝目錄你需要到安裝目錄下自行執行:"
- echo "docker compose down && docker compose down --rmi all"
- ;;
- *)
- break
- ;;
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ curl -O ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/docker-compose.yml
+ curl -L ${gh_proxy}raw.githubusercontent.com/linkwarden/linkwarden/refs/heads/main/.env.sample -o ".env.new"
+
+ # 保留原本的變數
+ source .env
+ mv .env.new .env
+ echo "NEXTAUTH_URL=$NEXTAUTH_URL" >> .env
+ echo "NEXTAUTH_SECRET=$NEXTAUTH_SECRET" >> .env
+ echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> .env
+ echo "MEILI_MASTER_KEY=$MEILI_MASTER_KEY" >> .env
+ echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> .env
+ echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/linkwarden/docker-compose.yml
+
+ docker compose up -d
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/linkwarden && docker compose down --rmi all
+ rm -rf /home/docker/linkwarden
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
- esac
- break_end
- done
+ ;;
- ;;
- 20)
- local docker_name="portainer"
- local docker_img="portainer/portainer"
- local docker_port=9050
- docker_rum() {
+ 81|jitsi)
+ local app_id="81"
+ local app_name="JitsiMeet視訊會議"
+ local app_text="一個開源的安全視訊會議解決方案,支援多人線上會議、螢幕分享與加密通訊。"
+ local app_url="官方網站: https://jitsi.org/"
+ local docker_name="jitsi"
+ local docker_app_service="jitsi-web-1"
+ local docker_port="8081"
+ local app_size="3"
- docker run -d \
- --name portainer \
- -p ${docker_port}:9000 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/portainer:/data \
- --restart always \
- portainer/portainer
+ docker_app_install() {
- }
+ add_yuming
+ mkdir -p /home/docker/jitsi && cd /home/docker/jitsi
+ rm -rf docker-jitsi-meet-* jitsi.zip
+ local jitsi_zip_url=$(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep -m1 '"zipball_url"' | cut -d\" -f4)
+ if [ -z "$jitsi_zip_url" ]; then
+ echo "Failed to get Jitsi Meet latest download URL"
+ return 1
+ fi
+ wget -O jitsi.zip "$jitsi_zip_url"
+ unzip -q jitsi.zip
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ cp env.example .env
+ ./gen-passwords.sh
+ mkdir -p /home/docker/jitsi/config/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
+ sed -i "s|^CONFIG=.*|CONFIG=/home/docker/jitsi/config|" .env
+ grep -q '^COMPOSE_PROJECT_NAME=' .env && sed -i "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=jitsi|" .env || echo "COMPOSE_PROJECT_NAME=jitsi" >> .env
+ sed -i "s|^HTTP_PORT=.*|HTTP_PORT=${docker_port}|" .env
+ sed -i "s|^#\?PUBLIC_URL=.*|PUBLIC_URL=https://$yuming|" .env
+ grep -q '^PUBLIC_URL=' .env || echo "PUBLIC_URL=https://$yuming" >> .env
+ grep -q '^DISABLE_HTTPS=' .env && sed -i "s|^DISABLE_HTTPS=.*|DISABLE_HTTPS=1|" .env || echo "DISABLE_HTTPS=1" >> .env
+ grep -q '^ENABLE_HTTP_REDIRECT=' .env && sed -i "s|^ENABLE_HTTP_REDIRECT=.*|ENABLE_HTTP_REDIRECT=0|" .env || echo "ENABLE_HTTP_REDIRECT=0" >> .env
+ sed -i "/'\${HTTPS_PORT}:443'/d" docker-compose.yml
+ docker compose up -d
+ local jitsi_web_container=$(docker compose ps -q web)
+
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ [ -n "$jitsi_web_container" ] && block_container_port "$jitsi_web_container" "$ipv4_address"
+
+ }
+
+ docker_app_update() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose pull
+ docker compose up -d
+
+ }
+
+ docker_app_uninstall() {
+ cd /home/docker/jitsi
+ cd "$(find . -maxdepth 1 -type d -name '*docker-jitsi-meet*' | head -n 1)"
+ docker compose down --rmi all
+ rm -rf /home/docker/jitsi
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+ ;;
- local docker_describe="portainer是一个轻量级的docker容器管理面板"
- local docker_url="官网介绍: https://www.portainer.io/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 21)
- local docker_name="vscode-web"
- local docker_img="codercom/code-server"
- local docker_port=8180
+ 82|gpt-load)
+ local app_id="82"
+ local docker_name="gpt-load"
+ local docker_img="tbphp/gpt-load:latest"
+ local docker_port=8082
- docker_rum() {
+ docker_rum() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/vscode-web:/home/coder/.local/share/code-server --name vscode-web --restart always codercom/code-server
+ read -e -p "設定${docker_name}的登入密鑰(sk-開頭字母和數字組合)如: sk-159kejilionyyds163:" app_passwd
- }
+ mkdir -p /home/docker/gpt-load && \
+ docker run -d --name gpt-load \
+ -p ${docker_port}:3001 \
+ -e AUTH_KEY=${app_passwd} \
+ -v "/home/docker/gpt-load/data":/app/data \
+ tbphp/gpt-load:latest
+ }
- local docker_describe="VScode是一款强大的在线代码编写工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/coder/code-server"
- local docker_use="sleep 3"
- local docker_passwd="docker exec vscode-web cat /home/coder/.config/code-server/config.yaml"
- local app_size="1"
- docker_app
- ;;
- 22)
- local docker_name="uptime-kuma"
- local docker_img="louislam/uptime-kuma:latest"
- local docker_port=3003
+ local docker_describe="高效能AI介面透明代理服務"
+ local docker_url="官網介紹: https://www.gpt-load.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- docker_rum() {
- docker run -d \
- --name=uptime-kuma \
- -p ${docker_port}:3001 \
- -v /home/docker/uptime-kuma/uptime-kuma-data:/app/data \
- --restart=always \
- louislam/uptime-kuma:latest
- }
+ 83|komari)
+ local app_id="83"
+ local docker_name="komari"
+ local docker_img="ghcr.io/komari-monitor/komari:latest"
+ local docker_port=8083
- local docker_describe="Uptime Kuma 易于使用的自托管监控工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/uptime-kuma"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 23)
- local docker_name="memos"
- local docker_img="ghcr.io/usememos/memos:latest"
- local docker_port=5230
+ mkdir -p /home/docker/komari && \
+ docker run -d \
+ --name komari \
+ -p ${docker_port}:25774 \
+ -v /home/docker/komari:/app/data \
+ -e ADMIN_USERNAME=admin \
+ -e ADMIN_PASSWORD=1212156 \
+ -e TZ=Asia/Shanghai \
+ --restart=always \
+ ghcr.io/komari-monitor/komari:latest
- docker_rum() {
+ }
- docker run -d --name memos -p ${docker_port}:5230 -v /home/docker/memos:/var/opt/memos --restart always ghcr.io/usememos/memos:latest
+ local docker_describe="輕量級的自架伺服器監控工具"
+ local docker_url="官網介紹:${gh_https_url}github.com/komari-monitor/komari/tree/main"
+ local docker_use="echo \"預設帳號: admin 預設密碼: 1212156\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
- local docker_describe="Memos是一款轻量级、自托管的备忘录中心"
- local docker_url="官网介绍: ${gh_proxy}github.com/usememos/memos"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 24)
- local docker_name="webtop"
- local docker_img="lscr.io/linuxserver/webtop:latest"
- local docker_port=3083
-
- docker_rum() {
-
-
- docker run -d \
- --name=webtop \
- --security-opt seccomp=unconfined \
- -e PUID=1000 \
- -e PGID=1000 \
- -e TZ=Etc/UTC \
- -e SUBFOLDER=/ \
- -e TITLE=Webtop \
- -e CUSTOM_USER=webtop-abc \
- -e PASSWORD=webtopABC123 \
- -e LC_ALL=zh_CN.UTF-8 \
- -e DOCKER_MODS=linuxserver/mods:universal-package-install \
- -e INSTALL_PACKAGES=font-noto-cjk \
- -p ${docker_port}:3000 \
- -v /home/docker/webtop/data:/config \
- -v /var/run/docker.sock:/var/run/docker.sock \
- --shm-size="1gb" \
- --restart unless-stopped \
- lscr.io/linuxserver/webtop:latest
+ 84|wallos)
- }
+ local app_id="84"
+ local docker_name="wallos"
+ local docker_img="bellamy/wallos:latest"
+ local docker_port=8084
+ docker_rum() {
- local docker_describe="webtop基于Alpine的中文版容器。若IP无法访问,请添加域名访问。"
- local docker_url="官网介绍: https://docs.linuxserver.io/images/docker-webtop/"
- local docker_use="echo \"用户名: webtop-abc\""
- local docker_passwd="echo \"密码: webtopABC123\""
- local app_size="2"
- docker_app
- ;;
+ mkdir -p /home/docker/wallos && \
+ docker run -d --name wallos \
+ -v /home/docker/wallos/db:/var/www/html/db \
+ -v /home/docker/wallos/logos:/var/www/html/images/uploads/logos \
+ -e TZ=UTC \
+ -p ${docker_port}:80 \
+ --restart=always \
+ bellamy/wallos:latest
- 25)
- local docker_name="nextcloud"
- local docker_img="nextcloud:latest"
- local docker_port=8989
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
+ }
- docker_rum() {
+ local docker_describe="開源個人訂閱追蹤器,可用於財務管理"
+ local docker_url="官網介紹:${gh_https_url}github.com/ellite/Wallos"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d --name nextcloud --restart=always -p ${docker_port}:80 -v /home/docker/nextcloud:/var/www/html -e NEXTCLOUD_ADMIN_USER=nextcloud -e NEXTCLOUD_ADMIN_PASSWORD=$rootpasswd nextcloud
+ ;;
- }
+ 85|immich)
- local docker_describe="Nextcloud拥有超过 400,000 个部署,是您可以下载的最受欢迎的本地内容协作平台"
- local docker_url="官网介绍: https://nextcloud.com/"
- local docker_use="echo \"账号: nextcloud 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ local app_id="85"
+ local app_name="immich圖片影片管理器"
+ local app_text="高效能自架照片和影片管理解決方案。"
+ local app_url="官網介紹:${gh_https_url}github.com/immich-app/immich"
+ local docker_name="immich_server"
+ local docker_port="8085"
+ local app_size="3"
- 26)
- local docker_name="qd"
- local docker_img="qdtoday/qd:latest"
- local docker_port=8923
+ docker_app_install() {
+ install git openssl wget
+ mkdir -p /home/docker/${docker_name} && cd /home/docker/${docker_name}
- docker_rum() {
+ wget -O docker-compose.yml ${gh_proxy}github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+ wget -O .env ${gh_proxy}github.com/immich-app/immich/releases/latest/download/example.env
+ sed -i "s/2283:2283/${docker_port}:2283/g" /home/docker/${docker_name}/docker-compose.yml
- docker run -d --name qd -p ${docker_port}:80 -v /home/docker/qd/config:/usr/src/app/config qdtoday/qd
+ docker compose up -d
- }
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
- local docker_describe="QD-Today是一个HTTP请求定时任务自动执行框架"
- local docker_url="官网介绍: https://qd-today.github.io/qd/zh_CN/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 27)
- local docker_name="dockge"
- local docker_img="louislam/dockge:latest"
- local docker_port=5003
+ }
- docker_rum() {
+ docker_app_update() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ docker_app_install
+ }
- docker run -d --name dockge --restart unless-stopped -p ${docker_port}:5001 -v /var/run/docker.sock:/var/run/docker.sock -v /home/docker/dockge/data:/app/data -v /home/docker/dockge/stacks:/home/docker/dockge/stacks -e DOCKGE_STACKS_DIR=/home/docker/dockge/stacks louislam/dockge
+ docker_app_uninstall() {
+ cd /home/docker/${docker_name} && docker compose down --rmi all
+ rm -rf /home/docker/${docker_name}
+ echo "應用程式已解除安裝"
+ }
- }
+ docker_app_plus
- local docker_describe="dockge是一个可视化的docker-compose容器管理面板"
- local docker_url="官网介绍: ${gh_proxy}github.com/louislam/dockge"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 28)
- local docker_name="speedtest"
- local docker_img="ghcr.io/librespeed/speedtest"
- local docker_port=8028
+ ;;
- docker_rum() {
- docker run -d -p ${docker_port}:8080 --name speedtest --restart always ghcr.io/librespeed/speedtest
+ 86|jellyfin)
- }
+ local app_id="86"
+ local docker_name="jellyfin"
+ local docker_img="jellyfin/jellyfin"
+ local docker_port=8086
- local docker_describe="librespeed是用Javascript实现的轻量级速度测试工具,即开即用"
- local docker_url="官网介绍: ${gh_proxy}github.com/librespeed/speedtest"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 29)
- local docker_name="searxng"
- local docker_img="searxng/searxng"
- local docker_port=8029
+ mkdir -p /home/docker/jellyfin/media
+ chmod -R 777 /home/docker/jellyfin
- docker_rum() {
+ docker run -d \
+ --name jellyfin \
+ --user root \
+ --volume /home/docker/jellyfin/config:/config \
+ --volume /home/docker/jellyfin/cache:/cache \
+ --mount type=bind,source=/home/docker/jellyfin/media,target=/media \
+ -p ${docker_port}:8096 \
+ -p 7359:7359/udp \
+ --restart=always \
+ jellyfin/jellyfin
- docker run -d \
- --name searxng \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -v "/home/docker/searxng:/etc/searxng" \
- searxng/searxng
- }
+ }
- local docker_describe="searxng是一个私有且隐私的搜索引擎站点"
- local docker_url="官网介绍: https://hub.docker.com/r/alandoyle/searxng"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="是一款開源媒體伺服器軟體"
+ local docker_url="官網介紹: https://jellyfin.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 30)
- local docker_name="photoprism"
- local docker_img="photoprism/photoprism:latest"
- local docker_port=2342
- local rootpasswd=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
-
- docker_rum() {
-
- docker run -d \
- --name photoprism \
- --restart always \
- --security-opt seccomp=unconfined \
- --security-opt apparmor=unconfined \
- -p ${docker_port}:2342 \
- -e PHOTOPRISM_UPLOAD_NSFW="true" \
- -e PHOTOPRISM_ADMIN_PASSWORD="$rootpasswd" \
- -v /home/docker/photoprism/storage:/photoprism/storage \
- -v /home/docker/photoprism/Pictures:/photoprism/originals \
- photoprism/photoprism
+ ;;
- }
+ 87|synctv)
- local docker_describe="photoprism非常强大的私有相册系统"
- local docker_url="官网介绍: https://www.photoprism.app/"
- local docker_use="echo \"账号: admin 密码: $rootpasswd\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="87"
+ local docker_name="synctv"
+ local docker_img="synctvorg/synctv"
+ local docker_port=8087
+
+ docker_rum() {
+
+ docker run -d \
+ --name synctv \
+ -v /home/docker/synctv:/root/.synctv \
+ -p ${docker_port}:8080 \
+ --restart=always \
+ synctvorg/synctv
+ }
+
+ local docker_describe="遠端一起觀看電影和直播的程式。它提供了同步觀影、直播、聊天等功能"
+ local docker_url="官網介紹:${gh_https_url}github.com/synctv-org/synctv"
+ local docker_use="echo \"初始帳號與密碼: root 登陸後請及時修改登入密碼\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+
+ ;;
- 31)
- local docker_name="s-pdf"
- local docker_img="frooodle/s-pdf:latest"
- local docker_port=8020
-
- docker_rum() {
-
- docker run -d \
- --name s-pdf \
- --restart=always \
- -p ${docker_port}:8080 \
- -v /home/docker/s-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata \
- -v /home/docker/s-pdf/extraConfigs:/configs \
- -v /home/docker/s-pdf/logs:/logs \
- -e DOCKER_ENABLE_SECURITY=false \
- frooodle/s-pdf:latest
- }
- local docker_describe="这是一个强大的本地托管基于 Web 的 PDF 操作工具,使用 docker,允许您对 PDF 文件执行各种操作,例如拆分合并、转换、重新组织、添加图像、旋转、压缩等。"
- local docker_url="官网介绍: ${gh_proxy}github.com/Stirling-Tools/Stirling-PDF"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 88|owncast)
- 32)
- local docker_name="drawio"
- local docker_img="jgraph/drawio"
- local docker_port=7080
+ local app_id="88"
+ local docker_name="owncast"
+ local docker_img="owncast/owncast:latest"
+ local docker_port=8088
- docker_rum() {
+ docker_rum() {
- docker run -d --restart=always --name drawio -p ${docker_port}:8080 -v /home/docker/drawio:/var/lib/drawio jgraph/drawio
+ docker run -d \
+ --name owncast \
+ -p ${docker_port}:8080 \
+ -p 1935:1935 \
+ -v /home/docker/owncast/data:/app/data \
+ --restart=always \
+ owncast/owncast:latest
- }
+ }
- local docker_describe="这是一个强大图表绘制软件。思维导图,拓扑图,流程图,都能画"
- local docker_url="官网介绍: https://www.drawio.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="開源、免費的自建直播平台"
+ local docker_url="官網介紹: https://owncast.online"
+ local docker_use="echo \"訪問地址後面帶 /admin 訪問管理員頁面\""
+ local docker_passwd="echo \"初始帳號: admin 初始密碼: abc123 登陸後請及時修改登入密碼\""
+ local app_size="1"
+ docker_app
- 33)
- local docker_name="sun-panel"
- local docker_img="hslr/sun-panel"
- local docker_port=3009
+ ;;
- docker_rum() {
- docker run -d --restart=always -p ${docker_port}:3002 \
- -v /home/docker/sun-panel/conf:/app/conf \
- -v /home/docker/sun-panel/uploads:/app/uploads \
- -v /home/docker/sun-panel/database:/app/database \
- --name sun-panel \
- hslr/sun-panel
- }
+ 89|file-code-box)
- local docker_describe="Sun-Panel服务器、NAS导航面板、Homepage、浏览器首页"
- local docker_url="官网介绍: https://doc.sun-panel.top/zh_cn/"
- local docker_use="echo \"账号: admin@sun.cc 密码: 12345678\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="89"
+ local docker_name="file-code-box"
+ local docker_img="lanol/filecodebox:latest"
+ local docker_port=8089
- 34)
- local docker_name="pingvin-share"
- local docker_img="stonith404/pingvin-share"
- local docker_port=3060
-
- docker_rum() {
-
- docker run -d \
- --name pingvin-share \
- --restart always \
- -p ${docker_port}:3000 \
- -v /home/docker/pingvin-share/data:/opt/app/backend/data \
- stonith404/pingvin-share
- }
+ docker_rum() {
- local docker_describe="Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品"
- local docker_url="官网介绍: ${gh_proxy}github.com/stonith404/pingvin-share"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --name file-code-box \
+ -p ${docker_port}:12345 \
+ -v /home/docker/file-code-box/data:/app/data \
+ --restart=always \
+ lanol/filecodebox:latest
+ }
- 35)
- local docker_name="moments"
- local docker_img="kingwrcy/moments:latest"
- local docker_port=8035
-
- docker_rum() {
-
- docker run -d --restart unless-stopped \
- -p ${docker_port}:3000 \
- -v /home/docker/moments/data:/app/data \
- -v /etc/localtime:/etc/localtime:ro \
- -v /etc/timezone:/etc/timezone:ro \
- --name moments \
- kingwrcy/moments:latest
- }
+ local docker_describe="匿名口令分享文字和文件,像拿快遞一樣取文件"
+ local docker_url="官網介紹:${gh_https_url}github.com/vastsa/FileCodeBox"
+ local docker_use="echo \"訪問地址後面帶 /#/admin 訪問管理員頁面\""
+ local docker_passwd="echo \"管理員密碼: FileCodeBox2023\""
+ local app_size="1"
+ docker_app
+ ;;
- local docker_describe="极简朋友圈,高仿微信朋友圈,记录你的美好生活"
- local docker_url="官網介紹:${gh_proxy}github.com/kingwrcy/moments?tab=readme-ov-file"
- local docker_use="echo \"账号: admin 密码: a123456\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 36)
- local docker_name="lobe-chat"
- local docker_img="lobehub/lobe-chat:latest"
- local docker_port=8036
+ 90|matrix)
- docker_rum() {
+ local app_id="90"
+ local docker_name="matrix"
+ local docker_img="matrixdotorg/synapse:latest"
+ local docker_port=8090
- docker run -d -p ${docker_port}:3210 \
- --name lobe-chat \
- --restart=always \
- lobehub/lobe-chat
- }
+ docker_rum() {
- local docker_describe="LobeChat聚合市面上主流的AI大模型,ChatGPT/Claude/Gemini/Groq/Ollama"
- local docker_url="官网介绍: ${gh_proxy}github.com/lobehub/lobe-chat"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ add_yuming
- 37)
- local docker_name="myip"
- local docker_img="jason5ng32/myip:latest"
- local docker_port=8037
+ if [ ! -d /home/docker/matrix/data ]; then
+ docker run --rm \
+ -v /home/docker/matrix/data:/data \
+ -e SYNAPSE_SERVER_NAME=${yuming} \
+ -e SYNAPSE_REPORT_STATS=yes \
+ --name matrix \
+ matrixdotorg/synapse:latest generate
+ fi
- docker_rum() {
+ docker run -d \
+ --name matrix \
+ -v /home/docker/matrix/data:/data \
+ -p ${docker_port}:8008 \
+ --restart=always \
+ matrixdotorg/synapse:latest
- docker run -d -p ${docker_port}:18966 --name myip jason5ng32/myip:latest
+ echo "建立初始使用者或管理員。請設定以下內容使用者名稱和密碼以及是否為管理員。"
+ docker exec -it matrix register_new_matrix_user \
+ http://localhost:8008 \
+ -c /data/homeserver.yaml
- }
+ sed -i '/^enable_registration:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration: true' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^enable_registration_without_verification:/d' /home/docker/matrix/data/homeserver.yaml
+ sed -i '/^# vim:ft=yaml/i enable_registration_without_verification: true' /home/docker/matrix/data/homeserver.yaml
+ docker restart matrix
- local docker_describe="是一个多功能IP工具箱,可以查看自己IP信息及连通性,用网页面板呈现"
- local docker_url="官网介绍: ${gh_proxy}github.com/jason5ng32/MyIP/blob/main/README_ZH.md"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 38)
- send_stats "小雅全家桶"
- clear
- install_docker
- check_disk_space 1
- bash -c "$(curl --insecure -fsSL https://ddsrem.com/xiaoya_install.sh)"
- ;;
+ }
- 39)
+ local docker_describe="Matrix是一個去中心化的聊天協議"
+ local docker_url="官網介紹: https://matrix.org/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- if [ ! -d /home/docker/bililive-go/ ]; then
- mkdir -p /home/docker/bililive-go/ > /dev/null 2>&1
- wget -O /home/docker/bililive-go/config.yml ${gh_proxy}raw.githubusercontent.com/hr3lxphr6j/bililive-go/master/config.yml > /dev/null 2>&1
- fi
+ ;;
- local docker_name="bililive-go"
- local docker_img="chigusa/bililive-go"
- local docker_port=8039
- docker_rum() {
- docker run --restart=always --name bililive-go -v /home/docker/bililive-go/config.yml:/etc/bililive-go/config.yml -v /home/docker/bililive-go/Videos:/srv/bililive -p ${docker_port}:8080 -d chigusa/bililive-go
+ 91|gitea)
- }
+ local app_id="91"
- local docker_describe="Bililive-go是一个支持多种直播平台的直播录制工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/hr3lxphr6j/bililive-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_name="gitea私有程式碼倉庫"
+ local app_text="免費新一代的程式碼託管平台,提供接近 GitHub 的使用體驗。"
+ local app_url="影片介紹:${gh_https_url}github.com/go-gitea/gitea"
+ local docker_name="gitea"
+ local docker_port="8091"
+ local app_size="2"
- 40)
- local docker_name="webssh"
- local docker_img="jrohy/webssh"
- local docker_port=8040
- docker_rum() {
- docker run -d -p ${docker_port}:5032 --restart always --name webssh -e TZ=Asia/Shanghai jrohy/webssh
- }
+ docker_app_install() {
- local docker_describe="简易在线ssh连接工具和sftp工具"
- local docker_url="官网介绍: ${gh_proxy}github.com/Jrohy/webssh"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ mkdir -p /home/docker/gitea
+ mkdir -p /home/docker/gitea/gitea
+ mkdir -p /home/docker/gitea/data
+ mkdir -p /home/docker/gitea/postgres
+ cd /home/docker/gitea
- 41)
+ curl -o /home/docker/gitea/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/gitea-docker-compose.yml
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/gitea/docker-compose.yml
+ cd /home/docker/gitea/
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="耗子面板"
- local panelurl="官方地址: ${gh_proxy}github.com/TheTNB/panel"
- panel_app_install() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
- cd ~
- }
+ docker_app_update() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ cd /home/docker/gitea/ && docker compose up -d
+ }
- panel_app_manage() {
- panel-cli
- }
- panel_app_uninstall() {
- mkdir -p ~/haozi && cd ~/haozi && curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
- cd ~
- }
+ docker_app_uninstall() {
+ cd /home/docker/gitea/ && docker compose down --rmi all
+ rm -rf /home/docker/gitea
+ echo "應用程式已解除安裝"
+ }
- install_panel
+ docker_app_plus
- ;;
+ ;;
- 42)
- local docker_name="nexterm"
- local docker_img="germannewsmaker/nexterm:latest"
- local docker_port=8042
- docker_rum() {
- docker run -d \
- --name nexterm \
- -p ${docker_port}:6989 \
- -v /home/docker/nexterm:/app/data \
- --restart unless-stopped \
- germannewsmaker/nexterm:latest
+ 92|filebrowser)
- }
+ local app_id="92"
+ local docker_name="filebrowser"
+ local docker_img="hurlenko/filebrowser"
+ local docker_port=8092
- local docker_describe="nexterm是一款强大的在线SSH/VNC/RDP连接工具。"
- local docker_url="官网介绍: ${gh_proxy}github.com/gnmyt/Nexterm"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_rum() {
- 43)
- local docker_name="hbbs"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker run -d \
+ --name filebrowser \
+ --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/filebrowser/data:/data \
+ -v /home/docker/filebrowser/config:/config \
+ -e FB_BASEURL=/filebrowser \
+ hurlenko/filebrowser
- docker_rum() {
+ }
- docker run --name hbbs -v /home/docker/hbbs/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbs
+ local docker_describe="是一個基於Web的文件管理器"
+ local docker_url="官網介紹: https://filebrowser.org/"
+ local docker_use="docker logs filebrowser"
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- }
+ ;;
+ 93|dufs)
- local docker_describe="rustdesk开源的远程桌面(服务端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="docker logs hbbs"
- local docker_passwd="echo \"把你的IP和key记录下,会在远程桌面客户端中用到。去44选项装中继端吧!\""
- local app_size="1"
- docker_app
- ;;
+ local app_id="93"
+ local docker_name="dufs"
+ local docker_img="sigoden/dufs"
+ local docker_port=8093
- 44)
- local docker_name="hbbr"
- local docker_img="rustdesk/rustdesk-server"
- local docker_port=0000
+ docker_rum() {
- docker_rum() {
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}:/data \
+ -p ${docker_port}:5000 \
+ ${docker_img} /data -A
- docker run --name hbbr -v /home/docker/hbbr/data:/root -td --net=host --restart unless-stopped rustdesk/rustdesk-server hbbr
+ }
- }
+ local docker_describe="極簡靜態檔案伺服器,支援上傳下載"
+ local docker_url="官網介紹:${gh_https_url}github.com/sigoden/dufs"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="rustdesk开源的远程桌面(中继端),类似自己的向日葵私服。"
- local docker_url="官网介绍: https://rustdesk.com/zh-cn/"
- local docker_use="echo \"前往官网下载远程桌面的客户端: https://rustdesk.com/zh-cn/\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 45)
- local docker_name="registry"
- local docker_img="registry:2"
- local docker_port=8045
+ 94|gopeed)
- docker_rum() {
+ local app_id="94"
+ local docker_name="gopeed"
+ local docker_img="liwei2633/gopeed"
+ local docker_port=8094
- docker run -d \
- -p ${docker_port}:5000 \
- --name registry \
- -v /home/docker/registry:/var/lib/registry \
- -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
- --restart always \
- registry:2
+ docker_rum() {
- }
+ read -e -p "設定登入用戶名:" app_use
+ read -e -p "設定登入密碼:" app_passwd
- local docker_describe="Docker Registry 是一个用于存储和分发 Docker 镜像的服务。"
- local docker_url="官网介绍: https://hub.docker.com/_/registry"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
- ;;
+ docker run -d \
+ --name ${docker_name} \
+ --restart=always \
+ -v /home/docker/${docker_name}/downloads:/app/Downloads \
+ -v /home/docker/${docker_name}/storage:/app/storage \
+ -p ${docker_port}:9999 \
+ ${docker_img} -u ${app_use} -p ${app_passwd}
- 46)
- local docker_name="ghproxy"
- local docker_img="wjqserver/ghproxy:latest"
- local docker_port=8046
+ }
- docker_rum() {
+ local docker_describe="分散式高速下載工具,支援多種協議"
+ local docker_url="官網介紹:${gh_https_url}github.com/GopeedLab/gopeed"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d --name ghproxy --restart always -p ${docker_port}:8080 wjqserver/ghproxy:latest
+ ;;
- }
- local docker_describe="使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取。"
- local docker_url="官网介绍: https://github.com/WJQSERVER-STUDIO/ghproxy"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
- 47)
+ 95|paperless)
+ local app_id="95"
+ local app_name="paperless文件管理平台"
+ local app_text="開源的電子文件管理系統,它的主要用途是把你的紙本文件數位化並管理起來。"
+ local app_url="影片介紹: https://docs.paperless-ngx.com/"
+ local docker_name="paperless-webserver-1"
+ local docker_port="8095"
+ local app_size="2"
- local app_name="普罗米修斯监控"
- local app_text="Prometheus+Grafana企业级监控系统"
- local app_url="官网介绍: https://prometheus.io"
- local docker_name="grafana"
- local docker_port="8047"
- local app_size="2"
+ docker_app_install() {
- docker_app_install() {
- prometheus_install
- clear
- ip_address
- echo "已經安裝完成"
- check_docker_app_ip
- echo "初始用戶名密碼均為: admin"
- }
+ mkdir -p /home/docker/paperless
+ mkdir -p /home/docker/paperless/export
+ mkdir -p /home/docker/paperless/consume
+ cd /home/docker/paperless
- docker_app_update() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
- docker_app_install
- }
+ curl -o /home/docker/paperless/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/docker-compose.postgres-tika.yml
+ curl -o /home/docker/paperless/docker-compose.env ${gh_proxy}raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/compose/.env
- docker_app_uninstall() {
- docker rm -f node-exporter prometheus grafana
- docker rmi -f prom/node-exporter
- docker rmi -f prom/prometheus:latest
- docker rmi -f grafana/grafana:latest
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/paperless/docker-compose.yml
+ cd /home/docker/paperless
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- rm -rf /home/docker/monitoring
- echo "應用已卸載"
- }
- docker_app_plus
- ;;
+ docker_app_update() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ docker_app_install
+ }
- 48)
- local docker_name="node-exporter"
- local docker_img="prom/node-exporter"
- local docker_port=8048
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/paperless/ && docker compose down --rmi all
+ rm -rf /home/docker/paperless
+ echo "應用程式已解除安裝"
+ }
- docker run -d \
- --name=node-exporter \
- -p ${docker_port}:9100 \
- --restart unless-stopped \
- prom/node-exporter
+ docker_app_plus
+ ;;
- }
- local docker_describe="这是一个普罗米修斯的主机数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/prometheus/node_exporter"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
-
- 49)
- local docker_name="cadvisor"
- local docker_img="gcr.io/cadvisor/cadvisor:latest"
- local docker_port=8049
-
- docker_rum() {
-
- docker run -d \
- --name=cadvisor \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- --volume=/:/rootfs:ro \
- --volume=/var/run:/var/run:rw \
- --volume=/sys:/sys:ro \
- --volume=/var/lib/docker/:/var/lib/docker:ro \
- gcr.io/cadvisor/cadvisor:latest \
- -housekeeping_interval=10s \
- -docker_only=true
- }
+ 96|2fauth)
- local docker_describe="这是一个普罗米修斯的容器数据采集组件,请部署在被监控主机上。"
- local docker_url="官网介绍: https://github.com/google/cadvisor"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local app_id="96"
+ local app_name="2FAuth自架二步驟驗證器"
+ local app_text="自託管的雙重身分驗證 (2FA) 帳戶管理和驗證碼產生工具。"
+ local app_url="官網:${gh_https_url}github.com/Bubka/2FAuth"
+ local docker_name="2fauth"
+ local docker_port="8096"
+ local app_size="1"
- 50)
- local docker_name="changedetection"
- local docker_img="dgtlmoon/changedetection.io:latest"
- local docker_port=8050
+ docker_app_install() {
- docker_rum() {
+ add_yuming
- docker run -d --restart always -p ${docker_port}:5000 \
- -v /home/docker/datastore:/datastore \
- --name changedetection dgtlmoon/changedetection.io:latest
+ mkdir -p /home/docker/2fauth
+ mkdir -p /home/docker/2fauth/data
+ chmod -R 777 /home/docker/2fauth/
+ cd /home/docker/2fauth
- }
+ curl -o /home/docker/2fauth/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/2fauth-docker-compose.yml
- local docker_describe="这是一款网站变化检测、补货监控和通知的小工具"
- local docker_url="官网介绍: https://github.com/dgtlmoon/changedetection.io"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ sed -i "s/8000:8000/${docker_port}:8000/g" /home/docker/2fauth/docker-compose.yml
+ sed -i "s/yuming.com/${yuming}/g" /home/docker/2fauth/docker-compose.yml
+ cd /home/docker/2fauth
+ docker compose up -d
+ ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
+ block_container_port "$docker_name" "$ipv4_address"
- 51)
clear
- send_stats "PVE開小雞"
- check_disk_space 1
- curl -L ${gh_proxy}raw.githubusercontent.com/oneclickvirt/pve/main/scripts/install_pve.sh -o install_pve.sh && chmod +x install_pve.sh && bash install_pve.sh
- ;;
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- 52)
- local docker_name="dpanel"
- local docker_img="dpanel/dpanel:lite"
- local docker_port=8052
+ docker_app_update() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ docker_app_install
+ }
- docker_rum() {
- docker run -it -d --name dpanel --restart=always \
- -p ${docker_port}:8080 -e APP_NAME=dpanel \
- -v /var/run/docker.sock:/var/run/docker.sock \
- -v /home/docker/dpanel:/dpanel \
- dpanel/dpanel:lite
+ docker_app_uninstall() {
+ cd /home/docker/2fauth/ && docker compose down --rmi all
+ rm -rf /home/docker/2fauth
+ echo "應用程式已解除安裝"
+ }
- }
+ docker_app_plus
- local docker_describe="Docker可视化面板系统,提供完善的docker管理功能。"
- local docker_url="官网介绍: https://github.com/donknap/dpanel"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 53)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
- docker_rum() {
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ 97|wgs)
- }
+ local app_id="97"
+ local docker_name="wireguard"
+ local docker_img="lscr.io/linuxserver/wireguard:latest"
+ local docker_port=8097
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的llama3大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run llama3.2:1b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_rum() {
- 54)
+ read -e -p "請輸入組網的用戶端數量 (預設 5):" COUNT
+ COUNT=${COUNT:-5}
+ read -e -p "請輸入 WireGuard 網段 (預設 10.13.13.0):" NETWORK
+ NETWORK=${NETWORK:-10.13.13.0}
- local lujing="[ -d "/www/server/panel" ]"
- local panelname="AMH面板"
- local panelurl="官方地址: https://amh.sh/index.htm?amh"
+ PEERS=$(seq -f "wg%02g" 1 "$COUNT" | paste -sd,)
- panel_app_install() {
- cd ~
- wget https://dl.amh.sh/amh.sh && bash amh.sh
- }
+ ip link delete wg0 &>/dev/null
- panel_app_manage() {
- panel_app_install
- }
+ ip_address
+ docker run -d \
+ --name=wireguard \
+ --network host \
+ --cap-add=NET_ADMIN \
+ --cap-add=SYS_MODULE \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e SERVERURL=${ipv4_address} \
+ -e SERVERPORT=51820 \
+ -e PEERS=${PEERS} \
+ -e INTERNAL_SUBNET=${NETWORK} \
+ -e ALLOWEDIPS=${NETWORK}/24 \
+ -e PERSISTENTKEEPALIVE_PEERS=all \
+ -e LOG_CONFS=true \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules \
+ --restart=always \
+ lscr.io/linuxserver/wireguard:latest
- panel_app_uninstall() {
- panel_app_install
- }
- install_panel
- ;;
+ sleep 3
+ docker exec wireguard sh -c "
+ f='/config/wg_confs/wg0.conf'
+ sed -i 's/51820/${docker_port}/g' \$f
+ "
- 55)
- frps_panel
- ;;
+ docker exec wireguard sh -c "
+ for d in /config/peer_*; do
+ sed -i 's/51820/${docker_port}/g' \$d/*.conf
+ done
+ "
- 56)
- frpc_panel
- ;;
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ sed -i "/^DNS/d" "$d"/*.conf
+ done
+ '
- 57)
- local docker_name="ollama"
- local docker_img="ghcr.io/open-webui/open-webui:ollama"
- local docker_port=8053
+ docker exec wireguard sh -c '
+ for d in /config/peer_*; do
+ for f in "$d"/*.conf; do
+ grep -q "^PersistentKeepalive" "$f" || \
+ sed -i "/^AllowedIPs/ a PersistentKeepalive = 25" "$f"
+ done
+ done
+ '
+
+ docker exec wireguard bash -c '
+ for d in /config/peer_*; do
+ cd "$d" || continue
+ conf_file=$(ls *.conf)
+ base_name="${conf_file%.conf}"
+ qrencode -o "$base_name.png" < "$conf_file"
+ done
+ '
- docker_rum() {
+ docker restart wireguard
- docker run -d -p ${docker_port}:8080 -v /home/docker/ollama:/root/.ollama -v /home/docker/ollama/open-webui:/app/backend/data --name ollama --restart always ghcr.io/open-webui/open-webui:ollama
+ sleep 2
+ echo
+ echo -e "${gl_huang}所有客戶端二維碼配置:${gl_bai}"
+ docker exec wireguard bash -c 'for i in $(ls /config | grep peer_ | sed "s/peer_//"); do echo "--- $i ---"; /app/show-peer $i; done'
+ sleep 2
+ echo
+ echo -e "${gl_huang}所有客戶端配置代碼:${gl_bai}"
+ docker exec wireguard sh -c 'for d in /config/peer_*; do echo "# $(basename $d) "; cat $d/*.conf; echo; done'
+ sleep 2
+ echo -e "${gl_lv}${COUNT}個客戶端配置全部輸出,使用方法如下:${gl_bai}"
+ echo -e "${gl_lv}1. 手機下載wg的APP,掃描上方二維碼,可快速連接網絡${gl_bai}"
+ echo -e "${gl_lv}2. Windows下載客戶端,複製設定碼連接網路。${gl_bai}"
+ echo -e "${gl_lv}3. Linux用腳本部署WG客戶端,複製設定碼連接網路。${gl_bai}"
+ echo -e "${gl_lv}官方客戶端下載方式: https://www.wireguard.com/install/${gl_bai}"
+ break_end
- }
+ }
- local docker_describe="OpenWebUI一款大语言模型网页框架,接入全新的DeepSeek R1大语言模型"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use="docker exec ollama ollama run deepseek-r1:1.5b"
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ local docker_describe="現代化、高效能的虛擬專用網路工具"
+ local docker_url="官網介紹: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 58)
- local app_name="Dify知识库"
- local app_text="是一款开源的大语言模型(LLM) 应用开发平台。自托管训练数据用于AI生成"
- local app_url="官方网站: https://docs.dify.ai/zh-hans"
- local docker_name="docker-nginx-1"
- local docker_port="8058"
- local app_size="3"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langgenius/dify.git && cd dify/docker && cp .env.example .env
- # sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- sed -i "s/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=${docker_port}/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/" /home/docker/dify/docker/.env
+ 98|wgc)
- docker compose up -d
- clear
- echo "已經安裝完成"
- check_docker_app_ip
- }
+ local app_id="98"
+ local docker_name="wireguardc"
+ local docker_img="kjlion/wireguard:alpine"
+ local docker_port=51820
- docker_app_update() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- cd /home/docker/dify/
- git pull origin main
- sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8058/; s/^EXPOSE_NGINX_SSL_PORT=.*/EXPOSE_NGINX_SSL_PORT=8858/' /home/docker/dify/docker/.env
- cd /home/docker/dify/docker/ && docker compose up -d
- }
+ docker_rum() {
- docker_app_uninstall() {
- cd /home/docker/dify/docker/ && docker compose down --rmi all
- rm -rf /home/docker/dify
- echo "應用已卸載"
- }
+ mkdir -p /home/docker/wireguard/config/
- docker_app_plus
+ local CONFIG_FILE="/home/docker/wireguard/config/wg0.conf"
- ;;
+ # 建立目錄(如果不存在)
+ mkdir -p "$(dirname "$CONFIG_FILE")"
- 59)
- local app_name="New API"
- local app_text="新一代大模型网关与AI资产管理系统"
- local app_url="官方网站: https://github.com/Calcium-Ion/new-api"
- local docker_name="new-api"
- local docker_port="8059"
- local app_size="3"
+ echo "請貼上你的客戶端配置,連續按兩次回車保存:"
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/Calcium-Ion/new-api.git && cd new-api
+ # 初始化變數
+ input=""
+ empty_line_count=0
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ # 逐行讀取使用者輸入
+ while IFS= read -r line; do
+ if [[ -z "$line" ]]; then
+ ((empty_line_count++))
+ if [[ $empty_line_count -ge 2 ]]; then
+ break
+ fi
+ else
+ empty_line_count=0
+ input+="$line"$'\n'
+ fi
+ done
+ # 寫入設定檔
+ echo "$input" > "$CONFIG_FILE"
- docker compose up -d
- clear
- echo "已經安裝完成"
- check_docker_app_ip
- }
+ echo "客戶端配置已儲存到$CONFIG_FILE"
- docker_app_update() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- cd /home/docker/new-api/
- git pull origin main
- sed -i -e "s/- \"3000:3000\"/- \"${docker_port}:3000\"/g" \
- -e 's/container_name: redis/container_name: redis-new-api/g' \
- -e 's/container_name: mysql/container_name: mysql-new-api/g' \
- docker-compose.yml
+ ip link delete wg0 &>/dev/null
- docker compose up -d
- clear
- echo "已經安裝完成"
- check_docker_app_ip
+ docker run -d \
+ --name wireguardc \
+ --network host \
+ --cap-add NET_ADMIN \
+ --cap-add SYS_MODULE \
+ -v /home/docker/wireguard/config:/config \
+ -v /lib/modules:/lib/modules:ro \
+ --restart=always \
+ kjlion/wireguard:alpine
- }
+ sleep 3
- docker_app_uninstall() {
- cd /home/docker/new-api/ && docker compose down --rmi all
- rm -rf /home/docker/new-api
- echo "應用已卸載"
- }
+ docker logs wireguardc
- docker_app_plus
+ break_end
- ;;
+ }
+ local docker_describe="現代化、高效能的虛擬專用網路工具"
+ local docker_url="官網介紹: https://www.wireguard.com/"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 60)
+ ;;
- local app_name="JumpServer开源堡垒机"
- local app_text="是一个开源的特权访问管理 (PAM) 工具,该程序占用80端口不支持添加域名访问了"
- local app_url="官方介绍: https://github.com/jumpserver/jumpserver"
- local docker_name="jms_web"
- local docker_port="80"
- local app_size="2"
- docker_app_install() {
- curl -sSL ${gh_proxy}github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
- clear
- echo "已經安裝完成"
- check_docker_app_ip
- echo "初始用戶名: admin"
- echo "初始密碼: ChangeMe"
- }
+ 99|dsm)
+ local app_id="99"
- docker_app_update() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh upgrade
- echo "應用已更新"
- }
+ local app_name="dsm群暉虛擬機"
+ local app_text="Docker容器中的虛擬DSM"
+ local app_url="官網:${gh_https_url}github.com/vdsm/virtual-dsm"
+ local docker_name="dsm"
+ local docker_port="8099"
+ local app_size="16"
+ docker_app_install() {
- docker_app_uninstall() {
- cd /opt/jumpserver-installer*/
- ./jmsctl.sh uninstall
- cd /opt
- rm -rf jumpserver-installer*/
- rm -rf jumpserver
- echo "應用已卸載"
- }
+ read -e -p "設定 CPU 核數 (預設 2):" CPU_CORES
+ local CPU_CORES=${CPU_CORES:-2}
- docker_app_plus
- ;;
+ read -e -p "設定記憶體大小 (預設 4G):" RAM_SIZE
+ local RAM_SIZE=${RAM_SIZE:-4}
- 61)
- local docker_name="libretranslate"
- local docker_img="libretranslate/libretranslate:latest"
- local docker_port=8061
+ mkdir -p /home/docker/dsm
+ mkdir -p /home/docker/dsm/dev
+ chmod -R 777 /home/docker/dsm/
+ cd /home/docker/dsm
- docker_rum() {
+ curl -o /home/docker/dsm/docker-compose.yml ${gh_proxy}raw.githubusercontent.com/kejilion/docker/main/dsm-docker-compose.yml
- docker run -d \
- -p ${docker_port}:5000 \
- --name libretranslate \
- libretranslate/libretranslate \
- --load-only ko,zt,zh,en,ja,pt,es,fr,de,ru
+ sed -i "s/5000:5000/${docker_port}:5000/g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|CPU_CORES: "2"|CPU_CORES: "${CPU_CORES}"|g" /home/docker/dsm/docker-compose.yml
+ sed -i "s|RAM_SIZE: "2G"|RAM_SIZE: "${RAM_SIZE}G"|g" /home/docker/dsm/docker-compose.yml
+ cd /home/docker/dsm
+ docker compose up -d
- }
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- local docker_describe="免费开源机器翻译 API,完全自托管,它的翻译引擎由开源Argos Translate库提供支持。"
- local docker_url="官网介绍: https://github.com/LibreTranslate/LibreTranslate"
- local docker_use=""
- local docker_passwd=""
- local app_size="5"
- docker_app
- ;;
+ docker_app_update() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ docker_app_install
+ }
- 62)
- local app_name="RAGFlow知识库"
- local app_text="基于深度文档理解的开源 RAG(检索增强生成)引擎"
- local app_url="官方网站: https://github.com/infiniflow/ragflow"
- local docker_name="ragflow-server"
- local docker_port="8062"
- local app_size="8"
+ docker_app_uninstall() {
+ cd /home/docker/dsm/ && docker compose down --rmi all
+ rm -rf /home/docker/dsm
+ echo "應用程式已解除安裝"
+ }
- docker_app_install() {
- install git
- mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/infiniflow/ragflow.git && cd ragflow/docker
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- clear
- echo "已經安裝完成"
- check_docker_app_ip
- }
+ docker_app_plus
- docker_app_update() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- cd /home/docker/ragflow/
- git pull origin main
- cd /home/docker/ragflow/docker/
- sed -i "s/- 80:80/- ${docker_port}:80/; /- 443:443/d" docker-compose.yml
- docker compose up -d
- }
+ ;;
- docker_app_uninstall() {
- cd /home/docker/ragflow/docker/ && docker compose down --rmi all
- rm -rf /home/docker/ragflow
- echo "應用已卸載"
- }
- docker_app_plus
- ;;
+ 100|syncthing)
+ local app_id="100"
+ local docker_name="syncthing"
+ local docker_img="syncthing/syncthing:latest"
+ local docker_port=8100
- 63)
- local docker_name="open-webui"
- local docker_img="ghcr.io/open-webui/open-webui:main"
- local docker_port=8063
+ docker_rum() {
+ docker run -d \
+ --name=syncthing \
+ --hostname=my-syncthing \
+ --restart=always \
+ -p ${docker_port}:8384 \
+ -p 22000:22000/tcp \
+ -p 22000:22000/udp \
+ -p 21027:21027/udp \
+ -v /home/docker/syncthing:/var/syncthing \
+ syncthing/syncthing:latest
+ }
- docker_rum() {
+ local docker_describe="開源的點對點檔案同步工具,類似 Dropbox、Resilio Sync,但完全去中心化。"
+ local docker_url="官網介紹:${gh_https_url}github.com/syncthing/syncthing"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- docker run -d -p ${docker_port}:8080 -v /home/docker/open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+ ;;
- }
- local docker_describe="OpenWebUI一款大语言模型网页框架,官方精简版本,支持各大模型API接入"
- local docker_url="官网介绍: https://github.com/open-webui/open-webui"
- local docker_use=""
- local docker_passwd=""
- local app_size="3"
- docker_app
- ;;
+ 101|moneyprinterturbo)
+ local app_id="101"
+ local app_name="AI影片生成工具"
+ local app_text="MoneyPrinterTurbo是一款使用AI大模型合成高清短影片的工具"
+ local app_url="官方網站:${gh_https_url}github.com/harry0703/MoneyPrinterTurbo"
+ local docker_name="moneyprinterturbo"
+ local docker_port="8101"
+ local app_size="3"
- 64)
- local docker_name="it-tools"
- local docker_img="corentinth/it-tools:latest"
- local docker_port=8064
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git && cd MoneyPrinterTurbo/
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
- docker_rum() {
- docker run -d --name it-tools --restart unless-stopped -p ${docker_port}:80 corentinth/it-tools:latest
- }
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- local docker_describe="对开发人员和 IT 工作者来说非常有用的工具"
- local docker_url="官网介绍: https://github.com/CorentinTh/it-tools"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker_app_update() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ cd /home/docker/MoneyPrinterTurbo/
+ git pull ${gh_proxy}github.com/harry0703/MoneyPrinterTurbo.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/MoneyPrinterTurbo/docker-compose.yml
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose up -d
+ }
- 65)
- local docker_name="n8n"
- local docker_img="docker.n8n.io/n8nio/n8n"
- local docker_port=8065
+ docker_app_uninstall() {
+ cd /home/docker/MoneyPrinterTurbo/ && docker compose down --rmi all
+ rm -rf /home/docker/MoneyPrinterTurbo
+ echo "應用程式已解除安裝"
+ }
- docker_rum() {
+ docker_app_plus
- add_yuming
- mkdir -p /home/docker/n8n
- chmod -R 777 /home/docker/n8n
-
- docker run -d --name n8n \
- --restart always \
- -p ${docker_port}:5678 \
- -v /home/docker/n8n:/home/node/.n8n \
- -e N8N_HOST=${yuming} \
- -e N8N_PORT=5678 \
- -e N8N_PROTOCOL=https \
- -e N8N_WEBHOOK_URL=https://${yuming}/ \
- docker.n8n.io/n8nio/n8n
+ ;;
- ldnmp_Proxy ${yuming} 127.0.0.1 ${docker_port}
- block_container_port "$docker_name" "$ipv4_address"
- }
- local docker_describe="是一款功能强大的自动化工作流平台"
- local docker_url="官网介绍: https://github.com/n8n-io/n8n"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 102|vocechat)
- 66)
- yt_menu_pro
- ;;
+ local app_id="102"
+ local docker_name="vocechat-server"
+ local docker_img="privoce/vocechat-server:latest"
+ local docker_port=8102
+ docker_rum() {
- 67)
- local docker_name="ddns-go"
- local docker_img="jeessy/ddns-go"
- local docker_port=8067
+ docker run -d --restart=always \
+ -p ${docker_port}:3000 \
+ --name vocechat-server \
+ -v /home/docker/vocechat/data:/home/vocechat-server/data \
+ privoce/vocechat-server:latest
- docker_rum() {
- docker run -d \
- --name ddns-go \
- --restart=always \
- -p ${docker_port}:9876 \
- -v /home/docker/ddns-go:/root \
- jeessy/ddns-go
+ }
- }
+ local docker_describe="是一款支援獨立部署的個人雲端社群媒體聊天服務"
+ local docker_url="官網介紹:${gh_https_url}github.com/Privoce/vocechat-web"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_describe="自动将你的公网 IP(IPv4/IPv6)实时更新到各大 DNS 服务商,实现动态域名解析。"
- local docker_url="官网介绍: https://github.com/jeessy2/ddns-go"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ ;;
- 68)
- local docker_name="allinssl"
- local docker_img="allinssl/allinssl:latest"
- local docker_port=8068
- docker_rum() {
- docker run -itd --name allinssl -p ${docker_port}:8888 -v /home/docker/allinssl/data:/www/allinssl/data -e ALLINSSL_USER=allinssl -e ALLINSSL_PWD=allinssldocker -e ALLINSSL_URL=allinssl allinssl/allinssl:latest
- }
+ 103|umami)
+ local app_id="103"
+ local app_name="Umami網站統計工具"
+ local app_text="開源、輕量、隱私友善的網站分析工具,類似GoogleAnalytics。"
+ local app_url="官方網站:${gh_https_url}github.com/umami-software/umami"
+ local docker_name="umami-umami-1"
+ local docker_port="8103"
+ local app_size="1"
- local docker_describe="开源免费的 SSL 证书自动化管理平台"
- local docker_url="官网介绍: https://allinssl.com"
- local docker_use="echo \"安全入口: /allinssl\""
- local docker_passwd="echo \"用户名: allinssl 密码: allinssldocker\""
- local app_size="1"
- docker_app
- ;;
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/umami-software/umami.git && cd umami
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/umami/docker-compose.yml
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ echo "初始使用者名稱: admin"
+ echo "初始密碼: umami"
+ }
- 69)
- local docker_name="sftpgo"
- local docker_img="drakkan/sftpgo:latest"
- local docker_port=8069
+ docker_app_update() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ cd /home/docker/umami/
+ git pull ${gh_proxy}github.com/umami-software/umami.git main > /dev/null 2>&1
+ sed -i "s/8501:8501/${docker_port}:8501/g" /home/docker/umami/docker-compose.yml
+ cd /home/docker/umami/ && docker compose up -d
+ }
- docker_rum() {
+ docker_app_uninstall() {
+ cd /home/docker/umami/ && docker compose down --rmi all
+ rm -rf /home/docker/umami
+ echo "應用程式已解除安裝"
+ }
- mkdir -p /home/docker/sftpgo/data
- mkdir -p /home/docker/sftpgo/config
- chown -R 1000:1000 /home/docker/sftpgo
+ docker_app_plus
- docker run -d \
- --name sftpgo \
- --restart=always \
- -p ${docker_port}:8080 \
- -p 22022:2022 \
- --mount type=bind,source=/home/docker/sftpgo/data,target=/srv/sftpgo \
- --mount type=bind,source=/home/docker/sftpgo/config,target=/var/lib/sftpgo \
- drakkan/sftpgo:latest
+ ;;
- }
+ 104|nginx-stream)
+ stream_panel
+ ;;
- local docker_describe="开源免费随时随地SFTP FTP WebDAV 文件传输工具"
- local docker_url="官网介绍: https://sftpgo.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ 105|siyuan)
- 70)
- local docker_name="astrbot"
- local docker_img="soulter/astrbot:latest"
- local docker_port=8070
+ local app_id="105"
+ local docker_name="siyuan"
+ local docker_img="b3log/siyuan"
+ local docker_port=8105
- docker_rum() {
+ docker_rum() {
- mkdir -p /home/docker/astrbot/data
+ read -e -p "設定登入密碼:" app_passwd
- sudo docker run -d \
- -p ${docker_port}:6185 \
- -p 6195:6195 \
- -p 6196:6196 \
- -p 6199:6199 \
- -p 11451:11451 \
- -v /home/docker/astrbot/data:/AstrBot/data \
- --restart unless-stopped \
- --name astrbot \
- soulter/astrbot:latest
+ docker run -d \
+ --name siyuan \
+ --restart=always \
+ -v /home/docker/siyuan/workspace:/siyuan/workspace \
+ -p ${docker_port}:6806 \
+ -e PUID=1001 \
+ -e PGID=1002 \
+ b3log/siyuan \
+ --workspace=/siyuan/workspace/ \
+ --accessAuthCode="${app_passwd}"
- }
+ }
- local docker_describe="开源AI聊天机器人框架,支持微信,QQ,TG接入AI大模型"
- local docker_url="官网介绍: https://astrbot.app/"
- local docker_use="echo \"用户名: astrbot 密码: astrbot\""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ local docker_describe="思源筆記是一款隱私優先的知識管理系統"
+ local docker_url="官網介紹:${gh_https_url}github.com/siyuan-note/siyuan"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 71)
- local docker_name="navidrome"
- local docker_img="deluan/navidrome:latest"
- local docker_port=8071
- docker_rum() {
+ 106|drawnix)
- docker run -d \
- --name navidrome \
- --restart=unless-stopped \
- --user $(id -u):$(id -g) \
- -v /home/docker/navidrome/music:/music \
- -v /home/docker/navidrome/data:/data \
- -p ${docker_port}:4533 \
- -e ND_LOGLEVEL=info \
- deluan/navidrome:latest
+ local app_id="106"
+ local docker_name="drawnix"
+ local docker_img="pubuzhixing/drawnix"
+ local docker_port=8106
- }
+ docker_rum() {
- local docker_describe="是一个轻量、高性能的音乐流媒体服务器"
- local docker_url="官网介绍: https://www.navidrome.org/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ docker run -d \
+ --restart=always \
+ --name drawnix \
+ -p ${docker_port}:80 \
+ pubuzhixing/drawnix
+ }
- 72)
+ local docker_describe="是一款強大的開源白板工具,整合心智圖、流程圖等。"
+ local docker_url="官網介紹:${gh_https_url}github.com/plait-board/drawnix"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- local docker_name="bitwarden"
- local docker_img="vaultwarden/server"
- local docker_port=8072
+ ;;
- docker_rum() {
- docker run -d \
- --name bitwarden \
- --restart always \
- -p ${docker_port}:80 \
- -v /home/docker/bitwarden/data:/data \
- vaultwarden/server
+ 107|pansou)
- }
+ local app_id="107"
+ local docker_name="pansou"
+ local docker_img="ghcr.io/fish2018/pansou-web"
+ local docker_port=8107
- local docker_describe="一个你可以控制数据的密码管理器"
- local docker_url="官网介绍: https://bitwarden.com/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_rum() {
+ docker run -d \
+ --name pansou \
+ --restart=always \
+ -p ${docker_port}:80 \
+ -v /home/docker/pansou/data:/app/data \
+ -v /home/docker/pansou/logs:/app/logs \
+ -e ENABLED_PLUGINS="hunhepan,jikepan,panwiki,pansearch,panta,qupansou,
+susu,thepiratebay,wanou,xuexizhinan,panyq,zhizhen,labi,muou,ouge,shandian,
+duoduo,huban,cyg,erxiao,miaoso,fox4k,pianku,clmao,wuji,cldi,xiaozhang,
+libvio,leijing,xb6v,xys,ddys,hdmoli,yuhuage,u3c3,javdb,clxiong,jutoushe,
+sdso,xiaoji,xdyh,haisou,bixin,djgou,nyaa,xinjuc,aikanzy,qupanshe,xdpan,
+discourse,yunsou,ahhhhfs,nsgame,gying" \
+ ghcr.io/fish2018/pansou-web
- ;;
+ }
+ local docker_describe="PanSou是一個高效能的網盤資源搜尋API服務。"
+ local docker_url="官網介紹:${gh_https_url}github.com/fish2018/pansou"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 73)
- local docker_name="libretv"
- local docker_img="bestzwei/libretv:latest"
- local docker_port=8073
- docker_rum() {
- read -e -p "設置LibreTV的登錄密碼:" app_passwd
+ 108|langbot)
+ local app_id="108"
+ local app_name="LangBot聊天機器人"
+ local app_text="是一個開源的大語言模式原生即時通訊機器人開發平台"
+ local app_url="官方網站:${gh_https_url}github.com/langbot-app/LangBot"
+ local docker_name="langbot_plugin_runtime"
+ local docker_port="8108"
+ local app_size="1"
- docker run -d \
- --name libretv \
- --restart unless-stopped \
- -p ${docker_port}:8080 \
- -e PASSWORD=${app_passwd} \
- bestzwei/libretv:latest
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/langbot-app/LangBot && cd LangBot/docker
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
- }
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/LibreSpark/LibreTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_update() {
+ cd /home/docker/LangBot/docker && docker compose down --rmi all
+ cd /home/docker/LangBot/
+ git pull ${gh_proxy}github.com/langbot-app/LangBot main > /dev/null 2>&1
+ sed -i "s/5300:5300/${docker_port}:5300/g" /home/docker/LangBot/docker/docker-compose.yaml
+ cd /home/docker/LangBot/docker/ && docker compose up -d
+ }
- ;;
+ docker_app_uninstall() {
+ cd /home/docker/LangBot/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/LangBot
+ echo "應用程式已解除安裝"
+ }
+
+ docker_app_plus
+ ;;
- 74)
- local docker_name="moontv"
- local docker_img="ghcr.io/senshinya/moontv:latest"
- local docker_port=8074
+ 109|zfile)
- docker_rum() {
+ local app_id="109"
+ local docker_name="zfile"
+ local docker_img="zhaojun1998/zfile:latest"
+ local docker_port=8109
- read -e -p "設置MoonTV的登錄密碼:" app_passwd
+ docker_rum() {
- docker run -d \
- --name moontv \
- --restart unless-stopped \
- -p ${docker_port}:3000 \
- -e PASSWORD=${app_passwd} \
- ghcr.io/senshinya/moontv:latest
- }
+ docker run -d --name=zfile --restart=always \
+ -p ${docker_port}:8080 \
+ -v /home/docker/zfile/db:/root/.zfile-v4/db \
+ -v /home/docker/zfile/logs:/root/.zfile-v4/logs \
+ -v /home/docker/zfile/file:/data/file \
+ -v /home/docker/zfile/application.properties:/root/.zfile-v4/application.properties \
+ zhaojun1998/zfile:latest
- local docker_describe="免费在线视频搜索与观看平台"
- local docker_url="官网介绍: https://github.com/senshinya/MoonTV"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
- ;;
+ }
+ local docker_describe="是一個適用於個人或小型團隊的線上網盤程式。"
+ local docker_url="官網介紹:${gh_https_url}github.com/zfile-dev/zfile"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
- 75)
+ ;;
- local docker_name="melody"
- local docker_img="foamzou/melody:latest"
- local docker_port=8075
- docker_rum() {
+ 110|karakeep)
+ local app_id="110"
+ local app_name="karakeep書籤管理"
+ local app_text="是一款可自行託管的書籤應用,具有人工智慧功能,專為資料囤積者而設計。"
+ local app_url="官方網站:${gh_https_url}github.com/karakeep-app/karakeep"
+ local docker_name="docker-web-1"
+ local docker_port="8110"
+ local app_size="1"
- docker run -d \
- --name melody \
- --restart unless-stopped \
- -p ${docker_port}:5566 \
- -v /home/docker/melody/.profile:/app/backend/.profile \
- foamzou/melody:latest
+ docker_app_install() {
+ install git
+ mkdir -p /home/docker/ && cd /home/docker/ && git clone ${gh_proxy}github.com/karakeep-app/karakeep.git && cd karakeep/docker && cp .env.sample .env
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ docker compose up -d
+ clear
+ echo "已經安裝完成"
+ check_docker_app_ip
+ }
- }
+ docker_app_update() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ cd /home/docker/karakeep/
+ git pull ${gh_proxy}github.com/karakeep-app/karakeep.git main > /dev/null 2>&1
+ sed -i "s/3000:3000/${docker_port}:3000/g" /home/docker/karakeep/docker/docker-compose.yml
+ cd /home/docker/karakeep/docker/ && docker compose up -d
+ }
- local docker_describe="你的音乐精灵,旨在帮助你更好地管理音乐。"
- local docker_url="官网介绍: https://github.com/foamzou/melody"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ docker_app_uninstall() {
+ cd /home/docker/karakeep/docker/ && docker compose down --rmi all
+ rm -rf /home/docker/karakeep
+ echo "應用程式已解除安裝"
+ }
+ docker_app_plus
- ;;
+ ;;
- 76)
- local docker_name="dosgame"
- local docker_img="oldiy/dosgame-web-docker:latest"
- local docker_port=8076
+ 111|convertx)
- docker_rum() {
- docker run -d \
- --name dosgame \
- --restart unless-stopped \
- -p ${docker_port}:262 \
- oldiy/dosgame-web-docker:latest
+ local app_id="111"
+ local docker_name="convertx"
+ local docker_img="ghcr.io/c4illin/convertx:latest"
+ local docker_port=8111
- }
+ docker_rum() {
- local docker_describe="是一个中文DOS游戏合集网站"
- local docker_url="官网介绍: https://github.com/rwv/chinese-dos-games"
- local docker_use=""
- local docker_passwd=""
- local app_size="2"
- docker_app
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:3000 \
+ -v /home/docker/convertx:/app/data \
+ ${docker_img}
+ }
- ;;
+ local docker_describe="是一個功能強大的多格式文件轉換工具(支援文件、圖像、音訊視訊等)強烈建議添加域名訪問"
+ local docker_url="項目地址:${gh_https_url}github.com/c4illin/ConvertX"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="2"
+ docker_app
- 77)
+ ;;
- local docker_name="xunlei"
- local docker_img="cnk3x/xunlei"
- local docker_port=8077
- docker_rum() {
+ 112|lucky)
- read -e -p "設定${docker_name}的登錄用戶名:" app_use
- read -e -p "設定${docker_name}的登錄密碼:" app_passwd
+ local app_id="112"
+ local docker_name="lucky"
+ local docker_img="gdy666/lucky:v2"
+ # 由於 Lucky 使用 host 網路模式,這裡的連接埠僅作記錄/說明參考,實際上由應用自身控制(預設16601)
+ local docker_port=8112
- docker run -d \
- --name xunlei \
- --restart unless-stopped \
- --privileged \
- -e XL_DASHBOARD_USERNAME=${app_use} \
- -e XL_DASHBOARD_PASSWORD=${app_passwd} \
- -v /home/docker/xunlei/data:/xunlei/data \
- -v /home/docker/xunlei/downloads:/xunlei/downloads \
- -p ${docker_port}:2345 \
- cnk3x/xunlei
+ docker_rum() {
- }
+ docker run -d --name=${docker_name} --restart=always \
+ --network host \
+ -v /home/docker/lucky/conf:/app/conf \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ ${docker_img}
- local docker_describe="迅雷你的离线高速BT磁力下载工具"
- local docker_url="官网介绍: https://github.com/cnk3x/xunlei"
- local docker_use="echo \"手机登录迅雷,再输入邀请码,邀请码: 迅雷牛通\""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ echo "正在等待 Lucky 初始化..."
+ sleep 10
+ docker exec lucky /app/lucky -rSetHttpAdminPort ${docker_port}
- ;;
+ }
+ local docker_describe="Lucky 是一個大內部網路穿透及連接埠轉送管理工具,支援 DDNS、反向代理、WOL 等功能。"
+ local docker_url="項目地址:${gh_https_url}github.com/gdy666/lucky"
+ local docker_use="echo \"預設帳號密碼: 666\""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
- 78)
- local app_name="PandaWiki"
- local app_text="PandaWiki是一款AI大模型驱动的开源智能文档管理系统,强烈建议不要自定义端口部署。"
- local app_url="官方介绍: https://github.com/chaitin/PandaWiki"
- local docker_name="panda-wiki-nginx"
- local docker_port="2443"
- local app_size="2"
+ 113|firefox)
- docker_app_install() {
- bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"
- }
+ local app_id="113"
+ local docker_name="firefox"
+ local docker_img="jlesage/firefox:latest"
+ local docker_port=8113
- docker_app_update() {
- docker_app_install
- }
+ docker_rum() {
+ read -e -p "設定登入密碼:" admin_password
- docker_app_uninstall() {
- docker_app_install
- }
+ docker run -d --name=${docker_name} --restart=always \
+ -p ${docker_port}:5800 \
+ -v /home/docker/firefox:/config:rw \
+ -e ENABLE_CJK_FONT=1 \
+ -e WEB_AUDIO=1 \
+ -e VNC_PASSWORD="${admin_password}" \
+ ${docker_img}
+ }
- docker_app_plus
- ;;
+ local docker_describe="是一個運行在 Docker 中的 Firefox 瀏覽器,支援透過網頁直接存取桌面版瀏覽器介面。"
+ local docker_url="項目地址:${gh_https_url}github.com/jlesage/docker-firefox"
+ local docker_use=""
+ local docker_passwd=""
+ local app_size="1"
+ docker_app
+ ;;
+ 114|Moltbot|ClawdBot|moltbot|clawdbot|openclaw|OpenClaw)
+ moltbot_menu
+ ;;
- 79)
+ 115|hermes)
+ bash <(curl -sL ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/hermes_manager.sh)
+ ;;
- local docker_name="beszel"
- local docker_img="henrygd/beszel"
- local docker_port=8079
+ b)
+ clear
+ send_stats "全部應用程式備份"
- docker_rum() {
+ local backup_filename="app_$(date +"%Y%m%d%H%M%S").tar.gz"
+ echo -e "${gl_kjlan}正在備份$backup_filename ...${gl_bai}"
+ cd / && tar czvf "$backup_filename" home
- mkdir -p /home/docker/beszel && \
- docker run -d \
- --name beszel \
- --restart=unless-stopped \
- -v /home/docker/beszel:/beszel_data \
- -p ${docker_port}:8090 \
- henrygd/beszel
+ while true; do
+ clear
+ echo "備份檔案已建立: /$backup_filename"
+ read -e -p "要傳送備份資料到遠端伺服器嗎? (Y/N):" choice
+ case "$choice" in
+ [Yy])
+ kj_ssh_read_host_port "請輸入遠端伺服器IP:" "目標伺服器SSH連接埠 [預設22]:" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local TARGET_PORT="$KJ_SSH_PORT"
+ local latest_tar=$(ls -t /app*.tar.gz | head -1)
+ if [ -n "$latest_tar" ]; then
+ ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
+ sleep 2 # 添加等待时间
+ scp -P "$TARGET_PORT" -o StrictHostKeyChecking=no "$latest_tar" "root@$remote_ip:/"
+ echo "檔案已傳送至遠端伺服器/根目錄。"
+ else
+ echo "未找到要傳送的文件。"
+ fi
+ break
+ ;;
+ *)
+ echo "注意: 目前備份僅包含docker項目,不包含寶塔,1panel等建站面板的資料備份。"
+ break
+ ;;
+ esac
+ done
- }
+ ;;
- local docker_describe="Beszel轻量易用的服务器监控"
- local docker_url="官网介绍: https://beszel.dev/zh/"
- local docker_use=""
- local docker_passwd=""
- local app_size="1"
- docker_app
+ r)
+ root_use
+ send_stats "全部應用還原"
+ echo "可用的應用程式備份"
+ echo "-------------------------"
+ ls -lt /app*.gz | awk '{print $NF}'
+ echo ""
+ read -e -p "回車鍵還原最新的備份,輸入備份檔案名稱還原指定的備份,輸入0退出:" filename
- ;;
+ if [ "$filename" == "0" ]; then
+ break_end
+ linux_panel
+ fi
+
+ # 如果使用者沒有輸入檔名,使用最新的壓縮包
+ if [ -z "$filename" ]; then
+ local filename=$(ls -t /app*.tar.gz | head -1)
+ fi
+
+ if [ -n "$filename" ]; then
+ echo -e "${gl_kjlan}正在解壓縮$filename ...${gl_bai}"
+ cd / && tar -xzf "$filename"
+ echo "應用資料已還原,目前請手動進入指定應用程式選單,更新應用,即可還原應用程式。"
+ else
+ echo "沒有找到壓縮包。"
+ fi
+ ;;
- 0)
- kejilion
- ;;
- *)
- echo "無效的輸入!"
- ;;
- esac
- break_end
+ 0)
+ kejilion
+ ;;
+ *)
+ cd ~
+ install git
+ if [ ! -d apps/.git ]; then
+ timeout 10s git clone ${gh_proxy}github.com/kejilion/apps.git
+ else
+ cd apps
+ # git pull origin main > /dev/null 2>&1
+ timeout 10s git pull ${gh_proxy}github.com/kejilion/apps.git main > /dev/null 2>&1
+ fi
+ local custom_app="$HOME/apps/${sub_choice}.conf"
+ if [ -f "$custom_app" ]; then
+ . "$custom_app"
+ else
+ echo -e "${gl_hong}錯誤: 未找到編號為${sub_choice}的應用程式配置${gl_bai}"
+ fi
+ ;;
+ esac
+ break_end
+ sub_choice=""
- done
+done
}
+
linux_work() {
while true; do
clear
send_stats "後台工作區"
echo -e "後台工作區"
- echo -e "系統將為你提供可以後台常駐運行的工作區,你可以用來執行長時間的任務"
- echo -e "即使你斷開SSH,工作區中的任務也不會中斷,後台常駐任務。"
- echo -e "${gl_huang}提示:${gl_bai}進入工作區後使用Ctrl+b再單獨按d,退出工作區!"
+ echo -e "系統將為你提供可以後台常駐運作的工作區,你可以用來執行長時間的任務"
+ echo -e "即使你斷開SSH,工作區的任務也不會中斷,後台常駐任務。"
+ echo -e "${gl_huang}提示:${gl_bai}進入工作區後再使用Ctrl+b再單獨按d,退出工作區!"
echo -e "${gl_kjlan}------------------------"
- echo "當前已存在的工作區列表"
+ echo "目前已存在的工作區列表"
echo -e "${gl_kjlan}------------------------"
tmux list-sessions
echo -e "${gl_kjlan}------------------------"
@@ -10663,10 +19480,10 @@ linux_work() {
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}SSH常駐模式${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}22. ${gl_bai}創建/進入工作區"
- echo -e "${gl_kjlan}23. ${gl_bai}注入命令到後台工作區"
+ echo -e "${gl_kjlan}23. ${gl_bai}注入指令到後台工作區"
echo -e "${gl_kjlan}24. ${gl_bai}刪除指定工作區"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
@@ -10748,15 +19565,15 @@ linux_work() {
while true; do
clear
if grep -q 'tmux attach-session -t sshd || tmux new-session -s sshd' ~/.bashrc; then
- local tmux_sshd_status="${gl_lv}开启${gl_bai}"
+ local tmux_sshd_status="${gl_lv}開啟${gl_bai}"
else
- local tmux_sshd_status="${gl_hui}关闭${gl_bai}"
+ local tmux_sshd_status="${gl_hui}關閉${gl_bai}"
fi
send_stats "SSH常駐模式"
echo -e "SSH常駐模式${tmux_sshd_status}"
- echo "開啟後SSH連接後會直接進入常駐模式,直接回到之前的工作狀態。"
+ echo "開啟後SSH連線後會直接進入常駐模式,直接回到先前的工作狀態。"
echo "------------------------"
- echo "1. 開啟 2. 關閉"
+ echo "1. 開啟 2. 關閉"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -10771,7 +19588,7 @@ linux_work() {
tmux_run
;;
2)
- sed -i '/# 自动进入 tmux 会话/,+4d' ~/.bashrc
+ sed -i '/# 自動進入 tmux 會話/,+4d' ~/.bashrc
tmux kill-window -t sshd
;;
*)
@@ -10781,37 +19598,527 @@ linux_work() {
done
;;
- 22)
- read -e -p "請輸入你創建或進入的工作區名稱,如1001 kj001 work1:" SESSION_NAME
- tmux_run
- send_stats "自定義工作區"
- ;;
+ 22)
+ read -e -p "請輸入你所建立或進入的工作區名稱,如1001 kj001 work1:" SESSION_NAME
+ tmux_run
+ send_stats "自訂工作區"
+ ;;
+
+
+ 23)
+ read -e -p "請輸入你要後台執行的指令,如:curl -fsSL https://get.docker.com | sh:" tmuxd
+ tmux_run_d
+ send_stats "注入指令到後台工作區"
+ ;;
+
+ 24)
+ read -e -p "請輸入要刪除的工作區名稱:" gongzuoqu_name
+ tmux kill-window -t $gongzuoqu_name
+ send_stats "刪除工作區"
+ ;;
+
+ 0)
+ kejilion
+ ;;
+ *)
+ echo "無效的輸入!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+# 智慧切換鏡像來源函數
+switch_mirror() {
+ # 可選參數,預設為 false
+ local upgrade_software=${1:-false}
+ local clean_cache=${2:-false}
+
+ # 取得用戶國家
+ local country
+ country=$(curl -s ipinfo.io/country)
+
+ echo "檢測到國家:$country"
+
+ if [ "$country" = "CN" ]; then
+ echo "使用國內鏡像來源..."
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.huaweicloud.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ echo "使用海外鏡像來源..."
+ if [ -f /etc/os-release ] && grep -qi "oracle" /etc/os-release; then
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --source mirrors.xtom.com \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ else
+ bash <(curl -sSL https://linuxmirrors.cn/main.sh) \
+ --use-official-source true \
+ --protocol https \
+ --use-intranet-source false \
+ --backup true \
+ --upgrade-software "$upgrade_software" \
+ --clean-cache "$clean_cache" \
+ --ignore-backup-tips \
+ --install-epel false \
+ --pure-mode
+ fi
+ fi
+}
+
+
+fail2ban_panel() {
+ root_use
+ send_stats "ssh防禦"
+ while true; do
+
+ check_f2b_status
+ echo -e "SSH防禦程序$check_f2b_status"
+ echo "fail2ban是一個SSH防止暴力破解工具"
+ echo "官網介紹:${gh_proxy}github.com/fail2ban/fail2ban"
+ echo "------------------------"
+ echo "1. 安裝防禦程序"
+ echo "------------------------"
+ echo "2. 查看SSH攔截記錄"
+ echo "3. 日誌即時監控"
+ echo "------------------------"
+ echo "4. 基礎參數配置(封禁時間/時間視窗/重試次數)"
+ echo "5. 編輯設定檔(nano)"
+ echo "------------------------"
+ echo "9. 卸載防禦程序"
+ echo "------------------------"
+ echo "0. 返回上一級選單"
+ echo "------------------------"
+ read -e -p "請輸入你的選擇:" sub_choice
+ case $sub_choice in
+ 1)
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ break_end
+ ;;
+ 2)
+ echo "------------------------"
+ f2b_sshd
+ echo "------------------------"
+ break_end
+ ;;
+ 3)
+ tail -f /var/log/fail2ban.log
+ break
+ ;;
+ 4)
+ send_stats "SSH防禦基礎參數配置"
+ f2b_basic_config
+ break_end
+ ;;
+ 5)
+ send_stats "SSH防禦編輯設定檔"
+ f2b_edit_config
+ break_end
+ ;;
+ 9)
+ remove fail2ban
+ rm -rf /etc/fail2ban
+ echo "Fail2Ban防禦程序已卸載"
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+}
+
+
+
+
+
+net_menu() {
+
+ send_stats "網路卡管理工具"
+ show_nics() {
+ echo "================ 目前網卡資訊 =================="
+ printf "%-18s %-12s %-20s %-26s\n" "網路卡名" "狀態" "IP位址" "MAC位址"
+ echo "------------------------------------------------"
+ for nic in $(ls /sys/class/net); do
+ state=$(cat /sys/class/net/$nic/operstate 2>/dev/null)
+ ipaddr=$(ip -4 addr show $nic | awk '/inet /{print $2}' | head -n1)
+ mac=$(cat /sys/class/net/$nic/address 2>/dev/null)
+ printf "%-15s %-10s %-18s %-20s\n" "$nic" "$state" "${ipaddr:-无}" "$mac"
+ done
+ echo "================================================"
+ }
+
+ while true; do
+ clear
+ show_nics
+ echo
+ echo "=========== 網路卡管理選單 ==========="
+ echo "1. 啟用網卡"
+ echo "2. 停用網路卡"
+ echo "3. 查看網卡詳細信息"
+ echo "4. 刷新網卡資訊"
+ echo "0. 返回上一級選單"
+ echo "===================================="
+ read -erp "請選擇操作:" choice
+
+ case $choice in
+ 1)
+ send_stats "啟用網卡"
+ read -erp "請輸入要啟用的網路卡名稱:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" up && echo "✔ 網路卡$nic已啟用"
+ else
+ echo "✘ 網路卡不存在"
+ fi
+ read -erp "按回車繼續..."
+ ;;
+ 2)
+ send_stats "停用網路卡"
+ read -erp "請輸入要停用的網路卡名稱:" nic
+ if ip link show "$nic" &>/dev/null; then
+ ip link set "$nic" down && echo "✔ 網路卡$nic已停用"
+ else
+ echo "✘ 網路卡不存在"
+ fi
+ read -erp "按回車繼續..."
+ ;;
+ 3)
+ send_stats "查看網卡詳情"
+ read -erp "請輸入要查看的網路卡名稱:" nic
+ if ip link show "$nic" &>/dev/null; then
+ echo "========== $nic詳細資訊 =========="
+ ip addr show "$nic"
+ ethtool "$nic" 2>/dev/null | head -n 10
+ else
+ echo "✘ 網路卡不存在"
+ fi
+ read -erp "按回車繼續..."
+ ;;
+ 4)
+ send_stats "刷新網卡資訊"
+ continue
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+log_menu() {
+ send_stats "系統日誌管理工具"
+
+ show_log_overview() {
+ echo "============= 系統日誌概覽 ============="
+ echo "主機名稱: $(hostname)"
+ echo "系統時間: $(date)"
+ echo
+ echo "[ /var/log 目錄佔用 ]"
+ du -sh /var/log 2>/dev/null
+ echo
+ echo "[ journal 日誌佔用 ]"
+ journalctl --disk-usage 2>/dev/null
+ echo "========================================"
+ }
+
+ while true; do
+ clear
+ show_log_overview
+ echo
+ echo "=========== 系統日誌管理選單 ==========="
+ echo "1. 查看最近系統日誌(journal)"
+ echo "2. 查看指定服務日誌"
+ echo "3. 查看登入/安全日誌"
+ echo "4. 即時追蹤日誌"
+ echo "5. 清理舊 journal 日誌"
+ echo "0. 返回上一級選單"
+ echo "======================================="
+ read -erp "請選擇操作:" choice
+
+ case $choice in
+ 1)
+ send_stats "查看最近日誌"
+ read -erp "查看最近多少行日誌? [預設 100]:" lines
+ lines=${lines:-100}
+ journalctl -n "$lines" --no-pager
+ read -erp "按回車繼續..."
+ ;;
+ 2)
+ send_stats "查看指定服務日誌"
+ read -erp "請輸入服務名稱(如 sshd、nginx):" svc
+ if systemctl list-unit-files | grep -q "^$svc"; then
+ journalctl -u "$svc" -n 100 --no-pager
+ else
+ echo "✘ 服務不存在或無日誌"
+ fi
+ read -erp "按回車繼續..."
+ ;;
+ 3)
+ send_stats "查看登入/安全日誌"
+ echo "====== 最近登入日誌 ======"
+ last -n 10
+ echo
+ echo "====== 認證日誌 ======"
+ if [ -f /var/log/secure ]; then
+ tail -n 20 /var/log/secure
+ elif [ -f /var/log/auth.log ]; then
+ tail -n 20 /var/log/auth.log
+ else
+ echo "未找到安全日誌文件"
+ fi
+ read -erp "按回車繼續..."
+ ;;
+ 4)
+ send_stats "即時追蹤日誌"
+ echo "1) 系統日誌"
+ echo "2) 指定服務日誌"
+ read -erp "選擇追蹤類型:" t
+ if [ "$t" = "1" ]; then
+ journalctl -f
+ elif [ "$t" = "2" ]; then
+ read -erp "輸入服務名稱:" svc
+ journalctl -u "$svc" -f
+ else
+ echo "無效選擇"
+ fi
+ ;;
+ 5)
+ send_stats "清理舊 journal 日誌"
+ echo "⚠️ 清理 journal 日誌(安全方式)"
+ echo "1) 保留最近 7 天"
+ echo "2) 保留最近 3 天"
+ echo "3) 限制日誌最大 500M"
+ read -erp "請選擇清理方式:" c
+ case $c in
+ 1) journalctl --vacuum-time=7d ;;
+ 2) journalctl --vacuum-time=3d ;;
+ 3) journalctl --vacuum-size=500M ;;
+ *) echo "無效選項" ;;
+ esac
+ echo "✔ journal 日誌清理完成"
+ sleep 2
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+}
+
+
+
+env_menu() {
+
+ BASHRC="$HOME/.bashrc"
+ PROFILE="$HOME/.profile"
+
+ send_stats "系統變數管理工具"
+
+ show_env_vars() {
+ clear
+ send_stats "目前已生效環境變數"
+ echo "========== 目前已生效環境變數(節選) =========="
+ printf "%-20s %s\n" "變數名" "值"
+ echo "-----------------------------------------------"
+ for v in USER HOME SHELL LANG PWD; do
+ printf "%-20s %s\n" "$v" "${!v}"
+ done
+
+ echo
+ echo "PATH:"
+ echo "$PATH" | tr ':' '\n' | nl -ba
+
+ echo
+ echo "========== 設定檔中定義的變數(解析) =========="
+
+ parse_file_vars() {
+ local file="$1"
+ [ -f "$file" ] || return
+
+ echo
+ echo ">>> 來源文件:$file"
+ echo "-----------------------------------------------"
+
+ # 提取 export VAR=xxx 或 VAR=xxx
+ grep -Ev '^\s*#|^\s*$' "$file" \
+ | grep -E '^(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' \
+ | while read -r line; do
+ var=$(echo "$line" | sed -E 's/^(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*).*/\2/')
+ val=$(echo "$line" | sed -E 's/^[^=]+=//')
+ printf "%-20s %s\n" "$var" "$val"
+ done
+ }
+
+ parse_file_vars "$HOME/.bashrc"
+ parse_file_vars "$HOME/.profile"
+
+ echo
+ echo "==============================================="
+ read -erp "按回車繼續..."
+ }
+
+
+ view_file() {
+ local file="$1"
+ send_stats "查看變數文件$file"
+ clear
+ if [ -f "$file" ]; then
+ echo "========== 查看文件:$file =========="
+ cat -n "$file"
+ echo "===================================="
+ else
+ echo "文件不存在:$file"
+ fi
+ read -erp "按回車繼續..."
+ }
+
+ edit_file() {
+ local file="$1"
+ send_stats "編輯變數文件$file"
+ install nano
+ nano "$file"
+ }
+
+ source_files() {
+ echo "正在重新載入環境變數..."
+ send_stats "正在重新載入環境變數"
+ source "$BASHRC"
+ source "$PROFILE"
+ echo "✔ 環境變數已重新載入"
+ read -erp "按回車繼續..."
+ }
+
+ while true; do
+ clear
+ echo "=========== 系統環境變數管理 =========="
+ echo "目前使用者:$USER"
+ echo "--------------------------------------"
+ echo "1. 查看目前常用環境變數"
+ echo "2. 查看 ~/.bashrc"
+ echo "3. 查看 ~/.profile"
+ echo "4. 編輯 ~/.bashrc"
+ echo "5. 編輯 ~/.profile"
+ echo "6. 重新載入環境變數(source)"
+ echo "--------------------------------------"
+ echo "0. 返回上一級選單"
+ echo "--------------------------------------"
+ read -erp "請選擇操作:" choice
+
+ case "$choice" in
+ 1)
+ show_env_vars
+ ;;
+ 2)
+ view_file "$BASHRC"
+ ;;
+ 3)
+ view_file "$PROFILE"
+ ;;
+ 4)
+ edit_file "$BASHRC"
+ ;;
+ 5)
+ edit_file "$PROFILE"
+ ;;
+ 6)
+ source_files
+ ;;
+ 0)
+ break
+ ;;
+ *)
+ echo "無效選項"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
+create_user_with_sshkey() {
+ local new_username="$1"
+ local is_sudo="${2:-false}"
+ local sshkey_vl
- 23)
- read -e -p "請輸入你要後台執行的命令,如:curl -fsSL https://get.docker.com | sh:" tmuxd
- tmux_run_d
- send_stats "注入命令到後台工作區"
- ;;
+ if [[ -z "$new_username" ]]; then
+ echo "用法:create_user_with_sshkey <使用者名稱>"
+ return 1
+ fi
- 24)
- read -e -p "請輸入要刪除的工作區名稱:" gongzuoqu_name
- tmux kill-window -t $gongzuoqu_name
- send_stats "刪除工作區"
- ;;
+ # 創建用戶
+ useradd -m -s /bin/bash "$new_username" || return 1
- 0)
- kejilion
- ;;
- *)
- echo "無效的輸入!"
- ;;
- esac
- break_end
+ echo "導入公鑰範例:"
+ echo " - URL: ${gh_https_url}github.com/torvalds.keys"
+ echo "- 直接貼上: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
+ read -e -p "請導入${new_username}的公鑰:" sshkey_vl
- done
+ case "$sshkey_vl" in
+ http://*|https://*)
+ send_stats "從 URL 匯入 SSH 公鑰"
+ fetch_remote_ssh_keys "$sshkey_vl" "/home/$new_username"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa*)
+ send_stats "公鑰直接導入"
+ import_sshkey "$sshkey_vl" "/home/$new_username"
+ ;;
+ *)
+ echo "錯誤:未知參數 '$sshkey_vl'"
+ return 1
+ ;;
+ esac
+
+
+ # 修正權限
+ chown -R "$new_username:$new_username" "/home/$new_username/.ssh"
+
+ install sudo
+ # sudo 免密
+ if [[ "$is_sudo" == "true" ]]; then
+ cat >"/etc/sudoers.d/$new_username" <> /etc/ssh/sshd_config
+ passwd -l "$new_username" &>/dev/null
+ restart_ssh
+ echo "使用者$new_username創建完成"
}
@@ -10825,6 +20132,9 @@ linux_work() {
+
+
+
linux_Settings() {
while true; do
@@ -10832,34 +20142,37 @@ linux_Settings() {
# send_stats "系統工具"
echo -e "系統工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}1. ${gl_bai}設置腳本啟動快捷鍵${gl_kjlan}2. ${gl_bai}修改登錄密碼"
- echo -e "${gl_kjlan}3. ${gl_bai}ROOT密碼登錄模式${gl_kjlan}4. ${gl_bai}安裝Python指定版本"
- echo -e "${gl_kjlan}5. ${gl_bai}開放所有端口${gl_kjlan}6. ${gl_bai}修改SSH連接端口"
- echo -e "${gl_kjlan}7. ${gl_bai}優化DNS地址${gl_kjlan}8. ${gl_bai}一鍵重裝系統${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}9. ${gl_bai}禁用ROOT賬戶創建新賬戶${gl_kjlan}10. ${gl_bai}切換優先ipv4/ipv6"
+ echo -e "${gl_kjlan}1. ${gl_bai}設定腳本啟動快捷鍵${gl_kjlan}2. ${gl_bai}修改登入密碼"
+ echo -e "${gl_kjlan}3. ${gl_bai}使用者密碼登入模式${gl_kjlan}4. ${gl_bai}安裝Python指定版本"
+ echo -e "${gl_kjlan}5. ${gl_bai}開放所有連接埠${gl_kjlan}6. ${gl_bai}修改SSH連接埠"
+ echo -e "${gl_kjlan}7. ${gl_bai}優化DNS位址${gl_kjlan}8. ${gl_bai}一鍵重裝系統${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}9. ${gl_bai}停用ROOT帳戶建立新帳戶${gl_kjlan}10. ${gl_bai}切換優先ipv4/ipv6"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}11. ${gl_bai}查看端口占用狀態${gl_kjlan}12. ${gl_bai}修改虛擬內存大小"
- echo -e "${gl_kjlan}13. ${gl_bai}用戶管理${gl_kjlan}14. ${gl_bai}用戶/密碼生成器"
- echo -e "${gl_kjlan}15. ${gl_bai}系統時區調整${gl_kjlan}16. ${gl_bai}設置BBR3加速"
- echo -e "${gl_kjlan}17. ${gl_bai}防火牆高級管理器${gl_kjlan}18. ${gl_bai}修改主機名"
- echo -e "${gl_kjlan}19. ${gl_bai}切換系統更新源${gl_kjlan}20. ${gl_bai}定時任務管理"
+ echo -e "${gl_kjlan}11. ${gl_bai}查看連接埠佔用狀態${gl_kjlan}12. ${gl_bai}修改虛擬記憶體大小"
+ echo -e "${gl_kjlan}13. ${gl_bai}使用者管理${gl_kjlan}14. ${gl_bai}使用者/密碼產生器"
+ echo -e "${gl_kjlan}15. ${gl_bai}系統時區調整${gl_kjlan}16. ${gl_bai}設定BBR3加速"
+ echo -e "${gl_kjlan}17. ${gl_bai}防火牆高階管理器${gl_kjlan}18. ${gl_bai}修改主機名"
+ echo -e "${gl_kjlan}19. ${gl_bai}切換系統更新來源${gl_kjlan}20. ${gl_bai}定時任務管理"
echo -e "${gl_kjlan}------------------------"
echo -e "${gl_kjlan}21. ${gl_bai}本機host解析${gl_kjlan}22. ${gl_bai}SSH防禦程序"
- echo -e "${gl_kjlan}23. ${gl_bai}限流自動關機${gl_kjlan}24. ${gl_bai}ROOT私鑰登錄模式"
- echo -e "${gl_kjlan}25. ${gl_bai}TG-bot系統監控預警${gl_kjlan}26. ${gl_bai}修復OpenSSH高危漏洞(岫源)"
- echo -e "${gl_kjlan}27. ${gl_bai}紅帽系Linux內核升級${gl_kjlan}28. ${gl_bai}Linux系統內核參數優化${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}23. ${gl_bai}限流自動關機${gl_kjlan}24. ${gl_bai}使用者密鑰登入模式"
+ echo -e "${gl_kjlan}25. ${gl_bai}TG-bot系統監控預警${gl_kjlan}26. ${gl_bai}修復OpenSSH高風險漏洞"
+ echo -e "${gl_kjlan}27. ${gl_bai}紅帽系Linux核心升級${gl_kjlan}28. ${gl_bai}Linux系統核心參數優化${gl_huang}★${gl_bai}"
echo -e "${gl_kjlan}29. ${gl_bai}病毒掃描工具${gl_huang}★${gl_bai} ${gl_kjlan}30. ${gl_bai}文件管理器"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}31. ${gl_bai}切換系統語言${gl_kjlan}32. ${gl_bai}命令行美化工具${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}33. ${gl_bai}設置系統回收站${gl_kjlan}34. ${gl_bai}系統備份與恢復"
- echo -e "${gl_kjlan}35. ${gl_bai}ssh遠程連接工具${gl_kjlan}36. ${gl_bai}硬盤分區管理工具"
- echo -e "${gl_kjlan}37. ${gl_bai}命令行歷史記錄${gl_kjlan}38. ${gl_bai}rsync遠程同步工具"
+ echo -e "${gl_kjlan}31. ${gl_bai}切換系統語言${gl_kjlan}32. ${gl_bai}命令列美化工具${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}33. ${gl_bai}設定係統回收站${gl_kjlan}34. ${gl_bai}系統備份與復原"
+ echo -e "${gl_kjlan}35. ${gl_bai}ssh遠端連線工具${gl_kjlan}36. ${gl_bai}硬碟分割區管理工具"
+ echo -e "${gl_kjlan}37. ${gl_bai}命令列歷史記錄${gl_kjlan}38. ${gl_bai}rsync遠端同步工具"
+ echo -e "${gl_kjlan}39. ${gl_bai}命令收藏夾${gl_huang}★${gl_bai} ${gl_kjlan}40. ${gl_bai}網路卡管理工具"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}41. ${gl_bai}系統日誌管理工具${gl_huang}★${gl_bai} ${gl_kjlan}42. ${gl_bai}系統變數管理工具"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}41. ${gl_bai}留言板${gl_kjlan}66. ${gl_bai}一條龍系統調優${gl_huang}★${gl_bai}"
- echo -e "${gl_kjlan}99. ${gl_bai}重啟服務器${gl_kjlan}100. ${gl_bai}隱私與安全"
- echo -e "${gl_kjlan}101. ${gl_bai}k命令高級用法${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}卸載科技lion腳本"
+ echo -e "${gl_kjlan}61. ${gl_bai}留言板${gl_kjlan}66. ${gl_bai}一條龍系統調優${gl_huang}★${gl_bai}"
+ echo -e "${gl_kjlan}99. ${gl_bai}重啟伺服器${gl_kjlan}100. ${gl_bai}隱私與安全"
+ echo -e "${gl_kjlan}101. ${gl_bai}k指令進階用法${gl_huang}★${gl_bai} ${gl_kjlan}102. ${gl_bai}解除安裝科技lion腳本"
echo -e "${gl_kjlan}------------------------"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
@@ -10867,15 +20180,18 @@ linux_Settings() {
1)
while true; do
clear
- read -e -p "請輸入你的快捷按鍵(輸入0退出):" kuaijiejian
+ read -e -p "請輸入你的快速按鍵(輸入0退出):" kuaijiejian
if [ "$kuaijiejian" == "0" ]; then
break_end
linux_Settings
fi
find /usr/local/bin/ -type l -exec bash -c 'test "$(readlink -f {})" = "/usr/local/bin/k" && rm -f {}' \;
- ln -s /usr/local/bin/k /usr/local/bin/$kuaijiejian
- echo "快捷鍵已設置"
- send_stats "腳本快捷鍵已設置"
+ if [ "$kuaijiejian" != "k" ]; then
+ ln -sf /usr/local/bin/k /usr/local/bin/$kuaijiejian
+ fi
+ ln -sf /usr/local/bin/k /usr/bin/$kuaijiejian > /dev/null 2>&1
+ echo "快速鍵已設定"
+ send_stats "腳本快捷鍵已設定"
break_end
linux_Settings
done
@@ -10883,13 +20199,12 @@ linux_Settings() {
2)
clear
- send_stats "設置你的登錄密碼"
- echo "設置你的登錄密碼"
+ send_stats "設定你的登入密碼"
+ echo "設定你的登入密碼"
passwd
;;
3)
- root_use
- send_stats "root密碼模式"
+ clear
add_sshpasswd
;;
@@ -10897,16 +20212,16 @@ linux_Settings() {
root_use
send_stats "py版本管理"
echo "python版本管理"
- echo "視頻介紹: https://www.bilibili.com/video/BV1Pm42157cK?t=0.1"
+ echo "影片介紹: https://www.bilibili.com/video/BV1Pm42157cK?t=0.1"
echo "---------------------------------------"
- echo "該功能可無縫安裝python官方支持的任何版本!"
+ echo "此功能可無縫安裝python官方支援的任何版本!"
local VERSION=$(python3 -V 2>&1 | awk '{print $2}')
- echo -e "當前python版本號:${gl_huang}$VERSION${gl_bai}"
+ echo -e "目前python版本號:${gl_huang}$VERSION${gl_bai}"
echo "------------"
- echo "推薦版本: 3.12 3.11 3.10 3.9 3.8 2.7"
+ echo "推薦版本: 3.12 3.11 3.10 3.9 3.8 2.7"
echo "查詢更多版本: https://www.python.org/downloads/"
echo "------------"
- read -e -p "輸入你要安裝的python版本號(輸入0退出):" py_new_v
+ read -e -p "輸入你要安裝的python版本號碼(輸入0退出):" py_new_v
if [[ "$py_new_v" == "0" ]]; then
@@ -10943,7 +20258,7 @@ linux_Settings() {
apk update && apk add git
apk add --no-cache bash gcc musl-dev libffi-dev openssl-dev bzip2-dev zlib-dev readline-dev sqlite-dev libc6-compat linux-headers make xz-dev build-base ncurses-dev
else
- echo "未知的包管理器!"
+ echo "未知的套件管理器!"
return
fi
@@ -10972,17 +20287,17 @@ EOF
rm -rf $(pyenv root)/cache/*
local VERSION=$(python -V 2>&1 | awk '{print $2}')
- echo -e "當前python版本號:${gl_huang}$VERSION${gl_bai}"
+ echo -e "目前python版本號:${gl_huang}$VERSION${gl_bai}"
send_stats "腳本PY版本切換"
;;
5)
root_use
- send_stats "開放端口"
+ send_stats "開放埠"
iptables_open
remove iptables-persistent ufw firewalld iptables-services > /dev/null 2>&1
- echo "端口已全部開放"
+ echo "連接埠已全部開放"
;;
6)
@@ -10991,30 +20306,30 @@ EOF
while true; do
clear
- sed -i 's/#Port/Port/' /etc/ssh/sshd_config
+ sed -i 's/^\s*#\?\s*Port/Port/' /etc/ssh/sshd_config
- # 讀取當前的 SSH 端口號
+ # 讀取目前的 SSH 連接埠號
local current_port=$(grep -E '^ *Port [0-9]+' /etc/ssh/sshd_config | awk '{print $2}')
- # 打印當前的 SSH 端口號
- echo -e "當前的 SSH 端口號是:${gl_huang}$current_port ${gl_bai}"
+ # 列印目前的 SSH 連接埠號碼
+ echo -e "目前的 SSH 連接埠號碼是:${gl_huang}$current_port ${gl_bai}"
echo "------------------------"
- echo "端口號範圍1到65535之間的數字。 (輸入0退出)"
+ echo "連接埠號碼範圍1到65535之間的數字。 (輸入0退出)"
- # 提示用戶輸入新的 SSH 端口號
- read -e -p "請輸入新的 SSH 端口號:" new_port
+ # 提示使用者輸入新的 SSH 連接埠號碼
+ read -e -p "請輸入新的 SSH 連接埠號碼:" new_port
- # 判斷端口號是否在有效範圍內
+ # 判斷連接埠號碼是否在有效範圍內
if [[ $new_port =~ ^[0-9]+$ ]]; then # 检查输入是否为数字
if [[ $new_port -ge 1 && $new_port -le 65535 ]]; then
- send_stats "SSH端口已修改"
- new_ssh_port
+ send_stats "SSH連接埠已修改"
+ new_ssh_port $new_port
elif [[ $new_port -eq 0 ]]; then
- send_stats "退出SSH端口修改"
+ send_stats "退出SSH埠修改"
break
else
- echo "端口號無效,請輸入1到65535之間的數字。"
+ echo "連接埠號碼無效,請輸入1到65535之間的數字。"
send_stats "輸入無效SSH端口"
break_end
fi
@@ -11040,53 +20355,51 @@ EOF
9)
root_use
send_stats "新用戶禁用root"
- read -e -p "請輸入新用戶名(輸入0退出):" new_username
+ read -e -p "請輸入新使用者名稱(輸入0退出):" new_username
if [ "$new_username" == "0" ]; then
break_end
linux_Settings
fi
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ create_user_with_sshkey $new_username true
- passwd -l root
+ ssh-keygen -l -f /home/$new_username/.ssh/authorized_keys &>/dev/null && {
+ passwd -l root &>/dev/null
+ sed -i 's/^[[:space:]]*#\?[[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
+ }
- echo "操作已完成。"
;;
10)
root_use
- send_stats "設置v4/v6優先級"
+ send_stats "設定v4/v6優先級"
while true; do
clear
- echo "設置v4/v6優先級"
+ echo "設定v4/v6優先級"
echo "------------------------"
- local ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6)
- if [ "$ipv6_disabled" -eq 1 ]; then
- echo -e "當前網絡優先級設置:${gl_huang}IPv4${gl_bai}優先"
+
+ if grep -Eq '^\s*precedence\s+::ffff:0:0/96\s+100\s*$' /etc/gai.conf 2>/dev/null; then
+ echo -e "目前網路優先權設定:${gl_huang}IPv4${gl_bai}優先"
else
- echo -e "當前網絡優先級設置:${gl_huang}IPv6${gl_bai}優先"
+ echo -e "目前網路優先權設定:${gl_huang}IPv6${gl_bai}優先"
fi
+
echo ""
echo "------------------------"
- echo "1. IPv4 優先 2. IPv6 優先 3. IPv6 修復工具"
+ echo "1. IPv4 優先 2. IPv6 優先權 3. IPv6 修復工具"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
- read -e -p "選擇優先的網絡:" choice
+ read -e -p "選擇優先的網路:" choice
case $choice in
1)
- sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
- echo "已切換為 IPv4 優先"
- send_stats "已切換為 IPv4 優先"
+ prefer_ipv4
;;
2)
- sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
+ rm -f /etc/gai.conf
echo "已切換為 IPv6 優先"
send_stats "已切換為 IPv6 優先"
;;
@@ -11094,7 +20407,7 @@ EOF
3)
clear
bash <(curl -L -s jhb.ovh/jb/v6.sh)
- echo "該功能由jhb大神提供,感謝他!"
+ echo "此功能由jhb大神提供,感謝他!"
send_stats "ipv6修復"
;;
@@ -11113,17 +20426,17 @@ EOF
12)
root_use
- send_stats "設置虛擬內存"
+ send_stats "設定虛擬記憶體"
while true; do
clear
- echo "設置虛擬內存"
+ echo "設定虛擬記憶體"
local swap_used=$(free -m | awk 'NR==3{print $3}')
local swap_total=$(free -m | awk 'NR==3{print $2}')
local swap_info=$(free -m | awk 'NR==3{used=$3; total=$2; if (total == 0) {percentage=0} else {percentage=used*100/total}; printf "%dM/%dM (%d%%)", used, total, percentage}')
- echo -e "當前虛擬內存:${gl_huang}$swap_info${gl_bai}"
+ echo -e "當前虛擬記憶體:${gl_huang}$swap_info${gl_bai}"
echo "------------------------"
- echo "1. 分配1024M 2. 分配2048M 3. 分配4096M 4. 自定義大小"
+ echo "1. 分配1024M 2. 分配2048M 3. 分配4096M 4. 自訂大小"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -11131,25 +20444,25 @@ EOF
case "$choice" in
1)
- send_stats "已設置1G虛擬內存"
+ send_stats "已設定1G虛擬內存"
add_swap 1024
;;
2)
- send_stats "已設置2G虛擬內存"
+ send_stats "已設定2G虛擬內存"
add_swap 2048
;;
3)
- send_stats "已設置4G虛擬內存"
+ send_stats "已設定4G虛擬內存"
add_swap 4096
;;
4)
- read -e -p "請輸入虛擬內存大小(單位M):" new_swap
+ read -e -p "請輸入虛擬記憶體大小(單位M):" new_swap
add_swap "$new_swap"
- send_stats "已設置自定義虛擬內存"
+ send_stats "已設定自訂虛擬內存"
;;
*)
@@ -11162,25 +20475,30 @@ EOF
13)
while true; do
root_use
- send_stats "用戶管理"
- echo "用戶列表"
+ send_stats "使用者管理"
+ echo "使用者列表"
echo "----------------------------------------------------------------------------"
- printf "%-24s %-34s %-20s %-10s\n" "用户名" "用户权限" "用户组" "sudo权限"
+ printf "%-24s %-34s %-20s %-10s\n" "使用者名稱" "使用者權限" "使用者群組" "sudo權限"
while IFS=: read -r username _ userid groupid _ _ homedir shell; do
local groups=$(groups "$username" | cut -d : -f 2)
- local sudo_status=$(sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL : ALL)' && echo "Yes" || echo "No")
+ local sudo_status
+ if sudo -n -lU "$username" 2>/dev/null | grep -q "(ALL) \(NOPASSWD: \)\?ALL"; then
+ sudo_status="Yes"
+ else
+ sudo_status="No"
+ fi
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$groups" "$sudo_status"
done < /etc/passwd
echo ""
- echo "賬戶操作"
+ echo "帳戶操作"
echo "------------------------"
- echo "1. 創建普通賬戶 2. 創建高級賬戶"
+ echo "1. 建立普通用戶 2. 建立進階用戶"
echo "------------------------"
- echo "3. 賦予最高權限 4. 取消最高權限"
+ echo "3. 賦予最高權限 4. 取消最高權限"
echo "------------------------"
- echo "5. 刪除賬號"
+ echo "5. 刪除帳號"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -11188,44 +20506,36 @@ EOF
case $sub_choice in
1)
- # 提示用戶輸入新用戶名
+ # 提示使用者輸入新使用者名稱
read -e -p "請輸入新用戶名:" new_username
+ create_user_with_sshkey $new_username false
- # 創建新用戶並設置密碼
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- echo "操作已完成。"
;;
2)
- # 提示用戶輸入新用戶名
+ # 提示使用者輸入新使用者名稱
read -e -p "請輸入新用戶名:" new_username
-
- # 創建新用戶並設置密碼
- useradd -m -s /bin/bash "$new_username"
- passwd "$new_username"
-
- # 賦予新用戶sudo權限
- echo "$new_username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
-
- echo "操作已完成。"
+ create_user_with_sshkey $new_username true
;;
3)
- read -e -p "請輸入用戶名:" username
- # 賦予新用戶sudo權限
- echo "$username ALL=(ALL:ALL) ALL" | tee -a /etc/sudoers
+ read -e -p "請輸入使用者名稱:" username
+ install sudo
+ cat >"/etc/sudoers.d/$username" </dev/null | xargs rm -f
+ fi
+ sed -i "/^$username\s*ALL=(ALL)/d" /etc/sudoers
;;
5)
- read -e -p "請輸入要刪除的用戶名:" username
- # 刪除用戶及其主目錄
+ read -e -p "請輸入要刪除的使用者名稱:" username
userdel -r "$username"
;;
@@ -11233,17 +20543,18 @@ EOF
break # 跳出循环,退出菜单
;;
esac
+
done
;;
14)
clear
- send_stats "用戶信息生成器"
- echo "隨機用戶名"
+ send_stats "使用者資訊產生器"
+ echo "隨機使用者名稱"
echo "------------------------"
for i in {1..5}; do
username="user$(< /dev/urandom tr -dc _a-z0-9 | head -c6)"
- echo "隨機用戶名$i: $username"
+ echo "隨機使用者名稱$i: $username"
done
echo ""
@@ -11252,7 +20563,7 @@ EOF
local first_names=("John" "Jane" "Michael" "Emily" "David" "Sophia" "William" "Olivia" "James" "Emma" "Ava" "Liam" "Mia" "Noah" "Isabella")
local last_names=("Smith" "Johnson" "Brown" "Davis" "Wilson" "Miller" "Jones" "Garcia" "Martinez" "Williams" "Lee" "Gonzalez" "Rodriguez" "Hernandez")
- # 生成5個隨機用戶姓名
+ # 產生5個隨機用戶姓名
for i in {1..5}; do
local first_name_index=$((RANDOM % ${#first_names[@]}))
local last_name_index=$((RANDOM % ${#last_names[@]}))
@@ -11292,37 +20603,37 @@ EOF
send_stats "換時區"
while true; do
clear
- echo "系統時間信息"
+ echo "系統時間資訊"
- # 獲取當前系統時區
+ # 取得目前系統時區
local timezone=$(current_timezone)
- # 獲取當前系統時間
+ # 取得目前系統時間
local current_time=$(date +"%Y-%m-%d %H:%M:%S")
# 顯示時區和時間
- echo "當前系統時區:$timezone"
- echo "當前系統時間:$current_time"
+ echo "目前系統時區:$timezone"
+ echo "目前系統時間:$current_time"
echo ""
echo "時區切換"
echo "------------------------"
echo "亞洲"
- echo "1. 中國上海時間 2. 中國香港時間"
- echo "3. 日本東京時間 4. 韓國首爾時間"
- echo "5. 新加坡時間 6. 印度加爾各答時間"
- echo "7. 阿聯酋迪拜時間 8. 澳大利亞悉尼時間"
- echo "9. 泰國曼谷時間"
+ echo "1. 中國上海時間 2. 中國香港時間"
+ echo "3. 日本東京時間 4. 韓國首爾時間"
+ echo "5. 新加坡時間 6. 印度加爾各答時間"
+ echo "7. 阿聯酋杜拜時間 8. 澳洲雪梨時間"
+ echo "9. 泰國曼谷時間"
echo "------------------------"
echo "歐洲"
- echo "11. 英國倫敦時間 12. 法國巴黎時間"
- echo "13. 德國柏林時間 14. 俄羅斯莫斯科時間"
- echo "15. 荷蘭尤特賴赫特時間 16. 西班牙馬德里時間"
+ echo "11. 英國倫敦時間 12. 法國巴黎時間"
+ echo "13. 德國柏林時間 14. 俄羅斯莫斯科時間"
+ echo "15. 荷蘭尤特賴赫特時間 16. 西班牙馬德里時間"
echo "------------------------"
echo "美洲"
- echo "21. 美國西部時間 22. 美國東部時間"
- echo "23. 加拿大時間 24. 墨西哥時間"
- echo "25. 巴西時間 26. 阿根廷時間"
+ echo "21. 美國西部時間 22. 美國東部時間"
+ echo "23. 加拿大時間 24. 墨西哥時間"
+ echo "25. 巴西時間 26. 阿根廷時間"
echo "------------------------"
echo "31. UTC全球標準時間"
echo "------------------------"
@@ -11376,9 +20687,9 @@ EOF
while true; do
clear
local current_hostname=$(uname -n)
- echo -e "當前主機名:${gl_huang}$current_hostname${gl_bai}"
+ echo -e "目前主機名稱:${gl_huang}$current_hostname${gl_bai}"
echo "------------------------"
- read -e -p "請輸入新的主機名(輸入0退出):" new_hostname
+ read -e -p "請輸入新的主機名稱(輸入0退出):" new_hostname
if [ -n "$new_hostname" ] && [ "$new_hostname" != "0" ]; then
if [ -f /etc/alpine-release ]; then
# Alpine
@@ -11403,11 +20714,11 @@ EOF
echo "::1 $new_hostname localhost localhost.localdomain ipv6-localhost ipv6-loopback" >> /etc/hosts
fi
- echo "主機名已更改為:$new_hostname"
- send_stats "主機名已更改"
+ echo "主機名稱已更改為:$new_hostname"
+ send_stats "主機名稱已更改"
sleep 1
else
- echo "已退出,未更改主機名。"
+ echo "已退出,未更改主機名稱。"
break
fi
done
@@ -11415,12 +20726,12 @@ EOF
19)
root_use
- send_stats "換系統更新源"
+ send_stats "換系統更新來源"
clear
- echo "選擇更新源區域"
- echo "接入LinuxMirrors切換系統更新源"
+ echo "選擇更新來源區域"
+ echo "接入LinuxMirrors切換系統更新來源"
echo "------------------------"
- echo "1. 中國大陸【默認】 2. 中國大陸【教育網】 3. 海外地區"
+ echo "1. 中國大陸【預設】 2. 中國大陸【教育網】 3. 海外地區 4. 智慧切換更新源"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -11428,7 +20739,7 @@ EOF
case $choice in
1)
- send_stats "中國大陸默認源"
+ send_stats "中國大陸預設來源"
bash <(curl -sSL https://linuxmirrors.cn/main.sh)
;;
2)
@@ -11439,6 +20750,11 @@ EOF
send_stats "海外源"
bash <(curl -sSL https://linuxmirrors.cn/main.sh) --abroad
;;
+ 4)
+ send_stats "智慧切換更新來源"
+ switch_mirror false false
+ ;;
+
*)
echo "已取消"
;;
@@ -11458,7 +20774,7 @@ EOF
echo ""
echo "操作"
echo "------------------------"
- echo "1. 添加定時任務 2. 刪除定時任務 3. 編輯定時任務"
+ echo "1. 新增定時任務 2. 刪除定時任務 3. 編輯定時任務"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -11466,10 +20782,10 @@ EOF
case $sub_choice in
1)
- read -e -p "請輸入新任務的執行命令:" newquest
+ read -e -p "請輸入新任務的執行指令:" newquest
echo "------------------------"
- echo "1. 每月任務 2. 每週任務"
- echo "3. 每天任務 4. 每小時任務"
+ echo "1. 每月任務 2. 每週任務"
+ echo "3. 每天任務 4. 每小時任務"
echo "------------------------"
read -e -p "請輸入你的選擇:" dingshi
@@ -11494,7 +20810,7 @@ EOF
break # 跳出
;;
esac
- send_stats "添加定時任務"
+ send_stats "新增定時任務"
;;
2)
read -e -p "請輸入需要刪除任務的關鍵字:" kquest
@@ -11524,7 +20840,7 @@ EOF
echo ""
echo "操作"
echo "------------------------"
- echo "1. 添加新的解析 2. 刪除解析地址"
+ echo "1. 新增新的解析 2. 刪除解析位址"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -11534,13 +20850,13 @@ EOF
1)
read -e -p "請輸入新的解析記錄 格式: 110.25.5.33 kejilion.pro :" addhost
echo "$addhost" >> /etc/hosts
- send_stats "本地host解析新增"
+ send_stats "本機host解析新增"
;;
2)
read -e -p "請輸入需要刪除的解析內容關鍵字:" delhost
sed -i "/$delhost/d" /etc/hosts
- send_stats "本地host解析刪除"
+ send_stats "本機host解析刪除"
;;
*)
break # 跳出循环,退出菜单
@@ -11550,63 +20866,7 @@ EOF
;;
22)
- root_use
- send_stats "ssh防禦"
- while true; do
- if [ -x "$(command -v fail2ban-client)" ] ; then
- clear
- remove fail2ban
- rm -rf /etc/fail2ban
- else
- clear
- rm -f /path/to/fail2ban/config/fail2ban/jail.d/sshd.conf > /dev/null 2>&1
- docker exec -it fail2ban fail2ban-client reload > /dev/null 2>&1
- docker_name="fail2ban"
- check_docker_app
- echo -e "SSH防禦程序$check_docker"
- echo "fail2ban是一個SSH防止暴力破解工具"
- echo "官網介紹:${gh_proxy}github.com/fail2ban/fail2ban"
- echo "------------------------"
- echo "1. 安裝防禦程序"
- echo "------------------------"
- echo "2. 查看SSH攔截記錄"
- echo "3. 日誌實時監控"
- echo "------------------------"
- echo "9. 卸載防禦程序"
- echo "------------------------"
- echo "0. 返回上一級選單"
- echo "------------------------"
- read -e -p "請輸入你的選擇:" sub_choice
- case $sub_choice in
- 1)
- install_docker
- f2b_install_sshd
-
- cd ~
- f2b_status
- break_end
- ;;
- 2)
- echo "------------------------"
- f2b_sshd
- echo "------------------------"
- break_end
- ;;
- 3)
- tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
- break
- ;;
- 9)
- docker rm -f fail2ban
- rm -rf /path/to/fail2ban
- echo "Fail2Ban防禦程序已卸載"
- ;;
- *)
- break
- ;;
- esac
- fi
- done
+ fail2ban_panel
;;
@@ -11616,29 +20876,29 @@ EOF
while true; do
clear
echo "限流關機功能"
- echo "視頻介紹: https://www.bilibili.com/video/BV1mC411j7Qd?t=0.1"
+ echo "影片介紹: https://www.bilibili.com/video/BV1mC411j7Qd?t=0.1"
echo "------------------------------------------------"
- echo "當前流量使用情況,重啟服務器流量計算會清零!"
+ echo "目前流量使用情況,重啟伺服器流量計算會清除!"
output_status
echo -e "${gl_kjlan}總接收:${gl_bai}$rx"
echo -e "${gl_kjlan}總發送:${gl_bai}$tx"
# 檢查是否存在 Limiting_Shut_down.sh 文件
if [ -f ~/Limiting_Shut_down.sh ]; then
- # 獲取 threshold_gb 的值
+ # 取得 threshold_gb 的值
local rx_threshold_gb=$(grep -oP 'rx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
local tx_threshold_gb=$(grep -oP 'tx_threshold_gb=\K\d+' ~/Limiting_Shut_down.sh)
- echo -e "${gl_lv}當前設置的進站限流閾值為:${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
- echo -e "${gl_lv}當前設置的出站限流閾值為:${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
+ echo -e "${gl_lv}目前設定的進站限流閾值為:${gl_huang}${rx_threshold_gb}${gl_lv}G${gl_bai}"
+ echo -e "${gl_lv}目前設定的出站限流閾值為:${gl_huang}${tx_threshold_gb}${gl_lv}GB${gl_bai}"
else
- echo -e "${gl_hui}當前未啟用限流關機功能${gl_bai}"
+ echo -e "${gl_hui}目前未啟用限流關機功能${gl_bai}"
fi
echo
echo "------------------------------------------------"
- echo "系統每分鐘會檢測實際流量是否到達閾值,到達後會自動關閉服務器!"
+ echo "系統每分鐘會偵測實際流量是否到達閾值,到達後會自動關閉伺服器!"
echo "------------------------"
- echo "1. 開啟限流關機功能 2. 停用限流關機功能"
+ echo "1. 開啟限流關機功能 2. 停用限流關機功能"
echo "------------------------"
echo "0. 返回上一級選單"
echo "------------------------"
@@ -11646,13 +20906,13 @@ EOF
case "$Limiting" in
1)
- # 輸入新的虛擬內存大小
- echo "如果實際服務器就100G流量,可設置閾值為95G,提前關機,以免出現流量誤差或溢出。"
- read -e -p "請輸入進站流量閾值(單位為G,默認100G):" rx_threshold_gb
+ # 輸入新的虛擬記憶體大小
+ echo "若實際伺服器就100G流量,可設定閾值為95G,提前關機,以免出現流量誤差或溢位。"
+ read -e -p "請輸入進站流量閾值(單位為G,預設100G):" rx_threshold_gb
rx_threshold_gb=${rx_threshold_gb:-100}
- read -e -p "請輸入出站流量閾值(單位為G,默認100G):" tx_threshold_gb
+ read -e -p "請輸入出站流量閾值(單位為G,預設100G):" tx_threshold_gb
tx_threshold_gb=${tx_threshold_gb:-100}
- read -e -p "請輸入流量重置日期(默認每月1日重置):" cz_day
+ read -e -p "請輸入流量重置日期(預設每月1日重設):" cz_day
cz_day=${cz_day:-1}
cd ~
@@ -11665,8 +20925,8 @@ EOF
(crontab -l ; echo "* * * * * ~/Limiting_Shut_down.sh") | crontab - > /dev/null 2>&1
crontab -l | grep -v 'reboot' | crontab -
(crontab -l ; echo "0 1 $cz_day * * reboot") | crontab - > /dev/null 2>&1
- echo "限流關機已設置"
- send_stats "限流關機已設置"
+ echo "限流關機已設定"
+ send_stats "限流關機已設定"
;;
2)
check_crontab_installed
@@ -11684,64 +20944,18 @@ EOF
24)
-
- root_use
- send_stats "私鑰登錄"
- while true; do
- clear
- echo "ROOT私鑰登錄模式"
- echo "視頻介紹: https://www.bilibili.com/video/BV1Q4421X78n?t=209.4"
- echo "------------------------------------------------"
- echo "將會生成密鑰對,更安全的方式SSH登錄"
- echo "------------------------"
- echo "1. 生成新密鑰 2. 導入已有密鑰 3. 查看本機密鑰"
- echo "------------------------"
- echo "0. 返回上一級選單"
- echo "------------------------"
- read -e -p "請輸入你的選擇:" host_dns
-
- case $host_dns in
- 1)
- send_stats "生成新密鑰"
- add_sshkey
- break_end
-
- ;;
- 2)
- send_stats "導入已有公鑰"
- import_sshkey
- break_end
-
- ;;
- 3)
- send_stats "查看本機密鑰"
- echo "------------------------"
- echo "公鑰信息"
- cat ~/.ssh/authorized_keys
- echo "------------------------"
- echo "私鑰信息"
- cat ~/.ssh/sshkey
- echo "------------------------"
- break_end
-
- ;;
- *)
- break # 跳出循环,退出菜单
- ;;
- esac
- done
-
+ sshkey_panel
;;
25)
root_use
send_stats "電報預警"
echo "TG-bot監控預警功能"
- echo "視頻介紹: https://youtu.be/vLL-eb3Z_TY"
+ echo "影片介紹: https://youtu.be/vLL-eb3Z_TY"
echo "------------------------------------------------"
- echo "您需要配置tg機器人API和接收預警的用戶ID,即可實現本機CPU,內存,硬盤,流量,SSH登錄的實時監控預警"
- echo "到達閾值後會向用戶發預警消息"
- echo -e "${gl_hui}-關於流量,重啟服務器將重新計算-${gl_bai}"
+ echo "您需要設定tg機器人API和接收預警的使用者ID,即可實現本機CPU,內存,硬碟,流量,SSH登入的即時監控預警"
+ echo "到達閾值後會向用戶發送預警訊息"
+ echo -e "${gl_hui}-關於流量,重啟伺服器將重新計算-${gl_bai}"
read -e -p "確定繼續嗎? (Y/N):" choice
case "$choice" in
@@ -11768,7 +20982,7 @@ EOF
sed -i "4i$(grep '^CHAT_ID=' ~/TG-check-notify.sh)" TG-SSH-check-notify.sh
chmod +x ~/TG-SSH-check-notify.sh
- # 添加到 ~/.profile 文件中
+ # 加入 ~/.profile 檔案中
if ! grep -q 'bash ~/TG-SSH-check-notify.sh' ~/.profile > /dev/null 2>&1; then
echo 'bash ~/TG-SSH-check-notify.sh' >> ~/.profile
if command -v dnf &>/dev/null || command -v yum &>/dev/null; then
@@ -11780,7 +20994,7 @@ EOF
clear
echo "TG-bot預警系統已啟動"
- echo -e "${gl_hui}你還可以將root目錄中的TG-check-notify.sh預警文件放到其他機器上直接使用!${gl_bai}"
+ echo -e "${gl_hui}你也可以將root目錄中的TG-check-notify.sh預警檔案放到其他機器上直接使用!${gl_bai}"
;;
[Nn])
echo "已取消"
@@ -11793,7 +21007,7 @@ EOF
26)
root_use
- send_stats "修復SSH高危漏洞"
+ send_stats "修復SSH高風險漏洞"
cd ~
curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/upgrade_openssh9.8p1.sh
chmod +x ~/upgrade_openssh9.8p1.sh
@@ -11837,7 +21051,7 @@ EOF
;;
37)
clear
- send_stats "命令行歷史記錄"
+ send_stats "命令列歷史記錄"
get_history_file() {
for file in "$HOME"/.bash_history "$HOME"/.ash_history "$HOME"/.zsh_history "$HOME"/.local/share/fish/fish_history; do
[ -f "$file" ] && { echo "$file"; return; }
@@ -11853,11 +21067,33 @@ EOF
;;
+ 39)
+ clear
+ linux_fav
+ ;;
+
+ 40)
+ clear
+ net_menu
+ ;;
+
41)
+ clear
+ log_menu
+ ;;
+
+ 42)
+ clear
+ env_menu
+ ;;
+
+
+ 61)
clear
send_stats "留言板"
- echo "科技lion留言板已遷移至官方社區!請在官方社區進行留言噢!"
- echo "https://bbs.kejilion.pro/"
+ echo "造訪科技lion官方留言板,您對腳本有任何想法歡迎留言交流!"
+ echo "https://board.kejilion.pro"
+ echo "公共密碼: kejilion.sh"
;;
66)
@@ -11867,16 +21103,18 @@ EOF
echo "一條龍系統調優"
echo "------------------------------------------------"
echo "將對以下內容進行操作與優化"
- echo "1. 更新系統到最新"
+ echo "1. 最佳化系統更新來源,更新系統到最新"
echo "2. 清理系統垃圾文件"
- echo -e "3. 設置虛擬內存${gl_huang}1G${gl_bai}"
- echo -e "4. 設置SSH端口號為${gl_huang}5522${gl_bai}"
- echo -e "5. 開放所有端口"
- echo -e "6. 開啟${gl_huang}BBR${gl_bai}加速"
- echo -e "7. 設置時區到${gl_huang}上海${gl_bai}"
- echo -e "8. 自動優化DNS地址${gl_huang}海外: 1.1.1.1 8.8.8.8 國內: 223.5.5.5${gl_bai}"
- echo -e "9. 安裝基礎工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
- echo -e "10. Linux系統內核參數優化切換到${gl_huang}均衡優化模式${gl_bai}"
+ echo -e "3. 設定虛擬內存${gl_huang}1G${gl_bai}"
+ echo -e "4. 設定SSH埠號為${gl_huang}5522${gl_bai}"
+ echo -e "5. 啟動fail2ban防禦SSH暴力破解"
+ echo -e "6. 開放所有端口"
+ echo -e "7. 開啟${gl_huang}BBR${gl_bai}加速"
+ echo -e "8. 設定時區到${gl_huang}上海${gl_bai}"
+ echo -e "9. 自動優化DNS位址${gl_huang}海外: 1.1.1.1 8.8.8.8 國內: 223.5.5.5${gl_bai}"
+ echo -e "10. 设置网络为${gl_huang}ipv4優先${gl_bai}"
+ echo -e "11. 安裝基礎工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "12. Linux系統核心參數優化${gl_huang}自動根據網路環境調優${gl_bai}"
echo "------------------------------------------------"
read -e -p "確定一鍵保養嗎? (Y/N):" choice
@@ -11885,58 +21123,53 @@ EOF
clear
send_stats "一條龍調優啟動"
echo "------------------------------------------------"
+ switch_mirror false false
linux_update
- echo -e "[${gl_lv}OK${gl_bai}] 1/10. 更新系統到最新"
+ echo -e "[${gl_lv}OK${gl_bai}] 1/12. 更新系統到最新"
echo "------------------------------------------------"
linux_clean
- echo -e "[${gl_lv}OK${gl_bai}] 2/10. 清理系統垃圾文件"
+ echo -e "[${gl_lv}OK${gl_bai}] 2/12. 清理系統垃圾文件"
echo "------------------------------------------------"
add_swap 1024
- echo -e "[${gl_lv}OK${gl_bai}] 3/10. 設置虛擬內存${gl_huang}1G${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 3/12. 設定虛擬內存${gl_huang}1G${gl_bai}"
echo "------------------------------------------------"
- local new_port=5522
- new_ssh_port
- echo -e "[${gl_lv}OK${gl_bai}] 4/10. 設置SSH端口號為${gl_huang}5522${gl_bai}"
+ new_ssh_port 5522
+ echo -e "[${gl_lv}OK${gl_bai}] 4/12. 設定SSH埠號為${gl_huang}5522${gl_bai}"
+ echo "------------------------------------------------"
+ f2b_install_sshd
+ cd ~
+ f2b_status
+ echo -e "[${gl_lv}OK${gl_bai}] 5/12. 啟動fail2ban防禦SSH暴力破解"
+
echo "------------------------------------------------"
- echo -e "[${gl_lv}OK${gl_bai}] 5/10. 開放所有端口"
+ echo -e "[${gl_lv}OK${gl_bai}] 6/12. 開放所有端口"
echo "------------------------------------------------"
bbr_on
- echo -e "[${gl_lv}OK${gl_bai}] 6/10. 開啟${gl_huang}BBR${gl_bai}加速"
+ echo -e "[${gl_lv}OK${gl_bai}] 7/12. 開啟${gl_huang}BBR${gl_bai}加速"
echo "------------------------------------------------"
set_timedate Asia/Shanghai
- echo -e "[${gl_lv}OK${gl_bai}] 7/10. 設置時區到${gl_huang}上海${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 8/12. 設定時區到${gl_huang}上海${gl_bai}"
echo "------------------------------------------------"
- local country=$(curl -s ipinfo.io/country)
- if [ "$country" = "CN" ]; then
- local dns1_ipv4="223.5.5.5"
- local dns2_ipv4="183.60.83.19"
- local dns1_ipv6="2400:3200::1"
- local dns2_ipv6="2400:da00::6666"
- else
- local dns1_ipv4="1.1.1.1"
- local dns2_ipv4="8.8.8.8"
- local dns1_ipv6="2606:4700:4700::1111"
- local dns2_ipv6="2001:4860:4860::8888"
- fi
-
- set_dns
- echo -e "[${gl_lv}OK${gl_bai}] 8/10. 自動優化DNS地址${gl_huang}${gl_bai}"
+ auto_optimize_dns
+ echo -e "[${gl_lv}OK${gl_bai}] 9/12. 自動最佳化DNS位址${gl_huang}${gl_bai}"
+ echo "------------------------------------------------"
+ prefer_ipv4
+ echo -e "[${gl_lv}OK${gl_bai}] 10/12. 設定網路為${gl_huang}ipv4優先${gl_bai}}"
echo "------------------------------------------------"
install_docker
install wget sudo tar unzip socat btop nano vim
- echo -e "[${gl_lv}OK${gl_bai}] 9/10. 安裝基礎工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
+ echo -e "[${gl_lv}OK${gl_bai}] 11/12. 安裝基礎工具${gl_huang}docker wget sudo tar unzip socat btop nano vim${gl_bai}"
echo "------------------------------------------------"
- echo "------------------------------------------------"
- optimize_balanced
- echo -e "[${gl_lv}OK${gl_bai}] 10/10. Linux系統內核參數優化"
+ curl -sS ${gh_proxy}raw.githubusercontent.com/kejilion/sh/refs/heads/main/network-optimize.sh | bash
+ echo -e "[${gl_lv}OK${gl_bai}] 12/12. Linux系統核心參數最佳化"
echo -e "${gl_lv}一條龍系統調優已完成${gl_bai}"
;;
@@ -11961,18 +21194,18 @@ EOF
while true; do
clear
if grep -q '^ENABLE_STATS="true"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_lv}正在采集数据${gl_bai}"
+ local status_message="${gl_lv}正在採集數據${gl_bai}"
elif grep -q '^ENABLE_STATS="false"' /usr/local/bin/k > /dev/null 2>&1; then
- local status_message="${gl_hui}采集已关闭${gl_bai}"
+ local status_message="${gl_hui}採集已關閉${gl_bai}"
else
- local status_message="无法确定的状态"
+ local status_message="無法確定的狀態"
fi
echo "隱私與安全"
- echo "腳本將收集用戶使用功能的數據,優化腳本體驗,製作更多好玩好用的功能"
+ echo "腳本將收集使用者使用功能的數據,優化腳本體驗,製作更多好玩好用的功能"
echo "將收集腳本版本號,使用的時間,系統版本,CPU架構,機器所屬國家和使用的功能的名稱,"
echo "------------------------------------------------"
- echo -e "當前狀態:$status_message"
+ echo -e "目前狀態:$status_message"
echo "--------------------"
echo "1. 開啟採集"
echo "2. 關閉採集"
@@ -11985,8 +21218,8 @@ EOF
cd ~
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' /usr/local/bin/k
sed -i 's/^ENABLE_STATS="false"/ENABLE_STATS="true"/' ~/kejilion.sh
- echo "已開啟採集"
- send_stats "隱私與安全已開啟採集"
+ echo "已開啟擷取"
+ send_stats "隱私與安全已開啟擷取"
;;
2)
cd ~
@@ -12009,8 +21242,8 @@ EOF
102)
clear
- send_stats "卸載科技lion腳本"
- echo "卸載科技lion腳本"
+ send_stats "解除安裝科技lion腳本"
+ echo "解除安裝科技lion腳本"
echo "------------------------------------------------"
echo "將徹底卸載kejilion腳本,不影響你其他功能"
read -e -p "確定繼續嗎? (Y/N):" choice
@@ -12063,21 +21296,21 @@ linux_file() {
clear
echo "文件管理器"
echo "------------------------"
- echo "當前路徑"
+ echo "目前路徑"
pwd
echo "------------------------"
ls --color=auto -x
echo "------------------------"
- echo "1. 進入目錄 2. 創建目錄 3. 修改目錄權限 4. 重命名目錄"
- echo "5. 刪除目錄 6. 返回上一級選單目錄"
+ echo "1. 進入目錄 2. 建立目錄 3. 修改目錄權限 4. 重新命名目錄"
+ echo "5. 刪除目錄 6. 返回上一層選單目錄"
echo "------------------------"
- echo "11. 創建文件 12. 編輯文件 13. 修改文件權限 14. 重命名文件"
+ echo "11. 建立檔案 12. 編輯檔案 13. 修改檔案權限 14. 重新命名文件"
echo "15. 刪除文件"
echo "------------------------"
- echo "21. 壓縮文件目錄 22. 解壓文件目錄 23. 移動文件目錄 24. 複製文件目錄"
- echo "25. 傳文件至其他服務器"
+ echo "21. 壓縮檔案目錄 22. 解壓縮檔案目錄 23. 行動檔案目錄 24. 複製檔案目錄"
+ echo "25. 傳文件至其他伺服器"
echo "------------------------"
- echo "0. 返回上一級選單"
+ echo "0. 返回上一級選單"
echo "------------------------"
read -e -p "請輸入你的選擇:" Limiting
@@ -12088,9 +21321,9 @@ linux_file() {
send_stats "進入目錄"
;;
2) # 创建目录
- read -e -p "請輸入要創建的目錄名:" dirname
- mkdir -p "$dirname" && echo "目錄已創建" || echo "創建失敗"
- send_stats "創建目錄"
+ read -e -p "請輸入要建立的目錄名稱:" dirname
+ mkdir -p "$dirname" && echo "目錄已建立" || echo "創建失敗"
+ send_stats "建立目錄"
;;
3) # 修改目录权限
read -e -p "請輸入目錄名:" dirname
@@ -12099,130 +21332,117 @@ linux_file() {
send_stats "修改目錄權限"
;;
4) # 重命名目录
- read -e -p "請輸入當前目錄名:" current_name
- read -e -p "請輸入新目錄名:" new_name
- mv "$current_name" "$new_name" && echo "目錄已重命名" || echo "重命名失敗"
- send_stats "重命名目錄"
+ read -e -p "請輸入目前目錄名稱:" current_name
+ read -e -p "請輸入新目錄名稱:" new_name
+ mv "$current_name" "$new_name" && echo "目錄已重新命名" || echo "重新命名失敗"
+ send_stats "重命名目录"
;;
5) # 删除目录
- read -e -p "請輸入要刪除的目錄名:" dirname
+ read -e -p "請輸入要刪除的目錄名稱:" dirname
rm -rf "$dirname" && echo "目錄已刪除" || echo "刪除失敗"
send_stats "刪除目錄"
;;
6) # 返回上一级选单目录
cd ..
- send_stats "返回上一級選單目錄"
+ send_stats "返回上一層選單目錄"
;;
11) # 创建文件
- read -e -p "請輸入要創建的文件名:" filename
- touch "$filename" && echo "文件已創建" || echo "創建失敗"
- send_stats "創建文件"
+ read -e -p "請輸入要建立的檔案名稱:" filename
+ touch "$filename" && echo "文件已建立" || echo "創建失敗"
+ send_stats "建立文件"
;;
12) # 编辑文件
- read -e -p "請輸入要編輯的文件名:" filename
+ read -e -p "請輸入要編輯的檔名:" filename
install nano
nano "$filename"
send_stats "編輯文件"
;;
13) # 修改文件权限
- read -e -p "請輸入文件名:" filename
+ read -e -p "請輸入檔名:" filename
read -e -p "請輸入權限 (如 755):" perm
chmod "$perm" "$filename" && echo "權限已修改" || echo "修改失敗"
- send_stats "修改文件權限"
+ send_stats "修改檔案權限"
;;
14) # 重命名文件
- read -e -p "請輸入當前文件名:" current_name
- read -e -p "請輸入新文件名:" new_name
- mv "$current_name" "$new_name" && echo "文件已重命名" || echo "重命名失敗"
- send_stats "重命名文件"
+ read -e -p "請輸入目前檔名:" current_name
+ read -e -p "請輸入新檔名:" new_name
+ mv "$current_name" "$new_name" && echo "文件已重新命名" || echo "重新命名失敗"
+ send_stats "重新命名文件"
;;
15) # 删除文件
- read -e -p "請輸入要刪除的文件名:" filename
+ read -e -p "請輸入要刪除的檔名:" filename
rm -f "$filename" && echo "文件已刪除" || echo "刪除失敗"
send_stats "刪除文件"
;;
21) # 压缩文件/目录
- read -e -p "請輸入要壓縮的文件/目錄名:" name
+ read -e -p "請輸入要壓縮的檔案/目錄名稱:" name
install tar
tar -czvf "$name.tar.gz" "$name" && echo "已壓縮為$name.tar.gz" || echo "壓縮失敗"
- send_stats "壓縮文件/目錄"
+ send_stats "壓縮檔案/目錄"
;;
22) # 解压文件/目录
- read -e -p "請輸入要解壓的文件名 (.tar.gz):" filename
+ read -e -p "請輸入要解壓縮的檔案名稱 (.tar.gz):" filename
install tar
- tar -xzvf "$filename" && echo "已解壓$filename" || echo "解壓失敗"
- send_stats "解壓文件/目錄"
+ tar -xzvf "$filename" && echo "已解壓縮$filename" || echo "解壓縮失敗"
+ send_stats "解壓縮檔案/目錄"
;;
23) # 移动文件或目录
- read -e -p "請輸入要移動的文件或目錄路徑:" src_path
+ read -e -p "請輸入要移動的檔案或目錄路徑:" src_path
if [ ! -e "$src_path" ]; then
- echo "錯誤: 文件或目錄不存在。"
- send_stats "移動文件或目錄失敗: 文件或目錄不存在"
+ echo "錯誤: 檔案或目錄不存在。"
+ send_stats "移動檔案或目錄失敗: 檔案或目錄不存在"
continue
fi
- read -e -p "請輸入目標路徑 (包括新文件名或目錄名):" dest_path
+ read -e -p "請輸入目標路徑 (包括新檔案名稱或目錄名稱):" dest_path
if [ -z "$dest_path" ]; then
echo "錯誤: 請輸入目標路徑。"
- send_stats "移動文件或目錄失敗: 目標路徑未指定"
+ send_stats "移動檔案或目錄失敗: 目標路徑未指定"
continue
fi
- mv "$src_path" "$dest_path" && echo "文件或目錄已移動到$dest_path" || echo "移動文件或目錄失敗"
- send_stats "移動文件或目錄"
+ mv "$src_path" "$dest_path" && echo "檔案或目錄已移動到$dest_path" || echo "移動檔案或目錄失敗"
+ send_stats "移動檔案或目錄"
;;
24) # 复制文件目录
- read -e -p "請輸入要復制的文件或目錄路徑:" src_path
+ read -e -p "請輸入要複製的檔案或目錄路徑:" src_path
if [ ! -e "$src_path" ]; then
- echo "錯誤: 文件或目錄不存在。"
- send_stats "複製文件或目錄失敗: 文件或目錄不存在"
+ echo "錯誤: 檔案或目錄不存在。"
+ send_stats "複製檔案或目錄失敗: 檔案或目錄不存在"
continue
fi
- read -e -p "請輸入目標路徑 (包括新文件名或目錄名):" dest_path
+ read -e -p "請輸入目標路徑 (包括新檔案名稱或目錄名稱):" dest_path
if [ -z "$dest_path" ]; then
echo "錯誤: 請輸入目標路徑。"
- send_stats "複製文件或目錄失敗: 目標路徑未指定"
+ send_stats "複製檔案或目錄失敗: 目標路徑未指定"
continue
fi
- # 使用 -r 選項以遞歸方式複制目錄
- cp -r "$src_path" "$dest_path" && echo "文件或目錄已復製到$dest_path" || echo "複製文件或目錄失敗"
- send_stats "複製文件或目錄"
+ # 使用 -r 選項以遞歸方式複製目錄
+ cp -r "$src_path" "$dest_path" && echo "檔案或目錄已複製到$dest_path" || echo "複製檔案或目錄失敗"
+ send_stats "複製檔案或目錄"
;;
25) # 传送文件至远端服务器
- read -e -p "請輸入要傳送的文件路徑:" file_to_transfer
+ read -e -p "請輸入要傳送的檔案路徑:" file_to_transfer
if [ ! -f "$file_to_transfer" ]; then
echo "錯誤: 文件不存在。"
send_stats "傳送文件失敗: 文件不存在"
continue
fi
- read -e -p "請輸入遠端服務器IP:" remote_ip
- if [ -z "$remote_ip" ]; then
- echo "錯誤: 請輸入遠端服務器IP。"
- send_stats "傳送文件失敗: 未輸入遠端服務器IP"
- continue
- fi
-
- read -e -p "請輸入遠端服務器用戶名 (默認root):" remote_user
- remote_user=${remote_user:-root}
-
- read -e -p "請輸入遠端服務器密碼:" -s remote_password
- echo
- if [ -z "$remote_password" ]; then
- echo "錯誤: 請輸入遠端服務器密碼。"
- send_stats "傳送文件失敗: 未輸入遠端服務器密碼"
- continue
- fi
+ kj_ssh_read_host_user_port "請輸入遠端伺服器IP:" "請輸入遠端伺服器使用者名稱 (預設root):" "請輸入登入連接埠 (預設22):" "root" "22"
+ local remote_ip="$KJ_SSH_HOST"
+ local remote_user="$KJ_SSH_USER"
+ local remote_port="$KJ_SSH_PORT"
- read -e -p "請輸入登錄端口 (默認22):" remote_port
- remote_port=${remote_port:-22}
+ kj_ssh_read_password "請輸入遠端伺服器密碼:"
+ local remote_password="$KJ_SSH_PASSWORD"
# 清除已知主機的舊條目
ssh-keygen -f "/root/.ssh/known_hosts" -R "$remote_ip"
@@ -12234,7 +21454,7 @@ $remote_password
EOF
if [ $? -eq 0 ]; then
- echo "文件已傳送至遠程服務器home目錄。"
+ echo "檔案已傳送至遠端伺服器home目錄。"
send_stats "文件傳送成功"
else
echo "文件傳送失敗。"
@@ -12247,7 +21467,7 @@ EOF
0) # 返回上一级选单
- send_stats "返回上一級選單菜單"
+ send_stats "返回上一層選單選單"
break
;;
*) # 处理无效输入
@@ -12278,10 +21498,10 @@ run_commands_on_servers() {
local SERVERS_FILE="$HOME/cluster/servers.py"
local SERVERS=$(grep -oP '{"name": "\K[^"]+|"hostname": "\K[^"]+|"port": \K[^,]+|"username": "\K[^"]+|"password": "\K[^"]+' "$SERVERS_FILE")
- # 將提取的信息轉換為數組
+ # 將提取的資訊轉換為數組
IFS=$'\n' read -r -d '' -a SERVER_ARRAY <<< "$SERVERS"
- # 遍歷服務器並執行命令
+ # 遍歷伺服器並執行命令
for ((i=0; i<${#SERVER_ARRAY[@]}; i+=5)); do
local name=${SERVER_ARRAY[i]}
local hostname=${SERVER_ARRAY[i+1]}
@@ -12312,58 +21532,58 @@ fi
while true; do
clear
send_stats "集群控制中心"
- echo "服務器集群控制"
+ echo "伺服器叢集控制"
cat ~/cluster/servers.py
echo
echo -e "${gl_kjlan}------------------------${gl_bai}"
- echo -e "${gl_kjlan}服務器列表管理${gl_bai}"
- echo -e "${gl_kjlan}1. ${gl_bai}添加服務器${gl_kjlan}2. ${gl_bai}刪除服務器${gl_kjlan}3. ${gl_bai}編輯服務器"
- echo -e "${gl_kjlan}4. ${gl_bai}備份集群${gl_kjlan}5. ${gl_bai}還原集群"
+ echo -e "${gl_kjlan}伺服器清單管理${gl_bai}"
+ echo -e "${gl_kjlan}1. ${gl_bai}新增伺服器${gl_kjlan}2. ${gl_bai}刪除伺服器${gl_kjlan}3. ${gl_bai}編輯伺服器"
+ echo -e "${gl_kjlan}4. ${gl_bai}備份叢集${gl_kjlan}5. ${gl_bai}還原叢集"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- echo -e "${gl_kjlan}批量執行任務${gl_bai}"
+ echo -e "${gl_kjlan}批次執行任務${gl_bai}"
echo -e "${gl_kjlan}11. ${gl_bai}安裝科技lion腳本${gl_kjlan}12. ${gl_bai}更新系統${gl_kjlan}13. ${gl_bai}清理系統"
- echo -e "${gl_kjlan}14. ${gl_bai}安裝docker${gl_kjlan}15. ${gl_bai}安裝BBR3${gl_kjlan}16. ${gl_bai}設置1G虛擬內存"
- echo -e "${gl_kjlan}17. ${gl_bai}設置時區到上海${gl_kjlan}18. ${gl_bai}開放所有端口${gl_kjlan}51. ${gl_bai}自定義指令"
+ echo -e "${gl_kjlan}14. ${gl_bai}安裝docker${gl_kjlan}15. ${gl_bai}安裝BBR3${gl_kjlan}16. ${gl_bai}設定1G虛擬內存"
+ echo -e "${gl_kjlan}17. ${gl_bai}設定時區到上海${gl_kjlan}18. ${gl_bai}開放所有連接埠${gl_kjlan}51. ${gl_bai}自訂指令"
echo -e "${gl_kjlan}------------------------${gl_bai}"
- echo -e "${gl_kjlan}0. ${gl_bai}返回主菜單"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" sub_choice
case $sub_choice in
1)
- send_stats "添加集群服務器"
- read -e -p "服務器名稱:" server_name
- read -e -p "服務器IP:" server_ip
- read -e -p "服務器端口(22):" server_port
+ send_stats "新增叢集伺服器"
+ read -e -p "伺服器名稱:" server_name
+ read -e -p "伺服器IP:" server_ip
+ read -e -p "伺服器連接埠(22):" server_port
local server_port=${server_port:-22}
- read -e -p "服務器用戶名(root):" server_username
+ read -e -p "伺服器使用者名稱(root):" server_username
local server_username=${server_username:-root}
- read -e -p "服務器用戶密碼:" server_password
+ read -e -p "伺服器用戶密碼:" server_password
sed -i "/servers = \[/a\ {\"name\": \"$server_name\", \"hostname\": \"$server_ip\", \"port\": $server_port, \"username\": \"$server_username\", \"password\": \"$server_password\", \"remote_path\": \"/home/\"}," ~/cluster/servers.py
;;
2)
- send_stats "刪除集群服務器"
+ send_stats "刪除叢集伺服器"
read -e -p "請輸入需要刪除的關鍵字:" rmserver
sed -i "/$rmserver/d" ~/cluster/servers.py
;;
3)
- send_stats "編輯集群服務器"
+ send_stats "編輯叢集伺服器"
install nano
nano ~/cluster/servers.py
;;
4)
clear
- send_stats "備份集群"
- echo -e "請將${gl_huang}/root/cluster/servers.py${gl_bai}文件下載,完成備份!"
+ send_stats "備份叢集"
+ echo -e "請將${gl_huang}/root/cluster/servers.py${gl_bai}檔案下載,完成備份!"
break_end
;;
5)
clear
- send_stats "還原集群"
+ send_stats "還原叢集"
echo "請上傳您的servers.py,按任意鍵開始上傳!"
echo -e "請上傳您的${gl_huang}servers.py${gl_bai}文件到${gl_huang}/root/cluster/${gl_bai}完成還原!"
break_end
@@ -12396,8 +21616,8 @@ while true; do
;;
51)
- send_stats "自定義執行命令"
- read -e -p "請輸入批量執行的命令:" mingling
+ send_stats "自訂執行命令"
+ read -e -p "請輸入批次執行的命令:" mingling
run_commands_on_servers "${mingling}"
;;
@@ -12420,43 +21640,43 @@ echo "廣告專欄"
echo "------------------------"
echo "將為用戶提供更簡單優雅的推廣與購買體驗!"
echo ""
-echo -e "服務器優惠"
+echo -e "伺服器優惠"
echo "------------------------"
echo -e "${gl_lan}萊卡雲 香港CN2 GIA 韓國雙ISP 美國CN2 GIA 優惠活動${gl_bai}"
-echo -e "${gl_bai}網址: https://www.lcayun.com/aff/ZEXUQBIM${gl_bai}"
+echo -e "${gl_bai}网址: https://www.lcayun.com/aff/ZEXUQBIM${gl_bai}"
echo "------------------------"
-echo -e "${gl_lan}RackNerd 10.99刀每年 美國 1核心 1G內存 20G硬盤 1T流量每月${gl_bai}"
+echo -e "${gl_lan}RackNerd 10.99刀每年 美國 1核心 1G記憶體 20G硬碟 1T流量每月${gl_bai}"
echo -e "${gl_bai}網址: https://my.racknerd.com/aff.php?aff=5501&pid=879${gl_bai}"
echo "------------------------"
-echo -e "${gl_zi}Hostinger 52.7刀每年 美國 1核心 4G內存 50G硬盤 4T流量每月${gl_bai}"
+echo -e "${gl_zi}Hostinger 52.7刀每年 美國 1核心 4G記憶體 50G硬碟 4T流量每月${gl_bai}"
echo -e "${gl_bai}網址: https://cart.hostinger.com/pay/d83c51e9-0c28-47a6-8414-b8ab010ef94f?_ga=GA1.3.942352702.1711283207${gl_bai}"
echo "------------------------"
-echo -e "${gl_huang}搬瓦工 49刀每季 美國CN2GIA 日本軟銀 2核心 1G內存 20G硬盤 1T流量每月${gl_bai}"
+echo -e "${gl_huang}搬運工 49刀每季 美國CN2GIA 日本軟銀 2核心 1G內存 20G硬碟 1T流量每月${gl_bai}"
echo -e "${gl_bai}網址: https://bandwagonhost.com/aff.php?aff=69004&pid=87${gl_bai}"
echo "------------------------"
-echo -e "${gl_lan}DMIT 28刀每季 美國CN2GIA 1核心 2G內存 20G硬盤 800G流量每月${gl_bai}"
+echo -e "${gl_lan}DMIT 28刀每季 美國CN2GIA 1核心 2G記憶體 20G硬碟 800G流量每月${gl_bai}"
echo -e "${gl_bai}網址: https://www.dmit.io/aff.php?aff=4966&pid=100${gl_bai}"
echo "------------------------"
-echo -e "${gl_zi}V.PS 6.9刀每月 東京軟銀 2核心 1G內存 20G硬盤 1T流量每月${gl_bai}"
+echo -e "${gl_zi}V.PS 6.9刀每月 東京軟銀 2核心 1G內存 20G硬碟 1T流量每月${gl_bai}"
echo -e "${gl_bai}網址: https://vps.hosting/cart/tokyo-cloud-kvm-vps/?id=148&?affid=1355&?affid=1355${gl_bai}"
echo "------------------------"
echo -e "${gl_kjlan}VPS更多熱門優惠${gl_bai}"
echo -e "${gl_bai}網址: https://kejilion.pro/topvps/${gl_bai}"
echo "------------------------"
echo ""
-echo -e "域名優惠"
+echo -e "網域優惠"
echo "------------------------"
echo -e "${gl_lan}GNAME 8.8刀首年COM域名 6.68刀首年CC域名${gl_bai}"
echo -e "${gl_bai}網址: https://www.gname.com/register?tt=86836&ttcode=KEJILION86836&ttbj=sh${gl_bai}"
echo "------------------------"
echo ""
-echo -e "科技lion周邊"
+echo -e "科技lion週邊"
echo "------------------------"
echo -e "${gl_kjlan}B站:${gl_bai}https://b23.tv/2mqnQyh ${gl_kjlan}油管:${gl_bai}https://www.youtube.com/@kejilion${gl_bai}"
echo -e "${gl_kjlan}官網:${gl_bai}https://kejilion.pro/ ${gl_kjlan}導航:${gl_bai}https://dh.kejilion.pro/${gl_bai}"
-echo -e "${gl_kjlan}部落格:${gl_bai}https://blog.kejilion.pro/ ${gl_kjlan}軟件中心:${gl_bai}https://app.kejilion.pro/${gl_bai}"
+echo -e "${gl_kjlan}部落格:${gl_bai}https://blog.kejilion.pro/ ${gl_kjlan}軟體中心:${gl_bai}https://app.kejilion.pro/${gl_bai}"
echo "------------------------"
-echo -e "${gl_kjlan}腳本官網:${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub地址:${gl_bai}https://github.com/kejilion/sh${gl_bai}"
+echo -e "${gl_kjlan}腳本官網:${gl_bai}https://kejilion.sh ${gl_kjlan}GitHub地址:${gl_bai}${gh_https_url}github.com/kejilion/sh${gl_bai}"
echo "------------------------"
echo ""
}
@@ -12464,6 +21684,64 @@ echo ""
+games_server_tools() {
+
+ while true; do
+ clear
+ echo -e "遊戲開服腳本合集"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}1. ${gl_bai}幻獸帕魯開服腳本"
+ echo -e "${gl_kjlan}2. ${gl_bai}我的世界開服腳本"
+ echo -e "${gl_kjlan}------------------------"
+ echo -e "${gl_kjlan}0. ${gl_bai}返回主選單"
+ echo -e "${gl_kjlan}------------------------${gl_bai}"
+ read -e -p "請輸入你的選擇:" sub_choice
+
+ case $sub_choice in
+
+ 1) send_stats "幻獸帕魯開服腳本" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
+ exit
+ ;;
+ 2) send_stats "我的世界開服腳本" ; cd ~
+ curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/mc.sh ; chmod +x mc.sh ; ./mc.sh
+ exit
+ ;;
+
+ 0)
+ kejilion
+ ;;
+
+ *)
+ echo "無效的輸入!"
+ ;;
+ esac
+ break_end
+
+ done
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
kejilion_update() {
@@ -12476,15 +21754,18 @@ while true; do
echo "全部日誌:${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt"
echo "------------------------"
- curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
- local sh_v_new=$(curl -s ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | cut -d '"' -f 2)
+ curl -s --max-time 15 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion_sh_log.txt | tail -n 30
+ # 只下載前5行取得版本號,避免下載整個腳本
+ local sh_v_new=$(curl -s --max-time 15 -r 0-200 ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh | grep -o 'sh_v="[0-9.]*"' | head -1 | cut -d '"' -f 2)
- if [ "$sh_v" = "$sh_v_new" ]; then
+ if [ -z "$sh_v_new" ]; then
+ echo -e "${gl_hong}無法獲取最新版本信息,請檢查網絡連接${gl_bai}"
+ elif [ "$sh_v" = "$sh_v_new" ]; then
echo -e "${gl_lv}你已經是最新版本!${gl_huang}v$sh_v${gl_bai}"
send_stats "腳本已經最新了,無需更新"
else
echo "發現新版本!"
- echo -e "當前版本 v$sh_v最新版本${gl_huang}v$sh_v_new${gl_bai}"
+ echo -e "目前版本 v$sh_v最新版本${gl_huang}v$sh_v_new${gl_bai}"
fi
@@ -12497,45 +21778,84 @@ while true; do
fi
echo "------------------------"
- echo "1. 現在更新 2. 開啟自動更新 3. 關閉自動更新"
+ echo "1. 現在更新 2. 開啟自動更新 3. 關閉自動更新"
echo "------------------------"
- echo "0. 返回主菜單"
+ echo "0. 返回主選單"
echo "------------------------"
read -e -p "請輸入你的選擇:" choice
case "$choice" in
1)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
+ local download_url
if [ "$country" = "CN" ]; then
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh && chmod +x kejilion.sh
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/cn/kejilion.sh"
+ else
+ download_url="${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh"
+ fi
+
+ # 備份當前腳本
+ cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null
+
+ # 下載到臨時文件,校驗後再替換
+ local tmp_file=$(mktemp ~/kejilion_tmp.XXXXXX)
+ if curl -sS --max-time 60 --fail -o "$tmp_file" "$download_url" && \
+ [ -s "$tmp_file" ] && \
+ head -1 "$tmp_file" | grep -q '^#!/bin/bash'; then
+ chmod +x "$tmp_file"
+ mv -f "$tmp_file" ~/kejilion.sh
+ canshu_v6
+ CheckFirstRun_true
+ yinsiyuanquan2
+ cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
+ ln -sf /usr/local/bin/k /usr/bin/k > /dev/null 2>&1
+ echo -e "${gl_lv}腳本已更新到最新版本!${gl_huang}v$sh_v_new${gl_bai}"
+ send_stats "腳本已經最新$sh_v_new"
else
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh
+ rm -f "$tmp_file"
+ # 恢復備份
+ if [ -f ~/kejilion.sh.bak ]; then
+ mv -f ~/kejilion.sh.bak ~/kejilion.sh
+ fi
+ echo -e "${gl_hong}更新失敗!下載出錯或檔案校驗不通過,已恢復原版本${gl_bai}"
+ send_stats "腳本更新失敗"
fi
- canshu_v6
- CheckFirstRun_true
- yinsiyuanquan2
- cp -f ~/kejilion.sh /usr/local/bin/k > /dev/null 2>&1
- echo -e "${gl_lv}腳本已更新到最新版本!${gl_huang}v$sh_v_new${gl_bai}"
- send_stats "腳本已經最新$sh_v_new"
break_end
~/kejilion.sh
exit
;;
2)
clear
- local country=$(curl -s ipinfo.io/country)
+ local country=$(curl -s --max-time 5 ipinfo.io/country)
local ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
+ local cron_proxy cron_sed_cmd
if [ "$country" = "CN" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"CN\"/g' ~/kejilion.sh"
elif [ -n "$ipv6_address" ]; then
- SH_Update_task="curl -sS -O https://gh.kejilion.pro/raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh && sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ./kejilion.sh"
+ cron_proxy="https://gh.kejilion.pro/"
+ cron_sed_cmd="sed -i 's/canshu=\"default\"/canshu=\"V6\"/g' ~/kejilion.sh"
else
- SH_Update_task="curl -sS -O https://raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && chmod +x kejilion.sh"
+ cron_proxy="https://"
+ cron_sed_cmd=""
+ fi
+
+ # 建置健全的自動更新指令:下載到暫存檔案 → 校驗 → 備份 → 取代 → 還原本機設定 → 部署
+ SH_Update_task="cd ~ && tmp=\$(mktemp ~/kejilion_tmp.XXXXXX) && curl -sS --max-time 60 --fail -o \"\$tmp\" ${cron_proxy}raw.githubusercontent.com/kejilion/sh/main/kejilion.sh && [ -s \"\$tmp\" ] && head -1 \"\$tmp\" | grep -q '^#!/bin/bash' && cp -f ~/kejilion.sh ~/kejilion.sh.bak 2>/dev/null && chmod +x \"\$tmp\" && mv -f \"\$tmp\" ~/kejilion.sh"
+ # 追加設定恢復
+ if [ -n "$cron_sed_cmd" ]; then
+ SH_Update_task="$SH_Update_task && $cron_sed_cmd"
fi
+ # 從舊腳本恢復 permission_granted 和 ENABLE_STATS 設置
+ SH_Update_task="$SH_Update_task && grep -q 'permission_granted=\"true\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/permission_granted=\"false\"/permission_granted=\"true\"/' ~/kejilion.sh; grep -q 'ENABLE_STATS=\"false\"' ~/kejilion.sh.bak 2>/dev/null && sed -i 's/ENABLE_STATS=\"true\"/ENABLE_STATS=\"false\"/' ~/kejilion.sh"
+ # 部署到 /usr/local/bin/k 和 /usr/bin/k
+ SH_Update_task="$SH_Update_task; cp -f ~/kejilion.sh /usr/local/bin/k 2>/dev/null; ln -sf /usr/local/bin/k /usr/bin/k 2>/dev/null"
+ # 下載失敗時清理暫存文件
+ SH_Update_task="$SH_Update_task || rm -f \"\$tmp\" 2>/dev/null"
+
check_crontab_installed
(crontab -l | grep -v "kejilion.sh") | crontab -
- # (crontab -l 2>/dev/null; echo "0 2 * * * bash -c \"$SH_Update_task\"") | crontab -
- (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c \"$SH_Update_task\"") | crontab -
+ (crontab -l 2>/dev/null; echo "$(shuf -i 0-59 -n 1) 2 * * * bash -c '$SH_Update_task'") | crontab -
echo -e "${gl_lv}自動更新已開啟,每天凌晨2點腳本會自動更新!${gl_bai}"
send_stats "開啟腳本自動更新"
break_end
@@ -12567,9 +21887,9 @@ echo "╦╔═╔═╗ ╦╦╦ ╦╔═╗╔╗╔ ╔═╗╦ ╦"
echo "╠╩╗║╣ ║║║ ║║ ║║║║ ╚═╗╠═╣"
echo "╩ ╩╚═╝╚╝╩╩═╝╩╚═╝╝╚╝o╚═╝╩ ╩"
echo -e "科技lion腳本工具箱 v$sh_v"
-echo -e "命令行輸入${gl_huang}k${gl_kjlan}可快速啟動腳本${gl_bai}"
+echo -e "命令列輸入${gl_huang}k${gl_kjlan}可快速啟動腳本${gl_bai}"
echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}1. ${gl_bai}系統信息查詢"
+echo -e "${gl_kjlan}1. ${gl_bai}系統資訊查詢"
echo -e "${gl_kjlan}2. ${gl_bai}系統更新"
echo -e "${gl_kjlan}3. ${gl_bai}系統清理"
echo -e "${gl_kjlan}4. ${gl_bai}基礎工具"
@@ -12577,15 +21897,14 @@ echo -e "${gl_kjlan}5. ${gl_bai}BBR管理"
echo -e "${gl_kjlan}6. ${gl_bai}Docker管理"
echo -e "${gl_kjlan}7. ${gl_bai}WARP管理"
echo -e "${gl_kjlan}8. ${gl_bai}測試腳本合集"
-echo -e "${gl_kjlan}9. ${gl_bai}甲骨文云腳本合集"
+echo -e "${gl_kjlan}9. ${gl_bai}甲骨文雲腳本合集"
echo -e "${gl_huang}10. ${gl_bai}LDNMP建站"
echo -e "${gl_kjlan}11. ${gl_bai}應用市場"
echo -e "${gl_kjlan}12. ${gl_bai}後台工作區"
echo -e "${gl_kjlan}13. ${gl_bai}系統工具"
-echo -e "${gl_kjlan}14. ${gl_bai}服務器集群控制"
+echo -e "${gl_kjlan}14. ${gl_bai}伺服器叢集控制"
echo -e "${gl_kjlan}15. ${gl_bai}廣告專欄"
-echo -e "${gl_kjlan}------------------------${gl_bai}"
-echo -e "${gl_kjlan}p. ${gl_bai}幻獸帕魯開服腳本"
+echo -e "${gl_kjlan}16. ${gl_bai}遊戲開服腳本合集"
echo -e "${gl_kjlan}------------------------${gl_bai}"
echo -e "${gl_kjlan}00. ${gl_bai}腳本更新"
echo -e "${gl_kjlan}------------------------${gl_bai}"
@@ -12594,7 +21913,7 @@ echo -e "${gl_kjlan}------------------------${gl_bai}"
read -e -p "請輸入你的選擇:" choice
case $choice in
- 1) linux_ps ;;
+ 1) linux_info ;;
2) clear ; send_stats "系統更新" ; linux_update ;;
3) clear ; send_stats "系統清理" ; linux_clean ;;
4) linux_tools ;;
@@ -12611,10 +21930,7 @@ case $choice in
13) linux_Settings ;;
14) linux_cluster ;;
15) kejilion_Affiliates ;;
- p) send_stats "幻獸帕魯開服腳本" ; cd ~
- curl -sS -O ${gh_proxy}raw.githubusercontent.com/kejilion/sh/main/palworld.sh ; chmod +x palworld.sh ; ./palworld.sh
- exit
- ;;
+ 16) games_server_tools ;;
00) kejilion_update ;;
0) clear ; exit ;;
*) echo "無效的輸入!" ;;
@@ -12625,67 +21941,76 @@ done
k_info() {
-send_stats "k命令參考用例"
+send_stats "k指令參考用例"
echo "-------------------"
-echo "視頻介紹: https://www.bilibili.com/video/BV1ib421E7it?t=0.1"
+echo "影片介紹: https://www.bilibili.com/video/BV1ib421E7it?t=0.1"
echo "以下是k命令參考用例:"
-echo "啟動腳本 k"
-echo "安裝軟件包 k install nano wget | k add nano wget | k 安裝 nano wget"
-echo "卸載軟件包 k remove nano wget | k del nano wget | k uninstall nano wget | k 卸載 nano wget"
-echo "更新系統 k update | k 更新"
-echo "清理系統垃圾 k clean | k 清理"
-echo "重裝系統面板 k dd | k 重裝"
-echo "bbr3控制面板 k bbr3 | k bbrv3"
-echo "內核調優面板 k nhyh | k 內核優化"
-echo "設置虛擬內存 k swap 2048"
-echo "設置虛擬時區 k time Asia/Shanghai | k 時區 Asia/Shanghai"
-echo "系統回收站 k trash | k hsz | k 回收站"
-echo "系統備份功能 k backup | k bf | k 備份"
-echo "ssh遠程連接工具 k ssh | k 遠程連接"
-echo "rsync遠程同步工具 k rsync | k 遠程同步"
-echo "硬盤管理工具 k disk | k 硬盤管理"
-echo "內網穿透(服務端) k frps"
-echo "內網穿透(客戶端) k frpc"
-echo "軟件啟動 k start sshd | k 啟動 sshd"
-echo "軟件停止 k stop sshd | k 停止 sshd"
-echo "軟件重啟 k restart sshd | k 重啟 sshd"
-echo "軟件狀態查看 k status sshd | k 狀態 sshd"
-echo "軟件開機啟動 k enable docker | k autostart docke | k 開機啟動 docker"
-echo "域名證書申請 k ssl"
-echo "域名證書到期查詢 k ssl ps"
-echo "docker環境安裝 k docker install |k docker 安裝"
-echo "docker容器管理 k docker ps |k docker 容器"
-echo "docker鏡像管理 k docker img |k docker 鏡像"
-echo "LDNMP站點管理 k web"
-echo "LDNMP緩存清理 k web cache"
-echo "安裝WordPress k wp |k wordpress |k wp xxx.com"
-echo "安裝反向代理 k fd |k rp |k 反代 |k fd xxx.com"
-echo "安裝負載均衡 k loadbalance |k 負載均衡"
-echo "防火牆面板 k fhq |k 防火牆"
-echo "開放端口 k dkdk 8080 |k 打開端口 8080"
-echo "關閉端口 k gbdk 7800 |k 關閉端口 7800"
+echo "啟動腳本 k"
+echo "安裝軟體包 k install nano wget | k add nano wget | k 安裝 nano wget"
+echo "卸載軟體包 k remove nano wget | k del nano wget | k uninstall nano wget | k 卸載 nano wget"
+echo "更新系統 k update | k 更新"
+echo "清理系統垃圾 k clean | k 清理"
+echo "重裝系統面板 k dd | k 重裝"
+echo "bbr3控制面板 k bbr3 | k bbrv3"
+echo "核心調優面板 k nhyh | k 核心最佳化"
+echo "設定虛擬記憶體 k swap 2048"
+echo "設定虛擬時區 k time Asia/Shanghai | k 時區 Asia/Shanghai"
+echo "系統回收站 k trash | k hsz | k 回收站"
+echo "系統備份功能 k backup | k bf | k 備份"
+echo "ssh遠端連線工具 k ssh | k 遠端連線"
+echo "rsync遠端同步工具 k rsync | k 遠端同步"
+echo "硬碟管理工具 k disk | k 硬碟管理"
+echo "內網穿透(服務端) k frps"
+echo "內網穿透(客戶端) k frpc"
+echo "軟體啟動 k start sshd | k 啟動 sshd"
+echo "軟體停止 k stop sshd | k 停止 sshd"
+echo "軟體重啟 k restart sshd | k 重啟 sshd"
+echo "軟體狀態檢視 k status sshd | k 狀態 sshd"
+echo "軟體開機啟動 k enable docker | k autostart docke | k 開機啟動 docker"
+echo "網域憑證申請 k ssl"
+echo "網域名稱憑證到期查詢 k ssl ps"
+echo "docker管理平面 k docker"
+echo "docker環境安裝 k docker install |k docker 安裝"
+echo "docker容器管理 k docker ps |k docker 容器"
+echo "docker映像管理 k docker img |k docker 映像"
+echo "LDNMP站台管理 k web"
+echo "LDNMP快取清理 k web cache"
+echo "安裝WordPress k wp |k wordpress |k wp xxx.com"
+echo "安裝反向代理 k fd |k rp |k 反代 |k fd xxx.com"
+echo "安裝負載平衡 k loadbalance |k 負載平衡"
+echo "安裝L4負載平衡 k stream |k L4負載平衡"
+echo "防火牆面板 k fhq |k 防火牆"
+echo "開放埠 k dkdk 8080 |k 開啟連接埠 8080"
+echo "關閉連接埠 k gbdk 7800 |k 關閉連接埠 7800"
echo "放行IP k fxip 127.0.0.0/8 |k 放行IP 127.0.0.0/8"
-echo "阻止IP k zzip 177.5.25.36 |k 阻止IP 177.5.25.36"
-
+echo "阻止IP k zzip 177.5.25.36 |k 阻止IP 177.5.25.36"
+echo "命令收藏 k fav | k 指令收藏夾"
+echo "應用市場管理 k app"
+echo "應用編號快捷管理 k app 26 | k app 1panel | k app npm"
+echo "fail2ban管理 k fail2ban | k f2b"
+echo "顯示系統資訊 k info"
+echo "ROOT金鑰管理 k sshkey"
+echo "SSH公鑰導入(URL) k sshkey "
+echo "SSH公鑰導入(GitHub) k sshkey github "
}
if [ "$#" -eq 0 ]; then
- # 如果沒有參數,運行交互式邏輯
+ # 如果沒有參數,運行互動式邏輯
kejilion_sh
else
- # 如果有參數,執行相應函數
+ # 如果有參數,執行對應函數
case $1 in
install|add|安装)
shift
- send_stats "安裝軟件"
+ send_stats "安裝軟體"
install "$@"
;;
remove|del|uninstall|卸载)
shift
- send_stats "卸載軟件"
+ send_stats "解除安裝軟體"
remove "$@"
;;
update|更新)
@@ -12738,9 +22063,10 @@ else
find_container_by_host_port "$port"
if [ -z "$docker_name" ]; then
close_port "$port"
- echo "已阻止IP+端口訪問該服務"
+ echo "已阻止IP+連接埠存取該服務"
else
ip_address
+ close_port "$port"
block_container_port "$docker_name" "$ipv4_address"
fi
;;
@@ -12749,15 +22075,20 @@ else
ldnmp_Proxy_backend
;;
+
+ stream|L4负载均衡)
+ ldnmp_Proxy_backend_stream
+ ;;
+
swap)
shift
- send_stats "快速設置虛擬內存"
+ send_stats "快速設定虛擬記憶體"
add_swap "$@"
;;
time|时区)
shift
- send_stats "快速設置時區"
+ send_stats "快速設定時區"
set_timedate "$@"
;;
@@ -12799,30 +22130,34 @@ else
iptables_panel
;;
+ 命令收藏夹|fav)
+ linux_fav
+ ;;
+
status|状态)
shift
- send_stats "軟件狀態查看"
+ send_stats "軟體狀態檢視"
status "$@"
;;
start|启动)
shift
- send_stats "軟件啟動"
+ send_stats "軟體啟動"
start "$@"
;;
stop|停止)
shift
- send_stats "軟件暫停"
+ send_stats "軟體暫停"
stop "$@"
;;
restart|重启)
shift
- send_stats "軟件重啟"
+ send_stats "軟體重啟"
restart "$@"
;;
enable|autostart|开机启动)
shift
- send_stats "軟件開機自啟"
+ send_stats "軟體開機自啟"
enable "$@"
;;
@@ -12846,19 +22181,19 @@ else
shift
case $1 in
install|安装)
- send_stats "快捷安裝docker"
+ send_stats "快速安裝docker"
install_docker
;;
ps|容器)
- send_stats "快捷容器管理"
+ send_stats "快速容器管理"
docker_ps
;;
img|镜像)
- send_stats "快捷鏡像管理"
+ send_stats "快速鏡像管理"
docker_image
;;
*)
- k_info
+ linux_docker
;;
esac
;;
@@ -12878,6 +22213,59 @@ else
fi
;;
+
+ app)
+ shift
+ send_stats "應用$@"
+ linux_panel "$@"
+ ;;
+
+ claw|oc|OpenClaw)
+ moltbot_menu
+ ;;
+
+ info)
+ linux_info
+ ;;
+
+ fail2ban|f2b)
+ fail2ban_panel
+ ;;
+
+
+ sshkey)
+
+ shift
+ case "$1" in
+ "" )
+ # sshkey → 互動選單
+ send_stats "SSHKey 互動選單"
+ sshkey_panel
+ ;;
+ github )
+ shift
+ send_stats "從 GitHub 導入 SSH 公鑰"
+ fetch_github_ssh_keys "$1"
+ ;;
+ http://*|https://* )
+ send_stats "從 URL 匯入 SSH 公鑰"
+ fetch_remote_ssh_keys "$1"
+ ;;
+ ssh-rsa*|ssh-ed25519*|ssh-ecdsa* )
+ send_stats "公鑰直接導入"
+ import_sshkey "$1"
+ ;;
+ * )
+ echo "錯誤:未知參數 '$1'"
+ echo "用法:"
+ echo "k sshkey 進入互動選單"
+ echo "k sshkey \"\" 直接導入 SSH 公鑰"
+ echo "k sshkey 從 URL 匯入 SSH 公鑰"
+ echo "k sshkey github 從 GitHub 匯入 SSH 公鑰"
+ ;;
+ esac
+
+ ;;
*)
k_info
;;
diff --git a/update_log.sh b/update_log.sh
index b67131966..0b5656b42 100644
--- a/update_log.sh
+++ b/update_log.sh
@@ -417,6 +417,7 @@
echo "2024-05-11 v2.5.1"
echo "对docker环境检测进行优化,应用部署将会更稳定"
echo "对一些提示的文字颜色进行了优化,对一些警示文字进行红色黄色标注"
+ echo "openclaw的备份还原功能新增"
echo "------------------------"
diff --git a/upgrade_openssh9.8p1.sh b/upgrade_openssh9.8p1.sh
index 78b5d7138..f56dc3d5a 100644
--- a/upgrade_openssh9.8p1.sh
+++ b/upgrade_openssh9.8p1.sh
@@ -70,6 +70,8 @@ install_openssh() {
# 重启SSH服务
restart_ssh() {
+ mv /usr/bin/ssh /usr/bin/ssh.bak
+ ln -s /usr/local/bin/ssh /usr/bin/ssh
case $OS in
ubuntu|debian)
systemctl restart ssh