fix(nodes): Set Cert from Panel uses the node's own web cert for node inbounds

For an inbound deployed to a node, the button read the central panel's webCertFile/webKeyFile and inserted paths that don't exist on the node, crashing the node's Xray on startup.

Add a token-accessible GET /panel/api/server/getWebCertFiles that returns a panel's own web cert/key paths, Remote.GetWebCertFiles to fetch it from a node, and GET /panel/api/nodes/webCert/:id to proxy it. setCertFromPanel now calls the node endpoint for a node-assigned inbound and the local settings otherwise, warning instead of inserting wrong paths on error/empty.

Fixes #4854
This commit is contained in:
MHSanaei
2026-06-03 16:41:02 +02:00
parent 42d7f62d8b
commit 55d6729955
8 changed files with 209 additions and 17 deletions
@@ -194,7 +194,7 @@ export default function InboundFormModal({
setCertFromPanel,
clearCertFiles,
onSecurityChange,
} = useSecurityActions({ form, setSaving, messageApi });
} = useSecurityActions({ form, setSaving, messageApi, nodeId: typeof wNodeId === 'number' ? wNodeId : null });
const toggleExternalProxy = (on: boolean) => {
if (on) {
@@ -13,13 +13,17 @@ interface UseSecurityActionsArgs {
form: FormInstance<InboundFormValues>;
setSaving: Dispatch<SetStateAction<boolean>>;
messageApi: MessageInstance;
// Node the inbound is deployed to (null = central panel). "Set Cert from
// Panel" must read the node's own cert paths for a node-assigned inbound —
// the central panel's paths don't exist on the node. See issue #4854.
nodeId: number | null;
}
// Server-side TLS / Reality key + certificate generation handlers for the
// inbound modal's security tab. Each talks to a /panel server endpoint and
// writes the result back into the form. Lifted out of InboundFormModal so
// the modal body stays focused on orchestration.
export function useSecurityActions({ form, setSaving, messageApi }: UseSecurityActionsArgs) {
export function useSecurityActions({ form, setSaving, messageApi, nodeId }: UseSecurityActionsArgs) {
const { t } = useTranslation();
const genRealityKeypair = async () => {
@@ -112,22 +116,28 @@ export function useSecurityActions({ form, setSaving, messageApi }: UseSecurityA
const setCertFromPanel = async (certName: number) => {
setSaving(true);
try {
const msg = await HttpUtil.post('/panel/setting/all', undefined, { silent: true });
if (msg?.success) {
const obj = msg.obj as { webCertFile?: string; webKeyFile?: string };
if (!obj.webCertFile && !obj.webKeyFile) {
messageApi.warning(t('pages.inbounds.setDefaultCertEmpty'));
return;
}
form.setFieldValue(
['streamSettings', 'tlsSettings', 'certificates', certName, 'certificateFile'],
obj.webCertFile ?? '',
);
form.setFieldValue(
['streamSettings', 'tlsSettings', 'certificates', certName, 'keyFile'],
obj.webKeyFile ?? '',
);
// Node-assigned inbounds run on the node, so their cert files must be the
// node's own paths (fetched through the central panel), not this panel's.
const msg = typeof nodeId === 'number'
? await HttpUtil.get(`/panel/api/nodes/webCert/${nodeId}`, undefined, { silent: true })
: await HttpUtil.post('/panel/setting/all', undefined, { silent: true });
if (!msg?.success) {
messageApi.warning(msg?.msg || t('pages.inbounds.setDefaultCertEmpty'));
return;
}
const obj = msg.obj as { webCertFile?: string; webKeyFile?: string };
if (!obj?.webCertFile && !obj?.webKeyFile) {
messageApi.warning(t('pages.inbounds.setDefaultCertEmpty'));
return;
}
form.setFieldValue(
['streamSettings', 'tlsSettings', 'certificates', certName, 'certificateFile'],
obj.webCertFile ?? '',
);
form.setFieldValue(
['streamSettings', 'tlsSettings', 'certificates', certName, 'keyFile'],
obj.webKeyFile ?? '',
);
} finally {
setSaving(false);
}