713 lines
24 KiB
Markdown
713 lines
24 KiB
Markdown
# 游牧中国 NomadCNA
|
||
|
||
NomadCNA 是面向数字游民的生活操作系统,用一个站点承载城市决策、会员加入、线上/线下活动、视频会议、Web 群聊、内容资源、任务赏金、支付和媒体存储等能力。
|
||
|
||
生产站点:
|
||
|
||
- 主站:https://nomadro.cn
|
||
- API:https://api.nomadro.cn
|
||
- PocketBase:https://pocketbase.nomadro.cn
|
||
- MinIO:https://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 P2P,Nginx 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
|
||
```
|