Files
HomeDash/README.md
JianFeeeee 7e49d2b51c feat: HomeDash - unified self-hosted navigation portal
Single-container navigation service combining:
- Dashy dashboard + SearXNG metasearch + history sync API + admin WebUI
- Go services for history/admin APIs, nginx as unified entry
- Browser extensions (Chrome MV3 / Firefox MV2) with 30s batched sync
- Production guide: portal-nginx unified auth reverse proxy + webui4frpc/frp tunnel
- Fully sanitized (no internal IPs/domains/credentials)
2026-09-08 22:25:00 +08:00

414 lines
12 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.

# HomeDash — 自托管内网导航门户
将 **Dashy + SearXNG + 浏览器历史同步 + 管理后台**整合在单个 Docker 容器中,并附带**完整的内网服务发布方案**(带统一认证的反向代理 + webui4frpc 内网穿透),让外网通过域名安全访问内网服务。
## 架构总览(生产全链路)
```
公网用户
│
▼
阿里云 VPS (frps 服务端, <vps-public-ip>: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@<your-gitea>:220/<owner>/HomeDash.git
cd HomeDash
cp .env.example .env # 编辑令牌
docker compose up -d
```
访问 `http://<your-ip>: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://<your-ip>:3003/admin/` 可视化管理导航项。
API(需 `NAVI_ADMIN_TOKEN`):
```bash
# 查看
curl -H "Authorization: Bearer <token>" http://<ip>:3003/admin/api/sections
# 添加
curl -H "Authorization: Bearer <token>" -X POST -H 'Content-Type: application/json' \
-d '{"sectionIndex":0,"item":{"title":"New","url":"https://example.com"}}' \
http://<ip>:3003/admin/api/item
# 删除
curl -H "Authorization: Bearer <token>" -X DELETE -H 'Content-Type: application/json' \
-d '{"sectionIndex":0,"itemIndex":2}' \
http://<ip>: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 <admin> -password <强密码> \
-workdir /opt/webui4frpc/data
```
### 3. 在 WebUI 中创建隧道
webui4frpc 把配置存为 `configs/*.json`。一个隧道文件示例:
```json
{
"serverAddr": "<vps-public-ip>",
"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 <token>`
部署时通过环境变量注入令牌:
```bash
docker run -d ... \
-e NAVI_SYNC_TOKEN=<长随机串> \ # 浏览器扩展同步用
-e NAVI_ADMIN_TOKEN=<长随机串> \ # 管理 API 写操作用
jianf-navi:latest
```
浏览器扩展在设置里填同一个 `NAVI_SYNC_TOKEN`,每次同步请求带 `Authorization: Bearer <token>`。
```bash
# 管理 API 示例(写操作必须带 admin token)
curl -H "Authorization: Bearer <NAVI_ADMIN_TOKEN>" \
-X POST -H 'Content-Type: application/json' \
-d '{"sectionIndex":0,"item":{"title":"New","url":"https://example.com"}}' \
http://<ip>: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