#!/usr/bin/env bash # migrate-to-modelrouter.sh — 把 llmsproxy 时代的部署迁到 modelrouter 命名。 # # 背景:产品名早已是 ModelRouter,但二进制/配置目录/服务名/插件目录仍沿用 # 仓库旧名 llmsproxy。这次改名不留"半新半旧"的机器:脚本复制(不是移动) # 旧目录到新路径、改写 systemd unit、安装新二进制,并在最后做一遍校验。 # # 安全约定: # * **默认 dry-run**,只打印将要做什么;加 --apply 才真正执行。 # * 旧目录**永不删除**,迁移结束仍可 `--rollback` 回到旧布局。 # * 已存在的新路径不会被静默覆盖:先备份成 .bak-<时间戳>,再写入。 # * 不自动重启正在运行的服务,除非显式 --restart。 # # 用法: # sudo ./packaging/migrate-to-modelrouter.sh # 预演 # sudo ./packaging/migrate-to-modelrouter.sh --apply # 执行 # sudo ./packaging/migrate-to-modelrouter.sh --apply --restart # sudo ./packaging/migrate-to-modelrouter.sh --rollback --apply set -euo pipefail OLD_NAME="llmsproxy" NEW_NAME="modelrouter" OLD_ETC="/etc/${OLD_NAME}" NEW_ETC="/etc/${NEW_NAME}" OLD_BIN="/usr/local/bin/${OLD_NAME}" NEW_BIN="/usr/local/bin/${NEW_NAME}" OLD_UNIT="/etc/systemd/system/${OLD_NAME}.service" NEW_UNIT="/etc/systemd/system/${NEW_NAME}.service" OLD_SHARE="/usr/share/${OLD_NAME}" NEW_SHARE="/usr/share/${NEW_NAME}" APPLY=0 RESTART=0 ROLLBACK=0 for arg in "$@"; do case "$arg" in --apply) APPLY=1 ;; --restart) RESTART=1 ;; --rollback) ROLLBACK=1 ;; -h|--help) sed -n '2,30p' "$0"; exit 0 ;; *) echo "unknown argument: $arg" >&2; exit 2 ;; esac done ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; } skip() { printf ' \033[90m·\033[0m %s\n' "$*"; } plan() { printf ' \033[33m→\033[0m %s\n' "$*"; } die() { printf '\033[31mfail\033[0m %s\n' "$*" >&2; exit 1; } run() { if [ "$APPLY" -eq 1 ]; then "$@" else printf ' [dry-run] %s\n' "$*" fi } backup_if_exists() { local p="$1" if [ -e "$p" ]; then local b="${p}.bak-$(date +%Y%m%d%H%M%S)" plan "备份已存在的 $p → $b" run cp -a "$p" "$b" fi } stamp() { date +%Y%m%d%H%M%S; } if [ "$(id -u)" -ne 0 ] && [ "$APPLY" -eq 1 ]; then die "--apply 需要 root(会写 /etc 与 /usr/local/bin)" fi if [ "$ROLLBACK" -eq 1 ]; then echo "== 回滚到 ${OLD_NAME} 布局 ==" [ -d "$NEW_ETC" ] || die "$NEW_ETC 不存在,没有可回滚的内容" if [ -e "$OLD_ETC" ]; then plan "$OLD_ETC 已存在,保留它(回滚包在 ${NEW_ETC} 原地)" plan "如需覆盖请先手工比对:diff -r $OLD_ETC $NEW_ETC" else plan "复制 $NEW_ETC → $OLD_ETC(不删除新目录)" run cp -a "$NEW_ETC" "$OLD_ETC" fi if [ -e "$NEW_UNIT" ]; then plan "恢复 $OLD_UNIT" run cp "$NEW_UNIT" "$OLD_UNIT" run sed -i "s#${NEW_NAME}#${OLD_NAME}#g" "$OLD_UNIT" fi if [ "$RESTART" -eq 1 ]; then run systemctl daemon-reload run systemctl enable --now "${OLD_NAME}.service" fi echo echo "回滚$( [ "$APPLY" -eq 1 ] && echo '完成' || echo '预演完成(未改动任何文件)' )" exit 0 fi echo "== 迁移 ${OLD_NAME} → ${NEW_NAME}$( [ "$APPLY" -eq 1 ] && echo '' || echo '(dry-run,加 --apply 执行)' ) ==" echo # ---- 1. 状态目录 ------------------------------------------------------- echo "[1/5] 配置与状态目录" if [ -d "$OLD_ETC" ]; then if [ -d "$NEW_ETC" ]; then ok "$NEW_ETC 已存在,保持不动(旧目录仍保留在 $OLD_ETC)" plan "合并前请人工比对:diff -r $OLD_ETC $NEW_ETC" else plan "复制 $OLD_ETC → $NEW_ETC(保留原目录,可回滚)" run cp -a "$OLD_ETC" "$NEW_ETC" fi else skip "$OLD_ETC 不存在,跳过(新安装无需迁移)" fi # 配置内的绝对路径也要跟着改;只改这些键,避免误伤注释里的示例。 if [ -d "$NEW_ETC" ] && [ -f "$NEW_ETC/config.yaml" ]; then if grep -q "$OLD_ETC" "$NEW_ETC/config.yaml"; then plan "把 $NEW_ETC/config.yaml 中的 $OLD_ETC 改为 $NEW_ETC" backup_if_exists "$NEW_ETC/config.yaml" run sed -i "s#${OLD_ETC}#${NEW_ETC}#g" "$NEW_ETC/config.yaml" else ok "config.yaml 内无旧路径" fi # master.key 的权限是安全边界,迁移后复核一次。 if [ -f "$NEW_ETC/master.key" ]; then plan "复核 master.key 权限为 0600" run chmod 0600 "$NEW_ETC/master.key" fi fi # ---- 2. 二进制 --------------------------------------------------------- echo "[2/5] 二进制" if [ -x "$NEW_BIN" ]; then ok "$NEW_BIN 已存在(安装包已就位)" elif [ -x "$OLD_BIN" ]; then plan "复制 $OLD_BIN → $NEW_BIN 并保留旧文件" run cp -a "$OLD_BIN" "$NEW_BIN" run chmod 0755 "$NEW_BIN" else plan "尚无二进制:从发行包或 'make build' 得到 $NEW_BIN 后重跑本脚本" fi # ---- 3. systemd ------------------------------------------------------ echo "[3/5] systemd unit" if [ -f "$NEW_UNIT" ]; then ok "$NEW_UNIT 已存在" elif [ -f "$OLD_UNIT" ]; then plan "生成 $NEW_UNIT(改写 ExecStart/WorkingDirectory/ReadWritePaths)" if [ "$APPLY" -eq 1 ]; then sed "s#/usr/local/bin/${OLD_NAME}#${NEW_BIN}#g; s#${OLD_ETC}#${NEW_ETC}#g" \ "$OLD_UNIT" > "$NEW_UNIT" chmod 0644 "$NEW_UNIT" else printf ' [dry-run] sed ... %s > %s\n' "$OLD_UNIT" "$NEW_UNIT" fi plan "旧 unit 保留:$OLD_UNIT(确认新服务可用后再 disable)" else skip "没有 systemd unit,跳过" fi # ---- 4. 分享目录(安装脚本/示例配置)---------------------------------- echo "[4/5] /usr/share 资源" if [ -d "$OLD_SHARE" ] && [ ! -d "$NEW_SHARE" ]; then plan "复制 $OLD_SHARE → $NEW_SHARE" run cp -a "$OLD_SHARE" "$NEW_SHARE" else skip "$OLD_SHARE → $NEW_SHARE 无需处理" fi # ---- 5. 校验 --------------------------------------------------------- echo "[5/5] 校验" if [ "$APPLY" -eq 1 ]; then [ -x "$NEW_BIN" ] || die "$NEW_BIN 不可执行" ok "$NEW_BIN 可执行" if [ -f "$NEW_ETC/config.yaml" ]; then "$NEW_BIN" -config "$NEW_ETC/config.yaml" -check >/dev/null || die "配置校验失败" ok "配置校验通过($NEW_ETC/config.yaml)" fi if [ -f "$NEW_UNIT" ]; then systemctl daemon-reload ok "systemd 已 reload" fi if [ "$RESTART" -eq 1 ] && [ -f "$NEW_UNIT" ]; then systemctl enable --now "${NEW_NAME}.service" ok "${NEW_NAME}.service 已启动" else skip "未重启服务(需要时加 --restart)" fi echo echo "迁移完成。旧布局原样保留,回滚:" echo " sudo $0 --rollback --apply" echo echo "确认无误后再手工清理(本脚本不会删):" echo " systemctl disable --now ${OLD_NAME}.service 2>/dev/null || true" echo " rm -f $OLD_UNIT" echo " rm -rf $OLD_BIN $OLD_ETC $OLD_SHARE" else echo echo "以上为预演。确认后执行:" echo " sudo $0 --apply" fi