From 006d14adbb986d58b2864b87c56c9b2f717a13ea Mon Sep 17 00:00:00 2001 From: SmileQWQ Date: Fri, 17 Apr 2026 08:40:01 +0800 Subject: [PATCH] docs: improve deployment docs and release assets - rewrite README for GitHub presentation and add env copy steps for local development - expand DEPLOYMENT guide with API-only and full compose deployment paths - add apps/api/.env.example for development bootstrap - add docker-compose.full.yml and nginx config for full self-hosted deployment - update release workflow to publish a deployment bundle alongside web dist assets --- .github/workflows/build-and-release.yml | 36 ++++- DEPLOYMENT.md | 176 +++++++++++++++++------- README.md | 76 +++++++--- docker-compose.full.yml | 26 ++++ docker/nginx.full.conf | 29 ++++ 5 files changed, 271 insertions(+), 72 deletions(-) create mode 100644 docker-compose.full.yml create mode 100644 docker/nginx.full.conf diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index b99448f..b956c70 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -49,6 +49,19 @@ jobs: cd apps/web/dist zip -r ../../../subtracker-web-dist.zip . + - name: Archive deployment bundle + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' + run: | + mkdir -p release-assets/subtracker-deploy/docker + cp docker-compose.yml release-assets/subtracker-deploy/ + cp docker-compose.full.yml release-assets/subtracker-deploy/ + cp DEPLOYMENT.md release-assets/subtracker-deploy/ + cp README.md release-assets/subtracker-deploy/ + cp apps/api/.env.example release-assets/subtracker-deploy/api.env.example + cp docker/nginx.full.conf release-assets/subtracker-deploy/docker/nginx.full.conf + cd release-assets + zip -r ../subtracker-deploy-bundle.zip subtracker-deploy + - name: Upload web artifact if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 @@ -56,6 +69,13 @@ jobs: name: subtracker-web-dist path: subtracker-web-dist.zip + - name: Upload deployment artifact + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: subtracker-deploy-bundle + path: subtracker-deploy-bundle.zip + release: if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest @@ -91,6 +111,18 @@ jobs: cd apps/web/dist zip -r ../../../subtracker-web-dist.zip . + - name: Archive deployment bundle + run: | + mkdir -p release-assets/subtracker-deploy/docker + cp docker-compose.yml release-assets/subtracker-deploy/ + cp docker-compose.full.yml release-assets/subtracker-deploy/ + cp DEPLOYMENT.md release-assets/subtracker-deploy/ + cp README.md release-assets/subtracker-deploy/ + cp apps/api/.env.example release-assets/subtracker-deploy/api.env.example + cp docker/nginx.full.conf release-assets/subtracker-deploy/docker/nginx.full.conf + cd release-assets + zip -r ../subtracker-deploy-bundle.zip subtracker-deploy + - name: Resolve release tag id: release_tag shell: bash @@ -136,5 +168,7 @@ jobs: with: tag_name: ${{ steps.release_tag.outputs.value }} name: ${{ steps.release_tag.outputs.value }} - files: subtracker-web-dist.zip + files: | + subtracker-web-dist.zip + subtracker-deploy-bundle.zip generate_release_notes: true diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index c6740c4..6b1cf53 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,54 +1,52 @@ # SubTracker Deployment -本文档面向服务器部署,默认前提: +本文档面向服务器部署,提供两种方式: -- 前端静态文件由 **外部 Nginx** 托管 -- API 使用 Docker 镜像部署 -- SQLite 与 Logo 文件通过宿主机目录持久化 +1. **推荐方式**:外部 Nginx 托管前端,Docker 仅部署 API +2. **完整方式**:使用 `docker-compose.full.yml` 同时启动前端 Nginx + API -## 1. API 镜像约定 +--- -当前推荐镜像名: +## 1. API 镜像 + +默认镜像地址: ```text ghcr.io/smile-qwq/subtracker-api:latest ``` -`docker-compose.yml` 使用环境变量注入镜像名: +`docker-compose.yml` 默认会使用: ```bash SUBTRACKER_API_IMAGE=ghcr.io/smile-qwq/subtracker-api:latest ``` -如果不传,Compose 也会默认使用上面的镜像地址。 +--- -## 2. 启动 API +## 2. 推荐方式:外部 Nginx + API Docker -在服务器目录准备好: +适合: + +- 前端静态文件已经由 GitHub Release 或 CI 构建好 +- 服务器已有外部 Nginx +- 你希望前后端职责分离 + +### 启动 API + +准备: - `docker-compose.yml` - `data/` - `data/logos/` -然后执行: +执行: ```bash docker compose pull docker compose up -d ``` -## 3. 核心环境变量 - -Compose 中默认只保留这些部署变量: - -- `SUBTRACKER_API_IMAGE` -- `PORT` -- `HOST` -- `DATABASE_URL` -- `WEB_ORIGIN` -- `LOG_LEVEL` - -示例: +### 核心环境变量 ```bash SUBTRACKER_API_IMAGE=ghcr.io/smile-qwq/subtracker-api:latest @@ -59,18 +57,12 @@ WEB_ORIGIN=https://subtracker.example.com LOG_LEVEL=warn ``` -其余业务默认值继续由后端 `config.ts` 提供,不需要在生产环境全部展开。 +### 持久化目录 -## 4. 持久化目录 +- `./data` -> SQLite 数据库 +- `./data/logos` -> 本地 Logo 文件 -Compose 已挂载: - -- `./data` → SQLite 数据库目录 -- `./data/logos` → 本地 Logo 文件目录 - -请确保宿主机目录可写。 - -## 5. 外部 Nginx 反代示例 +### 外部 Nginx 示例 ```nginx server { @@ -104,47 +96,127 @@ server { } ``` -## 6. 前端部署 +### 前端静态文件部署 -前端不进入 Docker。 +前端不进入 API 镜像。 -推荐流程: +你可以: + +1. 从 Release 下载 `subtracker-web-dist.zip` +2. 解压后部署到外部 Nginx 静态目录 + +或者在本地自行构建: ```bash npm install npm run build -w apps/web ``` -然后把 `apps/web/dist` 发布到外部 Nginx 静态目录。 +然后将 `apps/web/dist` 发布到外部 Nginx 根目录。 -## 7. GitHub Actions 与镜像发布 +--- -仓库已预置一个统一 workflow: +## 3. 完整方式:docker-compose.full.yml -- `Build and Release` - - 在 `main` 和 PR 上执行安装、Prisma Generate、Lint、Build - - 在 `v*` tag 或手动触发时,额外发布: - - API Docker 镜像到 GHCR - - 前端静态文件 `subtracker-web-dist.zip` 到 GitHub Release +适合: -默认镜像发布地址: +- 想用 Compose 一次性启动前端和 API +- 不想单独维护外部 Nginx + +### 前提 + +需要先准备前端静态文件目录: ```text -ghcr.io/smile-qwq/subtracker-api +./web-dist ``` -前端静态文件发布方式: +你可以通过两种方式获得它: -- Release asset:`subtracker-web-dist.zip` -- 可直接下载后解压到外部 Nginx 静态目录 +#### 方式 A:从 Release 解压 -## 8. 升级流程 +把 `subtracker-web-dist.zip` 解压到: -当 API 镜像发布完成后,服务器只需: +```text +./web-dist +``` + +#### 方式 B:本地构建 + +```bash +npm install +npm run build -w apps/web +``` + +然后把: + +```text +apps/web/dist +``` + +拷贝到: + +```text +./web-dist +``` + +### 启动完整部署 + +```bash +docker compose -f docker-compose.full.yml up -d +``` + +默认访问: + +- Web:`http://localhost:8080` +- API:由前端 Nginx 反代到内部 `api:3001` + +### 完整部署持久化目录 + +- `./data` +- `./data/logos` +- `./web-dist` + +--- + +## 4. Release 与镜像发布 + +仓库的 `Build and Release` workflow 会在发布 tag 时自动生成: + +- API 镜像:`ghcr.io/smile-qwq/subtracker-api` +- 前端静态包:`subtracker-web-dist.zip` + +因此服务端升级通常只需要: + +### API-only 模式 ```bash docker compose pull docker compose up -d ``` -前端静态资源单独更新到 Nginx 目录即可。 +### Full compose 模式 + +1. 更新 `web-dist` +2. 更新 API 镜像 + +```bash +docker compose -f docker-compose.full.yml pull +docker compose -f docker-compose.full.yml up -d +``` + +--- + +## 5. 开发环境变量 + +本地开发前请先复制: + +```bash +cp apps/api/.env.example apps/api/.env +``` + +PowerShell: + +```powershell +Copy-Item apps/api/.env.example apps/api/.env +``` diff --git a/README.md b/README.md index 9610df7..35b20b3 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,67 @@ # SubTracker -SubTracker 是一个简洁但功能完整的个人订阅管理系统,用来统一管理多币种订阅、续费提醒、预算统计、Logo 与 AI 辅助录入。 +一个现代化的自托管订阅管理工具,用来统一管理多币种订阅、预算、续订提醒、Logo 资源和 Wallos 数据迁移。 ## 功能亮点 -- 订阅管理:新增、编辑、续费、暂停、停用、删除、拖拽排序 -- 多币种支持:自动汇率换算、基准货币切换、汇率转换器 -- 预算统计:月预算、年预算、分类预算、仪表盘总览 -- 通知能力:Webhook、SMTP 邮件、PushPlus -- Logo 能力:上传、本地复用、网络搜索并保存到本地 -- AI 识别:支持文本或图片识别后自动填充订阅信息 +- **订阅管理**:新增、编辑、续订、暂停、停用、记录查看、拖拽排序 +- **标签系统**:多标签归类、筛选、预算统计 +- **多币种支持**:基准货币换算、汇率快照、货币转换器 +- **预算与统计**:月/年预算、标签占比、未来 12 个月支付趋势 +- **通知能力**:Webhook、SMTP 邮件、PushPlus +- **Logo 能力**:上传、本地复用、网络搜索、Wallos ZIP 导入匹配 +- **AI 识别**:支持文本/图片识别后自动填充订阅信息 +- **Wallos 导入**:兼容 JSON、SQLite、ZIP ## 技术栈 -- 前端:Vue 3、Vite、TypeScript、Naive UI、Pinia、Vue Router、TanStack Query、ECharts -- 后端:Fastify、Prisma、SQLite、Zod、node-cron +- **前端**:Vue 3、Vite、TypeScript、Naive UI、Pinia、TanStack Query、ECharts +- **后端**:Fastify、Prisma、SQLite、Zod、node-cron ## 本地开发 +### 1. 安装依赖 + ```bash npm install +``` + +### 2. 复制开发环境变量 + +```bash +cp apps/api/.env.example apps/api/.env +``` + +如果你在 Windows PowerShell 下,也可以用: + +```powershell +Copy-Item apps/api/.env.example apps/api/.env +``` + +### 3. 初始化数据库 + +```bash npm run prisma:generate npm run prisma:push npm run prisma:seed +``` + +### 4. 启动开发环境 + +```bash npm run dev ``` 默认地址: - Web:`http://127.0.0.1:5173` -- API:`http://localhost:3001` +- API:`http://127.0.0.1:3001` -默认账号: +默认登录: - 用户名:`admin` - 密码:`admin` -## 部署 - -- 详细部署文档见:[DEPLOYMENT.md](./DEPLOYMENT.md) -- 推荐方式: - - 前端静态文件由外部 Nginx 托管 - - API 使用 Docker 镜像部署 - - Nginx 反代 `/api/` 和 `/static/logos/` 到 API - ## 常用命令 ```bash @@ -52,3 +70,23 @@ npm run build npm run lint npm test ``` + +## 部署 + +详细部署说明见: + +- [DEPLOYMENT.md](./DEPLOYMENT.md) + +当前提供两种方式: + +1. **推荐**:外部 Nginx 托管前端静态文件,Docker 仅部署 API +2. **完整部署**:使用 `docker-compose.full.yml` 同时启动前端 Nginx + API + +## Release 产物 + +仓库的 `Build and Release` workflow 会在 tag 发布时自动产出: + +- `subtracker-web-dist.zip`:前端静态文件 +- `ghcr.io/smile-qwq/subtracker-api`:API Docker 镜像 + +适合直接用于服务器部署。 diff --git a/docker-compose.full.yml b/docker-compose.full.yml new file mode 100644 index 0000000..3a0030a --- /dev/null +++ b/docker-compose.full.yml @@ -0,0 +1,26 @@ +services: + api: + image: ${SUBTRACKER_API_IMAGE:-ghcr.io/smile-qwq/subtracker-api:latest} + container_name: subtracker-api + restart: unless-stopped + environment: + PORT: ${PORT:-3001} + HOST: ${HOST:-0.0.0.0} + DATABASE_URL: ${DATABASE_URL:-file:/app/data/subtracker.db} + WEB_ORIGIN: ${WEB_ORIGIN:-http://localhost:8080} + LOG_LEVEL: ${LOG_LEVEL:-warn} + volumes: + - ./data:/app/data + - ./data/logos:/app/apps/api/storage/logos + + web: + image: nginx:1.27-alpine + container_name: subtracker-web + restart: unless-stopped + depends_on: + - api + volumes: + - ./web-dist:/usr/share/nginx/html:ro + - ./docker/nginx.full.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - "${WEB_PORT:-8080}:80" diff --git a/docker/nginx.full.conf b/docker/nginx.full.conf new file mode 100644 index 0000000..addc59d --- /dev/null +++ b/docker/nginx.full.conf @@ -0,0 +1,29 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api:3001/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /static/logos/ { + proxy_pass http://api:3001/static/logos/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +}