diff --git a/app/console/commands/panel.go b/app/console/commands/panel.go index 51477cdd..22a5304f 100644 --- a/app/console/commands/panel.go +++ b/app/console/commands/panel.go @@ -2,7 +2,6 @@ package commands import ( "os" - "regexp" "github.com/gookit/color" "github.com/spf13/cast" @@ -106,29 +105,19 @@ func (receiver *Panel) Handle(ctx console.Context) error { return nil } + port := helper.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'") + color.Greenln("用户名: " + user.Username) color.Greenln("密码: " + password) - // color.Greenln("面板端口: " + port) - color.Greenln("面板入口: " + services.NewSettingImpl().Get("panel_entrance", "/")) + color.Greenln("面板端口: " + port) + color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyPanelEntrance, "/")) case "getPort": - nginxConf, err := os.ReadFile("/www/server/nginx/conf/nginx.conf") - if err != nil { - color.Redln("获取面板端口失败,请检查Nginx主配置文件") - return nil - } - - match := regexp.MustCompile(`listen\s+(\d+)`).FindStringSubmatch(string(nginxConf)) - if len(match) < 2 { - color.Redln("获取面板端口失败,请检查Nginx主配置文件") - return nil - } - - port := match[1] + port := helper.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'") color.Greenln("面板端口: " + port) case "getEntrance": - color.Greenln("面板入口: " + services.NewSettingImpl().Get("panel_entrance", "/")) + color.Greenln("面板入口: " + services.NewSettingImpl().Get(models.SettingKeyPanelEntrance, "/")) case "writePlugin": slug := arg1 diff --git a/app/http/controllers/cron_controller.go b/app/http/controllers/cron_controller.go index 3fcc46c7..4c0242ab 100644 --- a/app/http/controllers/cron_controller.go +++ b/app/http/controllers/cron_controller.go @@ -60,18 +60,14 @@ func (r *CronController) Add(ctx http.Context) { shellDir := "/www/server/cron/" shellLogDir := "/www/server/cron/logs/" if !helper.Exists(shellDir) { - if !helper.Mkdir(shellDir, 0644) { - facades.Log().Error("[面板][CronController] 创建计划任务目录失败 ", err) - Error(ctx, http.StatusInternalServerError, "系统内部错误") - return - } + facades.Log().Error("[面板][CronController] 计划任务目录不存在") + Error(ctx, http.StatusInternalServerError, "计划任务目录不存在") + return } if !helper.Exists(shellLogDir) { - if !helper.Mkdir(shellLogDir, 0644) { - facades.Log().Error("[面板][CronController] 创建计划任务日志目录失败 ", err) - Error(ctx, http.StatusInternalServerError, "系统内部错误") - return - } + facades.Log().Error("[面板][CronController] 计划任务日志目录不存在") + Error(ctx, http.StatusInternalServerError, "计划任务日志目录不存在") + return } shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + helper.RandomString(16) if !helper.WriteFile(shellDir+shellFile+".sh", ctx.Request().Input("script"), 0644) { diff --git a/app/http/controllers/setting_controller.go b/app/http/controllers/setting_controller.go index a85b5cb9..0b614498 100644 --- a/app/http/controllers/setting_controller.go +++ b/app/http/controllers/setting_controller.go @@ -3,6 +3,7 @@ package controllers import ( "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" + "panel/packages/helper" "panel/app/models" "panel/app/services" @@ -69,12 +70,9 @@ func (r *SettingController) Save(ctx http.Context) { return } - err = r.setting.Set(models.SettingKeyPort, port) - if err != nil { - facades.Log().Error("[面板][SettingController] 保存设置失败 ", err) - Error(ctx, http.StatusInternalServerError, "系统内部错误") - - return + oldPort := helper.ExecShell("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'") + if oldPort != port { + helper.ExecShell("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf") } err = r.setting.Set(models.SettingKeyBackupPath, backupPath) if err != nil { diff --git a/app/models/setting.go b/app/models/setting.go index 92c70d73..68c90170 100644 --- a/app/models/setting.go +++ b/app/models/setting.go @@ -4,7 +4,6 @@ import "github.com/goravel/framework/support/carbon" const ( SettingKeyName = "name" - SettingKeyPort = "port" SettingKeyMonitor = "monitor" SettingKeyMonitorDays = "monitor_days" SettingKeyBackupPath = "backup_path" diff --git a/app/providers/validation_service_provider.go b/app/providers/validation_service_provider.go index 919feea7..20878e26 100644 --- a/app/providers/validation_service_provider.go +++ b/app/providers/validation_service_provider.go @@ -26,6 +26,5 @@ func (receiver *ValidationServiceProvider) rules() []validation.Rule { &rules.Exists{}, &rules.NotExists{}, &rules.Captcha{}, - &rules.Regex{}, } } diff --git a/app/rules/regex.go b/app/rules/regex.go deleted file mode 100644 index 16dc2cfb..00000000 --- a/app/rules/regex.go +++ /dev/null @@ -1,39 +0,0 @@ -package rules - -import ( - "regexp" - - "github.com/goravel/framework/contracts/validation" -) - -type Regex struct { -} - -// Signature The name of the rule. -func (receiver *Regex) Signature() string { - return "regex" -} - -// Passes Determine if the validation rule passes. -func (receiver *Regex) Passes(data validation.Data, val any, options ...any) bool { - // 第一个参数,正则表达式 - regex := options[0].(string) - // 用户请求过来的数据 - requestValue, ok := val.(string) - if !ok { - return false - } - - // 判断是否为空 - if len(requestValue) == 0 { - return false - } - - // 判断是否匹配 - return regexp.MustCompile(regex).MatchString(requestValue) -} - -// Message Get the validation error message. -func (receiver *Regex) Message() string { - return "格式不正确" -} diff --git a/scripts/install_panel.sh b/scripts/install_panel.sh new file mode 100644 index 00000000..b476d567 --- /dev/null +++ b/scripts/install_panel.sh @@ -0,0 +1,267 @@ +#!/bin/bash +export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH + +: ' +Copyright 2022 HaoZi Technology Co., Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +' + +LOGO="+----------------------------------------------------\n| 耗子面板安装脚本\n+----------------------------------------------------\n| Copyright © 2022-"$(date +%Y)" 耗子科技 All rights reserved.\n+----------------------------------------------------" +HR="+----------------------------------------------------" +download_Url="" +setup_Path="/www" +sshPort=$(cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}') +ipLocation=$(curl -s https://ip.ping0.cc/geo) + +Prepare_system() { + if [ $(whoami) != "root" ]; then + echo -e $HR + echo "错误:请使用root用户运行安装命令。" + exit 1 + fi + + ARCH=$(uname -m) + OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown") + if [ "${OS}" == "unknown" ]; then + echo -e $HR + echo "错误:该系统不支持安装耗子面板,请更换Debian12/RHEL9安装。" + exit 1 + fi + if [ "${ARCH}" != "x86_64" ] && [ "${ARCH}" != "aarch64" ]; then + echo -e $HR + echo "错误:该系统架构不支持安装耗子面板,请更换x86_64/aarch64架构安装。" + exit 1 + fi + + is64bit=$(getconf LONG_BIT) + if [ "${is64bit}" != '64' ]; then + echo -e $HR + echo "错误:32位系统不支持安装耗子面板,请更换64位系统安装。" + exit 1 + fi + + isInstalled=$(systemctl status panel 2>&1 | grep "Active") + if [ "${isInstalled}" != "" ]; then + echo -e $HR + echo "错误:耗子面板已安装,请勿重复安装。" + exit 1 + fi + + wwwUserCheck=$(cat /etc/passwd | grep www) + if [ "${wwwUserCheck}" == "" ]; then + groupadd www + useradd -s /sbin/nologin -g www www + fi + + rm -rf /etc/localtime + ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + + [ -s /etc/selinux/config ] && sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config + setenforce 0 >/dev/null 2>&1 + + ulimit -n 204800 + echo 6553560 >/proc/sys/fs/file-max + checkSoftNofile=$(cat /etc/security/limits.conf | grep '^* soft nofile .*$') + checkHardNofile=$(cat /etc/security/limits.conf | grep '^* hard nofile .*$') + checkSoftNproc=$(cat /etc/security/limits.conf | grep '^* soft nproc .*$') + checkHardNproc=$(cat /etc/security/limits.conf | grep '^* hard nproc .*$') + checkFsFileMax=$(cat /etc/sysctl.conf | grep '^fs.file-max.*$') + if [ "${checkSoftNofile}" == "" ]; then + echo "* soft nofile 204800" >>/etc/security/limits.conf + fi + if [ "${checkHardNofile}" == "" ]; then + echo "* hard nofile 204800" >>/etc/security/limits.conf + fi + if [ "${checkSoftNproc}" == "" ]; then + echo "* soft nproc 204800" >>/etc/security/limits.conf + fi + if [ "${checkHardNproc}" == "" ]; then + echo "* hard nproc 204800 " >>/etc/security/limits.conf + fi + if [ "${checkFsFileMax}" == "" ]; then + echo fs.file-max = 6553560 >>/etc/sysctl.conf + fi + + if [ "${OS}" == "centos" ]; then + if [[ ${ipLocation} =~ "中国" ]]; then + sed -e 's|^mirrorlist=|#mirrorlist=|g' \ + -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \ + -i.bak \ + /etc/yum.repos.d/[Rr]ocky*.repo + sed -e 's|^mirrorlist=|#mirrorlist=|g' \ + -e 's|^# baseurl=https://repo.almalinux.org|baseurl=https://mirrors.aliyun.com|g' \ + -i.bak \ + /etc/yum.repos.d/[Aa]lmalinux*.repo + + dnf makecache + fi + dnf install dnf-plugins-core -y + dnf install epel-release -y + dnf config-manager --set-enabled epel + if [[ ${ipLocation} =~ "中国" ]]; then + sed -i 's|^#baseurl=https://download.example/pub|baseurl=https://mirrors.aliyun.com|' /etc/yum.repos.d/epel* + sed -i 's|^metalink|#metalink|' /etc/yum.repos.d/epel* + dnf makecache + fi + dnf config-manager --set-enabled PowerTools + dnf config-manager --set-enabled powertools + dnf config-manager --set-enabled CRB + dnf config-manager --set-enabled Crb + dnf config-manager --set-enabled crb + /usr/bin/crb enable + dnf makecache + dnf install -y curl wget zip unzip tar git jq git-core + elif [ "${OS}" == "debian" ]; then + if [[ ${ipLocation} =~ "中国" ]]; then + sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list + sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list + fi + apt update + apt install -y curl wget zip unzip tar git jq git + else + echo -e $HR + echo "错误:该系统不支持安装耗子面板,请更换Debian12/RHEL9安装。" + exit 1 + fi +} + +Auto_Swap() { + # 判断是否有swap + swap=$(LC_ALL=C free | grep Swap | awk '{print $2}') + if [ "${swap}" -gt 1 ]; then + return + fi + + if [ ! -d ${setup_Path} ]; then + mkdir ${setup_Path} + fi + + # 设置swap + swapFile="${setup_Path}/swap" + dd if=/dev/zero of=$swapFile bs=1M count=2048 + chmod 600 $swapFile + mkswap -f $swapFile + swapon $swapFile + echo "$swapFile swap swap defaults 0 0" >>/etc/fstab +} + +Init_Panel() { + mkdir ${setup_Path}/server/cron + mkdir ${setup_Path}/server/cron/logs + chmod -R 644 ${setup_Path}/server/cron + chmod -R 644 ${setup_Path}/server/cron/logs + mkdir ${setup_Path}/panel + rm -rf ${setup_Path}/panel/* + # 下载面板zip包并解压 + if [ "${ARCH}" == "x86_64" ]; then + panelZip=$(curl "https://api.github.com/repos/HaoZi-Team/Panel/releases/latest" | jq -r '.assets[] | select(.name | contains("amd64v3")) | .browser_download_url') + elif [ "${ARCH}" == "aarch64" ]; then + panelZip=$(curl "https://api.github.com/repos/HaoZi-Team/Panel/releases/latest" | jq -r '.assets[] | select(.name | contains("arm64")) | .browser_download_url') + else + echo -e $HR + echo "错误:该系统架构不支持安装耗子面板,请更换x86_64/aarch64架构安装。" + exit 1 + fi + wget -O ${setup_Path}/panel/panel.zip "${download_Url}${panelZip}" + cd ${setup_Path}/panel + unzip -o panel.zip + rm -rf panel.zip + cp panel-example.conf panel.conf + ${setup_Path}/panel/panel --env="panel.conf" artisan key:generate + ${setup_Path}/panel/panel --env="panel.conf" artisan jwt:secret + chmod -R 700 ${setup_Path}/panel + cp scripts/panel.sh /usr/bin/panel + chmod -R 700 /usr/bin/panel + # 防火墙放行 + if [ "${OS}" == "centos" ]; then + yum install firewalld -y + systemctl enable firewalld + systemctl start 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=8888/tcp >/dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=${sshPort}/tcp >/dev/null 2>&1 + firewall-cmd --reload + elif [ "${OS}" == "debian" ]; then + apt install ufw -y + sudo ufw enable + sudo ufw allow 22/tcp + sudo ufw allow 80/tcp + sudo ufw allow 443/tcp + sudo ufw allow 8888/tcp + sudo ufw allow ${sshPort}/tcp + fi + # 写入服务文件 + cat >/etc/systemd/system/panel.service <