From d4de25eb4e95f9380371507f399307c444048cec Mon Sep 17 00:00:00 2001 From: outlook84 <96007761+outlook84@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:47:05 +0800 Subject: [PATCH 1/5] feat(systemd): Harden openlist service unit Strengthen the security of the `openlist.service` systemd unit by applying a comprehensive set of modern sandboxing and hardening directives. This change sandboxes the process to limit its potential impact in case of a compromise. Key improvements include: - Restricting filesystem access (`ProtectSystem`, `PrivateTmp`, `PrivateDevices`) - Limiting the kernel attack surface with a strict system call filter - Preventing privilege escalation (`NoNewPrivileges`) - Dropping unnecessary capabilities (`CapabilityBoundingSet`) - Isolating the process from devices, IPC, and other system resources Additionally, the network dependency is updated to `network-online.target` to ensure the network is fully available before the service starts. --- script/v4.sh | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/script/v4.sh b/script/v4.sh index 3baf46b..6cd066f 100644 --- a/script/v4.sh +++ b/script/v4.sh @@ -732,14 +732,38 @@ INIT() { cat >/etc/systemd/system/openlist.service < Date: Fri, 15 Aug 2025 18:38:27 +0800 Subject: [PATCH 2/5] feat: Allow custom user/group for systemd service Introduce `SYSTEMD_USER` and `SYSTEMD_GROUP` variables to configure the systemd service user. Implement `parse_install_args` to allow specifying a custom installation path and user:group via command-line arguments. Update the systemd service unit to run OpenList under the specified user and group, adjusting capabilities and `ProtectSystem` accordingly. --- script/v4.sh | 117 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 99 insertions(+), 18 deletions(-) diff --git a/script/v4.sh b/script/v4.sh index 6cd066f..176b620 100644 --- a/script/v4.sh +++ b/script/v4.sh @@ -32,6 +32,10 @@ CYAN_COLOR='\e[1;36m' PURPLE_COLOR='\e[1;35m' RES='\e[0m' +# systemd service user, default root +SYSTEMD_USER="root" +SYSTEMD_GROUP="root" + # CPU架构定义 declare -A ARCH_MAP=( ["x86_64"]="amd64" @@ -60,6 +64,46 @@ if [ "$(id -u)" != "0" ]; then exec sudo "bash" "$0" "$@" fi +# 解析安装参数 +parse_install_args() { + if [ -n "$2" ]; then + if [[ "$2" == *":"* ]]; then + _USER_GROUP_ARG="$2" + else + INSTALL_PATH_FROM_ARGS="$2" + fi + fi + if [ -n "$3" ]; then + if [[ "$3" == *":"* ]]; then + if [ -n "$_USER_GROUP_ARG" ]; then + echo -e "${RED_COLOR}错误:提供了两个用户:用户组参数${RES}" >&2 + exit 1 + fi + _USER_GROUP_ARG="$3" + else + if [ -n "$INSTALL_PATH_FROM_ARGS" ]; then + echo -e "${RED_COLOR}错误:提供了两个路径参数${RES}" >&2 + exit 1 + fi + INSTALL_PATH_FROM_ARGS="$3" + fi + fi + + if [ -n "$_USER_GROUP_ARG" ]; then + SYSTEMD_USER=$(echo "$_USER_GROUP_ARG" | cut -d':' -f1) + SYSTEMD_GROUP=$(echo "$_USER_GROUP_ARG" | cut -d':' -f2) + if [ -z "$SYSTEMD_USER" ]; then SYSTEMD_USER="root"; fi + if [ -z "$SYSTEMD_GROUP" ]; then SYSTEMD_GROUP="$SYSTEMD_USER"; fi + fi +} + +INSTALL_PATH_FROM_ARGS="" +_USER_GROUP_ARG="" + +if [ "$1" = "install" ]; then + parse_install_args "$@" +fi + # 获取安装路径 get_install_path() { echo "/opt/openlist" @@ -147,10 +191,8 @@ GET_INSTALLED_PATH() { } # 设置安装路径 -if [ ! -n "$2" ]; then - INSTALL_PATH=$(get_install_path) -else - INSTALL_PATH=${2%/} +if [ -n "$INSTALL_PATH_FROM_ARGS" ]; then + INSTALL_PATH=${INSTALL_PATH_FROM_ARGS%/} if ! [[ $INSTALL_PATH == */openlist ]]; then INSTALL_PATH="$INSTALL_PATH/openlist" fi @@ -169,6 +211,8 @@ else echo -e "${RED_COLOR}错误:目录 $parent_dir 没有写入权限${RES}" exit 1 fi +else + INSTALL_PATH=$(get_install_path) fi # 如果是更新或卸载操作,使用已安装的路径 @@ -697,13 +741,20 @@ INSTALL() { chmod +x $INSTALL_PATH/openlist - # 获取初始账号密码(临时切换目录) - cd $INSTALL_PATH - ACCOUNT_INFO=$($INSTALL_PATH/openlist admin random 2>&1) + # Run as root in a subshell to create data directory and get admin info. + ACCOUNT_INFO=$( (cd "$INSTALL_PATH" && ./openlist admin random) 2>&1 ) + ADMIN_USER=$(echo "$ACCOUNT_INFO" | grep "username:" | sed 's/.*username://' | tr -d ' ') ADMIN_PASS=$(echo "$ACCOUNT_INFO" | grep "password:" | sed 's/.*password://' | tr -d ' ') - # 切回原目录 - cd "$CURRENT_DIR" + + # If a non-root user is specified, change the ownership of the data directory. + if [ "$SYSTEMD_USER" != "root" ] && [ "$SYSTEMD_USER" != "0" ]; then + echo -e "${GREEN_COLOR}为用户 ${SYSTEMD_USER}:${SYSTEMD_GROUP} 设置目录权限: $INSTALL_PATH ${RES}" + if ! chown -R "${SYSTEMD_USER}:${SYSTEMD_GROUP}" "$INSTALL_PATH"; then + echo -e "${RED_COLOR}错误:无法设置目录权限,请检查用户 ${SYSTEMD_USER} 或用户组 ${SYSTEMD_GROUP} 是否存在${RES}" + exit 1 + fi + fi else echo -e "${RED_COLOR}安装失败!${RES}" rm -rf "$INSTALL_PATH" @@ -728,20 +779,30 @@ INIT() { exit 1 fi + local cap_dac_override="" + local protect_system="true" + if [ "$SYSTEMD_USER" = "root" ] || [ "$SYSTEMD_USER" = "0" ]; then + cap_dac_override=" CAP_DAC_OVERRIDE" + else + protect_system="full" + fi + # 创建 systemd 服务文件 cat >/etc/systemd/system/openlist.service < Date: Sun, 17 Aug 2025 06:43:32 +0800 Subject: [PATCH 3/5] feat(script): Add permission checks and fix version file path - Add a `CHECK_PERMISSIONS` function to verify that a non-root user has sufficient permissions to access the installation path before proceeding. - Make the `.version` file path relative to the installation directory instead of hardcoded. --- script/v4.sh | 95 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 22 deletions(-) diff --git a/script/v4.sh b/script/v4.sh index 176b620..86c3b3f 100644 --- a/script/v4.sh +++ b/script/v4.sh @@ -32,7 +32,7 @@ CYAN_COLOR='\e[1;36m' PURPLE_COLOR='\e[1;35m' RES='\e[0m' -# systemd service user, default root +# systemd 服务用户, 默认为 root SYSTEMD_USER="root" SYSTEMD_GROUP="root" @@ -155,7 +155,6 @@ fi # GitHub 相关配置 GITHUB_REPO="OpenListTeam/OpenList" VERSION_TAG="beta" -VERSION_FILE="/opt/openlist/.version" GH_DOWNLOAD_URL="${GH_PROXY}https://github.com/OpenListTeam/OpenList/releases/latest/download" # Docker 配置 @@ -275,6 +274,40 @@ CHECK() { echo -e "${GREEN_COLOR}安装目录准备就绪:$INSTALL_PATH${RES}" } +CHECK_PERMISSIONS() { + # 如果指定了非 root 用户,首先检查是否可以访问该路径。 + if [ "$SYSTEMD_USER" != "root" ] && [ "$SYSTEMD_USER" != "0" ]; then + echo -e "${BLUE_COLOR}为用户 ${SYSTEMD_USER} 检查路径权限...${RES}" + # 检查服务用户的父目录权限。 + if [ "$INSTALL_PATH" = "/opt/openlist" ]; then + # 对于默认路径,确保 /opt 是可遍历的。 + chmod a+rx /opt + else + # 对于自定义路径,检查权限而不更改它们。 + local parent_dir + parent_dir=$(dirname "$INSTALL_PATH") + while [ "$parent_dir" != "/" ] && [ -n "$parent_dir" ]; do + local sudo_user + if [[ "$SYSTEMD_USER" =~ ^[0-9]+$ ]]; then + sudo_user="#$SYSTEMD_USER" + else + sudo_user="$SYSTEMD_USER" + fi + if ! sudo -u "$sudo_user" test -x "$parent_dir" 2>/dev/null; then + echo -e "${RED_COLOR}错误:用户 '$SYSTEMD_USER' 没有权限访问安装路径的父目录 '$parent_dir'。${RES}" >&2 + echo -e "${YELLOW_COLOR}请为该用户授予 '$parent_dir' 目录及其所有上级目录的执行和读取权限 (e.g., chmod a+rx '$parent_dir') 后重试。${RES}" >&2 + # 触发安装失败 + echo -e "${RED_COLOR}安装失败!${RES}" + rm -rf "$INSTALL_PATH" + mkdir -p "$INSTALL_PATH" + exit 1 + fi + parent_dir=$(dirname "$parent_dir") + done + fi + fi +} + # 添加全局变量存储账号密码 ADMIN_USER="" ADMIN_PASS="" @@ -558,7 +591,7 @@ docker_password() { } # Update -# 设置自动Update +# 设置自动 Update setup_auto_update() { echo -e "${GREEN_COLOR}设置定时自动更新${RES}" @@ -623,9 +656,9 @@ check_system_status() { fi # 显示版本信息 - if [ -f "$VERSION_FILE" ]; then - local version=$(head -n1 "$VERSION_FILE" 2>/dev/null) - local install_time=$(tail -n1 "$VERSION_FILE" 2>/dev/null) + if [ -f "$INSTALL_PATH/.version" ]; then + local version=$(head -n1 "$INSTALL_PATH/.version" 2>/dev/null) + local install_time=$(tail -n1 "$INSTALL_PATH/.version" 2>/dev/null) echo -e "${GREEN_COLOR}● 当前版本:${RES}$version" echo -e "${GREEN_COLOR}● 安装时间:${RES}$install_time" else @@ -741,17 +774,33 @@ INSTALL() { chmod +x $INSTALL_PATH/openlist - # Run as root in a subshell to create data directory and get admin info. + # 以 root 身份运行获取管理员信息。 + echo -e "${GREEN_COLOR}生成管理员账号...${RES}" ACCOUNT_INFO=$( (cd "$INSTALL_PATH" && ./openlist admin random) 2>&1 ) ADMIN_USER=$(echo "$ACCOUNT_INFO" | grep "username:" | sed 's/.*username://' | tr -d ' ') ADMIN_PASS=$(echo "$ACCOUNT_INFO" | grep "password:" | sed 's/.*password://' | tr -d ' ') - # If a non-root user is specified, change the ownership of the data directory. + # 检查管理员信息是否成功生成 + if [ -z "$ADMIN_PASS" ]; then + echo -e "${RED_COLOR}错误:生成管理员账号失败。输出如下:${RES}" >&2 + echo "$ACCOUNT_INFO" >&2 + # 触发安装失败程序 + echo -e "${RED_COLOR}安装失败!${RES}" + rm -rf "$INSTALL_PATH" + mkdir -p "$INSTALL_PATH" + exit 1 + fi + + # 如果指定了非 root 用户,现在更改目录的所有权。 if [ "$SYSTEMD_USER" != "root" ] && [ "$SYSTEMD_USER" != "0" ]; then echo -e "${GREEN_COLOR}为用户 ${SYSTEMD_USER}:${SYSTEMD_GROUP} 设置目录权限: $INSTALL_PATH ${RES}" if ! chown -R "${SYSTEMD_USER}:${SYSTEMD_GROUP}" "$INSTALL_PATH"; then echo -e "${RED_COLOR}错误:无法设置目录权限,请检查用户 ${SYSTEMD_USER} 或用户组 ${SYSTEMD_GROUP} 是否存在${RES}" + # 触发安装失败 + echo -e "${RED_COLOR}安装失败!${RES}" + rm -rf "$INSTALL_PATH" + mkdir -p "$INSTALL_PATH" exit 1 fi fi @@ -765,8 +814,8 @@ INSTALL() { # 获取并记录真实版本信息 echo -e "${GREEN_COLOR}获取版本信息...${RES}" REAL_VERSION=$(curl -s "https://api.github.com/repos/OpenListTeam/OpenList/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' 2>/dev/null || echo "$VERSION_TAG") - echo "$REAL_VERSION" > "$VERSION_FILE" - echo "$(date '+%Y-%m-%d %H:%M:%S')" >> "$VERSION_FILE" + echo "$REAL_VERSION" > "$INSTALL_PATH/.version" + echo "$(date '+%Y-%m-%d %H:%M:%S')" >> "$INSTALL_PATH/.version" # 清理临时文件 rm -f /tmp/openlist* @@ -835,7 +884,7 @@ EOF } SUCCESS() { - clear # 只在开始时清屏一次 + clear # 只在开始时清屏一次 print_line() { local text="$1" local width=50 @@ -850,8 +899,8 @@ SUCCESS() { # 获取版本信息 local version_info="UNKNOWN" - if [ -f "$VERSION_FILE" ]; then - version_info=$(head -n1 "$VERSION_FILE" 2>/dev/null) + if [ -f "$INSTALL_PATH/.version" ]; then + version_info=$(head -n1 "$INSTALL_PATH/.version" 2>/dev/null) elif [ ! -z "$REAL_VERSION" ]; then version_info="$REAL_VERSION" fi @@ -871,7 +920,7 @@ SUCCESS() { print_line "默认账号:$ADMIN_USER" print_line "初始密码:$ADMIN_PASS" fi - echo -e "└────────────────────────────────────────────────────┘" + echo -e "└───────────────────────────────────────────────────┘" # 安装命令行工具 if ! INSTALL_CLI; then @@ -884,7 +933,7 @@ SUCCESS() { echo -e "\n${YELLOW_COLOR}温馨提示:如果端口无法访问,请检查服务器安全组、防火墙和服务状态${RES}" echo - exit 0 # 直接退出,不再返回菜单 + exit 0 # 直接退出,不再返回菜单 } UPDATE() { @@ -907,7 +956,7 @@ UPDATE() { GH_DOWNLOAD_URL="${GH_PROXY}https://github.com/OpenListTeam/OpenList/releases/latest/download" echo -e "${GREEN_COLOR}已使用代理地址: $GH_PROXY${RES}" else - # 如果不需要代理,直接使用默认链接 + # 果不需要代理,直接使用默认链接 GH_DOWNLOAD_URL="https://github.com/OpenListTeam/OpenList/releases/latest/download" echo -e "${GREEN_COLOR}使用默认 GitHub 地址进行下载${RES}" fi @@ -971,8 +1020,8 @@ UPDATE() { # 获取并更新真实版本信息 echo -e "${GREEN_COLOR}获取版本信息...${RES}" REAL_VERSION=$(curl -s "https://api.github.com/repos/OpenListTeam/OpenList/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' 2>/dev/null || echo "$VERSION_TAG") - echo "$REAL_VERSION" > "$VERSION_FILE" - echo "$(date '+%Y-%m-%d %H:%M:%S')" >> "$VERSION_FILE" + echo "$REAL_VERSION" > "$INSTALL_PATH/.version" + echo "$(date '+%Y-%m-%d %H:%M:%S')" >> "$INSTALL_PATH/.version" # 清理临时文件 rm -f /tmp/openlist.tar.gz /tmp/openlist.bak @@ -986,8 +1035,8 @@ UPDATE() { # 获取并显示版本信息 local version_info="未知" - if [ -f "$VERSION_FILE" ]; then - version_info=$(head -n1 "$VERSION_FILE" 2>/dev/null) + if [ -f "$INSTALL_PATH/.version" ]; then + version_info=$(head -n1 "$INSTALL_PATH/.version" 2>/dev/null) elif [ ! -z "$REAL_VERSION" ]; then version_info="$REAL_VERSION" fi @@ -1378,11 +1427,11 @@ SHOW_MENU() { case "$choice" in 1) - read -p "请输入安装路径 (默认: /opt/openlist): " custom_path + read -p "请输入安装路径 (按 Enter 键使用默认路径: /opt/openlist): " custom_path if [ -n "$custom_path" ]; then INSTALL_PATH_FROM_ARGS="$custom_path" fi - read -p "请输入运行用户:用户组 (uid:gid, 默认: root:root): " custom_user + read -p "请输入运行用户:用户组 (uid:gid, 按 Enter 键使用默认用户用户组: root:root): " custom_user if [ -n "$custom_user" ]; then SYSTEMD_USER=$(echo "$custom_user" | cut -d':' -f1) SYSTEMD_GROUP=$(echo "$custom_user" | cut -d':' -f2) @@ -1402,6 +1451,7 @@ SHOW_MENU() { check_disk_space CHECK + CHECK_PERMISSIONS INSTALL INIT SUCCESS @@ -1582,6 +1632,7 @@ if [ $# -eq 0 ]; then elif [ "$1" = "install" ]; then check_disk_space CHECK + CHECK_PERMISSIONS INSTALL INIT SUCCESS From 875cdcc52747c0c96dcbf66da0308eef4bf94f3a Mon Sep 17 00:00:00 2001 From: outlook84 <96007761+outlook84@users.noreply.github.com> Date: Sun, 17 Aug 2025 07:00:31 +0800 Subject: [PATCH 4/5] Refactor(script): The read (`r`) permission is not needed for parent directory. Only execute (`x`) permission is required for a service user to traverse a directory path to access its subdirectories. The read (`r`) permission is not necessary for this purpose. --- script/v4.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/v4.sh b/script/v4.sh index 86c3b3f..166a36d 100644 --- a/script/v4.sh +++ b/script/v4.sh @@ -281,7 +281,7 @@ CHECK_PERMISSIONS() { # 检查服务用户的父目录权限。 if [ "$INSTALL_PATH" = "/opt/openlist" ]; then # 对于默认路径,确保 /opt 是可遍历的。 - chmod a+rx /opt + chmod a+x /opt else # 对于自定义路径,检查权限而不更改它们。 local parent_dir @@ -295,7 +295,7 @@ CHECK_PERMISSIONS() { fi if ! sudo -u "$sudo_user" test -x "$parent_dir" 2>/dev/null; then echo -e "${RED_COLOR}错误:用户 '$SYSTEMD_USER' 没有权限访问安装路径的父目录 '$parent_dir'。${RES}" >&2 - echo -e "${YELLOW_COLOR}请为该用户授予 '$parent_dir' 目录及其所有上级目录的执行和读取权限 (e.g., chmod a+rx '$parent_dir') 后重试。${RES}" >&2 + echo -e "${YELLOW_COLOR}请为该用户授予 '$parent_dir' 目录及其所有上级目录的执行权限 (e.g., chmod a+x '$parent_dir') 后重试。${RES}" >&2 # 触发安装失败 echo -e "${RED_COLOR}安装失败!${RES}" rm -rf "$INSTALL_PATH" From 16081c3eab3c3f6a0ed69a2008a60638748d7680 Mon Sep 17 00:00:00 2001 From: outlook84 <96007761+outlook84@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:10:39 +0800 Subject: [PATCH 5/5] feat(script): Validate systemd user and group existence Add a check to verify that the user and group specified for the systemd service exist on the system before proceeding with the installation. Previously, the script would accept any user:group string, which could lead to a failed service configuration if the user or group was invalid. This commit introduces a `check_user_group` function that uses `getent` for validation. This check is now integrated into both the command-line argument parsing (`--user`) and the interactive menu, ensuring a more robust installation process by preventing setup with non-existent credentials. --- script/v4.sh | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/script/v4.sh b/script/v4.sh index 166a36d..5a0a9fd 100644 --- a/script/v4.sh +++ b/script/v4.sh @@ -94,7 +94,36 @@ parse_install_args() { SYSTEMD_GROUP=$(echo "$_USER_GROUP_ARG" | cut -d':' -f2) if [ -z "$SYSTEMD_USER" ]; then SYSTEMD_USER="root"; fi if [ -z "$SYSTEMD_GROUP" ]; then SYSTEMD_GROUP="$SYSTEMD_USER"; fi + + if ! check_user_group "$SYSTEMD_USER" "$SYSTEMD_GROUP"; then + exit 1 + fi + fi +} + +# 检查用户和用户组是否存在 +check_user_group() { + local user="$1" + local group="$2" + local has_error=0 + + # 检查用户是否存在 + if ! getent passwd "$user" &>/dev/null; then + echo -e "${RED_COLOR}错误:用户 '$user' 不存在。${RES}" >&2 + has_error=1 + fi + + # 检查用户组是否存在 + if ! getent group "$group" &>/dev/null; then + echo -e "${RED_COLOR}错误:用户组 '$group' 不存在。${RES}" >&2 + has_error=1 + fi + + if [ "$has_error" -eq 1 ]; then + return 1 fi + + return 0 } INSTALL_PATH_FROM_ARGS="" @@ -956,7 +985,7 @@ UPDATE() { GH_DOWNLOAD_URL="${GH_PROXY}https://github.com/OpenListTeam/OpenList/releases/latest/download" echo -e "${GREEN_COLOR}已使用代理地址: $GH_PROXY${RES}" else - # 果不需要代理,直接使用默认链接 + # 如果不需要代理,直接使用默认链接 GH_DOWNLOAD_URL="https://github.com/OpenListTeam/OpenList/releases/latest/download" echo -e "${GREEN_COLOR}使用默认 GitHub 地址进行下载${RES}" fi @@ -1437,6 +1466,10 @@ SHOW_MENU() { SYSTEMD_GROUP=$(echo "$custom_user" | cut -d':' -f2) if [ -z "$SYSTEMD_USER" ]; then SYSTEMD_USER="root"; fi if [ -z "$SYSTEMD_GROUP" ]; then SYSTEMD_GROUP="$SYSTEMD_USER"; fi + + if ! check_user_group "$SYSTEMD_USER" "$SYSTEMD_GROUP"; then + return 1 + fi fi # re-init install path