Template
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:
@@ -210,7 +210,7 @@
|
||||
awg: 'AmneziaWG',
|
||||
awg_legacy: 'AWG Legacy',
|
||||
wireguard: 'WireGuard',
|
||||
xray: 'Xray (VLESS-Reality)',
|
||||
xray: 'Xray (VLESS-XHTTP-TLS)',
|
||||
telemt: 'Telemt',
|
||||
hysteria: 'Hysteria 2',
|
||||
naiveproxy: 'NaiveProxy',
|
||||
|
||||
+104
-2
@@ -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 ${domain} ${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 ${domain} ${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();
|
||||
|
||||
@@ -1563,7 +1563,7 @@
|
||||
awg: 'AmneziaWG',
|
||||
awg_legacy: 'AWG Legacy',
|
||||
wireguard: 'WireGuard',
|
||||
xray: 'Xray (VLESS-Reality)',
|
||||
xray: 'Xray (VLESS-XHTTP-TLS)',
|
||||
telemt: 'Telemt',
|
||||
hysteria: 'Hysteria 2',
|
||||
naiveproxy: 'NaiveProxy',
|
||||
|
||||
@@ -637,7 +637,7 @@
|
||||
awg: 'AmneziaWG',
|
||||
awg_legacy: 'AWG Legacy',
|
||||
wireguard: 'WireGuard',
|
||||
xray: 'Xray (VLESS-Reality)',
|
||||
xray: 'Xray (VLESS-XHTTP-TLS)',
|
||||
telemt: 'Telemt',
|
||||
hysteria: 'Hysteria 2',
|
||||
naiveproxy: 'NaiveProxy',
|
||||
|
||||
Reference in New Issue
Block a user