Files
MuMuAINovel/install-termux.sh
2026-05-18 14:31:54 +08:00

364 lines
15 KiB
Bash
Raw Permalink 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.
#!/bin/bash
# =============================================================================
# 墨木灵思 Termux 一键安装脚本
# =============================================================================
#
set -e
# ── 路径配置 ──────────────────────────────────────────────────────────────────
INSTALL_DIR="$HOME/mumulingsi" # 项目安装目录
DATA_DIR="$HOME/mumulingsi/data" # 数据库目录
LOG_DIR="$HOME/mumulingsi/logs" # 日志目录
REPO="https://ghfast.top/https://github.com/mumulingsi-project/mumulingsi.git" # GitHub 镜像
# ── 输出函数 ──────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
err() { echo -e "${RED}[✗]${NC} $1"; }
step() { echo -e "\n${CYAN}[$1/$2]${NC} $3"; }
# ── 转圈动画函数 ──────────────────────────────────────────────────────────────
# 用法: SPIN <后台进程PID> <提示文字> <日志文件路径>
# 原理: 检测进程是否存活,存活就显示旋转动画,结束后显示 ✅ 或 ❌
SPIN() {
local PID=$1 MSG=$2 LOGF=$3
echo -n " $MSG"
while kill -0 $PID 2>/dev/null; do
for s in ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏; do
echo -ne "\r $s $MSG"
sleep 0.3
kill -0 $PID 2>/dev/null || break 2
done
done
wait $PID
local RET=$?
if [ $RET -eq 0 ]; then
echo -e "\r ✅ $MSG 完成 "
else
echo -e "\r ❌ $MSG 失败 "
if [ -n "$LOGF" ] && [ -f "$LOGF" ]; then
echo -e "${RED}--- 错误日志 (最后20行) ---${NC}"
tail -20 "$LOGF"
echo -e "${RED}--- 日志结束 ---${NC}"
fi
exit 1
fi
}
# ── pip 镜像源(国内加速)──────────────────────────────────────────────────────
MIRROR="-i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com"
# =============================================================================
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ 📚 墨木灵思 Termux 一键安装 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════╝${NC}"
echo ""
TOTAL=9
# =============================================================================
# 步骤 1: 检查 Termux 环境
# =============================================================================
step 1 $TOTAL "检查环境"
if [ ! -d "/data/data/com.termux" ]; then
err "未检测到 Termux 环境,请在 Termux 中运行"
exit 1
fi
info "Termux 环境检测通过"
# =============================================================================
# 步骤 2: 安装系统依赖 (python/nodejs/git)
# 说明: pkg install 自动跳过已安装的包,重复运行不会重新下载
# =============================================================================
step 2 $TOTAL "安装系统依赖"
LOG="$TMPDIR/pkg-install.log"
pkg install -y python nodejs git > "$LOG" 2>&1 &
SPIN $! "安装中" "$LOG"
# =============================================================================
# 步骤 3: 拉取/更新项目源码
# 说明: 已有 .git 目录 → git pull 增量更新 (保留 venv/.env/data)
# 没有 → git clone 全量下载
# =============================================================================
step 3 $TOTAL "拉取/更新项目源码"
if [ -d "$INSTALL_DIR/.git" ]; then
LOG="$TMPDIR/git-pull.log"
(
cd "$INSTALL_DIR"
git fetch origin
git reset --hard origin/main 2>/dev/null || git reset --hard origin/master
) > "$LOG" 2>&1 &
SPIN $! "拉取中" "$LOG"
else
# 目录存在但不是 git 仓库,清理后重新克隆
if [ -d "$INSTALL_DIR" ]; then
rm -rf "$INSTALL_DIR"
fi
LOG="$TMPDIR/git-clone.log"
git clone "$REPO" "$INSTALL_DIR" > "$LOG" 2>&1 &
SPIN $! "克隆中" "$LOG"
fi
BACKEND="$INSTALL_DIR/backend"
FRONTEND="$INSTALL_DIR/frontend"
# =============================================================================
# 步骤 4: 应用 Termux 兼容补丁
# 说明: Termux 不支持 chromadb/sentence-transformers,需要修补代码避免崩溃
# 4a. memory_service.py — import 改为 try/except,缺失时优雅降级
# 4b. API 文件 — memory_service 导入改为 try/except
# 4c. .env — 已有则跳过,新建则写入默认配置
# =============================================================================
step 4 $TOTAL "应用 Termux 补丁"
LOG="$TMPDIR/patch.log"
(
# ── 4a. 修补 memory_service.py ──────────────────────────────────────────────
python3 << 'PYEOF'
import os
f = os.path.expanduser("~/mumulingsi/backend/app/services/memory_service.py")
with open(f) as fh:
c = fh.read()
# 顶层 import 改为 try/except
c = c.replace(
"import chromadb\\nfrom sentence_transformers import SentenceTransformer",
"""try:
import chromadb
from sentence_transformers import SentenceTransformer
MEMORY_AVAILABLE = True
except ImportError:
MEMORY_AVAILABLE = False
chromadb = None
SentenceTransformer = None"""
)
# __init__ 中加 MEMORY_AVAILABLE 检查,缺失时直接 return 不初始化
old_init = ' def __init__(self):\n \\\"\\\"\\\"初始化ChromaDB和Embedding模型\\\"\\\"\\\"\n if self._initialized:\n return\n \n try:'
new_init = ' def __init__(self):\n \\\"\\\"\\\"初始化ChromaDB和Embedding模型\\\"\\\"\\\"\n if self._initialized:\n return\n\n if not MEMORY_AVAILABLE:\n self.client = None\n self.model = None\n self.collection = None\n self._initialized = True\n logger.warning("⚠️ 向量记忆功能不可用(缺少 chromadb/sentence-transformers")\n return\n\n try:'
c = c.replace(old_init, new_init, 1)
with open(f, "w") as fh:
fh.write(c)
print(" ✅ memory_service.py 已修补")
PYEOF
# ── 4b. 修补 API 文件的 memory_service 导入 ──────────────────────────────────
python3 << 'PYEOF'
import os
home = os.path.expanduser("~")
files = [
f"{home}/mumulingsi/backend/app/api/chapters.py",
f"{home}/mumulingsi/backend/app/api/memories.py",
f"{home}/mumulingsi/backend/app/api/outlines.py",
f"{home}/mumulingsi/backend/app/api/projects.py",
f"{home}/mumulingsi/backend/app/services/foreshadow_service.py",
]
old = 'from app.services.memory_service import memory_service'
new = 'try:\n from app.services.memory_service import memory_service\nexcept ImportError:\n memory_service = None'
count = 0
for f in files:
if not os.path.exists(f): continue
with open(f) as fh: c = fh.read()
if old in c:
c = c.replace(old, new)
with open(f, 'w') as fh: fh.write(c)
count += 1
print(f" ✅ API 文件已修补({count} 个)")
PYEOF
# ── 4c. 创建 .env 配置文件 (已有则跳过) ─────────────────────────────────────
mkdir -p "$DATA_DIR" "$LOG_DIR"
if [ ! -f "$BACKEND/.env" ]; then
cat > "$BACKEND/.env" << 'ENVEOF'
# 墨木灵思 Termux 配置
APP_NAME=墨木灵思
APP_HOST=0.0.0.0
APP_PORT=8000
DEBUG=false
TZ=Asia/Shanghai
# SQLite 数据库(替代 PostgreSQL
DATABASE_URL=sqlite+aiosqlite:///data/data/com.termux/files/home/mumulingsi/data/ai_story.db
# 日志
LOG_LEVEL=INFO
LOG_TO_FILE=true
LOG_FILE_PATH=/data/data/com.termux/files/home/mumulingsi/logs/app.log
LOG_MAX_BYTES=10485760
LOG_BACKUP_COUNT=5
# CORS
CORS_ORIGINS=["http://localhost:3000","http://127.0.0.1:8000"]
# ⚠️ 请填入你的 API Key
OPENAI_API_KEY=***
OPENAI_BASE_URL=https://api.openai.com/v1
DEFAULT_AI_PROVIDER=openai
DEFAULT_MODEL=gpt-4o-mini
DEFAULT_TEMPERATURE=0.7
DEFAULT_MAX_TOKENS=4096
# 本地登录账号
LOCAL_AUTH_ENABLED=True
LOCAL_AUTH_USERNAME=admin
LOCAL_AUTH_PASSWORD=admin123
LOCAL_AUTH_DISPLAY_NAME=Admin
ENVEOF
# 替换占位符路径为实际 $HOME 路径
sed -i "s|/data/data/com.termux/files/home|$HOME|g" "$BACKEND/.env"
sed -i "s|LOG_FILE_PATH=.*|LOG_FILE_PATH=$LOG_DIR/app.log|" "$BACKEND/.env"
echo " ✅ .env 已创建"
else
echo " ✅ .env 已存在,跳过"
fi
) > "$LOG" 2>&1 &
SPIN $! "修补中" "$LOG"
# =============================================================================
# 步骤 5: 安装 Python 依赖
# 说明: venv 不存在则创建;pip 自动跳过已安装的包,只安装新增的
# =============================================================================
step 5 $TOTAL "安装 Python 依赖"
if [ ! -d "$BACKEND/venv" ]; then
python -m venv "$BACKEND/venv"
fi
PIP="$BACKEND/venv/bin/pip"
# 写入精简依赖列表 (不含 Termux 不兼容的 psutil/chromadb/sentence-transformers)
cat > "$BACKEND/requirements-lite.txt" << 'REQEOF'
fastapi==0.121.0
uvicorn==0.38.0
python-multipart==0.0.20
sqlalchemy==2.0.36
alembic==1.14.0
aiosqlite==0.22.1
pydantic==2.12.4
pydantic-settings==2.11.0
openai==2.7.0
anthropic==0.72.0
httpx==0.28.1
python-dotenv==1.1.0
aiosmtplib==4.0.2
mcp==1.22.0
greenlet>=3.0
REQEOF
LOG="$TMPDIR/pip-install.log"
(
$PIP install --upgrade pip setuptools wheel -q $MIRROR
$PIP install -r "$BACKEND/requirements-lite.txt" $MIRROR
) > "$LOG" 2>&1 &
SPIN $! "安装中" "$LOG"
# =============================================================================
# 步骤 6: 数据库迁移
# 说明: 首次安装创建所有表;重复运行自动跳过已执行的迁移
# =============================================================================
step 6 $TOTAL "数据库迁移"
export DATABASE_URL="sqlite+aiosqlite:///$DATA_DIR/ai_story.db"
LOG="$TMPDIR/alembic.log"
(
cd "$BACKEND"
"$BACKEND/venv/bin/python" -m alembic -c alembic-sqlite.ini upgrade head
) > "$LOG" 2>&1 &
SPIN $! "迁移中" "$LOG"
# =============================================================================
# 步骤 7: 安装前端依赖
# 说明: node_modules 已存在则跳过;首次运行 npm install
# =============================================================================
step 7 $TOTAL "安装前端依赖"
cd "$FRONTEND"
if [ -d "node_modules" ] && [ -f "node_modules/.package-lock.json" ]; then
info "前端依赖已安装,跳过"
else
LOG="$TMPDIR/npm-install.log"
npm install --include=dev --loglevel=silent > "$LOG" 2>&1 &
SPIN $! "安装中" "$LOG"
fi
# =============================================================================
# 步骤 8: 构建前端
# 说明: 每次都重新构建,确保最新代码生效
# =============================================================================
step 8 $TOTAL "构建前端"
node "$FRONTEND/node_modules/typescript/bin/tsc" -b 2>/dev/null || true
LOG="$TMPDIR/vite-build.log"
node "$FRONTEND/node_modules/vite/bin/vite.js" build > "$LOG" 2>&1 &
SPIN $! "构建中" "$LOG"
grep -E "built in" "$LOG" | sed 's/^/ /'
# =============================================================================
# 步骤 9: 创建启动脚本
# 说明: 生成 ~/mumulingsi-start.sh,支持前台/后台运行
# =============================================================================
step 9 $TOTAL "创建启动脚本"
cat > "$HOME/mumulingsi-start.sh" << STARTEOF
#!/bin/bash
# 墨木灵思 Termux 启动脚本
set -e
BACKEND="$BACKEND"
PYTHON="\$BACKEND/venv/bin/python"
DATA_DIR="$DATA_DIR"
LOG_DIR="$LOG_DIR"
mkdir -p "\$DATA_DIR" "\$LOG_DIR"
export DATABASE_URL="sqlite+aiosqlite:///\$DATA_DIR/ai_story.db"
cd "\$BACKEND"
if [ "\$1" = "--bg" ]; then
echo "🚀 后台启动 墨木灵思 (端口 8000)..."
nohup "\$PYTHON" -m uvicorn app.main:app --host 0.0.0.0 --port 8000 \\
> "\$LOG_DIR/app.log" 2>&1 &
echo \$! > "$HOME/mumulingsi.pid"
sleep 2
if kill -0 \$(cat "$HOME/mumulingsi.pid") 2>/dev/null; then
echo "✅ 已启动, PID: \$(cat $HOME/mumulingsi.pid)"
else
echo "❌ 启动失败,查看日志: \$LOG_DIR/app.log"
exit 1
fi
else
echo "🚀 启动 墨木灵思 (端口 8000, Ctrl+C 停止)..."
exec "\$PYTHON" -m uvicorn app.main:app --host 0.0.0.0 --port 8000
fi
STARTEOF
chmod +x "$HOME/mumulingsi-start.sh"
info "启动脚本已创建: ~/mumulingsi-start.sh"
# =============================================================================
# 安装完成
# =============================================================================
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ 🎉 墨木灵思 安装完成! ║${NC}"
echo -e "${GREEN}╠══════════════════════════════════════════════╣${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}║ 前台运行(Ctrl+C 停止): ║${NC}"
echo -e "${GREEN}║ bash ~/mumulingsi-start.sh ║${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}║ 后台运行: ║${NC}"
echo -e "${GREEN}║ bash ~/mumulingsi-start.sh --bg ║${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}║ 停止后台: ║${NC}"
echo -e "${GREEN}║ kill \$(cat ~/mumulingsi.pid) ║${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}║ 查看日志: ║${NC}"
echo -e "${GREEN}║ tail -f ~/mumulingsi/logs/app.log ║${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}║ 🌐 访问: http://127.0.0.1:8000 ║${NC}"
echo -e "${GREEN}║ 🔑 账号: admin / admin123 ║${NC}"
echo -e "${GREEN}║ ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${YELLOW} ⚠️ 首次使用前请编辑 API Key:${NC}"
echo -e " nano $BACKEND/.env"
echo -e " 修改 OPENAI_API_KEY 和 OPENAI_BASE_URL"
echo ""