Appearance
一键部署脚本(hy2、vless、anytls)
环境变量文件 .env
shell
CF_TOKEN="onXxS9ffo7N3OP0E2wKdJPKOus-T4bp5yZNDY3gV"
CF_ZONE_ID="690e7331d42c074ac3fb5ed586e003b0"
DOMAIN=ilym.top
XRAY_PREFIX=flare
ANYTLS_PREFIX=anytest
XRAY_KEY="7898568a-d1bc-4fb5-ad14-515e049ac4b6"
XRAY_PRIVATE_KEY="uHcaFeAxt93EriyUbHY3hpEq-tTQ0g7NmdBSRFmv0lM"
XRAY_XHTTP_PATH="/wHR7itXI"
ANYTLS_NAME="ilymee"
ANYTLS_PASSWORD="6309e3b1-b3cb-43f4-9dbf-39cdb1a0a276"
HY2_USER="youarenotmydalinganymor407de"
HY2_PASSWORD="78cf7dfa-_ebf@c5f2-407d-@efbd=e6d-b94edfe649c08b9f=wee?dd65_79e5d4"
HY2_MASQUERADE="https://demo.cloudreve.org"docker-compose.yml
shell
services:
xray:
image: ghcr.io/xtls/xray-core:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
container_name: nginx-xray
restart: always
user: "0:0"
environment:
- TZ=Asia/Shanghai
volumes:
- /home/xray/log:/tmp/xray
- /home/nginx/cert:/etc/nginx/cert
configs:
- source: xray_config
target: /home/xray/xconfig.json
network_mode: host
command: run -c /home/xray/xconfig.json
sing-box:
image: ghcr.io/sagernet/sing-box
labels:
- "com.centurylinklabs.watchtower.enable=true"
container_name: nginx-sing-box
restart: always
environment:
- TZ=Asia/Shanghai
volumes:
- /home/sing-box/log:/etc/sing-box/log
- /home/nginx/cert:/etc/nginx/cert
configs:
- source: singbox_config
target: /etc/sing-box/config.json
network_mode: host
command: -D /var/lib/sing-box -C /etc/sing-box/ run
nginx:
image: nginx:1.27.1
container_name: nginx
restart: always
network_mode: host
environment:
- TZ=Asia/Shanghai
volumes:
- /home/nginx/log:/var/log/nginx
- /home/nginx/html:/usr/share/nginx/html
- /home/nginx/cert:/etc/nginx/cert
configs:
- source: nginx_conf
target: /etc/nginx/nginx.conf
- source: nginx_default_conf
target: /etc/nginx/conf.d/default.conf
hysteria:
image: tobyxdd/hysteria:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
container_name: nginx-hysteria
restart: always
environment:
- TZ=Asia/Shanghai
- HYSTERIA_LOG_LEVEL=warn
volumes:
- /home/hysteria/log:/home/hysteria/log
- /home/nginx/cert:/etc/nginx/cert
configs:
- source: hysteria_yaml
target: /etc/hysteria.yaml
- source: rules_txt
target: /home/hysteria/acl/rules.txt
entrypoint: ["/bin/sh", "-c"]
command: ["hysteria server -c /etc/hysteria.yaml 2>&1 | tee -a /home/hysteria/log/hysteria.log"]
network_mode: host
watchtower:
image: containrrr/watchtower
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_POLL_INTERVAL=86400
- DOCKER_API_VERSION=1.44
acme-worker:
image: neilpang/acme.sh:latest
container_name: acme-worker
restart: always
environment:
- CF_Token=${CF_TOKEN}
- CF_Zone_ID=${CF_ZONE_ID}
- DOMAIN=${DOMAIN}
volumes:
- /home/nginx/cert:/home/nginx/cert
- /var/run/docker.sock:/var/run/docker.sock
- acme-data:/acme.sh
configs:
- source: acme_init_script
target: /run_acme.sh
command: sh /run_acme.sh
volumes:
acme-data:
configs:
xray_config:
content: |
{
"log": {
"access": "/tmp/xray/access.log",
"error": "/tmp/xray/error.log",
"loglevel": "warn"
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"protocol": [
"bittorrent"
],
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"geoip:cn"
],
"outboundTag": "block"
},
{
"type": "field",
"domain": [
"geosite:category-ads-all"
],
"outboundTag": "block"
}
]
},
"inbounds": [
{
"listen": "0.0.0.0",
"port": 1443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "${XRAY_KEY}",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none",
"fallbacks": [
{
"dest": 1880
}
]
},
"streamSettings": {
"network": "raw",
"security": "tls",
"tlsSettings": {
"rejectUnknownSni": true,
"minVersion": "1.2",
"certificates": [
{
"ocspStapling": 3600,
"certificateFile": "/etc/nginx/cert/cert.pem",
"keyFile": "/etc/nginx/cert/key.pem"
}
]
},
"rawSettings": {
"acceptProxyProtocol": true
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
},
{
"listen": "0.0.0.0",
"port": 3443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "${XRAY_KEY}",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none",
"fallbacks": [
{
"dest": 1880
}
]
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"target": 8443,
"xver": 1,
"show": true,
"serverNames": [
"flare.${DOMAIN}"
],
"privateKey": "${XRAY_PRIVATE_KEY}",
"shortIds": [
""
]
},
"rawSettings": {
"acceptProxyProtocol": true
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
},
{
"listen": "0.0.0.0",
"port": 2024,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "${XRAY_KEY}"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"xhttpSettings": {
"path": "${XRAY_XHTTP_PATH}"
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
]
}
singbox_config:
content: |
{
"log": {
"disabled": false,
"level": "warn",
"output": "/etc/sing-box/log/sing-box.log",
"timestamp": true
},
"inbounds": [
{
"type": "anytls",
"tag": "anytls-in",
"listen": "::",
"listen_port": 4443,
"users": [
{
"name": "${ANYTLS_NAME}",
"password": "${ANYTLS_PASSWORD}"
}
],
"tls": {
"enabled": true,
"server_name": "${ANYTLS_PREFIX}.${DOMAIN}",
"certificate_path": "/etc/nginx/cert/cert.pem",
"key_path": "/etc/nginx/cert/key.pem"
}
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"ip_is_private": true,
"outbound": "block"
}
],
"final": "direct"
},
"outbounds": [
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
}
]
}
nginx_conf:
content: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 注意这里的 Nginx 变量全部改成了 $$
map $$http_x_forwarded_for $$clientRealIp {
"" $$remote_addr;
"~*(?P<firstAddr>([0-9a-f]{0,4}:){1,7}[0-9a-f]{1,4}|([0-9]{1,3}\.){3}[0-9]{1,3})$$" $$firstAddr;
}
log_format main '$$clientRealIp $$remote_addr $$remote_user [$$time_local] "$$request" '
'$$status $$body_bytes_sent "$$http_referer" '
'"$$http_user_agent" $$http_x_forwarded_for '
'"$$upstream_addr" "$$upstream_status" "$$upstream_response_time" "$$request_time" ';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
include /etc/nginx/conf.d/*.conf;
}
stream {
error_log /var/log/nginx/stream_error.log warn;
# Nginx 变量用 $$
map $$ssl_preread_server_name $$name {
# 自定义环境变量保持单 $
${XRAY_PREFIX}.${DOMAIN} xray_backend;
${ANYTLS_PREFIX}.${DOMAIN} sing_box_wrapper;
default web_backend;
}
upstream sing_box_wrapper {
server 127.0.0.1:14443;
}
server {
listen 127.0.0.1:14443 proxy_protocol;
proxy_pass 127.0.0.1:4443;
}
upstream web_backend {
server 127.0.0.1:8443;
}
upstream hy2_backend {
server 127.0.0.1:2443;
}
upstream hy2_backend_80 {
server 127.0.0.1:446;
}
upstream xray_backend {
server 127.0.0.1:1443;
}
server {
listen 443;
listen [::]:443;
ssl_preread on;
error_log /var/log/nginx/stream_443.log warn;
# Nginx 变量用 $$
proxy_pass $$name;
proxy_protocol on;
}
server {
listen 443 udp reuseport;
listen [::]:443 udp reuseport;
proxy_pass hy2_backend;
proxy_timeout 20s;
}
}
nginx_default_conf:
content: |
server {
error_log /var/log/nginx/error_80.log warn;
listen 80;
listen [::]:80;
# Nginx 变量用 $$
return 301 https://$$host$$request_uri;
}
server {
listen 127.0.0.1:8443 quic reuseport;
listen 127.0.0.1:8443 ssl proxy_protocol reuseport;
http2 on;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
ssl_certificate /etc/nginx/cert/cert.pem;
ssl_certificate_key /etc/nginx/cert/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305;
ssl_ecdh_curve secp521r1:secp384r1:secp256r1:x25519;
# 自定义环境变量保持单 $
location ${XRAY_XHTTP_PATH} {
grpc_pass grpc://127.0.0.1:2024;
# Nginx 变量用 $$
grpc_set_header Host $$host;
grpc_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
}
location = /favicon.ico {
access_log off;
log_not_found off;
return 204;
}
location / {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Alt-Svc 'h3=":443"; ma=86400';
# Nginx 变量用 $$
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header Host $$host;
proxy_pass http://127.0.0.1:1880;
proxy_http_version 1.1;
proxy_set_header Upgrade $$http_upgrade;
proxy_set_header Connection "upgrade";
}
}
hysteria_yaml:
content: |
listen: :2443
tls:
cert: /etc/nginx/cert/cert.pem
key: /etc/nginx/cert/key.pem
auth:
type: userpass
userpass:
${HY2_USER}: ${HY2_PASSWORD}
resolver:
type: tcp
tcp:
addr: 8.8.8.8:53
timeout: 4s
udp:
addr: 1.1.1.1:53
timeout: 4s
tls:
addr: 1.1.1.1:853
timeout: 10s
sni: cloudflare-dns.com
insecure: false
https:
addr: 1.1.1.1:443
timeout: 10s
sni: cloudflare-dns.com
insecure: false
masquerade:
type: proxy
proxy:
url: ${HY2_MASQUERADE}
rewriteHost: true
listenHTTP: :1880
listenHTTPS: :5443
acl:
file: /home/hysteria/acl/rules.txt
outbounds:
- name: warp
type: socks5
socks5:
addr: 127.0.0.1:40000
- name: drt
type: direct
sniff:
enable: true
timeout: 2s
rewriteDomain: false
tcpPorts: 443,19999-29999
udpPorts: all
speedTest: true
rules_txt:
content: |
# 直连所有其他地址
drt(all)
acme_init_script:
content: |
#!/bin/sh
apk add --no-cache curl openssl
# 注意:在 docker-compose.yml 中的 Shell 变量和命令替换,必须使用 $$ 转义
IS_TEMP=$$(openssl x509 -in /home/nginx/cert/cert.pem -noout -issuer 2>/dev/null | grep "temp.cert")
if [ -n "$$IS_TEMP" ] || [ ! -f "/home/nginx/cert/cert.pem" ]; then
echo "检测到临时证书或无证书,开始通过 Cloudflare DNS 申请真实证书..."
# 这里的 ${ANYTLS_PREFIX} 和 ${DOMAIN} 保留单 $,因为它们确实是需要 Docker Compose 注入的环境变量
acme.sh --issue --dns dns_cf -d ${DOMAIN} -d *.${DOMAIN} --server letsencrypt
echo "安装真实证书,并重启所有依赖证书的服务..."
acme.sh --install-cert -d ${DOMAIN} \
--key-file /home/nginx/cert/key.pem \
--fullchain-file /home/nginx/cert/cert.pem \
--reloadcmd "curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/nginx/restart && \
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/nginx-xray/restart && \
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/nginx-sing-box/restart && \
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/nginx-hysteria/restart"
else
echo "真实证书已存在,跳过初始申请流程。"
fi
exec crond -f一键运行脚本
shell
#!/bin/bash
# 开启严格模式:遇到错误立即退出
set -e
# 1. 检查是否为 root 用户
if [ "$EUID" -ne 0 ]; then
echo "❌ 权限不足:请使用 root 用户或加上 sudo 执行此脚本。"
exit 1
fi
echo "======================================================="
echo " Docker 环境一键安装 & 代理/Web 服务部署脚本 "
echo "======================================================="
# 2. 检查并安装 Docker
if ! command -v docker &> /dev/null; then
echo "未检测到 Docker,开始自动安装..."
# 使用官方安装脚本,并指定阿里云镜像源加速安装
curl -fsSL https://get.docker.com | bash -s docker #--mirror Aliyun
echo "启动 Docker 服务并设置开机自启..."
systemctl enable docker
systemctl start docker
echo "✅ Docker 安装成功!"
else
echo "✅ 经检测,Docker 已安装,跳过该步骤。"
fi
# 3. 检查 Docker Compose (现在的 Docker 官方推荐使用 docker compose 插件,安装脚本已默认包含)
DOCKER_COMPOSE_CMD=""
if docker compose version &> /dev/null; then
DOCKER_COMPOSE_CMD="docker compose"
echo "✅ 检测到新版 Docker Compose 插件 ($DOCKER_COMPOSE_CMD)。"
elif docker-compose --version &> /dev/null; then
DOCKER_COMPOSE_CMD="docker-compose"
echo "✅ 检测到独立版 Docker Compose ($DOCKER_COMPOSE_CMD)。"
else
echo "❌ 找不到 Docker Compose 组件!尝试自动安装插件..."
# 尝试补充安装插件版
if command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y docker-compose-plugin
DOCKER_COMPOSE_CMD="docker compose"
elif command -v yum &> /dev/null; then
yum install -y docker-compose-plugin
DOCKER_COMPOSE_CMD="docker compose"
else
echo "❌ 无法自动安装,请手动安装 docker-compose-plugin。"
exit 1
fi
fi
echo "-------------------------------------------------------"
# 4. 检查前置配置文件
if [ ! -f "docker-compose.yml" ]; then
echo "❌ 错误:当前目录下未找到 docker-compose.yml!请确保脚本与配置文件在同一目录。"
exit 1
fi
if [ ! -f ".env" ]; then
echo "⚠️ 警告:当前目录下未找到 .env 文件!"
echo "ACME 申请证书需要 Cloudflare 凭证。如果你已经把变量硬编码到了 compose 文件中,可以忽略此警告。"
read -p "是否继续部署?(y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# ================= 新增部分:预生成临时证书占位 =================
echo "-------------------------------------------------------"
CERT_DIR="/home/nginx/cert"
mkdir -p "$CERT_DIR"
if [ ! -f "$CERT_DIR/cert.pem" ]; then
echo "🔧 生成临时自签名证书,防止代理服务首次启动时因找不到证书而崩溃退出..."
# 使用 openssl 生成一个有效期 1 天的临时证书,颁发者标识为 "temp.cert"
openssl req -x509 -nodes -newkey rsa:2048 -days 1 \
-keyout "$CERT_DIR/key.pem" \
-out "$CERT_DIR/cert.pem" \
-subj "/CN=temp.cert" >/dev/null 2>&1
echo "✅ 临时证书生成完毕。"
fi
# ===============================================================
# 5. 执行部署操作
echo "🚀 开始拉取镜像并部署服务..."
# 使用动态识别到的命令执行
$DOCKER_COMPOSE_CMD up -d
echo "======================================================="
echo "🎉 部署完成!"
echo "👉 你可以使用以下命令查看运行状态:"
echo " $DOCKER_COMPOSE_CMD ps"
echo "👉 查看证书申请进度或相关日志:"
echo " $DOCKER_COMPOSE_CMD -f acme-worker"
echo "======================================================="