HomeDash — 自托管内网导航门户
将 Dashy + SearXNG + 浏览器历史同步 + 管理后台整合在单个 Docker 容器中,并附带完整的内网服务发布方案(带统一认证的反向代理 + webui4frpc 内网穿透),让外网通过域名安全访问内网服务。
架构总览(生产全链路)
公网用户
│
▼
阿里云 VPS (frps 服务端, <vps-public-ip>:7000/8080/443)
│ frps: vhost 端口 + tcp 隧道
▼
frpc (由 webui4frpc 在内网主机上管理, 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. 克隆并启动
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 自定义导航项,重启生效:
docker compose restart
3. 导入预构建镜像(可选)
如果无法构建,使用预导出的镜像:
docker load < navi-image.tar.gz
docker compose up -d
配置说明
conf.yml 核心字段
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 引用文件名:
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):
# 查看
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 | 统计信息 |
// 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 终止 + 按域名分流 + 统一认证。
# 按域名映射到内网后端
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),只做两件事:
GET/POST /login— 校验用户名密码,签发portal_sessioncookie/_auth被 nginxauth_request调用 — 校验 cookie 是否有效(200=通过,401=拒绝)
# 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
部署:
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 块加:
location /search { auth_request off; proxy_pass $backend; }
⚠️ 管理后台
/admin/与历史 API/api/不应免认证—— 它们能修改导航与数据,建议保留登录墙,或改用 Bearer Token(见下文鉴权章节)。
内网穿透:webui4frpc + frp
webui4frpc — 基于 Go 与 Web 的可视化 frpc 配置管理工具, 提供 WebUI 创建/启停 frpc 隧道,无需手写 frpc.toml。
拓扑
公网用户 → 阿里云 VPS frps(7000/8080/443) → frpc ← webui4frpc(内网:7500) 管理
→ portal-nginx(内网) 认证分流 → navi:3003
1. 公网 VPS 部署 frps
# frps.toml
bindPort = 7000
vhostHTTPPort = 8080
# ...按需配置 auth.token 与 TLS
2. 内网主机部署 webui4frpc
/opt/webui4frpc/webui4frpc \
-addr 0.0.0.0:7500 \
-user <admin> -password <强密码> \
-workdir /opt/webui4frpc/data
3. 在 WebUI 中创建隧道
webui4frpc 把配置存为 configs/*.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/ 能读写数据,不应裸奔。推荐两层:
- 外层:portal-nginx 保留登录墙(
/admin/页面需登录) - 内层:Go 服务校验
Authorization: Bearer <token>
部署时通过环境变量注入令牌:
docker run -d ... \
-e NAVI_SYNC_TOKEN=<长随机串> \ # 浏览器扩展同步用
-e NAVI_ADMIN_TOKEN=<长随机串> \ # 管理 API 写操作用
jianf-navi:latest
浏览器扩展在设置里填同一个 NAVI_SYNC_TOKEN,每次同步请求带 Authorization: Bearer <token>。
# 管理 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/ # 自定义图标
# 自动备份
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