#!/usr/bin/env bash set -eu usage() { echo "用法:$0 [发布目录]" echo "每次部署都是全新部署,必须输入确认词后才会清理 Smail 专属运行数据。" } fresh=true root_argument="" while [ "$#" -gt 0 ]; do case "$1" in --fresh) # 保留旧参数兼容性;部署始终按全新部署处理。 ;; --help|-h) usage exit 0 ;; -* ) echo "未知参数:$1" >&2 usage >&2 exit 1 ;; *) if [ -n "$root_argument" ]; then echo "发布目录只能指定一次。" >&2 exit 1 fi root_argument="$1" ;; esac shift done if [ "$(id -u)" -ne 0 ]; then echo "必须使用 root 用户安装 Smail" >&2 exit 1 fi root_dir="$(realpath -e -- "${root_argument:-$(dirname "$0")/..}")" is_packaged=false if [ -f "$root_dir/smail" ] && [ -f "$root_dir/smail-admin" ] && [ -f "$root_dir/smail-config" ]; then is_packaged=true fi runtime_dir="${root_dir}" fresh_confirmation="确认全新部署 Smail" printf '这是不可逆的 Smail 专属数据清理操作。请输入“%s”继续: ' "$fresh_confirmation" if ! IFS= read -r confirmation || [ "$confirmation" != "$fresh_confirmation" ]; then echo "未获得有效确认,已取消全新部署。" >&2 exit 1 fi case "$(uname -m)" in x86_64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; *) echo "不支持的系统架构" >&2; exit 1 ;; esac # 安装前先检查所有输入和运行状态,不能覆盖仍在运行的程序。 if [ "$is_packaged" = true ]; then backend_smail="$root_dir/smail" backend_smail_admin="$root_dir/smail-admin" backend_smail_config="$root_dir/smail-config" else backend_smail="$root_dir/out/backend/smail_linux_$arch" backend_smail_admin="$root_dir/out/backend/smail-admin_linux_$arch" backend_smail_config="$root_dir/out/backend/smail-config_linux_$arch" fi for file in "$backend_smail" "$backend_smail_admin" \ "$backend_smail_config" \ "$root_dir/deploy/systemd/smail.service" "$root_dir/deploy/systemd/smail-admin.service"; do if [ ! -f "$file" ] || [ -L "$file" ] || [ ! -s "$file" ]; then echo "安装输入不存在或不是普通文件:$file" >&2 exit 1 fi done if systemctl is-active --quiet smail.service || systemctl is-active --quiet smail-admin.service; then echo "请先停止 Smail 主服务和管理员重置服务,安装不会自动中断现有业务。" >&2 exit 1 fi for process in /proc/[0-9]*/exe; do executable="$(readlink "$process" 2>/dev/null || true)" case "$executable" in "$runtime_dir/smail"|"$runtime_dir/smail (deleted)"|"$runtime_dir/smail-admin"|"$runtime_dir/smail-admin (deleted)") echo "部署目录中的程序仍在运行,请确认并停止后再安装。" >&2 exit 1 ;; esac done if [ -L "$runtime_dir" ] || { [ -e "$runtime_dir" ] && [ ! -d "$runtime_dir" ]; }; then echo "部署路径不能是链接或普通文件。" >&2 exit 1 fi for directory in config data ssl log plugins; do target="$runtime_dir/$directory" if [ -L "$target" ] || { [ -e "$target" ] && [ ! -d "$target" ]; }; then echo "运行目录不能是链接或普通文件:$target" >&2 exit 1 fi done for target in "$runtime_dir/smail" "$runtime_dir/smail-admin" \ /etc/systemd/system/smail.service /etc/systemd/system/smail-admin.service \ /etc/smail/smail.env; do if [ -L "$target" ] || { [ -e "$target" ] && [ ! -f "$target" ]; }; then echo "程序或服务文件不能是链接或特殊文件:$target" >&2 exit 1 fi done if [ -L /etc/smail ] || { [ -e /etc/smail ] && [ ! -d /etc/smail ]; }; then echo "服务环境目录不能是链接或普通文件:/etc/smail" >&2 exit 1 fi if [ -L /run/smail ] || { [ -e /run/smail ] && [ ! -d /run/smail ]; }; then echo "本机控制目录不能是链接或普通文件。" >&2 exit 1 fi if [ -L /run/smail/admin-control.sock ] || { [ -e /run/smail/admin-control.sock ] && [ ! -S /run/smail/admin-control.sock ]; }; then echo "本机控制路径不是安全套接字。" >&2 exit 1 fi # 仅保留程序和服务文件的短期备份,不保留配置、数据库、证书、日志、插件或其他运行数据。 for legacy_backup in /var/backups/smail-deploy-*; do if [ ! -e "$legacy_backup" ]; then continue; fi if [ -L "$legacy_backup" ] || [ ! -d "$legacy_backup" ]; then echo "历史 Smail 备份不是安全目录,拒绝清理:$legacy_backup" >&2 exit 1 fi rm -rf -- "$legacy_backup" done if [ "$fresh" = true ]; then if [ -d "$runtime_dir" ] && [ -n "$(find "$runtime_dir" -mindepth 1 -maxdepth 1 -type l -print -quit)" ]; then echo "全新部署已停止:/opt/smail 顶层存在符号链接,拒绝清理。" >&2 exit 1 fi if [ -d "$runtime_dir" ]; then if [ "$is_packaged" = true ]; then for directory in config data ssl log; do if [ -d "$runtime_dir/$directory" ]; then find "$runtime_dir/$directory" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + fi done else find "$runtime_dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + fi fi if [ -d /etc/smail ]; then find /etc/smail -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + fi if [ -d /etc/systemd/system/smail.service.d ]; then rm -rf -- /etc/systemd/system/smail.service.d fi if [ -d /run/smail ]; then find /run/smail -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + fi systemctl disable smail smail-admin >/dev/null 2>&1 || true systemctl daemon-reload fi if ! getent group smail >/dev/null; then groupadd --system smail; fi if ! getent passwd smail >/dev/null; then useradd --system --gid smail --home-dir "$runtime_dir" --shell /usr/sbin/nologin smail; fi install -d -o smail -g smail -m 0755 "$runtime_dir" chown smail:smail "$runtime_dir" install -d -o root -g root -m 0750 /etc/smail if [ ! -e /etc/smail/smail.env ]; then # 每次部署都重新创建服务环境文件,不继承旧的凭据或代理配置。 umask 0077 printf '%s\n' "SMAIL_ROOT=$runtime_dir" 'GOMEMLIMIT=256MiB' > /etc/smail/smail.env fi if ! grep -q '^SMAIL_PLUGIN_UID=' /etc/smail/smail.env; then printf 'SMAIL_PLUGIN_UID=%s\n' "$(id -u smail)" >> /etc/smail/smail.env fi if ! grep -q '^SMAIL_PLUGIN_GID=' /etc/smail/smail.env; then printf 'SMAIL_PLUGIN_GID=%s\n' "$(id -g smail)" >> /etc/smail/smail.env fi chown root:root /etc/smail/smail.env chmod 0600 /etc/smail/smail.env for directory in config data ssl log plugins; do target="$runtime_dir/$directory" # 内置插件在临时沙箱中展开,只兼容已存在的历史插件目录。 if [ "$directory" = plugins ] && [ ! -d "$target" ]; then continue; fi install -d -o smail -g smail -m 0700 "$target" # 不跟随链接,也不越过挂载点;仅为本部署的存量运行数据调整属主。 find -P "$target" -xdev \( -type d -o -type f \) -exec chown --no-dereference smail:smail {} + done install -d -o smail -g smail -m 0750 /run/smail if [ -S /run/smail/admin-control.sock ]; then # 从旧版 root 主服务迁移时保留残留套接字,由主程序验证失活后自行处理。 chown --no-dereference smail:smail /run/smail/admin-control.sock fi install -d -o root -g root -m 0755 /run/systemd/ask-password if [ "$backend_smail" != "$runtime_dir/smail" ]; then install -o root -g root -m 0755 "$backend_smail" "$runtime_dir/smail" else chown root:root "$runtime_dir/smail" chmod 0755 "$runtime_dir/smail" fi if [ "$backend_smail_admin" != "$runtime_dir/smail-admin" ]; then install -o root -g root -m 0700 "$backend_smail_admin" "$runtime_dir/smail-admin" else chown root:root "$runtime_dir/smail-admin" chmod 0700 "$runtime_dir/smail-admin" fi if [ "$backend_smail_config" != "$runtime_dir/smail-config" ]; then install -o root -g root -m 0700 "$backend_smail_config" "$runtime_dir/smail-config" else chown root:root "$runtime_dir/smail-config" chmod 0700 "$runtime_dir/smail-config" fi sed -e "s|WorkingDirectory=/opt/smail|WorkingDirectory=$runtime_dir|g" \ -e "s|ExecStart=/opt/smail/smail|ExecStart=$runtime_dir/smail|g" \ -e "s|/opt/smail/config|$runtime_dir/config|g" \ -e "s|/opt/smail/data|$runtime_dir/data|g" \ -e "s|/opt/smail/ssl|$runtime_dir/ssl|g" \ -e "s|/opt/smail/log|$runtime_dir/log|g" \ -e "s|/opt/smail/plugins|$runtime_dir/plugins|g" \ "$root_dir/deploy/systemd/smail.service" > /etc/systemd/system/smail.service chmod 0644 /etc/systemd/system/smail.service chown root:root /etc/systemd/system/smail.service sed -e "s|WorkingDirectory=/opt/smail|WorkingDirectory=$runtime_dir|g" \ -e "s|ExecStart=/opt/smail/smail-admin|ExecStart=$runtime_dir/smail-admin|g" \ "$root_dir/deploy/systemd/smail-admin.service" > /etc/systemd/system/smail-admin.service chmod 0644 /etc/systemd/system/smail-admin.service chown root:root /etc/systemd/system/smail-admin.service systemctl daemon-reload systemctl enable smail systemctl disable smail-admin >/dev/null 2>&1 || true echo "=== 正在启动 Smail 系统服务 ===" systemctl restart smail echo "=== Smail 服务状态 ===" systemctl status smail --no-pager || true echo "==========================================================" echo " Smail 安装与系统服务注册完成!" echo " 安装路径: $runtime_dir" echo " 运行用户: smail" echo " 开机自启: 已启用 (systemctl enable smail)" echo " 服务状态: 已启动 (systemctl status smail)" echo "==========================================================" # 实时追踪服务日志,直到检测到初始化完成(出现“Setup End!”或“初始化完成”)后自动退出 echo "" echo "=== 正在等待并监控初始化流程 (按 Ctrl+C 可提前退出监控) ===" echo "提示:请在浏览器打开初始化页面完成系统初始化配置..." # 使用 journalctl 持续监听 smail 服务日志 journalctl -u smail -f -n 20 --no-pager | while IFS= read -r line; do printf '%s\n' "$line" case "$line" in *"Setup End!"*) printf '\n%s\n' "==========================================================" printf '%s\n' " 检测到 Smail 初始化已顺利完成 (Setup End!)!监控自动退出。" printf '%s\n' "==========================================================" break ;; esac done