diff --git a/.env.example b/.env.example index adf6823..3cae941 100644 --- a/.env.example +++ b/.env.example @@ -13,10 +13,17 @@ ACME_EMAIL=932465777@qq.com # Reality 伪装目标(真实大站,不要用你自己的域名) REALITY_SERVER_NAME=www.microsoft.com -# 管理面板登录(安装完成后访问 https://域名:8444) +# 管理面板登录(安装完成后访问 http://域名/PANEL_PATH/) PANEL_USERNAME=dekun PANEL_PASSWORD=Woaini521@ +# 面板访问路径(留空则安装时自动生成随机路径,如 jiedian-a1b2c3d4) +# 访问示例:http://66.hyf2.cc/jiedian-a1b2c3d4/ +# PANEL_PATH=jiedian-a1b2c3d4 + +# 可选:仅允许指定 IP 访问面板(留空则不限制) +# PANEL_ALLOW_IP=1.2.3.4 + # 以下由 scripts/generate-keys.sh 自动生成,也可手动填写 # REALITY_PRIVATE_KEY= # REALITY_PUBLIC_KEY= diff --git a/README.md b/README.md index 9c0d94e..5dcfa5d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ |------|-----| | VPS IP | `47.76.87.111` | | 域名 | `66.hyf2.cc` | -| 管理面板 | `https://66.hyf2.cc:8444` | +| 管理面板 | `http://66.hyf2.cc//`(安装时输出,见 `.env`) | > 完整部署步骤见 **[docs/DEPLOY.md](docs/DEPLOY.md)** @@ -33,7 +33,7 @@ bash scripts/install.sh ## 架构 ``` -浏览器 ──► Nginx:8444 ──► Web 管理面板(登录、添加节点) +浏览器 ──► Nginx:80// ──► Web 管理面板(登录、添加节点) │ ▼ sing-box 配置重载 @@ -42,7 +42,7 @@ bash scripts/install.sh ├─ TCP 443 ──► sing-box VLESS+Reality └─ UDP 8443 ─► sing-box Hysteria2 -Nginx 127.0.0.1:8080 ← 伪装静态页(fallback) +Nginx 127.0.0.1:8080 ← 伪装静态页(Reality fallback 场景) ``` --- @@ -71,18 +71,17 @@ Nginx 127.0.0.1:8080 ← 伪装静态页(fallback) | 端口 | 协议 | 用途 | |------|------|------| | 22 | TCP | SSH | -| 80 | TCP | HTTP(Let's Encrypt 验证) | +| 80 | TCP | HTTP(ACME 验证 + **管理面板反向代理**) | | 443 | TCP | VLESS + Reality | | 8443 | UDP | Hysteria2 | -| 8444 | TCP | **Web 管理面板(HTTPS)** | --- ## 常用运维 ```bash -# 面板 / 节点 -https://66.hyf2.cc:8444 +# 面板地址(安装时输出) +grep PANEL_PATH /opt/jiedian/.env # 服务状态 systemctl status sing-box jiedian-panel @@ -100,7 +99,7 @@ bash scripts/install.sh 1. 不要公开分享节点链接 2. Reality SNI 使用 `www.microsoft.com`,不要用 `66.hyf2.cc` 3. 客户端开启 uTLS / chrome 指纹 -4. 面板密码请妥善保管,安装后可在 `.env` 查看 `PANEL_PASSWORD` +4. 面板路径与密码请妥善保管,安装后可在 `.env` 查看 `PANEL_PATH` / `PANEL_PASSWORD` --- diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 7ef8c1e..fbf5b4b 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -6,7 +6,7 @@ |------|-----| | VPS IP | `47.76.87.111` | | 域名 | `66.hyf2.cc` | -| 管理面板 | `https://66.hyf2.cc:8444` | +| 管理面板 | `http://66.hyf2.cc//` | | 部署目录 | `/opt/jiedian` | | 系统 | Ubuntu 22.04 / 24.04 | @@ -28,7 +28,7 @@ dig +short A 66.hyf2.cc ### 2. 阿里云安全组 -放行:`22`、`80`、`443/TCP`、`8443/UDP`、`8444/TCP` +放行:`22`、`80`、`443/TCP`、`8443/UDP`(**无需 8444**) --- @@ -44,7 +44,8 @@ bash scripts/install.sh 安装结束会输出: ``` -管理面板: https://66.hyf2.cc:8444 +管理面板: http://66.hyf2.cc/jiedian-xxxx/ +面板路径: jiedian-xxxx (见 .env 中 PANEL_PATH) 用户名: admin 密码: xxxxx ``` @@ -72,11 +73,11 @@ bash scripts/install.sh ## 安装脚本做了什么 1. 安装 sing-box、nginx、Python 面板依赖 -2. 防火墙放行 22/80/443/8443/8444 +2. 防火墙放行 22/80/443/8443(不暴露 8444) 3. acme.sh 申请 `66.hyf2.cc` 证书 4. 初始化 SQLite 节点库 + 默认管理员 5. 生成 sing-box 配置并启动服务 -6. Nginx 8444 端口提供 HTTPS 管理面板 +6. Nginx 80 端口子路径反向代理管理面板 --- @@ -95,9 +96,10 @@ bash scripts/install.sh ```bash systemctl status sing-box jiedian-panel -ss -tlnp | grep -E '443|8444' +ss -tlnp | grep -E '80|443|5080' ss -ulnp | grep 8443 -curl -k -I https://66.hyf2.cc:8444/login +PANEL_PATH=$(grep ^PANEL_PATH= /opt/jiedian/.env | cut -d= -f2) +curl -I "http://66.hyf2.cc/${PANEL_PATH}/login" ``` --- @@ -106,9 +108,10 @@ curl -k -I https://66.hyf2.cc:8444/login | 问题 | 处理 | |------|------| +| 面板 404 | 确认 URL 含完整 `PANEL_PATH`,见 `grep PANEL_PATH .env` | | apt 锁被占用 | 等待自动更新结束,或 `bash scripts/install.sh` 会自动等待 | | sing-box 443 被占用 | `ss -tlnp \| grep 443`,停止占用进程后重装 | -| 忘记面板密码 | `grep PANEL_PASSWORD /opt/jiedian/.env` 或重新 `generate-keys.sh` | +| 忘记面板密码/路径 | `grep PANEL_ /opt/jiedian/.env` 或重新 `generate-keys.sh` | | SSH 主机密钥变更 | 重装系统后本地执行 `ssh-keygen -R 47.76.87.111` | 更多见 [troubleshooting.md](troubleshooting.md)。 diff --git a/docs/STACK.md b/docs/STACK.md index d47585b..7057325 100644 --- a/docs/STACK.md +++ b/docs/STACK.md @@ -13,7 +13,9 @@ - `443/TCP` — VLESS + Reality(主力) - `8443/UDP` — Hysteria2(备用) +- `80/TCP` — ACME 验证 + 管理面板 Nginx 反向代理 - `127.0.0.1:8080` — Nginx 伪装静态页(Reality fallback 场景) +- `127.0.0.1:5080` — Flask 面板后端(不对外暴露) ## 单协议简化 diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index cbce5b6..9e1ee3f 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -6,19 +6,85 @@ # sing-box 是否运行 systemctl is-active sing-box +# 管理面板是否运行 +systemctl is-active jiedian-panel + # 配置语法 sing-box check -c /etc/sing-box/config.json # 端口监听 -ss -tlnp | grep 443 # Reality TCP -ss -ulnp | grep 8443 # Hysteria2 UDP +ss -tlnp | grep 443 # Reality TCP +ss -tlnp | grep 5080 # 面板后端(仅本机) +ss -tlnp | grep :80 # Nginx(ACME + 面板反向代理) +ss -ulnp | grep 8443 # Hysteria2 UDP # Nginx fallback curl -s http://127.0.0.1:8080 ``` +## 管理面板访问 + +面板通过 **Nginx 80 端口反向代理**,不再单独暴露 8444。 + +```bash +# 查看面板路径(安装时自动生成或 .env 中配置) +grep PANEL_PATH /opt/jiedian/.env + +# 示例:PANEL_PATH=jiedian-a1b2c3d4 +# 访问地址:http://66.hyf2.cc/jiedian-a1b2c3d4/ +curl -I "http://66.hyf2.cc/$(grep ^PANEL_PATH= /opt/jiedian/.env | cut -d= -f2)/login" +``` + +> 443 端口已被 sing-box Reality 占用,面板走 80 端口子路径。请妥善保管 `PANEL_PATH`,相当于隐藏入口。 + +--- + ## 常见问题 +### 面板打不开 / 404 + +1. **路径不对**:必须用完整路径,末尾带 `/`,例如 `http://域名/jiedian-xxxx/login` +2. **Nginx 未加载配置**: + ```bash + nginx -t && systemctl reload nginx + cat /etc/nginx/sites-available/acme | grep -A5 jiedian + ``` +3. **面板进程未运行**: + ```bash + systemctl status jiedian-panel + journalctl -u jiedian-panel -n 30 --no-pager + curl -I http://127.0.0.1:5080/login + ``` +4. **阿里云安全组**:只需放行 `80/TCP`,**无需**再放行 8444 + +### 面板登录后跳回登录页 / 样式丢失 + +通常是子路径反向代理头未生效,检查 Nginx 是否包含: + +```nginx +proxy_set_header X-Forwarded-Prefix /你的PANEL_PATH; +``` + +修改后 `systemctl reload nginx`,并确认 `.env` 中 `PANEL_PATH` 与 Nginx 一致。 + +### 设置了 PANEL_ALLOW_IP 后无法访问 + +`.env` 中 `PANEL_ALLOW_IP` 会限制仅该 IP 可访问面板。本机公网 IP 变更后需更新并重装 Nginx 配置: + +```bash +# 编辑 .env 后重新渲染 nginx +cd /opt/jiedian +bash scripts/install.sh # 或手动 sed acme 配置后 reload nginx +``` + +### 忘记面板密码或路径 + +```bash +grep -E 'PANEL_(USERNAME|PASSWORD|PATH)' /opt/jiedian/.env +# 重置密码与 Reality 密钥 +bash scripts/generate-keys.sh +``` + ### acme.sh 证书申请失败 ```bash @@ -29,7 +95,7 @@ dig +short A your.domain.com ss -tlnp | grep :80 # 手动重试 -/root/.acme.sh/acme.sh --issue -d your.domain.com --nginx --force +/root/.acme.sh/acme.sh --issue -d your.domain.com -w /var/www/acme --force ``` ### sing-box 无法启动 @@ -40,6 +106,11 @@ journalctl -u sing-box -n 50 --no-pager 常见原因:证书路径错误、JSON 语法错误、443 被占用。 +```bash +ss -tlnp | grep :443 +sing-box check -c /etc/sing-box/config.json +``` + ### 客户端能连但速度慢 - 换 Hysteria2 节点(UDP/QUIC 抗丢包) @@ -52,6 +123,35 @@ journalctl -u sing-box -n 50 --no-pager 2. 修改 `.env` 中 `REALITY_SERVER_NAME` 为其他大站(如 `www.apple.com`) 3. 重新运行 `install.sh` 或手动更新 `/etc/sing-box/config.json` 并 restart +### apt 锁被占用 + +安装脚本会自动等待最多 10 分钟。若超时: + +```bash +# 查看占用进程 +fuser /var/lib/dpkg/lock-frontend +# 等待 cloud-init / unattended-upgrades 结束后再执行 +bash scripts/install.sh +``` + +### SSH 主机密钥变更 + +VPS 重装系统后,本地执行: + +```bash +ssh-keygen -R 你的VPS_IP +``` + +### 卸载后重装 + +```bash +cd /opt/jiedian +git pull +bash scripts/uninstall.sh +bash scripts/generate-keys.sh # 可选:重置密钥与面板密码 +bash scripts/install.sh +``` + ### 改用 Xray 替代 sing-box(可选) 若更熟悉 Xray,可使用 `server/xray-server.json.template`: @@ -66,10 +166,24 @@ sed -e "s|\${UUID}|...|g" ... server/xray-server.json.template > /usr/local/etc/ systemctl restart xray ``` -Hysteria2 仍需单独部署(或使用 sing-box 仅跑 Hy2 inbound)。 +Hysteria2 仍需单独部署(或使用 sing-box 仅跑 Hy2 inbound)。 + +--- + +## 防火墙与安全组对照 + +| 端口 | 协议 | 用途 | 是否必须放行 | +|------|------|------|-------------| +| 22 | TCP | SSH | 是 | +| 80 | TCP | ACME 验证 + **管理面板** | 是 | +| 443 | TCP | VLESS + Reality | 是 | +| 8443 | UDP | Hysteria2 | 是 | +| ~~8444~~ | ~~TCP~~ | ~~旧版面板的独立 HTTPS~~ | **已废弃,无需放行** | ## 安全建议 +- 使用随机 `PANEL_PATH`,不要公开分享面板地址 +- 可选在 `.env` 设置 `PANEL_ALLOW_IP=你的公网IP` 限制访问来源 - SSH 改用密钥登录,禁用密码:`PermitRootLogin prohibit-password` - 可选修改 SSH 端口,ufw 放行新端口后再删 22 -- 不要将 `.env` 或 `share-links.txt` 上传到公开仓库 +- 不要将 `.env` 或节点分享链接上传到公开仓库 diff --git a/panel/app.py b/panel/app.py index e315080..e4b9942 100644 --- a/panel/app.py +++ b/panel/app.py @@ -17,6 +17,7 @@ from flask import ( session, url_for, ) +from werkzeug.middleware.proxy_fix import ProxyFix from db import add_node, delete_node, list_nodes, node_count, verify_admin from links import build_links, load_env @@ -44,6 +45,11 @@ app.config.update( PERMANENT_SESSION_LIFETIME=86400 * 7, ) +_panel_path = os.environ.get("PANEL_PATH", "").strip().strip("/") +if _panel_path: + app.config["SESSION_COOKIE_PATH"] = f"/{_panel_path}/" + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) + def login_required(view): @wraps(view) diff --git a/scripts/install.sh b/scripts/install.sh index 9f37cdd..0e02872 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -61,6 +61,31 @@ fi : "${REALITY_SHORT_ID:?}" : "${PANEL_PASSWORD:?}" +normalize_panel_path() { + local p="${1:-}" + p="${p#/}" + p="${p%/}" + echo "$p" +} + +PANEL_PATH="$(normalize_panel_path "${PANEL_PATH:-}")" +if [[ -z "$PANEL_PATH" ]]; then + PANEL_PATH="jiedian-$(openssl rand -hex 4)" + if grep -q "^PANEL_PATH=" "$ENV_FILE" 2>/dev/null; then + sed -i "s|^PANEL_PATH=.*|PANEL_PATH=${PANEL_PATH}|" "$ENV_FILE" + else + echo "PANEL_PATH=${PANEL_PATH}" >> "$ENV_FILE" + fi +fi +PANEL_LOCATION="/${PANEL_PATH}/" +PANEL_PREFIX="/${PANEL_PATH}" + +PANEL_ALLOW_BLOCK="" +if [[ -n "${PANEL_ALLOW_IP:-}" ]]; then + PANEL_ALLOW_BLOCK=" allow ${PANEL_ALLOW_IP}; + deny all;" +fi + export JIEDIAN_ROOT="$ROOT_DIR" ARCH="$(uname -m)" @@ -90,10 +115,9 @@ ufw --force reset ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp comment 'SSH' -ufw allow 80/tcp comment 'HTTP-ACME' +ufw allow 80/tcp comment 'HTTP-ACME-Panel' ufw allow 443/tcp comment 'Reality' ufw allow 8443/udp comment 'Hysteria2' -ufw allow 8444/tcp comment 'Panel-HTTPS' ufw --force enable log "部署 Nginx fallback 站点 ..." @@ -103,11 +127,16 @@ cp "$ROOT_DIR/server/nginx/fallback.conf" /etc/nginx/sites-available/fallback ln -sf /etc/nginx/sites-available/fallback /etc/nginx/sites-enabled/fallback rm -f /etc/nginx/sites-enabled/default -log "部署 Nginx ACME 验证站点 (80) ..." +log "部署 Nginx ACME + 管理面板反向代理 (80) ..." mkdir -p /var/www/acme -sed "s|__DOMAIN__|${DOMAIN}|g" "$ROOT_DIR/server/nginx/acme.conf.template" \ +sed -e "s|__DOMAIN__|${DOMAIN}|g" \ + -e "s|__PANEL_LOCATION__|${PANEL_LOCATION}|g" \ + -e "s|__PANEL_PREFIX__|${PANEL_PREFIX}|g" \ + -e "s|__PANEL_ALLOW__|${PANEL_ALLOW_BLOCK}|g" \ + "$ROOT_DIR/server/nginx/acme.conf.template" \ > /etc/nginx/sites-available/acme ln -sf /etc/nginx/sites-available/acme /etc/nginx/sites-enabled/acme +nginx -t && systemctl enable nginx && systemctl restart nginx log "申请 TLS 证书 (Let's Encrypt) ..." mkdir -p /etc/sing-box/certs @@ -132,11 +161,8 @@ log "安装 TLS 证书到 sing-box ..." --key-file /etc/sing-box/certs/privkey.pem \ --fullchain-file /etc/sing-box/certs/fullchain.pem -log "部署管理面板 Nginx (8444) ..." -sed "s|__DOMAIN__|${DOMAIN}|g" "$ROOT_DIR/server/nginx/panel.conf.template" \ - > /etc/nginx/sites-available/panel -ln -sf /etc/nginx/sites-available/panel /etc/nginx/sites-enabled/panel -nginx -t && systemctl enable nginx && systemctl restart nginx +rm -f /etc/nginx/sites-enabled/panel /etc/nginx/sites-available/panel +nginx -t && systemctl reload nginx log "安装 Python 面板依赖 ..." python3 -m venv "$ROOT_DIR/panel/venv" @@ -179,6 +205,7 @@ After=network.target sing-box.service Type=simple WorkingDirectory=${ROOT_DIR}/panel Environment=JIEDIAN_ROOT=${ROOT_DIR} +Environment=PANEL_PATH=${PANEL_PATH} ExecStart=${ROOT_DIR}/panel/venv/bin/python app.py Restart=on-failure RestartSec=5 @@ -194,7 +221,7 @@ log "注册证书续期 reload 命令 ..." /root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \ --key-file /etc/sing-box/certs/privkey.pem \ --fullchain-file /etc/sing-box/certs/fullchain.pem \ - --reloadcmd "systemctl restart sing-box" \ + --reloadcmd "systemctl restart sing-box && systemctl reload nginx" \ || log "acme reloadcmd 注册失败,可忽略" systemctl restart sing-box jiedian-panel @@ -202,7 +229,8 @@ systemctl restart sing-box jiedian-panel log "部署完成!" echo "" echo "==========================================" -echo " 管理面板: https://${DOMAIN}:8444" +echo " 管理面板: http://${DOMAIN}${PANEL_LOCATION}" +echo " 面板路径: ${PANEL_PATH} (见 .env 中 PANEL_PATH)" echo " 用户名: ${PANEL_USERNAME}" echo " 密码: ${PANEL_PASSWORD}" echo "==========================================" diff --git a/server/nginx/acme.conf.template b/server/nginx/acme.conf.template index 67cf235..dbd56d2 100644 --- a/server/nginx/acme.conf.template +++ b/server/nginx/acme.conf.template @@ -14,4 +14,16 @@ server { return 200 'ok'; add_header Content-Type text/plain; } + + # 管理面板(Nginx 反向代理至 Flask,无需额外暴露 8444) + location ^~ __PANEL_LOCATION__ { +__PANEL_ALLOW__ + proxy_pass http://127.0.0.1:5080/; + proxy_http_version 1.1; + 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; + proxy_set_header X-Forwarded-Prefix __PANEL_PREFIX__; + } } diff --git a/server/nginx/panel.conf.template b/server/nginx/panel.conf.template deleted file mode 100644 index 2494ff1..0000000 --- a/server/nginx/panel.conf.template +++ /dev/null @@ -1,18 +0,0 @@ -server { - listen 8444 ssl; - listen [::]:8444 ssl; - server_name __DOMAIN__; - - ssl_certificate /etc/sing-box/certs/fullchain.pem; - ssl_certificate_key /etc/sing-box/certs/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - - location / { - proxy_pass http://127.0.0.1:5080; - proxy_http_version 1.1; - 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; - } -}