Files
gitlab-instance-0a899031_cn…/README.md
2026-06-07 23:57:52 -05:00

713 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 游牧中国 NomadCNA
NomadCNA 是面向数字游民的生活操作系统,用一个站点承载城市决策、会员加入、线上/线下活动、视频会议、Web 群聊、内容资源、任务赏金、支付和媒体存储等能力。
生产站点:
- 主站https://nomadro.cn
- APIhttps://api.nomadro.cn
- PocketBasehttps://pocketbase.nomadro.cn
- MinIOhttps://minio.nomadro.cn
- MiroTalk P2P 视频房https://mirotalk.nomadro.cn
- Web 群聊https://lounge.nomadro.cn
- IRC 服务名https://ergo.nomadro.cn
## 业务介绍
NomadCNA 的目标用户是正在中国或亚洲范围内长期旅居、远程工作、寻找社群和机会的数字游民。业务上分为以下几条主线:
| 模块 | 说明 |
| --- | --- |
| 城市探索 | 展示中国数字游民城市数据,包括生活成本、网络、温度、社群密度、安全、文化等指标。 |
| 下一站决策 | 根据预算、网络、气候、标签偏好生成城市推荐。 |
| 线上/线下活动 | 支持公开、登录成员、VIP、主持人等权限层级线上/混合活动进入 **统一活动间**MiroTalk 视频 + The Lounge 群聊分屏),账户昵称自动带入视频房。 |
| Web 群聊 | The Lounge + Ergo IRC每活动可配置 `loungeChannel`(如 `#nomadcna-20260608`),默认频道 `#nomadcna,#online-events`。 |
| 访谈视频 | `/videos` 博主访谈列表,`/videos/[slug]` 单条详情;首页「访谈视频」链到列表页。 |
| 社区匹配 | `/dating` 滑动匹配:意图 Tab、城市筛选、互赞、每日额度、高级筛选、撤销滑动。 |
| 会员加入与支付 | 支持加入表单、支付订单、支付状态轮询、VIP/会员写入。 |
| Digital 子站 | `/digital` 是内容专题站,包含数字游民指南、课程、电子书、资源、工作机会、个人资料等。 |
| 媒体与内容 | 支持图片/视频上传、远程媒体代理缓存、内容投稿、博客、电子书、视频等内容页面。 |
| 社区与增长 | 包含讨论、交友匹配、任务赏金、反馈、通知、站内消息、服务线索。 |
## 技术栈
| 层级 | 技术 |
| --- | --- |
| Web 前端 | Next.js 16 App Router、React 19、TypeScript、Tailwind CSS 4 |
| API | Next.js Route Handlers、FastAPI、PocketBase SDK/HTTP |
| 数据 | PocketBase、静态数据、MinIO 对象存储 |
| 支付 | ZPay / XorPay 适配层 |
| 视频会议 | MiroTalk P2PNginx HTTPS 反代 |
| Web 群聊 | The Lounge 4.5.0 + Ergo IRC Server 2.18.0 |
| 部署 | systemd、Nginx、Certbot、Docker |
| 包管理 | pnpm 10.25.0 |
生产服务器当前 Node.js 版本为 `v22.13.1`
## 源码解析
### 顶层结构
```text
.
├── app/ # Next.js App Router 主目录
│ ├── [locale]/ # 中文/英文路由,默认 zh
│ ├── api/ # Next.js Route Handlers
│ ├── components/ # 主站通用组件
│ ├── context/ # Locale、Theme 等上下文
│ ├── data/ # 城市、地图、mock 数据
│ ├── digital/ # Digital 子站组件、配置、数据和工具
│ └── lib/ # API、支付、媒体、活动、PocketBase 等工具
├── backend/ # FastAPI 后端
├── config/ # 主站和 Digital 子站配置
│ └── remote-services.ts # 线上服务地址、Windows 远程开发模式
├── scripts/ # dev-tunnel.py、dev-windows.ps1
├── i18n/ # 路由和国际化导航封装
├── messages/ # zh/en 文案
├── public/ # 静态资源
├── cnomadcna.sh # 生产 Next.js 启动脚本
├── next.config.ts # Next.js 配置与 API rewrites
├── package.json # 前端依赖与脚本
└── tsconfig.json # TypeScript 配置
```
### Next.js 页面结构
主站所有面向用户的页面位于 `app/[locale]`,路由强制带语言前缀:
- `/zh``/en`:首页与城市探索
- `/zh/dashboard`:下一站决策仪表盘
- `/zh/meetups`:线上/线下活动列表与详情弹窗
- `/zh/meetups/host`:活动发布页
- `/zh/meetups/[id]/live`**统一活动间**(内嵌 MiroTalk + The Lounge需权限
- `/zh/videos`:博主访谈视频列表
- `/zh/videos/[slug]`:单条访谈详情
- `/zh/dating`:社区滑动匹配(意图、城市、互赞、额度)
- `/zh/join`:成员加入(城市、寻找意图、资料)
- `/zh/discuss`:讨论
- `/zh/gigs`:任务赏金
- `/zh/tools`:工具箱
- `/zh/report``/zh/report/data`:统计与年报
- `/zh/digital`Digital 子站入口
通用布局与组件:
- `app/layout.tsx`:根布局
- `app/[locale]/layout.tsx`:语言布局
- `app/components/Navbar.tsx``Footer.tsx``Sidebar.tsx`:主站导航与外壳
- `app/context/LocaleContext.tsx`:运行时文案读取
- `app/context/ThemeContext.tsx`:亮色/暗色主题
### Digital 子站
Digital 子站代码集中在 `app/digital``app/[locale]/digital`
- `app/[locale]/digital/page.tsx`:专题首页
- `app/[locale]/digital/course`:课程页
- `app/[locale]/digital/course/[moduleIndex]/[lessonIndex]`:课程章节
- `app/[locale]/digital/ebook`:电子书
- `app/[locale]/digital/jobs`:远程工作
- `app/[locale]/digital/join`:加入/申请
- `app/[locale]/digital/profile`:用户资料
配置在 `config/digital`
- `site.config.ts`:专题主题、功能开关、课程/电子书/加入等能力
- `services.config.ts`PocketBase、支付、MinIO 配置读取
- `domain.config.ts`:域名和服务地址推导
- `course.ts``lessons.ts`:课程数据
### API 分层
本项目有两层 API
1. Next.js 内置 API`app/api/*`
2. FastAPI 后端:`backend/main.py`
`next.config.ts` 将大多数 `/api/*` 转发到 FastAPI
```ts
source: "/api/:path((?!(?:digital|media)(?:$|/.*)).*)"
destination: "http://127.0.0.1:8000/api/:path*"
```
因此:
- `/api/digital/*` 由 Next.js Route Handlers 处理
- `/api/media/*` 由 Next.js Route Handlers 处理
- 其他 `/api/*` 默认转发到 FastAPI
### Next.js API
| 路径 | 作用 |
| --- | --- |
| `app/api/digital/auth/*` | Digital 子站登录、退出、当前用户、注册、跨站同步 |
| `app/api/digital/join` | Digital 加入/申请表单写入 PocketBase |
| `app/api/digital/pay/*` | Digital 支付创建、确认、状态查询 |
| `app/api/digital/upload-media` | 媒体上传到 MinIO |
| `app/api/digital/vip/check` | VIP 状态检查 |
| `app/api/media/proxy` | 远程媒体抓取、校验、缓存到 MinIO 后跳转公开 URL |
### FastAPI 后端
FastAPI 入口为 `backend/main.py`,配置读取在 `backend/settings.py`。核心能力:
| API | 作用 |
| --- | --- |
| `/api/health` | 健康检查 |
| `/api/auth/email``/api/auth/google``/api/auth/me` | 主站认证 |
| `/api/join``/api/join/status` | 加入表单与状态 |
| `/api/upload-media` | 后端媒体上传 |
| `/api/pay``/api/pay/status` | 支付创建与查询 |
| `/cnomadcna/*_notify` | 支付网关回调 |
| `/api/cities``/api/cities/{slug}` | 城市数据 |
| `/api/content/*` | 首页内容、内容投稿、内容详情 |
| `/api/videos` | 访谈视频列表(`type=video` 的 content_items |
| `/api/notifications/*` | 通知、回执、偏好 |
| `/api/recommendations/next-stop` | 下一站推荐 |
| `/api/meetups` | 活动列表与创建 |
| `/api/meetups/{id}` | 单个活动详情 |
| `/api/meetups/{id}/session` | 活动间会话:权限校验、视频/群聊 URL、社区昵称 |
| `/api/matches/*` | 匹配候选、滑动、互赞、额度、撤销、我的资料 |
| `/api/discussions` | 讨论列表与创建 |
| `/api/gigs` | 任务赏金列表、创建、申请 |
| `/api/profiles` | 资料列表 |
| `/api/feedback` | 用户反馈(支持 `?type=report` 预填) |
| `/api/stats/*` | 成员地图、概览、城市排行 |
| `/api/services``/api/services/leads` | 增值服务与线索 |
| `/api/ai/assistant` | AI 助手占位接口 |
后端辅助模块:
- `backend/pb_client.py`PocketBase HTTP 客户端
- `backend/payment.py`ZPay / XorPay 支付适配
- `backend/minio_storage.py`MinIO 上传
- `backend/meetup_live.py`:活动间 URL 生成、权限判断MiroTalk / Lounge
- `backend/avatar_assets.py`与前端同步的头像池seed 用)
- `backend/init_db.py``seed_data.py``enhanced_seed.py`:初始化与种子数据
- `backend/settings.py`:环境变量读取(含 `MIROTALK_URL``LOUNGE_URL`
### 活动、视频与群聊(深度融合)
相关文件:
| 文件 | 作用 |
| --- | --- |
| `app/[locale]/meetups/page.tsx` | 活动列表、详情弹窗、「进入活动间」入口 |
| `app/[locale]/meetups/[id]/live/page.tsx` | 统一活动间页面 |
| `app/components/meetups/EventLiveRoom.tsx` | 分屏 UI视频 iframe + 群聊 iframe |
| `app/[locale]/meetups/host/page.tsx` | 发起活动 |
| `app/lib/meetups.ts` | MiroTalk/Lounge URL、房间 slug、`loungeChannel`、权限工具 |
| `backend/meetup_live.py` | 后端同名逻辑,供 `/api/meetups/{id}/session` 使用 |
`app/lib/meetups.ts` 负责:
- MiroTalk 默认地址:`NEXT_PUBLIC_MIROTALK_URL`(生产 `https://mirotalk.nomadro.cn`
- MiroTalk 房间名规范化、`buildMiroTalkJoinUrl(meetup, displayName)`
- Lounge 默认地址:`NEXT_PUBLIC_LOUNGE_URL`(生产 `https://lounge.nomadro.cn`
- 活动专属 IRC 频道:`loungeChannel` 字段,或从 `mirotalkRoom` 推导
- 群聊 deep link`buildMeetupChatUrl(meetup)``lounge.nomadro.cn/#/chan-#频道`
- 活动间路径:`buildMeetupLivePath(id)``/meetups/{id}/live`
**账户打通**`GET /api/meetups/{id}/session` 读取 PocketBase 登录态,校验 `public/members/vip/hosts`,将社区昵称写入 MiroTalk `name=` 参数。
PocketBase `meetups` 集合字段(线上活动相关):
- `mode``offline` / `online` / `hybrid`
- `accessLevel``public` / `members` / `vip` / `hosts`
- `meetingProvider`:如 `mirotalk`
- `mirotalkRoom`:视频房间 slug
- `loungeChannel`IRC 频道,如 `#nomadcna-20260608`
- `features`JSON`group_video``screen_share`
### 访谈视频
- 列表:`app/[locale]/videos/page.tsx`,数据来自 `GET /api/videos`
- 详情:`app/[locale]/videos/[slug]/page.tsx`
- 首页右侧「访谈视频」卡片链到 `/videos`(非单条 slug
### 社区匹配Dating
- 页面:`app/[locale]/dating/page.tsx`
- 组件:`app/[locale]/dating/components/``IntentTabs``CityFilter``SwipeStack``LikesPanel``MatchModal``AdvancedFilters``SwipeQuota` 等)
- 工具:`app/[locale]/dating/utils.ts``constants.ts``types.ts`
- 头像池:`app/data/avatars.ts`Unsplash 人像,与 `backend/avatar_assets.py` 同步)
匹配 API均需登录部分需 VIP
- `GET /api/matches/candidates` — 候选列表(意图、城市、性别筛选)
- `POST /api/matches/swipes` — 左滑/右滑/超级赞
- `POST /api/matches/swipes/undo` — 撤销上一次滑动
- `GET /api/matches/likes``/mutual``/quota``/profile/me`
### 媒体代理
- `app/lib/media.ts`:封面 URL 规范化Unsplash 直连、MinIO 相对路径补全)
- `app/api/media/proxy/route.ts`远程媒体抓取MinIO 失败时直返图片
### 配置与国际化
| 文件 | 作用 |
| --- | --- |
| `config/site.config.ts` | 主站 SEO 与站点基础信息 |
| `config/nav.config.ts` | 顶部导航 |
| `config/footer.config.ts` | 页脚 |
| `config/services.config.ts` | 主站支付配置 |
| `config/runtime-env.ts` | 运行环境判断 |
| `config/remote-services.ts` | 线上域名、MinIO/PocketBase 解析、远程开发模式 |
| `config/digital/*` | Digital 子站主题、服务、课程、域名配置 |
| `messages/zh.json``messages/en.json` | 中英文文案 |
| `i18n/routing.ts` | 语言列表与默认语言 |
| `i18n/navigation.ts` | 语言路由 Link / Router 封装 |
## 本地开发
### 环境要求
- Node.js 20+,生产当前使用 Node.js 22.13.1
- pnpm 10+
- Python 3.12+
- PocketBase
- 可选MinIO、ZPay/XorPay 测试配置
### 安装依赖
```bash
pnpm install
```
### Windows 本地连线上服务
典型配置见 `.env.example` / `.env.local`
1. PocketBase 用公网 `https://pocketbase.nomadro.cn`
2. MinIO S3 API 用 SSH 隧道映射到本机 `9100`
```powershell
python scripts/dev-tunnel.py
```
3. 本地跑 Next.js + FastAPI
```powershell
pnpm dev
# 另开终端
backend\.venv\Scripts\uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
```
或一键脚本:
```powershell
powershell -ExecutionPolicy Bypass -File scripts/dev-windows.ps1
```
服务地址推导见 `config/remote-services.ts`
### 初始化数据库
```bash
python -m backend.init_db
```
会创建/补全 PocketBase 集合字段,并 upsert 城市、活动、内容、资料等种子数据。
### 前端开发
```bash
pnpm dev
```
默认启动在:
```text
http://localhost:3001
```
### FastAPI 后端开发
```bash
python3 -m venv backend/.venv
source backend/.venv/bin/activate
pip install -r backend/requirements.txt
uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
```
### 构建与启动
```bash
pnpm build
pnpm start
```
### 常用检查
```bash
pnpm lint
pnpm exec tsc --noEmit --pretty false
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
```
## 环境变量
不要把真实密钥提交到 Git。`.env.example` 只放示例值,生产值放 `.env.local``backend/.env` 或 systemd Environment。
### 前端公开变量
| 变量 | 说明 |
| --- | --- |
| `APP_ENV` | `development` / `production` |
| `NEXT_PUBLIC_APP_ENV` | 浏览器可见运行环境 |
| `NEXT_PUBLIC_SITE_URL` | 站点根地址 |
| `NEXT_PUBLIC_SITE_ID` | 站点 ID用于会员/VIP 隔离 |
| `NEXT_PUBLIC_API_BASE_URL` | API 基础地址,生产通常为 `/api` |
| `FASTAPI_PROXY_TARGET` | Next.js rewrite 转发 FastAPI 的目标(本地 `http://127.0.0.1:8000` |
| `NEXT_PUBLIC_POCKETBASE_URL` | 浏览器可访问 PocketBase 地址 |
| `NEXT_PUBLIC_MIROTALK_URL` | MiroTalk P2P 服务地址 |
| `NEXT_PUBLIC_LOUNGE_URL` | The Lounge Web 群聊地址 |
| `NEXT_PUBLIC_MINIO_PUBLIC_URL` | MinIO 文件公开地址 |
### 后端和服务端变量
| 变量 | 说明 |
| --- | --- |
| `POCKETBASE_URL` | 服务端访问 PocketBase 的内网地址 |
| `POCKETBASE_EMAIL` | PocketBase 管理员邮箱 |
| `POCKETBASE_PASSWORD` | PocketBase 管理员密码 |
| `PAYMENT_API_URL` | 支付 API 地址 |
| `PAYMENT_PROVIDER` | `zpay``xorpay` |
| `PAYMENT_DEFAULT_AMOUNT` | 默认支付金额 |
| `PAYMENT_JOIN_AMOUNT` | 加入场景金额 |
| `PAYMENT_JOIN_TYPE` | 加入场景订单类型 |
| `ZPAY_PID``ZPAY_KEY` | ZPay 商户配置 |
| `XORPAY_AID``XORPAY_SECRET` | XorPay 商户配置 |
| `MINIO_ENDPOINT` | MinIO 内网端点 |
| `MINIO_PORT` | MinIO 端口 |
| `MINIO_USE_SSL` | 是否对 MinIO 内网连接启用 SSL |
| `MINIO_BUCKET` | 存储桶 |
| `MINIO_ACCESS_KEY` | MinIO Access Key |
| `MINIO_SECRET_KEY` | MinIO Secret Key |
| `MINIO_REGION` | MinIO 区域 |
| `MINIO_UPLOAD_PREFIX` | 上传路径前缀 |
| `FRONTEND_ORIGINS` | FastAPI CORS 白名单 |
| `UPLOAD_DIR` | 后端本地上传目录 |
| `MIROTALK_URL` / `LOUNGE_URL` | 后端生成活动间 URL默认同公网域名 |
| `DEV_SSH_HOST` / `DEV_SSH_PASSWORD` | Windows SSH 隧道(仅本地,勿提交) |
## 生产服务器支持
生产代码目录:
```text
/home/ubuntu/gitlab-instance-0a899031_cnomadcna
```
### 域名与服务映射
| 域名 | 上游 | 说明 |
| --- | --- | --- |
| `nomadro.cn` / `www.nomadro.cn` | `127.0.0.1:4002` | Next.js 主站,`cnomadcna.service` |
| `api.nomadro.cn` | `127.0.0.1:8000` | FastAPI |
| `pocketbase.nomadro.cn` | `127.0.0.1:8090` | PocketBase |
| `minio.nomadro.cn` | `127.0.0.1:9100` / `9101` | MinIO API / Console |
| `mirotalk.nomadro.cn` | `127.0.0.1:9300` | MiroTalk P2P |
| `lounge.nomadro.cn` | `127.0.0.1:9000` | The Lounge Web IRC |
| `ergo.nomadro.cn` | HTTPS redirect | 跳转到 `lounge.nomadro.cn`IRC 后端只内网监听 |
| `ntfy.nomadro.cn` | `127.0.0.1:9180` | ntfy 通知服务 |
### systemd 服务
| 服务 | 作用 | 当前策略 |
| --- | --- | --- |
| `cnomadcna.service` | Next.js 生产服务 | enabled监听 `0.0.0.0:4002` |
| `mirotalk-p2p.service` | MiroTalk P2P | enabled监听 `127.0.0.1:9300` |
| `ergo.service` | Ergo IRC Server | enabled监听 `127.0.0.1:6667``[::1]:6667` |
| `thelounge.service` | The Lounge Web IRC | enabled监听 `127.0.0.1:9000` |
常用命令:
```bash
systemctl status cnomadcna.service
systemctl status mirotalk-p2p.service
systemctl status ergo.service
systemctl status thelounge.service
journalctl -u cnomadcna.service -n 100 --no-pager
journalctl -u mirotalk-p2p.service -n 100 --no-pager
journalctl -u ergo.service -n 100 --no-pager
journalctl -u thelounge.service -n 100 --no-pager
```
### Docker 服务
当前生产容器:
| 容器 | 镜像 | 端口 |
| --- | --- | --- |
| `nomadro-minio` | `minio/minio:latest` | `127.0.0.1:9100->9000``127.0.0.1:9101->9001` |
| `nomadro-ntfy` | `binwiederhier/ntfy:latest` | `127.0.0.1:9180->80` |
检查:
```bash
docker ps
docker logs nomadro-minio --tail 100
docker logs nomadro-ntfy --tail 100
```
### Nginx
Nginx vhost 目录:
```text
/www/server/panel/vhost/nginx
```
关键配置:
- `www.nomadro.cn.conf`:主站
- `api.nomadro.cn.conf`FastAPI
- `mirotalk.nomadro.cn.conf`MiroTalk
- `lounge.nomadro.cn.conf`The Lounge + Ergo redirect
- `minio.nomadro.cn.conf`MinIO
- `pocketbase.nomadro.cn.conf`PocketBase
常用命令:
```bash
nginx -t
nginx -s reload
```
### HTTPS 证书
证书由 Certbot 管理。`lounge.nomadro.cn` 证书包含两个域名:
```text
lounge.nomadro.cn
ergo.nomadro.cn
```
当前证书路径:
```text
/etc/letsencrypt/live/lounge.nomadro.cn/fullchain.pem
/etc/letsencrypt/live/lounge.nomadro.cn/privkey.pem
```
检查:
```bash
certbot certificates
```
### MiroTalk P2P
安装目录:
```text
/opt/nomadro-services/mirotalk
```
运行方式:
- systemd`mirotalk-p2p.service`
- 内网端口:`127.0.0.1:9300`
- 公网入口:`https://mirotalk.nomadro.cn`
- 活动页自动生成链接:`https://mirotalk.nomadro.cn/join?room=...&name=...`
健康检查:
```bash
curl -I https://mirotalk.nomadro.cn/join?room=nomadcna-healthcheck
```
### Ergo IRC Server
安装目录:
```text
/opt/ergo
```
配置:
```text
/etc/ergo/ircd.yaml
/etc/ergo/ergo.motd
/var/lib/ergo/ircd.db
```
当前策略:
- 网络名:`NomadCNA`
- 服务名:`ergo.nomadro.cn`
- 只监听本机 `127.0.0.1:6667``[::1]:6667`
- 不开放公网 IRC TLS `6697`
- `ergo.nomadro.cn` 的 HTTPS 请求跳转到 The Lounge
本机 IRC 握手检查:
```bash
{ printf 'NICK health\r\nUSER health 0 * :NomadCNA Health\r\nJOIN #nomadcna\r\nQUIT :healthcheck\r\n'; sleep 1; } \
| timeout 5s nc 127.0.0.1 6667
```
### The Lounge Web 群聊
安装目录:
```text
/opt/nomadro-services/thelounge
```
配置:
```text
/var/lib/thelounge/config.js
```
当前策略:
- public mode
- 只监听 `127.0.0.1:9000`
- Nginx 通过 `https://lounge.nomadro.cn` 反代
- 默认网络锁定到本机 Ergo`127.0.0.1:6667`
- 默认加入频道:`#nomadcna,#online-events`(可按活动追加,如 `#nomadcna-20260608`
- 文件上传关闭
- The Lounge 自身消息持久化关闭Ergo 保留内存历史
健康检查:
```bash
curl -I https://lounge.nomadro.cn/
curl 'https://lounge.nomadro.cn/socket.io/?EIO=4&transport=polling&t=health'
```
## 上线流程
### 前端上线
```bash
cd /home/ubuntu/gitlab-instance-0a899031_cnomadcna
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
systemctl restart cnomadcna.service
systemctl status cnomadcna.service --no-pager
```
### 后端上线
FastAPI 当前运行在 `127.0.0.1:8000`。更新后需重启对应 uvicorn 进程或其 systemd 服务。如果后续为 FastAPI 增加 systemd 单元,建议命名为:
```text
cnomadcna-api.service
```
### Nginx 配置变更
```bash
nginx -t
nginx -s reload
```
### 服务健康检查
```bash
curl -I https://nomadro.cn/zh/meetups
curl -I https://api.nomadro.cn/api/health
curl -I https://mirotalk.nomadro.cn/join?room=nomadcna-healthcheck
curl -I https://lounge.nomadro.cn/
curl -I https://ergo.nomadro.cn/
```
## 数据与备份
建议纳入备份的内容:
| 路径/服务 | 说明 |
| --- | --- |
| `.env.local``backend/.env` | 生产环境变量和密钥 |
| PocketBase 数据目录 | 用户、会员、订单、内容、活动等业务数据 |
| MinIO 数据卷 | 上传媒体、远程媒体缓存 |
| `/var/lib/ergo` | Ergo IRC 数据库 |
| `/var/lib/thelounge` | The Lounge 配置、VAPID key、包配置 |
| `/opt/nomadro-services/mirotalk` | MiroTalk 源码部署目录 |
| `/www/server/panel/vhost/nginx/*.conf` | Nginx vhost 配置 |
| `/etc/systemd/system/*.service` | systemd 单元 |
## 安全注意事项
- README 不应包含真实密钥、管理员密码、支付密钥或 MinIO Secret。
- `.env.local``backend/.env` 不应提交到 Git。
- The Lounge 当前为 public mode适合活动公开群聊如需长期身份和历史可切换 private mode 并创建用户账号。
- Ergo 原生 IRC 公网端口当前不开放,这是有意设计;需要原生 IRC 客户端时再开放 `6697` 并配置 TLS。
- 内部服务优先绑定 `127.0.0.1`,统一通过 Nginx HTTPS 暴露。
- 修改 Nginx 前必须先 `nginx -t`
- 更新证书后确认 SAN 包含对应域名。
## 开发约定
- 新页面优先放入 `app/[locale]`,并补充 `messages/zh.json``messages/en.json`
- 需要语言感知链接时使用 `i18n/navigation.ts` 导出的 `Link`
- 调用后端 API 使用 `app/lib/api-client.ts`
- 主站通用配置放 `config/*`Digital 子站配置放 `config/digital/*`
- 媒体上传和远程媒体代理优先走 MinIO。
- 支付相关逻辑优先复用 `app/lib/payment``backend/payment.py``config/*/services.config.ts`
- 线上活动相关逻辑优先复用 `app/lib/meetups.ts`
## 常见问题
### 为什么 `ergo.nomadro.cn` 不直接打开 IRC
`ergo.nomadro.cn` 是 IRC 服务名,但原生 IRC 端口没有对公网开放。用户从浏览器进入 `lounge.nomadro.cn`The Lounge 在服务器本机连接 Ergo。这样能减少公网攻击面同时满足 Web 群聊需求。
### 为什么 `/api/digital/*` 没有转发到 FastAPI
`next.config.ts` 明确排除了 `digital``media` 两类 API它们由 Next.js Route Handlers 直接处理。其他 `/api/*` 才会转发到 FastAPI。
### 活动视频房如何生成?
`app/lib/meetups.ts` 会根据活动 `mirotalkRoom`、或城市/日期/时间/ID 生成 room slug。登录用户通过 `/api/meetups/{id}/session` 获取带 `name=` 的入会 URL。
### 如何进入统一活动间?
活动详情弹窗点 **「进入活动间」** → `/meetups/{id}/live`。页面内分屏嵌入 MiroTalk 与 The Lounge也可新窗口单独打开。
### 群聊频道如何进入?
- 全局:`https://lounge.nomadro.cn`(默认 `#nomadcna``#online-events`
- 单活动:`buildMeetupChatUrl(meetup)``lounge.nomadro.cn/#/chan-#频道`
- PocketBase 活动记录可设 `loungeChannel` 字段
## 维护命令速查
```bash
# 构建并重启主站
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
systemctl restart cnomadcna.service
# 查看服务
systemctl status cnomadcna.service --no-pager
systemctl status mirotalk-p2p.service --no-pager
systemctl status ergo.service --no-pager
systemctl status thelounge.service --no-pager
# 查看日志
journalctl -u cnomadcna.service -n 100 --no-pager
journalctl -u mirotalk-p2p.service -n 100 --no-pager
journalctl -u ergo.service -n 100 --no-pager
journalctl -u thelounge.service -n 100 --no-pager
# Nginx
nginx -t
nginx -s reload
# 端口
ss -ltnp | rg ':4002|:8000|:8090|:9000|:9300|:9100|:9101|:6667|:6697'
# Docker
docker ps
# HTTPS
certbot certificates
```