# HomeDash — 自托管内网导航门户 将 **Dashy + SearXNG + 浏览器历史同步 + 管理后台**整合在单个 Docker 容器中,并附带**完整的内网服务发布方案**(带统一认证的反向代理 + webui4frpc 内网穿透),让外网通过域名安全访问内网服务。 ## 架构总览(生产全链路) ``` 公网用户 │ ▼ 阿里云 VPS (frps 服务端, :7000/8080/443) │ frps: vhost 端口 + tcp 隧道 ▼ frpc (由 webui4frpc 在 .60 上管理, WebUI :7500) │ configs/*.json → 定义各隧道 ▼ portal-nginx (统一认证反向代理, 容器) │ auth_request → portal-auth(:4001) 统一登录 │ map $host → 按子域名分流 ▼ navi 单容器 (Dashy+SearXNG+历史+管理, :3003) │ 内层 nginx :8080 按路径分流 ▼ Dashy / SearXNG(→Clash 代理出网) / 历史 API / 管理 UI ``` ### navi 容器内部 ``` ┌─────────────────────────────────────────────────────┐ │ nginx (:8080) — 统一入口,同域名同端口 │ │ ├─ / → Dashy 静态仪表盘 │ │ ├─ /search → SearXNG 搜索 (:8888) │ │ ├─ /static/ → SearXNG 静态资源 (:8888) │ │ ├─ /api/history/ → 历史同步 API (:3004) │ │ ├─ /admin/ → 管理后台 WebUI (:3005) │ │ ├─ /conf.yml → 运行时导航配置(数据卷) │ │ └─ /item-icons/ → 自定义图标 (数据卷) │ └─────────────────────────────────────────────────────┘ ``` **技术栈**:Go(历史 API + 管理 API)· SearXNG(Python) · Dashy/Vue · Nginx · Supervisord · frp/webui4frpc ## 快速开始 ### 前置条件 - Docker + Docker Compose v2 - 可选:反向代理(Nginx / Caddy / Traefik)用于 HTTPS ### 1. 克隆并启动 ```bash git clone ssh://git@:220//HomeDash.git cd HomeDash cp .env.example .env # 编辑令牌 docker compose up -d ``` 访问 `http://:3003` ### 2. 首次配置 编辑 `data/conf.yml` 自定义导航项,重启生效: ```bash docker compose restart ``` ### 3. 导入预构建镜像(可选) 如果无法构建,使用预导出的镜像: ```bash docker load < navi-image.tar.gz docker compose up -d ``` ## 配置说明 ### conf.yml 核心字段 ```yaml appConfig: theme: default webSearch: searchEngine: custom customSearchEngine: /search?q= # 同域名反代到 SearXNG openingMethod: newtab sections: - name: 常用服务 # 按访问频次自动排序 icon: fas fa-star collapsed: false items: - title: Gitea icon: fas fa-code-branch url: http://192.168.1.100:3000 statusCheck: true ``` ### 图标 放入 `data/item-icons/`,conf.yml 引用文件名: ```yaml icon: my-icon.png # 本地图标 icon: fas fa-star # Font Awesome ``` ### 环境变量 | 变量 | 说明 | 默认 | |------|------|------| | `NAVI_SYNC_TOKEN` | 浏览器插件同步令牌 | 空(不鉴权) | | `NAVI_ADMIN_TOKEN` | 管理后台写操作令牌 | 空(不鉴权) | ## 浏览器扩展 ### 安装 **Chrome / Edge**:`chrome://extensions/` → 开发者模式 → 加载 `extension/chrome/` **Firefox**:`about:debugwriting#/runtime/this-firefox` → 加载 `extension/firefox/manifest.json` ### 配置 | 字段 | 示例 | |------|------| | 服务地址 | `https://nav.example.com` | | 同步令牌 | `.env` 中的 `NAVI_SYNC_TOKEN` | | 设备名称 | `desktop-chrome` | ### 行为 - 每 30 秒检查本地队列,**仅在有新数据时 POST** - 搜索引擎 URL 在本地过滤,不上传 - 「常用服务」和「历史访问」按总访问频次自动排序 ## 管理后台 访问 `http://:3003/admin/` 可视化管理导航项。 API(需 `NAVI_ADMIN_TOKEN`): ```bash # 查看 curl -H "Authorization: Bearer " http://:3003/admin/api/sections # 添加 curl -H "Authorization: Bearer " -X POST -H 'Content-Type: application/json' \ -d '{"sectionIndex":0,"item":{"title":"New","url":"https://example.com"}}' \ http://:3003/admin/api/item # 删除 curl -H "Authorization: Bearer " -X DELETE -H 'Content-Type: application/json' \ -d '{"sectionIndex":0,"itemIndex":2}' \ http://:3003/admin/api/item/delete ``` ## 历史同步 API | 端点 | 方法 | 说明 | |------|------|------| | `/api/history/sync` | POST | 批量同步浏览记录 | | `/api/history/` | GET | 获取历史(按频次排序) | | `/api/history/` | DELETE | 清空全部历史 | | `/api/history/stats` | GET | 统计信息 | ```json // POST /api/history/sync { "items": [{ "url": "https://example.com/page", "title": "Page Title", "device": "desktop-chrome", "timestamp": 1725800000, "visit_count": 3 }] } ``` ## 生产部署:外层统一认证反向代理 > 内网门户通常不直接暴露 3003 端口,而是放在一个**带统一登录认证的反向代理**后面, > 所有子域名共用一个登录入口。下面是本项目参考的 portal-nginx 方案。 ### portal-nginx(统一入口容器) 结构:一个 nginx 容器负责所有 `*.example.com` 的 TLS 终止 + 按域名分流 + 统一认证。 ```nginx # 按域名映射到内网后端 map $host $backend { hostnames; default ""; navi.example.com http://127.0.0.1:3003; # 本导航容器 gitea.example.com http://127.0.0.1:3000; blog.example.com http://127.0.0.1:8080; } # 需登录的域名统一走 auth_request server { listen 443 ssl; http2 on; server_name navi.example.com gitea.example.com blog.example.com; auth_request /_auth; # 先问认证服务 error_page 401 = @login; # 未登录 → 跳登录页 location /_auth { internal; proxy_pass http://127.0.0.1:4001; # portal-auth 认证服务 proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header Cookie $http_cookie; } location @login { return 302 https://login.example.com/login?redirect=$scheme://$host$request_uri; } location / { if ($backend = "") { return 404; } proxy_pass $backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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; } } ``` ### portal-auth(统一认证服务) 一个极简 HTTP 认证服务(:4001),只做两件事: 1. `GET/POST /login` — 校验用户名密码,签发 `portal_session` cookie 2. `/_auth` 被 nginx `auth_request` 调用 — 校验 cookie 是否有效(200=通过,401=拒绝) ```python # app.py(示意) USERNAME = os.environ['PORTAL_USERNAME'] # admin PASSWORD = os.environ['PORTAL_PASSWORD'] # 强密码 sessions = {} # token -> 过期时间 # /login 成功 → uuid token 写入 sessions + Set-Cookie # /_auth 读 Cookie → 校验存在且未过期 → 200/401 ``` 部署: ```bash docker run -d --name portal-auth \ -p 4001:4001 \ -e PORTAL_USERNAME=admin \ -e PORTAL_PASSWORD=change-me \ -v /path/auth/app.py:/app/app.py:ro \ python:3.11-slim ``` ### 让搜索/管理子路径免登录(可选) Dashy 域名整体在认证后,登录用户能正常搜索。若希望搜索页**免登录公开访问**,在对应 server 块加: ```nginx location /search { auth_request off; proxy_pass $backend; } ``` > ⚠️ 管理后台 `/admin/` 与历史 API `/api/` **不应**免认证—— > 它们能修改导航与数据,建议保留登录墙,或改用 **Bearer Token**(见下文鉴权章节)。 ## 内网穿透:webui4frpc + frp > [webui4frpc](https://gitcode.com/JianFeeeee/webui4frpc) — 基于 Go 与 Web 的可视化 frpc 配置管理工具, > 提供 WebUI 创建/启停 frpc 隧道,无需手写 frpc.toml。 ### 拓扑 ``` 公网用户 → 阿里云 VPS frps(7000/8080/443) → frpc ← webui4frpc(.60:7500) 管理 → portal-nginx(.106) 认证分流 → navi:3003 ``` ### 1. 公网 VPS 部署 frps ```bash # frps.toml bindPort = 7000 vhostHTTPPort = 8080 # ...按需配置 auth.token 与 TLS ``` ### 2. 内网主机部署 webui4frpc ```bash /opt/webui4frpc/webui4frpc \ -addr 0.0.0.0:7500 \ -user -password <强密码> \ -workdir /opt/webui4frpc/data ``` ### 3. 在 WebUI 中创建隧道 webui4frpc 把配置存为 `configs/*.json`。一个隧道文件示例: ```json { "serverAddr": "", "serverPort": 7000, "auth": { "method": "token", "token": "frp-token" }, "proxies": [ { "name": "portal_https", "type": "https", "localIP": "192.168.1.100", "localPort": 3080, "customDomains": ["*.navi.example.com", "navi.example.com"] }, { "name": "portal_http", "type": "tcp", "localIP": "192.168.1.100", "localPort": 3081, "remotePort": 8080 } ] } ``` - `type: https` + `customDomains` → frps 把 443 的域名流量转发给内网 portal-nginx 的 3080(TLS 终止在内网) - `type: tcp` 用于无域名的端口映射(如 Gitea SSH、HTTP 跳转) ### 4. frpc 客户端生成 webui4frpc 自动下载并启动 `frpc` 二进制(`data/bin/`),配置变更后热加载。 多份配置 = 多个 frpc 进程,各自连接 frps。 ## 鉴权(历史同步 + 管理 API) 导航门户的 `/api/` 和 `/admin/api/` 能读写数据,**不应裸奔**。推荐两层: 1. **外层**:portal-nginx 保留登录墙(`/admin/` 页面需登录) 2. **内层**:Go 服务校验 `Authorization: Bearer ` 部署时通过环境变量注入令牌: ```bash docker run -d ... \ -e NAVI_SYNC_TOKEN=<长随机串> \ # 浏览器扩展同步用 -e NAVI_ADMIN_TOKEN=<长随机串> \ # 管理 API 写操作用 jianf-navi:latest ``` 浏览器扩展在设置里填同一个 `NAVI_SYNC_TOKEN`,每次同步请求带 `Authorization: Bearer `。 ```bash # 管理 API 示例(写操作必须带 admin token) curl -H "Authorization: Bearer " \ -X POST -H 'Content-Type: application/json' \ -d '{"sectionIndex":0,"item":{"title":"New","url":"https://example.com"}}' \ http://:3003/admin/api/item ``` > 实现参考:`history-server` 的 `/sync` 校验 `NAVI_SYNC_TOKEN`, > `admin-server` 的写操作校验 `NAVI_ADMIN_TOKEN`。 ## 数据备份 ``` data/ ├── conf.yml # 导航配置 ├── history.json # 浏览历史 └── item-icons/ # 自定义图标 ``` ```bash # 自动备份 0 2 * * * docker exec navi tar czf /tmp/bak.tar.gz -C /app/data . && \ docker cp navi:/tmp/bak.tar.gz /backup/navi-$(date +\%Y\%m\%d).tar.gz ``` ## 项目结构 ``` ├── history-server/ # Go 历史同步 API (:3004) ├── admin-server/ # Go 管理后台 API (:3005) ├── admin/ # 管理后台前端 ├── web/ # Dashy 注入脚本 ├── extension/ # 浏览器扩展 (Chrome/Firefox) ├── nginx.conf # 容器内统一反代 ├── supervisord.conf # 进程管理 ├── conf.example.yml # 脱敏示例配置 ├── searxng-settings.yml ├── docker-compose.yml ├── Dockerfile └── .env.example ``` ## 常见问题 **搜索无结果**:确认容器能访问搜索引擎。国内服务器建议只启用百度/Bing中国/搜狗。 **管理后台 401**:设置了 `NAVI_ADMIN_TOKEN` 时需携带 Bearer token。 **历史不显示**:检查扩展配置中的服务地址和同步令牌。 **内存占用**:正常约 100MB。 ## 许可 MIT