# 游牧中国 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 ```