- 移除 WARP、Python 安装、留言板、重启和集群管理菜单 - 删除相关辅助函数和远程执行逻辑 - 将脚本版本提升至 3.0.0,以体现破坏性变更 - 重写功能文档并清理过时说明
2406 lines
89 KiB
Bash
2406 lines
89 KiB
Bash
#!/bin/bash
|
||
|
||
sh_v="3.0.0"
|
||
|
||
huang='\033[33m'
|
||
bai='\033[0m'
|
||
lv='\033[0;32m'
|
||
hong='\033[31m'
|
||
kjlan='\033[96m'
|
||
hui='\e[37m'
|
||
|
||
# 统一使用 GitHub 官方 Raw 地址,可通过环境变量 GITHUB_RAW_URL 覆盖
|
||
GITHUB_RAW_URL="${GITHUB_RAW_URL:-https://raw.githubusercontent.com}"
|
||
GITHUB_RAW_URL="${GITHUB_RAW_URL%/}"
|
||
|
||
# 本脚本自身的更新源
|
||
UPDATE_RAW_URL="${UPDATE_RAW_URL:-https://gitea.tohub.top/Share/kejilion/raw/branch/main}"
|
||
UPDATE_RAW_URL="${UPDATE_RAW_URL%/}"
|
||
|
||
XANMOD_SOURCE_FILE=/etc/apt/sources.list.d/kejilion-xanmod.list
|
||
|
||
if [ ! -r /etc/os-release ]; then
|
||
echo "无法识别操作系统,仅支持 Debian 和 Ubuntu。" >&2
|
||
exit 1
|
||
fi
|
||
|
||
OS_ID=$(. /etc/os-release && printf '%s' "$ID")
|
||
case "$OS_ID" in
|
||
debian|ubuntu) ;;
|
||
*)
|
||
echo "当前系统不受支持,仅支持 Debian 和 Ubuntu。" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
# 记录脚本自身的绝对路径,供重启自身和安装快捷命令使用
|
||
SCRIPT_PATH=$(readlink -f "$0" 2>/dev/null)
|
||
[ -f "$SCRIPT_PATH" ] || SCRIPT_PATH="$0"
|
||
if [ ! -f "$SCRIPT_PATH" ]; then
|
||
echo "无法定位脚本自身路径,请先将脚本保存到本地再运行。" >&2
|
||
exit 1
|
||
fi
|
||
|
||
cp "$SCRIPT_PATH" /usr/local/bin/k > /dev/null 2>&1
|
||
|
||
# 统一的错误提示
|
||
err_msg() {
|
||
echo -e "${hong}$*${bai}" >&2
|
||
}
|
||
|
||
# 新建账户用:与 adduser 默认 NAME_REGEX 一致,只允许小写
|
||
valid_username() {
|
||
[ "${#1}" -le 32 ] && [[ "$1" =~ ^[a-z_][a-z0-9_-]*[$]?$ ]]
|
||
}
|
||
|
||
# 仅校验字符集:useradd 允许含大写字母的用户名,操作已有账户时沿用新建账户
|
||
# 的严格规则会让这类账户无法通过菜单管理。白名单仍要保留,避免把任意字符串
|
||
# 写进 sudoers 或拼进文件路径。
|
||
valid_username_chars() {
|
||
[ -n "$1" ] && [ "${#1}" -le 32 ] && [[ "$1" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*[$]?$ ]]
|
||
}
|
||
|
||
# 授予权限等场景用:额外要求账户真实存在。比对 id -un 而非只看 id 的退出码,
|
||
# 是为了挡掉直接传 UID 数字的情况(id 0 会成功并解析成 root)。
|
||
valid_existing_username() {
|
||
valid_username_chars "$1" && [ "$(id -un "$1" 2>/dev/null)" = "$1" ]
|
||
}
|
||
|
||
valid_hostname() {
|
||
local hostname="$1" label
|
||
local hostname_labels=()
|
||
[ -n "$hostname" ] && [ "${#hostname}" -le 253 ] || return 1
|
||
[[ "$hostname" != .* && "$hostname" != *. && "$hostname" != *..* ]] || return 1
|
||
IFS=. read -r -a hostname_labels <<< "$hostname"
|
||
for label in "${hostname_labels[@]}"; do
|
||
[ -n "$label" ] && [ "${#label}" -le 63 ] &&
|
||
[[ "$label" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$ ]] || return 1
|
||
done
|
||
}
|
||
|
||
grant_sudo() {
|
||
local username="$1" sudoers_file="/etc/sudoers.d/kejilion-$1" tmp_sudoers
|
||
if ! valid_existing_username "$username"; then
|
||
err_msg "用户不存在或用户名格式不合法: $username"
|
||
return 1
|
||
fi
|
||
if ! command -v visudo &>/dev/null; then
|
||
install sudo || return 1
|
||
fi
|
||
mkdir -p /etc/sudoers.d || return 1
|
||
tmp_sudoers=$(mktemp /etc/sudoers.d/.kejilion.XXXXXX) || return 1
|
||
if ! printf '%s ALL=(ALL:ALL) ALL\n' "$username" > "$tmp_sudoers" ||
|
||
! chmod 440 "$tmp_sudoers" ||
|
||
! visudo -cf "$tmp_sudoers" >/dev/null; then
|
||
rm -f "$tmp_sudoers"
|
||
err_msg "sudoers 配置校验失败,未授予 sudo 权限。"
|
||
return 1
|
||
fi
|
||
if ! mv -f "$tmp_sudoers" "$sudoers_file"; then
|
||
rm -f "$tmp_sudoers"
|
||
err_msg "写入 $sudoers_file 失败。"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
revoke_sudo() {
|
||
local username="$1" sudoers_file="/etc/sudoers.d/kejilion-$1" tmp_sudoers legacy_rule
|
||
# 这里只校验字符集,不要求账户仍然存在:账户已被删除、只剩 sudoers
|
||
# 残留规则时,仍应允许清理。
|
||
if ! valid_username_chars "$username"; then
|
||
err_msg "用户名格式不合法: $username"
|
||
return 1
|
||
fi
|
||
if ! command -v visudo &>/dev/null; then
|
||
err_msg "未找到 visudo,无法安全修改 sudoers。"
|
||
return 1
|
||
fi
|
||
|
||
# 兼容旧版本直接追加到 /etc/sudoers 的授权;没有旧规则时不重写主配置。
|
||
legacy_rule="$username ALL=(ALL:ALL) ALL"
|
||
if grep -Fqx -- "$legacy_rule" /etc/sudoers; then
|
||
tmp_sudoers=$(mktemp /etc/.sudoers.kejilion.XXXXXX) || return 1
|
||
awk -v rule="$legacy_rule" '$0 != rule' /etc/sudoers > "$tmp_sudoers" || {
|
||
rm -f "$tmp_sudoers"
|
||
return 1
|
||
}
|
||
if ! chmod --reference=/etc/sudoers "$tmp_sudoers" 2>/dev/null ||
|
||
! chown --reference=/etc/sudoers "$tmp_sudoers" 2>/dev/null ||
|
||
! visudo -cf "$tmp_sudoers" >/dev/null ||
|
||
! mv -f "$tmp_sudoers" /etc/sudoers; then
|
||
rm -f "$tmp_sudoers"
|
||
err_msg "sudoers 配置校验或写入失败,未修改主配置。"
|
||
return 1
|
||
fi
|
||
fi
|
||
rm -f "$sudoers_file" || {
|
||
err_msg "删除 $sudoers_file 失败。"
|
||
return 1
|
||
}
|
||
}
|
||
|
||
# 下载文件,失败时给出明确提示并返回非零
|
||
download_file() {
|
||
local url="$1" dest="$2"
|
||
if curl -fsSL --retry 2 --max-time 60 -o "$dest" "$url"; then
|
||
return 0
|
||
fi
|
||
err_msg "下载失败: $url"
|
||
return 1
|
||
}
|
||
|
||
update_limit_cron() {
|
||
local action="$1" limit_script="$2" current_cron new_cron cron_error
|
||
current_cron=$(mktemp) || return 1
|
||
new_cron=$(mktemp) || {
|
||
rm -f "$current_cron"
|
||
return 1
|
||
}
|
||
cron_error=$(mktemp) || {
|
||
rm -f "$current_cron" "$new_cron"
|
||
return 1
|
||
}
|
||
if ! crontab -l > "$current_cron" 2> "$cron_error"; then
|
||
if grep -qi 'no crontab' "$cron_error"; then
|
||
: > "$current_cron"
|
||
else
|
||
rm -f "$current_cron" "$new_cron" "$cron_error"
|
||
err_msg "无法读取现有 crontab,已取消修改。"
|
||
return 1
|
||
fi
|
||
fi
|
||
rm -f "$cron_error"
|
||
|
||
if ! awk -v script="$limit_script" '
|
||
$0 == "# KEJILION_LIMIT_BEGIN" {
|
||
if (in_block) exit 2
|
||
in_block = 1
|
||
next
|
||
}
|
||
$0 == "# KEJILION_LIMIT_END" {
|
||
if (!in_block) exit 2
|
||
in_block = 0
|
||
next
|
||
}
|
||
in_block { next }
|
||
$0 == "* * * * * ~/Limiting_Shut_down.sh" { next }
|
||
$0 == "* * * * * " script { next }
|
||
$0 == "0 1 1 * * reboot" { next }
|
||
{ print }
|
||
END { if (in_block) exit 2 }
|
||
' "$current_cron" > "$new_cron"; then
|
||
rm -f "$current_cron" "$new_cron"
|
||
err_msg "检测到不完整的限流 crontab 标记,已保留原定时任务。"
|
||
return 1
|
||
fi
|
||
|
||
if [ "$action" = "enable" ]; then
|
||
{
|
||
echo "# KEJILION_LIMIT_BEGIN"
|
||
printf '* * * * * /bin/bash %q\n' "$limit_script"
|
||
echo "0 1 1 * * /sbin/reboot"
|
||
echo "# KEJILION_LIMIT_END"
|
||
} >> "$new_cron"
|
||
elif [ "$action" != "disable" ]; then
|
||
rm -f "$current_cron" "$new_cron"
|
||
err_msg "未知的限流定时任务操作: $action"
|
||
return 1
|
||
fi
|
||
|
||
if crontab "$new_cron"; then
|
||
rm -f "$current_cron" "$new_cron"
|
||
return 0
|
||
fi
|
||
rm -f "$current_cron" "$new_cron"
|
||
return 1
|
||
}
|
||
|
||
# 版本号比较:$1 严格大于 $2 时返回 0,用于避免自更新把脚本降级
|
||
version_gt() {
|
||
[ "$1" = "$2" ] && return 1
|
||
local IFS=. i x y
|
||
local a=($1) b=($2)
|
||
for ((i = 0; i < ${#a[@]} || i < ${#b[@]}; i++)); do
|
||
x=${a[i]:-0}; y=${b[i]:-0}
|
||
x=${x//[!0-9]/}; y=${y//[!0-9]/}
|
||
x=${x:-0}; y=${y:-0}
|
||
[ "$x" -gt "$y" ] && return 0
|
||
[ "$x" -lt "$y" ] && return 1
|
||
done
|
||
return 1
|
||
}
|
||
|
||
|
||
|
||
|
||
ip_address() {
|
||
ipv4_address=$(curl -s ipv4.ip.sb)
|
||
ipv6_address=$(curl -s --max-time 1 ipv6.ip.sb)
|
||
}
|
||
|
||
|
||
|
||
package_installed() {
|
||
[ "$(dpkg-query -W -f='${db:Status-Abbrev}' "$1" 2>/dev/null)" = "ii " ]
|
||
}
|
||
|
||
install() {
|
||
if [ $# -eq 0 ]; then
|
||
echo "未提供软件包参数!"
|
||
return 1
|
||
fi
|
||
|
||
local package
|
||
local missing_packages=()
|
||
for package in "$@"; do
|
||
if package_installed "$package"; then
|
||
echo "$package 已经安装。"
|
||
else
|
||
missing_packages+=("$package")
|
||
fi
|
||
done
|
||
|
||
[ "${#missing_packages[@]}" -gt 0 ] || return 0
|
||
if ! command -v apt-get &>/dev/null; then
|
||
err_msg "未找到 apt-get,当前系统环境不受支持。"
|
||
return 1
|
||
fi
|
||
|
||
echo "正在安装: ${missing_packages[*]}"
|
||
if apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y -- "${missing_packages[@]}"; then
|
||
return 0
|
||
fi
|
||
err_msg "安装失败: ${missing_packages[*]}"
|
||
return 1
|
||
}
|
||
|
||
|
||
remove() {
|
||
if [ $# -eq 0 ]; then
|
||
echo "未提供软件包参数!"
|
||
return 1
|
||
fi
|
||
|
||
if ! command -v apt-get &>/dev/null; then
|
||
err_msg "未找到 apt-get,当前系统环境不受支持。"
|
||
return 1
|
||
fi
|
||
|
||
echo "正在卸载: $*"
|
||
DEBIAN_FRONTEND=noninteractive apt-get purge -y -- "$@"
|
||
}
|
||
|
||
|
||
break_end() {
|
||
echo -e "${lv}操作完成${bai}"
|
||
echo "按任意键继续..."
|
||
read -n 1 -s -r -p ""
|
||
echo ""
|
||
clear
|
||
}
|
||
|
||
# 用当前脚本自身替换进程,回到主菜单。
|
||
# 用 exec 而非再开子进程,既不依赖 /usr/local/bin/k 是否存在(非 root 时 cp 会失败),
|
||
# 也避免每次“返回主菜单”都叠加一层进程。
|
||
kejilion() {
|
||
exec bash "$SCRIPT_PATH"
|
||
}
|
||
|
||
install_add_docker() {
|
||
local installer
|
||
installer=$(mktemp) || return 1
|
||
if ! download_file "https://get.docker.com" "$installer" ||
|
||
! sh "$installer"; then
|
||
rm -f "$installer"
|
||
err_msg "Docker 安装失败。"
|
||
return 1
|
||
fi
|
||
rm -f "$installer"
|
||
if ! systemctl enable --now docker; then
|
||
err_msg "Docker 服务启动失败。"
|
||
return 1
|
||
fi
|
||
sleep 2
|
||
}
|
||
|
||
|
||
install_docker() {
|
||
if ! command -v docker &>/dev/null; then
|
||
install_add_docker || return 1
|
||
elif ! docker info >/dev/null 2>&1; then
|
||
if ! systemctl enable --now docker || ! docker info >/dev/null 2>&1; then
|
||
err_msg "Docker 已安装,但守护进程无法启动。"
|
||
return 1
|
||
fi
|
||
fi
|
||
echo "Docker环境已经就绪"
|
||
}
|
||
|
||
|
||
add_swap() {
|
||
# 先校验大小,避免把已有的 /swapfile 删掉之后才发现参数不合法
|
||
if ! [[ "$new_swap" =~ ^[0-9]+$ ]] || [ "$new_swap" -le 0 ]; then
|
||
err_msg "虚拟内存大小必须是大于 0 的整数(单位 MB),当前输入: '${new_swap}'"
|
||
return 1
|
||
fi
|
||
|
||
# 先完整创建新文件,成功后再替换旧 swapfile,避免磁盘不足时丢失原配置
|
||
local new_file=/swapfile.new old_file=/swapfile.old old_swap_active=0 fstab_tmp
|
||
if [ -e "$old_file" ]; then
|
||
err_msg "检测到未处理的恢复文件 $old_file,请先人工确认后再重试。"
|
||
return 1
|
||
fi
|
||
if [ -e /swapfile ] && [ ! -f /swapfile ]; then
|
||
err_msg "检测到 /swapfile 不是普通文件,已取消调整。"
|
||
return 1
|
||
fi
|
||
rm -f "$new_file"
|
||
if ! dd if=/dev/zero of="$new_file" bs=1M count="$new_swap" status=none ||
|
||
! chmod 600 "$new_file" ||
|
||
! mkswap "$new_file" >/dev/null; then
|
||
err_msg "创建新 swapfile 失败,请检查磁盘剩余空间。"
|
||
rm -f "$new_file"
|
||
return 1
|
||
fi
|
||
|
||
if awk 'NR > 1 && $1 == "/swapfile" { found = 1 } END { exit !found }' /proc/swaps; then
|
||
old_swap_active=1
|
||
if ! swapoff /swapfile; then
|
||
err_msg "无法停用当前 swapfile,已保留原配置。"
|
||
rm -f "$new_file"
|
||
return 1
|
||
fi
|
||
fi
|
||
if [ -f /swapfile ] && ! mv /swapfile "$old_file"; then
|
||
err_msg "无法备份当前 swapfile,已保留原配置。"
|
||
rm -f "$new_file"
|
||
[ "$old_swap_active" -eq 0 ] || swapon /swapfile 2>/dev/null
|
||
return 1
|
||
fi
|
||
if mv "$new_file" /swapfile && swapon /swapfile; then
|
||
:
|
||
else
|
||
err_msg "启用新 swapfile 失败,正在恢复原文件。"
|
||
rm -f "$new_file"
|
||
swapoff /swapfile 2>/dev/null
|
||
rm -f /swapfile
|
||
if [ -f "$old_file" ]; then
|
||
mv "$old_file" /swapfile
|
||
[ "$old_swap_active" -eq 0 ] || swapon /swapfile 2>/dev/null
|
||
fi
|
||
return 1
|
||
fi
|
||
|
||
# 原子更新 fstab,并把重复的 /swapfile 记录归并为一条。
|
||
fstab_tmp=$(mktemp /etc/.fstab.kejilion.XXXXXX) || true
|
||
if [ -z "$fstab_tmp" ] ||
|
||
! awk '
|
||
$1 == "/swapfile" {
|
||
if (!seen) print "/swapfile swap swap defaults 0 0"
|
||
seen = 1
|
||
next
|
||
}
|
||
{ print }
|
||
END { if (!seen) print "/swapfile swap swap defaults 0 0" }
|
||
' /etc/fstab > "$fstab_tmp" ||
|
||
! chmod --reference=/etc/fstab "$fstab_tmp" 2>/dev/null ||
|
||
! chown --reference=/etc/fstab "$fstab_tmp" 2>/dev/null ||
|
||
! mv -f "$fstab_tmp" /etc/fstab; then
|
||
[ -z "$fstab_tmp" ] || rm -f "$fstab_tmp"
|
||
err_msg "写入 /etc/fstab 失败,正在恢复原 swapfile。"
|
||
swapoff /swapfile 2>/dev/null
|
||
rm -f /swapfile
|
||
if [ -f "$old_file" ]; then
|
||
mv "$old_file" /swapfile
|
||
[ "$old_swap_active" -eq 0 ] || swapon /swapfile 2>/dev/null
|
||
fi
|
||
return 1
|
||
fi
|
||
if ! rm -f "$old_file"; then
|
||
err_msg "新 swapfile 已启用,但无法删除恢复文件 $old_file。"
|
||
return 1
|
||
fi
|
||
|
||
echo -e "swapfile 已设置为${huang}${new_swap}${bai}MB(若系统另有 swap 分区,总量会大于该值)"
|
||
}
|
||
|
||
# 兜底场景专用:只在现有 swap 不足时才调整。
|
||
# 跑测试/装内核前的 swap 只是给低内存机器补位,直接调 add_swap 会把已有的
|
||
# 4G swap 缩成 1G。菜单里用户显式指定大小的地方仍应直接调 add_swap,
|
||
# 否则就无法主动调小。
|
||
ensure_swap() {
|
||
local current
|
||
current=$(LC_ALL=C free -m | awk '/^Swap:/{print $2}')
|
||
current=${current:-0}
|
||
if [[ "$current" =~ ^[0-9]+$ ]] && [[ "$new_swap" =~ ^[0-9]+$ ]] && [ "$current" -ge "$new_swap" ]; then
|
||
echo "当前虚拟内存 ${current}MB 已满足需求(目标 ${new_swap}MB),跳过调整。"
|
||
return 0
|
||
fi
|
||
add_swap
|
||
}
|
||
|
||
f2b_status() {
|
||
docker restart fail2ban
|
||
sleep 3
|
||
docker exec -it fail2ban fail2ban-client status
|
||
}
|
||
|
||
f2b_status_xxx() {
|
||
docker exec -it fail2ban fail2ban-client status "$xxx"
|
||
}
|
||
|
||
# 等待容器把配置目录创建出来,原先固定 sleep 3 在慢机器上会导致
|
||
# cd 失败、配置文件被写到错误的目录
|
||
f2b_wait_config_dir() {
|
||
local dir="$1" i
|
||
for i in $(seq 1 30); do
|
||
[ -d "$dir" ] && return 0
|
||
sleep 1
|
||
done
|
||
err_msg "等待 fail2ban 配置目录超时: $dir"
|
||
return 1
|
||
}
|
||
|
||
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 \
|
||
--restart unless-stopped \
|
||
lscr.io/linuxserver/fail2ban:latest || {
|
||
err_msg "Fail2Ban 容器启动失败。"
|
||
return 1
|
||
}
|
||
|
||
local f2b_url="${GITHUB_RAW_URL}/kejilion/config/main/fail2ban"
|
||
local jail_d=/path/to/fail2ban/config/fail2ban/jail.d
|
||
|
||
f2b_wait_config_dir "$jail_d" || return 1
|
||
install rsyslog || return 1
|
||
systemctl enable --now rsyslog || return 1
|
||
download_file "${f2b_url}/linux-ssh.conf" "${jail_d}/linux-ssh.conf" || return 1
|
||
}
|
||
|
||
f2b_sshd() {
|
||
xxx=linux-sshd
|
||
f2b_status_xxx
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
server_reboot() {
|
||
|
||
read -p "$(echo -e "${huang}现在重启服务器吗?(Y/N): ${bai}")" rboot
|
||
case "$rboot" in
|
||
[Yy])
|
||
echo "已重启"
|
||
reboot
|
||
;;
|
||
[Nn])
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 Y 或 N。"
|
||
;;
|
||
esac
|
||
|
||
|
||
}
|
||
|
||
output_status() {
|
||
# 跳过 lo 等回环/虚拟接口,否则本机内部流量会被计入总量,
|
||
# 导致“限流自动关机”提前触发
|
||
output=$(awk 'BEGIN { rx_total = 0; tx_total = 0 }
|
||
NR > 2 {
|
||
# 以冒号切分,兼容接口名较长时数值与冒号粘连的内核输出格式
|
||
idx = index($0, ":");
|
||
if (idx == 0) next;
|
||
iface = substr($0, 1, idx - 1); gsub(/[ \t]/, "", iface);
|
||
if (iface == "lo" || iface ~ /^(docker|veth|br-|virbr|tun|tap|wg|tailscale|zt)/) next;
|
||
split(substr($0, idx + 1), f, " ");
|
||
rx_total += f[1]; tx_total += f[9]
|
||
}
|
||
END {
|
||
rx_units = "Bytes";
|
||
tx_units = "Bytes";
|
||
if (rx_total > 1024) { rx_total /= 1024; rx_units = "KB"; }
|
||
if (rx_total > 1024) { rx_total /= 1024; rx_units = "MB"; }
|
||
if (rx_total > 1024) { rx_total /= 1024; rx_units = "GB"; }
|
||
|
||
if (tx_total > 1024) { tx_total /= 1024; tx_units = "KB"; }
|
||
if (tx_total > 1024) { tx_total /= 1024; tx_units = "MB"; }
|
||
if (tx_total > 1024) { tx_total /= 1024; tx_units = "GB"; }
|
||
|
||
printf("总接收: %.2f %s\n总发送: %.2f %s\n", rx_total, rx_units, tx_total, tx_units);
|
||
}' /proc/net/dev)
|
||
|
||
}
|
||
|
||
|
||
current_timezone() {
|
||
timedatectl show --property=Timezone --value 2>/dev/null
|
||
}
|
||
|
||
|
||
set_timedate() {
|
||
local shiqu="$1"
|
||
timedatectl set-timezone "$shiqu"
|
||
}
|
||
|
||
|
||
|
||
|
||
linux_update() {
|
||
if ! command -v apt-get &>/dev/null; then
|
||
err_msg "未找到 apt-get,当前系统环境不受支持。"
|
||
return 1
|
||
fi
|
||
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y
|
||
}
|
||
|
||
|
||
|
||
linux_clean() {
|
||
local old_pkgs
|
||
if ! command -v apt-get &>/dev/null; then
|
||
err_msg "未找到 apt-get,当前系统环境不受支持。"
|
||
return 1
|
||
fi
|
||
|
||
DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y
|
||
apt-get clean
|
||
old_pkgs=$(dpkg -l | awk '/^rc/ {print $2}')
|
||
[ -n "$old_pkgs" ] && DEBIAN_FRONTEND=noninteractive apt-get purge -y $old_pkgs
|
||
journalctl --rotate
|
||
journalctl --vacuum-time=1s
|
||
journalctl --vacuum-size=50M
|
||
# 不自动批量删除内核,保留当前内核和可启动的回退版本
|
||
}
|
||
|
||
|
||
|
||
|
||
# 只增量修改指定的内核参数,不再用 > 整体覆盖 /etc/sysctl.conf,
|
||
# 否则用户原有的全部内核调优配置都会丢失
|
||
set_sysctl() {
|
||
local key="$1" value="$2" conf=/etc/sysctl.conf escaped_key
|
||
touch "$conf" || return 1
|
||
escaped_key=${key//./\\.}
|
||
if grep -qE "^[[:space:]]*#?[[:space:]]*${escaped_key}[[:space:]]*=" "$conf"; then
|
||
sed -i "s|^[[:space:]]*#\?[[:space:]]*${escaped_key}[[:space:]]*=.*|${key}=${value}|" "$conf"
|
||
else
|
||
printf '%s=%s\n' "$key" "$value" >> "$conf"
|
||
fi
|
||
}
|
||
|
||
set_dns() {
|
||
|
||
# 检查机器是否有IPv6地址
|
||
ipv6_available=0
|
||
if [[ $(ip -6 addr | grep -c "inet6") -gt 0 ]]; then
|
||
ipv6_available=1
|
||
fi
|
||
|
||
echo "nameserver $dns1_ipv4" > /etc/resolv.conf
|
||
echo "nameserver $dns2_ipv4" >> /etc/resolv.conf
|
||
|
||
|
||
if [[ $ipv6_available -eq 1 ]]; then
|
||
echo "nameserver $dns1_ipv6" >> /etc/resolv.conf
|
||
echo "nameserver $dns2_ipv6" >> /etc/resolv.conf
|
||
fi
|
||
|
||
echo "DNS地址已更新"
|
||
echo "------------------------"
|
||
cat /etc/resolv.conf
|
||
echo "------------------------"
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
root_use() {
|
||
clear
|
||
[ "$EUID" -ne 0 ] && echo -e "${huang}请注意,该功能需要root用户才能运行!${bai}" && break_end && kejilion
|
||
}
|
||
|
||
|
||
|
||
|
||
# 探测 CPU 的 x86-64 微架构等级(v1~v4),决定安装哪个 xanmod 包。
|
||
# 下载到临时目录,避免在当前工作目录留下文件、或被 rm -f 误删同名文件
|
||
xanmod_psabi_version() {
|
||
local tmpdir ver
|
||
tmpdir=$(mktemp -d) || return 1
|
||
if ! download_file "${GITHUB_RAW_URL}/kejilion/sh/main/check_x86-64_psabi.sh" "${tmpdir}/check.sh"; then
|
||
rm -rf "$tmpdir"
|
||
return 1
|
||
fi
|
||
chmod +x "${tmpdir}/check.sh"
|
||
# 用 -oE 而非 -oP,busybox grep 不支持 PCRE
|
||
ver=$("${tmpdir}/check.sh" | grep -oE 'x86-64-v[0-9]+' | grep -oE '[0-9]+$' | head -n 1)
|
||
rm -rf "$tmpdir"
|
||
[ -n "$ver" ] || return 1
|
||
echo "$ver"
|
||
}
|
||
|
||
xanmod_add_repo() {
|
||
local key_file key_tmp source_tmp
|
||
install gnupg || return 1
|
||
key_file=$(mktemp) || return 1
|
||
key_tmp=$(mktemp /usr/share/keyrings/.xanmod-archive-keyring.XXXXXX) || {
|
||
rm -f "$key_file"
|
||
return 1
|
||
}
|
||
if ! download_file "${GITHUB_RAW_URL}/kejilion/sh/main/archive.key" "$key_file" ||
|
||
! gpg --dearmor --yes -o "$key_tmp" "$key_file" ||
|
||
! chmod 644 "$key_tmp" ||
|
||
! mv -f "$key_tmp" /usr/share/keyrings/xanmod-archive-keyring.gpg; then
|
||
rm -f "$key_file" "$key_tmp"
|
||
err_msg "XanMod 签名密钥下载或导入失败。"
|
||
return 1
|
||
fi
|
||
rm -f "$key_file"
|
||
source_tmp=$(mktemp "${XANMOD_SOURCE_FILE}.XXXXXX") || return 1
|
||
if ! printf '%s\n' 'deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] https://deb.xanmod.org releases main' > "$source_tmp" ||
|
||
! chmod 644 "$source_tmp" ||
|
||
! mv -f "$source_tmp" "$XANMOD_SOURCE_FILE"; then
|
||
rm -f "$source_tmp"
|
||
err_msg "XanMod 软件源写入失败。"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
xanmod_install_kernel() {
|
||
local version
|
||
if ! version=$(xanmod_psabi_version); then
|
||
err_msg "无法探测 CPU 微架构等级,已中止内核安装。"
|
||
rm -f "$XANMOD_SOURCE_FILE"
|
||
return 1
|
||
fi
|
||
case "$version" in
|
||
1|2|3|4) ;;
|
||
*)
|
||
err_msg "CPU 微架构等级异常: $version"
|
||
rm -f "$XANMOD_SOURCE_FILE"
|
||
return 1
|
||
;;
|
||
esac
|
||
if ! apt-get update; then
|
||
err_msg "XanMod 软件源更新失败。"
|
||
rm -f "$XANMOD_SOURCE_FILE"
|
||
return 1
|
||
fi
|
||
if ! DEBIAN_FRONTEND=noninteractive apt-get install -y "linux-xanmod-x64v${version}"; then
|
||
err_msg "XanMod 内核安装失败(linux-xanmod-x64v${version})。"
|
||
rm -f "$XANMOD_SOURCE_FILE"
|
||
return 1
|
||
fi
|
||
rm -f "$XANMOD_SOURCE_FILE"
|
||
return 0
|
||
}
|
||
|
||
list_xanmod_packages() {
|
||
dpkg-query -W -f='${binary:Package}\t${db:Status-Abbrev}\n' 2>/dev/null |
|
||
awk -F '\t' '$2 == "ii " && $1 ~ /xanmod/ { print $1 }'
|
||
}
|
||
|
||
bbrv3() {
|
||
local fallback_kernel os_id arch
|
||
local -a xanmod_packages
|
||
root_use
|
||
if command -v dpkg-query &>/dev/null && list_xanmod_packages | grep -q .; then
|
||
while true; do
|
||
|
||
kernel_version=$(uname -r)
|
||
echo "您已安装xanmod的BBRv3内核"
|
||
echo "当前内核版本: $kernel_version"
|
||
|
||
echo ""
|
||
echo "内核管理"
|
||
echo "------------------------"
|
||
echo "1. 更新BBRv3内核 2. 卸载BBRv3内核"
|
||
echo "------------------------"
|
||
echo "0. 返回上一级选单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
|
||
case $sub_choice in
|
||
1)
|
||
# 先安装并写入启动菜单,避免更新失败后没有可启动的旧内核
|
||
if xanmod_add_repo && xanmod_install_kernel && update-grub; then
|
||
echo "XanMod内核已更新,旧内核已保留作为回退。重启后生效"
|
||
server_reboot
|
||
else
|
||
err_msg "XanMod 内核更新失败,旧内核保持不变。"
|
||
fi
|
||
;;
|
||
2)
|
||
xanmod_packages=()
|
||
case "$OS_ID" in
|
||
ubuntu) fallback_kernel=linux-generic ;;
|
||
debian) fallback_kernel=linux-image-amd64 ;;
|
||
esac
|
||
mapfile -t xanmod_packages < <(list_xanmod_packages)
|
||
if [ "${#xanmod_packages[@]}" -eq 0 ]; then
|
||
err_msg "未找到已安装的 XanMod 软件包。"
|
||
elif apt-get update &&
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y -- "$fallback_kernel" &&
|
||
DEBIAN_FRONTEND=noninteractive apt-get purge -y -- "${xanmod_packages[@]}" &&
|
||
update-grub; then
|
||
rm -f "$XANMOD_SOURCE_FILE"
|
||
echo "XanMod内核已卸载,并已确认系统回退内核可用。重启后生效"
|
||
server_reboot
|
||
else
|
||
err_msg "无法确认回退内核或卸载失败,已取消自动重启。"
|
||
fi
|
||
;;
|
||
0)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
|
||
*)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
|
||
esac
|
||
done
|
||
else
|
||
|
||
clear
|
||
echo "请备份数据,将为你升级Linux内核开启BBR3"
|
||
echo "官网介绍: https://xanmod.org/"
|
||
echo "------------------------------------------------"
|
||
echo "仅支持Debian/Ubuntu 仅支持x86_64架构"
|
||
echo "VPS是512M内存的,请提前添加1G虚拟内存,防止因内存不足失联!"
|
||
echo "------------------------------------------------"
|
||
read -p "确定继续吗?(Y/N): " choice
|
||
|
||
case "$choice" in
|
||
[Yy])
|
||
# 注意:这里必须用 return,不能用 break —— 当前不在任何循环内,
|
||
# break 只会报错并继续往下执行,导致在不支持的系统上照样改内核和 sysctl
|
||
if [ -r /etc/os-release ]; then
|
||
# 放到子 shell 里取值,避免污染当前 shell 的 ID/VERSION 等变量
|
||
os_id=$(. /etc/os-release && echo "$ID")
|
||
if [ "$os_id" != "debian" ] && [ "$os_id" != "ubuntu" ]; then
|
||
err_msg "当前环境不支持,仅支持Debian和Ubuntu系统"
|
||
return 1
|
||
fi
|
||
else
|
||
err_msg "无法确定操作系统类型"
|
||
return 1
|
||
fi
|
||
|
||
# 检查系统架构
|
||
arch=$(dpkg --print-architecture 2>/dev/null)
|
||
if [ "$arch" != "amd64" ]; then
|
||
err_msg "当前环境不支持,仅支持x86_64架构"
|
||
return 1
|
||
fi
|
||
|
||
new_swap=1024
|
||
ensure_swap || return 1
|
||
|
||
xanmod_add_repo || return 1
|
||
xanmod_install_kernel || return 1
|
||
update-grub || {
|
||
err_msg "更新启动菜单失败,已取消自动重启。"
|
||
return 1
|
||
}
|
||
|
||
# 步骤5:启用BBR3
|
||
if ! set_sysctl net.core.default_qdisc fq_pie ||
|
||
! set_sysctl net.ipv4.tcp_congestion_control bbr; then
|
||
err_msg "写入 BBR3 内核参数失败,已取消自动重启。"
|
||
return 1
|
||
fi
|
||
echo "XanMod内核安装并BBR3启用成功。重启后生效"
|
||
server_reboot
|
||
|
||
;;
|
||
[Nn])
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 Y 或 N。"
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
kejilion_sh() {
|
||
while true; do
|
||
clear
|
||
|
||
echo -e "${kjlan}_ _ ____ _ _ _ _ ____ _ _ "
|
||
echo "|_/ |___ | | | | | | |\ | "
|
||
echo "| \_ |___ _| | |___ | |__| | \| "
|
||
echo " "
|
||
echo -e "${kjlan}科技lion一键脚本工具 v$sh_v (仅支持 Ubuntu / Debian)${bai}"
|
||
echo -e "${kjlan}-输入${huang}k${kjlan}可快速启动此脚本-${bai}"
|
||
echo "------------------------"
|
||
echo "1. 系统信息查询"
|
||
echo "2. 系统更新"
|
||
echo "3. 系统清理"
|
||
echo "5. BBR管理 ▶"
|
||
echo "8. 测试脚本合集 ▶ "
|
||
echo "13. 系统工具 ▶ "
|
||
echo "------------------------"
|
||
echo "00. 脚本更新"
|
||
echo "------------------------"
|
||
echo "0. 退出脚本"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " choice
|
||
|
||
case $choice in
|
||
1)
|
||
clear
|
||
# 函数: 获取IPv4和IPv6地址
|
||
ip_address
|
||
|
||
if [ "$(uname -m)" == "x86_64" ]; then
|
||
cpu_info=$(cat /proc/cpuinfo | grep 'model name' | uniq | sed -e 's/model name[[:space:]]*: //')
|
||
else
|
||
cpu_info=$(lscpu | grep 'BIOS Model name' | awk -F': ' '{print $2}' | sed 's/^[ \t]*//')
|
||
fi
|
||
|
||
|
||
cpu_usage_percent=$(awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else printf "%.0f\n", (($2+$4-u1) * 100 / (t-t1))}' \
|
||
<(grep 'cpu ' /proc/stat) <(sleep 1; grep 'cpu ' /proc/stat))
|
||
|
||
|
||
cpu_cores=$(nproc)
|
||
|
||
mem_info=$(free -b | awk 'NR==2{printf "%.2f/%.2f MB (%.2f%%)", $3/1024/1024, $2/1024/1024, $3*100/$2}')
|
||
|
||
disk_info=$(df -h | awk '$NF=="/"{printf "%s/%s (%s)", $3, $2, $5}')
|
||
|
||
# 加超时,避免无外网时整个信息页卡住
|
||
country=$(curl -s --max-time 5 ipinfo.io/country)
|
||
city=$(curl -s --max-time 5 ipinfo.io/city)
|
||
|
||
isp_info=$(curl -s --max-time 5 ipinfo.io/org)
|
||
|
||
cpu_arch=$(uname -m)
|
||
|
||
hostname=$(hostname)
|
||
|
||
kernel_version=$(uname -r)
|
||
|
||
congestion_algorithm=$(sysctl -n net.ipv4.tcp_congestion_control)
|
||
queue_algorithm=$(sysctl -n net.core.default_qdisc)
|
||
|
||
# 尝试使用 lsb_release 获取系统信息
|
||
os_info=$(lsb_release -ds 2>/dev/null)
|
||
|
||
# 如果 lsb_release 命令失败,则尝试其他方法
|
||
if [ -z "$os_info" ]; then
|
||
# 检查常见的发行文件
|
||
if [ -f "/etc/os-release" ]; then
|
||
os_info=$(source /etc/os-release && echo "$PRETTY_NAME")
|
||
elif [ -f "/etc/debian_version" ]; then
|
||
os_info="Debian $(cat /etc/debian_version)"
|
||
else
|
||
os_info="Unknown"
|
||
fi
|
||
fi
|
||
|
||
output_status
|
||
|
||
current_time=$(date "+%Y-%m-%d %I:%M %p")
|
||
|
||
|
||
# 用 Swap 行定位而非固定行号;强制 C locale,否则中文系统下 free 的行标签
|
||
# 会被本地化,/^Swap:/ 匹配不到而恒显示 0MB/0MB
|
||
swap_used=$(LC_ALL=C free -m | awk '/^Swap:/{print $3}')
|
||
swap_total=$(LC_ALL=C free -m | awk '/^Swap:/{print $2}')
|
||
swap_used=${swap_used:-0}
|
||
swap_total=${swap_total:-0}
|
||
|
||
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}%)"
|
||
|
||
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)}')
|
||
|
||
timezone=$(current_timezone)
|
||
|
||
|
||
echo ""
|
||
echo "系统信息查询"
|
||
echo "------------------------"
|
||
echo "主机名: $hostname"
|
||
echo "运营商: $isp_info"
|
||
echo "------------------------"
|
||
echo "系统版本: $os_info"
|
||
echo "Linux版本: $kernel_version"
|
||
echo "------------------------"
|
||
echo "CPU架构: $cpu_arch"
|
||
echo "CPU型号: $cpu_info"
|
||
echo "CPU核心数: $cpu_cores"
|
||
echo "------------------------"
|
||
echo "CPU占用: $cpu_usage_percent%"
|
||
echo "物理内存: $mem_info"
|
||
echo "虚拟内存: $swap_info"
|
||
echo "硬盘占用: $disk_info"
|
||
echo "------------------------"
|
||
echo "$output"
|
||
echo "------------------------"
|
||
echo "网络拥堵算法: $congestion_algorithm $queue_algorithm"
|
||
echo "------------------------"
|
||
echo "公网IPv4地址: $ipv4_address"
|
||
echo "公网IPv6地址: $ipv6_address"
|
||
echo "------------------------"
|
||
echo "地理位置: $country $city"
|
||
echo "系统时区: $timezone"
|
||
echo "系统时间: $current_time"
|
||
echo "------------------------"
|
||
echo "系统运行时长: $runtime"
|
||
echo
|
||
|
||
;;
|
||
|
||
2)
|
||
clear
|
||
linux_update
|
||
;;
|
||
|
||
3)
|
||
clear
|
||
linux_clean
|
||
;;
|
||
|
||
5)
|
||
clear
|
||
tcpx_dir=$(mktemp -d) || {
|
||
err_msg "无法创建临时目录。"
|
||
echo "按任意键继续..."
|
||
read -n 1 -s -r -p ""
|
||
continue
|
||
}
|
||
if download_file "${GITHUB_RAW_URL}/ylx2016/Linux-NetSpeed/master/tcpx.sh" "${tcpx_dir}/tcpx.sh"; then
|
||
bash "${tcpx_dir}/tcpx.sh"
|
||
fi
|
||
rm -rf "$tcpx_dir"
|
||
;;
|
||
|
||
8)
|
||
while true; do
|
||
clear
|
||
echo "▶ 测试脚本合集"
|
||
echo ""
|
||
echo "----IP及解锁状态检测-----------"
|
||
echo "1. ChatGPT解锁状态检测"
|
||
echo "2. Region流媒体解锁测试"
|
||
echo "3. yeahwu流媒体解锁检测"
|
||
echo "4. xykt_IP质量体检脚本"
|
||
echo ""
|
||
echo "----网络线路测速-----------"
|
||
echo "11. besttrace三网回程延迟路由测试"
|
||
echo "12. mtr_trace三网回程线路测试"
|
||
echo "13. Superspeed三网测速"
|
||
echo "14. nxtrace快速回程测试脚本"
|
||
echo "15. nxtrace指定IP回程测试脚本"
|
||
echo "16. ludashi2020三网线路测试"
|
||
echo "17. i-abc多功能测速脚本"
|
||
echo ""
|
||
echo "----硬件性能测试----------"
|
||
echo "21. yabs性能测试"
|
||
echo "22. GB5 CPU性能测试脚本"
|
||
echo ""
|
||
echo "----综合性测试-----------"
|
||
echo "31. bench性能测试"
|
||
echo "32. spiritysdx融合怪测评"
|
||
echo ""
|
||
echo "------------------------"
|
||
echo "0. 返回主菜单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
|
||
case $sub_choice in
|
||
1)
|
||
clear
|
||
bash <(curl -Ls https://cdn.jsdelivr.net/gh/missuo/OpenAI-Checker/openai.sh)
|
||
;;
|
||
2)
|
||
clear
|
||
bash <(curl -L -s check.unlock.media)
|
||
;;
|
||
3)
|
||
clear
|
||
install wget
|
||
wget -qO- https://github.com/yeahwu/check/raw/main/check.sh | bash
|
||
;;
|
||
4)
|
||
clear
|
||
bash <(curl -Ls IP.Check.Place)
|
||
;;
|
||
11)
|
||
clear
|
||
install wget
|
||
wget -qO- git.io/besttrace | bash
|
||
;;
|
||
12)
|
||
clear
|
||
curl "${GITHUB_RAW_URL}/zhucaidan/mtr_trace/main/mtr_trace.sh" | bash
|
||
;;
|
||
13)
|
||
clear
|
||
bash <(curl -Lso- https://git.io/superspeed_uxh)
|
||
;;
|
||
14)
|
||
clear
|
||
curl nxtrace.org/nt |bash
|
||
nexttrace --fast-trace --tcp
|
||
;;
|
||
15)
|
||
clear
|
||
|
||
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 "湖南电信: 36.111.200.100"
|
||
echo "湖南联通: 42.48.16.100"
|
||
echo "湖南移动: 39.134.254.6"
|
||
echo "------------------------"
|
||
|
||
read -p "输入一个指定IP: " testip
|
||
curl nxtrace.org/nt |bash
|
||
nexttrace $testip
|
||
;;
|
||
|
||
16)
|
||
clear
|
||
curl "${GITHUB_RAW_URL}/ludashi2020/backtrace/main/install.sh" -sSf | sh
|
||
;;
|
||
|
||
17)
|
||
clear
|
||
bash <(curl -sL "${GITHUB_RAW_URL}/i-abc/Speedtest/main/speedtest.sh")
|
||
;;
|
||
|
||
|
||
21)
|
||
clear
|
||
# swap 只是低内存机器的兜底,失败不应中断测试
|
||
new_swap=1024
|
||
ensure_swap || echo -e "${huang}未能调整虚拟内存,将直接开始测试。${bai}"
|
||
curl -sL yabs.sh | bash -s -- -i -5
|
||
;;
|
||
22)
|
||
clear
|
||
new_swap=1024
|
||
ensure_swap || echo -e "${huang}未能调整虚拟内存,将直接开始测试。${bai}"
|
||
bash <(curl -sL "${GITHUB_RAW_URL}/i-abc/GB5/main/gb5-test.sh")
|
||
;;
|
||
|
||
31)
|
||
clear
|
||
curl -Lso- bench.sh | bash
|
||
;;
|
||
32)
|
||
clear
|
||
curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh
|
||
;;
|
||
|
||
|
||
0)
|
||
kejilion
|
||
|
||
;;
|
||
*)
|
||
echo "无效的输入!"
|
||
;;
|
||
esac
|
||
break_end
|
||
|
||
done
|
||
;;
|
||
|
||
13)
|
||
while true; do
|
||
clear
|
||
echo "▶ 系统工具"
|
||
echo "------------------------"
|
||
echo "1. 设置脚本启动快捷键 2. 修改登录密码"
|
||
echo "7. 优化DNS地址 10. 切换优先ipv4/ipv6"
|
||
echo "------------------------"
|
||
echo "11. 查看端口占用状态 12. 修改虚拟内存大小"
|
||
echo "13. 用户管理 14. 用户/密码生成器"
|
||
echo "15. 系统时区调整 16. 设置BBR3加速"
|
||
echo "18. 修改主机名"
|
||
echo "19. 切换系统更新源 20. 定时任务管理"
|
||
echo "------------------------"
|
||
echo "21. 本机host解析 22. fail2banSSH防御程序"
|
||
echo "23. 限流自动关机"
|
||
echo "------------------------"
|
||
echo "0. 返回主菜单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
|
||
case $sub_choice in
|
||
1)
|
||
clear
|
||
read -r -p "请输入你的快捷按键: " kuaijiejian
|
||
if ! [[ "$kuaijiejian" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
|
||
err_msg "快捷键只能包含字母、数字和下划线,且不能以数字开头。"
|
||
else
|
||
sed -i "/^alias ${kuaijiejian}=/d" "$HOME/.bashrc" 2>/dev/null
|
||
printf 'alias %s=%q\n' "$kuaijiejian" "bash $SCRIPT_PATH" >> "$HOME/.bashrc"
|
||
echo "快捷键已设置,请重新登录终端或执行 source ~/.bashrc 后生效"
|
||
fi
|
||
;;
|
||
|
||
2)
|
||
clear
|
||
echo "设置你的登录密码"
|
||
passwd
|
||
;;
|
||
7)
|
||
root_use
|
||
echo "当前DNS地址"
|
||
echo "------------------------"
|
||
cat /etc/resolv.conf
|
||
echo "------------------------"
|
||
echo ""
|
||
# 询问用户是否要优化DNS设置
|
||
read -p "是否要设置DNS地址?(y/n): " choice
|
||
|
||
if [ "$choice" == "y" ]; then
|
||
read -p "1. 国外DNS优化 2. 国内DNS优化 0. 退出 : " Limiting
|
||
|
||
case "$Limiting" in
|
||
1)
|
||
|
||
dns1_ipv4="1.1.1.1"
|
||
dns2_ipv4="8.8.8.8"
|
||
dns1_ipv6="2606:4700:4700::1111"
|
||
dns2_ipv6="2001:4860:4860::8888"
|
||
set_dns
|
||
;;
|
||
|
||
2)
|
||
dns1_ipv4="223.5.5.5"
|
||
dns2_ipv4="183.60.83.19"
|
||
dns1_ipv6="2400:3200::1"
|
||
dns2_ipv6="2400:da00::6666"
|
||
set_dns
|
||
;;
|
||
0)
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 0、1 或 2。"
|
||
;;
|
||
esac
|
||
|
||
|
||
else
|
||
echo "DNS设置未更改"
|
||
fi
|
||
|
||
;;
|
||
|
||
10)
|
||
root_use
|
||
# 内核未编译 IPv6 时该 sysctl 不存在,取值为空会导致下面的整数比较报错
|
||
ipv6_disabled=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)
|
||
if [ -z "$ipv6_disabled" ]; then
|
||
err_msg "当前内核未启用 IPv6,无法切换网络优先级。"
|
||
break
|
||
fi
|
||
|
||
echo ""
|
||
if [ "$ipv6_disabled" -eq 1 ]; then
|
||
echo "当前网络优先级设置: IPv4 优先"
|
||
else
|
||
echo "当前网络优先级设置: IPv6 优先"
|
||
fi
|
||
echo "------------------------"
|
||
|
||
echo ""
|
||
echo "切换的网络优先级"
|
||
echo "------------------------"
|
||
echo "1. IPv4 优先 2. IPv6 优先"
|
||
echo "------------------------"
|
||
read -p "选择优先的网络: " choice
|
||
|
||
case $choice in
|
||
1)
|
||
sysctl -w net.ipv6.conf.all.disable_ipv6=1 > /dev/null 2>&1
|
||
echo "已切换为 IPv4 优先"
|
||
;;
|
||
2)
|
||
sysctl -w net.ipv6.conf.all.disable_ipv6=0 > /dev/null 2>&1
|
||
echo "已切换为 IPv6 优先"
|
||
;;
|
||
*)
|
||
echo "无效的选择"
|
||
;;
|
||
|
||
esac
|
||
;;
|
||
|
||
11)
|
||
clear
|
||
if command -v ss &>/dev/null; then
|
||
ss -tulnape
|
||
elif command -v netstat &>/dev/null; then
|
||
netstat -tulnape
|
||
else
|
||
install iproute2 && ss -tulnape
|
||
fi
|
||
;;
|
||
|
||
12)
|
||
|
||
|
||
root_use
|
||
# 获取当前交换空间信息(强制 C locale,避免中文系统下标签被本地化)
|
||
swap_used=$(LC_ALL=C free -m | awk '/^Swap:/{print $3}')
|
||
swap_total=$(LC_ALL=C free -m | awk '/^Swap:/{print $2}')
|
||
swap_used=${swap_used:-0}
|
||
swap_total=${swap_total:-0}
|
||
|
||
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
|
||
add_swap
|
||
|
||
;;
|
||
[Nn])
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 Y 或 N。"
|
||
;;
|
||
esac
|
||
;;
|
||
|
||
13)
|
||
while true; do
|
||
root_use
|
||
|
||
# 显示所有用户、用户权限、用户组和是否在sudoers中
|
||
echo "用户列表"
|
||
echo "----------------------------------------------------------------------------"
|
||
printf "%-20s %-30s %-20s %-10s\n" "用户名" "主目录" "用户组" "sudo权限"
|
||
# /etc/passwd 共 7 个字段: 用户名:密码:UID:GID:备注:主目录:登录shell
|
||
while IFS=: read -r username _ userid groupid _ homedir shell; do
|
||
user_groups=$(groups "$username" 2>/dev/null | cut -d: -f2- | sed 's/^ *//')
|
||
if sudo -n -lU "$username" 2>/dev/null | grep -q '(ALL'; then
|
||
sudo_status="Yes"
|
||
else
|
||
sudo_status="No"
|
||
fi
|
||
printf "%-20s %-30s %-20s %-10s\n" "$username" "$homedir" "$user_groups" "$sudo_status"
|
||
done < /etc/passwd
|
||
|
||
|
||
echo ""
|
||
echo "账户操作"
|
||
echo "------------------------"
|
||
echo "1. 创建普通账户 2. 创建高级账户"
|
||
echo "------------------------"
|
||
echo "3. 授予 sudo 权限 4. 撤销脚本授予的 sudo 权限"
|
||
echo "------------------------"
|
||
echo "5. 删除账号"
|
||
echo "------------------------"
|
||
echo "0. 返回上一级选单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
|
||
case $sub_choice in
|
||
1)
|
||
# 提示用户输入新用户名
|
||
read -p "请输入新用户名: " new_username
|
||
|
||
if ! valid_username "$new_username"; then
|
||
err_msg "用户名格式不合法。"
|
||
elif id "$new_username" &>/dev/null; then
|
||
err_msg "用户 $new_username 已存在。"
|
||
elif useradd -m -s /bin/bash "$new_username"; then
|
||
if passwd "$new_username"; then
|
||
echo "操作已完成。"
|
||
else
|
||
if userdel -r "$new_username" >/dev/null 2>&1; then
|
||
err_msg "密码设置失败,已回滚新建账户。"
|
||
else
|
||
err_msg "密码设置失败,且新建账户无法自动回滚,请人工检查: $new_username"
|
||
fi
|
||
fi
|
||
else
|
||
err_msg "账户创建失败。"
|
||
fi
|
||
;;
|
||
|
||
2)
|
||
# 提示用户输入新用户名
|
||
read -p "请输入新用户名: " new_username
|
||
|
||
if ! valid_username "$new_username"; then
|
||
err_msg "用户名格式不合法。"
|
||
elif id "$new_username" &>/dev/null; then
|
||
err_msg "用户 $new_username 已存在。"
|
||
elif useradd -m -s /bin/bash "$new_username"; then
|
||
if passwd "$new_username" && grant_sudo "$new_username"; then
|
||
echo "操作已完成。"
|
||
else
|
||
rm -f "/etc/sudoers.d/kejilion-$new_username"
|
||
if userdel -r "$new_username" >/dev/null 2>&1; then
|
||
err_msg "密码设置或 sudo 授权失败,已回滚新建账户。"
|
||
else
|
||
err_msg "密码设置或 sudo 授权失败,且新建账户无法自动回滚,请人工检查: $new_username"
|
||
fi
|
||
fi
|
||
else
|
||
err_msg "账户创建失败,未授予 sudo 权限。"
|
||
fi
|
||
|
||
;;
|
||
3)
|
||
read -p "请输入用户名: " username
|
||
if ! id "$username" &>/dev/null; then
|
||
err_msg "用户 $username 不存在。"
|
||
elif ! valid_existing_username "$username"; then
|
||
err_msg "用户名格式不合法。"
|
||
elif grant_sudo "$username"; then
|
||
echo "操作已完成。"
|
||
fi
|
||
;;
|
||
4)
|
||
read -p "请输入用户名: " username
|
||
if revoke_sudo "$username"; then
|
||
echo "操作已完成。"
|
||
fi
|
||
|
||
;;
|
||
5)
|
||
read -p "请输入要删除的用户名: " username
|
||
if [ "$username" = "root" ]; then
|
||
err_msg "不允许删除 root 用户。"
|
||
elif ! id "$username" &>/dev/null; then
|
||
err_msg "用户 $username 不存在。"
|
||
elif ! valid_existing_username "$username"; then
|
||
err_msg "用户名格式不合法。"
|
||
elif [ "$username" = "$(id -un)" ]; then
|
||
err_msg "不允许删除当前正在运行脚本的用户。"
|
||
else
|
||
sudo_rule="$username ALL=(ALL:ALL) ALL"
|
||
if { [ ! -e "/etc/sudoers.d/kejilion-$username" ] &&
|
||
! grep -Fqx -- "$sudo_rule" /etc/sudoers; } || revoke_sudo "$username"; then
|
||
if userdel -r "$username"; then
|
||
echo "用户 $username 已删除。"
|
||
else
|
||
err_msg "删除用户 $username 失败。"
|
||
fi
|
||
else
|
||
err_msg "无法安全清理用户的 sudo 规则,已取消删除。"
|
||
fi
|
||
fi
|
||
;;
|
||
|
||
0)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
|
||
*)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
esac
|
||
done
|
||
;;
|
||
|
||
14)
|
||
clear
|
||
|
||
echo "随机用户名"
|
||
echo "------------------------"
|
||
for i in {1..5}; do
|
||
username="user$(< /dev/urandom tr -dc _a-z0-9 | head -c6)"
|
||
echo "随机用户名 $i: $username"
|
||
done
|
||
|
||
echo ""
|
||
echo "随机姓名"
|
||
echo "------------------------"
|
||
first_names=("John" "Jane" "Michael" "Emily" "David" "Sophia" "William" "Olivia" "James" "Emma" "Ava" "Liam" "Mia" "Noah" "Isabella")
|
||
last_names=("Smith" "Johnson" "Brown" "Davis" "Wilson" "Miller" "Jones" "Garcia" "Martinez" "Williams" "Lee" "Gonzalez" "Rodriguez" "Hernandez")
|
||
|
||
# 生成5个随机用户姓名
|
||
for i in {1..5}; do
|
||
first_name_index=$((RANDOM % ${#first_names[@]}))
|
||
last_name_index=$((RANDOM % ${#last_names[@]}))
|
||
user_name="${first_names[$first_name_index]} ${last_names[$last_name_index]}"
|
||
echo "随机用户姓名 $i: $user_name"
|
||
done
|
||
|
||
echo ""
|
||
echo "随机UUID"
|
||
echo "------------------------"
|
||
for i in {1..5}; do
|
||
uuid=$(cat /proc/sys/kernel/random/uuid)
|
||
echo "随机UUID $i: $uuid"
|
||
done
|
||
|
||
echo ""
|
||
echo "16位随机密码"
|
||
echo "------------------------"
|
||
for i in {1..5}; do
|
||
password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c16)
|
||
echo "随机密码 $i: $password"
|
||
done
|
||
|
||
echo ""
|
||
echo "32位随机密码"
|
||
echo "------------------------"
|
||
for i in {1..5}; do
|
||
password=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c32)
|
||
echo "随机密码 $i: $password"
|
||
done
|
||
echo ""
|
||
|
||
;;
|
||
|
||
15)
|
||
root_use
|
||
while true; do
|
||
|
||
echo "系统时间信息"
|
||
|
||
# 获取当前系统时区
|
||
timezone=$(current_timezone)
|
||
|
||
# 获取当前系统时间
|
||
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
||
|
||
# 显示时区和时间
|
||
echo "当前系统时区:$timezone"
|
||
echo "当前系统时间:$current_time"
|
||
|
||
echo ""
|
||
echo "时区切换"
|
||
echo "亚洲------------------------"
|
||
echo "1. 中国上海时间 2. 中国香港时间"
|
||
echo "3. 日本东京时间 4. 韩国首尔时间"
|
||
echo "5. 新加坡时间 6. 印度加尔各答时间"
|
||
echo "7. 阿联酋迪拜时间 8. 澳大利亚悉尼时间"
|
||
echo "欧洲------------------------"
|
||
echo "11. 英国伦敦时间 12. 法国巴黎时间"
|
||
echo "13. 德国柏林时间 14. 俄罗斯莫斯科时间"
|
||
echo "15. 荷兰尤特赖赫特时间 16. 西班牙马德里时间"
|
||
echo "美洲------------------------"
|
||
echo "21. 美国西部时间 22. 美国东部时间"
|
||
echo "23. 加拿大时间 24. 墨西哥时间"
|
||
echo "25. 巴西时间 26. 阿根廷时间"
|
||
echo "------------------------"
|
||
echo "0. 返回上一级选单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
|
||
|
||
case $sub_choice in
|
||
1) set_timedate Asia/Shanghai ;;
|
||
2) set_timedate Asia/Hong_Kong ;;
|
||
3) set_timedate Asia/Tokyo ;;
|
||
4) set_timedate Asia/Seoul ;;
|
||
5) set_timedate Asia/Singapore ;;
|
||
6) set_timedate Asia/Kolkata ;;
|
||
7) set_timedate Asia/Dubai ;;
|
||
8) set_timedate Australia/Sydney ;;
|
||
11) set_timedate Europe/London ;;
|
||
12) set_timedate Europe/Paris ;;
|
||
13) set_timedate Europe/Berlin ;;
|
||
14) set_timedate Europe/Moscow ;;
|
||
15) set_timedate Europe/Amsterdam ;;
|
||
16) set_timedate Europe/Madrid ;;
|
||
21) set_timedate America/Los_Angeles ;;
|
||
22) set_timedate America/New_York ;;
|
||
23) set_timedate America/Vancouver ;;
|
||
24) set_timedate America/Mexico_City ;;
|
||
25) set_timedate America/Sao_Paulo ;;
|
||
26) set_timedate America/Argentina/Buenos_Aires ;;
|
||
0) break ;; # 跳出循环,退出菜单
|
||
*) break ;; # 跳出循环,退出菜单
|
||
esac
|
||
done
|
||
;;
|
||
|
||
16)
|
||
bbrv3
|
||
;;
|
||
|
||
18)
|
||
root_use
|
||
current_hostname=$(hostname)
|
||
echo "当前主机名: $current_hostname"
|
||
read -p "是否要更改主机名?(y/n): " answer
|
||
if [[ "${answer,,}" == "y" ]]; then
|
||
# 获取新的主机名
|
||
read -p "请输入新的主机名: " new_hostname
|
||
if valid_hostname "$new_hostname"; then
|
||
if hostnamectl set-hostname "$new_hostname"; then
|
||
systemctl restart systemd-hostnamed
|
||
echo "主机名已更改为: $new_hostname"
|
||
else
|
||
err_msg "主机名修改失败。"
|
||
fi
|
||
else
|
||
err_msg "主机名格式不合法。未更改主机名。"
|
||
fi
|
||
else
|
||
echo "未更改主机名。"
|
||
fi
|
||
;;
|
||
|
||
19)
|
||
root_use
|
||
# 直接复用脚本开头已解析好的 OS_ID,避免 source /etc/os-release
|
||
# 把 ID/VERSION/PRETTY_NAME 等一并注入当前 shell
|
||
|
||
case "$(dpkg --print-architecture 2>/dev/null)" in
|
||
amd64|i386)
|
||
aliyun_ubuntu_source="https://mirrors.aliyun.com/ubuntu/"
|
||
official_ubuntu_source="https://archive.ubuntu.com/ubuntu/"
|
||
official_ubuntu_security_source="https://security.ubuntu.com/ubuntu/"
|
||
;;
|
||
*)
|
||
aliyun_ubuntu_source="https://mirrors.aliyun.com/ubuntu-ports/"
|
||
official_ubuntu_source="https://ports.ubuntu.com/ubuntu-ports/"
|
||
official_ubuntu_security_source="$official_ubuntu_source"
|
||
;;
|
||
esac
|
||
aliyun_debian_source="https://mirrors.aliyun.com/debian/"
|
||
official_debian_source="https://deb.debian.org/debian/"
|
||
|
||
list_apt_source_files() {
|
||
local file pattern
|
||
case "$OS_ID" in
|
||
ubuntu) pattern='(([^./[:space:]]+\.)?archive\.ubuntu\.com|security\.ubuntu\.com|ports\.ubuntu\.com|mirrors\.aliyun\.com)/(ubuntu|ubuntu-ports)' ;;
|
||
debian) pattern='(debian\.org|debian\.net|aliyun\.com)/(debian|debian-security)' ;;
|
||
*) return 1 ;;
|
||
esac
|
||
for file in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
|
||
[ -f "$file" ] || continue
|
||
case "$file" in
|
||
*.sources)
|
||
grep -qE '^[[:space:]]*URIs:[[:space:]]*' "$file" &&
|
||
grep -qE "$pattern" "$file" && printf '%s\n' "$file"
|
||
;;
|
||
*)
|
||
grep -qE "^[[:space:]]*(deb|deb-src)[[:space:]].*${pattern}" "$file" && printf '%s\n' "$file"
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
mapfile -t apt_source_files < <(list_apt_source_files)
|
||
if [ "${#apt_source_files[@]}" -eq 0 ]; then
|
||
err_msg "未找到可识别的 APT 更新源配置。"
|
||
break
|
||
fi
|
||
|
||
backup_sources() {
|
||
local file backup created=()
|
||
for file in "${apt_source_files[@]}"; do
|
||
backup="${file}.kejilion.bak"
|
||
if [ -f "$backup" ] && [ ! -L "$backup" ]; then
|
||
# 已存在是预期结果而非错误:本函数只保存初始源,不覆盖
|
||
echo "初始备份已存在,跳过: $backup"
|
||
continue
|
||
elif [ -e "$backup" ] || [ -L "$backup" ]; then
|
||
err_msg "备份路径不是普通文件,已取消操作: $backup"
|
||
rm -f "${created[@]}"
|
||
return 1
|
||
fi
|
||
if cp -- "$file" "$backup"; then
|
||
created+=("$backup")
|
||
else
|
||
rm -f "$backup" "${created[@]}"
|
||
err_msg "备份更新源失败,已清理本次生成的备份。"
|
||
return 1
|
||
fi
|
||
done
|
||
echo "初始更新源备份已就绪。"
|
||
}
|
||
|
||
ensure_source_backup() {
|
||
local file
|
||
for file in "${apt_source_files[@]}"; do
|
||
if [ ! -f "${file}.kejilion.bak" ] || [ -L "${file}.kejilion.bak" ]; then
|
||
backup_sources || return 1
|
||
break
|
||
fi
|
||
done
|
||
for file in "${apt_source_files[@]}"; do
|
||
[ -f "${file}.kejilion.bak" ] && [ ! -L "${file}.kejilion.bak" ] || return 1
|
||
done
|
||
}
|
||
|
||
restore_initial_source() {
|
||
local file saved rollback_dir ready=1
|
||
rollback_dir=$(mktemp -d) || return 1
|
||
for file in "${apt_source_files[@]}"; do
|
||
saved="$rollback_dir/$(printf '%s' "$file" | sed 's|/|_|g')"
|
||
if [ ! -f "${file}.kejilion.bak" ] || [ -L "${file}.kejilion.bak" ] ||
|
||
! cp -- "$file" "$saved"; then
|
||
ready=0
|
||
break
|
||
fi
|
||
done
|
||
if [ "$ready" -eq 1 ]; then
|
||
for file in "${apt_source_files[@]}"; do
|
||
cp -- "${file}.kejilion.bak" "$file" || { ready=0; break; }
|
||
done
|
||
fi
|
||
if [ "$ready" -eq 1 ] && apt-get update; then
|
||
rm -rf "$rollback_dir"
|
||
echo "已还原初始更新源并验证成功。"
|
||
return 0
|
||
fi
|
||
local rollback_ok=1
|
||
for file in "${apt_source_files[@]}"; do
|
||
saved="$rollback_dir/$(printf '%s' "$file" | sed 's|/|_|g')"
|
||
if [ -f "$saved" ] && ! cp -- "$saved" "$file"; then
|
||
rollback_ok=0
|
||
fi
|
||
done
|
||
if [ "$rollback_ok" -eq 1 ]; then
|
||
rm -rf "$rollback_dir"
|
||
err_msg "还原或验证失败,已恢复还原前的配置。"
|
||
else
|
||
err_msg "还原失败且部分配置无法回滚,事务快照保留在: $rollback_dir"
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
switch_source() {
|
||
local mode="$1" file tmp_file rollback_dir saved failed=0 processed=0 is_deb822
|
||
case "$mode" in
|
||
mirror|official) ;;
|
||
*)
|
||
err_msg "未知的更新源切换模式: $mode"
|
||
return 1
|
||
;;
|
||
esac
|
||
rollback_dir=$(mktemp -d) || return 1
|
||
|
||
# 先完成所有快照,再开始修改,避免快照中途失败时留下半套新配置。
|
||
for file in "${apt_source_files[@]}"; do
|
||
saved="$rollback_dir/$(printf '%s' "$file" | sed 's|/|_|g')"
|
||
if ! cp -- "$file" "$saved"; then
|
||
failed=1
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ "$failed" -eq 0 ]; then
|
||
for file in "${apt_source_files[@]}"; do
|
||
tmp_file=$(mktemp "${file}.XXXXXX") || { failed=1; break; }
|
||
case "$file" in
|
||
*.sources) is_deb822=1 ;;
|
||
*) is_deb822=0 ;;
|
||
esac
|
||
if ! awk -v mode="$mode" -v distro="$OS_ID" \
|
||
-v ubuntu_archive="$official_ubuntu_source" -v ubuntu_mirror="$aliyun_ubuntu_source" \
|
||
-v ubuntu_security="$official_ubuntu_security_source" -v deb822="$is_deb822" \
|
||
-v debian_archive="$official_debian_source" -v debian_mirror="$aliyun_debian_source" '
|
||
BEGIN {
|
||
FS = "\n"
|
||
if (deb822) {
|
||
RS = ""
|
||
}
|
||
}
|
||
function replacement(uri, security) {
|
||
if (distro == "ubuntu" && uri ~ "^https?://mirrors[.]aliyun[.]com/(ubuntu|ubuntu-ports)/?$")
|
||
return mode == "mirror" ? ubuntu_mirror : (security ? ubuntu_security : ubuntu_archive)
|
||
if (distro == "ubuntu" && uri ~ "^https?://([^/]+[.])?archive[.]ubuntu[.]com/ubuntu/?$")
|
||
return mode == "mirror" ? ubuntu_mirror : ubuntu_archive
|
||
if (distro == "ubuntu" && uri ~ "^https?://security[.]ubuntu[.]com/ubuntu/?$")
|
||
return mode == "mirror" ? ubuntu_mirror : ubuntu_security
|
||
if (distro == "ubuntu" && uri ~ "^https?://ports[.]ubuntu[.]com/ubuntu-ports/?$")
|
||
return mode == "mirror" ? ubuntu_mirror : ubuntu_archive
|
||
if (distro == "debian" && uri ~ "^https?://[^/]*(debian[.]org|debian[.]net|aliyun[.]com)/debian-security/?$")
|
||
return mode == "mirror" ? "https://mirrors.aliyun.com/debian-security/" : "https://security.debian.org/debian-security/"
|
||
if (distro == "debian" && uri ~ "^https?://[^/]*(debian[.]org|debian[.]net|aliyun[.]com)/debian/?$")
|
||
return mode == "mirror" ? debian_mirror : debian_archive
|
||
return uri
|
||
}
|
||
function rewrite(line, security, fields, count, i, result) {
|
||
sub(/^[[:space:]]+/, "", line)
|
||
count = split(line, fields, /[[:space:]]+/)
|
||
result = fields[1]
|
||
for (i = 2; i <= count; i++)
|
||
result = result " " replacement(fields[i], security)
|
||
return result
|
||
}
|
||
function rewrite_continuation(line, security, fields, count, i, result) {
|
||
sub(/^[[:space:]]+/, "", line)
|
||
count = split(line, fields, /[[:space:]]+/)
|
||
result = ""
|
||
for (i = 1; i <= count; i++)
|
||
result = result " " replacement(fields[i], security)
|
||
return result
|
||
}
|
||
{
|
||
# FS="\n" 下空行的 NF 为 0,末尾输出循环不会执行,
|
||
# 不特判会导致换源后原文件里的空行被静默删除
|
||
if (NF == 0) {
|
||
print ""
|
||
next
|
||
}
|
||
security = 0
|
||
if (deb822) {
|
||
in_suites = 0
|
||
for (i = 1; i <= NF; i++) {
|
||
if ($i ~ /^[[:space:]]*Suites:/) {
|
||
in_suites = 1
|
||
if ($i ~ /-security([[:space:]]|$)/) security = 1
|
||
} else if ($i ~ /^[[:space:]]+/) {
|
||
if (in_suites && $i ~ /-security([[:space:]]|$)/) security = 1
|
||
} else {
|
||
in_suites = 0
|
||
}
|
||
}
|
||
} else if ($0 ~ /-security([[:space:]]|$)/) {
|
||
security = 1
|
||
}
|
||
in_uris = 0
|
||
for (i = 1; i <= NF; i++) {
|
||
if ($i ~ /^[[:space:]]*URIs:[[:space:]]*/) {
|
||
$i = rewrite($i, security)
|
||
in_uris = 1
|
||
} else if (deb822 && in_uris && $i ~ /^[[:space:]]+/) {
|
||
$i = rewrite_continuation($i, security)
|
||
} else {
|
||
in_uris = 0
|
||
if ($i ~ /^[[:space:]]*deb(-src)?[[:space:]]/)
|
||
$i = rewrite($i, security)
|
||
}
|
||
}
|
||
for (i = 1; i <= NF; i++)
|
||
printf "%s%s", $i, (i < NF ? "\n" : (deb822 ? "\n\n" : "\n"))
|
||
}
|
||
' "$file" > "$tmp_file"; then
|
||
rm -f "$tmp_file"
|
||
failed=1
|
||
break
|
||
fi
|
||
if ! chmod --reference="$file" "$tmp_file" 2>/dev/null ||
|
||
! chown --reference="$file" "$tmp_file" 2>/dev/null ||
|
||
! mv -f "$tmp_file" "$file"; then
|
||
rm -f "$tmp_file"
|
||
failed=1
|
||
break
|
||
fi
|
||
processed=$((processed + 1))
|
||
done
|
||
fi
|
||
if [ "$failed" -eq 0 ] && [ "$processed" -eq "${#apt_source_files[@]}" ] && apt-get update; then
|
||
rm -rf "$rollback_dir"
|
||
return 0
|
||
fi
|
||
local rollback_ok=1
|
||
for file in "${apt_source_files[@]}"; do
|
||
saved="$rollback_dir/$(printf '%s' "$file" | sed 's|/|_|g')"
|
||
if [ -f "$saved" ] && ! cp -- "$saved" "$file"; then
|
||
rollback_ok=0
|
||
fi
|
||
done
|
||
if [ "$rollback_ok" -eq 1 ]; then
|
||
rm -rf "$rollback_dir"
|
||
apt-get update >/dev/null 2>&1
|
||
err_msg "新更新源验证失败,已恢复原配置。"
|
||
else
|
||
err_msg "新源验证失败且部分配置无法回滚,事务快照保留在: $rollback_dir"
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# 主菜单
|
||
while true; do
|
||
case "$OS_ID" in
|
||
ubuntu)
|
||
echo "Ubuntu 更新源切换脚本"
|
||
echo "------------------------"
|
||
;;
|
||
debian)
|
||
echo "Debian 更新源切换脚本"
|
||
echo "------------------------"
|
||
;;
|
||
*)
|
||
err_msg "未知系统,无法执行脚本"
|
||
break
|
||
;;
|
||
esac
|
||
|
||
echo "1. 切换到阿里云源"
|
||
echo "2. 切换到官方源"
|
||
echo "------------------------"
|
||
echo "3. 备份初始更新源(已备份则跳过)"
|
||
echo "4. 还原初始更新源"
|
||
echo "------------------------"
|
||
echo "0. 返回上一级"
|
||
echo "------------------------"
|
||
read -p "请选择操作: " choice
|
||
|
||
case $choice in
|
||
1)
|
||
if ensure_source_backup && switch_source mirror; then
|
||
echo "已切换到阿里云源"
|
||
fi
|
||
;;
|
||
2)
|
||
if ensure_source_backup && switch_source official; then
|
||
echo "已切换到官方源"
|
||
fi
|
||
;;
|
||
3)
|
||
backup_sources
|
||
;;
|
||
4)
|
||
restore_initial_source
|
||
;;
|
||
0)
|
||
break
|
||
;;
|
||
*)
|
||
echo "无效的选择,请重新输入"
|
||
;;
|
||
esac
|
||
break_end
|
||
|
||
done
|
||
|
||
;;
|
||
|
||
20)
|
||
|
||
while true; do
|
||
clear
|
||
echo "定时任务列表"
|
||
crontab -l 2>/dev/null
|
||
echo ""
|
||
echo "操作"
|
||
echo "------------------------"
|
||
echo "1. 添加定时任务 2. 删除定时任务 3. 编辑定时任务"
|
||
echo "------------------------"
|
||
echo "0. 返回上一级选单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
|
||
case $sub_choice in
|
||
1)
|
||
read -p "请输入新任务的执行命令: " newquest
|
||
if [ -z "$newquest" ]; then
|
||
err_msg "执行命令不能为空。"
|
||
echo "按任意键继续..."
|
||
read -n 1 -s -r -p ""
|
||
continue
|
||
fi
|
||
echo "------------------------"
|
||
echo "1. 每月任务 2. 每周任务"
|
||
echo "3. 每天任务 4. 每小时任务"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " dingshi
|
||
|
||
case $dingshi in
|
||
1)
|
||
read -p "选择每月的几号执行任务? (1-28): " day
|
||
if [[ "$day" =~ ^[0-9]+$ ]] && [ "$day" -ge 1 ] && [ "$day" -le 28 ]; then
|
||
(crontab -l 2>/dev/null ; echo "0 0 $day * * $newquest") | crontab -
|
||
else
|
||
err_msg "日期必须是 1 到 28 之间的整数。"
|
||
fi
|
||
;;
|
||
2)
|
||
read -p "选择周几执行任务? (0-6,0代表星期日): " weekday
|
||
if [[ "$weekday" =~ ^[0-9]+$ ]] && [ "$weekday" -ge 0 ] && [ "$weekday" -le 6 ]; then
|
||
(crontab -l 2>/dev/null ; echo "0 0 * * $weekday $newquest") | crontab -
|
||
else
|
||
err_msg "星期必须是 0 到 6 之间的整数。"
|
||
fi
|
||
;;
|
||
3)
|
||
read -p "选择每天几点执行任务?(小时,0-23): " hour
|
||
if [[ "$hour" =~ ^[0-9]+$ ]] && [ "$hour" -ge 0 ] && [ "$hour" -le 23 ]; then
|
||
(crontab -l 2>/dev/null ; echo "0 $hour * * * $newquest") | crontab -
|
||
else
|
||
err_msg "小时必须是 0 到 23 之间的整数。"
|
||
fi
|
||
;;
|
||
4)
|
||
read -p "输入每小时的第几分钟执行任务?(分钟,0-59): " minute
|
||
if [[ "$minute" =~ ^[0-9]+$ ]] && [ "$minute" -ge 0 ] && [ "$minute" -le 59 ]; then
|
||
(crontab -l 2>/dev/null ; echo "$minute * * * * $newquest") | crontab -
|
||
else
|
||
err_msg "分钟必须是 0 到 59 之间的整数。"
|
||
fi
|
||
;;
|
||
*)
|
||
break # 跳出
|
||
;;
|
||
esac
|
||
;;
|
||
2)
|
||
read -p "请输入需要删除任务的关键字: " kquest
|
||
# 关键字为空时 grep -v "" 会匹配所有行,导致整份 crontab 被清空
|
||
if [ -z "$kquest" ]; then
|
||
err_msg "关键字不能为空,已取消删除。"
|
||
else
|
||
crontab -l 2>/dev/null | grep -vF "$kquest" | crontab - > /dev/null 2>&1
|
||
fi
|
||
;;
|
||
3)
|
||
crontab -e
|
||
;;
|
||
0)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
|
||
*)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
esac
|
||
done
|
||
|
||
;;
|
||
|
||
21)
|
||
root_use
|
||
while true; do
|
||
echo "本机host解析列表"
|
||
echo "如果你在这里添加解析匹配,将不再使用动态解析了"
|
||
cat /etc/hosts
|
||
echo ""
|
||
echo "操作"
|
||
echo "------------------------"
|
||
echo "1. 添加新的解析 2. 删除解析地址"
|
||
echo "------------------------"
|
||
echo "0. 返回上一级选单"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " host_dns
|
||
|
||
case $host_dns in
|
||
1)
|
||
read -p "请输入新的解析记录 格式: 110.25.5.33 kejilion.pro : " addhost
|
||
if [ -z "$addhost" ]; then
|
||
err_msg "解析记录不能为空。"
|
||
else
|
||
echo "$addhost" >> /etc/hosts
|
||
fi
|
||
|
||
;;
|
||
2)
|
||
read -p "请输入需要删除的解析内容关键字: " delhost
|
||
# 关键字为空时 sed //d 会清空整个 /etc/hosts
|
||
if [ -z "$delhost" ]; then
|
||
err_msg "关键字不能为空,已取消删除。"
|
||
else
|
||
hosts_tmp=$(mktemp /etc/.hosts.kejilion.XXXXXX) || {
|
||
err_msg "无法创建 hosts 临时文件。"
|
||
continue
|
||
}
|
||
if awk -v keyword="$delhost" 'index($0, keyword) == 0' /etc/hosts > "$hosts_tmp" &&
|
||
chmod --reference=/etc/hosts "$hosts_tmp" 2>/dev/null &&
|
||
chown --reference=/etc/hosts "$hosts_tmp" 2>/dev/null &&
|
||
mv -f "$hosts_tmp" /etc/hosts; then
|
||
echo "匹配的解析记录已删除。"
|
||
else
|
||
rm -f "$hosts_tmp"
|
||
err_msg "更新 /etc/hosts 失败,已保留原文件。"
|
||
fi
|
||
fi
|
||
;;
|
||
0)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
|
||
*)
|
||
break # 跳出循环,退出菜单
|
||
;;
|
||
esac
|
||
done
|
||
;;
|
||
|
||
22)
|
||
root_use
|
||
if docker inspect fail2ban &>/dev/null ; then
|
||
while true; do
|
||
clear
|
||
echo "SSH防御程序已启动"
|
||
echo "------------------------"
|
||
echo "1. 查看SSH拦截记录"
|
||
echo "2. 日志实时监控"
|
||
echo "------------------------"
|
||
echo "9. 卸载防御程序"
|
||
echo "------------------------"
|
||
echo "0. 退出"
|
||
echo "------------------------"
|
||
read -p "请输入你的选择: " sub_choice
|
||
case $sub_choice in
|
||
|
||
1)
|
||
echo "------------------------"
|
||
f2b_sshd
|
||
echo "------------------------"
|
||
;;
|
||
2)
|
||
tail -f /path/to/fail2ban/config/log/fail2ban/fail2ban.log
|
||
break
|
||
;;
|
||
9)
|
||
docker rm -f fail2ban
|
||
rm -rf /path/to/fail2ban
|
||
echo "Fail2Ban防御程序已卸载"
|
||
|
||
break
|
||
;;
|
||
0)
|
||
break
|
||
;;
|
||
*)
|
||
echo "无效的选择,请重新输入。"
|
||
;;
|
||
esac
|
||
break_end
|
||
|
||
done
|
||
|
||
elif [ -x "$(command -v fail2ban-client)" ] ; then
|
||
clear
|
||
echo "卸载旧版fail2ban"
|
||
read -p "确定继续吗?(Y/N): " choice
|
||
case "$choice" in
|
||
[Yy])
|
||
remove fail2ban
|
||
rm -rf /etc/fail2ban
|
||
echo "Fail2Ban防御程序已卸载"
|
||
;;
|
||
[Nn])
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 Y 或 N。"
|
||
;;
|
||
esac
|
||
|
||
else
|
||
|
||
clear
|
||
echo "fail2ban是一个SSH防止暴力破解工具"
|
||
echo "官网介绍: https://github.com/fail2ban/fail2ban"
|
||
echo "------------------------------------------------"
|
||
echo "工作原理:研判非法IP恶意高频访问SSH端口,自动进行IP封锁"
|
||
echo "------------------------------------------------"
|
||
read -p "确定继续吗?(Y/N): " choice
|
||
|
||
case "$choice" in
|
||
[Yy])
|
||
clear
|
||
if ! install_docker; then
|
||
err_msg "Docker 环境准备失败。"
|
||
break
|
||
fi
|
||
# 只有安装/配置失败才回滚:慢机器上 fail2ban socket 尚未就绪时
|
||
# 状态查询会非零退出,此时容器其实是好的,不该被强删
|
||
if f2b_install_sshd; then
|
||
f2b_status || err_msg "Fail2Ban 已安装,但状态查询失败,可稍后用菜单查看运行状态确认。"
|
||
echo "Fail2Ban防御程序已开启"
|
||
else
|
||
docker rm -f fail2ban >/dev/null 2>&1
|
||
err_msg "Fail2Ban 安装或配置失败,已清理未完成的容器。"
|
||
fi
|
||
|
||
;;
|
||
[Nn])
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 Y 或 N。"
|
||
;;
|
||
esac
|
||
fi
|
||
;;
|
||
|
||
|
||
23)
|
||
root_use
|
||
echo "当前流量使用情况,重启服务器流量计算会清零!"
|
||
output_status
|
||
echo "$output"
|
||
|
||
# 检查是否存在 Limiting_Shut_down.sh 文件
|
||
if [ -f ~/Limiting_Shut_down.sh ]; then
|
||
# 获取 threshold_gb 的值(使用 POSIX ERE,不依赖 PCRE)
|
||
threshold_gb=$(grep -oE '^threshold_gb=[0-9]+' ~/Limiting_Shut_down.sh | head -n 1 | cut -d= -f2)
|
||
if [ -n "$threshold_gb" ]; then
|
||
echo -e "当前设置的限流阈值为 ${huang}${threshold_gb}${bai}GB"
|
||
else
|
||
err_msg "现有限流脚本的阈值配置异常。"
|
||
fi
|
||
else
|
||
echo -e "${hui}当前未启用限流关机功能${bai}"
|
||
fi
|
||
|
||
echo
|
||
echo "------------------------------------------------"
|
||
echo "系统每分钟检测一次流量,接收或发送任一方向到达阈值就会自动关闭服务器!每月1日重置流量重启服务器。"
|
||
read -p "1. 开启限流关机功能 2. 停用限流关机功能 0. 退出 : " Limiting
|
||
|
||
case "$Limiting" in
|
||
1)
|
||
# 输入新的流量阈值
|
||
echo "按单向流量计算(多数 VPS 只计出站)。如果实际服务器就100G流量,可设置阈值为95G,提前关机,以免出现流量误差或溢出."
|
||
read -p "请输入流量阈值(单位为GB): " threshold_gb
|
||
if ! [[ "$threshold_gb" =~ ^[0-9]+$ ]] || [ "$threshold_gb" -le 0 ]; then
|
||
err_msg "流量阈值必须是大于 0 的整数(单位 GB),当前输入: '${threshold_gb}'"
|
||
elif ! command -v crontab &>/dev/null; then
|
||
err_msg "未找到 crontab,无法启用限流关机功能。"
|
||
else
|
||
limit_script="$HOME/Limiting_Shut_down.sh"
|
||
tmp_limit_script=$(mktemp "$HOME/.Limiting_Shut_down.sh.XXXXXX") || {
|
||
err_msg "无法创建限流脚本临时文件。"
|
||
break
|
||
}
|
||
limit_backup=""
|
||
limit_script_existed=0
|
||
if [ -e "$limit_script" ]; then
|
||
limit_backup=$(mktemp "$HOME/.Limiting_Shut_down.sh.backup.XXXXXX") || {
|
||
rm -f "$tmp_limit_script"
|
||
err_msg "无法创建现有限流脚本的备份。"
|
||
break
|
||
}
|
||
if ! cp -p -- "$limit_script" "$limit_backup"; then
|
||
rm -f "$tmp_limit_script" "$limit_backup"
|
||
err_msg "无法备份现有限流脚本,已取消更新。"
|
||
break
|
||
fi
|
||
limit_script_existed=1
|
||
fi
|
||
if download_file "${UPDATE_RAW_URL}/Limiting_Shut_down.sh" "$tmp_limit_script" &&
|
||
sed -i "s/^threshold_gb=.*/threshold_gb=${threshold_gb}/" "$tmp_limit_script" &&
|
||
grep -q "^threshold_gb=${threshold_gb}$" "$tmp_limit_script" &&
|
||
bash -n "$tmp_limit_script" &&
|
||
chmod +x "$tmp_limit_script" &&
|
||
mv -f "$tmp_limit_script" "$limit_script"; then
|
||
if update_limit_cron enable "$limit_script"; then
|
||
[ -z "$limit_backup" ] || rm -f "$limit_backup"
|
||
echo "限流关机已设置"
|
||
else
|
||
if [ "$limit_script_existed" -eq 1 ]; then
|
||
if ! mv -f "$limit_backup" "$limit_script"; then
|
||
err_msg "定时任务写入失败,且旧限流脚本恢复失败: $limit_backup"
|
||
break
|
||
fi
|
||
else
|
||
rm -f "$limit_script"
|
||
fi
|
||
err_msg "定时任务写入失败,已恢复原限流脚本。"
|
||
fi
|
||
else
|
||
rm -f "$tmp_limit_script"
|
||
[ -z "$limit_backup" ] || rm -f "$limit_backup"
|
||
err_msg "限流关机脚本下载或校验失败,已保留原配置。"
|
||
fi
|
||
fi
|
||
|
||
;;
|
||
0)
|
||
echo "已取消"
|
||
;;
|
||
2)
|
||
if ! command -v crontab &>/dev/null; then
|
||
err_msg "未找到 crontab,无法安全停用定时任务。"
|
||
elif update_limit_cron disable "$HOME/Limiting_Shut_down.sh"; then
|
||
rm -f "$HOME/Limiting_Shut_down.sh"
|
||
echo "已关闭限流关机功能"
|
||
else
|
||
err_msg "定时任务更新失败,限流脚本已保留。"
|
||
fi
|
||
;;
|
||
*)
|
||
echo "无效的选择,请输入 0、1 或 2。"
|
||
;;
|
||
esac
|
||
|
||
;;
|
||
|
||
|
||
0)
|
||
kejilion
|
||
|
||
;;
|
||
*)
|
||
echo "无效的输入!"
|
||
;;
|
||
esac
|
||
break_end
|
||
|
||
done
|
||
;;
|
||
|
||
00)
|
||
clear
|
||
echo "更新日志"
|
||
echo "------------------------"
|
||
changelog=$(curl -fsSL --max-time 20 "${UPDATE_RAW_URL}/kejilion_sh_log.txt")
|
||
if [ -n "$changelog" ]; then
|
||
echo "全部日志: ${UPDATE_RAW_URL}/kejilion_sh_log.txt"
|
||
echo "------------------------"
|
||
echo "$changelog" | tail -n 35
|
||
else
|
||
echo "(当前更新源未提供更新日志)"
|
||
fi
|
||
echo "------------------------"
|
||
|
||
sh_v_new=$(curl -fsSL --max-time 20 "${UPDATE_RAW_URL}/kejilion.sh" | grep -o 'sh_v="[0-9.]*"' | head -n 1 | cut -d '"' -f 2)
|
||
|
||
if [ -z "$sh_v_new" ]; then
|
||
err_msg "无法获取远程版本信息,请检查网络或更新源: ${UPDATE_RAW_URL}"
|
||
elif [ "$sh_v" = "$sh_v_new" ]; then
|
||
echo -e "${lv}你已经是最新版本!${huang}v$sh_v${bai}"
|
||
elif ! version_gt "$sh_v_new" "$sh_v"; then
|
||
echo -e "${lv}本地版本 ${huang}v$sh_v${lv} 高于更新源版本 ${huang}v$sh_v_new${lv},已跳过更新。${bai}"
|
||
else
|
||
echo "发现新版本!"
|
||
echo -e "当前版本 v$sh_v 最新版本 ${huang}v$sh_v_new${bai}"
|
||
echo "------------------------"
|
||
read -p "确定更新脚本吗?(Y/N): " choice
|
||
case "$choice" in
|
||
[Yy])
|
||
clear
|
||
# 在脚本所在目录下载并原子替换,避免更新失败时截断当前脚本
|
||
script_dir=$(dirname "$SCRIPT_PATH")
|
||
tmp_sh=$(mktemp "${script_dir}/.kejilion.sh.XXXXXX") || {
|
||
err_msg "创建更新临时文件失败。"
|
||
echo "按任意键继续..."
|
||
read -n 1 -s -r -p ""
|
||
continue
|
||
}
|
||
if download_file "${UPDATE_RAW_URL}/kejilion.sh" "$tmp_sh" &&
|
||
bash -n "$tmp_sh" &&
|
||
downloaded_version=$(grep -m1 -oE '^sh_v="[0-9]+([.][0-9]+)*"$' "$tmp_sh" | cut -d '"' -f 2) &&
|
||
[ "$downloaded_version" = "$sh_v_new" ] &&
|
||
version_gt "$downloaded_version" "$sh_v" &&
|
||
chmod +x "$tmp_sh" &&
|
||
mv -f "$tmp_sh" "$SCRIPT_PATH"; then
|
||
cp "$SCRIPT_PATH" /usr/local/bin/k > /dev/null 2>&1
|
||
echo -e "${lv}脚本已更新到最新版本!${huang}v$sh_v_new${bai}"
|
||
break_end
|
||
exec bash "$SCRIPT_PATH"
|
||
else
|
||
rm -f "$tmp_sh"
|
||
err_msg "更新失败,已保留当前版本。"
|
||
fi
|
||
;;
|
||
[Nn])
|
||
echo "已取消"
|
||
;;
|
||
*)
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
;;
|
||
|
||
0)
|
||
clear
|
||
exit
|
||
;;
|
||
|
||
*)
|
||
echo "无效的输入!"
|
||
;;
|
||
esac
|
||
break_end
|
||
done
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
if [ "$#" -eq 0 ]; then
|
||
# 如果没有参数,运行交互式逻辑
|
||
kejilion_sh
|
||
else
|
||
# 如果有参数,执行相应函数
|
||
case $1 in
|
||
install|add|安装)
|
||
shift
|
||
install "$@"
|
||
;;
|
||
remove|del|卸载)
|
||
shift
|
||
remove "$@"
|
||
;;
|
||
update|更新)
|
||
linux_update
|
||
;;
|
||
clean|清理)
|
||
linux_clean
|
||
;;
|
||
bbr3|bbrv3)
|
||
bbrv3
|
||
;;
|
||
*)
|
||
echo "无效参数"
|
||
;;
|
||
esac
|
||
fi
|