diff --git a/package-lock.json b/package-lock.json
index 5ad1e1b..c47a320 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -644,9 +644,6 @@
"cpu": [
"arm"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -660,9 +657,6 @@
"cpu": [
"arm"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -676,9 +670,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -692,9 +683,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -708,9 +696,6 @@
"cpu": [
"loong64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -724,9 +709,6 @@
"cpu": [
"loong64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -740,9 +722,6 @@
"cpu": [
"ppc64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -756,9 +735,6 @@
"cpu": [
"ppc64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -772,9 +748,6 @@
"cpu": [
"riscv64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -788,9 +761,6 @@
"cpu": [
"riscv64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -804,9 +774,6 @@
"cpu": [
"s390x"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -820,9 +787,6 @@
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -836,9 +800,6 @@
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
diff --git a/src/components/ConfigForm.vue b/src/components/ConfigForm.vue
index b9177b9..8d0407f 100644
--- a/src/components/ConfigForm.vue
+++ b/src/components/ConfigForm.vue
@@ -141,6 +141,7 @@ function isFieldVisible(field) {
const dep = field.dependsOn
const val = formData.value[dep.key]
if ('value' in dep) return val === dep.value
+ if ('valueIn' in dep) return dep.valueIn.includes(val)
if ('valueNotIn' in dep) return !dep.valueNotIn.includes(val)
return true
}
diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue
index 9df45f9..2bd549c 100644
--- a/src/components/Sidebar.vue
+++ b/src/components/Sidebar.vue
@@ -60,7 +60,7 @@
diff --git a/src/schemas/index.js b/src/schemas/index.js
index 709091c..8d7543c 100644
--- a/src/schemas/index.js
+++ b/src/schemas/index.js
@@ -95,6 +95,11 @@ import { sersyncSchema, generateSersyncXml } from './sersync'
import { vsftpdSchema, generateVsftpdConf } from './vsftpd'
import { subversionSchema, generateSubversionConf } from './subversion'
+// ============ 网络配置 ============
+import { networkRhelIfcfgSchema, generateNetworkRhelIfcfg } from './network-rhel-ifcfg'
+import { networkRhelNmKeyfileSchema, generateNetworkRhelNmKeyfile } from './network-rhel-nmkeyfile'
+import { networkUbuntuNetplanSchema, generateNetworkUbuntuNetplan } from './network-ubuntu-netplan'
+
// ============ 数据库中间件 ============
import { mycatSchema, generateMycatServerXml } from './mycat'
@@ -183,6 +188,10 @@ export const schemas = {
subversion: subversionSchema,
// 数据库中间件
mycat: mycatSchema,
+ // 网络配置
+ 'network-rhel-ifcfg': networkRhelIfcfgSchema,
+ 'network-rhel-nmkeyfile': networkRhelNmKeyfileSchema,
+ 'network-ubuntu-netplan': networkUbuntuNetplanSchema,
}
// ============================================================
@@ -254,6 +263,10 @@ export const generators = {
subversion: generateSubversionConf,
// 数据库中间件
mycat: generateMycatServerXml,
+ // 网络配置
+ 'network-rhel-ifcfg': generateNetworkRhelIfcfg,
+ 'network-rhel-nmkeyfile': generateNetworkRhelNmKeyfile,
+ 'network-ubuntu-netplan': generateNetworkUbuntuNetplan,
}
// ============================================================
@@ -351,6 +364,11 @@ export const categories = [
icon: 'Connection',
items: ['keepalived', 'haproxy', 'lvs'],
},
+ {
+ name: '网络配置',
+ icon: 'Connection',
+ items: ['network-rhel-ifcfg', 'network-rhel-nmkeyfile', 'network-ubuntu-netplan'],
+ },
{
name: '系统服务',
icon: 'Setting',
diff --git a/src/schemas/network-rhel-ifcfg.js b/src/schemas/network-rhel-ifcfg.js
new file mode 100644
index 0000000..a0e9ad4
--- /dev/null
+++ b/src/schemas/network-rhel-ifcfg.js
@@ -0,0 +1,458 @@
+// RHEL / CentOS 7-8 ifcfg 风格网络配置
+// 覆盖: CentOS 7, RHEL 7, CentOS 8/Stream 8, RHEL 8, Rocky 8, AlmaLinux 8
+// 配置文件位于 /etc/sysconfig/network-scripts/ifcfg-
+
+export const networkRhelIfcfgSchema = {
+ id: 'network-rhel-ifcfg',
+ name: 'RHEL/CentOS ifcfg',
+ icon: 'Connection',
+ category: '网络配置',
+ description: 'RHEL/CentOS 7-8 ifcfg 网络脚本(含 Bond/Bridge/VLAN)',
+ format: 'conf',
+ fileName: 'ifcfg-eth0',
+ groups: [
+ {
+ title: '系统版本',
+ fields: [
+ {
+ key: 'osVersion',
+ label: '操作系统版本',
+ type: 'select',
+ options: [
+ { label: 'CentOS 7', value: 'centos7' },
+ { label: 'RHEL 7', value: 'rhel7' },
+ { label: 'CentOS 8 / Stream 8', value: 'centos8' },
+ { label: 'RHEL 8', value: 'rhel8' },
+ { label: 'Rocky Linux 8 / AlmaLinux 8', value: 'el8-clone' },
+ ],
+ default: 'centos7',
+ tip: '影响配置文件注释与建议说明,ifcfg 语法本身通用',
+ },
+ ],
+ },
+ {
+ title: '配置类型',
+ fields: [
+ {
+ key: 'configType',
+ label: '配置类型',
+ type: 'select',
+ options: [
+ { label: '单网卡 - 静态 IP', value: 'static' },
+ { label: '单网卡 - DHCP', value: 'dhcp' },
+ { label: 'Bond - 链路聚合', value: 'bond' },
+ { label: 'Bridge - 桥接', value: 'bridge' },
+ { label: 'VLAN - 子接口', value: 'vlan' },
+ ],
+ default: 'static',
+ },
+ ],
+ },
+ {
+ title: '主接口配置',
+ fields: [
+ {
+ key: 'interface',
+ label: '主接口名',
+ type: 'text',
+ default: 'eth0',
+ tip: 'ifcfg-<此名称>,例如 eth0 / ens33 / enp0s3',
+ },
+ {
+ key: 'bootproto',
+ label: '协议',
+ type: 'select',
+ options: [
+ { label: 'static / none - 静态', value: 'none' },
+ { label: 'dhcp - 自动获取', value: 'dhcp' },
+ ],
+ default: 'none',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'ipaddr',
+ label: 'IP 地址',
+ type: 'text',
+ default: '192.168.1.10',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'prefix',
+ label: '子网前缀 (CIDR)',
+ type: 'number',
+ min: 1,
+ max: 32,
+ default: 24,
+ tip: '例如 24 = 255.255.255.0',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'gateway',
+ label: '默认网关',
+ type: 'text',
+ default: '192.168.1.1',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'dns1',
+ label: '主 DNS',
+ type: 'text',
+ default: '8.8.8.8',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'dns2',
+ label: '备用 DNS',
+ type: 'text',
+ default: '8.8.4.4',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'routeMetric',
+ label: '路由 Metric',
+ type: 'number',
+ min: 0,
+ max: 9999,
+ default: 0,
+ tip: '默认路由优先级,值越小优先级越高;0 表示不输出',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'onboot',
+ label: '开机自动启用',
+ type: 'switch',
+ default: true,
+ },
+ {
+ key: 'ipv6',
+ label: '禁用 IPv6',
+ type: 'switch',
+ default: true,
+ tip: '在 ifcfg 中写入 IPV6INIT=no',
+ },
+ ],
+ },
+ {
+ title: 'Bond 配置',
+ fields: [
+ {
+ key: 'bondName',
+ label: 'Bond 接口名',
+ type: 'text',
+ default: 'bond0',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'bondMode',
+ label: 'Bond 模式',
+ type: 'select',
+ options: [
+ { label: '0 - balance-rr (轮询)', value: '0' },
+ { label: '1 - active-backup (主备)', value: '1' },
+ { label: '2 - balance-xor (XOR)', value: '2' },
+ { label: '3 - broadcast (广播)', value: '3' },
+ { label: '4 - 802.3ad (LACP 动态聚合)', value: '4' },
+ { label: '5 - balance-tlb (自适应发送负载)', value: '5' },
+ { label: '6 - balance-alb (自适应负载均衡)', value: '6' },
+ ],
+ default: '4',
+ tip: '模式 4 需要交换机端开启 LACP;模式 1 兼容性最好',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'miimon',
+ label: 'MII 监控间隔 (ms)',
+ type: 'number',
+ min: 0,
+ max: 10000,
+ default: 100,
+ tip: '链路状态检测间隔,0 表示关闭',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'lacpRate',
+ label: 'LACP 速率',
+ type: 'select',
+ options: [
+ { label: 'slow (默认 30s)', value: 'slow' },
+ { label: 'fast (1s)', value: 'fast' },
+ ],
+ default: 'fast',
+ tip: '仅 mode=4 (802.3ad) 生效',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'bondSlaves',
+ label: '成员网卡 (逗号分隔)',
+ type: 'text',
+ default: 'eth0,eth1',
+ tip: '会被设为 SLAVE=yes,MASTER=',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'primarySlave',
+ label: '主网卡 (可选)',
+ type: 'text',
+ default: '',
+ placeholder: '留空使用默认',
+ tip: '仅 mode=1 active-backup 时作为活动网卡,可留空',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ ],
+ },
+ {
+ title: 'Bridge 配置',
+ fields: [
+ {
+ key: 'bridgeName',
+ label: 'Bridge 接口名',
+ type: 'text',
+ default: 'br0',
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgePorts',
+ label: '桥接成员 (逗号分隔)',
+ type: 'text',
+ default: 'eth0',
+ tip: '成员网卡会写成 BRIDGE=',
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgeStp',
+ label: '启用 STP 生成树',
+ type: 'switch',
+ default: false,
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ ],
+ },
+ {
+ title: 'VLAN 配置',
+ fields: [
+ {
+ key: 'vlanParent',
+ label: '父接口',
+ type: 'text',
+ default: 'eth0',
+ tip: '例如 eth0,生成的子接口为 eth0.',
+ dependsOn: { key: 'configType', value: 'vlan' },
+ },
+ {
+ key: 'vlanId',
+ label: 'VLAN ID',
+ type: 'number',
+ min: 1,
+ max: 4094,
+ default: 100,
+ dependsOn: { key: 'configType', value: 'vlan' },
+ },
+ ],
+ },
+ ],
+}
+
+function genIfcfgFile(fileName, fields, header) {
+ const lines = []
+ if (header) lines.push(header)
+ for (const [k, v] of Object.entries(fields)) {
+ if (v === undefined || v === null) continue
+ lines.push(`${k}=${v}`)
+ }
+ return lines.join('\n')
+}
+
+export function generateNetworkRhelIfcfg(config) {
+ const out = []
+ const ts = new Date().toLocaleString('zh-CN')
+ out.push(`# =========================================================`)
+ out.push(`# RHEL/CentOS ifcfg 网络配置 - 由 ConfTemplate 生成`)
+ out.push(`# 生成时间: ${ts}`)
+ out.push(`# 目标目录: /etc/sysconfig/network-scripts/`)
+ out.push(`# 配置类型: ${config.configType}`)
+ out.push(`# =========================================================`)
+ out.push(``)
+ out.push(`# 应用步骤:`)
+ out.push(`# 1. 将下面每个 ifcfg-xxx 内容保存到对应文件`)
+ out.push(`# 2. 执行: systemctl restart network # CentOS 7`)
+ out.push(`# nmcli connection reload # CentOS 8+ (NetworkManager)`)
+ out.push(`# nmcli connection up `)
+ out.push(``)
+ out.push(`# -----------------------------------------------------------`)
+
+ const ipv6Line = config.ipv6 ? 'IPV6INIT=no' : null
+
+ if (config.configType === 'static' || config.configType === 'dhcp') {
+ const bootproto = config.configType === 'dhcp' ? 'dhcp' : (config.bootproto || 'none')
+ const base = {
+ TYPE: 'Ethernet',
+ PROXY_METHOD: 'none',
+ BROWSER_ONLY: 'no',
+ BOOTPROTO: bootproto,
+ DEFROUTE: 'yes',
+ IPV4_FAILURE_FATAL: 'no',
+ NAME: config.interface,
+ DEVICE: config.interface,
+ ONBOOT: config.onboot ? 'yes' : 'no',
+ }
+ if (config.ipv6) base.IPV6INIT = 'no'
+ if (config.configType === 'static') {
+ Object.assign(base, {
+ IPADDR: config.ipaddr,
+ PREFIX: String(config.prefix),
+ GATEWAY: config.gateway,
+ DNS1: config.dns1,
+ DNS2: config.dns2,
+ })
+ if (config.routeMetric) base.METRIC = String(config.routeMetric)
+ }
+ out.push(``)
+ out.push(`# >>> 文件: ifcfg-${config.interface}`)
+ out.push(`# >>> 接口 ${config.interface} (${config.configType === 'dhcp' ? 'DHCP' : '静态 IP'})`)
+ out.push(genIfcfgFile(`ifcfg-${config.interface}`, base))
+ out.push(``)
+ }
+
+ if (config.configType === 'bond') {
+ // bond 主接口
+ const bondOpts = [`mode=${config.bondMode}`, `miimon=${config.miimon}`]
+ if (config.bondMode === '4') {
+ bondOpts.push(`lacp_rate=${config.lacpRate}`)
+ }
+ if (config.primarySlave) {
+ bondOpts.push(`primary=${config.primarySlave}`)
+ }
+ const master = {
+ TYPE: 'Bond',
+ BONDING_MASTER: 'yes',
+ PROXY_METHOD: 'none',
+ BROWSER_ONLY: 'no',
+ BOOTPROTO: config.bootproto || 'none',
+ DEFROUTE: 'yes',
+ IPV4_FAILURE_FATAL: 'no',
+ NAME: config.bondName,
+ DEVICE: config.bondName,
+ ONBOOT: config.onboot ? 'yes' : 'no',
+ BONDING_OPTS: `"${bondOpts.join(' ')}"`,
+ IPADDR: config.ipaddr,
+ PREFIX: String(config.prefix),
+ GATEWAY: config.gateway,
+ DNS1: config.dns1,
+ DNS2: config.dns2,
+ }
+ if (config.ipv6) master.IPV6INIT = 'no'
+ if (config.routeMetric) master.METRIC = String(config.routeMetric)
+ out.push(``)
+ out.push(`# >>> 文件: ifcfg-${config.bondName}`)
+ out.push(`# >>> Bond 主接口 ${config.bondName} (mode=${config.bondMode})`)
+ out.push(genIfcfgFile(`ifcfg-${config.bondName}`, master))
+ out.push(``)
+
+ // 成员接口
+ const slaves = String(config.bondSlaves || '').split(',').map((s) => s.trim()).filter(Boolean)
+ for (const slv of slaves) {
+ const slave = {
+ TYPE: 'Ethernet',
+ PROXY_METHOD: 'none',
+ BROWSER_ONLY: 'no',
+ BOOTPROTO: 'none',
+ DEFROUTE: 'no',
+ IPV4_FAILURE_FATAL: 'no',
+ NAME: slv,
+ DEVICE: slv,
+ ONBOOT: config.onboot ? 'yes' : 'no',
+ MASTER: config.bondName,
+ SLAVE: 'yes',
+ }
+ if (config.ipv6) slave.IPV6INIT = 'no'
+ out.push(`# >>> 文件: ifcfg-${slv}`)
+ out.push(`# >>> Bond 成员 ${slv} → ${config.bondName}`)
+ out.push(genIfcfgFile(`ifcfg-${slv}`, slave))
+ out.push(``)
+ }
+ out.push(`# 提示: mode=4 (LACP) 需要在交换机端开启 LACP 动态聚合`)
+ }
+
+ if (config.configType === 'bridge') {
+ // bridge 主接口
+ const br = {
+ TYPE: 'Bridge',
+ PROXY_METHOD: 'none',
+ BROWSER_ONLY: 'no',
+ BOOTPROTO: config.bootproto || 'none',
+ DEFROUTE: 'yes',
+ IPV4_FAILURE_FATAL: 'no',
+ NAME: config.bridgeName,
+ DEVICE: config.bridgeName,
+ ONBOOT: config.onboot ? 'yes' : 'no',
+ STP: config.bridgeStp ? 'yes' : 'no',
+ IPADDR: config.ipaddr,
+ PREFIX: String(config.prefix),
+ GATEWAY: config.gateway,
+ DNS1: config.dns1,
+ DNS2: config.dns2,
+ }
+ if (config.ipv6) br.IPV6INIT = 'no'
+ if (config.routeMetric) br.METRIC = String(config.routeMetric)
+ out.push(``)
+ out.push(`# >>> 文件: ifcfg-${config.bridgeName}`)
+ out.push(`# >>> Bridge ${config.bridgeName} (STP=${config.bridgeStp ? 'on' : 'off'})`)
+ out.push(genIfcfgFile(`ifcfg-${config.bridgeName}`, br))
+ out.push(``)
+
+ // 成员接口
+ const ports = String(config.bridgePorts || '').split(',').map((s) => s.trim()).filter(Boolean)
+ for (const p of ports) {
+ const port = {
+ TYPE: 'Ethernet',
+ PROXY_METHOD: 'none',
+ BROWSER_ONLY: 'no',
+ BOOTPROTO: 'none',
+ DEFROUTE: 'no',
+ IPV4_FAILURE_FATAL: 'no',
+ NAME: p,
+ DEVICE: p,
+ ONBOOT: config.onboot ? 'yes' : 'no',
+ BRIDGE: config.bridgeName,
+ }
+ if (config.ipv6) port.IPV6INIT = 'no'
+ out.push(`# >>> 文件: ifcfg-${p}`)
+ out.push(`# >>> Bridge 成员 ${p} → ${config.bridgeName}`)
+ out.push(genIfcfgFile(`ifcfg-${p}`, port))
+ out.push(``)
+ }
+ }
+
+ if (config.configType === 'vlan') {
+ const subName = `${config.vlanParent}.${config.vlanId}`
+ const vlan = {
+ TYPE: 'Ethernet',
+ PROXY_METHOD: 'none',
+ BROWSER_ONLY: 'no',
+ BOOTPROTO: config.bootproto || 'none',
+ DEFROUTE: 'yes',
+ IPV4_FAILURE_FATAL: 'no',
+ NAME: subName,
+ DEVICE: subName,
+ ONBOOT: config.onboot ? 'yes' : 'no',
+ VLAN: 'yes',
+ IPADDR: config.ipaddr,
+ PREFIX: String(config.prefix),
+ GATEWAY: config.gateway,
+ DNS1: config.dns1,
+ DNS2: config.dns2,
+ }
+ if (config.ipv6) vlan.IPV6INIT = 'no'
+ if (config.routeMetric) vlan.METRIC = String(config.routeMetric)
+ out.push(``)
+ out.push(`# >>> 文件: ifcfg-${subName}`)
+ out.push(`# >>> VLAN 子接口 ${subName} (VLAN ID=${config.vlanId})`)
+ out.push(genIfcfgFile(`ifcfg-${subName}`, vlan))
+ out.push(``)
+ out.push(`# 提示: 父接口 ${config.vlanParent} 也需要 ONBOOT=yes 并处于 up 状态`)
+ out.push(`# 若使用基于接口名的隐式 VLAN,VLAN=yes 即可;`)
+ out.push(`# 若用显式 VLAN_ID,可增加: PHYSDEV=${config.vlanParent}, VLAN_ID=${config.vlanId}`)
+ }
+
+ return out.join('\n')
+}
diff --git a/src/schemas/network-rhel-nmkeyfile.js b/src/schemas/network-rhel-nmkeyfile.js
new file mode 100644
index 0000000..191c92d
--- /dev/null
+++ b/src/schemas/network-rhel-nmkeyfile.js
@@ -0,0 +1,430 @@
+// RHEL / CentOS 9 NetworkManager keyfile 风格网络配置
+// 覆盖: CentOS Stream 9, RHEL 9, Rocky Linux 9, AlmaLinux 9
+// 配置文件位于 /etc/NetworkManager/system-connections/.nmconnection
+// 说明: RHEL 9 / CentOS Stream 9 已弃用 network-scripts (ifcfg-*),必须使用 NM keyfile
+
+export const networkRhelNmKeyfileSchema = {
+ id: 'network-rhel-nmkeyfile',
+ name: 'RHEL/CentOS 9 NM',
+ icon: 'Connection',
+ category: '网络配置',
+ description: 'RHEL/CentOS 9 NetworkManager keyfile(含 Bond/Bridge/VLAN)',
+ format: 'ini',
+ fileName: 'connection.nmconnection',
+ groups: [
+ {
+ title: '系统版本',
+ fields: [
+ {
+ key: 'osVersion',
+ label: '操作系统版本',
+ type: 'select',
+ options: [
+ { label: 'CentOS Stream 9', value: 'centos-stream9' },
+ { label: 'RHEL 9', value: 'rhel9' },
+ { label: 'Rocky Linux 9', value: 'rocky9' },
+ { label: 'AlmaLinux 9', value: 'alma9' },
+ ],
+ default: 'centos-stream9',
+ tip: '语法相同,仅影响注释与说明',
+ },
+ ],
+ },
+ {
+ title: '配置类型',
+ fields: [
+ {
+ key: 'configType',
+ label: '配置类型',
+ type: 'select',
+ options: [
+ { label: '单网卡 - 静态 IP', value: 'static' },
+ { label: '单网卡 - DHCP', value: 'dhcp' },
+ { label: 'Bond - 链路聚合', value: 'bond' },
+ { label: 'Bridge - 桥接', value: 'bridge' },
+ { label: 'VLAN - 子接口', value: 'vlan' },
+ ],
+ default: 'static',
+ },
+ ],
+ },
+ {
+ title: '主接口配置',
+ fields: [
+ {
+ key: 'interface',
+ label: '主接口名',
+ type: 'text',
+ default: 'eth0',
+ tip: 'nmconnection 文件名建议与接口名一致',
+ },
+ {
+ key: 'ipaddr',
+ label: 'IP 地址',
+ type: 'text',
+ default: '192.168.1.10',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'prefix',
+ label: '子网前缀 (CIDR)',
+ type: 'number',
+ min: 1,
+ max: 32,
+ default: 24,
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'gateway',
+ label: '默认网关',
+ type: 'text',
+ default: '192.168.1.1',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'dns',
+ label: 'DNS (逗号分隔)',
+ type: 'text',
+ default: '8.8.8.8,8.8.4.4',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'routeMetric',
+ label: '路由 Metric',
+ type: 'number',
+ min: 0,
+ max: 9999,
+ default: 0,
+ tip: '默认路由优先级,值越小优先级越高;0 表示不输出',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'autoconnect',
+ label: '开机自动启用',
+ type: 'switch',
+ default: true,
+ },
+ {
+ key: 'ipv6Method',
+ label: 'IPv6 方法',
+ type: 'select',
+ options: [
+ { label: 'disabled - 禁用', value: 'disabled' },
+ { label: 'auto - 自动', value: 'auto' },
+ { label: 'ignore - 忽略', value: 'ignore' },
+ ],
+ default: 'disabled',
+ },
+ ],
+ },
+ {
+ title: 'Bond 配置',
+ fields: [
+ {
+ key: 'bondName',
+ label: 'Bond 接口名',
+ type: 'text',
+ default: 'bond0',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'bondMode',
+ label: 'Bond 模式',
+ type: 'select',
+ options: [
+ { label: 'balance-rr (0)', value: 'balance-rr' },
+ { label: 'active-backup (1)', value: 'active-backup' },
+ { label: 'balance-xor (2)', value: 'balance-xor' },
+ { label: 'broadcast (3)', value: 'broadcast' },
+ { label: '802.3ad (4 LACP)', value: '802.3ad' },
+ { label: 'balance-tlb (5)', value: 'balance-tlb' },
+ { label: 'balance-alb (6)', value: 'balance-alb' },
+ ],
+ default: '802.3ad',
+ tip: 'mode 4 需要交换机端开启 LACP',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'miimon',
+ label: 'MII 监控间隔 (ms)',
+ type: 'number',
+ min: 0,
+ max: 10000,
+ default: 100,
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'lacpRate',
+ label: 'LACP 速率',
+ type: 'select',
+ options: [
+ { label: 'slow (30s)', value: 'slow' },
+ { label: 'fast (1s)', value: 'fast' },
+ ],
+ default: 'fast',
+ tip: '仅 802.3ad 模式生效',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'bondSlaves',
+ label: '成员网卡 (逗号分隔)',
+ type: 'text',
+ default: 'eth0,eth1',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ ],
+ },
+ {
+ title: 'Bridge 配置',
+ fields: [
+ {
+ key: 'bridgeName',
+ label: 'Bridge 接口名',
+ type: 'text',
+ default: 'br0',
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgePorts',
+ label: '桥接成员 (逗号分隔)',
+ type: 'text',
+ default: 'eth0',
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgeStp',
+ label: '启用 STP',
+ type: 'switch',
+ default: false,
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgeHelloTime',
+ label: 'STP Hello 时间 (秒)',
+ type: 'number',
+ min: 1,
+ max: 10,
+ default: 2,
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ ],
+ },
+ {
+ title: 'VLAN 配置',
+ fields: [
+ {
+ key: 'vlanParent',
+ label: '父接口',
+ type: 'text',
+ default: 'eth0',
+ dependsOn: { key: 'configType', value: 'vlan' },
+ },
+ {
+ key: 'vlanId',
+ label: 'VLAN ID',
+ type: 'number',
+ min: 1,
+ max: 4094,
+ default: 100,
+ dependsOn: { key: 'configType', value: 'vlan' },
+ },
+ ],
+ },
+ ],
+}
+
+function genKeyfile(sections, headerComment) {
+ const lines = []
+ if (headerComment) {
+ lines.push(headerComment)
+ }
+ for (const [section, kv] of Object.entries(sections)) {
+ lines.push(`[${section}]`)
+ for (const [k, v] of Object.entries(kv)) {
+ if (v === undefined || v === null || v === '') continue
+ lines.push(`${k}=${v}`)
+ }
+ lines.push('')
+ }
+ return lines.join('\n')
+}
+
+export function generateNetworkRhelNmKeyfile(config) {
+ const out = []
+ const ts = new Date().toLocaleString('zh-CN')
+ out.push(`# =========================================================`)
+ out.push(`# RHEL/CentOS 9 NetworkManager keyfile 配置 - 由 ConfTemplate 生成`)
+ out.push(`# 生成时间: ${ts}`)
+ out.push(`# 目标目录: /etc/NetworkManager/system-connections/`)
+ out.push(`# 配置类型: ${config.configType}`)
+ out.push(`# =========================================================`)
+ out.push(``)
+ out.push(`# 应用步骤:`)
+ out.push(`# 1. 将下面每个 .nmconnection 内容保存到对应文件`)
+ out.push(`# 2. 设置权限: chmod 600 .nmconnection`)
+ out.push(`# 3. 执行: nmcli connection reload`)
+ out.push(`# nmcli connection up `)
+ out.push(``)
+ out.push(`# keyfile 必须有 600 权限,否则 NetworkManager 会忽略它`)
+ out.push(`# -----------------------------------------------------------`)
+
+ const autoStr = config.autoconnect ? 'true' : 'false'
+ const dnsList = String(config.dns || '').split(',').map((s) => s.trim()).filter(Boolean).join(';')
+ const dnsSuffix = dnsList ? `${dnsList};` : ''
+
+ if (config.configType === 'static' || config.configType === 'dhcp') {
+ const sections = {
+ connection: {
+ id: config.interface,
+ type: 'ethernet',
+ 'interface-name': config.interface,
+ autoconnect: autoStr,
+ },
+ ipv4: {},
+ ipv6: { method: config.ipv6Method },
+ }
+ if (config.configType === 'dhcp') {
+ sections.ipv4.method = 'auto'
+ } else {
+ sections.ipv4.method = 'manual'
+ sections.ipv4.addresses = `${config.ipaddr}/${config.prefix}`
+ sections.ipv4.gateway = config.gateway
+ if (dnsSuffix) sections.ipv4.dns = dnsSuffix
+ if (config.routeMetric) sections.ipv4['route-metric'] = String(config.routeMetric)
+ }
+ out.push(``)
+ out.push(`# >>> 文件: ${config.interface}.nmconnection`)
+ out.push(`# >>> 接口 ${config.interface} (${config.configType === 'dhcp' ? 'DHCP' : '静态'})`)
+ out.push(genKeyfile(sections))
+ }
+
+ if (config.configType === 'bond') {
+ // bond 主接口
+ const bondSections = {
+ connection: {
+ id: config.bondName,
+ type: 'bond',
+ 'interface-name': config.bondName,
+ autoconnect: autoStr,
+ },
+ bond: {
+ miimon: String(config.miimon),
+ mode: config.bondMode,
+ },
+ ipv4: {
+ method: 'manual',
+ addresses: `${config.ipaddr}/${config.prefix}`,
+ gateway: config.gateway,
+ dns: dnsSuffix,
+ },
+ ipv6: { method: config.ipv6Method },
+ }
+ if (config.routeMetric) bondSections.ipv4['route-metric'] = String(config.routeMetric)
+ if (config.bondMode === '802.3ad') {
+ bondSections.bond['lacp_rate'] = config.lacpRate
+ }
+ out.push(``)
+ out.push(`# >>> 文件: ${config.bondName}.nmconnection`)
+ out.push(`# >>> Bond 主接口 ${config.bondName} (mode=${config.bondMode})`)
+ out.push(genKeyfile(bondSections))
+
+ // 成员接口
+ const slaves = String(config.bondSlaves || '').split(',').map((s) => s.trim()).filter(Boolean)
+ for (const slv of slaves) {
+ const slaveSections = {
+ connection: {
+ id: slv,
+ type: 'ethernet',
+ 'interface-name': slv,
+ master: config.bondName,
+ 'slave-type': 'bond',
+ autoconnect: autoStr,
+ },
+ ipv4: { method: 'disabled' },
+ ipv6: { method: config.ipv6Method },
+ }
+ out.push(`# >>> 文件: ${slv}.nmconnection`)
+ out.push(`# >>> Bond 成员 ${slv} → ${config.bondName}`)
+ out.push(genKeyfile(slaveSections))
+ }
+ out.push(`# 提示: mode=802.3ad 需要在交换机端开启 LACP`)
+ }
+
+ if (config.configType === 'bridge') {
+ // bridge 主接口
+ const brSections = {
+ connection: {
+ id: config.bridgeName,
+ type: 'bridge',
+ 'interface-name': config.bridgeName,
+ autoconnect: autoStr,
+ },
+ bridge: {
+ stp: config.bridgeStp ? 'yes' : 'no',
+ 'hello-time': String(config.bridgeHelloTime),
+ },
+ ipv4: {
+ method: 'manual',
+ addresses: `${config.ipaddr}/${config.prefix}`,
+ gateway: config.gateway,
+ dns: dnsSuffix,
+ },
+ ipv6: { method: config.ipv6Method },
+ }
+ if (config.routeMetric) brSections.ipv4['route-metric'] = String(config.routeMetric)
+ out.push(``)
+ out.push(`# >>> 文件: ${config.bridgeName}.nmconnection`)
+ out.push(`# >>> Bridge ${config.bridgeName} (STP=${config.bridgeStp ? 'on' : 'off'})`)
+ out.push(genKeyfile(brSections))
+
+ // 成员接口
+ const ports = String(config.bridgePorts || '').split(',').map((s) => s.trim()).filter(Boolean)
+ for (const p of ports) {
+ const portSections = {
+ connection: {
+ id: p,
+ type: 'ethernet',
+ 'interface-name': p,
+ master: config.bridgeName,
+ 'slave-type': 'bridge',
+ autoconnect: autoStr,
+ },
+ ipv4: { method: 'disabled' },
+ ipv6: { method: config.ipv6Method },
+ }
+ out.push(`# >>> 文件: ${p}.nmconnection`)
+ out.push(`# >>> Bridge 成员 ${p} → ${config.bridgeName}`)
+ out.push(genKeyfile(portSections))
+ }
+ }
+
+ if (config.configType === 'vlan') {
+ const subName = `${config.vlanParent}.${config.vlanId}`
+ const vlanSections = {
+ connection: {
+ id: subName,
+ type: 'vlan',
+ 'interface-name': subName,
+ autoconnect: autoStr,
+ },
+ vlan: {
+ parent: config.vlanParent,
+ id: String(config.vlanId),
+ },
+ ipv4: {
+ method: 'manual',
+ addresses: `${config.ipaddr}/${config.prefix}`,
+ gateway: config.gateway,
+ dns: dnsSuffix,
+ },
+ ipv6: { method: config.ipv6Method },
+ }
+ if (config.routeMetric) vlanSections.ipv4['route-metric'] = String(config.routeMetric)
+ out.push(``)
+ out.push(`# >>> 文件: ${subName}.nmconnection`)
+ out.push(`# >>> VLAN 子接口 ${subName} (VLAN ID=${config.vlanId})`)
+ out.push(genKeyfile(vlanSections))
+ }
+
+ return out.join('\n')
+}
diff --git a/src/schemas/network-ubuntu-netplan.js b/src/schemas/network-ubuntu-netplan.js
new file mode 100644
index 0000000..a938f5f
--- /dev/null
+++ b/src/schemas/network-ubuntu-netplan.js
@@ -0,0 +1,329 @@
+// Ubuntu Netplan 网络配置
+// 覆盖: Ubuntu 18.04 / 20.04 / 22.04 / 24.04
+// 配置文件位于 /etc/netplan/.yaml
+
+export const networkUbuntuNetplanSchema = {
+ id: 'network-ubuntu-netplan',
+ name: 'Ubuntu Netplan',
+ icon: 'Connection',
+ category: '网络配置',
+ description: 'Ubuntu Netplan 配置(含 Bond/Bridge/VLAN)',
+ format: 'yaml',
+ fileName: '01-netcfg.yaml',
+ groups: [
+ {
+ title: '系统版本',
+ fields: [
+ {
+ key: 'osVersion',
+ label: '操作系统版本',
+ type: 'select',
+ options: [
+ { label: 'Ubuntu 18.04 LTS', value: '18.04' },
+ { label: 'Ubuntu 20.04 LTS', value: '20.04' },
+ { label: 'Ubuntu 22.04 LTS', value: '22.04' },
+ { label: 'Ubuntu 24.04 LTS', value: '24.04' },
+ ],
+ default: '22.04',
+ tip: '18.04 推荐 renderer: networkd;20.04+ 同样支持',
+ },
+ {
+ key: 'renderer',
+ label: '渲染器',
+ type: 'select',
+ options: [
+ { label: 'networkd - systemd-networkd', value: 'networkd' },
+ { label: 'NetworkManager', value: 'NetworkManager' },
+ ],
+ default: 'networkd',
+ tip: '服务器默认 networkd;桌面版可改 NetworkManager',
+ },
+ ],
+ },
+ {
+ title: '配置类型',
+ fields: [
+ {
+ key: 'configType',
+ label: '配置类型',
+ type: 'select',
+ options: [
+ { label: '单网卡 - 静态 IP', value: 'static' },
+ { label: '单网卡 - DHCP', value: 'dhcp' },
+ { label: 'Bond - 链路聚合', value: 'bond' },
+ { label: 'Bridge - 桥接', value: 'bridge' },
+ { label: 'VLAN - 子接口', value: 'vlan' },
+ ],
+ default: 'static',
+ },
+ ],
+ },
+ {
+ title: '主接口配置',
+ fields: [
+ {
+ key: 'interface',
+ label: '主接口名',
+ type: 'text',
+ default: 'eth0',
+ tip: 'Ubuntu 18.04 常见 ens33 / enp0s3;20.04+ 已恢复 eth0',
+ },
+ {
+ key: 'ipaddr',
+ label: 'IP 地址',
+ type: 'text',
+ default: '192.168.1.10',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'prefix',
+ label: '子网前缀 (CIDR)',
+ type: 'number',
+ min: 1,
+ max: 32,
+ default: 24,
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'gateway',
+ label: '默认网关',
+ type: 'text',
+ default: '192.168.1.1',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'dns',
+ label: 'DNS (逗号分隔)',
+ type: 'text',
+ default: '8.8.8.8,8.8.4.4',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ {
+ key: 'routeMetric',
+ label: '路由 Metric',
+ type: 'number',
+ min: 0,
+ max: 9999,
+ default: 0,
+ tip: '默认路由优先级,值越小优先级越高;0 表示不输出',
+ dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] },
+ },
+ ],
+ },
+ {
+ title: 'Bond 配置',
+ fields: [
+ {
+ key: 'bondName',
+ label: 'Bond 接口名',
+ type: 'text',
+ default: 'bond0',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'bondMode',
+ label: 'Bond 模式',
+ type: 'select',
+ options: [
+ { label: 'balance-rr (0)', value: 'balance-rr' },
+ { label: 'active-backup (1)', value: 'active-backup' },
+ { label: 'balance-xor (2)', value: 'balance-xor' },
+ { label: 'broadcast (3)', value: 'broadcast' },
+ { label: '802.3ad (4 LACP)', value: '802.3ad' },
+ { label: 'balance-tlb (5)', value: 'balance-tlb' },
+ { label: 'balance-alb (6)', value: 'balance-alb' },
+ ],
+ default: '802.3ad',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'miimon',
+ label: 'MII 监控间隔 (ms)',
+ type: 'number',
+ min: 0,
+ max: 10000,
+ default: 100,
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'lacpRate',
+ label: 'LACP 速率',
+ type: 'select',
+ options: [
+ { label: 'slow (30s)', value: 'slow' },
+ { label: 'fast (1s)', value: 'fast' },
+ ],
+ default: 'fast',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ {
+ key: 'bondSlaves',
+ label: '成员网卡 (逗号分隔)',
+ type: 'text',
+ default: 'eth0,eth1',
+ dependsOn: { key: 'configType', value: 'bond' },
+ },
+ ],
+ },
+ {
+ title: 'Bridge 配置',
+ fields: [
+ {
+ key: 'bridgeName',
+ label: 'Bridge 接口名',
+ type: 'text',
+ default: 'br0',
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgePorts',
+ label: '桥接成员 (逗号分隔)',
+ type: 'text',
+ default: 'eth0',
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ {
+ key: 'bridgeStp',
+ label: '启用 STP',
+ type: 'switch',
+ default: false,
+ dependsOn: { key: 'configType', value: 'bridge' },
+ },
+ ],
+ },
+ {
+ title: 'VLAN 配置',
+ fields: [
+ {
+ key: 'vlanParent',
+ label: '父接口',
+ type: 'text',
+ default: 'eth0',
+ dependsOn: { key: 'configType', value: 'vlan' },
+ },
+ {
+ key: 'vlanId',
+ label: 'VLAN ID',
+ type: 'number',
+ min: 1,
+ max: 4094,
+ default: 100,
+ dependsOn: { key: 'configType', value: 'vlan' },
+ },
+ ],
+ },
+ ],
+}
+
+// 生成接口下的 IPv4 静态配置块(addresses / routes / nameservers)
+// base = 接口下属性的缩进空格数(如接口在 2 空格则 base=4)
+function ipBlock(base, extraLines) {
+ const pad = (n) => ' '.repeat(base + n)
+ const lines = []
+ lines.push(`${pad(0)}addresses:`)
+ lines.push(`${pad(2)}- ${this.cidr}`)
+ if (this.gateway) {
+ lines.push(`${pad(0)}routes:`)
+ lines.push(`${pad(2)}- to: default`)
+ lines.push(`${pad(4)}via: ${this.gateway}`)
+ if (this.routeMetric) {
+ lines.push(`${pad(4)}metric: ${this.routeMetric}`)
+ }
+ }
+ if (this.dnsList.length > 0) {
+ lines.push(`${pad(0)}nameservers:`)
+ lines.push(`${pad(2)}addresses:`)
+ for (const d of this.dnsList) lines.push(`${pad(4)}- ${d}`)
+ }
+ return lines.concat(extraLines || [])
+}
+
+export function generateNetworkUbuntuNetplan(config) {
+ const dnsList = String(config.dns || '').split(',').map((s) => s.trim()).filter(Boolean)
+ const cidr = `${config.ipaddr}/${config.prefix}`
+ const ts = new Date().toLocaleString('zh-CN')
+
+ // 绑定 ipBlock 的 this 上下文
+ const ctx = { cidr, gateway: config.gateway, dnsList, routeMetric: config.routeMetric }
+ const genIp = (base, extra) => ipBlock.call(ctx, base, extra)
+
+ const body = [] // 每个元素是一行,已包含完整缩进
+
+ if (config.configType === 'static') {
+ body.push(` ${config.interface}:`)
+ body.push(...genIp(4))
+ } else if (config.configType === 'dhcp') {
+ body.push(` ${config.interface}:`)
+ body.push(` dhcp4: true`)
+ if (dnsList.length > 0) {
+ body.push(` nameservers:`)
+ body.push(` addresses:`)
+ for (const d of dnsList) body.push(` - ${d}`)
+ }
+ } else if (config.configType === 'bond') {
+ const slaves = String(config.bondSlaves || '').split(',').map((s) => s.trim()).filter(Boolean)
+ body.push(` ethernets:`)
+ for (const slv of slaves) {
+ body.push(` ${slv}:`)
+ body.push(` dhcp4: false`)
+ body.push(` optional: true`)
+ }
+ body.push(` bonds:`)
+ body.push(` ${config.bondName}:`)
+ body.push(` interfaces:`)
+ for (const slv of slaves) body.push(` - ${slv}`)
+ body.push(...genIp(6, [
+ ` parameters:`,
+ ` mode: ${config.bondMode}`,
+ ` miimon: ${config.miimon}`,
+ ...(config.bondMode === '802.3ad' ? [` lacp-rate: ${config.lacpRate}`] : []),
+ ]))
+ } else if (config.configType === 'bridge') {
+ const ports = String(config.bridgePorts || '').split(',').map((s) => s.trim()).filter(Boolean)
+ body.push(` ethernets:`)
+ for (const p of ports) {
+ body.push(` ${p}:`)
+ body.push(` dhcp4: false`)
+ body.push(` optional: true`)
+ }
+ body.push(` bridges:`)
+ body.push(` ${config.bridgeName}:`)
+ body.push(` interfaces:`)
+ for (const p of ports) body.push(` - ${p}`)
+ body.push(...genIp(6, [
+ ` parameters:`,
+ ` stp: ${config.bridgeStp ? 'true' : 'false'}`,
+ ]))
+ } else if (config.configType === 'vlan') {
+ const subName = `${config.vlanParent}.${config.vlanId}`
+ body.push(` ethernets:`)
+ body.push(` ${config.vlanParent}:`)
+ body.push(` dhcp4: false`)
+ body.push(` optional: true`)
+ body.push(` vlans:`)
+ body.push(` ${subName}:`)
+ body.push(...genIp(6, [
+ ` id: ${config.vlanId}`,
+ ` link: ${config.vlanParent}`,
+ ]))
+ }
+
+ const header = []
+ header.push(`# =========================================================`)
+ header.push(`# Ubuntu Netplan 配置 - 由 ConfTemplate 生成`)
+ header.push(`# 生成时间: ${ts}`)
+ header.push(`# 目标文件: /etc/netplan/01-netcfg.yaml`)
+ header.push(`# 配置类型: ${config.configType}`)
+ header.push(`# =========================================================`)
+ header.push(`#`)
+ header.push(`# 应用步骤:`)
+ header.push(`# 1. 将本文件保存到 /etc/netplan/01-netcfg.yaml`)
+ header.push(`# 2. 权限: sudo chmod 600 /etc/netplan/01-netcfg.yaml`)
+ header.push(`# 3. 应用: sudo netplan apply`)
+ header.push(`# 4. 验证: ip a / networkctl status`)
+ header.push(`#`)
+ header.push(`# 注意: netplan 文件需 600 权限,否则 apply 会拒绝执行`)
+ header.push(``)
+
+ return header.join('\n') + `network:\n version: 2\n renderer: ${config.renderer}\n` + body.join('\n') + '\n'
+}