#!/bin/bash export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH : ' Copyright (C) 2022 - now HaoZi Technology Co., Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ' source <(curl -f -s --connect-timeout 10 --retry 3 https://dl.cdn.haozi.net/panel/public.sh) if [ $? -ne 0 ]; then echo "下载 public.sh 失败,请检查网络或稍后重试。" echo "Download public.sh failed, please check the network or try again later." exit 1 fi LOGO="+----------------------------------------------------\n| 耗子面板安装脚本\n| Rat Panel install script\n+----------------------------------------------------\n| Copyright © 2022-"$(date +%Y)" 耗子科技 All rights reserved.\n+----------------------------------------------------" current_path=$(pwd) ssh_port=$(cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}') in_china=$(curl --retry 2 -m 10 -L https://www.qualcomm.cn/cdn-cgi/trace 2>/dev/null | grep -qx 'loc=CN' && echo "true" || echo "false") # 检查系统 Prepare_System() { if [ $(whoami) != "root" ]; then error "请使用root用户运行安装命令(Please run the installation command using the root user)" fi if [ ${OS} == "unknown" ]; then error "系统不支持安装面板(The system does not support installing the panel)" fi if [ ${ARCH} != "x86_64" ] && [ ${ARCH} != "aarch64" ]; then error "系统架构不支持安装面板(The system architecture does not support installing the panel)" fi if [ ${ARCH} == "x86_64" ]; then if [ "$(cat /proc/cpuinfo | grep -c ssse3)" -lt "1" ]; then error "CPU至少需支持x86-64-v2指令集(CPU must support at least the x86-64-v2 instruction set)" fi fi kernel_version=$(uname -r | awk -F '.' '{print $1}') if [ "${kernel_version}" -lt "4" ]; then error "系统内核版本太低,请升级到4.x以上版本(The system kernel version is too low, please upgrade to version 4.x or above)" fi is_64bit=$(getconf LONG_BIT) if [ "${is_64bit}" != '64' ]; then error "请更换64位系统安装面板(Please switch to a 64-bit system to install the panel)" fi if [ -f "${setup_path}/panel/web" ]; then error "面板已安装,无需重复安装(Panel is already installed, no need to install again)" fi if ! id -u "www" >/dev/null 2>&1; then groupadd www useradd -s /sbin/nologin -g www www fi if [ ! -d ${setup_path} ]; then mkdir ${setup_path} fi timedatectl set-timezone Asia/Shanghai [ -s /etc/selinux/config ] && sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config setenforce 0 >/dev/null 2>&1 ulimit -n 1048576 echo 2147483584 >/proc/sys/fs/file-max soft_nofile_check=$(cat /etc/security/limits.conf | grep '^* soft nofile .*$') hard_nofile_check=$(cat /etc/security/limits.conf | grep '^* hard nofile .*$') soft_nproc_check=$(cat /etc/security/limits.conf | grep '^* soft nproc .*$') hard_nproc_check=$(cat /etc/security/limits.conf | grep '^* hard nproc .*$') fs_file_max_check=$(cat /etc/sysctl.conf | grep '^fs.file-max.*$') if [ "${soft_nofile_check}" == "" ]; then echo "* soft nofile 1048576" >>/etc/security/limits.conf fi if [ "${hard_nofile_check}" == "" ]; then echo "* hard nofile 1048576" >>/etc/security/limits.conf fi if [ "${soft_nproc_check}" == "" ]; then echo "* soft nproc 1048576" >>/etc/security/limits.conf fi if [ "${hard_nproc_check}" == "" ]; then echo "* hard nproc 1048576" >>/etc/security/limits.conf fi if [ "${fs_file_max_check}" == "" ]; then echo fs.file-max = 2147483584 >>/etc/sysctl.conf fi # 自动开启 BBR # 清理旧配置,防止干扰 sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf bbr_support_check=$(ls -l /lib/modules/*/kernel/net/ipv4 | grep -c tcp_bbr) bbr_open_check=$(sysctl net.ipv4.tcp_congestion_control | grep -c bbr) if [ "${bbr_support_check}" != "0" ] && [ "${bbr_open_check}" == "0" ]; then qdisc=$(sysctl net.core.default_qdisc | awk '{print $3}') # cake 是 fq_codel 的升级版,优先使用 if cat /boot/config-$(uname -r) | grep CONFIG_NET_SCH_CAKE | grep -q "="; then qdisc="cake" elif cat /boot/config-$(uname -r) | grep CONFIG_NET_SCH_FQ_CODEL | grep -q "="; then qdisc="fq_codel" elif cat /boot/config-$(uname -r) | grep CONFIG_NET_SCH_FQ_PIE | grep -q "="; then qdisc="fq_pie" elif cat /boot/config-$(uname -r) | grep CONFIG_NET_SCH_FQ | grep -q "="; then qdisc="fq" fi echo "net.core.default_qdisc=${qdisc}" >>/etc/sysctl.conf echo "net.ipv4.tcp_congestion_control=bbr" >>/etc/sysctl.conf fi # nf_conntrack 相关参数调优 # 这个配置非常重要,默认值很容易出现 nf_conntrack: table full, dropping packet 丢包问题 sed -i '/nf_conntrack_max/d' /etc/sysctl.conf sed -i '/nf_conntrack_buckets/d' /etc/sysctl.conf if [ ${MEM} -lt 2100 ]; then echo "net.netfilter.nf_conntrack_max=262144" >>/etc/sysctl.conf echo "net.netfilter.nf_conntrack_buckets=65536" >>/etc/sysctl.conf elif [ ${MEM} -lt 4100 ]; then echo "net.netfilter.nf_conntrack_max=655360" >>/etc/sysctl.conf echo "net.netfilter.nf_conntrack_buckets=163840" >>/etc/sysctl.conf elif [ ${MEM} -lt 8200 ]; then echo "net.netfilter.nf_conntrack_max=1048576" >>/etc/sysctl.conf echo "net.netfilter.nf_conntrack_buckets=262144" >>/etc/sysctl.conf else echo "net.netfilter.nf_conntrack_max=1503232" >>/etc/sysctl.conf echo "net.netfilter.nf_conntrack_buckets=375808" >>/etc/sysctl.conf fi sysctl -p if [ ${OS} == "rhel" ]; then if ${in_china}; then sed -e 's|^mirrorlist=|#mirrorlist=|g' \ -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.tencent.com/rocky|g' \ -e 's|^# baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.tencent.com/rocky|g' \ -i.bak \ /etc/yum.repos.d/[Rr]ocky*.repo >/dev/null 2>&1 sed -e 's|^mirrorlist=|#mirrorlist=|g' \ -e 's|^#baseurl=https://repo.almalinux.org|baseurl=https://mirrors.tencent.com|g' \ -e 's|^# baseurl=https://repo.almalinux.org|baseurl=https://mirrors.tencent.com|g' \ -i.bak \ /etc/yum.repos.d/[Aa]lmalinux*.repo >/dev/null 2>&1 sed -e 's|^mirrorlist=|#mirrorlist=|g' \ -e 's|^#baseurl=http://mirror.centos.org/$contentdir|baseurl=https://mirrors.tencent.com/centos-stream|g' \ -e 's|^# baseurl=http://mirror.centos.org/$contentdir|baseurl=https://mirrors.tencent.com/centos-stream|g' \ -i.bak \ /etc/yum.repos.d/[Cc]ent*.repo >/dev/null 2>&1 fi dnf makecache -y dnf install dnf-plugins-core -y dnf config-manager --set-enabled epel if ${in_china}; then sed -i 's|^#baseurl=https://download.example/pub|baseurl=https://mirrors.tencent.com|' /etc/yum.repos.d/epel* >/dev/null 2>&1 sed -i 's|^# baseurl=https://download.example/pub|baseurl=https://mirrors.tencent.com|' /etc/yum.repos.d/epel* >/dev/null 2>&1 sed -i 's|^metalink|#metalink|' /etc/yum.repos.d/epel* >/dev/null 2>&1 dnf makecache -y fi # EL 9 dnf config-manager --set-enabled crb dnf install epel-release epel-next-release -y # 部分系统可能没有这两个包,需要手动安装 # 对于 openEuler 这种大改的系统,下载会 404,这没有影响 if [ "$?" != "0" ]; then if ${in_china}; then dnf install -y https://mirrors.tencent.com/epel/epel-release-latest-$(rpm -E %{rhel}).noarch.rpm dnf install -y https://mirrors.tencent.com/epel/epel-next-release-latest-$(rpm -E %{rhel}).noarch.rpm else dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-$(rpm -E %{rhel}).noarch.rpm dnf install -y https://dl.fedoraproject.org/pub/epel/epel-next-release-latest-$(rpm -E %{rhel}).noarch.rpm fi fi # Rocky Linux /usr/bin/crb enable >/dev/null 2>&1 # openEuler if [ -f /etc/openEuler-release ]; then # 清理旧配置 grep -rl '^baseurl=https://repo.oepkgs.net' /etc/yum.repos.d/ | xargs -I {} rm -f {} dnf config-manager --add-repo https://repo.oepkgs.net/openeuler/rpm/$(awk '{print $1"-"$3"-"$4}' /etc/openEuler-release | sed 's/[()]//g')/extras/$(uname -m)/ oe_version=$(awk '{print $3}' /etc/openEuler-release | cut -d '.' -f 1) case ${oe_version} in 22) dnf config-manager --add-repo https://repo.oepkgs.net/openeuler/rpm/$(awk '{print $1"-"$3"-"$4}' /etc/openEuler-release | sed 's/[()]//g')/extras/$(uname -m)/ dnf config-manager --add-repo https://repo.oepkgs.net/openeuler/rpm/$(awk '{print $1"-"$3"-"$4}' /etc/openEuler-release | sed 's/[()]//g')/compatible/f33/$(uname -m)/ ;; 24) # openEuler 24 目前没有 p7zip-plugins,等他们自己修复 error "openEuler 24 暂不支持安装面板(Panel installation is not supported on openEuler 24)" ;; *) error "不支持的 openEuler 版本(Unsupported openEuler version)" ;; esac # 禁用gpcheck,这仓库缺签名 grep -rl '^baseurl=https://repo.oepkgs.net' /etc/yum.repos.d/ | xargs -I {} sh -c 'echo "gpgcheck=0" >> "{}"' fi dnf makecache -y dnf install -y bash curl wget zip unzip tar p7zip p7zip-plugins git jq git-core dos2unix make sudo elif [ ${OS} == "debian" ] || [ ${OS} == "ubuntu" ]; then if ${in_china}; then # Debian sed -i 's/deb.debian.org/mirrors.tencent.com/g' /etc/apt/sources.list >/dev/null 2>&1 sed -i 's/deb.debian.org/mirrors.tencent.com/g' /etc/apt/sources.list.d/debian.sources >/dev/null 2>&1 sed -i -e 's|security.debian.org/\? |security.debian.org/debian-security |g' \ -e 's|security.debian.org|mirrors.tencent.com|g' \ -e 's|deb.debian.org/debian-security|mirrors.tencent.com/debian-security|g' \ /etc/apt/sources.list >/dev/null 2>&1 # Ubuntu sed -i 's@//.*archive.ubuntu.com@//mirrors.tencent.com@g' /etc/apt/sources.list >/dev/null 2>&1 sed -i 's@//.*archive.ubuntu.com@//mirrors.tencent.com@g' /etc/apt/sources.list.d/ubuntu.sources >/dev/null 2>&1 sed -i 's/security.ubuntu.com/mirrors.tencent.com/g' /etc/apt/sources.list >/dev/null 2>&1 sed -i 's/security.ubuntu.com/mirrors.tencent.com/g' /etc/apt/sources.list.d/ubuntu.sources >/dev/null 2>&1 fi apt-get update -y apt-get install -y bash curl wget zip unzip tar p7zip p7zip-full git jq git dos2unix make sudo fi if [ "$?" != "0" ]; then error "安装面板依赖软件失败(Installation of panel dependency software failed)" fi } # 自动 swap Auto_Swap() { # 判断是否有swap if [ "${SWAP}" -gt 1 ]; then return fi # 内存 >= 4G 不设置swap if [ "${MEM}" -ge 3900 ]; then return fi # 设置swap swap_file="${setup_path}/swap" btrfs_check=$(df -T ${setup_path} | awk '{print $2}' | tail -n 1) if [ "${btrfs_check}" == "btrfs" ]; then btrfs filesystem mkswap_file --size 2G --uuid clear ${swap_file} else dd if=/dev/zero of=$swap_file bs=8M count=256 fi chmod 600 $swap_file mkswap -f $swap_file swapon $swap_file echo "$swap_file swap swap defaults 0 0" >>/etc/fstab mount -a if [ "$?" != "0" ]; then error "/etc/fstab 文件配置有误,请检查排除后重试,问题解决前勿重启系统(There is an error in the /etc/fstab file configuration, please check and try again after excluding, do not restart the system until the problem is resolved)" fi } # 初始化面板 Init_Panel() { systemctl stop panel >/dev/null 2>&1 systemctl disable panel >/dev/null 2>&1 rm -f /etc/systemd/system/panel.service rm -rf ${setup_path}/panel mkdir ${setup_path}/server mkdir ${setup_path}/server/cron mkdir ${setup_path}/server/cron/logs chmod -R 755 ${setup_path}/server mkdir ${setup_path}/panel # 下载面板zip包并解压 version=$(curl -fsLm 10 --retry 3 "https://panel.haozi.net/api/version/latest") if [ ${ARCH} == "x86_64" ]; then panel_url=$(echo "$version" | jq -r '.data.downloads[] | select(.arch == "amd64") | .url') panel_checksum=$(echo "$version" | jq -r '.data.downloads[] | select(.arch == "amd64") | .checksum') elif [ ${ARCH} == "aarch64" ]; then panel_url=$(echo "$version" | jq -r '.data.downloads[] | select(.arch == "arm64") | .url') panel_checksum=$(echo "$version" | jq -r '.data.downloads[] | select(.arch == "arm64") | .checksum') fi if [ "$?" != "0" ] || [ "${panel_url}" == "" ] || [ "${panel_checksum}" == "" ]; then error "获取面板下载链接失败(Failed to get the panel download url)" fi panel_name=$(echo "$panel_url" | awk -F '/' '{print $NF}') wget --retry-connrefused --retry-on-host-error --retry-on-http-error=429,500,502,503,504 -t 10 -T 120 -O ${setup_path}/panel/${panel_name} "${panel_url}" wget --retry-connrefused --retry-on-host-error --retry-on-http-error=429,500,502,503,504 -t 10 -T 120 -O ${setup_path}/panel/${panel_name}.sha256 "${panel_checksum}" cd ${setup_path}/panel if ! sha256sum --status -c ${panel_name}.sha256 --ignore-missing; then error "面板压缩包校验失败(The panel zip package verification failed)" fi unzip -o ${panel_name} if [ "$?" != "0" ]; then error "解压面板失败(Failed to unzip the panel)" fi rm -f ${panel_name} rm -f ${panel_name}.sha256 mkdir -p /usr/local/etc/panel mv -f config.example.yml /usr/local/etc/panel/config.yml sed -i "s|/www|${setup_path}|g" /usr/local/etc/panel/config.yml chmod 600 /usr/local/etc/panel/config.yml # 设置面板 chmod -R 700 ${setup_path}/panel mv -f ${setup_path}/panel/cli /usr/local/sbin/panel-cli # 防火墙放行 if [ ${OS} == "rhel" ]; then dnf install firewalld -y elif [ ${OS} == "debian" ] || [ ${OS} == "ubuntu" ]; then ufw disable >/dev/null 2>&1 systemctl stop ufw >/dev/null 2>&1 systemctl disable ufw >/dev/null 2>&1 apt-get purge --auto-remove ufw -y apt-get install firewalld -y fi systemctl enable --now firewalld firewall-cmd --set-default-zone=public >/dev/null 2>&1 firewall-cmd --permanent --zone=public --add-port=22/tcp >/dev/null 2>&1 firewall-cmd --permanent --zone=public --add-port=80/tcp >/dev/null 2>&1 firewall-cmd --permanent --zone=public --add-port=443/tcp >/dev/null 2>&1 firewall-cmd --permanent --zone=public --add-port=443/udp >/dev/null 2>&1 firewall-cmd --permanent --zone=public --add-port=8888/tcp >/dev/null 2>&1 firewall-cmd --permanent --zone=public --add-port=${ssh_port}/tcp >/dev/null 2>&1 firewall-cmd --reload if [ "$?" != "0" ]; then error "防火墙端口放行失败(Failed to add firewall port)" fi # 写入服务文件 wget --retry-connrefused --retry-on-host-error --retry-on-http-error=429,500,502,503,504 -t 10 -T 120 -O /etc/systemd/system/panel.service ${download_url}/panel.service sed -i "s|/www|${setup_path}|g" /etc/systemd/system/panel.service chmod 644 /etc/systemd/system/panel.service systemctl daemon-reload panel-cli init panel-cli sync systemctl enable --now panel.service if [ "$?" != "0" ]; then error "面板启动失败(Failed to start the panel)" fi } # 自动标记已存在的应用 Auto_Mark_Installed() { # docker docker_Flag=0 if [ -x "$(command -v docker)" ]; then systemctl enable --now docker docker_Flag=1 docker_version=$(docker -v | awk '{print $3}' | cut -d ',' -f 1) panel-cli app write docker stable ${docker_version} fi # podman if [ -x "$(command -v podman)" ] && [ "${docker_Flag}" == "0" ]; then systemctl enable --now podman systemctl enable --now podman.socket systemctl enable --now podman-restart podman_version=$(podman -v | awk '{print $3}') panel-cli app write podman stable ${podman_version} fi # fail2ban if [ -x "$(command -v fail2ban-server)" ]; then systemctl enable --now fail2ban fail2ban_version=$(fail2ban-server -V) panel-cli app write fail2ban stable ${fail2ban_version} fi } clear echo -e $LOGO # 设置安装目录 while true; do read -p "请输入安装目录(默认/www)(Enter the installation directory (default /www)): " setup_path setup_path=${setup_path:-/www} # 检查目录是否是绝对路径 if [ "${setup_path:0:1}" != "/" ]; then echo "请输入绝对路径(Please enter an absolute path)" else break fi done # 安装确认 read -p "面板将安装至 ${setup_path} 目录,请输入 y 并回车以开始安装 (Enter 'y' to start installation): " install if [ "$install" != 'y' ]; then echo "输入不正确,已退出安装。" echo "Incorrect input, installation has been exited." exit fi clear echo -e $LOGO echo "安装面板依赖软件(如报错请检查软件源是否正常)" echo "Installing panel dependency software (if error, please check the software source)" echo -e $HR sleep 1s Prepare_System Auto_Swap echo -e $LOGO echo "安装面板运行环境(视网络情况可能需要较长时间)" echo "Installing the panel running environment (may take a long time depending on the network)" echo -e $HR sleep 1s Init_Panel Auto_Mark_Installed clear echo -e $LOGO echo '面板安装成功!' echo -e $HR panel-cli info cd ${current_path} rm -f install.sh