diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index d49f646..f690016 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -3,27 +3,36 @@ name: Build and Deploy on: push: branches: [main] - pull_request: - branches: [main] jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build Docker image + run: | + docker build -t awesome-index:latest . + deploy: + needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Copy files to server + - name: Copy scripts to server uses: appleboy/scp-action@master with: host: 114.134.187.170 username: root password: ${{ secrets.SERVER_PASSWORD }} - source: "package.json,package-lock.json,src,public,.env.example,start.sh" + source: "start.sh,.env.example" target: "/app/awesome/" - - name: Deploy and start service + - name: Restart container uses: appleboy/ssh-action@master with: host: 114.134.187.170 @@ -31,10 +40,5 @@ jobs: password: ${{ secrets.SERVER_PASSWORD }} script: | cd /app/awesome - - # 如不存在 .env 则从 example 复制 - [ ! -f .env ] && cp .env.example .env - - # 执行启动脚本 chmod +x start.sh bash start.sh diff --git a/Jenkinsfile b/Jenkinsfile index 08b83dc..40d40d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,6 @@ pipeline { environment { IMAGE_NAME = 'awesome-index' - CONTAINER_NAME = 'awesome' DEPLOY_PATH = '/app/awesome' SSH_HOST = '114.134.187.170' SSH_USER = 'root' @@ -13,9 +12,8 @@ pipeline { stage('Build') { steps { script { - echo "Building Docker image ${IMAGE_NAME}:${BUILD_NUMBER}" - sh "docker build -t ${IMAGE_NAME}:${BUILD_NUMBER} ." - sh "docker tag ${IMAGE_NAME}:${BUILD_NUMBER} ${IMAGE_NAME}:latest" + echo "Building Docker image ${IMAGE_NAME}:latest" + sh "docker build -t ${IMAGE_NAME}:latest ." } } } @@ -29,28 +27,12 @@ pipeline { // 创建部署目录 sh "sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${remote} 'mkdir -p ${DEPLOY_PATH}/data ${DEPLOY_PATH}/logs'" - // 复制必要文件 - sh "sshpass -p '${SSH_PASS}' scp -o StrictHostKeyChecking=no docker-compose.yml ${remote}:${DEPLOY_PATH}/" - sh "sshpass -p '${SSH_PASS}' scp -o StrictHostKeyChecking=no .env.example ${remote}:${DEPLOY_PATH}/.env" + // 上传脚本(构建机 = 部署机,镜像已在服务器上,无需传输镜像) + sh "sshpass -p '${SSH_PASS}' scp -o StrictHostKeyChecking=no .env.example ${remote}:${DEPLOY_PATH}/.env.example" sh "sshpass -p '${SSH_PASS}' scp -o StrictHostKeyChecking=no start.sh ${remote}:${DEPLOY_PATH}/" - // 导入镜像 - sh "docker save ${IMAGE_NAME}:latest | gzip > ${IMAGE_NAME}.tar.gz" - sh "sshpass -p '${SSH_PASS}' scp -o StrictHostKeyChecking=no ${IMAGE_NAME}.tar.gz ${remote}:${DEPLOY_PATH}/" - - // 在服务器上加载并重启 - sh """ - sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${remote} ' - cd ${DEPLOY_PATH} && - gunzip -c ${IMAGE_NAME}.tar.gz | docker load && - rm -f ${IMAGE_NAME}.tar.gz && - docker compose down --remove-orphans && - docker compose up -d - ' - """ - - // 清理本地 - sh "rm -f ${IMAGE_NAME}.tar.gz" + // 在服务器上重启容器 + sh "sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${remote} 'cd ${DEPLOY_PATH} && chmod +x start.sh && bash start.sh'" } } } diff --git a/README.md b/README.md index 8bc3c26..92ae3c4 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ npm test # node:test,10 个用例 ### Docker 部署 ```bash -cp .env.example .env # 修改 SESSION_SECRET / ADMIN_INIT_PASSWORD / AI_INGEST_KEY -docker compose up -d --build +docker build -t awesome-index:latest . +bash start.sh # 用新镜像重启容器 ``` -数据持久化在宿主机 `./data/awesome.duckdb`,升级镜像不丢数据。 +`start.sh` 会检查 `.env`(不存在则从 `.env.example` 复制),然后 `docker run` 启动容器。数据持久化在宿主机 `./data/awesome.duckdb`,重启不丢数据。 ## 给人的入口 diff --git a/deploy.sh b/deploy.sh index b3ba0a2..73cec79 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Awesome Index 部署脚本(直接部署,不使用 Docker) +# Awesome Index 部署脚本(构建机 = 部署机:本地构建镜像,SSH 重启容器) # 服务器: 114.134.187.170 set -e @@ -19,20 +19,15 @@ if ! command -v sshpass &> /dev/null; then exit 1 fi -echo "1. 创建部署目录..." -sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" \ - "mkdir -p $DEPLOY_PATH/data $DEPLOY_PATH/logs" +echo "1. 构建 Docker 镜像..." +docker build -t awesome-index:latest . -echo "2. 上传源码..." -sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no -r \ - package.json package-lock.json src public .env.example start.sh \ +echo "2. 上传脚本到服务器..." +sshpass -p "$REMOTE_PASS" scp -o StrictHostKeyChecking=no \ + start.sh .env.example \ "$REMOTE_USER@$REMOTE_HOST:$DEPLOY_PATH/" -echo "3. 重命名 .env.example 为 .env(如不存在)..." -sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" \ - "cd $DEPLOY_PATH && [ ! -f .env ] && cp .env.example .env" - -echo "4. 在服务器上执行启动脚本..." +echo "3. 在服务器上重启容器..." sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" \ "cd $DEPLOY_PATH && chmod +x start.sh && bash start.sh" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f461aaf..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -services: - web: - build: . - container_name: awesome-index - ports: - - "3000:3000" - volumes: - - ./data:/app/data - environment: - PORT: "3000" - DUCKDB_PATH: /app/data/awesome.duckdb - SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production} - ADMIN_INIT_PASSWORD: ${ADMIN_INIT_PASSWORD:-admin} - AI_INGEST_KEY: ${AI_INGEST_KEY:-} - BASE_URL: ${BASE_URL:-http://localhost:3000} - restart: unless-stopped diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 43c15ff..02e7864 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -58,8 +58,8 @@ ``` awesome/ ├── package.json # 依赖与 scripts(dev / start / test / migrate) -├── Dockerfile # 多阶段构建(node:22-alpine) -├── docker-compose.yml # 单服务编排 + data 卷 + healthcheck +├── Dockerfile # 多阶段构建(node:22-bookworm-slim) +├── start.sh # 用已构建镜像重启容器(docker run,无 compose) ├── .env.example # 配置样例 ├── .gitignore # node_modules / data / .env │ diff --git a/start.sh b/start.sh index ae15473..fd4a553 100644 --- a/start.sh +++ b/start.sh @@ -1,32 +1,38 @@ #!/bin/bash set -e -# Awesome Index 启动脚本(直接部署,不使用 Docker) +# Awesome Index 重启脚本(使用已构建的镜像,纯 docker run,无 compose) DEPLOY_PATH="$(cd "$(dirname "$0")" && pwd)" cd "$DEPLOY_PATH" # 检查 .env 文件 if [ ! -f .env ]; then - echo "错误: 缺少 .env 文件,请从 .env.example 复制并修改" - exit 1 + if [ -f .env.example ]; then + cp .env.example .env + echo "已从 .env.example 创建 .env(请检查 SESSION_SECRET / ADMIN_INIT_PASSWORD / AI_INGEST_KEY)" + else + echo "错误: 缺少 .env 文件" + exit 1 + fi fi -# 安装依赖 -echo "安装依赖..." -npm ci --omit=dev --no-audit --no-fund +# 停止并删除旧容器 +echo "停止旧容器..." +docker rm -f awesome-index 2>/dev/null || true -# 停止旧进程 -echo "停止旧进程..." -pkill -f "node src/index.js" 2>/dev/null || true -sleep 1 - -# 启动服务 +# 用新镜像启动容器 echo "启动 Awesome Index..." -nohup node src/index.js > logs/app.log 2>&1 & +docker run -d \ + --name awesome-index \ + --restart unless-stopped \ + -p 3000:3000 \ + -v "$DEPLOY_PATH/data:/app/data" \ + --env-file .env \ + awesome-index:latest # 健康检查 echo "等待服务就绪..." -sleep 2 +sleep 5 MAX_RETRIES=10 RETRY_COUNT=0 @@ -35,12 +41,11 @@ while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do echo "✓ 服务已启动: http://localhost:3000" echo "" echo "管理后台: http://localhost:3000/admin" - echo "默认账号: admin / (见 .env 中的 ADMIN_INIT_PASSWORD)" exit 0 fi RETRY_COUNT=$((RETRY_COUNT + 1)) sleep 2 done -echo "✗ 服务启动失败,请检查日志: tail -50 logs/app.log" +echo "✗ 服务启动失败,请检查日志: docker logs awesome-index" exit 1