diff --git a/servers/bwh-us-dc9/frontdoor/.env.example b/servers/bwh-us-dc9/frontdoor/.env.example
new file mode 100644
index 0000000..db71747
--- /dev/null
+++ b/servers/bwh-us-dc9/frontdoor/.env.example
@@ -0,0 +1,3 @@
+# Stage on 8444 first. Change to 443 only after the Arcane Agent Caddy is
+# reachable through its temporary management port and host TCP 443 is free.
+HAPROXY_PUBLIC_PORT=8444
diff --git a/servers/bwh-us-dc9/frontdoor/README.md b/servers/bwh-us-dc9/frontdoor/README.md
new file mode 100644
index 0000000..7eea090
--- /dev/null
+++ b/servers/bwh-us-dc9/frontdoor/README.md
@@ -0,0 +1,12 @@
+# DC9 TLS front door
+
+HAProxy inspects TCP SNI without terminating TLS:
+
+- `dc9-tls-e2f48b.opfo.xyz` -> managed sing-box Trojan on host TCP 22591
+- `sb-dd65a3.kais.cloud` -> managed sing-box AnyTLS on host TCP 43824
+- `dc9.kais.cloud` -> the existing Arcane Agent Caddy on `arcane_default`
+- unmatched SNI -> the dedicated Caddy fallback serving `dc9.opfo.xyz`
+
+The first deployment uses host TCP 8444. Moving it to TCP 443 requires a staged
+handoff from the existing Arcane Agent Caddy so the Direct HTTPS management path
+remains reachable throughout the change.
diff --git a/servers/bwh-us-dc9/frontdoor/caddy/Caddyfile b/servers/bwh-us-dc9/frontdoor/caddy/Caddyfile
new file mode 100644
index 0000000..26ea7cf
--- /dev/null
+++ b/servers/bwh-us-dc9/frontdoor/caddy/Caddyfile
@@ -0,0 +1,21 @@
+{
+ servers {
+ protocols h1 h2
+ }
+}
+
+dc9.opfo.xyz {
+ encode zstd gzip
+ header {
+ X-Content-Type-Options nosniff
+ X-Frame-Options DENY
+ Referrer-Policy no-referrer
+ }
+ respond <
+
+
Service Status
+Service online
The requested service is available.
+
+HTML
+}
diff --git a/servers/bwh-us-dc9/frontdoor/compose.yaml b/servers/bwh-us-dc9/frontdoor/compose.yaml
new file mode 100644
index 0000000..c17ad3f
--- /dev/null
+++ b/servers/bwh-us-dc9/frontdoor/compose.yaml
@@ -0,0 +1,44 @@
+services:
+ haproxy:
+ image: haproxy:3.4.4-alpine3.24
+ container_name: dc9-haproxy
+ restart: unless-stopped
+ ports:
+ - "${HAPROXY_PUBLIC_PORT:-8444}:8443/tcp"
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ depends_on:
+ - decoy-caddy
+ networks:
+ - default
+ - arcane
+ volumes:
+ - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
+ logging:
+ driver: json-file
+ options:
+ max-size: 200k
+ max-file: "2"
+
+ decoy-caddy:
+ image: caddy:2.11.4
+ container_name: dc9-decoy-caddy
+ restart: unless-stopped
+ volumes:
+ - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
+ - caddy-data:/data
+ - caddy-config:/config
+ logging:
+ driver: json-file
+ options:
+ max-size: 200k
+ max-file: "2"
+
+networks:
+ arcane:
+ external: true
+ name: arcane_default
+
+volumes:
+ caddy-data:
+ caddy-config:
diff --git a/servers/bwh-us-dc9/frontdoor/haproxy/haproxy.cfg b/servers/bwh-us-dc9/frontdoor/haproxy/haproxy.cfg
new file mode 100644
index 0000000..94fd1b7
--- /dev/null
+++ b/servers/bwh-us-dc9/frontdoor/haproxy/haproxy.cfg
@@ -0,0 +1,37 @@
+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:8443
+ tcp-request inspect-delay 5s
+ tcp-request content accept if { req.ssl_hello_type 1 }
+
+ acl sni_trojan req.ssl_sni -i dc9-tls-e2f48b.opfo.xyz
+ acl sni_anytls req.ssl_sni -i sb-dd65a3.kais.cloud
+ acl sni_arcane req.ssl_sni -i dc9.kais.cloud
+
+ use_backend singbox_trojan if sni_trojan
+ use_backend singbox_anytls if sni_anytls
+ use_backend arcane_agent if sni_arcane
+ default_backend decoy_https
+
+backend singbox_trojan
+ server trojan host.docker.internal:22591
+
+backend singbox_anytls
+ server anytls host.docker.internal:43824
+
+backend arcane_agent
+ server arcane arcane-agent-caddy:443
+
+backend decoy_https
+ server caddy decoy-caddy:443
diff --git a/servers/bwh-us-dc9/sing-box/.env.example b/servers/bwh-us-dc9/sing-box/.env.example
index 2dfa9a8..6eebd56 100644
--- a/servers/bwh-us-dc9/sing-box/.env.example
+++ b/servers/bwh-us-dc9/sing-box/.env.example
@@ -5,16 +5,11 @@ USER1_PASSWORD=change-me
CLOUDFLARE_API_TOKEN=change-me
ANYTLS_DOMAIN=sb-dd65a3.kais.cloud
-ANYTLS_HY2_PORT=43824
+ANYTLS_PORT=43824
ANYTLS_PASSWORD=change-me
HY2_PASSWORD=change-me
HY2_OBFS_PASSWORD=change-me
-
-VLESS_PORT=22590
-VLESS_UUID=00000000-0000-4000-8000-000000000000
-REALITY_SERVER_NAME=www.cloudflare.com
-REALITY_PRIVATE_KEY=change-me
-REALITY_SHORT_ID=0123456789abcdef
+HY2_PORT=443
# sing-box uses version 5 for the Snell v4-compatible TCP wire protocol.
SNELL_V4_PORT=53469
diff --git a/servers/bwh-us-dc9/sing-box/compose.yaml b/servers/bwh-us-dc9/sing-box/compose.yaml
index 89b2d95..8440b1a 100644
--- a/servers/bwh-us-dc9/sing-box/compose.yaml
+++ b/servers/bwh-us-dc9/sing-box/compose.yaml
@@ -1,6 +1,6 @@
services:
config-render:
- image: alpine:3.20
+ image: alpine:3.24
environment:
DOMAIN: ${DOMAIN}
ACME_EMAIL: ${ACME_EMAIL}
@@ -8,15 +8,11 @@ services:
USER1_PASSWORD: ${USER1_PASSWORD}
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
ANYTLS_DOMAIN: ${ANYTLS_DOMAIN}
- ANYTLS_HY2_PORT: ${ANYTLS_HY2_PORT}
+ ANYTLS_PORT: ${ANYTLS_PORT}
ANYTLS_PASSWORD: ${ANYTLS_PASSWORD}
HY2_PASSWORD: ${HY2_PASSWORD}
HY2_OBFS_PASSWORD: ${HY2_OBFS_PASSWORD}
- VLESS_PORT: ${VLESS_PORT}
- VLESS_UUID: ${VLESS_UUID}
- REALITY_SERVER_NAME: ${REALITY_SERVER_NAME}
- REALITY_PRIVATE_KEY: ${REALITY_PRIVATE_KEY}
- REALITY_SHORT_ID: ${REALITY_SHORT_ID}
+ HY2_PORT: ${HY2_PORT}
SNELL_V4_PORT: ${SNELL_V4_PORT}
SNELL_V4_PSK: ${SNELL_V4_PSK}
SNELL_V6_PORT: ${SNELL_V6_PORT}
diff --git a/servers/bwh-us-dc9/sing-box/etc/sing-box/config.json.template b/servers/bwh-us-dc9/sing-box/etc/sing-box/config.json.template
index 9e36b57..dd2db25 100644
--- a/servers/bwh-us-dc9/sing-box/etc/sing-box/config.json.template
+++ b/servers/bwh-us-dc9/sing-box/etc/sing-box/config.json.template
@@ -1,4 +1,19 @@
{
+ "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",
@@ -14,15 +29,7 @@
"tls": {
"enabled": true,
"server_name": "${DOMAIN}",
- "acme": {
- "domain": "${DOMAIN}",
- "data_directory": "/var/lib/sing-box/certmagic-anytls",
- "email": "${ACME_EMAIL}",
- "dns01_challenge": {
- "provider": "cloudflare",
- "api_token": "${CLOUDFLARE_API_TOKEN}"
- }
- }
+ "certificate_provider": "public-cert"
},
"multiplex": {
"enabled": true
@@ -32,7 +39,7 @@
"type": "anytls",
"tag": "anytls-in",
"listen": "::",
- "listen_port": ${ANYTLS_HY2_PORT},
+ "listen_port": ${ANYTLS_PORT},
"users": [
{
"name": "user1",
@@ -42,22 +49,14 @@
"tls": {
"enabled": true,
"server_name": "${ANYTLS_DOMAIN}",
- "acme": {
- "domain": "${ANYTLS_DOMAIN}",
- "data_directory": "/var/lib/sing-box/certmagic-anytls",
- "email": "${ACME_EMAIL}",
- "dns01_challenge": {
- "provider": "cloudflare",
- "api_token": "${CLOUDFLARE_API_TOKEN}"
- }
- }
+ "certificate_provider": "public-cert"
}
},
{
"type": "hysteria2",
"tag": "hysteria2-in",
"listen": "0.0.0.0",
- "listen_port": ${ANYTLS_HY2_PORT},
+ "listen_port": ${HY2_PORT},
"obfs": {
"type": "salamander",
"password": "${HY2_OBFS_PASSWORD}"
@@ -74,44 +73,7 @@
"alpn": [
"h3"
],
- "acme": {
- "domain": "${ANYTLS_DOMAIN}",
- "data_directory": "/var/lib/sing-box/certmagic-anytls",
- "email": "${ACME_EMAIL}",
- "dns01_challenge": {
- "provider": "cloudflare",
- "api_token": "${CLOUDFLARE_API_TOKEN}"
- }
- }
- }
- },
- {
- "type": "vless",
- "tag": "vless-reality-in",
- "listen": "::",
- "listen_port": ${VLESS_PORT},
- "users": [
- {
- "name": "user1",
- "uuid": "${VLESS_UUID}",
- "flow": "xtls-rprx-vision"
- }
- ],
- "tls": {
- "enabled": true,
- "server_name": "${REALITY_SERVER_NAME}",
- "reality": {
- "enabled": true,
- "handshake": {
- "server": "${REALITY_SERVER_NAME}",
- "server_port": 443
- },
- "private_key": "${REALITY_PRIVATE_KEY}",
- "short_id": [
- "${REALITY_SHORT_ID}"
- ],
- "max_time_difference": "2m"
- }
+ "certificate_provider": "public-cert"
}
},
{