Add HAProxy TLS routing for HK CMI
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Hong Kong CMI LiteLLM Vertex AI Relay
|
||||
|
||||
- 仅通过 Hong Kong CMI 节点的 Tailscale IPv4 地址监听;`LISTEN_IP` 不要填写公网地址或 `0.0.0.0`。
|
||||
- ADC、LiteLLM key 仅存放于 Arcane `.env`,不得提交 Git。
|
||||
- 默认监听端口为 `1049`;需要修改时设置 `LITELLM_PORT`。
|
||||
- 后续 Vertex Anthropic 模型在 `config.yaml` 增加独立模型项。
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
adc-init:
|
||||
image: alpine:3.24
|
||||
restart: "no"
|
||||
environment:
|
||||
GOOGLE_ADC_B64: ${GOOGLE_ADC_B64}
|
||||
volumes:
|
||||
- google-adc:/credentials
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
umask 077
|
||||
printf '%s' "$${GOOGLE_ADC_B64}" | base64 -d > /credentials/application_default_credentials.json
|
||||
|
||||
litellm:
|
||||
image: ghcr.io/berriai/litellm:main-stable
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
adc-init:
|
||||
condition: service_completed_successfully
|
||||
network_mode: host
|
||||
environment:
|
||||
GOOGLE_APPLICATION_CREDENTIALS: /credentials/application_default_credentials.json
|
||||
VERTEXAI_PROJECT: ${VERTEXAI_PROJECT}
|
||||
VERTEXAI_LOCATION: ${VERTEXAI_LOCATION:-global}
|
||||
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
|
||||
volumes:
|
||||
- ./config.yaml:/app/config.yaml:ro
|
||||
- google-adc:/credentials:ro
|
||||
command: ["--config", "/app/config.yaml", "--host", "${LISTEN_IP:-127.0.0.1}", "--port", "${LITELLM_PORT:-1049}"]
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 1m
|
||||
max-file: "2"
|
||||
|
||||
volumes:
|
||||
google-adc:
|
||||
@@ -0,0 +1,16 @@
|
||||
# Hong Kong CMI node model routing.
|
||||
model_list:
|
||||
- model_name: gemini-3.6-flash
|
||||
litellm_params:
|
||||
model: vertex_ai/gemini-3.6-flash
|
||||
vertex_project: os.environ/VERTEXAI_PROJECT
|
||||
vertex_location: os.environ/VERTEXAI_LOCATION
|
||||
|
||||
- model_name: gemini-3.5-flash-lite
|
||||
litellm_params:
|
||||
model: vertex_ai/gemini-3.5-flash-lite
|
||||
vertex_project: os.environ/VERTEXAI_PROJECT
|
||||
vertex_location: os.environ/VERTEXAI_LOCATION
|
||||
|
||||
general_settings:
|
||||
master_key: os.environ/LITELLM_MASTER_KEY
|
||||
@@ -0,0 +1,51 @@
|
||||
services:
|
||||
config-render:
|
||||
image: alpine:3.24
|
||||
environment:
|
||||
DOMAIN: ${DOMAIN}
|
||||
ANYTLS_DOMAIN: ${ANYTLS_DOMAIN}
|
||||
ACME_EMAIL: ${ACME_EMAIL}
|
||||
LISTEN_PORT: ${LISTEN_PORT}
|
||||
USER1_PASSWORD: ${USER1_PASSWORD}
|
||||
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
|
||||
ANYTLS_PORT: ${ANYTLS_PORT}
|
||||
ANYTLS_PASSWORD: ${ANYTLS_PASSWORD}
|
||||
HY2_PASSWORD: ${HY2_PASSWORD}
|
||||
HY2_OBFS_PASSWORD: ${HY2_OBFS_PASSWORD}
|
||||
HY2_PORT: ${HY2_PORT}
|
||||
SNELL_V4_PORT: ${SNELL_V4_PORT}
|
||||
SNELL_V4_PSK: ${SNELL_V4_PSK}
|
||||
SNELL_V6_PORT: ${SNELL_V6_PORT}
|
||||
SNELL_V6_PSK: ${SNELL_V6_PSK}
|
||||
volumes:
|
||||
- ./etc/sing-box/config.json.template:/template/config.json.template:ro
|
||||
- sing-box-config:/output
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
apk add --no-cache gettext
|
||||
envsubst < /template/config.json.template > /output/config.json
|
||||
chmod 600 /output/config.json
|
||||
restart: "no"
|
||||
|
||||
sing-box:
|
||||
image: ghcr.io/sagernet/sing-box:v1.14.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
config-render:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- sing-box-config:/etc/sing-box:ro
|
||||
- sing-box-data:/var/lib/sing-box
|
||||
command: -D /var/lib/sing-box -C /etc/sing-box/ run
|
||||
network_mode: host
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 200k
|
||||
max-file: "2"
|
||||
|
||||
volumes:
|
||||
sing-box-config:
|
||||
sing-box-data:
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"certificate_providers": [
|
||||
{
|
||||
"type": "acme",
|
||||
"tag": "public-cert",
|
||||
"domain": [
|
||||
"${DOMAIN}",
|
||||
"${ANYTLS_DOMAIN}"
|
||||
],
|
||||
"email": "${ACME_EMAIL}",
|
||||
"dns01_challenge": {
|
||||
"provider": "cloudflare",
|
||||
"api_token": "${CLOUDFLARE_API_TOKEN}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "trojan",
|
||||
"tag": "trojan-in",
|
||||
"listen": "::",
|
||||
"listen_port": ${LISTEN_PORT},
|
||||
"users": [
|
||||
{
|
||||
"name": "user1",
|
||||
"password": "${USER1_PASSWORD}"
|
||||
}
|
||||
],
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "${DOMAIN}",
|
||||
"certificate_provider": "public-cert"
|
||||
},
|
||||
"multiplex": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "anytls",
|
||||
"tag": "anytls-in",
|
||||
"listen": "::",
|
||||
"listen_port": ${ANYTLS_PORT},
|
||||
"users": [
|
||||
{
|
||||
"name": "user1",
|
||||
"password": "${ANYTLS_PASSWORD}"
|
||||
}
|
||||
],
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "${ANYTLS_DOMAIN}",
|
||||
"certificate_provider": "public-cert"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "hysteria2-in",
|
||||
"listen": "0.0.0.0",
|
||||
"listen_port": ${HY2_PORT},
|
||||
"obfs": {
|
||||
"type": "salamander",
|
||||
"password": "${HY2_OBFS_PASSWORD}"
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"name": "user1",
|
||||
"password": "${HY2_PASSWORD}"
|
||||
}
|
||||
],
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "${DOMAIN}",
|
||||
"alpn": [
|
||||
"h3"
|
||||
],
|
||||
"certificate_provider": "public-cert"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "snell",
|
||||
"tag": "snell-v4-compatible-in",
|
||||
"listen": "::",
|
||||
"listen_port": ${SNELL_V4_PORT},
|
||||
"version": 5,
|
||||
"psk": "${SNELL_V4_PSK}",
|
||||
"obfs_mode": "none"
|
||||
},
|
||||
{
|
||||
"type": "snell",
|
||||
"tag": "snell-v6-in",
|
||||
"listen": "::",
|
||||
"listen_port": ${SNELL_V6_PORT},
|
||||
"version": 6,
|
||||
"psk": "${SNELL_V6_PSK}",
|
||||
"mode": "default"
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"disabled": false,
|
||||
"level": "info",
|
||||
"timestamp": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM alpine:3.24 AS downloader
|
||||
|
||||
ARG SNELL_VERSION=5.0.1
|
||||
ARG SNELL_ZIP_SHA256=9bea1c2b9e35b73b31634856c04d18c393072b9e5dcde6a32781d8b8f908c539
|
||||
|
||||
RUN apk add --no-cache curl unzip \
|
||||
&& curl -fsSL "https://dl.nssurge.com/snell/snell-server-v${SNELL_VERSION}-linux-amd64.zip" -o /tmp/snell.zip \
|
||||
&& echo "${SNELL_ZIP_SHA256} /tmp/snell.zip" | sha256sum -c - \
|
||||
&& unzip -p /tmp/snell.zip snell-server > /snell-server \
|
||||
&& chmod 755 /snell-server
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates libstdc++6 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=downloader /snell-server /usr/local/bin/snell-server
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod 755 /usr/local/bin/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
@@ -0,0 +1,5 @@
|
||||
# Hong Kong CMI Snell Server (rollback only)
|
||||
|
||||
- 当前锁定 Snell Server 5.0.1,由本目录 Dockerfile 从官方包构建并校验 SHA-256。
|
||||
- 客户端因禁用 QUIC 显示 `version=4` 属现有兼容用法,不改变服务端 5.0.1 部署。
|
||||
- Snell 6 与 5 不兼容。迁移时新增独立 `snell-v6` 服务、端口和 PSK,与 v5 并存;客户端迁移完成后再停 v5。
|
||||
@@ -0,0 +1,20 @@
|
||||
# Retained temporarily as a rollback target during the sing-box migration.
|
||||
services:
|
||||
snell-v5:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
SNELL_VERSION: 5.0.1
|
||||
SNELL_ZIP_SHA256: 9bea1c2b9e35b73b31634856c04d18c393072b9e5dcde6a32781d8b8f908c539
|
||||
image: local/snell-server:5.0.1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${HOST_PORT:-47854}:9102/tcp"
|
||||
environment:
|
||||
PSK: ${PSK}
|
||||
PORT: 9102
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 200k
|
||||
max-file: "2"
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Hong Kong CMI rollback-only Snell entrypoint.
|
||||
set -eu
|
||||
|
||||
: "${PSK:?PSK is required}"
|
||||
: "${PORT:?PORT is required}"
|
||||
|
||||
case "$PORT" in
|
||||
*[!0-9]*|'') echo "PORT must be numeric" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
umask 077
|
||||
cat > /tmp/snell-server.conf <<EOF
|
||||
[snell-server]
|
||||
listen = :::${PORT}
|
||||
psk = ${PSK}
|
||||
EOF
|
||||
|
||||
exec /usr/local/bin/snell-server -c /tmp/snell-server.conf
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
servers {
|
||||
protocols h1 h2
|
||||
}
|
||||
}
|
||||
|
||||
# Hong Kong CMI web endpoints. MeTube is intentionally not routed here.
|
||||
rules.kais.cloud {
|
||||
root * /srv/web
|
||||
reverse_proxy filebrowser:80
|
||||
}
|
||||
|
||||
dav.kais.cloud {
|
||||
root * /srv/web
|
||||
reverse_proxy webdav:80
|
||||
}
|
||||
|
||||
share.kais.cloud {
|
||||
root * /srv/share
|
||||
encode zstd gzip
|
||||
file_server browse
|
||||
}
|
||||
|
||||
api.opfo.xyz {
|
||||
reverse_proxy 100.100.130.66:1049
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Hong Kong CMI Web Services
|
||||
|
||||
- Arcane 项目名:`web-managed`
|
||||
- 复用旧项目 `/opt/stacks/web` 下的持久化数据。
|
||||
- Secret 仅存 Arcane `.env`:`WEBDAV_PASSWORD`。
|
||||
- HAProxy 使用 SNI passthrough 接管 TCP 443:`cmi-tls-a7f29c.opfo.xyz` 转发至 AnyTLS,`conn.opfo.xyz` 转发至 Trojan,其余连接转发至 Caddy。
|
||||
- Caddy HTTPS 仅发布在宿主机 `127.0.0.1:8443`,并关闭 HTTP/3,将 UDP 443 留给 Hysteria2。
|
||||
- `metacubexd` 与 `metube` 不迁移。
|
||||
@@ -0,0 +1,49 @@
|
||||
services:
|
||||
haproxy:
|
||||
image: haproxy:3.4.4-alpine3.24
|
||||
container_name: haproxy
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
- caddy
|
||||
volumes:
|
||||
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 200k
|
||||
max-file: "2"
|
||||
|
||||
caddy:
|
||||
image: caddy:2.11.4
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "127.0.0.1:8443:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- /opt/stacks/web/caddy/site:/srv
|
||||
- /opt/stacks/web/caddy/data:/data
|
||||
- /opt/stacks/web/caddy/config:/config
|
||||
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:s6
|
||||
container_name: filebrowser
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PUID: 65534
|
||||
PGID: 65534
|
||||
volumes:
|
||||
- /opt/stacks/web/filebrowser/database:/database
|
||||
- /opt/stacks/web/filebrowser/config:/config
|
||||
- /opt/stacks/web/filebrowser/storage:/srv/web
|
||||
|
||||
webdav:
|
||||
image: jinlife/docker-cwebdav
|
||||
restart: always
|
||||
environment:
|
||||
USERNAME: dav
|
||||
PASSWORD: ${WEBDAV_PASSWORD}
|
||||
volumes:
|
||||
- /opt/stacks/web/dav:/media
|
||||
@@ -0,0 +1,32 @@
|
||||
global
|
||||
log stdout format raw local0
|
||||
maxconn 4096
|
||||
|
||||
defaults
|
||||
log global
|
||||
mode tcp
|
||||
option tcplog
|
||||
timeout connect 5s
|
||||
timeout client 24h
|
||||
timeout server 24h
|
||||
|
||||
frontend tls_443
|
||||
bind 0.0.0.0:443
|
||||
tcp-request inspect-delay 5s
|
||||
tcp-request content accept if { req.ssl_hello_type 1 }
|
||||
|
||||
acl sni_anytls req.ssl_sni -i cmi-tls-a7f29c.opfo.xyz
|
||||
acl sni_trojan req.ssl_sni -i conn.opfo.xyz
|
||||
|
||||
use_backend singbox_anytls if sni_anytls
|
||||
use_backend singbox_trojan if sni_trojan
|
||||
default_backend caddy_https
|
||||
|
||||
backend singbox_anytls
|
||||
server anytls 127.0.0.1:28443
|
||||
|
||||
backend singbox_trojan
|
||||
server trojan 127.0.0.1:32700
|
||||
|
||||
backend caddy_https
|
||||
server caddy 127.0.0.1:8443
|
||||
Reference in New Issue
Block a user