📋 概述

这是一个专为 Debian 12 系统设计的 Self Service Password (SSP) 一键安装脚本,基于实际部署经验优化,能够自动处理各种常见问题并提供多种安装模式。

版本: V1.0
作者: 大刘讲IT 微信公众号同号
最后更新: 2025年6月

注: 配置文件config.inc.php是一个相当重要的文件,需要了解基本的ldap配置和邮件配置;自助链接密码找回需要配置启用邮件,AD或ldap需要mail字段需要配置好邮件地址。

🌟 Self Service Password 功能介绍

什么是 Self Service Password?

Self Service Password (SSP) 是一个开源的企业级密码自助服务系统,专为 LDAP/Active Directory 环境设计。它允许用户在无需管理员干预的情况下,自主完成密码重置、修改等操作,大大减轻了IT管理员的工作负担。

🔑 核心功能

密码管理功能
  • 🔐 密码重置:用户忘记密码时可通过邮件、短信等方式自助重置
  • 🔄 密码修改:已登录用户可直接修改当前密码
  • 📊 密码策略:支持复杂密码策略配置和实时验证
  • 密码过期提醒:主动提醒用户密码即将过期
身份验证方式
  • 📧 邮件验证:通过邮件发送重置链接或验证码
  • 📱 短信验证:支持SMS短信验证码验证
  • 安全问题:预设安全问题验证身份
  • 🔢 CAPTCHA:防止恶意攻击的图形验证码
  • 🎫 令牌验证:支持时间限制的安全令牌
目录服务支持
  • 🏢 Active Directory:完整支持微软AD环境
  • 🐧 OpenLDAP:支持各种OpenLDAP部署
  • 🌐 LDAPS/StartTLS:支持加密连接保障安全
  • 🔗 多域支持:可同时管理多个LDAP域

🎨 用户界面特性

多语言支持
  • 🌍 国际化:支持30+种语言界面
  • 🇨🇳 中文界面:完整的简体中文支持
  • 🎨 主题定制:可自定义界面主题和样式
  • 📱 响应式设计:支持桌面、平板、手机等设备
用户体验
  • 直观界面:简洁明了的操作界面
  • 🚀 快速操作:几步即可完成密码重置
  • 💡 智能提示:实时密码强度提示和格式验证
  • 🔍 搜索功能:管理员可快速查找用户

🛡️ 安全特性

安全防护
  • 🔒 加密传输:支持HTTPS加密通信
  • 🛡️ 防暴力破解:登录尝试次数限制和锁定机制
  • 📝 审计日志:详细记录所有操作日志
  • 🚫 IP限制:支持IP白名单和黑名单
  • ⏱️ 会话管理:安全的会话超时和管理
密码安全
  • 📏 复杂度要求:可配置密码长度、字符类型要求
  • 📚 历史检查:防止重复使用历史密码
  • 🚫 弱密码检测:内置常见弱密码字典
  • 🔄 强制更新:支持强制密码更新策略

🔧 管理功能

系统管理
  • ⚙️ 配置管理:Web界面配置系统参数
  • 📊 统计报表:密码重置统计和用户活动报表
  • 👥 用户管理:批量用户操作和权限管理
  • 🔔 通知系统:邮件和短信通知配置
集成能力
  • 🔌 API接口:RESTful API支持第三方集成
  • 📧 邮件系统:支持SMTP、Exchange等邮件系统
  • 📱 短信网关:集成各种短信服务提供商
  • 🔗 SSO集成:支持单点登录系统集成

📈 企业级特性

高可用性
  • 负载均衡:支持多实例负载均衡部署
  • 💾 数据备份:配置和日志数据备份机制
  • 🔄 故障恢复:快速故障检测和恢复
  • 📊 性能监控:系统性能指标监控
合规性
  • 📋 审计合规:满足企业审计要求
  • 🔐 数据保护:符合GDPR等数据保护法规
  • 📝 操作记录:完整的操作审计轨迹
  • 🛡️ 安全标准:遵循企业安全最佳实践

🎯 适用场景

企业环境
  • 🏢 中大型企业:员工自助密码管理
  • 🏫 教育机构:学生和教职工密码服务
  • 🏥 医疗机构:医护人员密码管理
  • 🏭 制造业:工厂员工密码自助服务
技术环境
  • 🖥️ 混合环境:Windows/Linux混合环境
  • ☁️ 云端部署:支持云服务器部署
  • 🏠 本地部署:内网环境独立部署
  • 🔗 混合云:混合云环境统一管理

💰 成本效益

降低成本
  • 减少工单:大幅减少密码重置工单数量
  • 👨‍💼 节省人力:减轻IT管理员工作负担
  • 📞 减少呼叫:降低IT服务台电话咨询
  • 🚀 提高效率:用户可立即解决密码问题
提升体验
  • 24/7可用:全天候自助服务
  • 即时解决:无需等待IT支持
  • 🎯 精准定位:快速定位和解决问题
  • 😊 用户满意:显著提升用户满意度

🎯 主要特性

  • 智能安装模式:支持全新安装、升级、重新安装等多种模式
  • 本地安装包支持:自动检测脚本目录中的安装包,无需网络下载
  • 错误自动修复:智能检测并修复配置文件语法错误
  • 非中断安装:配置文件问题不会阻止安装继续进行
  • 完整依赖管理:自动安装和配置所有必需的软件包
  • 安全配置:自动生成安全密钥和基本配置
  • Apache 优化:根据官方文档配置 Apache 虚拟主机

🔧 环境要求

操作系统

  • 推荐: Debian 12 (Bookworm) 全新安装
  • 兼容: Ubuntu 20.04+ 或其他基于 Debian 的发行版

硬件要求

  • CPU: 1 核心以上
  • 内存: 1GB 以上
  • 磁盘: 2GB 以上可用空间
  • 网络: 能够访问软件源(用于安装依赖包)

权限要求

  • 必须: root 用户或具有 sudo 权限的用户
  • 推荐: 直接使用 root 用户执行

软件依赖

脚本会自动安装以下软件包,无需预先安装:

  • Apache2 Web 服务器
  • PHP 8.2 及相关扩展
  • Composer PHP 包管理器
  • 其他必需的系统工具

📦 安装包准备

下载 SSP 安装包

  1. 访问 SSP 官方发布页面
  2. 下载最新稳定版本的源码包
  3. 支持的格式:.tar.gz.zip

推荐的文件名格式

  • self-service-password-1.6.tar.gz
  • self-service-password-latest.zip
  • ssp-1.6.tar.gz

文件放置

将下载的安装包放置在与安装脚本相同的目录中:

文档项目/
├── install_ssp.sh                    # 主安装脚本
├── self-service-password-1.6.tar.gz  # SSP 安装包
├── fix_config_syntax.sh              # 配置修复脚本(可选)
└── README_SSP安装脚本使用说明.md      # 本说明文档

🚀 安装步骤

1. 准备工作

# 确保以 root 用户身份登录
sudo su -

# 进入脚本目录
cd /path/to/script/directory

# 确认文件存在
ls -la install_ssp.sh self-service-password*.tar.gz

2. 赋予执行权限

chmod +x install_ssp.sh
chmod +x fix_config_syntax.sh  # 可选

3. 运行安装脚本

交互式安装(推荐)
./install_ssp.sh

脚本会自动检测现有安装并询问您的选择。

非交互式安装
# 全新安装
./install_ssp.sh --force-fresh

# 升级现有安装
./install_ssp.sh --force-upgrade

# 完全重新安装
./install_ssp.sh --force-reinstall

# 指定安装目录
./install_ssp.sh --install-dir /opt/ssp

# 非交互模式
./install_ssp.sh --non-interactive

4. 查看帮助信息

./install_ssp.sh --help

📋 安装模式说明

🆕 全新安装 (fresh)

  • 适用于全新的系统环境
  • 安装所有依赖包和 SSP
  • 创建全新的配置文件

🔄 升级模式 (upgrade)

  • 保留现有配置文件
  • 更新 SSP 代码和依赖
  • 智能跳过已安装的系统组件

🗑️ 重新安装 (reinstall)

  • 删除现有安装
  • 备份重要配置文件
  • 执行全新安装

📂 新位置安装 (new_location)

  • 在新目录安装 SSP
  • 不影响现有安装
  • 适用于测试或多实例部署

🔧 安装过程详解

第1步:环境检查

  • 检查操作系统兼容性
  • 验证网络连接
  • 检测现有安装状态

第2步:依赖安装

  • 更新系统软件包
  • 安装 Apache2 和 PHP 8.2
  • 安装必需的 PHP 扩展

第3步:SSP 部署

  • 解压安装包到指定目录
  • 智能检测 SSP 源码结构
  • 验证关键文件完整性

第4步:Composer 依赖

  • 强制全新安装 PHP 依赖
  • 自动检测并修复缺失的库
  • 优化 autoload 配置

第5步:配置文件设置

  • 生成安全的随机密钥
  • 设置基本运行参数
  • 智能修复语法错误

第6步:权限配置

  • 设置正确的文件权限
  • 创建必需的缓存目录
  • 配置 Web 服务器用户权限

第7步:Apache 配置

  • 创建专用虚拟主机
  • 启用必需的 Apache 模块
  • 配置安全访问规则

第8步:最终验证

  • 验证所有服务状态
  • 检查配置文件语法
  • 创建诊断页面

🌐 访问和使用

安装完成后的访问地址

# 核心依赖检查页面
http://your-server-ip/install_check.php

# SSP 主页
http://your-server-ip/

# 本地访问
http://localhost/

首次配置

  1. 访问 SSP 主页
  2. 根据向导配置 LDAP/AD 连接
  3. 设置密码策略和安全选项
  4. 测试功能是否正常

🛠️ 故障排除

常见问题及解决方案

1. 配置文件语法错误
# 检查语法
php -l /var/www/html/ssp/conf/config.inc.php

# 自动修复
./fix_config_syntax.sh

# 手动修复第507行错误
vim /var/www/html/ssp/conf/config.inc.php
# 将 if (!define("SMARTY" 改为 if (!defined("SMARTY"))
2. Apache 服务问题
# 检查 Apache 状态
systemctl status apache2

# 重启 Apache
systemctl restart apache2

# 检查配置语法
apache2ctl configtest

# 查看错误日志
tail -f /var/log/apache2/ssp_error.log
3. PHP 依赖问题
# 重新安装 Composer 依赖
cd /var/www/html/ssp
rm -rf vendor composer.lock
composer install --no-dev

# 检查 autoload 文件
ls -la vendor/autoload.php
4. 权限问题
# 重新设置权限
chown -R www-data:www-data /var/www/html/ssp
chmod -R 755 /var/www/html/ssp
chmod -R 750 /var/www/html/ssp/conf
chmod -R 750 /var/www/html/ssp/cache

日志文件位置

  • Apache 错误日志: /var/log/apache2/ssp_error.log
  • Apache 访问日志: /var/log/apache2/ssp_access.log
  • SSP 审计日志: /var/log/apache2/ssp_audit.log
  • 系统日志: /var/log/syslog

配置文件位置

  • 主配置文件: /var/www/html/ssp/conf/config.inc.php
  • Apache 配置: /etc/apache2/sites-available/ssp.conf
  • PHP 配置: /etc/php/8.2/apache2/php.ini

📚 高级配置

LDAP 连接配置

安装完成后,您需要手动配置 LDAP 连接参数:

# 编辑配置文件
vim /var/www/html/ssp/conf/config.inc.php

# 添加 LDAP 配置
$ldap_url = "ldap://your-ldap-server:389";\\AD域控要使用ldaps,端口636(需要部署证书服务即可)
$ldap_binddn = "cn=admin,dc=example,dc=com";
$ldap_bindpw = "your-admin-password";
$ldap_base = "dc=example,dc=com";

SSL/HTTPS 配置

# 安装 Certbot
apt-get install certbot python3-certbot-apache

# 获取 SSL 证书
certbot --apache -d your-domain.com

# 自动续期
crontab -e
# 添加:0 12 * * * /usr/bin/certbot renew --quiet

性能优化

# 启用 PHP OPcache
vim /etc/php/8.2/apache2/php.ini
# 设置:opcache.enable=1

# 配置 Apache MPM
a2dismod mpm_prefork
a2enmod mpm_event

# 重启服务
systemctl restart apache2

🔒 安全建议

基本安全措施

  1. 定期更新:保持系统和 SSP 版本最新
  2. 强密码策略:配置复杂的密码要求
  3. 访问控制:限制管理界面访问
  4. 日志监控:定期检查访问和错误日志
  5. 备份策略:定期备份配置文件和数据

防火墙配置

# 安装 UFW
apt-get install ufw

# 基本规则
ufw allow ssh
ufw allow http
ufw allow https
ufw enable

📞 支持和反馈

获取帮助

  • 官方文档: https://self-service-password.readthedocs.io/
  • GitHub 项目: https://github.com/ltb-project/self-service-password
  • 社区论坛: 相关技术论坛和社区

脚本问题反馈

如果您在使用脚本过程中遇到问题,请提供以下信息:

  1. 操作系统版本和架构
  2. 错误信息的完整输出
  3. 相关日志文件内容
  4. 使用的安装包版本

📄 许可证

本安装脚本基于实际部署经验编写,遵循开源精神,可自由使用和修改。

Self Service Password 本身遵循 GPL v3 许可证。


最后更新: 2025年6月
版本: v1.0
维护者: 大刘讲IT

#!/bin/bash

# =================================================================
# Self Service Password (SSP) One-Click Installation Script for Debian 12
#
# Author: Assistant (基于实际问题修复经验优化)
# Based on official documentation:
# - https://self-service-password.readthedocs.io/
# - https://github.com/ltb-project/self-service-password
# 
# 版本: v3.7 - 完全移除自动 LDAP 配置,仅保留必要的安全设置
# =================================================================

# --- Script Configuration ---
# SSP 安装目录
INSTALL_DIR="/var/www/html/ssp"
# Apache 配置文件路径
APACHE_CONFIG_FILE="/etc/apache2/sites-available/ssp.conf"
# 脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# --- 注意:不再自动修改 LDAP 配置 ---
# LDAP 配置请在安装完成后手动在 config.inc.php 中配置

# --- Command Line Arguments ---
FORCE_MODE=""
NON_INTERACTIVE=false

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    case $1 in
        --force-fresh)
            FORCE_MODE="fresh"
            NON_INTERACTIVE=true
            shift
            ;;
        --force-upgrade)
            FORCE_MODE="upgrade"
            NON_INTERACTIVE=true
            shift
            ;;
        --force-reinstall)
            FORCE_MODE="reinstall"
            NON_INTERACTIVE=true
            shift
            ;;
        --non-interactive)
            NON_INTERACTIVE=true
            shift
            ;;
        --install-dir)
            INSTALL_DIR="$2"
            shift 2
            ;;
        --help|-h)
            echo "Self Service Password 一键安装脚本"
            echo ""
            echo "用法: $0 [选项]"
            echo ""
            echo "选项:"
            echo "  --force-fresh        强制全新安装(忽略现有安装)"
            echo "  --force-upgrade      强制升级模式"
            echo "  --force-reinstall    强制重新安装"
            echo "  --non-interactive    非交互模式"
            echo "  --install-dir DIR    指定安装目录(默认: /var/www/html/ssp)"
            echo "  --help, -h           显示此帮助信息"
            echo ""
            echo "示例:"
            echo "  $0                           # 交互式安装"
            echo "  $0 --force-fresh             # 强制全新安装"
            echo "  $0 --force-upgrade           # 强制升级现有安装"
            echo "  $0 --install-dir /opt/ssp    # 安装到指定目录"
            exit 0
            ;;
        *)
            echo "未知参数: $1"
            echo "使用 --help 查看帮助信息"
            exit 1
            ;;
    esac
done

# 更新 Apache 配置文件路径(如果安装目录被修改)
if [ "$INSTALL_DIR" != "/var/www/html/ssp" ]; then
    APACHE_CONFIG_FILE="/etc/apache2/sites-available/$(basename "$INSTALL_DIR").conf"
fi

# --- Color output functions ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

print_success() {
    echo -e "${GREEN}$1${NC}"
}

print_error() {
    echo -e "${RED}$1${NC}"
}

print_warning() {
    echo -e "${YELLOW}⚠️  $1${NC}"
}

print_info() {
    echo -e "${BLUE}ℹ️  $1${NC}"
}

# --- Environment Check Function ---
function check_environment() {
    echo "正在检查环境要求..."
    
    # 检查操作系统
    if ! grep -q "debian" /etc/os-release && ! grep -q "ubuntu" /etc/os-release; then
        print_warning "此脚本专为 Debian/Ubuntu 系统设计"
    fi
    
    # 检查网络连接
    if ! ping -c 1 114.114.114.114 >/dev/null 2>&1; then
        print_warning "网络连接可能存在问题,某些功能可能受影响"
    fi
    
    print_success "环境检查完成"
}

# --- Installation Status Check Function ---
function check_installation_status() {
    echo "正在检查现有安装状态..."
    
    # 检查是否已经安装过
    EXISTING_INSTALL=false
    APACHE_INSTALLED=false
    PHP_INSTALLED=false
    SSP_INSTALLED=false
    
    # 检查 Apache
    if systemctl is-enabled apache2 >/dev/null 2>&1; then
        APACHE_INSTALLED=true
        print_info "检测到 Apache2 已安装"
    fi
    
    # 检查 PHP
    if command -v php >/dev/null 2>&1; then
        PHP_INSTALLED=true
        PHP_VERSION=$(php -v | head -n1 | cut -d' ' -f2 | cut -d'.' -f1,2)
        print_info "检测到 PHP 已安装 (版本: $PHP_VERSION)"
    fi
    
    # 检查 SSP 安装目录
    if [ -d "$INSTALL_DIR" ]; then
        SSP_INSTALLED=true
        print_info "检测到 SSP 安装目录: $INSTALL_DIR"
        
        if [ -f "$INSTALL_DIR/conf/config.inc.php" ]; then
            print_info "检测到 SSP 配置文件存在"
        fi
        
        if [ -f "$APACHE_CONFIG_FILE" ]; then
            print_info "检测到 Apache SSP 配置文件存在"
        fi
        
        EXISTING_INSTALL=true
    fi
    
    # 处理安装模式选择
    if [ -n "$FORCE_MODE" ]; then
        # 命令行指定了强制模式
        INSTALL_MODE="$FORCE_MODE"
        print_info "命令行强制模式: $INSTALL_MODE"
    elif [ "$EXISTING_INSTALL" = true ]; then
        # 检测到现有安装
        if [ "$NON_INTERACTIVE" = true ]; then
            # 非交互模式默认选择升级
            INSTALL_MODE="upgrade"
            print_info "非交互模式:自动选择升级模式"
        else
            # 交互模式询问用户
            echo ""
            print_warning "检测到现有的 SSP 安装!"
            echo "您希望如何处理?"
            echo "  1) 🔄 升级/重新配置现有安装"
            echo "  2) 🗑️  删除现有安装并重新安装"
            echo "  3) 📂 保留现有安装并在新目录安装"
            echo "  4) ❌ 退出安装"
            echo ""
            
            while true; do
                read -p "请选择 (1-4): " choice
                case $choice in
                    1)
                        INSTALL_MODE="upgrade"
                        print_info "选择:升级/重新配置模式"
                        break
                        ;;
                    2)
                        INSTALL_MODE="reinstall"
                        print_info "选择:完全重新安装模式"
                        break
                        ;;
                    3)
                        INSTALL_MODE="new_location"
                        print_info "选择:新位置安装模式"
                        read -p "请输入新的安装目录 [/var/www/html/ssp-new]: " new_dir
                        INSTALL_DIR=${new_dir:-/var/www/html/ssp-new}
                        # 更新 Apache 配置文件路径
                        APACHE_CONFIG_FILE="/etc/apache2/sites-available/$(basename "$INSTALL_DIR").conf"
                        break
                        ;;
                    4)
                        print_info "用户选择退出安装"
                        exit 0
                        ;;
                    *)
                        echo "无效选择,请重新输入"
                        ;;
                esac
            done
        fi
    else
        # 全新环境
        INSTALL_MODE="fresh"
        print_success "检测到全新环境,将进行首次安装"
    fi
    
    echo ""
}

# --- Cleanup Existing Installation ---
function cleanup_existing_installation() {
    if [ "$INSTALL_MODE" = "reinstall" ]; then
        echo "--> 清理现有安装..."
        
        # 停止 Apache
        print_info "停止 Apache 服务..."
        systemctl stop apache2 2>/dev/null || true
        
        # 禁用 SSP 站点
        if [ -f "$APACHE_CONFIG_FILE" ]; then
            print_info "禁用现有的 SSP 站点配置..."
            a2dissite ssp.conf 2>/dev/null || true
        fi
        
        # 备份重要文件
        if [ -d "$INSTALL_DIR" ]; then
            BACKUP_DIR="/tmp/ssp_backup_$(date +%Y%m%d_%H%M%S)"
            mkdir -p "$BACKUP_DIR"
            
            # 备份配置文件
            if [ -f "$INSTALL_DIR/conf/config.inc.php" ]; then
                cp "$INSTALL_DIR/conf/config.inc.php" "$BACKUP_DIR/"
                print_success "已备份配置文件到: $BACKUP_DIR/"
            fi
            
            # 备份自定义文件
            if [ -d "$INSTALL_DIR/custom" ]; then
                cp -r "$INSTALL_DIR/custom" "$BACKUP_DIR/"
                print_success "已备份自定义文件"
            fi
            
            # 删除安装目录
            rm -rf "$INSTALL_DIR"
            print_success "已删除现有安装目录"
        fi
        
        # 删除 Apache 配置
        if [ -f "$APACHE_CONFIG_FILE" ]; then
            rm -f "$APACHE_CONFIG_FILE"
            print_success "已删除 Apache 配置文件"
        fi
        
        print_success "现有安装清理完成,备份保存在: $BACKUP_DIR"
        echo ""
    fi
}

# --- Skip or Update Dependencies ---
function handle_dependencies() {
    if [ "$APACHE_INSTALLED" = true ] && [ "$PHP_INSTALLED" = true ] && [ "$INSTALL_MODE" = "upgrade" ]; then
        echo "--> 跳过依赖安装(检测到现有环境)..."
        print_info "Apache 和 PHP 已安装,跳过系统依赖安装"
        
        # 只更新必要的 PHP 模块
        print_info "检查并安装缺失的 PHP 模块..."
        apt-get update >/dev/null 2>&1
        apt-get install -y --no-upgrade \
            php8.2-ldap \
            php8.2-ctype \
            php8.2-mbstring \
            php8.2-gd \
            php8.2-xml \
            php8.2-curl \
            php8.2-zip \
            php8.2-soap \
            composer
        
        print_success "PHP 模块检查完成"
        return 0
    else
        return 1
    fi
}

# --- Local Package Check Function ---
function find_ssp_package() {
    echo "正在查找本地 SSP 安装包..."
    
    # 可能的安装包文件名模式
    local package_patterns=(
        "self-service-password*.tar.gz"
        "self-service-password*.zip"
        "ssp*.tar.gz"
        "ssp*.zip"
        "*.tar.gz"
        "*.zip"
    )
    
    SSP_PACKAGE=""
    
    # 在脚本目录中查找安装包
    for pattern in "${package_patterns[@]}"; do
        for file in "${SCRIPT_DIR}"/$pattern; do
            if [ -f "$file" ] && [ ! "$file" -ef "$0" ]; then
                SSP_PACKAGE="$file"
                print_success "找到 SSP 安装包: $(basename "$SSP_PACKAGE")"
                return 0
            fi
        done
    done
    
    print_error "未在脚本目录中找到 SSP 安装包!"
    echo "请将 Self Service Password 安装包放置在脚本同一目录中。"
    echo "支持的文件格式: .tar.gz 或 .zip"
    echo "推荐的文件名: self-service-password-*.tar.gz"
    echo ""
    echo "下载地址: https://github.com/ltb-project/self-service-password/releases"
    exit 1
}



# --- Generate Secure Keyphrase --- (已移除,现在在 setup_config_file 中直接使用 openssl)

# --- Configuration File Setup ---
function setup_config_file() {
    print_info "--> 步骤 7/10: 正在修改 SSP 配置文件(仅必要配置)..."
    
    local CONFIG_FILE="${INSTALL_DIR}/conf/config.inc.php"
    
    # 备份原始配置文件
    cp "$CONFIG_FILE" "$CONFIG_FILE.backup.$(date +%Y%m%d_%H%M%S)"

    # --- 生成必要的安全配置 ---
    local SERVER_IP=$(hostname -I | awk '{print $1}')
    local SECURE_KEYPHRASE=$(openssl rand -hex 32)
    local RESET_URL="http://${SERVER_IP}/"

    print_info "只修改必要的配置项..."

    # 1. 设置安全的随机 keyphrase(这是必须的)
    if grep -q "^\$keyphrase" "$CONFIG_FILE"; then
        sed -i "s|^\$keyphrase = .*|\$keyphrase = \"${SECURE_KEYPHRASE}\";|" "$CONFIG_FILE"
        print_success "已更新 keyphrase"
    else
        # 如果没有 keyphrase 行,添加它
        echo "" >> "$CONFIG_FILE"
        echo "# Security keyphrase (auto-generated)" >> "$CONFIG_FILE"
        echo "\$keyphrase = \"${SECURE_KEYPHRASE}\";" >> "$CONFIG_FILE"
        print_success "已添加 keyphrase"
    fi

    # 2. 设置 reset_url(基本功能需要)
    if grep -q "^\$reset_url" "$CONFIG_FILE"; then
        sed -i "s|^\$reset_url = .*|\$reset_url = \"${RESET_URL}\";|" "$CONFIG_FILE"
        print_success "已更新 reset_url"
    fi

    # 3. 启用 debug 模式(便于排查问题)
    if grep -q "^\$debug" "$CONFIG_FILE"; then
        sed -i 's/^\$debug = .*/\$debug = true;/' "$CONFIG_FILE"
        print_success "已启用 debug 模式"
    fi

    # 4. 添加 audit_log_file(防止未定义变量警告)
    if ! grep -q "audit_log_file" "$CONFIG_FILE"; then
        print_info "添加 audit_log_file 变量..."
        echo "" >> "$CONFIG_FILE"
        echo "# Audit log file" >> "$CONFIG_FILE"
        echo "\$audit_log_file = \"${AUDIT_LOG}\";" >> "$CONFIG_FILE"
        print_success "已添加 audit_log_file"
    fi

    # 5. 验证配置文件语法
    print_info "验证配置文件语法..."
    if php -l "$CONFIG_FILE" >/dev/null 2>&1; then
        print_success "配置文件语法正确"
    else
        print_warning "配置文件存在语法错误,尝试修复..."
        php -l "$CONFIG_FILE"
        
        # 显示问题行附近的内容
        ERROR_LINE=$(php -l "$CONFIG_FILE" 2>&1 | grep -o "line [0-9]*" | grep -o "[0-9]*")
        if [ -n "$ERROR_LINE" ]; then
            print_info "显示第${ERROR_LINE}行附近的内容:"
            START_LINE=$((ERROR_LINE - 5))
            END_LINE=$((ERROR_LINE + 5))
            sed -n "${START_LINE},${END_LINE}p" "$CONFIG_FILE" | nl -ba -v${START_LINE}
            
            # 针对性修复常见的 SMARTY 定义错误
            if [ "$ERROR_LINE" -ge 505 ] && [ "$ERROR_LINE" -le 510 ]; then
                print_info "检测到 SMARTY 定义相关错误,进行针对性修复..."
                
                # 修复 define 语法错误
                sed -i 's/if (!define("SMARTY"/if (!defined("SMARTY"))/g' "$CONFIG_FILE"
                
                # 删除错误的行并重新写入正确的 SMARTY 定义
                sed -i '/if (!defined("SMARTY"))/,/^[[:space:]]*}[[:space:]]*$/d' "$CONFIG_FILE"
                
                # 在文件末尾添加正确的 SMARTY 定义
                cat >> "$CONFIG_FILE" << SMARTY_EOF

# SMARTY template engine path (corrected)
if (!defined("SMARTY")) {
    define("SMARTY", "${FOUND_SMARTY_PATH:-/var/www/html/ssp/vendor/smarty/smarty/libs/Smarty.class.php}");
}
SMARTY_EOF
                
                # 验证修复结果
                if php -l "$CONFIG_FILE" >/dev/null 2>&1; then
                    print_success "SMARTY 定义语法修复成功!"
                else
                    print_warning "SMARTY 修复失败,使用紧急配置..."
                fi
            fi
        fi
        
        # 如果还是有错误,创建最小配置但不退出
        if ! php -l "$CONFIG_FILE" >/dev/null 2>&1; then
            print_warning "创建紧急最小配置文件(安装将继续)..."
            cp "$CONFIG_FILE" "$CONFIG_FILE.broken"
            
            cat > "$CONFIG_FILE" << EOF
<?php
#==============================================================================
# LTB Self Service Password - Emergency Minimal Configuration
# 
# 注意:这是紧急生成的最小配置文件
# 损坏的原文件已保存为: $CONFIG_FILE.broken
# 请根据您的环境手动配置 LDAP 连接参数
#==============================================================================

# Debug mode
\$debug = true;

# Security (auto-generated)
\$keyphrase = "${SECURE_KEYPHRASE}";
\$reset_url = "${RESET_URL}";

# Audit log
\$audit_log_file = "${AUDIT_LOG}";

# SMARTY path (if found)
$([ -n "$FOUND_SMARTY_PATH" ] && echo "if (!defined(\"SMARTY\")) { define(\"SMARTY\", \"$FOUND_SMARTY_PATH\"); }")

?>
EOF
            
            if php -l "$CONFIG_FILE" >/dev/null 2>&1; then
                print_success "紧急最小配置文件创建成功!"
                print_warning "⚠️  这是最小配置,安装将继续完成,但您需要手动添加 LDAP 配置"
                print_info "损坏的配置文件已保存为: $CONFIG_FILE.broken"
            else
                print_error "无法创建可用的配置文件,但安装将继续..."
                print_warning "您需要在安装完成后手动修复配置文件"
            fi
        fi
    fi
    
    print_success "配置文件修改完成(仅修改了必要配置项)"
}

# --- 配置 Smarty 路径函数 ---
function configure_smarty_path() {
    print_info "配置 Smarty 模板引擎路径..."
    
    local CONFIG_FILE="${INSTALL_DIR}/conf/config.inc.php"
    
    # 查找 vendor 目录中的 Smarty
    SMARTY_CLASS_PATH=""
    if [ -f "${INSTALL_DIR}/vendor/smarty/smarty/libs/Smarty.class.php" ]; then
        SMARTY_CLASS_PATH="${INSTALL_DIR}/vendor/smarty/smarty/libs/Smarty.class.php"
    elif [ -f "${INSTALL_DIR}/vendor/smarty/smarty/Smarty.class.php" ]; then
        SMARTY_CLASS_PATH="${INSTALL_DIR}/vendor/smarty/smarty/Smarty.class.php"
    else
        # 搜索 vendor 目录中的 Smarty
        SMARTY_CLASS_PATH=$(find "${INSTALL_DIR}/vendor" -name "Smarty.class.php" 2>/dev/null | head -1)
    fi
    
    # 如果找到了 Smarty,在配置文件中定义路径
    if [ -n "$SMARTY_CLASS_PATH" ] && [ -f "$SMARTY_CLASS_PATH" ]; then
        print_info "找到 Smarty: $SMARTY_CLASS_PATH"
        
        # 检查是否已有 SMARTY 定义
        if grep -q "define.*SMARTY" "$CONFIG_FILE"; then
            sed -i "s|define.*SMARTY.*|define('SMARTY', '${SMARTY_CLASS_PATH}');|" "$CONFIG_FILE"
            print_info "更新了现有的 SMARTY 定义"
        else
            # 在文件开头添加 SMARTY 定义(在 <?php 之后)
            sed '/^<?php/a\
\
# Smarty template engine path\
define('\''SMARTY'\'', '\'''"${SMARTY_CLASS_PATH}"''\'');' "$CONFIG_FILE" > "$CONFIG_FILE.tmp"
            mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
            print_info "添加了新的 SMARTY 定义"
        fi
        
        print_success "Smarty 路径配置完成"
    else
        print_warning "未找到 Smarty,将依赖 autoload 自动加载"
    fi
}

# --- Safety Checks ---
# 如果不以 root 身份运行,则退出
if [ "$(id -u)" -ne 0 ]; then
  print_error "此脚本必须以 root 用户身份运行"
  exit 1
fi

# 出现任何错误时立即中止脚本(但配置文件错误除外)
# set -e  # 暂时禁用,改为手动错误检查以避免配置文件问题导致安装中断

# 确保包含 Apache 工具的路径
export PATH="/usr/sbin:/sbin:$PATH"

# --- Main Installation Process ---

echo "=================================================================="
echo "🚀 Self Service Password 一键安装脚本 v1.0"
echo "=================================================================="
echo "📋 大刘讲IT出品,微信公众号同号"
echo "=================================================================="

# 步骤 0: 环境检查
check_environment

# 步骤 0.1: 检查现有安装状态
check_installation_status

# 步骤 0.2: 处理现有安装
cleanup_existing_installation

# 步骤 0.5: 查找本地安装包
find_ssp_package

# 步骤 1: 更新系统并安装所有依赖项
echo "--> 1/9: 更新系统并安装依赖项..."

# 尝试智能跳过已安装的组件
if ! handle_dependencies; then
    print_info "执行完整的依赖安装..."
    apt-get update
    if [ $? -ne 0 ]; then
        print_error "系统更新失败"
        exit 1
    fi
    
    # 设置为非交互模式,避免安装过程中断
    export DEBIAN_FRONTEND=noninteractive
    apt-get upgrade -y
    if [ $? -ne 0 ]; then
        print_warning "系统升级失败,但继续安装"
    fi
    
    apt-get install -y \
        apache2 \
        php8.2 \
        php8.2-fpm \
        libapache2-mod-php8.2 \
        php8.2-ldap \
        php8.2-ctype \
        php8.2-iconv \
        php8.2-gettext \
        php8.2-mbstring \
        php8.2-gd \
        php8.2-xml \
        php8.2-curl \
        php8.2-zip \
        php8.2-soap \
        php8.2-cli \
        composer \
        wget \
        tar \
        unzip
    
    if [ $? -ne 0 ]; then
        print_error "依赖包安装失败"
        exit 1
    fi
    
    print_success "依赖项安装完成"
else
    print_success "依赖项检查完成(跳过重复安装)"
fi
echo ""

# 步骤 2: 配置 PHP 环境
echo "--> 2/9: 配置 PHP 环境..."
# 为 Apache 找到正确的 php.ini 文件
PHP_INI_FILE="/etc/php/8.2/apache2/php.ini"

if [ -f "$PHP_INI_FILE" ]; then
    echo "在 ${PHP_INI_FILE} 中设置 PHP 配置..."
    # 设置时区
    if grep -q ";date.timezone =" "$PHP_INI_FILE"; then
        sed -i 's/;date.timezone =/date.timezone = UTC/g' "$PHP_INI_FILE"
    elif ! grep -q "^date.timezone" "$PHP_INI_FILE"; then
        echo "date.timezone = UTC" >> "$PHP_INI_FILE"
    fi
    
    # 启用错误显示(调试用)
    sed -i 's/display_errors = Off/display_errors = On/g' "$PHP_INI_FILE"
    sed -i 's/display_startup_errors = Off/display_startup_errors = On/g' "$PHP_INI_FILE"
    
else
    print_warning "未找到 Apache 的 php.ini 文件: ${PHP_INI_FILE}"
fi

print_success "PHP 配置完成"
echo ""

# 步骤 2.5: 配置 LDAP 客户端以临时禁用证书验证
echo "--> 2.5/10: 配置 LDAP 客户端以临时禁用证书验证..."
LDAP_CONF_FILE="/etc/ldap/ldap.conf"
# 检查是否已存在该配置,避免重复添加
if ! grep -q "^TLS_REQCERT" "$LDAP_CONF_FILE"; then
    echo "" >> "$LDAP_CONF_FILE"
    echo "# WARNING: This setting bypasses LDAPS certificate validation for initial setup." >> "$LDAP_CONF_FILE"
    echo "# For production, you should import your CA cert and remove this line." >> "$LDAP_CONF_FILE"
    echo "TLS_REQCERT never" >> "$LDAP_CONF_FILE"
    print_success "已配置 LDAP 客户端以禁用证书验证。"
else
    print_info "LDAP 客户端证书配置已存在,跳过。"
fi
echo ""

# 步骤 3: 解压并部署 Self Service Password
echo "--> 3/10: 解压并部署 Self Service Password..."
print_info "使用安装包: $(basename "$SSP_PACKAGE")"

# 创建安装目录
mkdir -p "${INSTALL_DIR}"

# 创建临时工作目录
TEMP_EXTRACT_DIR="/tmp/ssp_extract_$$"
mkdir -p "${TEMP_EXTRACT_DIR}"

# 根据文件扩展名解压
if [[ "$SSP_PACKAGE" == *.tar.gz ]] || [[ "$SSP_PACKAGE" == *.tgz ]]; then
    echo "解压 tar.gz 格式文件..."
    tar -xzf "${SSP_PACKAGE}" -C "${TEMP_EXTRACT_DIR}"
elif [[ "$SSP_PACKAGE" == *.zip ]]; then
    echo "解压 zip 格式文件..."
    unzip -q "${SSP_PACKAGE}" -d "${TEMP_EXTRACT_DIR}"
else
    print_error "不支持的文件格式"
    rm -rf "${TEMP_EXTRACT_DIR}"
    exit 1
fi

if [ $? -ne 0 ]; then
    print_error "解压失败"
    rm -rf "${TEMP_EXTRACT_DIR}"
    exit 1
fi

# 查找解压后的 SSP 目录
SSP_SOURCE_DIR=""
print_info "搜索解压后的 SSP 源码目录..."

# 首先检查解压目录的内容
print_info "解压目录内容:"
ls -la "${TEMP_EXTRACT_DIR}/"

# 智能查找 SSP 源码目录
for dir in "${TEMP_EXTRACT_DIR}"/*; do
    if [ -d "$dir" ]; then
        print_info "检查目录: $(basename "$dir")"
        if [ -f "$dir/composer.json" ] || [ -d "$dir/htdocs" ] || [ -f "$dir/index.php" ]; then
            SSP_SOURCE_DIR="$dir"
            print_success "找到有效的 SSP 源码目录: $(basename "$SSP_SOURCE_DIR")"
            break
        fi
    fi
done

# 如果在子目录中没找到,检查解压根目录
if [ -z "$SSP_SOURCE_DIR" ]; then
    print_info "在子目录中未找到,检查解压根目录..."
    if [ -f "${TEMP_EXTRACT_DIR}/composer.json" ] || [ -d "${TEMP_EXTRACT_DIR}/htdocs" ] || [ -f "${TEMP_EXTRACT_DIR}/index.php" ]; then
        SSP_SOURCE_DIR="${TEMP_EXTRACT_DIR}"
        print_success "在解压根目录找到 SSP 源码"
    fi
fi

# 如果还是没找到,列出所有可能的文件
if [ -z "$SSP_SOURCE_DIR" ]; then
    print_error "无法找到有效的 SSP 源代码目录"
    print_info "解压目录完整内容:"
    find "${TEMP_EXTRACT_DIR}" -type f -name "*.php" -o -name "composer.json" -o -name "*.md" | head -20
    rm -rf "${TEMP_EXTRACT_DIR}"
    exit 1
fi

# 复制文件到安装目录
print_info "复制文件从 $(basename "$SSP_SOURCE_DIR")${INSTALL_DIR}..."
cp -r "${SSP_SOURCE_DIR}"/* "${INSTALL_DIR}/"
if [ $? -ne 0 ]; then
    print_error "复制文件失败"
    rm -rf "${TEMP_EXTRACT_DIR}"
    exit 1
fi

# 验证关键文件是否存在
print_info "验证关键文件..."
if [ -f "${INSTALL_DIR}/composer.json" ]; then
    print_success "composer.json 文件存在"
else
    print_warning "composer.json 文件不存在"
fi

if [ -d "${INSTALL_DIR}/htdocs" ]; then
    print_success "htdocs 目录存在"
else
    print_warning "htdocs 目录不存在"
fi

# 清理临时文件
rm -rf "${TEMP_EXTRACT_DIR}"

print_success "SSP 已解压到 ${INSTALL_DIR}"
echo ""

# 步骤 4: 强制全新安装 PHP 依赖
echo "--> 4/9: 强制全新安装 PHP 依赖..."
cd "${INSTALL_DIR}"

print_info "清理旧的依赖状态以确保全新安装..."
rm -rf vendor composer.lock

# 设置 Composer 环境变量
export COMPOSER_ALLOW_SUPERUSER=1
export COMPOSER_HOME="/tmp/composer"
export COMPOSER_CACHE_DIR="/tmp/composer/cache"

# 强制检查和安装 Composer 依赖
print_info "检查 Composer 依赖安装状态..."

if [ -f "composer.json" ]; then
    print_success "找到 composer.json 文件"
    
    # 显示 composer.json 内容以便调试
    print_info "composer.json 内容预览:"
    head -10 composer.json
    
    print_info "强制全新安装 Composer 依赖..."
    
    # 设置 Composer 环境变量
    export COMPOSER_ALLOW_SUPERUSER=1
    export COMPOSER_HOME="/tmp/composer"
    export COMPOSER_CACHE_DIR="/tmp/composer/cache"
    
    # 强制删除现有的 vendor 目录和锁文件
    rm -rf vendor composer.lock
    
    # 安装依赖
    print_info "正在运行 composer install..."
    composer install --no-dev --optimize-autoloader --verbose
    
    if [ $? -ne 0 ]; then
        print_error "Composer install 失败,尝试更新 Composer 并重试..."
        composer self-update
        composer install --no-dev --optimize-autoloader --verbose
        
        if [ $? -ne 0 ]; then
            print_error "Composer 安装仍然失败"
            exit 1
        fi
    fi
    
    # 验证关键依赖
    print_info "验证关键依赖是否安装成功..."
    
    if [ ! -f "vendor/autoload.php" ]; then
        print_error "vendor/autoload.php 文件不存在!"
        exit 1
    else
        print_success "vendor/autoload.php 文件存在"
    fi
    
    # 检查并安装缺失的关键依赖
    if [ ! -d "vendor/defuse/php-encryption" ]; then
        print_warning "检测到核心加密库缺失,正在强制安装..."
        composer require defuse/php-encryption:"^2.2"
    else
        print_success "Defuse 加密库已安装"
    fi
    
    # 检查 LTB 项目依赖
    if [ ! -d "vendor/ltb-project" ]; then
        print_warning "检测到 LTB 项目库缺失,正在尝试安装..."
        composer require ltb-project/ldap:"^0.3" || print_warning "LTB LDAP 库安装失败,将依赖内置类"
    else
        print_success "LTB 项目库已安装"
    fi
    
    print_info "设置 vendor 目录权限..."
    chown -R www-data:www-data vendor composer.lock
    
    print_success "Composer 依赖安装并验证完成"
    
else
    print_error "未找到 composer.json 文件!"
    print_info "当前目录内容:"
    ls -la
    print_info "搜索可能的 composer.json 文件..."
    find . -name "composer.json" -type f 2>/dev/null || print_warning "确实没有找到 composer.json 文件"
    
    # 尝试创建一个基本的 composer.json
    print_warning "尝试创建基本的 composer.json 文件..."
    cat > composer.json << 'EOF'
{
    "name": "ltb-project/self-service-password",
    "description": "Self Service Password",
    "type": "project",
    "require": {
        "php": ">=7.4",
        "defuse/php-encryption": "^2.2",
        "ltb-project/ldap": "^0.3",
        "smarty/smarty": "^3.1"
    },
    "autoload": {
        "psr-4": {
            "Ltb\\": "src/Ltb/"
        }
    }
}
EOF
    
    print_info "创建了基本的 composer.json,重新尝试安装..."
    
    # 创建基本的 src/Ltb 目录结构
    mkdir -p src/Ltb
    
    # 重新运行 Composer 安装
    export COMPOSER_ALLOW_SUPERUSER=1
    composer install --no-dev --optimize-autoloader
    
    if [ $? -ne 0 ]; then
        print_error "即使创建了 composer.json 也无法安装依赖"
        exit 1
    fi
    
    print_success "使用创建的 composer.json 成功安装依赖"
fi

# 最终验证
print_info "最终验证 autoload.php..."
if [ -f "vendor/autoload.php" ]; then
    print_success "✅ vendor/autoload.php 文件确认存在"
else
    print_error "❌ vendor/autoload.php 文件仍然不存在!"
    exit 1
fi

# 检查并修复 Smarty 路径配置
print_info "检查和修复 Smarty 路径配置..."
SMARTY_PATHS=(
    "${INSTALL_DIR}/vendor/smarty/smarty/libs/Smarty.class.php"
    "${INSTALL_DIR}/vendor/smarty/smarty/Smarty.class.php"
    "${INSTALL_DIR}/vendor/smarty/smarty/src/Smarty.php"
)

FOUND_SMARTY_PATH=""
for path in "${SMARTY_PATHS[@]}"; do
    if [ -f "$path" ]; then
        FOUND_SMARTY_PATH="$path"
        print_success "找到 Smarty: $path"
        break
    fi
done

if [ -n "$FOUND_SMARTY_PATH" ]; then
    CONFIG_FILE="${INSTALL_DIR}/conf/config.inc.php"
    if [ -f "$CONFIG_FILE" ]; then
        print_info "更新配置文件中的 Smarty 路径..."
        
        # 检查是否已有 SMARTY 定义
        if grep -q "define.*SMARTY" "$CONFIG_FILE"; then
            print_info "更新现有的 SMARTY 定义..."
            sed -i "s|define.*SMARTY.*|define(\"SMARTY\", \"$FOUND_SMARTY_PATH\");|" "$CONFIG_FILE"
        else
            print_info "添加新的 SMARTY 定义..."
            # 在 <?php 后添加 SMARTY 定义(使用更安全的方法)
            sed '/^<?php/a\
\
# Smarty template engine path (auto-detected)\
define("SMARTY", "'"$FOUND_SMARTY_PATH"'");' "$CONFIG_FILE" > "$CONFIG_FILE.tmp"
            mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
        fi
        
        print_success "Smarty 路径已更新到配置文件"
        
        # 验证配置文件语法
        if php -l "$CONFIG_FILE" >/dev/null 2>&1; then
            print_success "配置文件语法验证通过"
        else
            print_warning "配置文件语法验证失败,但 Smarty 路径已更新"
        fi
    else
        print_warning "配置文件不存在,跳过 Smarty 路径更新"
    fi
else
    print_warning "未找到 Smarty 文件,将依赖 autoload 自动加载"
fi

# 检查其他关键依赖路径
print_info "检查其他关键依赖..."

# 检查 Defuse 加密库
if [ -d "${INSTALL_DIR}/vendor/defuse/php-encryption" ]; then
    print_success "Defuse 加密库已安装"
else
    print_warning "Defuse 加密库未找到"
fi

# 检查 LTB 项目依赖
if [ -d "${INSTALL_DIR}/vendor/ltb-project" ]; then
    print_success "LTB 项目依赖已安装"
else
    print_warning "LTB 项目依赖未找到"
fi

# 检查关键目录权限
CRITICAL_DIRS=(
    "${INSTALL_DIR}/conf"
    "${INSTALL_DIR}/cache"
    "${INSTALL_DIR}/compile"
    "${INSTALL_DIR}/templates_c"
    "${INSTALL_DIR}/vendor"
)

print_info "验证关键目录权限..."
for dir in "${CRITICAL_DIRS[@]}"; do
    if [ -d "$dir" ]; then
        if [ -w "$dir" ]; then
            print_success "目录 $(basename "$dir") 权限正确"
        else
            print_warning "目录 $(basename "$dir") 权限可能有问题"
            chown -R www-data:www-data "$dir"
            chmod -R 750 "$dir"
            print_info "已修复目录 $(basename "$dir") 权限"
        fi
    else
        print_warning "目录 $(basename "$dir") 不存在"
    fi
done

echo ""

# 步骤 6: 跳过手动创建 Ltb 类(使用 Composer 官方依赖)
echo "--> 6/10: 跳过手动创建 Ltb 类,使用 Composer 官方依赖..."
print_success "已跳过手动创建 Ltb 类,将依赖 Composer 安装的官方包"
echo ""

# 步骤 5: 设置配置文件
echo "--> 5/9: 设置配置文件..."
setup_config_file
echo ""

# 步骤 6: 跳过单独的 Smarty 配置(已集成到 Composer 步骤)
echo "--> 6/9: Smarty 路径配置已集成到 Composer 安装步骤..."
print_success "Smarty 路径配置已在 Composer 安装后自动完成"
echo ""

# 步骤 7: 设置文件和目录权限  
echo "--> 7/9: 设置文件权限..."

# 创建必要的目录
print_info "创建必要的目录..."
mkdir -p "${INSTALL_DIR}/cache" "${INSTALL_DIR}/compile" "${INSTALL_DIR}/templates_c"

# PHP sessions 由系统管理
if [ -d "/var/lib/php/sessions" ]; then
    print_info "配置系统 PHP sessions 目录权限..."
    chown root:www-data /var/lib/php/sessions
    chmod 770 /var/lib/php/sessions
fi

# 创建审计日志文件
AUDIT_LOG="/var/log/apache2/ssp_audit.log"
touch "$AUDIT_LOG"
chown www-data:www-data "$AUDIT_LOG"
chmod 644 "$AUDIT_LOG"
print_success "创建审计日志文件: $AUDIT_LOG"

# 设置基本目录权限
print_info "设置目录权限..."
chown -R www-data:www-data "${INSTALL_DIR}"
chmod -R 755 "${INSTALL_DIR}"

# 设置特殊权限的目录
directories_to_set=(
    "${INSTALL_DIR}/conf"
    "${INSTALL_DIR}/cache"
    "${INSTALL_DIR}/compile"
    "${INSTALL_DIR}/templates_c"
)

for dir in "${directories_to_set[@]}"; do
    if [ -d "$dir" ]; then
        print_info "设置 $(basename "$dir") 目录权限..."
        chown -R www-data:www-data "$dir"
        chmod -R 750 "$dir"
    fi
done

print_success "文件权限设置完成"
echo ""

# 步骤 8: 配置 Apache Web 服务器
echo "--> 8/9: 配置 Apache2..."

# 根据官方文档创建 Apache 虚拟主机配置文件
# 参考: https://self-service-password.readthedocs.io/en/latest/config_apache.html
cat > "${APACHE_CONFIG_FILE}" << EOF
<VirtualHost *:80>
    ServerName localhost
    DocumentRoot ${INSTALL_DIR}/htdocs
    DirectoryIndex index.php
    
    # 设置默认字符集
    AddDefaultCharset UTF-8

    <Directory ${INSTALL_DIR}/htdocs>
        # 根据官方文档,设置 AllowOverride None
        AllowOverride None
        <IfVersion >= 2.3>
            Require all granted
        </IfVersion>
        <IfVersion < 2.3>
            Order Deny,Allow
            Allow from all
        </IfVersion>
        
        # 确保 index.php 作为目录索引
        DirectoryIndex index.php
        AddDefaultCharset UTF-8
    </Directory>

    # 保护配置目录
    <Directory ${INSTALL_DIR}/conf>
        <IfVersion >= 2.3>
            Require all denied
        </IfVersion>
        <IfVersion < 2.3>
            Order Deny,Allow
            Deny from all
        </IfVersion>
    </Directory>

    # 保护缓存目录
    <Directory ${INSTALL_DIR}/cache>
        <IfVersion >= 2.3>
            Require all denied
        </IfVersion>
        <IfVersion < 2.3>
            Order Deny,Allow
            Deny from all
        </IfVersion>
    </Directory>

    # 保护编译目录
    <Directory ${INSTALL_DIR}/compile>
        <IfVersion >= 2.3>
            Require all denied
        </IfVersion>
        <IfVersion < 2.3>
            Order Deny,Allow
            Deny from all
        </IfVersion>
    </Directory>

    # 保护模板编译目录
    <Directory ${INSTALL_DIR}/templates_c>
        <IfVersion >= 2.3>
            Require all denied
        </IfVersion>
        <IfVersion < 2.3>
            Order Deny,Allow
            Deny from all
        </IfVersion>
    </Directory>

    # 如果存在 rest API 目录,保护它
    <Directory ${INSTALL_DIR}/rest>
        AllowOverride None
        <IfVersion >= 2.3>
            Require all denied
        </IfVersion>
        <IfVersion < 2.3>
            Order Deny,Allow
            Deny from all
        </IfVersion>
    </Directory>

    # 日志配置
    LogLevel warn
    ErrorLog \${APACHE_LOG_DIR}/ssp_error.log
    CustomLog \${APACHE_LOG_DIR}/ssp_access.log combined
</VirtualHost>
EOF

# 启用 Apache 模块和站点配置
print_info "启用 Apache 模块和站点配置..."

# 检查并启用必要的 Apache 模块
check_module() {
    local module=$1
    if apache2ctl -M 2>/dev/null | grep -q "${module}_module"; then
        print_info "模块 $module 已启用"
        return 0
    else
        print_info "启用模块 $module..."
        /usr/sbin/a2enmod $module
        return $?
    fi
}

# 启用必要的模块
check_module "rewrite"
check_module "php8.2"
check_module "dir"

# 禁用可能冲突的站点
print_info "禁用默认站点..."
/usr/sbin/a2dissite 000-default.conf 2>/dev/null || true

# 启用 SSP 站点
print_info "启用 SSP 站点..."
/usr/sbin/a2ensite ssp.conf

# 检查 .htaccess 文件(由于使用 AllowOverride None,需要禁用)
HTACCESS_FILE="${INSTALL_DIR}/htdocs/.htaccess"
if [ -f "$HTACCESS_FILE" ]; then
    print_info "发现 .htaccess 文件,由于使用 AllowOverride None,将其重命名"
    mv "$HTACCESS_FILE" "${HTACCESS_FILE}.disabled"
    print_warning "已将 .htaccess 重命名为 .htaccess.disabled"
fi

# 测试 Apache 配置
if ! apache2ctl configtest; then
    print_error "Apache 配置测试失败"
    exit 1
fi

# 检查并启动 Apache 服务
print_info "检查 Apache 服务状态..."
if ! systemctl is-active --quiet apache2; then
    print_warning "Apache 服务未运行,正在启动..."
    systemctl start apache2
    if [ $? -ne 0 ]; then
        print_error "Apache 启动失败,尝试重新安装并启动..."
        systemctl enable apache2
        systemctl start apache2
        if [ $? -ne 0 ]; then
            print_error "Apache 启动失败"
            systemctl status apache2
            exit 1
        fi
    fi
    print_success "Apache 服务启动成功"
else
    print_info "Apache 服务已在运行"
fi

# 重新加载 Apache 配置
print_info "重新加载 Apache 配置..."
systemctl reload apache2
if [ $? -ne 0 ]; then
    print_warning "Apache 配置重新加载失败,尝试重启..."
    systemctl restart apache2
    if [ $? -ne 0 ]; then
        print_error "Apache 重启失败"
        systemctl status apache2
        exit 1
    else
        print_success "Apache 重启成功"
    fi
else
    print_success "Apache 配置重新加载成功"
fi

# 验证 Apache 状态
if ! systemctl is-active --quiet apache2; then
    print_error "Apache 服务未正常运行"
    exit 1
fi

print_success "Apache 配置完成"
echo ""

# --- 创建简化的安装验证页面 ---
echo "--> 创建一个简化的安装验证页面..."
cat > "${INSTALL_DIR}/htdocs/install_check.php" << 'EOF'
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

echo "<html><head><title>SSP 核心依赖检查</title>";
echo "<style>body{font-family:monospace;margin:40px;} .ok{color:green;} .err{color:red;}</style>";
echo "</head><body><h1>SSP Core Dependency Check</h1>";

$all_ok = true;

// 1. 检查 Composer Autoloader
echo "<h2>1. Composer Autoloader</h2>";
$autoload_file = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoload_file)) {
    require_once $autoload_file;
    echo "<p class='ok'>[OK] vendor/autoload.php found and included.</p>";
} else {
    echo "<p class='err'>[FAIL] vendor/autoload.php not found!</p>";
    $all_ok = false;
}

// 2. 检查核心 LTB 类
echo "<h2>2. LTB Project Core Class</h2>";
if (class_exists('Ltb\Directory')) {
    echo "<p class='ok'>[OK] Ltb\\Directory class is available.</p>";
} else {
    echo "<p class='err'>[FAIL] Ltb\\Directory class not found!</p>";
    $all_ok = false;
}

// 3. 检查核心加密库
echo "<h2>3. Defuse Crypto Library</h2>";
if (class_exists('Defuse\\Crypto\\Crypto')) {
    echo "<p class='ok'>[OK] Defuse\\Crypto\\Crypto class is available.</p>";
} else {
    echo "<p class='err'>[FAIL] Defuse\\Crypto\\Crypto class not found!</p>";
    $all_ok = false;
}

echo "<h2>Final Result</h2>";
if ($all_ok) {
    echo "<h3 class='ok'>ALL CHECKS PASSED! Your SSP installation should be working correctly.</h3>";
    echo "<p><a href='./'>→ Go to SSP Main Page</a></p>";
} else {
    echo "<h3 class='err'>SOME CHECKS FAILED! Please review the errors above.</h3>";
}
echo "</body></html>";
?>
EOF

chown www-data:www-data "${INSTALL_DIR}/htdocs/install_check.php"
chmod 644 "${INSTALL_DIR}/htdocs/install_check.php"






# --- 步骤 9: 最终验证 ---
echo ""
echo "--> 9/9: 最终验证安装..."

# 验证配置文件语法
print_info "验证配置文件语法..."
php -l "${INSTALL_DIR}/conf/config.inc.php"
if [ $? -eq 0 ]; then
    print_success "配置文件语法正确"
else
    print_error "配置文件语法错误"
fi



# 验证 Apache 状态
if systemctl is-active --quiet apache2; then
    print_success "Apache 服务运行正常"
else
    print_error "Apache 服务未运行"
fi

print_success "最终验证完成"

# --- 获取服务器信息 ---
SERVER_IP=$(hostname -I | awk '{print $1}')

# --- Final Instructions ---
echo ""
echo "=================================================================="
case "$INSTALL_MODE" in
    "fresh")
        print_success "🎉 Self Service Password 全新安装完成!"
        ;;
    "upgrade")
        print_success "🔄 Self Service Password 升级完成!"
        ;;
    "reinstall")
        print_success "🔄 Self Service Password 重新安装完成!"
        if [ -n "$BACKUP_DIR" ]; then
            echo "   💾 原配置备份: $BACKUP_DIR"
        fi
        ;;
    "new_location")
        print_success "📂 Self Service Password 新位置安装完成!"
        ;;
    *)
        print_success "🎉 Self Service Password 安装完成!"
        ;;
esac
echo "=================================================================="
echo ""
echo "🔗 **访问地址**:"
echo "   📋 核心依赖检查:     http://${SERVER_IP}/install_check.php"
echo "   🏠 SSP 主页:         http://${SERVER_IP}/"
echo "   🌐 本机访问:         http://localhost/"
echo ""
echo "📋 **安装信息**:"
echo "   🏗️  安装模式: $INSTALL_MODE"
echo "   📦 安装包: $(basename "$SSP_PACKAGE")"
echo "   📁 安装目录: ${INSTALL_DIR}"
echo "   ⚙️  Apache 配置: ${APACHE_CONFIG_FILE}"
echo "   🌐 服务器 IP: ${SERVER_IP}"
echo "   📝 审计日志: ${AUDIT_LOG}"
echo ""
echo "📝 **Apache 配置优化**:"
echo "   ✅ 根据官方文档配置 Apache"
echo "   ✅ 设置 AllowOverride None(官方推荐)"
echo "   ✅ 添加正确的目录保护"
echo "   ✅ 设置默认字符集 UTF-8"
echo "   ✅ 禁用可能冲突的 .htaccess"
echo ""
echo "🔧 **依赖管理优化**:"
echo "   ✅ 强制清理旧的 vendor 目录和 composer.lock"
echo "   ✅ 简化 Composer 执行逻辑(root 安装 + 权限修正)"
echo "   ✅ 自动检测并安装缺失的核心加密库"
echo "   ✅ 依赖官方 Composer 包,避免手动创建不完整类"
echo ""
echo "🔧 **配置文件优化修复**:"
echo "   ✅ 仅修改必要的配置项(keyphrase、reset_url、debug)"
echo "   ✅ 自动添加 audit_log_file 变量(防止未定义警告)"
echo "   ✅ 智能配置 Smarty 模板引擎路径"
echo "   ✅ 生成安全的随机密钥短语"
echo "   ✅ 智能语法错误检测和修复"
echo "   ✅ 紧急配置文件重建功能"

echo ""
echo "🔧 **下一步操作**:"
echo "   1. 访问核心依赖检查页面确认安装状态"
echo "   2. 手动配置 LDAP/AD 连接(编辑 ${INSTALL_DIR}/conf/config.inc.php)"
echo "   3. 访问 SSP 主页开始使用"
echo ""
echo "📚 **故障排除**:"
echo "   🔍 查看 Apache 错误日志: tail -f /var/log/apache2/ssp_error.log"
echo "   🔍 查看 Apache 访问日志: tail -f /var/log/apache2/ssp_access.log"
echo "   🧪 测试 Apache 配置: apache2ctl configtest"
echo "   🔄 重启 Apache: systemctl restart apache2"
echo "   🔧 修复配置文件语法: ./fix_config_syntax.sh"
echo "   📝 检查配置文件语法: php -l ${INSTALL_DIR}/conf/config.inc.php"
echo ""
print_success "安装脚本执行完毕!享受您的 Self Service Password!" 
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐