| Server IP : 156.238.232.47 / Your IP : 216.73.216.150 Web Server : nginx/1.25.3 System : Linux C202504152095410 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64 User : www ( 1000) PHP Version : 8.3.25 Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /www/ |
Upload File : |
#!/usr/bin/env bash
set -eo pipefail
SCRIPT_NAME="$(basename "$0")"
KEEP_CONFIG_FILES=("config.php" "database.php" "route.php")
usage() {
cat <<EOF
用法:
单站点:
bash $SCRIPT_NAME --site /www/wwwroot/site1 --apps-zip /root/apps.zip --core-zip /root/core.zip
多站点:
bash $SCRIPT_NAME --scan-root /www/wwwroot --apps-zip /root/apps.zip --core-zip /root/core.zip
说明:
- 多站点模式只扫描 scan-root 下一层目录
- 执行前会统一列出待处理站点和待删除文件,输入 YES 才继续
EOF
}
die() {
echo "[ERR] $*" >&2
exit 1
}
log() {
echo "[INFO] $*"
}
warn() {
echo "[WARN] $*" >&2
}
safe_chmod() {
local mode="$1"
local path="$2"
if [[ "$(basename "$path")" == ".user.ini" ]]; then
return 0
fi
if ! chmod "$mode" "$path" 2>/dev/null; then
warn "chmod $mode 失败,已跳过: $path"
fi
}
resolve_path() {
local p="$1"
if command -v realpath >/dev/null 2>&1; then
realpath "$p"
elif command -v readlink >/dev/null 2>&1; then
readlink -f "$p"
else
(cd "$(dirname "$p")" && printf '%s/%s\n' "$(pwd -P)" "$(basename "$p")")
fi
}
check_prerequisites() {
local cmd
for cmd in unzip find chmod cp rm mkdir mktemp mv; do
command -v "$cmd" >/dev/null 2>&1 || die "缺少必要命令: $cmd"
done
}
is_pbootcms_site() {
local site="$1"
[[ -f "$site/apps/common/version.php" ]] &&
[[ -f "$site/core/start.php" ]] &&
[[ -f "$site/index.php" ]] &&
[[ -f "$site/admin.php" ]]
}
normalize_extract_root() {
local extracted="$1"
local top_count
local -a top_entries=()
mapfile -t top_entries < <(find "$extracted" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort)
top_count="${#top_entries[@]}"
if [[ "$top_count" -eq 1 ]] && [[ -d "$extracted/${top_entries[0]}" ]]; then
printf '%s\n' "$extracted/${top_entries[0]}"
else
printf '%s\n' "$extracted"
fi
}
collect_php_files() {
local site="$1"
local -a paths=()
local dir
for dir in data rewrite static template skin; do
if [[ -d "$site/$dir" ]]; then
while IFS= read -r -d '' f; do
paths+=("$f")
done < <(find "$site/$dir" -type f \( -iname '*.php' -o -iname '*.phtml' -o -iname '*.php3' -o -iname '*.php4' -o -iname '*.php5' -o -iname '*.php7' -o -iname '*.phar' \) -print0)
fi
done
if [[ -d "$site/config" ]]; then
while IFS= read -r -d '' f; do
local base
base="$(basename "$f")"
local keep=false
local k
for k in "${KEEP_CONFIG_FILES[@]}"; do
if [[ "$base" == "$k" ]]; then
keep=true
break
fi
done
if [[ "$keep" == false ]]; then
paths+=("$f")
fi
done < <(find "$site/config" -maxdepth 1 -type f \( -iname '*.php' -o -iname '*.phtml' -o -iname '*.php3' -o -iname '*.php4' -o -iname '*.php5' -o -iname '*.php7' -o -iname '*.phar' \) -print0)
fi
if [[ "${#paths[@]}" -gt 0 ]]; then
printf '%s\0' "${paths[@]}"
fi
}
collect_sites_from_scanroot() {
local root="$1"
local -a sites=()
if is_pbootcms_site "$root"; then
sites+=("$root")
fi
while IFS= read -r -d '' d; do
if is_pbootcms_site "$d"; then
sites+=("$d")
fi
done < <(find "$root" -mindepth 1 -maxdepth 1 -type d -print0)
if [[ "${#sites[@]}" -gt 0 ]]; then
printf '%s\0' "${sites[@]}"
fi
}
collect_sites() {
local mode="$1"
local target="$2"
local -a sites=()
if [[ "$mode" == "site" ]]; then
is_pbootcms_site "$target" || die "目标目录不是 PbootCMS 站点: $target"
sites+=("$target")
else
while IFS= read -r -d '' s; do
sites+=("$s")
done < <(collect_sites_from_scanroot "$target")
[[ "${#sites[@]}" -gt 0 ]] || die "未在扫描目录中找到符合条件的 PbootCMS 站点: $target"
fi
if [[ "${#sites[@]}" -gt 0 ]]; then
printf '%s\0' "${sites[@]}"
fi
}
replace_tree_from_zip() {
local site="$1"
local zip_path="$2"
local dest_dir="$3"
local tmp_root="$4"
local new_dir="$site/.${dest_dir}.clean.$$"
[[ "$dest_dir" == "apps" || "$dest_dir" == "core" ]] || die "非法替换目录: $dest_dir"
[[ -n "$site" && "$site" != "/" ]] || die "拒绝处理危险站点路径: $site"
unzip -oq "$zip_path" -d "$tmp_root" >/dev/null
local src_dir
src_dir="$(normalize_extract_root "$tmp_root")"
rm -rf "$new_dir"
mkdir -p "$new_dir"
cp -a "$src_dir"/. "$new_dir"/
rm -rf "$site/$dest_dir"
mv "$new_dir" "$site/$dest_dir"
}
cleanup_php_files() {
local site="$1"
local -a files=()
local f
while IFS= read -r -d '' f; do
files+=("$f")
done < <(collect_php_files "$site")
for f in "${files[@]}"; do
rm -f "$f"
done
}
apply_permissions() {
local site="$1"
local path
while IFS= read -r -d '' path; do
safe_chmod 555 "$path"
done < <(find "$site" -type d -print0)
while IFS= read -r -d '' path; do
safe_chmod 555 "$path"
done < <(find "$site" -type f -print0)
for dir in runtime static data; do
if [[ -e "$site/$dir" ]]; then
while IFS= read -r -d '' path; do
safe_chmod 755 "$path"
done < <(find "$site/$dir" -print0)
fi
done
}
print_plan() {
local site="$1"
local apps_zip="$2"
local core_zip="$3"
echo "站点: $site"
echo " 替换: apps <- $apps_zip"
echo " 替换: core <- $core_zip"
echo " 删除:"
local -a files=()
local f
while IFS= read -r -d '' f; do
files+=("$f")
done < <(collect_php_files "$site")
if [[ "${#files[@]}" -eq 0 ]]; then
echo " (无)"
else
for f in "${files[@]}"; do
echo " $f"
done
fi
}
main() {
local mode=""
local site_arg=""
local scan_root_arg=""
local apps_zip_arg=""
local core_zip_arg=""
while [[ $# -gt 0 ]]; do
case "$1" in
--site)
[[ -z "$mode" ]] || die "不能同时指定 --site 和 --scan-root"
[[ $# -ge 2 ]] || die "--site 缺少参数"
mode="site"
site_arg="${2:-}"
shift 2
;;
--scan-root)
[[ -z "$mode" ]] || die "不能同时指定 --site 和 --scan-root"
[[ $# -ge 2 ]] || die "--scan-root 缺少参数"
mode="scan"
scan_root_arg="${2:-}"
shift 2
;;
--apps-zip)
[[ $# -ge 2 ]] || die "--apps-zip 缺少参数"
apps_zip_arg="${2:-}"
shift 2
;;
--core-zip)
[[ $# -ge 2 ]] || die "--core-zip 缺少参数"
core_zip_arg="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "未知参数: $1"
;;
esac
done
[[ -n "$mode" ]] || { usage; die "请指定 --site 或 --scan-root"; }
[[ -n "$apps_zip_arg" ]] || die "缺少 --apps-zip"
[[ -n "$core_zip_arg" ]] || die "缺少 --core-zip"
check_prerequisites
[[ -f "$apps_zip_arg" ]] || die "apps.zip 不存在: $apps_zip_arg"
[[ -f "$core_zip_arg" ]] || die "core.zip 不存在: $core_zip_arg"
local apps_zip core_zip target_root
apps_zip="$(resolve_path "$apps_zip_arg")"
core_zip="$(resolve_path "$core_zip_arg")"
unzip -tq "$apps_zip" >/dev/null || die "apps.zip 校验失败: $apps_zip"
unzip -tq "$core_zip" >/dev/null || die "core.zip 校验失败: $core_zip"
if [[ "$mode" == "site" ]]; then
[[ -n "$site_arg" ]] || die "缺少 --site 参数"
[[ -d "$site_arg" ]] || die "站点目录不存在: $site_arg"
target_root="$(resolve_path "$site_arg")"
else
[[ -n "$scan_root_arg" ]] || die "缺少 --scan-root 参数"
[[ -d "$scan_root_arg" ]] || die "扫描目录不存在: $scan_root_arg"
target_root="$(resolve_path "$scan_root_arg")"
fi
local -a sites=()
while IFS= read -r -d '' s; do
sites+=("$s")
done < <(collect_sites "$mode" "$target_root")
log "待处理站点数量: ${#sites[@]}"
echo
for site in "${sites[@]}"; do
print_plan "$site" "$apps_zip" "$core_zip"
echo
done
read -r -p "确认开始清理并替换吗?输入 YES 继续: " confirm
[[ "$confirm" == "YES" ]] || { warn "已取消"; exit 0; }
local site tmp_root apps_tmp core_tmp
for site in "${sites[@]}"; do
log "开始处理: $site"
tmp_root="$(mktemp -d)"
apps_tmp="$tmp_root/apps"
core_tmp="$tmp_root/core"
mkdir -p "$apps_tmp" "$core_tmp"
replace_tree_from_zip "$site" "$apps_zip" "apps" "$apps_tmp"
replace_tree_from_zip "$site" "$core_zip" "core" "$core_tmp"
cleanup_php_files "$site"
apply_permissions "$site"
rm -rf "$tmp_root"
log "完成: $site"
done
log "全部处理完成"
}
main "$@"