Smail/deploy/install-systemd.sh
2026-10-17 00:19:02 +08:00

264 lines
10 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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!"*|*"初始化完成"*|*"Setup Completed"*|*"Setup finished"*)
printf '\n%s\n' "=========================================================="
printf '%s\n' " 检测到 Smail 初始化已顺利完成!监控自动退出。"
printf '%s\n' "=========================================================="
break
;;
esac
done