Files
HomeDash/README.md
JianFeeeee 33564fb8f5 docs: complete deployment tutorial + frp/webui4frpc guide, expand .env/docker-compose for tokens & base_url
- Rewrite quick-start for source clone & build (A: compose, B: docker run)
- Expand frp section: full frps.toml, webui4frpc WebUI workflow, native frpc.toml, tunnel planning table
- docker-compose: NAVI_PORT / SEARXNG_BASE_URL env passthrough, better docs
- .env.example: document all vars incl token generation & base_url
- Fix firefox debugging URL typo, extension token note
2026-09-08 22:39:52 +08:00

499 lines
15 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 在内网主机上管理, 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 20+ 与 Docker Compose v2
- 可选:公网域名 + 反向代理,用于外网访问(见下文「生产部署」)
### 方式 A:从本仓库拉取源码构建(推荐)
```bash
# 1. 拉取仓库(把 <your-gitea> 换成你自己的 Git 托管地址)
git clone <repo-url> HomeDash
cd HomeDash
# 2. 配置环境变量
cp .env.example .env
# 编辑 .env:
# - NAVI_SYNC_TOKEN / NAVI_ADMIN_TOKEN:鉴权令牌,openssl rand -hex 24 生成
# - SEARXNG_BASE_URL:对外访问地址(有域名填 https://nav.example.com)
# - NAVI_PORT:对外端口(默认 3003)
# 3. 首次启动(会拉取 golang/dashy/searxng 基础镜像并编译)
docker compose up -d --build
# 4. 访问
echo "打开 http://<ip>:3003"
```
### 方式 B:直接构建镜像运行
```bash
docker build -t jianf-navi .
docker run -d --name jianf-navi -p 3003:8080 \
-e SEARXNG_BASE_URL=https://nav.example.com \
-e NAVI_SYNC_TOKEN=<random> \
-e NAVI_ADMIN_TOKEN=<random> \
-v $PWD/data:/app/data \
jianf-navi
```
### 首次配置导航项
容器启动后按需编辑 `data/conf.yml`(sections 列表),或直接用管理后台添加:
```bash
# 先让 conf.yml 从模板生成
docker compose restart
# 或用管理 WebUI 在浏览器添加:http://<ip>:3003/admin/
```
> 首次没有 data/ 目录时,容器会用内置 `conf.example.yml` 生成一份最小配置。
## 配置说明
### 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` | 管理后台/API 写操作令牌 | 空(不鉴权) |
| `SEARXNG_BASE_URL` | 对外访问地址,影响搜索结果链接 | `http://localhost:3003` |
| `NAVI_PORT` | compose 对外端口 | `3003` |
## 浏览器扩展
### 安装
**Chrome / Edge**:`chrome://extensions/` → 开发者模式 → 加载 `extension/chrome/`
**Firefox**:`about:debugging#/runtime/this-firefox` → 临时加载 `extension/firefox/manifest.json`
> 扩展的「同步令牌」需填服务端 `.env` 里 `NAVI_SYNC_TOKEN` 的**同一个值**,否则同步被拒(401)。
### 配置
| 字段 | 示例 |
|------|------|
| 服务地址 | `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。
> 也可不用它,直接用原生 frpc(见文末「原生 frpc 方式」)。
### 为什么要穿透
本导航容器跑在**内网**(如 192.168.x.x:3003)。要让**外网通过域名访问**,需要:
```
外网用户
→ 公网 VPS(有公网IP) 上的 frps [frp 服务端,监听控制端口 + 域名端口]
→ frpc(内网运行,webui4frpc 管理或原生)
→ 你内网的 portal-nginx / navi [把域名流量转发进来]
```
### 第 1 步:公网 VPS 部署 frps
下载 frp 服务端二进制并写配置:
```toml
# frps.toml
bindPort = 7000 # frpc 连接 frps 的控制端口
vhostHTTPPort = 8080 # HTTP 类型代理的入口端口(雷池/WAF 也走它)
auth.method = "token"
auth.token = "<长随机token>" # frpc 必须带相同 token 才能连
tls.enable = true # frpc↔frps 加密
# 用 systemd 启动,或直接:
# ./frps -c frps.toml
```
VPS 安全组/防火墙需放行:`7000`(frpc连接)、`8080`(http域名)、`443/80`(若 https 由 frps 终结)。
### 第 2 步:内网主机部署 webui4frpc(可视化)
从 [webui4frpc](https://gitcode.com/JianFeeeee/webui4frpc) 下载二进制,启动:
```bash
/opt/webui4frpc/webui4frpc \
-addr 0.0.0.0:7500 \
-user admin -password '<强密码>' \
-workdir /opt/webui4frpc/data
# 打开 http://<内网IP>:7500 进入 WebUI
```
### 第 3 步:在 WebUI 建「portal」隧道
在 WebUI 里新建配置,指向公网 frps,并添加代理。等价配置文件存于 `configs/*.json`:
```json
{
"serverAddr": "<vps-public-ip>",
"serverPort": 7000,
"auth": { "method": "token", "token": "<frp-token>" },
"proxies": [
{
"name": "portal_https",
"type": "https",
"localIP": "<内网portal-nginx主机IP>",
"localPort": 3080,
"customDomains": ["*.nav.example.com", "nav.example.com"]
},
{
"name": "portal_http",
"type": "tcp",
"localIP": "<内网portal-nginx主机IP>",
"localPort": 3081,
"remotePort": 8080
},
{
"name": "navi_ssh",
"type": "tcp",
"localIP": "<内网目标主机IP>",
"localPort": 22,
"remotePort": 220
}
]
}
```
webui4frpc 会自动下载并启动 `frpc`(存 `data/bin/`),配置变更即热加载。
### 第 4 步:DNS 解析
把 `nav.example.com`、`*.nav.example.com` 的 DNS 指向**公网 VPS IP**。
- `type: https` 代理:frps 把域名 443 流量转发内网 portal-nginx:3080,**TLS 证书由你内网管理**
- `type: tcp`(portal_http):把外网 8080 → 内网 3081(HTTP→HTTPS 跳转或备用入口)
### 原生 frpc 方式(不用 webui4frpc)
```toml
# frpc.toml
serverAddr = "<vps-public-ip>"
serverPort = 7000
auth.method = "token"
auth.token = "<frp-token>"
[[proxies]]
name = "portal_https"
type = "https"
localIP = "192.168.1.100"
localPort = 3080
customDomains = ["nav.example.com", "*.nav.example.com"]
[[proxies]]
name = "portal_http"
type = "tcp"
localIP = "192.168.1.100"
localPort = 3081
remotePort = 8080
```
```bash
./frpc -c frpc.toml
```
### 隧道规划对照
| 代理名 | type | 本地 | 远端 | 用途 |
|--------|------|------|------|------|
| portal_https | https | 内网:3080 | 443域名 | 统一反代 HTTPS 入口 |
| portal_http | tcp | 内网:3081 | 8080 | HTTP 跳 HTTPS / 备用 |
| gitea_ssh | tcp | 内网:22 | 220 | Git SSH |
## 鉴权(历史同步 + 管理 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