#!/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")/..}")" runtime_dir=/opt/smail 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 # 安装前先检查所有输入和运行状态,不能覆盖仍在运行的程序。 for file in "$root_dir/out/backend/smail_linux_$arch" "$root_dir/out/backend/smail-admin_linux_$arch" \ "$root_dir/out/backend/smail-config_linux_$arch" \ "$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 /opt ] || [ -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 find "$runtime_dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + 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 if ! getent group smail-plugin >/dev/null; then groupadd --system smail-plugin; fi if ! getent passwd smail-plugin >/dev/null; then useradd --system --gid smail-plugin --home-dir /nonexistent --shell /usr/sbin/nologin smail-plugin; fi install -d -o root -g root -m 0755 "$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=/opt/smail' '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-plugin)" >> /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-plugin)" >> /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 install -o root -g root -m 0755 "$root_dir/out/backend/smail_linux_$arch" "$runtime_dir/smail" install -o root -g root -m 0700 "$root_dir/out/backend/smail-admin_linux_$arch" "$runtime_dir/smail-admin" install -o root -g root -m 0700 "$root_dir/out/backend/smail-config_linux_$arch" "$runtime_dir/smail-config" install -o root -g root -m 0644 "$root_dir/deploy/systemd/smail.service" /etc/systemd/system/smail.service install -o root -g root -m 0644 "$root_dir/deploy/systemd/smail-admin.service" /etc/systemd/system/smail-admin.service systemctl daemon-reload systemctl enable smail # 清理旧版本可能留下的开机启动关系。smail-admin 单元没有 [Install],不能再次 enable。 systemctl disable smail-admin >/dev/null 2>&1 || true echo "配置、数据库、证书、日志、插件和绑定信息均未继承;请先完成初始化配置,再使用 systemctl start smail 启动。" echo "smail-admin 仅允许按需执行 start/stop,不支持开机自启动。" echo "主服务默认内存高水位 384 MiB、硬上限 512 MiB;不是容量承诺,请先核对同机资源预算。" echo "旧环境文件不会自动补写 GOMEMLIMIT;启动后请按《生产资源部署说明》执行只读资源预检。"