Files
diary-news/DEPLOY.md
Mavis 60b062daf2 feat: initial MVP - FastAPI backend + Vue3 frontend + docker-compose
- backend: FastAPI + SQLAlchemy 2.0(async) + asyncpg + Alembic
- 7 API routes: auth/me/articles/sources/bookmarks/subscriptions/admin
- models: User/Source/Article/Bookmark/Subscription/ApiToken
- services: RSS fetcher (feedparser) + Tencent TMT translator with quota + cache + local NLLB fallback
- workers: APScheduler + asyncio pipeline (fetch -> dedupe -> insert -> translate)
- seed scripts: create_user, seed_sources (5 RSS: Reuters/BBC/Al Jazeera/NHK/DW)
- frontend: Vue 3 + Vite + Naive UI + Pinia + vue-router
- pages: Login, Feed (24h), ArticleDetail, Sources, Bookmarks, AdminSources
- deploy: docker-compose (postgres/redis/api/worker/frontend/caddy)
- docs: README, DEPLOY, architecture, acceptance
2026-06-07 21:51:01 +08:00

166 lines
4.1 KiB
Markdown

# 部署指南 · DEPLOY
目标:从一台全新的 Ubuntu 24 香港 VPS,到能访问的私人新闻系统。
## 0. 准备
- 香港 VPS(最低 2C2G 30G)
- 域名(可选,没域名走 IP + 自签证书)
- 腾讯云账号 + 已开通「文本翻译 TMT」
## 1. 服务器初始化
```bash
# SSH 登录
ssh root@YOUR_SERVER_IP
# 创建非 root 用户
adduser news
usermod -aG sudo news
# 基础包
apt update && apt -y upgrade
apt -y install curl git ufw fail2ban
# 防火墙
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
# Docker
curl -fsSL https://get.docker.com | sh
usermod -aG docker news
# 退出 root,切换到 news
exit
ssh news@YOUR_SERVER_IP
```
## 2. 拉代码
```bash
sudo mkdir -p /srv/news
sudo chown news:news /srv/news
cd /srv/news
git clone <你的仓库地址> .
# 或者 scp 上传
```
## 3. 配置环境变量
```bash
cp .env.example .env
nano .env
```
**必填字段:**
| 字段 | 怎么填 |
| --- | --- |
| `POSTGRES_PASSWORD` | `openssl rand -hex 24` |
| `REDIS_PASSWORD` | `openssl rand -hex 24` |
| `JWT_SECRET` | `openssl rand -hex 64` |
| `TENCENTCLOUD_SECRET_ID` | 腾讯云控制台 → 访问管理 → API 密钥 |
| `TENCENTCLOUD_SECRET_KEY` | 同上 |
| `TENCENTCLOUD_REGION` | `ap-hongkong` |
| `DOMAIN` | 域名(可选,留空走 IP) |
## 4. 启动
```bash
docker compose up -d --build
# 等 30 秒
docker compose ps
# 全部 healthy 即可
```
## 5. 初始化
```bash
# 5.1 数据库迁移
docker compose exec api alembic upgrade head
# 5.2 创建 owner 账号
docker compose exec api python -m app.scripts.create_user \
--username owner --password YOUR_STRONG_PASS
# 5.3 导入 5 个种子源
docker compose exec api python -m app.scripts.seed_sources
# 5.4 手动触发一次抓取(看效果)
docker compose exec worker python -c "import asyncio; from app.workers.pipeline import run_once; asyncio.run(run_once())"
# 等 1~3 分钟,刷一下
docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "SELECT count(*) FROM articles;"
```
## 6. 验证清单
- [ ] 浏览器打开 `http://YOUR_IP/` 看到登录页
- [ ] 用 owner 登录成功
- [ ] Feed 列表显示 24h 内新闻(标题中英对照)
- [ ] 详情页原文+译文并列
- [ ] `/admin/sources` 能看到 5 个源
- [ ] 翻译配额仪表盘显示已用字符
- [ ] 等到凌晨,worker 自动跑批,文章数持续增长
## 7. 域名 + HTTPS(可选)
1. 域名 A 记录指向服务器 IP
2. 编辑 `.env``DOMAIN=news.example.com` + `ACME_EMAIL=you@example.com`
3. 编辑 `Caddyfile`,把 `http://{$DOMAIN}` 改成 `{$DOMAIN}`(取消注释下面块)
4. `docker compose restart caddy`
5. Caddy 自动申请 Let's Encrypt 证书
## 8. 备份
```bash
# 每天凌晨 4 点备份到本地
cat > /srv/news/scripts/backup.sh <<'EOF'
#!/bin/bash
set -e
BACKUP_DIR=/srv/news/backups/$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"
docker compose exec -T postgres pg_dump -U $POSTGRES_USER $POSTGRES_DB | gzip > "$BACKUP_DIR/db.sql.gz"
# 保留 7 天
find /srv/news/backups -type d -mtime +7 -exec rm -rf {} +
EOF
chmod +x /srv/news/scripts/backup.sh
# 加 cron
crontab -e
# 添加一行:
# 0 4 * * * /srv/news/scripts/backup.sh
```
**强烈建议**:把 `/srv/news/backups/` 同步到腾讯云 COS / 阿里云 OSS,做异地灾备。
## 9. 升级
```bash
cd /srv/news
git pull
docker compose pull
docker compose up -d --build
docker compose exec api alembic upgrade head
```
## 10. 常见问题
**Q: 某个源一直 fail?**
A: 看 `docker compose logs worker | grep <source_slug>`,90% 是 RSS URL 失效或者被反爬。在 `sources` 表里 `enabled=false` 暂停。
**Q: 翻译字符超 500 万?**
A: 配 `.env``TENCENT_TMT_QUOTA_BUFFER=0.05`,系统在 475 万字符后自动切本地 NLLB(需启用 `LOCAL_TRANSLATE_ENABLED=true`)。
未启用本地翻译时,系统会在原文末尾标 `[本条未翻译]`
**Q: 30G 硬盘快满了?**
A: 执行冷热分层 cron:
```sql
DELETE FROM articles WHERE published_at < now() - interval '90 day' AND duplicate_of IS NULL;
```
**Q: 怎么加新源?**
A: 网页登录 owner → `/admin/sources` → 新增。填 name / kind=rss / url=RSS 链接 / 优先级 / 抓取频率。保存后 worker 下个轮询周期自动拉。