From bbc4bdd14089cdd60db082eb970965877f823dd7 Mon Sep 17 00:00:00 2001 From: kinghwj Date: Sat, 12 Sep 2026 17:23:37 +0800 Subject: [PATCH] =?UTF-8?q?ci(deploy):=20=E6=B7=BB=E5=8A=A0=20dev=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 48 +++++++++++++++++ README.md | 10 ++++ deploy.sh | 101 +++++++++++++++++++++++++++++++++++ docs/code-design.md | 8 +++ docs/deploy-dev.md | 65 ++++++++++++++++++++++ docs/technical-guide.md | 41 ++++++++++++++ start_dashboard.sh | 6 ++- start_server.sh | 7 ++- 8 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100755 deploy.sh create mode 100644 docs/deploy-dev.md diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..6eb4138 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,48 @@ +name: Deploy WeReadBoard + +on: + push: + branches: + - dev + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + concurrency: + group: deploy-dev + cancel-in-progress: true + + steps: + - name: Prepare SSH + run: | + mkdir -p ~/.ssh + printf '%s\n' "${{ secrets.SERVER_SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + - name: Deploy + env: + SERVER_HOST: ${{ secrets.SERVER_HOST }} + SERVER_PORT: ${{ secrets.SERVER_PORT }} + SERVER_USER: ${{ secrets.SERVER_USER }} + DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} + SERVICE_NAME: ${{ vars.SERVICE_NAME }} + DEPLOY_BRANCH: ${{ github.ref_name }} + run: | + if [ -z "$SERVER_PORT" ]; then + SERVER_PORT="22" + fi + if [ -z "$DEPLOY_PATH" ]; then + DEPLOY_PATH="/opt/weread-board/WeReadBoard" + fi + if [ -z "$SERVICE_NAME" ]; then + SERVICE_NAME="weread.service" + fi + + ssh -i ~/.ssh/deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" \ + "cd '$DEPLOY_PATH' && + git fetch origin && + git checkout '$DEPLOY_BRANCH' && + git pull --ff-only origin '$DEPLOY_BRANCH' && + chmod +x ./deploy.sh && + SKIP_GIT_PULL=1 DEPLOY_BRANCH='$DEPLOY_BRANCH' SERVICE_NAME='$SERVICE_NAME' ./deploy.sh" diff --git a/README.md b/README.md index 189040c..190cc0b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,16 @@ PORT=8080 ./start_dashboard.sh # 或指定端口 > v2 的书架、统计、推荐、搜索等数据实时来自微信读书接口; > 未运行 `fetch_data.py` 时也能使用,只有进度/笔记等局部数据会缺少缓存加速。 +## 自动部署 + +服务器部署使用根目录的 `deploy.sh`: + +```bash +DEPLOY_BRANCH=dev ./deploy.sh +``` + +脚本会拉取指定分支,自动创建 `.venv`,安装 `requirements.txt`,执行数据库迁移,然后重启 `weread.service`。GitHub Actions 配置在 `.github/workflows/deploy.yml`,默认监听 `dev` 分支。 + ## 功能模块 | 模块 | 说明 | diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..0f2af9e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# 服务器部署脚本:拉取代码、准备 .venv、安装依赖、执行迁移并重启服务。 +set -euo pipefail + +PROJECT_DIR="${PROJECT_DIR:-$(cd "$(dirname "$0")" && pwd)}" +REMOTE="${REMOTE:-origin}" +BRANCH="${DEPLOY_BRANCH:-${GITHUB_REF_NAME:-}}" +SERVICE_NAME="${SERVICE_NAME:-weread.service}" +VENV_DIR="${VENV_DIR:-$PROJECT_DIR/.venv}" +ENV_FILE="${ENV_FILE:-$PROJECT_DIR/.env}" + +cd "$PROJECT_DIR" + +if [ -z "$BRANCH" ]; then + BRANCH="$(git branch --show-current)" +fi + +if [ -z "$BRANCH" ]; then + echo "未检测到部署分支,请设置 DEPLOY_BRANCH" + exit 1 +fi + +if [ "${SKIP_GIT_PULL:-0}" = "1" ]; then + echo "[1/5] 代码已拉取,跳过 git pull" +else + echo "[1/5] 拉取 $REMOTE/$BRANCH" + git fetch "$REMOTE" + git checkout "$BRANCH" + git pull --ff-only "$REMOTE" "$BRANCH" +fi + +if [ -f "$ENV_FILE" ]; then + set -a + # shellcheck disable=SC1090 + . "$ENV_FILE" + set +a +elif command -v systemctl >/dev/null 2>&1; then + SERVICE_ENV="$(systemctl show "$SERVICE_NAME" --property=Environment --value 2>/dev/null || true)" + for item in $SERVICE_ENV; do + case "$item" in + *=*) export "$item" ;; + esac + done +fi + +find_python() { + if command -v python3 >/dev/null 2>&1; then + command -v python3 + return + fi + if command -v python >/dev/null 2>&1; then + command -v python + return + fi + echo "未找到 python3 或 python,请先在服务器安装 Python" + exit 1 +} + +echo "[2/5] 检测 Python 虚拟环境" +if [ ! -x "$VENV_DIR/bin/python" ]; then + "$(find_python)" -m venv "$VENV_DIR" +fi + +PYTHON="$VENV_DIR/bin/python" + +echo "[3/5] 安装依赖" +if ! "$PYTHON" -m pip --version >/dev/null 2>&1; then + "$PYTHON" -m ensurepip --upgrade +fi +"$PYTHON" -m pip install -r requirements.txt + +echo "[4/5] 执行数据库迁移" +"$PYTHON" - <<'PY' +from pathlib import Path + +import pymysql + +from api_v2 import MYSQL_CONFIG + +migration = Path("migrations/001_reading_goals.sql") +if migration.exists(): + with pymysql.connect(**MYSQL_CONFIG) as conn: + with conn.cursor() as cur: + for sql in migration.read_text(encoding="utf-8").split(";"): + if sql.strip(): + cur.execute(sql) + conn.commit() +PY + +echo "[5/5] 重启服务 $SERVICE_NAME" +if command -v systemctl >/dev/null 2>&1; then + if [ "$(id -u)" -eq 0 ]; then + systemctl restart "$SERVICE_NAME" + systemctl status "$SERVICE_NAME" --no-pager + else + sudo systemctl restart "$SERVICE_NAME" + sudo systemctl status "$SERVICE_NAME" --no-pager + fi +else + echo "当前系统没有 systemctl,请手动用 $PYTHON app.py 启动" +fi diff --git a/docs/code-design.md b/docs/code-design.md index e5917e2..50193b5 100644 --- a/docs/code-design.md +++ b/docs/code-design.md @@ -41,6 +41,14 @@ 用户输入的目标名称和来源在 HTML 渲染前转义。日期和内容在保存前校验;原生对话框提供焦点管理和 Escape 关闭。 +## 部署流程 + +GitHub Actions 的 `.github/workflows/deploy.yml` 监听 `dev` 分支。分支 push 后,Actions 使用仓库 Secrets 中的服务器地址、用户和 SSH 私钥登录服务器,进入 `/opt/weread-board/WeReadBoard` 后调用 `deploy.sh`。 + +`deploy.sh` 以当前触发分支为准拉取代码,再检查项目 `.venv`。如果 `.venv/bin/python` 不存在,脚本使用服务器上的 `python3` 或 `python` 创建虚拟环境;随后用 `.venv/bin/python -m pip install -r requirements.txt` 安装依赖,执行 `migrations/001_reading_goals.sql`,最后重启 `weread.service`。 + +例:用户把代码推送到 `dev` 分支,服务器原来没有 `.venv`。部署脚本会在 `/opt/weread-board/WeReadBoard/.venv` 创建环境,安装 Flask 和 PyMySQL,确认阅读目标表存在,再让 systemd 重新用 `.venv/bin/python app.py` 启动服务。 + ## 示例与真实接口的边界 概览图表使用固定样例;目标日期状态使用浏览器当前本地日期。笔记导出包含当前选中书目的划线和个人想法全集,不受内容类型筛选影响,不包含书签内容及公开点评。 diff --git a/docs/deploy-dev.md b/docs/deploy-dev.md new file mode 100644 index 0000000..86bcda9 --- /dev/null +++ b/docs/deploy-dev.md @@ -0,0 +1,65 @@ +# dev 自动部署 + +GitHub Actions workflow 位于 `.github/workflows/deploy.yml`。每次 `dev` 分支 push 后,Actions 会通过 SSH 登录服务器,在部署目录执行根目录 `deploy.sh`,拉取最新 `dev`、安装依赖、执行阅读目标表迁移并重启服务。 + +## GitHub 配置 + +已配置的仓库 Secrets: + +- `SERVER_HOST` +- `SERVER_PORT` +- `SERVER_SSH_KEY` +- `SERVER_USER` + +可选再配置两个仓库 Variables: + +- `DEPLOY_PATH`:服务器上的项目目录,例如 `/opt/WeReadBoard` +- `SERVICE_NAME`:systemd 服务名,例如 `weread.service` + +未配置 Variables 时,workflow 默认使用 `/opt/weread-board/WeReadBoard` 和 `weread.service`。 + +## 服务器要求 + +服务器部署目录需要已经是本仓库的 Git checkout,并且能访问 `origin/dev`。运行服务的环境变量仍按现有应用配置,例如 `WEREAD_API_KEY`、`WEREAD_DB_HOST`、`WEREAD_DB_PORT`、`WEREAD_DB_USER`、`WEREAD_DB_PASSWORD` 和 `WEREAD_DB_NAME`。 + +如果这些变量放在项目目录的 `.env` 文件中,部署脚本会先加载 `.env`,再执行数据库迁移。SSH 用户需要能执行 `systemctl restart <服务名>`;非 root 用户需要配置免密 `sudo systemctl restart/status <服务名>`。 + +`deploy.sh` 会执行: + +```bash +git fetch origin +git checkout dev +git pull --ff-only origin dev +python3 -m venv .venv # 仅 .venv 不存在时执行 +.venv/bin/python -m pip install -r requirements.txt +# 读取 api_v2.MYSQL_CONFIG 后执行 migrations/001_reading_goals.sql +sudo systemctl restart "$SERVICE_NAME" +``` + +其中迁移实际通过 `api_v2.MYSQL_CONFIG` 读取数据库配置并执行 SQL 文件;SQL 使用 `CREATE TABLE IF NOT EXISTS`,可重复执行。 + +## systemd 示例 + +```ini +[Unit] +Description=WeReadBoard +After=network.target + +[Service] +WorkingDirectory=/opt/weread-board/WeReadBoard +Environment=WEREAD_API_KEY=wrk-xxxxxxxx +Environment=WEREAD_DB_HOST=127.0.0.1 +Environment=WEREAD_DB_PORT=3306 +Environment=WEREAD_DB_USER=weread +Environment=WEREAD_DB_PASSWORD=change-me +Environment=WEREAD_DB_NAME=weread +Environment=PORT=8770 +ExecStart=/opt/weread-board/WeReadBoard/.venv/bin/python app.py +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +``` + +如果服务名或部署目录不同,在仓库 Variables 中调整 `SERVICE_NAME` 和 `DEPLOY_PATH`。 diff --git a/docs/technical-guide.md b/docs/technical-guide.md index e183ea6..a352567 100644 --- a/docs/technical-guide.md +++ b/docs/technical-guide.md @@ -88,6 +88,43 @@ PORT=8080 python3 app.py 使用已安装 `requirements.txt` 的 Python 环境。若 8080 已有本项目实例,先停止旧实例再启动,使 Flask 加载新接口。迁移可重复执行;运行账户需要目标表的读写权限。目标表不参与 `fetch_data.py` 的采集覆盖。 +## GitHub 自动部署 + +服务器部署使用根目录的 `deploy.sh`。脚本会先拉取指定分支,再检测 Python 环境;若项目 `.venv` 不存在,会自动用系统 `python3` 或 `python` 创建 `.venv`,随后安装 `requirements.txt`、执行阅读目标迁移,并重启 systemd 服务。 + +当前服务器配置可按下列默认值使用: + +```sh +PROJECT_DIR=/opt/weread-board/WeReadBoard +SERVICE_NAME=weread.service +VENV_DIR=/opt/weread-board/WeReadBoard/.venv +``` + +GitHub Actions 文件位于 `.github/workflows/deploy.yml`,默认在 `dev` 分支 push 后触发,也支持手动触发。仓库需要在 `Settings -> Secrets and variables -> Actions -> Secrets` 配置: + +| Secret | 说明 | +|---|---| +| `SERVER_HOST` | 服务器 IP | +| `SERVER_PORT` | SSH 端口,未配置时按 22 处理 | +| `SERVER_USER` | SSH 登录用户 | +| `SERVER_SSH_KEY` | GitHub Actions 登录服务器用的私钥 | + +可选配置放在 `Settings -> Secrets and variables -> Actions -> Variables`: + +| Variable | 说明 | +|---|---| +| `DEPLOY_PATH` | 可选,默认 `/opt/weread-board/WeReadBoard` | +| `SERVICE_NAME` | 可选,默认 `weread.service` | + +如果在服务器手动部署,可执行: + +```sh +cd /opt/weread-board/WeReadBoard +DEPLOY_BRANCH=dev ./deploy.sh +``` + +`deploy.sh` 会优先读取项目根目录 `.env`;没有 `.env` 时,会读取 `weread.service` 当前 `Environment=` 配置。按截图中的 service 写法也能拿到端口、API Key 和数据库配置。若以后改用普通部署用户,需要允许该用户免密执行 `systemctl restart weread.service`。 + 浏览器旧目标不会自动覆盖数据库。在原访问地址打开“阅读目标”,点击“导入已有目标”;数据库已有同一 ID 时跳过,整批成功才标记导入完成。`weread-goals-v2` 原数据保留,`weread-goals-v2-imported` 只记录已导入。不同浏览器、域名和端口的旧存储需要分别在原地址导入。 保存失败会保留表单并提示重试;读取失败显示错误和重试按钮,不冒充空列表。若新建提示 ID 已存在,刷新目标列表确认上次请求是否已经保存。数据库不可用或尚未执行迁移时,目标接口返回 503,不退回浏览器保存。 @@ -105,4 +142,8 @@ node --check static/js/weapi.js 本次样式收紧覆盖正式看板的概览、书架和阅读目标页。顶栏、标题区、筛选区、统计卡、书架网格、右侧目标摘要、目标介绍区和目标卡片均使用覆盖规则压缩占位;书架在窄屏固定两列,避免横向溢出。本地真实数据验收覆盖 1280 px 桌面和 390 px 手机宽度:桌面顶栏 58 px,统计卡从约 200 px 开始,书架一行 7 本,目标页介绍区约 67 px 高,目标卡约 317 px 宽;手机顶栏 56 px,书架两列,目标卡约 298 px 宽,概览、书架和目标页均未出现横向溢出。 +## dev 自动部署 + +`dev` 分支 push 后由 `.github/workflows/deploy.yml` 触发部署。Actions 通过仓库 Secrets 登录服务器,在 `DEPLOY_PATH` 指定的目录先拉取 `origin/dev`,再执行根目录 `deploy.sh`,完成加载可选 `.env`、安装 Python 依赖、执行阅读目标表迁移和重启 systemd 服务。详细配置见 `docs/deploy-dev.md`。 + 本次数据库验收已在 8080 正式看板完成:页面新建目标后刷新仍可读取,编辑后再次刷新仍可读取,并已清理临时验收目标。读取目标列表未完成时,新建和编辑入口只显示目标页的加载或错误状态,不会打开保存表单。 diff --git a/start_dashboard.sh b/start_dashboard.sh index e06b743..a63149e 100755 --- a/start_dashboard.sh +++ b/start_dashboard.sh @@ -12,7 +12,11 @@ set -euo pipefail cd "$(dirname "$0")" -PYTHON_BIN="${PYTHON_BIN:-$HOME/.workbuddy/binaries/python/envs/default/bin/python}" +PROJECT_PYTHON="$(pwd)/.venv/bin/python" +PYTHON_BIN="${PYTHON_BIN:-$PROJECT_PYTHON}" +if [ ! -x "$PYTHON_BIN" ]; then + PYTHON_BIN="$HOME/.workbuddy/binaries/python/envs/default/bin/python" +fi if [ ! -x "$PYTHON_BIN" ]; then PYTHON_BIN="$(command -v python3)" fi diff --git a/start_server.sh b/start_server.sh index 1c1fbfe..fa4bbd2 100755 --- a/start_server.sh +++ b/start_server.sh @@ -8,7 +8,12 @@ # ./start_server.sh status 查看状态 PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" -PYTHON="${PYTHON:-$(command -v python3 || command -v python)}" +if [ -x "$PROJECT_DIR/.venv/bin/python" ]; then + DEFAULT_PYTHON="$PROJECT_DIR/.venv/bin/python" +else + DEFAULT_PYTHON="$(command -v python3 || command -v python)" +fi +PYTHON="${PYTHON:-$DEFAULT_PYTHON}" PID_FILE="$PROJECT_DIR/.server.pid" LOG_FILE="/tmp/weread_server.log" # 默认从 8080 启动;也可通过 PORT 或 WEREAD_PORT 指定端口