From 7089fa5777f5448e24cae2c9d37d5726f79fca0a Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 16 Jun 2026 08:25:49 +0800 Subject: [PATCH] fix: convert shell scripts to LF line endings for Linux Co-authored-by: Cursor --- .env | 26 +-- .env.example | 42 ++--- .gitattributes | 7 + docs/DEPLOY.md | 1 + scripts/generate-keys.sh | 108 ++++++------- scripts/install.sh | 342 +++++++++++++++++++-------------------- scripts/render-client.sh | 78 ++++----- 7 files changed, 306 insertions(+), 298 deletions(-) create mode 100644 .gitattributes diff --git a/.env b/.env index 2d51f9c..bf975ed 100644 --- a/.env +++ b/.env @@ -1,13 +1,13 @@ -# VPS 环境配置(66.hyf2.cc @ 47.76.87.111) -# 部署路径:/opt/jiedian - -VPS_IP=47.76.87.111 -DOMAIN=66.hyf2.cc -ACME_EMAIL=admin@hyf2.cc -REALITY_SERVER_NAME=www.microsoft.com - -UUID=42f5b04d-292d-4f13-b892-b70553a714d5 -REALITY_PRIVATE_KEY=IPKtaw1aVb4fS0TPcimu8zwaVGml-JJ5H1rj-_TFQHM -REALITY_PUBLIC_KEY=51H_ikqYdDRgCpjq3pvMYNbqrX8S3zuow1UEjqTN-nI -REALITY_SHORT_ID=e126b4ef9d36adfc -HY2_PASSWORD=npDFaGfRzAPLS3Hh7iM6TEOk +# VPS 环境配置(66.hyf2.cc @ 47.76.87.111) +# 部署路径:/opt/jiedian + +VPS_IP=47.76.87.111 +DOMAIN=66.hyf2.cc +ACME_EMAIL=admin@hyf2.cc +REALITY_SERVER_NAME=www.microsoft.com + +UUID=42f5b04d-292d-4f13-b892-b70553a714d5 +REALITY_PRIVATE_KEY=IPKtaw1aVb4fS0TPcimu8zwaVGml-JJ5H1rj-_TFQHM +REALITY_PUBLIC_KEY=51H_ikqYdDRgCpjq3pvMYNbqrX8S3zuow1UEjqTN-nI +REALITY_SHORT_ID=e126b4ef9d36adfc +HY2_PASSWORD=npDFaGfRzAPLS3Hh7iM6TEOk diff --git a/.env.example b/.env.example index 023044e..4db49e8 100644 --- a/.env.example +++ b/.env.example @@ -1,21 +1,21 @@ -# 复制为 .env 后填写,部署脚本会读取这些变量 -# cp .env.example .env - -# VPS 公网 IP -VPS_IP=47.76.87.111 - -# 域名(Hysteria2 证书用) -DOMAIN=66.hyf2.cc - -# Let's Encrypt 申请证书邮箱 -ACME_EMAIL=admin@hyf2.cc - -# Reality 伪装目标(真实大站,不要用你自己的域名) -REALITY_SERVER_NAME=www.microsoft.com - -# 以下由 scripts/generate-keys.sh 自动生成,也可手动填写 -# UUID= -# REALITY_PRIVATE_KEY= -# REALITY_PUBLIC_KEY= -# REALITY_SHORT_ID= -# HY2_PASSWORD= +# 复制为 .env 后填写,部署脚本会读取这些变量 +# cp .env.example .env + +# VPS 公网 IP +VPS_IP=47.76.87.111 + +# 域名(Hysteria2 证书用) +DOMAIN=66.hyf2.cc + +# Let's Encrypt 申请证书邮箱 +ACME_EMAIL=admin@hyf2.cc + +# Reality 伪装目标(真实大站,不要用你自己的域名) +REALITY_SERVER_NAME=www.microsoft.com + +# 以下由 scripts/generate-keys.sh 自动生成,也可手动填写 +# UUID= +# REALITY_PRIVATE_KEY= +# REALITY_PUBLIC_KEY= +# REALITY_SHORT_ID= +# HY2_PASSWORD= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dc2c18c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Shell scripts must use LF on Linux +*.sh text eol=lf +.env text eol=lf +.env.example text eol=lf + +# Default: normalize text files +* text=auto diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 41cb357..a57691a 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -144,6 +144,7 @@ systemctl restart sing-box | 问题 | 处理 | |------|------| +| `set: pipefail: invalid option` | Windows 换行符问题,执行:`sed -i 's/\r$//' scripts/*.sh .env` 后重试 | | `dig` 未返回正确 IP | 等待 DNS 生效或检查解析记录 | | acme 证书失败 | 确认 80 端口可访问:`curl -I http://66.hyf2.cc` | | sing-box 启动失败 | `journalctl -u sing-box -n 50` 查看报错 | diff --git a/scripts/generate-keys.sh b/scripts/generate-keys.sh index 6e218e8..17078d4 100644 --- a/scripts/generate-keys.sh +++ b/scripts/generate-keys.sh @@ -1,54 +1,54 @@ -#!/usr/bin/env bash -# 生成 Reality 与 Hysteria2 所需密钥,输出到 stdout 并写入 .env -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(dirname "$SCRIPT_DIR")" -ENV_FILE="${ROOT_DIR}/.env" - -# 依赖 sing-box 生成 reality 密钥对 -if ! command -v sing-box &>/dev/null; then - echo "sing-box 未安装,使用临时下载..." >&2 - TMP="$(mktemp -d)" - ARCH="$(uname -m)" - case "$ARCH" in - x86_64) SB_ARCH="amd64" ;; - aarch64) SB_ARCH="arm64" ;; - *) echo "不支持的架构: $ARCH" >&2; exit 1 ;; - esac - curl -fsSL "https://github.com/SagerNet/sing-box/releases/latest/download/sing-box-1.11.0-linux-${SB_ARCH}.tar.gz" \ - | tar -xz -C "$TMP" --strip-components=1 - SB="$TMP/sing-box" -else - SB="sing-box" -fi - -UUID="$("$SB" generate uuid)" -KEYPAIR="$("$SB" generate reality-keypair)" -PRIVATE_KEY="$(echo "$KEYPAIR" | grep 'PrivateKey:' | awk '{print $2}')" -PUBLIC_KEY="$(echo "$KEYPAIR" | grep 'PublicKey:' | awk '{print $2}')" -SHORT_ID="$("$SB" generate rand --hex 8)" -HY2_PASSWORD="$("$SB" generate rand --base64 32 | tr -d '/+=' | head -c 24)" - -echo "========== 生成的密钥 ==========" -echo "UUID: $UUID" -echo "REALITY_PRIVATE_KEY: $PRIVATE_KEY" -echo "REALITY_PUBLIC_KEY: $PUBLIC_KEY" -echo "REALITY_SHORT_ID: $SHORT_ID" -echo "HY2_PASSWORD: $HY2_PASSWORD" -echo "================================" - -if [[ -f "$ENV_FILE" ]]; then - # 更新或追加 .env 中的密钥字段 - for var in UUID REALITY_PRIVATE_KEY REALITY_PUBLIC_KEY REALITY_SHORT_ID HY2_PASSWORD; do - val="${!var}" - if grep -q "^${var}=" "$ENV_FILE" 2>/dev/null; then - sed -i "s|^${var}=.*|${var}=${val}|" "$ENV_FILE" - else - echo "${var}=${val}" >> "$ENV_FILE" - fi - done - echo "已写入 $ENV_FILE" -else - echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等,再重新运行本脚本" >&2 -fi +#!/usr/bin/env bash +# 生成 Reality 与 Hysteria2 所需密钥,输出到 stdout 并写入 .env +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ENV_FILE="${ROOT_DIR}/.env" + +# 依赖 sing-box 生成 reality 密钥对 +if ! command -v sing-box &>/dev/null; then + echo "sing-box 未安装,使用临时下载..." >&2 + TMP="$(mktemp -d)" + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) SB_ARCH="amd64" ;; + aarch64) SB_ARCH="arm64" ;; + *) echo "不支持的架构: $ARCH" >&2; exit 1 ;; + esac + curl -fsSL "https://github.com/SagerNet/sing-box/releases/latest/download/sing-box-1.11.0-linux-${SB_ARCH}.tar.gz" \ + | tar -xz -C "$TMP" --strip-components=1 + SB="$TMP/sing-box" +else + SB="sing-box" +fi + +UUID="$("$SB" generate uuid)" +KEYPAIR="$("$SB" generate reality-keypair)" +PRIVATE_KEY="$(echo "$KEYPAIR" | grep 'PrivateKey:' | awk '{print $2}')" +PUBLIC_KEY="$(echo "$KEYPAIR" | grep 'PublicKey:' | awk '{print $2}')" +SHORT_ID="$("$SB" generate rand --hex 8)" +HY2_PASSWORD="$("$SB" generate rand --base64 32 | tr -d '/+=' | head -c 24)" + +echo "========== 生成的密钥 ==========" +echo "UUID: $UUID" +echo "REALITY_PRIVATE_KEY: $PRIVATE_KEY" +echo "REALITY_PUBLIC_KEY: $PUBLIC_KEY" +echo "REALITY_SHORT_ID: $SHORT_ID" +echo "HY2_PASSWORD: $HY2_PASSWORD" +echo "================================" + +if [[ -f "$ENV_FILE" ]]; then + # 更新或追加 .env 中的密钥字段 + for var in UUID REALITY_PRIVATE_KEY REALITY_PUBLIC_KEY REALITY_SHORT_ID HY2_PASSWORD; do + val="${!var}" + if grep -q "^${var}=" "$ENV_FILE" 2>/dev/null; then + sed -i "s|^${var}=.*|${var}=${val}|" "$ENV_FILE" + else + echo "${var}=${val}" >> "$ENV_FILE" + fi + done + echo "已写入 $ENV_FILE" +else + echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等,再重新运行本脚本" >&2 +fi diff --git a/scripts/install.sh b/scripts/install.sh index 96076c7..58f3c02 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,171 +1,171 @@ -#!/usr/bin/env bash -# VPS 一键部署:sing-box (Reality + Hysteria2) + Nginx fallback -# 适用:Ubuntu 22.04/24.04、Debian 12 -# 用法:sudo bash scripts/install.sh -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(dirname "$SCRIPT_DIR")" -ENV_FILE="${ROOT_DIR}/.env" - -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -log() { echo -e "${GREEN}[+]${NC} $*"; } -err() { echo -e "${RED}[!]${NC} $*" >&2; exit 1; } - -[[ $EUID -eq 0 ]] || err "请使用 root 运行: sudo bash scripts/install.sh" -[[ -f "$ENV_FILE" ]] || err "缺少 .env 文件,请先: cp .env.example .env 并填写" - -# shellcheck disable=SC1090 -source "$ENV_FILE" - -: "${VPS_IP:?请在 .env 中设置 VPS_IP}" -: "${DOMAIN:?请在 .env 中设置 DOMAIN}" -: "${ACME_EMAIL:?请在 .env 中设置 ACME_EMAIL}" -: "${REALITY_SERVER_NAME:=www.microsoft.com}" - -if [[ -z "${UUID:-}" || -z "${REALITY_PRIVATE_KEY:-}" ]]; then - log "未检测到密钥,运行 generate-keys.sh ..." - bash "$SCRIPT_DIR/generate-keys.sh" - source "$ENV_FILE" -fi - -: "${UUID:?}" -: "${REALITY_PRIVATE_KEY:?}" -: "${REALITY_SHORT_ID:?}" -: "${HY2_PASSWORD:?}" - -ARCH="$(uname -m)" -case "$ARCH" in - x86_64) SB_ARCH="amd64" ;; - aarch64) SB_ARCH="arm64" ;; - *) err "不支持的架构: $ARCH" ;; -esac - -SB_VERSION="1.11.0" -SB_URL="https://github.com/SagerNet/sing-box/releases/download/v${SB_VERSION}/sing-box-${SB_VERSION}-linux-${SB_ARCH}.tar.gz" - -log "更新系统包 ..." -export DEBIAN_FRONTEND=noninteractive -apt-get update -qq -apt-get install -y -qq curl wget nginx ufw ca-certificates - -log "安装 sing-box ${SB_VERSION} ..." -TMP="$(mktemp -d)" -curl -fsSL "$SB_URL" | tar -xz -C "$TMP" --strip-components=1 -install -m 755 "$TMP/sing-box" /usr/local/bin/sing-box -rm -rf "$TMP" - -log "配置防火墙 ..." -ufw --force reset -ufw default deny incoming -ufw default allow outgoing -ufw allow 22/tcp comment 'SSH' -ufw allow 443/tcp comment 'Reality' -ufw allow 8443/udp comment 'Hysteria2' -ufw --force enable - -log "部署 Nginx fallback 站点 ..." -mkdir -p /var/www/fallback -cp "$ROOT_DIR/server/nginx/index.html" /var/www/fallback/ -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 -nginx -t && systemctl enable nginx && systemctl restart nginx - -log "申请 TLS 证书 (Let's Encrypt) ..." -mkdir -p /etc/sing-box/certs -if [[ ! -f /root/.acme.sh/acme.sh ]]; then - curl -fsSL https://get.acme.sh | sh -s email="$ACME_EMAIL" -fi -# shellcheck disable=SC1091 -source /root/.acme.sh/acme.sh.env || true - -# 确保域名已解析到本机 -CURRENT_IP="$(curl -4 -fsSL ifconfig.me 2>/dev/null || curl -4 -fsSL ip.sb)" -if [[ "$CURRENT_IP" != "$VPS_IP" ]]; then - err "域名 $DOMAIN 需先解析到 VPS IP ($VPS_IP),当前 VPS 出口 IP 为 $CURRENT_IP" -fi - -/root/.acme.sh/acme.sh --set-default-ca --server letsencrypt -/root/.acme.sh/acme.sh --issue -d "$DOMAIN" --nginx --force -/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" - -log "生成 sing-box 服务端配置 ..." -mkdir -p /etc/sing-box -sed -e "s|\${UUID}|${UUID}|g" \ - -e "s|\${REALITY_SERVER_NAME}|${REALITY_SERVER_NAME}|g" \ - -e "s|\${REALITY_PRIVATE_KEY}|${REALITY_PRIVATE_KEY}|g" \ - -e "s|\${REALITY_SHORT_ID}|${REALITY_SHORT_ID}|g" \ - -e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \ - -e "s|\${DOMAIN}|${DOMAIN}|g" \ - "$ROOT_DIR/server/sing-box.json.template" > /etc/sing-box/config.json - -sing-box check -c /etc/sing-box/config.json - -log "创建 systemd 服务 ..." -cat > /etc/systemd/system/sing-box.service <<'UNIT' -[Unit] -Description=sing-box service -After=network-online.target nginx.service -Wants=network-online.target - -[Service] -Type=simple -ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json -Restart=on-failure -RestartSec=5 -LimitNOFILE=1048576 - -[Install] -WantedBy=multi-user.target -UNIT - -systemctl daemon-reload -systemctl enable sing-box -systemctl restart sing-box - -log "生成客户端配置 ..." -CLIENT_DIR="${ROOT_DIR}/client/generated" -mkdir -p "$CLIENT_DIR" -: "${REALITY_PUBLIC_KEY:?请在 .env 中设置 REALITY_PUBLIC_KEY(运行 generate-keys.sh 可自动生成)}" - -sed -e "s|\${VPS_IP}|${VPS_IP}|g" \ - -e "s|\${DOMAIN}|${DOMAIN}|g" \ - -e "s|\${UUID}|${UUID}|g" \ - -e "s|\${REALITY_SERVER_NAME}|${REALITY_SERVER_NAME}|g" \ - -e "s|\${REALITY_PUBLIC_KEY}|${REALITY_PUBLIC_KEY}|g" \ - -e "s|\${REALITY_SHORT_ID}|${REALITY_SHORT_ID}|g" \ - -e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \ - "$ROOT_DIR/client/sing-box-client.json.template" > "$CLIENT_DIR/sing-box-client.json" - -# 生成分享链接 -cat > "$CLIENT_DIR/share-links.txt" <&2; exit 1; } + +[[ $EUID -eq 0 ]] || err "请使用 root 运行: sudo bash scripts/install.sh" +[[ -f "$ENV_FILE" ]] || err "缺少 .env 文件,请先: cp .env.example .env 并填写" + +# shellcheck disable=SC1090 +source "$ENV_FILE" + +: "${VPS_IP:?请在 .env 中设置 VPS_IP}" +: "${DOMAIN:?请在 .env 中设置 DOMAIN}" +: "${ACME_EMAIL:?请在 .env 中设置 ACME_EMAIL}" +: "${REALITY_SERVER_NAME:=www.microsoft.com}" + +if [[ -z "${UUID:-}" || -z "${REALITY_PRIVATE_KEY:-}" ]]; then + log "未检测到密钥,运行 generate-keys.sh ..." + bash "$SCRIPT_DIR/generate-keys.sh" + source "$ENV_FILE" +fi + +: "${UUID:?}" +: "${REALITY_PRIVATE_KEY:?}" +: "${REALITY_SHORT_ID:?}" +: "${HY2_PASSWORD:?}" + +ARCH="$(uname -m)" +case "$ARCH" in + x86_64) SB_ARCH="amd64" ;; + aarch64) SB_ARCH="arm64" ;; + *) err "不支持的架构: $ARCH" ;; +esac + +SB_VERSION="1.11.0" +SB_URL="https://github.com/SagerNet/sing-box/releases/download/v${SB_VERSION}/sing-box-${SB_VERSION}-linux-${SB_ARCH}.tar.gz" + +log "更新系统包 ..." +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y -qq curl wget nginx ufw ca-certificates + +log "安装 sing-box ${SB_VERSION} ..." +TMP="$(mktemp -d)" +curl -fsSL "$SB_URL" | tar -xz -C "$TMP" --strip-components=1 +install -m 755 "$TMP/sing-box" /usr/local/bin/sing-box +rm -rf "$TMP" + +log "配置防火墙 ..." +ufw --force reset +ufw default deny incoming +ufw default allow outgoing +ufw allow 22/tcp comment 'SSH' +ufw allow 443/tcp comment 'Reality' +ufw allow 8443/udp comment 'Hysteria2' +ufw --force enable + +log "部署 Nginx fallback 站点 ..." +mkdir -p /var/www/fallback +cp "$ROOT_DIR/server/nginx/index.html" /var/www/fallback/ +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 +nginx -t && systemctl enable nginx && systemctl restart nginx + +log "申请 TLS 证书 (Let's Encrypt) ..." +mkdir -p /etc/sing-box/certs +if [[ ! -f /root/.acme.sh/acme.sh ]]; then + curl -fsSL https://get.acme.sh | sh -s email="$ACME_EMAIL" +fi +# shellcheck disable=SC1091 +source /root/.acme.sh/acme.sh.env || true + +# 确保域名已解析到本机 +CURRENT_IP="$(curl -4 -fsSL ifconfig.me 2>/dev/null || curl -4 -fsSL ip.sb)" +if [[ "$CURRENT_IP" != "$VPS_IP" ]]; then + err "域名 $DOMAIN 需先解析到 VPS IP ($VPS_IP),当前 VPS 出口 IP 为 $CURRENT_IP" +fi + +/root/.acme.sh/acme.sh --set-default-ca --server letsencrypt +/root/.acme.sh/acme.sh --issue -d "$DOMAIN" --nginx --force +/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" + +log "生成 sing-box 服务端配置 ..." +mkdir -p /etc/sing-box +sed -e "s|\${UUID}|${UUID}|g" \ + -e "s|\${REALITY_SERVER_NAME}|${REALITY_SERVER_NAME}|g" \ + -e "s|\${REALITY_PRIVATE_KEY}|${REALITY_PRIVATE_KEY}|g" \ + -e "s|\${REALITY_SHORT_ID}|${REALITY_SHORT_ID}|g" \ + -e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \ + -e "s|\${DOMAIN}|${DOMAIN}|g" \ + "$ROOT_DIR/server/sing-box.json.template" > /etc/sing-box/config.json + +sing-box check -c /etc/sing-box/config.json + +log "创建 systemd 服务 ..." +cat > /etc/systemd/system/sing-box.service <<'UNIT' +[Unit] +Description=sing-box service +After=network-online.target nginx.service +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json +Restart=on-failure +RestartSec=5 +LimitNOFILE=1048576 + +[Install] +WantedBy=multi-user.target +UNIT + +systemctl daemon-reload +systemctl enable sing-box +systemctl restart sing-box + +log "生成客户端配置 ..." +CLIENT_DIR="${ROOT_DIR}/client/generated" +mkdir -p "$CLIENT_DIR" +: "${REALITY_PUBLIC_KEY:?请在 .env 中设置 REALITY_PUBLIC_KEY(运行 generate-keys.sh 可自动生成)}" + +sed -e "s|\${VPS_IP}|${VPS_IP}|g" \ + -e "s|\${DOMAIN}|${DOMAIN}|g" \ + -e "s|\${UUID}|${UUID}|g" \ + -e "s|\${REALITY_SERVER_NAME}|${REALITY_SERVER_NAME}|g" \ + -e "s|\${REALITY_PUBLIC_KEY}|${REALITY_PUBLIC_KEY}|g" \ + -e "s|\${REALITY_SHORT_ID}|${REALITY_SHORT_ID}|g" \ + -e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \ + "$ROOT_DIR/client/sing-box-client.json.template" > "$CLIENT_DIR/sing-box-client.json" + +# 生成分享链接 +cat > "$CLIENT_DIR/share-links.txt" < "$OUT_DIR/sing-box-client.json" - -cat > "$OUT_DIR/share-links.txt" < "$OUT_DIR/sing-box-client.json" + +cat > "$OUT_DIR/share-links.txt" <