Switch Xray to VLESS+XHTTP+TLS with Cloudflare DNS ACME (v3.1.1).

Issue Let's Encrypt via Cloudflare API token so install no longer needs port 80; keep HTTP-01 as fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohimaru2
2026-08-09 19:01:52 +03:00
co-authored by Cursor
parent 3ea638d9c5
commit 4720d8f2bd
11 changed files with 573 additions and 153 deletions
+104 -2
View File
@@ -272,7 +272,7 @@
<div class="protocol-icon">{{ icon('zap') }}</div>
<div class="flex gap-sm" id="xray-ctrl" style="display:none!important;"></div>
</div>
<div class="protocol-name">Xray (VLESS-Reality)</div>
<div class="protocol-name">Xray (VLESS-XHTTP-TLS)</div>
<div class="protocol-desc">
{{ _('xray_desc') }}
</div>
@@ -796,6 +796,41 @@
<div class="form-hint">{{ _('nginx_install_hint') }}</div>
</div>
<div id="xrayOptions"
style="display:none; padding: var(--space-md); background: rgba(0,0,0,0.03); border-radius: var(--radius-md); margin-bottom: var(--space-md);">
<div class="form-hint" id="xrayPortsWarning" style="margin-bottom: var(--space-md); padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); background: rgba(234,179,8,0.12); border: 1px solid rgba(234,179,8,0.35); color: var(--text);">
{{ _('xray_ports_warning_cf') }}
</div>
<div class="form-group">
<label class="form-label">{{ _('xray_domain') }} *</label>
<input class="form-input" type="text" id="installXrayDomain" placeholder="vpn.example.com" oninput="updateXrayDnsHint()">
<div class="form-hint" id="xrayDnsHint"></div>
</div>
<div class="form-group">
<label class="form-label">{{ _('xray_email') }} *</label>
<input class="form-input" type="email" id="installXrayEmail" placeholder="admin@example.com">
</div>
<div class="form-group">
<label class="form-label">{{ _('xray_acme_method') }}</label>
<div style="display:flex; flex-direction:column; gap:8px;">
<label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer;">
<input type="radio" name="xrayAcmeMethod" value="cloudflare" checked onchange="updateXrayAcmeUi()">
<span>{{ _('xray_acme_cloudflare') }}</span>
</label>
<label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer;">
<input type="radio" name="xrayAcmeMethod" value="http" onchange="updateXrayAcmeUi()">
<span>{{ _('xray_acme_http') }}</span>
</label>
</div>
</div>
<div class="form-group" id="xrayCfTokenGroup">
<label class="form-label">{{ _('xray_cf_token') }} *</label>
<input class="form-input" type="password" id="installXrayCfToken" autocomplete="off" placeholder="Cloudflare API Token">
<div class="form-hint">{{ _('xray_cf_token_hint') }}</div>
</div>
<div class="form-hint">{{ _('xray_install_hint') }}</div>
</div>
<div id="hysteriaOptions"
style="display:none; padding: var(--space-md); background: rgba(0,0,0,0.03); border-radius: var(--radius-md); margin-bottom: var(--space-md);">
<div class="form-hint" style="margin-bottom: var(--space-md); padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); background: rgba(234,179,8,0.12); border: 1px solid rgba(234,179,8,0.35); color: var(--text);">
@@ -1233,7 +1268,7 @@
{ proto: 'awg2', category: 'protocols', icon: 'sparkles', title: 'AmneziaWG 2.0', descKey: 'awg_desc', badge: 'NEW' },
{ proto: 'awg', category: 'protocols', icon: 'shield', title: 'AmneziaWG', descKey: 'awg_desc' },
{ proto: 'awg_legacy', category: 'protocols', icon: 'radio', title: 'AmneziaWG Legacy', descKey: 'awg_legacy_desc' },
{ proto: 'xray', category: 'protocols', icon: 'zap', title: 'Xray (VLESS-Reality)', descKey: 'xray_desc' },
{ proto: 'xray', category: 'protocols', icon: 'zap', title: 'Xray (VLESS-XHTTP-TLS)', descKey: 'xray_desc' },
{ proto: 'telemt', category: 'protocols', icon: 'plane', title: 'Telemt (Telegram Proxy)', descKey: 'telemt_desc' },
{ proto: 'mieru', category: 'protocols', icon: 'zap', title: 'Mieru', descKey: 'mieru_desc', badge: 'v3.28.0' },
{ proto: 'hysteria', category: 'protocols', icon: 'refresh', title: 'Hysteria 2', descKey: 'hysteria_desc' },
@@ -1946,6 +1981,16 @@
if (protoBase(proto) === 'hysteria' && info.domain) {
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('hysteria_domain')}</span><span class="protocol-info-value">${info.domain}</span></div>`;
}
if (protoBase(proto) === 'xray' && info.domain) {
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('xray_domain')}</span><span class="protocol-info-value">${info.domain}</span></div>`;
}
if (protoBase(proto) === 'xray' && info.acme_method) {
const acmeLabel = info.acme_method === 'cloudflare' ? _('xray_acme_cloudflare_short') : _('xray_acme_http_short');
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('xray_acme_method')}</span><span class="protocol-info-value">${acmeLabel}</span></div>`;
}
if (protoBase(proto) === 'xray' && (info.transport || info.security)) {
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('protocol_label')}</span><span class="protocol-info-value">VLESS · ${(info.transport || 'xhttp').toUpperCase()}+${(info.security || 'tls').toUpperCase()}</span></div>`;
}
if (protoBase(proto) === 'naiveproxy' && info.domain) {
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('naiveproxy_domain')}</span><span class="protocol-info-value">${info.domain}</span></div>`;
}
@@ -2394,6 +2439,34 @@
hint.innerHTML = `${_('naiveproxy_dns_hint')} <code>A&nbsp;&nbsp;${domain}&nbsp;&nbsp;${SERVER_HOST}</code>`;
}
function getXrayAcmeMethod() {
const el = document.querySelector('input[name="xrayAcmeMethod"]:checked');
return el ? el.value : 'cloudflare';
}
function updateXrayAcmeUi() {
const method = getXrayAcmeMethod();
const cfGroup = document.getElementById('xrayCfTokenGroup');
const warn = document.getElementById('xrayPortsWarning');
if (cfGroup) cfGroup.style.display = method === 'cloudflare' ? '' : 'none';
if (warn) warn.textContent = method === 'cloudflare' ? _('xray_ports_warning_cf') : _('xray_ports_warning');
updateXrayDnsHint();
const portHint = document.getElementById('installPortHint');
if (portHint && protoBase(currentInstallProto) === 'xray' && !currentInstallAnother) {
portHint.textContent = method === 'cloudflare' ? _('port_xray_hint_cf') : _('port_xray_hint');
}
}
function updateXrayDnsHint() {
const input = document.getElementById('installXrayDomain');
const hint = document.getElementById('xrayDnsHint');
if (!input || !hint) return;
const domain = (input.value || '').trim() || 'vpn.example.com';
const method = getXrayAcmeMethod();
const prefix = method === 'cloudflare' ? _('xray_dns_hint_cf') : _('xray_dns_hint');
hint.innerHTML = `${prefix} <code>A&nbsp;&nbsp;${domain}&nbsp;&nbsp;${SERVER_HOST}</code>`;
}
function openInstallModal(proto, installAnother = false) {
const base = protoBase(proto);
currentInstallProto = installAnother ? base : proto;
@@ -2412,6 +2485,7 @@
const hysteriaOpts = document.getElementById('hysteriaOptions');
const naiveproxyOpts = document.getElementById('naiveproxyOptions');
const mieruOpts = document.getElementById('mieruOptions');
const xrayOpts = document.getElementById('xrayOptions');
telemtOpts.style.display = 'none';
socks5Opts.style.display = 'none';
@@ -2420,6 +2494,7 @@
hysteriaOpts.style.display = 'none';
if (naiveproxyOpts) naiveproxyOpts.style.display = 'none';
if (mieruOpts) mieruOpts.style.display = 'none';
if (xrayOpts) xrayOpts.style.display = 'none';
if (portGroup) portGroup.style.display = '';
if (base === 'dns') {
@@ -2432,6 +2507,12 @@
portInput.disabled = false;
portInput.value = currentInstallAnother ? nextSuggestedPort(currentInstallProto, 443) : '443';
portHint.textContent = currentInstallAnother ? _('port_next_instance_hint') : _('port_xray_hint');
if (xrayOpts) xrayOpts.style.display = 'block';
const xrDomain = document.getElementById('installXrayDomain');
const xrEmail = document.getElementById('installXrayEmail');
if (xrDomain && !xrDomain.value && SERVER_SSL_DOMAIN) xrDomain.value = SERVER_SSL_DOMAIN;
if (xrEmail && !xrEmail.value && SERVER_SSL_EMAIL) xrEmail.value = SERVER_SSL_EMAIL;
updateXrayAcmeUi();
} else if (base === 'telemt') {
portLabel.textContent = _('port') + ' (TCP)';
portInput.value = currentInstallAnother ? nextSuggestedPort(currentInstallProto, 443) : '443';
@@ -2513,6 +2594,20 @@
async function installProtocol() {
const port = document.getElementById('installPort').value;
if (protoBase(currentInstallProto) === 'xray') {
const xrDomain = (document.getElementById('installXrayDomain')?.value || '').trim();
const xrEmail = (document.getElementById('installXrayEmail')?.value || '').trim();
const acme = getXrayAcmeMethod();
const cfToken = (document.getElementById('installXrayCfToken')?.value || '').trim();
if (!xrDomain || !xrEmail) {
showToast(_('xray_domain') + ' / ' + _('xray_email'), 'error');
return;
}
if (acme === 'cloudflare' && !cfToken) {
showToast(_('xray_cf_token'), 'error');
return;
}
}
const btn = document.getElementById('installBtn');
const text = document.getElementById('installBtnText');
const spinner = document.getElementById('installSpinner');
@@ -2571,6 +2666,13 @@
}
params.hysteria_domain = document.getElementById('installHysteriaDomain').value.trim();
params.hysteria_email = document.getElementById('installHysteriaEmail').value.trim();
} else if (protoBase(currentInstallProto) === 'xray') {
params.xray_domain = (document.getElementById('installXrayDomain')?.value || '').trim();
params.xray_email = (document.getElementById('installXrayEmail')?.value || '').trim();
params.xray_acme_method = getXrayAcmeMethod();
if (params.xray_acme_method === 'cloudflare') {
params.xray_cf_token = (document.getElementById('installXrayCfToken')?.value || '').trim();
}
} else if (protoBase(currentInstallProto) === 'naiveproxy') {
params.port = '443';
params.naiveproxy_domain = document.getElementById('installNaiveproxyDomain').value.trim();