命令说明
kvm.sh —— 虚拟机管理
kvm list 列出所有虚拟机 + 开机自启列表
kvm info [虚拟机] 看静态配置 + 运行信息(缺省取 list 第一个)
kvm create [虚拟机] <目录|qcow2路径> [方案] 创建(定义)虚拟机,创建后为 off,不自动运行
kvm switch [虚拟机] [方案] [on|off|run] 切换 xml 方案(缺省方案=虚拟机名.xml;缺省 run)
kvm run|boot|start|up|on [虚拟机] 开机(已开机不报错)
kvm stop|down|off [虚拟机] 关机(ACPI 优雅)
kvm reboot|restart [虚拟机] 重启
kvm poweroff|destroy [虚拟机] 强制断电
kvm autostart [--disable] [虚拟机] 设/取消 开机自启
kvm login|desktop [虚拟机] 进入图形桌面
kvm exec|console [虚拟机] 进入文本控制台
/etc/nixos/modules/system/service/qemu.nix
# 把组件脚本装成系统命令 `kvm`(内部函数名/用法均不变)
kvmCmd = pkgs.runCommand "kvm" { } ''
install -Dm755 ${c.script} $out/bin/kvm
sed -i '1s|.*|#!${pkgs.bash}/bin/bash|' $out/bin/kvm
'';
/etc/nixos/modules/system/service/qemu.nix
#!/usr/bin/env bash
# ============================================================
# kvm.sh —— 本机 KVM/libvirt 虚拟机管理命令
# 约定: 一台虚拟机 = /s500data/kvm/<虚拟机名>/ 一个子目录
# 方案文件: <虚拟机名>.xml(默认) 或 <虚拟机名>-<方案名>.xml
# 系统盘: <虚拟机名>.qcow2
# 建议: alias kvm=/s500data/kvm/kvm.sh 之后即可用 `kvm <命令>`
# ============================================================
set -uo pipefail
KVM_ROOT="/s500data/kvm"
CONN="qemu:///system"
V="virsh -c $CONN"
die(){ echo "错误: $*" >&2; exit 1; }
# ---------- 辅助 ----------
scheme_xml(){ # <vm> [scheme] -> 方案 xml 路径
if [ -z "${2:-}" ]; then echo "$KVM_ROOT/$1/$1.xml"; else echo "$KVM_ROOT/$1/$1-$2.xml"; fi
}
xml_name(){ sed -n 's:.*<name>\(.*\)</name>.*:\1:p' "$1" 2>/dev/null | head -1; }
list_vms(){ # 所有虚拟机(含 .qcow2 或 .xml 的子目录),按名排序
local d
for d in "$KVM_ROOT"/*/; do
[ -d "$d" ] || continue
if compgen -G "${d}*.qcow2" >/dev/null || compgen -G "${d}*.xml" >/dev/null; then
basename "$d"
fi
done | sort
}
dom_of_vm(){ # 该虚拟机当前已定义的域(按“磁盘路径归属”识别),无则空
local vm="$1" d
while read -r d; do
[ -n "$d" ] || continue
if $V dumpxml --inactive "$d" 2>/dev/null | grep -q "source file='$KVM_ROOT/$vm/"; then
echo "$d"; return 0
fi
done < <($V list --all --name 2>/dev/null | sed '/^$/d')
return 1
}
vm_for(){ # 目标虚拟机:给了就用;没给取 list 第一个
if [ -n "${1:-}" ]; then echo "$1"; return; fi
local f; f="$(list_vms | head -1)"
[ -n "$f" ] || die "没有可用虚拟机"
echo "$f"
}
avail_schemes(){ ls "$KVM_ROOT/$1/$1"*.xml 2>/dev/null | xargs -n1 basename 2>/dev/null | tr '\n' ' '; }
usage(){
cat <<'EOF'
kvm.sh —— 虚拟机管理
kvm list 列出所有虚拟机 + 开机自启列表
kvm info [虚拟机] 看静态配置 + 运行信息(缺省取 list 第一个)
kvm create [虚拟机] <目录|qcow2路径> [方案] 创建(定义)虚拟机,创建后为 off,不自动运行
kvm switch [虚拟机] [方案] [on|off|run] 切换 xml 方案(缺省方案=虚拟机名.xml;缺省 run)
kvm run|boot|start|up|on [虚拟机] 开机(已开机不报错)
kvm stop|down|off [虚拟机] 关机(ACPI 优雅)
kvm reboot|restart [虚拟机] 重启
kvm poweroff|destroy [虚拟机] 强制断电
kvm autostart [--disable] [虚拟机] 设/取消 开机自启
kvm login|desktop [虚拟机] 进入图形桌面
kvm exec|console [虚拟机] 进入文本控制台
方案名: 空=默认(<虚拟机>.xml) | 例如 fast / t14 / max2 → <虚拟机>-<方案>.xml
EOF
}
# ---------- 命令 ----------
cmd_list(){
printf "%-14s %-20s %-9s %-6s %s\n" "虚拟机" "当前方案/域" "状态" "自启" "磁盘"
local vm cur st as disk
for vm in $(list_vms); do
cur="$(dom_of_vm "$vm" || true)"
if [ -n "$cur" ]; then
st="$($V domstate "$cur" 2>/dev/null)"; [ "$st" = running ] && st="运行中" || st="已关机"
as="$($V dominfo "$cur" 2>/dev/null | awk '/^Autostart:/{print $2}')"
[ "$as" = enable ] && as="是" || as="否"
else
st="未定义"; as="-"; cur="—"
fi
disk="$(ls "$KVM_ROOT/$vm"/*.qcow2 2>/dev/null | xargs -n1 basename 2>/dev/null | tr '\n' ' ')"
printf "%-14s %-20s %-9s %-6s %s\n" "$vm" "$cur" "$st" "$as" "${disk:-—}"
done
echo
echo "【开机自启列表】"
$V list --autostart --all 2>/dev/null
}
cmd_info(){
local vm cur X
vm="$(vm_for "${1:-}")"
echo "==================== 虚拟机: $vm ===================="
echo "实例目录: $KVM_ROOT/$vm"
cur="$(dom_of_vm "$vm" || true)"
if [ -n "$cur" ]; then
echo "当前域 : $cur ($($V domstate "$cur" 2>/dev/null))"
X="$($V dumpxml --inactive "$cur" 2>/dev/null)"
else
echo "当前域 : (未定义)"
X="$(cat "$KVM_ROOT/$vm/$vm.xml" 2>/dev/null)"
fi
[ -n "$X" ] || die "无可用配置(既未定义域,也无默认 $vm.xml)"
echo "---- 静态配置 (KVM 设定) ----"
echo "$X" | sed -n 's/^[[:space:]]*//;s/^\(<name>\|<uuid>\|<vcpu[^>]*>\|<memory[^>]*>\|<currentMemory[^>]*>\|.*machine=[^ ]*\|<source file=.*\|<model type=.*\|<mac address=.*\|<graphics type=.*\|<source network=.*\)/\1/p'
echo "---- 运行信息 ----"
if [ -n "$cur" ] && [ "$($V domstate "$cur" 2>/dev/null)" = running ]; then
$V domstats "$cur" 2>/dev/null | grep -E "state.state|cpu.time|vcpu.current|vcpu.maximum|balloon.rss|block.1.(rd|wr)_bytes|net.0.(rx|tx).bytes" | sed 's/^/ /'
echo " -- 磁盘 --"; $V domblklist "$cur" 2>/dev/null | sed 's/^/ /'
echo " -- 网卡 --"; $V domiflist "$cur" 2>/dev/null | sed 's/^/ /'
echo " -- IP --"; $V domifaddr "$cur" --source lease 2>/dev/null | sed 's/^/ /'
else
echo " (未运行)"
fi
echo "---- 存储池 / 卷 ----"
$V pool-list --all 2>/dev/null | sed 's/^/ /'
$V vol-list "$vm" 2>/dev/null | sed 's/^/ /'
echo "---- 网络 ----"
$V net-list --all 2>/dev/null | sed 's/^/ /'
ip -4 addr show virbr0 2>/dev/null | awk '/inet /{print " virbr0 "$2}'
}
cmd_create(){
local a1="${1:-}" a2="${2:-}" a3="${3:-}" vm path sc dir disk xml cur
[ -n "$a1" ] || die "用法: kvm create [虚拟机] <目录|qcow2> [方案]"
if [[ "$a1" == */* || -e "$a1" ]]; then vm=""; path="$a1"; sc="$a2"; else vm="$a1"; path="$a2"; sc="$a3"; fi
[ -n "$path" ] || die "缺少路径"
if [ -d "$path" ]; then dir="${path%/}"; else dir="$(dirname "$path")"; fi
[ -n "$vm" ] || vm="$(basename "$dir")"
if [ -d "$path" ]; then disk="$dir/$vm.qcow2"; else disk="$path"; fi
xml="$(scheme_xml "$vm" "$sc")"
[ -d "$dir" ] || die "目录不存在: $dir"
[ -f "$disk" ] || die "磁盘不存在: $disk"
[ -f "$xml" ] || die "方案文件不存在: $xml (现有: $(avail_schemes "$vm"))"
if ! $V pool-info "$vm" >/dev/null 2>&1; then
$V pool-define-as "$vm" dir --target "$dir" >/dev/null 2>&1
$V pool-build "$vm" >/dev/null 2>&1; $V pool-start "$vm" >/dev/null 2>&1; $V pool-autostart "$vm" >/dev/null 2>&1
echo "已建存储池: $vm → $dir"
fi
cur="$(dom_of_vm "$vm" || true)"
if [ -n "$cur" ]; then
[ "$($V domstate "$cur" 2>/dev/null)" = running ] && die "虚拟机 $vm 正在运行($cur),请先 stop/poweroff 再 create"
$V undefine "$cur" --nvram >/dev/null 2>&1 || $V undefine "$cur" >/dev/null 2>&1
fi
$V define "$xml" >/dev/null || die "define 失败: $xml"
echo "已创建: $vm 方案=${sc:-默认} 域='$(xml_name "$xml")' 状态=off(create 不自动运行)"
}
cmd_switch(){
local vm="${1:-}" sc="${2:-}" mode="${3:-}"
case "$sc" in on|off|run|start) mode="$sc"; sc="";; esac
[ -n "$mode" ] || mode="run"
vm="$(vm_for "$vm")"
local dir="$KVM_ROOT/$vm" xml cur dn
xml="$(scheme_xml "$vm" "$sc")"
[ -f "$xml" ] || die "找不到方案文件: $xml (现有: $(avail_schemes "$vm"))"
dn="$(xml_name "$xml")"; [ -n "$dn" ] || die "方案文件缺 <name>: $xml"
cur="$(dom_of_vm "$vm" || true)"
if [ -n "$cur" ]; then
[ "$($V domstate "$cur" 2>/dev/null)" = running ] && $V destroy "$cur" >/dev/null 2>&1
{ $V undefine "$cur" --nvram >/dev/null 2>&1 || $V undefine "$cur" >/dev/null 2>&1; }
fi
$V define "$xml" >/dev/null || die "define 失败: $xml"
echo "已切换: $vm 方案=${sc:-(默认)} → 域 '$dn' (${xml##*/})"
if [ "$mode" = off ]; then echo "(off:切换后未启动)"; return 0; fi
$V start "$dn" >/dev/null && echo "已启动 '$dn'"
}
cmd_run(){
local vm cur dx
vm="$(vm_for "${1:-}")"
cur="$(dom_of_vm "$vm" || true)"
if [ -z "$cur" ]; then
dx="$KVM_ROOT/$vm/$vm.xml"
[ -f "$dx" ] || die "虚拟机 $vm 未定义,且无默认方案 $vm.xml(请先 create)"
cur="$(xml_name "$dx")"; $V define "$dx" >/dev/null || die "define 失败: $dx"
echo "未定义 → 已按默认方案 $vm.xml 定义域 '$cur'"
fi
if [ "$($V domstate "$cur" 2>/dev/null)" = running ]; then echo "虚拟机 $vm('$cur')已在运行"; return 0; fi
$V start "$cur" >/dev/null && echo "已启动 $vm('$cur')"
}
cmd_stop(){
local vm cur; vm="$(vm_for "${1:-}")"; cur="$(dom_of_vm "$vm" || true)"
[ -n "$cur" ] || die "虚拟机 $vm 未定义"
[ "$($V domstate "$cur" 2>/dev/null)" = running ] || { echo "虚拟机 $vm 未运行"; return 0; }
$V shutdown "$cur" >/dev/null && echo "已发送关机(ACPI): $vm('$cur')"
}
cmd_reboot(){
local vm cur; vm="$(vm_for "${1:-}")"; cur="$(dom_of_vm "$vm" || true)"
[ -n "$cur" ] || die "虚拟机 $vm 未定义"
[ "$($V domstate "$cur" 2>/dev/null)" = running ] || die "虚拟机 $vm 未运行,无法重启"
$V reboot "$cur" >/dev/null && echo "已重启: $vm('$cur')"
}
cmd_poweroff(){
local vm cur; vm="$(vm_for "${1:-}")"; cur="$(dom_of_vm "$vm" || true)"
[ -n "$cur" ] || die "虚拟机 $vm 未定义"
[ "$($V domstate "$cur" 2>/dev/null)" = running ] || { echo "虚拟机 $vm 未运行"; return 0; }
$V destroy "$cur" >/dev/null && echo "已强制断电: $vm('$cur')"
}
cmd_autostart(){
local disable=0 vm cur
if [ "${1:-}" = "--disable" ]; then disable=1; shift; fi
vm="$(vm_for "${1:-}")"; cur="$(dom_of_vm "$vm" || true)"
[ -n "$cur" ] || die "虚拟机 $vm 未定义"
if [ "$disable" = 1 ]; then $V autostart --disable "$cur" >/dev/null && echo "已取消开机自启: $vm('$cur')"
else $V autostart "$cur" >/dev/null && echo "已设为开机自启: $vm('$cur')"; fi
}
cmd_login(){
local vm cur; vm="$(vm_for "${1:-}")"; cur="$(dom_of_vm "$vm" || true)"
[ -n "$cur" ] || die "虚拟机 $vm 未定义"
[ "$($V domstate "$cur" 2>/dev/null)" = running ] || die "虚拟机 $vm 未运行,请先 run"
command -v virt-viewer >/dev/null || die "缺 virt-viewer"
echo "打开桌面: $vm('$cur')..."; exec virt-viewer -c "$CONN" "$cur"
}
cmd_console(){
local vm cur; vm="$(vm_for "${1:-}")"; cur="$(dom_of_vm "$vm" || true)"
[ -n "$cur" ] || die "虚拟机 $vm 未定义"
[ "$($V domstate "$cur" 2>/dev/null)" = running ] || die "虚拟机 $vm 未运行,请先 run"
echo "进入文本控制台: $vm('$cur') (退出: Ctrl+] )"
exec $V console "$cur"
}
# ---------- 分发 ----------
cmd="${1:-}"; [ $# -gt 0 ] && shift
case "$cmd" in
list|ls) cmd_list ;;
info) cmd_info "${1:-}" ;;
create) cmd_create "${1:-}" "${2:-}" "${3:-}" ;;
switch|sw) cmd_switch "${1:-}" "${2:-}" "${3:-}" ;;
run|boot|start|up|on) cmd_run "${1:-}" ;;
stop|down|off) cmd_stop "${1:-}" ;;
reboot|restart) cmd_reboot "${1:-}" ;;
poweroff|destroy|destory) cmd_poweroff "${1:-}" ;;
autostart) cmd_autostart "${1:-}" "${2:-}" ;;
login|desktop|view) cmd_login "${1:-}" ;;
exec|console) cmd_console "${1:-}" ;;
""|-h|--help|help) usage ;;
*) echo "未知命令: $cmd"; echo; usage; exit 2 ;;
esac