diff --git a/internal/services/ansible.go b/internal/services/ansible.go index 0e2b1ac..3ea4cdc 100644 --- a/internal/services/ansible.go +++ b/internal/services/ansible.go @@ -459,6 +459,10 @@ func (s *AnsibleService) updateInventoryFile() { keyPath := s.resolveSSHKeyPath(h.SSHKey) line += fmt.Sprintf(" ansible_ssh_private_key_file=%s", keyPath) } + // 写入主机自定义变量(如 bmc_ip, bmc_user 等) + for k, v := range h.Vars { + line += fmt.Sprintf(" %s=%s", k, v) + } lines = append(lines, line) } lines = append(lines, "") diff --git a/internal/services/templates.go b/internal/services/templates.go index 1403af5..e3bfc56 100644 --- a/internal/services/templates.go +++ b/internal/services/templates.go @@ -1390,22 +1390,39 @@ func GetPlaybookTemplates() []PlaybookTemplate { Description: "使用ipmitool配置服务器BMC/IPMI管理口IP地址", Icon: "🔧", Tags: []string{"ipmi", "bmc", "ipmitool", "hardware"}, Content: `--- +# ============================================================ +# IPMI配置BMC地址 +# 使用方法: +# 在主机管理中为每台主机设置变量,例如: +# bmc_ip=10.10.10.1 +# bmc_netmask=255.255.255.0 +# bmc_gateway=10.10.10.1 +# bmc_user=admin +# bmc_password=Admin@123 +# 变量会写入inventory,本Playbook自动读取每台主机的值 +# 未设置的主机将使用下方默认值 +# ============================================================ - name: IPMI配置BMC地址 hosts: "{{ target_hosts | default('all') }}" become: yes vars: - # BMC网络配置 - bmc_ip: "10.10.10.100" - bmc_netmask: "255.255.255.0" - bmc_gateway: "10.10.10.1" - # BMC用户配置 - bmc_user: "admin" - bmc_password: "Admin@123" + # 默认值(主机变量会覆盖这些) + default_bmc_netmask: "255.255.255.0" + default_bmc_gateway: "10.10.10.1" + default_bmc_user: "admin" + default_bmc_password: "Admin@123" bmc_channel: 1 - # 是否启用DHCP(设为true则忽略上面的静态IP配置) + # 是否启用DHCP(设为true则忽略静态IP配置) bmc_dhcp: false tasks: + - name: 检查BMC变量 + assert: + that: + - bmc_ip is defined + fail_msg: "❌ 主机 {{ inventory_hostname }} 未配置 bmc_ip 变量,请在主机管理中设置" + success_msg: "✅ {{ inventory_hostname }} → BMC IP: {{ bmc_ip }}" + - name: 安装ipmitool package: name: ipmitool @@ -1439,11 +1456,11 @@ func GetPlaybookTemplates() []PlaybookTemplate { when: not bmc_dhcp - name: 配置BMC子网掩码 - shell: ipmitool lan set {{ bmc_channel }} netmask {{ bmc_netmask }} + shell: ipmitool lan set {{ bmc_channel }} netmask {{ bmc_netmask | default(default_bmc_netmask) }} when: not bmc_dhcp - name: 配置BMC默认网关 - shell: ipmitool lan set {{ bmc_channel }} defgw ipaddr {{ bmc_gateway }} + shell: ipmitool lan set {{ bmc_channel }} defgw ipaddr {{ bmc_gateway | default(default_bmc_gateway) }} when: not bmc_dhcp - name: 设置BMC为DHCP模式 @@ -1452,8 +1469,8 @@ func GetPlaybookTemplates() []PlaybookTemplate { - name: 配置BMC用户密码 shell: | - ipmitool user set name {{ bmc_channel }} 2 {{ bmc_user }} 2>/dev/null || true - ipmitool user set password 2 {{ bmc_password }} + ipmitool user set name {{ bmc_channel }} 2 {{ bmc_user | default(default_bmc_user) }} 2>/dev/null || true + ipmitool user set password 2 {{ bmc_password | default(default_bmc_password) }} ipmitool user enable 2 ipmitool channel setaccess {{ bmc_channel }} 2 privilege=4 no_log: true @@ -1467,12 +1484,12 @@ func GetPlaybookTemplates() []PlaybookTemplate { - name: 显示最终配置 debug: msg: | - ═══════ BMC配置完成 ═══════ + ═══════ {{ inventory_hostname }} BMC配置完成 ═══════ 模式: {{ 'DHCP' if bmc_dhcp else '静态IP' }} IP: {{ bmc_ip }} - 掩码: {{ bmc_netmask }} - 网关: {{ bmc_gateway }} - 用户: {{ bmc_user }} + 掩码: {{ bmc_netmask | default(default_bmc_netmask) }} + 网关: {{ bmc_gateway | default(default_bmc_gateway) }} + 用户: {{ bmc_user | default(default_bmc_user) }} ═══════════════════════════ {{ bmc_verify.stdout }} `, diff --git a/web/dist/index.html b/web/dist/index.html index f590c86..88e33fe 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -845,6 +845,10 @@ async function openHostModal(id){
${S.groups.map(g=>`${esc(g.name)}`).join('')||'暂无主机组,可在主机页右侧面板创建'}
+
+
+ +
变量会写入 inventory,Playbook 中可用 {{ '{{' }} bmc_ip {{ '}}' }} 引用
`, '',``); const seg=$('#hf-auth'); @@ -858,7 +862,8 @@ async function openHostModal(id){ const auth=seg.querySelector('.on').dataset.v; const body={name,ip,port:+$('#hf-port').value||22,username:$('#hf-user').value.trim()||'root',auth_type:auth, password:auth==='password'?$('#hf-pwd').value:'',ssh_key:auth==='sshkey'?$('#hf-key').value:'', - groups:$$('#hf-groups .chip.on').map(c=>c.dataset.g)}; + groups:$$('#hf-groups .chip.on').map(c=>c.dataset.g), + vars:Object.fromEntries($('#hf-vars').value.split('\n').map(l=>l.trim()).filter(l=>l&&!l.startsWith('#')).map(l=>{const i=l.indexOf('=');return i>0?[l.slice(0,i).trim(),l.slice(i+1).trim()]:null;}).filter(Boolean))}; await withBtn($('#hf-save'),async()=>{ if(h)await api(`/api/hosts/${h.id}`,{method:'PUT',body}); else await api('/api/hosts',{method:'POST',body});