diff --git a/scripts/install-local-source.sh b/scripts/install-local-source.sh new file mode 100755 index 0000000..5eaa67f --- /dev/null +++ b/scripts/install-local-source.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +APP_NAME="infinite-canvas" +REPO_URL="${REPO_URL:-https://gitea.chickliu.fun/Hermes/infinite-canvas.git}" +APP_DIR="${APP_DIR:-$HOME/.Hermes/workspace/git/infinite-canvas}" +HOST="${HOST:-0.0.0.0}" +WEB_PORT="${WEB_PORT:-3000}" +API_PORT="${API_PORT:-8080}" +DATA_DIR="${DATA_DIR:-$APP_DIR/data}" +DB_PATH="${DATABASE_DSN:-$DATA_DIR/infinite-canvas.db}" +PROMPT_DATA_DIR="${PROMPT_DATA_DIR:-$DATA_DIR/prompts}" +LOG_DIR="${LOG_DIR:-$APP_DIR/logs}" +PID_DIR="${PID_DIR:-$APP_DIR/.pids}" +MODE="${1:-start}" + +info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33mWARN:\033[0m %s\n' "$*" >&2; } +fail() { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } +need_cmd() { command -v "$1" >/dev/null 2>&1 || fail "缺少命令:$1"; } + +ensure_repo() { + if [ -d "$APP_DIR/.git" ]; then + info "使用现有代码目录:$APP_DIR" + return 0 + fi + info "克隆代码到:$APP_DIR" + mkdir -p "$(dirname "$APP_DIR")" + git clone "$REPO_URL" "$APP_DIR" +} + +ensure_deps() { + need_cmd git + need_cmd go + need_cmd npm + mkdir -p "$DATA_DIR" "$PROMPT_DATA_DIR" "$LOG_DIR" "$PID_DIR" + if [ ! -d "$APP_DIR/web/node_modules" ]; then + info "安装前端依赖" + (cd "$APP_DIR/web" && npm install --legacy-peer-deps) + fi +} + +kill_pid_file() { + local file="$1" + if [ -f "$file" ]; then + local pid + pid="$(cat "$file" 2>/dev/null || true)" + if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + for _ in $(seq 1 20); do + kill -0 "$pid" 2>/dev/null || break + sleep 0.2 + done + kill -9 "$pid" 2>/dev/null || true + fi + rm -f "$file" + fi +} + +stop_app() { + info "停止 $APP_NAME 源码服务" + kill_pid_file "$PID_DIR/backend.pid" + kill_pid_file "$PID_DIR/frontend.pid" +} + +start_app() { + ensure_repo + ensure_deps + stop_app + + info "启动后端:http://127.0.0.1:$API_PORT" + ( + cd "$APP_DIR" + PORT="$API_PORT" \ + DATABASE_DSN="$DB_PATH" \ + PROMPT_DATA_DIR="$PROMPT_DATA_DIR" \ + nohup go run . >>"$LOG_DIR/backend.log" 2>&1 & + echo $! >"$PID_DIR/backend.pid" + ) + + info "启动前端:http://127.0.0.1:$WEB_PORT" + ( + cd "$APP_DIR/web" + API_BASE_URL="http://127.0.0.1:$API_PORT" \ + nohup npm run dev -- -H "$HOST" -p "$WEB_PORT" >>"$LOG_DIR/frontend.log" 2>&1 & + echo $! >"$PID_DIR/frontend.pid" + ) + + info "等待服务就绪" + for _ in $(seq 1 120); do + if curl -fsS "http://127.0.0.1:$API_PORT/api/health" >/dev/null 2>&1 && \ + curl -fsS "http://127.0.0.1:$WEB_PORT/api/health" >/dev/null 2>&1; then + info "启动完成" + printf '前端: http://127.0.0.1:%s\n' "$WEB_PORT" + printf '后端: http://127.0.0.1:%s\n' "$API_PORT" + printf '账号: admin\n' + printf '默认密码: infinite-canvas(如已改过,以数据库中现有用户为准)\n' + return 0 + fi + sleep 1 + done + warn "服务未在预期时间内完成健康检查,请查看日志:" + printf ' tail -f %q %q\n' "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log" + return 1 +} + +status_app() { + for name in backend frontend; do + local file="$PID_DIR/$name.pid" + if [ -f "$file" ] && kill -0 "$(cat "$file")" 2>/dev/null; then + printf '%s: running pid=%s\n' "$name" "$(cat "$file")" + else + printf '%s: stopped\n' "$name" + fi + done + curl -fsS "http://127.0.0.1:$API_PORT/api/health" 2>/dev/null && printf ' backend_http=ok\n' || true + curl -fsS "http://127.0.0.1:$WEB_PORT/api/health" 2>/dev/null && printf ' frontend_proxy=ok\n' || true +} + +case "$MODE" in + start|install) start_app ;; + stop) stop_app ;; + restart) stop_app; start_app ;; + status) status_app ;; + *) + cat <