From d5a236943ba76cd2c8a0ed2566b11a89a7729ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AC=A2?= <2711452309@qq.com> Date: Sat, 13 Jun 2026 00:52:19 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=98=B2=E7=81=AB?= =?UTF-8?q?=E5=A2=99=E8=87=B3=E4=B8=8D=E6=94=AF=E6=8C=81=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E7=AD=89=E5=BE=85Claude=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mofang/clicd.php | 8 + Mofang/templates/firewall.html | 1122 +++++++++++++++++++++----------- 2 files changed, 737 insertions(+), 393 deletions(-) diff --git a/Mofang/clicd.php b/Mofang/clicd.php index d7c313a..7c0282a 100644 --- a/Mofang/clicd.php +++ b/Mofang/clicd.php @@ -1445,6 +1445,7 @@ function clicd_firewallUpdate($params) $input = clicd_json_input(); $enabled = clicd_param_value($input, 'enabled', 'true'); $enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN); + $defaultAction = strtoupper(trim((string)clicd_param_value($input, 'default_action', ''))); $rules = clicd_param_value($input, 'rules', '[]'); if (is_string($rules)) { @@ -1461,6 +1462,9 @@ function clicd_firewallUpdate($params) 'enabled' => $enabled, 'rules' => $rules, ]; + if (in_array($defaultAction, ['ACCEPT', 'DROP'], true)) { + $payload['default_action'] = $defaultAction; + } $container = []; $containerId = clicd_container_api_id($params, $container); @@ -1526,6 +1530,7 @@ function clicd_firewall_ajax($params) // update $enabled = clicd_param_value($input, 'enabled', 'true'); $enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN); + $defaultAction = strtoupper(trim((string)clicd_param_value($input, 'default_action', ''))); $rules = clicd_param_value($input, 'rules', '[]'); if (is_string($rules)) { @@ -1542,6 +1547,9 @@ function clicd_firewall_ajax($params) 'enabled' => $enabled, 'rules' => $rules, ]; + if (in_array($defaultAction, ['ACCEPT', 'DROP'], true)) { + $payload['default_action'] = $defaultAction; + } $call = clicd_request_debug($params, '/api/v1/containers/' . rawurlencode($containerId) . '/firewall', $payload, 'PUT', 30); $debug[] = $call['debug']; diff --git a/Mofang/templates/firewall.html b/Mofang/templates/firewall.html index 4ca45e4..29c0ae5 100644 --- a/Mofang/templates/firewall.html +++ b/Mofang/templates/firewall.html @@ -1,52 +1,370 @@ 
@@ -72,11 +390,26 @@ 启用防火墙 加载中...
+
+ + +
添加规则
+
+ + +
- 启用防火墙 - 加载中...
- - + +
-
- -
添加规则
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
+
+
网络范围
+
可配置:IPv4(NAT)。 IPv4 规则覆盖 IPv4 NAT 端口映射。 NAT 入站端口按容器内部端口匹配,不是宿主机公网端口。
+
+
+ 当前设置已保存 +
-
防火墙规则
+
+
防火墙规则
+ +
加载中...

 
+    
+
+
+
添加规则
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
NAT 入站填容器内部端口,例如公网 22023 -> 容器 22,这里填 22
+
+
+ + +
留空为任意 IPv4,支持 CIDR: 192.168.1.0/24
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
确认删除
@@ -481,12 +673,23 @@ var enabledCheckbox = document.getElementById('clicd-fw-enabled'); var defaultActionSelect = document.getElementById('clicd-fw-default-action'); var statusText = document.getElementById('clicd-fw-status-text'); + var saveSettingsButton = document.getElementById('clicd-fw-save-settings'); + var saveHint = document.getElementById('clicd-fw-save-hint'); + var addModal = document.getElementById('clicd-fw-add-modal'); + var addClose = document.getElementById('clicd-fw-add-close'); + var addCancel = document.getElementById('clicd-fw-add-cancel'); + var addConfirm = document.getElementById('clicd-fw-add-confirm'); + var ruleModalTitle = document.getElementById('clicd-fw-rule-modal-title'); var deleteModal = document.getElementById('clicd-fw-delete-modal'); var deleteText = document.getElementById('clicd-fw-delete-text'); var deleteCancel = document.getElementById('clicd-fw-delete-cancel'); var deleteConfirm = document.getElementById('clicd-fw-delete-confirm'); var pendingDeleteRule = null; var currentRules = []; + var ruleModalMode = 'add'; + var editingRuleIndex = null; + var settingsDirty = false; + var busy = false; function showMessage(type, text) { message.className = 'clicd-fw-message' + (type === 'error' ? ' error' : ''); @@ -507,8 +710,31 @@ return panel.getAttribute('data-service-id') || ''; } - function setBusy(busy) { - panel.querySelectorAll('button, input, select').forEach(function (el) { el.disabled = !!busy; }); + function updateSaveState() { + if (saveSettingsButton) { + saveSettingsButton.disabled = busy || !settingsDirty; + } + if (saveHint) { + saveHint.textContent = settingsDirty ? '有未保存更改,点击保存设置后生效' : '当前设置已保存'; + } + } + + function setBusy(value) { + busy = !!value; + panel.querySelectorAll('button, input, select').forEach(function (el) { el.disabled = busy; }); + updateSaveState(); + } + + function markDirty(text) { + settingsDirty = true; + updateStatusText(); + updateSaveState(); + showMessage('success', text || '设置已修改,点击保存设置后生效'); + } + + function clearDirty() { + settingsDirty = false; + updateSaveState(); } function escapeHtml(value) { @@ -532,7 +758,7 @@ rulesContainer.innerHTML = '
暂无防火墙规则
'; return; } - rulesContainer.innerHTML = currentRules.map(function (rule, idx) { + var rows = currentRules.map(function (rule, idx) { var dir = (rule.direction || 'in').toLowerCase(); var proto = (rule.protocol || 'tcp').toLowerCase(); var action = (rule.action || 'ACCEPT').toUpperCase(); @@ -540,110 +766,98 @@ var port = escapeHtml(portDisplay(rule.port)); var srcIp = escapeHtml(sourceIpDisplay(rule.source_ip)); var desc = escapeHtml(rule.description || ''); - var ruleId = escapeHtml(rule.id || ''); var enabled = rule.enabled !== false; var enabledChecked = enabled ? 'checked' : ''; var dirClass = dir === 'in' ? 'clicd-fw-direction-in' : 'clicd-fw-direction-out'; var actionClass = 'clicd-fw-action-' + action; - return '
' + - '
' + - '
' + - '' + (dir === 'in' ? '↑ 入站' : '↓ 出站') + '' + - '' + (action === 'ACCEPT' ? '放行' : '拒绝') + '' + - '' + proto.toUpperCase() + '' + - '' + networkLabel(network) + '' + - '' + - (port !== '所有' ? '端口: ' + port : '') + - (srcIp !== '任意' && port !== '所有' ? ' | ' : '') + - (srcIp !== '任意' ? '来源: ' + srcIp : '') + - '' + - '
' + - '
' + - '' + - '
' + - '
' + - '
' + - '' + (desc || '无说明') + '' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '' + - '' + - '
' + - '
' + - '
'; + return '' + + '' + + '' + networkLabel(network) + '' + + '' + (dir === 'in' ? '入站' : '出站') + '' + + '' + escapeHtml(proto.toUpperCase()) + '' + + '' + port + '' + + '' + srcIp + '' + + '' + (action === 'ACCEPT' ? '放行' : '拒绝') + '' + + '' + (desc || '-') + '' + + '
' + + '' + + '' + + '
' + + ''; }).join(''); + rulesContainer.innerHTML = '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + rows + '
状态网络方向协议端口来源/目标 IP动作描述操作
'; + rulesContainer.querySelectorAll('.clicd-fw-rule-enabled').forEach(function (toggle, idx) { toggle.addEventListener('change', function () { var rule = currentRules[idx]; if (!rule) return; rule.enabled = toggle.checked; - saveFirewall(); + markDirty('规则开关已修改,点击保存设置后生效'); }); }); } - function getEditField(item, name) { - return item.querySelector('[data-field="' + name + '"]'); + function setRuleField(id, value) { + var field = document.getElementById(id); + if (field) { + field.value = value == null ? '' : value; + } } - function updateRuleFromFields(item, idx) { - currentRules[idx].network = getEditField(item, 'network') ? getEditField(item, 'network').value : 'ipv4'; - currentRules[idx].direction = getEditField(item, 'direction') ? getEditField(item, 'direction').value : 'in'; - currentRules[idx].protocol = getEditField(item, 'protocol') ? getEditField(item, 'protocol').value : 'tcp'; - currentRules[idx].port = getEditField(item, 'port') ? getEditField(item, 'port').value : ''; - currentRules[idx].source_ip = getEditField(item, 'source_ip') ? getEditField(item, 'source_ip').value : ''; - currentRules[idx].action = getEditField(item, 'action') ? getEditField(item, 'action').value : 'ACCEPT'; - currentRules[idx].description = getEditField(item, 'description') ? getEditField(item, 'description').value : ''; + function getRuleField(id, fallback) { + var field = document.getElementById(id); + if (!field) return fallback || ''; + return field.value; } - function getAddRulePayload() { + function getRuleFormPayload(baseRule) { + var base = baseRule || {}; return { - id: '', - network: document.getElementById('clicd-fw-add-network').value, - direction: document.getElementById('clicd-fw-add-direction').value, - protocol: document.getElementById('clicd-fw-add-protocol').value, - port: document.getElementById('clicd-fw-add-port').value, - source_ip: document.getElementById('clicd-fw-add-source-ip').value, - action: document.getElementById('clicd-fw-add-action').value, - description: document.getElementById('clicd-fw-add-desc').value, - enabled: true + id: base.id || '', + network: getRuleField('clicd-fw-add-network', 'ipv4'), + direction: getRuleField('clicd-fw-add-direction', 'in'), + protocol: getRuleField('clicd-fw-add-protocol', 'tcp'), + port: getRuleField('clicd-fw-add-port', ''), + source_ip: getRuleField('clicd-fw-add-source-ip', ''), + action: getRuleField('clicd-fw-add-action', 'DROP'), + description: getRuleField('clicd-fw-add-desc', ''), + enabled: baseRule ? base.enabled !== false : true }; } function clearAddForm() { - document.getElementById('clicd-fw-add-network').value = 'ipv4'; - document.getElementById('clicd-fw-add-port').value = ''; - document.getElementById('clicd-fw-add-source-ip').value = ''; - document.getElementById('clicd-fw-add-action').value = 'ACCEPT'; - document.getElementById('clicd-fw-add-desc').value = ''; + setRuleField('clicd-fw-add-network', 'ipv4'); + setRuleField('clicd-fw-add-direction', 'in'); + setRuleField('clicd-fw-add-protocol', 'tcp'); + setRuleField('clicd-fw-add-port', ''); + setRuleField('clicd-fw-add-source-ip', ''); + setRuleField('clicd-fw-add-action', 'DROP'); + setRuleField('clicd-fw-add-desc', ''); + } + + function fillRuleForm(rule) { + var item = rule || {}; + setRuleField('clicd-fw-add-network', item.network || 'ipv4'); + setRuleField('clicd-fw-add-direction', item.direction || 'in'); + setRuleField('clicd-fw-add-protocol', item.protocol || 'tcp'); + setRuleField('clicd-fw-add-port', item.port || ''); + setRuleField('clicd-fw-add-source-ip', item.source_ip || ''); + setRuleField('clicd-fw-add-action', item.action || 'DROP'); + setRuleField('clicd-fw-add-desc', item.description || ''); } - // ── FIX: 发 JSON body,走 firewallajax 路由 ── function saveFirewall() { var enabled = enabledCheckbox.checked; var defaultAction = defaultActionSelect.value; @@ -661,22 +875,22 @@ }; }); + var body = new URLSearchParams(); + body.set('id', serviceId()); + body.set('func', 'firewallUpdate'); + body.set('enabled', enabled ? 'true' : 'false'); + body.set('default_action', defaultAction); + body.set('rules', JSON.stringify(rules)); + setBusy(true); fetch(endpoint(), { method: 'POST', headers: { - 'Content-Type': 'application/json; charset=UTF-8', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Authorization': 'JWT {$Think.get.jwt}' }, credentials: 'same-origin', - body: JSON.stringify({ - id: serviceId(), - func: 'firewallajax', - action: 'update', - enabled: enabled, - default_action: defaultAction, - rules: rules - }) + body: body.toString() }) .then(function (res) { return res.text(); }) .then(function (text) { @@ -685,10 +899,17 @@ showDebug((data.data && data.data.debug) || data.debug || data); if (data.status === 200 || data.status === 'success') { showMessage('success', data.msg || '防火墙设置已更新'); + if (data.data && Object.prototype.hasOwnProperty.call(data.data, 'enabled')) { + enabledCheckbox.checked = data.data.enabled === true || data.data.enabled === 'true' || data.data.enabled === 1; + } + if (data.data && data.data.default_action && defaultActionSelect) { + defaultActionSelect.value = data.data.default_action; + } if (data.data && Array.isArray(data.data.rules)) { currentRules = data.data.rules; renderRules(currentRules); } + clearDirty(); updateStatusText(); } else { showMessage('error', data.msg || '更新失败'); @@ -703,21 +924,20 @@ }); } - // ── FIX: 发 JSON body,走 firewallajax 路由 ── function loadFirewall() { + var body = new URLSearchParams(); + body.set('id', serviceId()); + body.set('func', 'firewallList'); + setBusy(true); fetch(endpoint(), { method: 'POST', headers: { - 'Content-Type': 'application/json; charset=UTF-8', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Authorization': 'JWT {$Think.get.jwt}' }, credentials: 'same-origin', - body: JSON.stringify({ - id: serviceId(), - func: 'firewallajax', - action: 'list' - }) + body: body.toString() }) .then(function (res) { return res.text(); }) .then(function (text) { @@ -732,6 +952,7 @@ } currentRules = Array.isArray(data.data.rules) ? data.data.rules : []; renderRules(currentRules); + clearDirty(); updateStatusText(); } } else { @@ -751,9 +972,9 @@ function updateStatusText() { if (enabledCheckbox.checked) { - statusText.textContent = '已启用 - 默认' + (defaultActionSelect.value === 'DROP' ? '拒绝' : '放行') + '未匹配流量'; + statusText.textContent = '已启用,未匹配规则的流量将被' + (defaultActionSelect.value === 'DROP' ? '拒绝' : '放行'); } else { - statusText.textContent = '已禁用 - 所有流量不受限制'; + statusText.textContent = '未启用时不接管该容器流量'; } } @@ -774,27 +995,51 @@ } } + function openRuleModal(mode, idx) { + ruleModalMode = mode === 'edit' ? 'edit' : 'add'; + editingRuleIndex = ruleModalMode === 'edit' ? idx : null; + if (ruleModalTitle) { + ruleModalTitle.textContent = ruleModalMode === 'edit' ? '编辑规则' : '添加规则'; + } + if (ruleModalMode === 'edit' && currentRules[idx]) { + fillRuleForm(currentRules[idx]); + } else { + clearAddForm(); + } + if (addModal) { + addModal.style.display = 'flex'; + } + var portInput = document.getElementById('clicd-fw-add-port'); + if (portInput) { + setTimeout(function () { portInput.focus(); }, 0); + } + } + + function closeAddModal() { + ruleModalMode = 'add'; + editingRuleIndex = null; + if (addModal) { + addModal.style.display = 'none'; + } + } + panel.addEventListener('click', function (event) { var button = event.target.closest('[data-clicd-fw-action]'); if (!button) return; var action = button.getAttribute('data-clicd-fw-action'); - if (action === 'add-rule') { - currentRules.push(getAddRulePayload()); - renderRules(currentRules); - clearAddForm(); - saveFirewall(); + if (action === 'open-add-rule') { + openRuleModal('add'); return; } - var item = button.closest('.clicd-fw-rule'); + var item = button.closest('[data-rule-index]'); if (!item) return; var idx = parseInt(item.getAttribute('data-rule-index'), 10); if (isNaN(idx) || !currentRules[idx]) return; - if (action === 'update-rule') { - updateRuleFromFields(item, idx); - saveFirewall(); + if (action === 'edit-rule') { + openRuleModal('edit', idx); return; } @@ -807,14 +1052,46 @@ enabledCheckbox.addEventListener('change', function () { updateStatusText(); - saveFirewall(); + markDirty('防火墙开关已修改,点击保存设置后生效'); }); defaultActionSelect.addEventListener('change', function () { updateStatusText(); - saveFirewall(); + markDirty('默认动作已修改,点击保存设置后生效'); }); + if (saveSettingsButton) { + saveSettingsButton.addEventListener('click', saveFirewall); + } + + if (addClose) { + addClose.addEventListener('click', closeAddModal); + } + if (addCancel) { + addCancel.addEventListener('click', closeAddModal); + } + if (addModal) { + addModal.addEventListener('click', function (event) { + if (event.target === addModal) closeAddModal(); + }); + } + if (addConfirm) { + addConfirm.addEventListener('click', function () { + if (ruleModalMode === 'edit' && editingRuleIndex !== null && currentRules[editingRuleIndex]) { + currentRules[editingRuleIndex] = getRuleFormPayload(currentRules[editingRuleIndex]); + renderRules(currentRules); + closeAddModal(); + markDirty('规则已更新,点击保存设置后生效'); + return; + } + currentRules.push(getRuleFormPayload(null)); + renderRules(currentRules); + closeAddModal(); + clearAddForm(); + markDirty('规则已添加,点击保存设置后生效'); + }); + } + if (deleteCancel) { deleteCancel.addEventListener('click', closeDeleteModal); } @@ -831,11 +1108,12 @@ if (idx >= 0 && idx < currentRules.length) { currentRules.splice(idx, 1); renderRules(currentRules); - saveFirewall(); + markDirty('规则已删除,点击保存设置后生效'); } }); } + updateSaveState(); loadFirewall(); })(); - \ No newline at end of file + From 2ed42992ed131b1419df6a04c27993a047c30385 Mon Sep 17 00:00:00 2001 From: Meng Meng Date: Sat, 13 Jun 2026 21:04:10 +0800 Subject: [PATCH 3/4] Potential fix for code scanning alert no. 29: DOM text reinterpreted as HTML Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- Mofang/templates/firewall.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mofang/templates/firewall.html b/Mofang/templates/firewall.html index df836ba..1375ade 100644 --- a/Mofang/templates/firewall.html +++ b/Mofang/templates/firewall.html @@ -761,7 +761,8 @@ var rows = currentRules.map(function (rule, idx) { var dir = (rule.direction || 'in').toLowerCase(); var proto = (rule.protocol || 'tcp').toLowerCase(); - var action = (rule.action || 'ACCEPT').toUpperCase(); + var rawAction = (rule.action || 'ACCEPT').toUpperCase(); + var action = rawAction === 'ACCEPT' ? 'ACCEPT' : 'DROP'; var network = (rule.network || 'ipv4').toLowerCase(); var port = escapeHtml(portDisplay(rule.port)); var srcIp = escapeHtml(sourceIpDisplay(rule.source_ip)); From a54e03b9242674ce8bf89521a7b15fb612ca16fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AC=A2?= <2711452309@qq.com> Date: Sun, 14 Jun 2026 03:15:02 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E5=AE=89=E8=A3=85certbot=E8=84=9A=E6=9C=AC+=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=9B=B4=E5=AE=8C=E6=95=B4=E7=9A=84webssh=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mofang/clicd.php | 14 ++++++- Mofang/handlers/webssh.php | 19 ++++++++- install-certbot.sh | 80 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 install-certbot.sh diff --git a/Mofang/clicd.php b/Mofang/clicd.php index db09a36..353f912 100644 --- a/Mofang/clicd.php +++ b/Mofang/clicd.php @@ -40,7 +40,7 @@ function clicd_MetaData() 'DisplayName' => 'CLICD 对接模块 by 欢-Huan and ChatGPT 5.5 and DeepSeek V4', 'APIVersion' => '1.1', 'HelpDoc' => 'https://github.com/MengMengCode/CLICD', - 'version' => '1.0.10', + 'version' => '1.0.11', ]; } @@ -365,7 +365,10 @@ function clicd_webssh_url($params, $ticket, $containerName) $host = parse_url($baseUrl, PHP_URL_HOST); $port = parse_url($baseUrl, PHP_URL_PORT); $wsBase = $scheme . '://' . $host . ($port ? ':' . $port : ''); - $wsUrl = $wsBase . '/api/ssh?container=' . rawurlencode((string)$containerName); + $wsUrl = $wsBase + . '/api/ssh?container=' . rawurlencode((string)$containerName) + . '&container_name=' . rawurlencode((string)$containerName) + . '&ticket=' . rawurlencode((string)$ticket); $siteScheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $siteHost = $_SERVER['HTTP_HOST'] ?? ''; @@ -374,6 +377,7 @@ function clicd_webssh_url($params, $ticket, $containerName) return $handler . '?ws=' . rawurlencode($wsUrl) . '&protocol=' . rawurlencode('clicd-ticket.' . (string)$ticket) + . '&ticket=' . rawurlencode((string)$ticket) . '&container=' . rawurlencode((string)$containerName); } @@ -1419,7 +1423,13 @@ function clicd_ClientButton($params) function clicd_webssh($params) { + $container = []; $containerName = clicd_container_name($params); + clicd_container_api_id($params, $container); + if (!empty($container['name'])) { + $containerName = (string)$container['name']; + } + $res = clicd_request($params, '/api/v1/ssh-ticket', ['container_name' => $containerName], 'POST', 30); if (!clicd_success($res)) { return ['status' => 'error', 'msg' => clicd_message($res, 'WebSSH ticket create failed')]; diff --git a/Mofang/handlers/webssh.php b/Mofang/handlers/webssh.php index 3be35c7..510308a 100644 --- a/Mofang/handlers/webssh.php +++ b/Mofang/handlers/webssh.php @@ -2,8 +2,13 @@ $ws = isset($_GET['ws']) ? (string)$_GET['ws'] : (isset($_GET['amp;ws']) ? (string)$_GET['amp;ws'] : ''); $protocol = isset($_GET['protocol']) ? (string)$_GET['protocol'] : (isset($_GET['amp;protocol']) ? (string)$_GET['amp;protocol'] : ''); $container = isset($_GET['container']) ? (string)$_GET['container'] : (isset($_GET['amp;container']) ? (string)$_GET['amp;container'] : ''); +$ticket = isset($_GET['ticket']) ? (string)$_GET['ticket'] : (isset($_GET['amp;ticket']) ? (string)$_GET['amp;ticket'] : ''); -if ($ws === '' || $protocol === '') { +if ($protocol === '' && $ticket !== '') { + $protocol = 'clicd-ticket.' . $ticket; +} + +if ($ws === '') { http_response_code(400); header('Content-Type: text/plain; charset=utf-8'); echo "Missing WebSSH parameters\n"; @@ -64,6 +69,7 @@ if ($ws === '' || $protocol === '') { (function(){ var wsUrl = ; var protocol = ; + var ticket = ; var term = document.getElementById('term'); var state = document.getElementById('state'); var modeSelect = document.getElementById('send-mode'); @@ -189,8 +195,17 @@ if ($ws === '' || $protocol === '') { iostat.textContent = 'S' + sentCount + ' R' + recvCount + ' ' + stateText; } + function websocketProtocolValue(value) { + value = String(value || ''); + return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value) ? value : ''; + } + try { - socket = new WebSocket(wsUrl, protocol); + var protocolValue = websocketProtocolValue(protocol); + if (!protocolValue && ticket) { + append('[WebSSH] 票据已通过 URL 参数传递,当前浏览器不会发送子协议。\n'); + } + socket = protocolValue ? new WebSocket(wsUrl, protocolValue) : new WebSocket(wsUrl); socket.binaryType = 'arraybuffer'; } catch (e) { setState('err', '\nWebSocket 创建失败:' + e.message + '\n'); diff --git a/install-certbot.sh b/install-certbot.sh new file mode 100644 index 0000000..71c6a73 --- /dev/null +++ b/install-certbot.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -e + +echo "==============================" +echo " Certbot (Snap) Auto Installer" +echo "==============================" + +# 检测系统 +if [ -f /etc/os-release ]; then + . /etc/os-release + OS=$ID + VER=$VERSION_ID +else + echo "无法识别系统版本" + exit 1 +fi + +echo "检测到系统: $OS" + +install_snap_debian() { + apt update -y + apt install -y snapd + systemctl enable --now snapd.socket || true + + # 修复 snap 路径 + ln -sf /var/lib/snapd/snap /snap + + # 安装 certbot + snap install --classic certbot + + # 软链 + ln -sf /snap/bin/certbot /usr/bin/certbot +} + +install_snap_rhel() { + # 启用 EPEL(部分系统需要) + if command -v dnf >/dev/null 2>&1; then + dnf install -y epel-release || true + dnf install -y snapd + systemctl enable --now snapd.socket || true + else + yum install -y epel-release || true + yum install -y snapd + systemctl enable --now snapd.socket || true + fi + + # snap 经典路径 + ln -sf /var/lib/snapd/snap /snap + + # 安装 certbot + snap install --classic certbot + + # 软链 + ln -sf /snap/bin/certbot /usr/bin/certbot +} + +case "$OS" in + ubuntu|debian) + install_snap_debian + ;; + centos|rhel|almalinux|rocky) + install_snap_rhel + ;; + fedora) + dnf install -y snapd + systemctl enable --now snapd.socket || true + ln -sf /var/lib/snapd/snap /snap + snap install --classic certbot + ln -sf /snap/bin/certbot /usr/bin/certbot + ;; + *) + echo "不支持的系统: $OS" + exit 1 + ;; +esac + +echo "==============================" +echo "安装完成!验证版本:" +certbot --version || true +echo "==============================" \ No newline at end of file