diff --git a/htool.conf b/htool.conf new file mode 100644 index 0000000..3ee0fea --- /dev/null +++ b/htool.conf @@ -0,0 +1,24 @@ +# ==================================================== +# Generated by htool commands +# ==================================================== + +interface eth1 +server 172.16.9.254 +pkgsroot /srv/htool/pkgs/ +wwwroot /var/www/html/linux +tftpboot /srv/pxelinux/tftpboot +startip 172.16.9.10 +endip 172.16.9.90 +dns 223.5.5.5 +osname rockylinux +osvers 9.7 +ospath rockylinux/9.7 +lrepo /srv/repos/rockylinux +bios /srv/pxelinux/tftpboot/boot/undionly.kpxe +uefi /srv/pxelinux/tftpboot/boot/bootx64.efi +dfmt http://172.16.9.254/linux/ks/dfmt.sh +ksaddr http://172.16.9.254/linux/ks/rhel.ks +ipxeboot http://172.16.9.254/linux/ks/ipxe.sh +repos http://172.16.9.254/linux/rockylinux/9.7 +vmlinuz http://172.16.9.254/linux/rockylinux/9.7/images/pxeboot/vmlinuz +initrd http://172.16.9.254/linux/rockylinux/9.7/images/pxeboot/initrd.img diff --git a/pkgs/ks/init.sh b/pkgs/ks/init.sh index 0c17134..b9c810d 100644 --- a/pkgs/ks/init.sh +++ b/pkgs/ks/init.sh @@ -14,14 +14,14 @@ set -e # 日志文件 LOG_FILE="/var/log/sunhpc-init.log" -FRONTEND_IP="172.16.9.254" +FRONTEND_IP='@osipaddr@' # HTTP 服务器地址 HTTP_SERVER="http://${FRONTEND_IP}" # YUM 仓库配置 YUM_REPO_NAME="sunhpc-local" -YUM_REPO_BASEURL="${HTTP_SERVER}/rocky/9.7" +YUM_REPO_BASEURL="${HTTP_SERVER}/@ospath@" YUM_REPO_GPGCHECK=0 # 主机名映射文件(在 HTTP 服务器上) @@ -79,7 +79,7 @@ check_network() { log_step "Checking network connectivity..." # 测试网络连接 - if ping -c 1 -W 3 172.16.9.254 &>/dev/null; then + if ping -c 1 -W 3 FRONTEND_IP &>/dev/null; then log_info "Network connectivity: OK" return 0 else @@ -164,8 +164,6 @@ $ip_addr $hostname $FRONTEND_IP cluster # Optional: Add other compute nodes here -# 172.16.9.1 cn001 -# 172.16.9.2 cn002 EOF # 尝试下载完整的集群 hosts 文件 diff --git a/pkgs/ks/ipxe.sh b/pkgs/ks/ipxe.sh index b559b2c..ca66c79 100644 --- a/pkgs/ks/ipxe.sh +++ b/pkgs/ks/ipxe.sh @@ -3,18 +3,18 @@ :main_menu menu iPXE boot menu -item rocky9 Install Rocky Linux 9 +item @osname@ Install @osrels@ @osvers@ item local Boot from Local Disk -choose --default rocky9 --timeout 5000 selected || goto local +choose --default @osname@ --timeout 5000 selected || goto local -:rocky9 -echo Booting RockyLinux from networking -kernel http://172.16.9.254/rocky/9.7/isolinux/vmlinuz \ - net.ifnames=0 biosdevname=0 \ - inst.repo=http://172.16.9.254/rocky/9.7 \ - inst.ks=http://172.16.9.254/ks/rhel.ks -initrd http://172.16.9.254/rocky/9.7/isolinux/initrd.img +:@osname@ +echo Booting @osrels@ from networking +kernel @vmlinuz@ \ + @kargs@ \ + @repos@ \ + @ksarg@ +initrd @initrds@ boot :local diff --git a/pkgs/ks/rhel.ks b/pkgs/ks/rhel.ks index a7be3ec..9287157 100644 --- a/pkgs/ks/rhel.ks +++ b/pkgs/ks/rhel.ks @@ -1,35 +1,28 @@ graphical - -%addon com_redhat_kdump --disable -%end - timezone Asia/Shanghai --utc -keyboard --xlayouts='us' +keyboard --xlayouts="us" lang en_US.UTF-8 - selinux --disabled -url --url="http://172.16.9.254/rocky/9.7" +url --url='@url@' -# Network information network --bootproto=dhcp --device=link --ipv6=auto --activate # Partition clearing information -#zerombr -#clearpart --all --initlabel -#autopart --type=lvm +# zerombr +# clearpart --all --initlabel +# ignoredisk --only-use=sda +# part /boot --fstype="xfs" --ondisk=sda --size=1024 +# part swap --fstype="swap" --ondisk=sda --size=4096 +# part / --fstype="xfs" --ondisk=sda --size=97278 +# part /home --fstype="xfs" --ondisk=sda --size=20480 +# autopart --type=lvm %include /tmp/diskinfo -#ignoredisk --only-use=sda -#part /boot --fstype="xfs" --ondisk=sda --size=1024 -#part swap --fstype="swap" --ondisk=sda --size=4096 -#part / --fstype="xfs" --ondisk=sda --size=97278 -#part /home --fstype="xfs" --ondisk=sda --size=20480 - %packages @^minimal-environment -@standard @development +@standard vim wget curl @@ -37,24 +30,23 @@ autofs nfs-utils nfs4-acl-tools sssd-nfs-idmap -pcp-pmda-nfsclient %end -# Root password -rootpw --iscrypted $6$muqhPjb0F9IM2/Fg$pPaVF7DTjs/zz91vHMrcL8jPLQoLFCjUUxHkIZao9C6OFbBPof2AtmTRfvO4Ix.8al3dnMz8/aAbd88sHSQTK. -user --name=kelvin --password=$6$kAz6MRJFIpIyhKuv$YZntcNpyoSYRMD6y5qmZIIBiklzaskqHWE4A0oXI8vX492bcL/.z6xF3MjVDgVJzZ0FaNDSy8BFeEhD9mfr67/ --iscrypted --gecos="kelvin" - -#reboot +rootpw --plaintext admin_b101 +user --name=dell --plaintext admin_b101 --gecos="dell" +reboot %pre --interpreter=/bin/bash -curl -o /tmp/dfmt.sh http://172.16.9.254/ks/dfmt.sh +curl -o /tmp/dfmt.sh @dfmt@ chmod +x /tmp/dfmt.sh - /tmp/dfmt.sh > /tmp/diskinfo %end %post -curl -s -o /tmp/init.sh http://172.16.9.254/ks/init.sh +curl -s -o /tmp/init.sh @init@ chmod +x /tmp/init.sh /tmp/init.sh %end + +%addon com_redhat_kdump --disable +%end diff --git a/sunhpc b/sunhpc index b614586..51eb09d 100755 --- a/sunhpc +++ b/sunhpc @@ -74,40 +74,7 @@ DEFAULT_NODES=("localhost") GLOBAL_NODES=() declare -A GLOBAL_PORT declare -A global_env - -ask() { - # 基本用法:ask "是否删除文件?" - 无默认值,必须输入 y/yes 或 n/no - # ask "是否继续?" "y" - 默认继续,直接回车相当于 y - # ask "是否继续?" "n" - 默认取消,直接回车相当于 n - # if ask "警告:即将删除所有文件,是否继续?" "n"; then - local prompt="$1" - local default="$2" - local answer - - # 构建提示信息 - if [ "$default" = "y" ]; then - prompt="$prompt [Y/n]: " - elif [ "$default" = "n" ]; then - prompt="$prompt [y/N]: " - else - prompt="$prompt [y/n]: " - fi - - while true; do - read -p "$prompt" answer - # 如果用户直接回车,使用默认值 - if [ -z "$answer" ] && [ -n "$default" ]; then - answer="$default" - fi - # 转换为小写进行比较 - answer=$(echo "$answer" | tr '[:upper:]' '[:lower:]') - case "$answer" in - y|yes) return 0 ;; - n|no) return 1 ;; - *) echo "请输入 y/yes 或 n/no" ;; - esac - done -} +declare -A global_treeinfo load_os_envs() { interfaces=$(ls /sys/class/net | grep -v lo) @@ -132,6 +99,49 @@ load_os_envs() { done } +load_conf_envs() { + + while read -r key val; do + # 跳过空行、注释行 + [[ -z "$key" || "$key" == \#* ]] && continue + + global_env["$key"]="$val" + done < "$config_file" +} + +parse_treeinfo() { + local treeinfo_file="${1:-.treeinfo}" + local current_section="" + + while IFS= read -r line || [[ -n "$line" ]]; do + # 跳过空行和注释行 + [[ -z "$line" || "$line" =~ ^[[:space:]]*\; ]] && continue + + # 匹配 section,例如 [general] + if [[ "$line" =~ ^\[([^]]+)\]$ ]]; then + current_section="${BASH_REMATCH[1]}" + continue + fi + + # 匹配 key = value + if [[ "$line" =~ ^[[:space:]]*([^=]+)[[:space:]]*=[[:space:]]*(.*)$ ]]; then + local key="${BASH_REMATCH[1]}" + local value="${BASH_REMATCH[2]}" + + key="${key#"${key%%[![:space:]]*}"}" + key="${key%"${key##*[![:space:]]}"}" + + # 去除值两端和中间的所有空格 + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + value=$(echo "$value" | tr -d ' ' | tr '[:upper:]' '[:lower:]') + + # 存储到关联数组,使用 "section.key" 作为键名 + global_treeinfo["${current_section}.${key}"]="$value" + fi + done < "$treeinfo_file" +} + getFileArgs(){ # 调用模式: getFileArgs myfile "true" 未指定文件时只返回错误码 # 调用模式: result=$(getFileArgs myfile "true") 返回文件 @@ -168,15 +178,44 @@ getFileArgs(){ fi } -# ====================== 核心函数:加载环境变量信息 ============== -load_conf_envs() { +getArgs() { + # getArgs interface iface e "msg" + local key="$1" + local result="$2" + local level="${3:-w}" # 默认级别为 warn + local msg="${4:-}" + local value="" - while read -r key val; do - # 跳过空行、注释行 - [[ -z "$key" || "$key" == \#* ]] && continue + # 优先级1:检查 GLOBAL_OPTS(命令行参数) + if [[ -n "${GLOBAL_OPTS[$key]}" ]]; then + value="${GLOBAL_OPTS[$key]}" + # 优先级2:检查 global_env(配置文件) + elif [[ -n "${global_env[$key]}" ]]; then + value="${global_env[$key]}" + fi - global_env["$key"]="$val" - done < "$config_file" + # 检查 GLOBAL_OPTS 数组中是否存在该键 + if [[ -n "$value" ]]; then + printf -v "$result" "%s" "$value" + return 0 + else + # 根据不同的级别处理错误 + case "$level" in + e) + [[ -n "$msg" ]] && error "$msg" || error "必须指定 --$key 参数" + ;; + w) + [[ -n "$msg" ]] && warn "$msg" || warn "未指定 --$key 参数,将使用默认值或跳过" + ;; + i) + [[ -n "$msg" ]] && info "$msg" || info "未指定 --$key 参数" + ;; + *) + [[ -n "$msg" ]] && info "$msg" || info "未指定 --$key 参数" + ;; + esac + return 1 + fi } # ============================================================ @@ -189,30 +228,6 @@ print_global_env() { echo "======================================" } -# ============================================================ -# 辅助函数:获取指定网卡IP -# ============================================================ -get_interface_ip() { - local interface="$1" - if ip link show "$interface" &>/dev/null; then - ip addr show "$interface" | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1 - else - echo "" - fi -} - -# ============================================================ -# 辅助函数:获取指定网卡MAC地址 -# ============================================================ -get_interface_mac() { - local interface="$1" - if ip link show "$interface" &>/dev/null; then - ip link show "$interface" | grep -oP '(?<=link/ether\s)[0-9a-f:]+' | head -1 - else - echo "" - fi -} - # ============================================================ # 辅助函数:快速获取系统信息摘要 # ============================================================ @@ -360,157 +375,6 @@ get_subnet_address() { } # ====================== pxe server functions ==================== -pxe_dhcp_build() { - log_debug "当前节点:${GLOBAL_NODES[*]}" - log_debug "公共参数:${PUBLIC_ARGS[*]}" - log_debug "私有参数:${PRIVATE_ARGS[*]}" - - interface="${GLOBAL_OPTS[interface]}" - ipaddr=${global_env[${interface}_ip]} - netcidr=${global_env[${interface}_mask]} - netmask=$(cidr_to_netmask 24) - subnet=$(get_subnet_address $ipaddr $netmask) - - IFS=. read -r i1 i2 i3 i4 <<< "$ipaddr" - start_ip="$i1.$i2.$i3.10" - ender_ip="$i1.$i2.$i3.90" - tests_ip="$i1.$i2.$i3.252" - - info "DHCP Interface name: $interface" - info "DHCP IP Address : $ipaddr" - info "DHCP IP Netmask : $netmask" - info "DHCP IP Subnets : $subnet" - info "DHCP Start IPAddr : $start_ip" - info "DHCP End IPAddr : $ender_ip" - - nopkgs=() - pkgs=("dhcp-server dhcp-common") - filter_pkgs pkgs nopkgs - - local cmd=("dnf -y install ${nopkgs[*]}") - [[ -z ${nopkgs[@]} ]] || run_with_progress "DNF 安装DHCP服务..." "$cmd" "$log_file" - - dhcp_config="/etc/dhcp/dhcpd.conf" - - { - echo "option domain-name \"`hostname -s`\";" - echo "option domain-name-servers 8.8.8.8;" - echo "default-lease-time 600;" - echo "max-lease-time 7200;" - echo "ddns-update-style none;" - echo "" - echo "allow booting;" - echo "allow bootp;" - echo "" - echo "option arch code 93 = unsigned integer 16;" - echo "" - echo "subnet ${subnet} netmask ${netmask} {" - echo " option routers ${ipaddr};" - echo " range $start_ip $ender_ip;" - echo "" - echo " next-server ${ipaddr};" - echo "" - echo ' host test_host_bond {' - echo ' option dhcp-client-identifier "test_host_bond";' - echo " fixed-address $tests_ip;" - echo ' }' - echo ' if option arch= 00:07 or option arch = 00:09 {' - echo ' # UEFI x64' - echo ' filename "/EFI/x86/grub.efi";' - echo ' } else if option arch = 00:0b {' - echo ' # UEFI ARM64' - echo ' filename "/EFI/aarch64/bootaa64.efi";' - echo ' } else {' - echo ' # Legcy BIOS' - echo ' filename "pxelinux.0";' - echo ' }' - echo '}' - } > ${dhcp_config} - - # sed -i 's/\$DHCPDARGS/eth0/' /usr/lib/systemd/system/dhcpd.service - dhcp_systemd="/usr/lib/systemd/system/dhcpd.service" - grep -q "DHCPDARGS" $dhcp_systemd &>/dev/null && \ - sed -i "s/\$DHCPDARGS/${interface}/" $dhcp_systemd &>/dev/null - - systemctl daemon-reload && systemctl restart dhcpd.service - systemctl is-enabled dhcpd &>/dev/null || systemctl enable dhcpd.service &>/dev/null - - systemctl is-active dhcpd &>/dev/null && info "DHCP 服务已经配置成功..." \ - || warn "DHCP 服务部署失败,请查看服务信息 systemctl status dhcpd.service" - -} - -pxe_tftp_build() { - log_debug "当前节点:${GLOBAL_NODES[*]}" - log_debug "公共参数:${PUBLIC_ARGS[*]}" - log_debug "私有参数:${PRIVATE_ARGS[*]}" - - nopkgs=() - pkgs=("tftp-server tftp syslinux") - filter_pkgs pkgs nopkgs - - local cmd=("dnf -y install ${nopkgs[*]}") - [[ -z ${nopkgs[@]} ]] || run_with_progress "DNF 安装TFTP服务..." "$cmd" "$log_file" - - tftp_server_conf="/usr/lib/systemd/system/tftp.service" - tftp_socket_conf="/usr/lib/systemd/system/tftp.socket" - tftp_pxe_dirs="/var/lib/tftpboot" - - [[ -e "$tftp_pxe_dirs" ]] || mkdir -p "$tftp_pxe_dirs/pxelinux.cfg" - chmod -R 755 $tftp_pxe_dirs - - # Pxe 启动文件配置 - src_pxelinux="/usr/share/syslinux" - src_files=("pxelinux.0 menu.c32 ldlinux.c32") - for fname in ${src_files[@]}; do - src_file="$src_pxelinux/$fname" - dst_file="$tftp_pxe_dirs/$fname" - - [[ -f $src_file ]] || warn "$src_file not found!" - - if [[ -f $dst_file ]]; then - info "The $fname already exists, Skipt shis copy..." - else - info "Copying the $fname to $dst_file" - /usr/bin/cp $src_file $dst_file - fi - done - - # 文件配置 - if ! getFileArgs myfile "true"; then - warn " 必须指定系统ISO文件或挂载位置,例如: " - warn " -f /root/Rocky-9.7-x86_64-dvd.iso 或 -f /mnt/cdrom/" - exit 1 - fi - - echo "------------> $myfile" - - exit 1 - - # -c 允许创建新文件(上传) -p 使用普通系统权限检查 - { - echo '[Unit]' - echo 'Description=Tftp Server' - echo 'Requires=tftp.socket' - echo 'Documentation=man:in.tftpd' - echo '' - echo '[Service]' - echo "ExecStart=/usr/sbin/in.tftpd -c -p -s "$tftp_pxe_dirs"" - echo 'StandardInput=socket' - echo '' - echo '[Install]' - echo 'WantedBy=multi-user.target' - echo 'Also=tftp-server.socket' - } > ${tftp_server_conf} - - systemctl daemon-reload && systemctl restart tftp.socket - systemctl is-enabled tftp.socket &>/dev/null || systemctl enable tftp.socket &>/dev/null - - info "TFTP 服务端口: 69" - systemctl is-active tftp.socket &>/dev/null && info "TFTP 服务已经配置成功..." \ - || warn "TFTP 服务部署失败,请查看服务信息 systemctl status tftp.socket" -} - pxe_http_build() { log_debug "当前节点:${GLOBAL_NODES[*]}" log_debug "公共参数:${PUBLIC_ARGS[*]}" @@ -530,12 +394,12 @@ pxe_http_build() { custom_conf="$http_root_conf/conf.d/sunhpc.conf" { - echo 'Alias /rocky97 /var/www/html/rocky97' + echo 'Alias /linux /var/www/html/linux' echo '' echo 'UseCanonicalName Off' echo "ServerName `hostname -f`" echo '' - echo "" + echo '' echo " Options Indexes FollowSymLinks" echo " AllowOverride None" echo " Require all granted" @@ -548,52 +412,12 @@ pxe_http_build() { #systemctl is-enabled httpd &>/dev/null || systemctl enable httpd &>/dev/null - info "HTTP 服务端口: 80,443" if systemctl is-active httpd &>/dev/null ; then - info "HTTP 服务已经配置成功..." - info "后续配置: " - info " 拷贝系统ISO文件到 $rocky_repo_src" - info " # rsync -ah --info=progress2 /mnt/ /srv/repos/rocky/9.7/" + info "HTTP 服务部署成功" + info "HTTP 服务端口: 80,443" else warn "HTTP 服务部署失败,请查看服务信息 systemctl status httpd.service" fi - - if getFileArgs myfile "true"; then - local mnt_iso_dirs=$myfile - if [[ -f $myfile ]]; then - mnt_dst_dirs="/mnt/cdrom" - [[ -e "$mnt_dst_dirs" ]] && umount $mnt_dst_dirs &>/dev/null || mkdir -p $mnt_dst_dirs - info "Mount path: $mnt_dst_dirs" - mount $myfile $mnt_dst_dirs &>/dev/null - mnt_iso_dirs=$mnt_dst_dirs - fi - - rpmname=$(find $mnt_iso_dirs -iname "rocky-release-*.rpm") - filename=$(basename "$rpmname" .rpm) - distro=$(echo "$filename" | cut -d'-' -f1) # rocky - version=$(echo "$filename" | cut -d'-' -f3) # 9.7 - distro=${distro:-"linux"} - version=${version:-"1.0"} - - rocky_repo_distro="/srv/repos/$distro" - rocky_repo_version="$rocky_repo_distro/$version" - rocky_html_dst="$http_html_dirs/$distro" - [[ ! -e $rocky_repo_version ]] && mkdir -p $rocky_repo_version - [[ ! -e $rocky_html_dst ]] && ln -s $rocky_repo_distro $rocky_html_dst - - info "拷贝系统镜像到本地: $rocky_repo_src" - info " 系统发行版: $distro" - info " 系统版本 : $version" - info " 源地址 : $mnt_iso_dirs" - info " 目的地址 : $rocky_repo_version" - - rsync -ah --info=progress2 "$mnt_iso_dirs" "$rocky_repo_version/" - info "测试访问: curl -L http://`hostname -s`/$distro/$version/" - else - info "后续配置: " - info " 拷贝系统ISO文件到 $rocky_repo_src" - info " # rsync -ah --info=progress2 /mnt/ /srv/repos/rocky/9.7/" - fi } pxe_dnsmasq_build() { @@ -608,53 +432,31 @@ pxe_dnsmasq_build() { local cmd=("dnf -y install ${nopkgs[*]}") [[ -z ${nopkgs[@]} ]] || run_with_progress "DNF 安装 dnsmasq 服务..." "$cmd" "$log_file" - # -n 非空True,-z 为空True - [[ -n ${global_env[interface]} ]] && iface=${global_env[interface]} || \ - error "请指定DHCP监听接口命名, 编辑 $config_file 文件, 例如: interface eth1" + getArgs interface iface e "必须指定一个网络接口,例如: -i/--interface eth1" + getArgs server ipaddr e "Check the configuration file $config_file: server " + getArgs tftpboot tftpboot e "Check the configuration file $config_file: tftpboot " + getArgs startip start_ip e "Check the configuration file $config_file: startip " + getArgs endip ender_ip e "Check the configuration file $config_file: endip " + getArgs dns dnsip e "Check the configuration file $config_file: dns " - [[ -n ${global_env[server]} ]] && ipaddr=${global_env[server]} || \ - error "请指定监听接口IP地址, 编辑 $config_file 文件, 例如: server 172.16.9.254" - - [[ -n ${global_env[dns]} ]] && dnsip=${global_env[dns]} || dnsip="223.5.5.5" - - [[ -n ${global_env[range]} ]] && iprange=${global_env[range]} || \ - error "请指定DHCP网络IP地址范围, 编辑 $config_file 文件, 例如: range 172.16.9.1,172.16.9.100" - - vmlinuz="http://$ipaddr/rocky/isolinux/vmlinuz" - [[ -n ${global_env[vmlinuz]} ]] && vmlinuz=${global_env[vmlinuz]} || \ - error "请指定vmlinuz web路径, 编辑 $config_file 文件, 例如: vmlinuz http://$ipaddr/rocky/isolinux/vmlinuz" - - initrd="http://$ipaddr/rocky/isolinux/initrd.img" - [[ -n ${global_env[initrd]} ]] && initrd=${global_env[initrd]} || \ - error "请指定initrd web路径, 编辑 $config_file 文件, 例如: initrd http://$ipaddr/rocky/isolinux/initrd.img" - [[ -n ${global_env[ks]} ]] && ks=${global_env[ks]} || \ - error "请指定ks web路径, 编辑 $config_file 文件, 例如: ks http://$ipaddr/ks/ks.ks" - [[ -n ${global_env[repo]} ]] && repo=${global_env[repo]} || \ - error "请指定ks web路径, 编辑 $config_file 文件, 例如: repo http://$ipaddr/rocky/" - - kargs="net.ifnames=0 biosdevname=0" - [[ -n ${global_env[kargs]} ]] && kargs="$kargs ${global_env[kargs]}" - - ipxeboot="http://$ipaddr/ks/boot.ipxe" - [[ -n ${global_env[bootipxe]} ]] && ipxeboot=${global_env[bootipxe]} - - htmlroot="/var/www/html" - [[ -n ${global_env[htmlroot]} ]] && htmlroot=${global_env[htmlroot]} + getArgs bios biosboot e "Check the configuration file $config_file: biosboot " + getArgs uefi uefiboot e "Check the configuration file $config_file: uefiboot " + getArgs ipxeboot ipxeboot e "Check the configuration file $config_file: ipxeboot " - boot_root="/srv/pxelinux/tftpboot" + biosboot="${biosboot#$tftpboot/}" + uefiboot="${uefiboot#$tftpboot/}" dnsmasq_conf="/etc/dnsmasq.d/sunhpc.conf" - [[ -e "$boot_root/boot" ]] || mkdir -p "$boot_root/boot" { - echo "interface=$interface" + echo "interface=$iface" echo "bind-interfaces" echo "" echo "enable-tftp" - echo "tftp-root=${boot_root}" + echo "tftp-root=${tftpboot}" echo "" echo "# Gateway:3, DNS:6" - echo "dhcp-range=$iprange,12h" + echo "dhcp-range=$start_ip,$ender_ip,12h" echo "dhcp-option=3,${ipaddr}" echo "dhcp-option=6,$dnsip" echo "" @@ -662,14 +464,11 @@ pxe_dnsmasq_build() { echo "dhcp-match=set:uefi,option:client-arch,7" echo "dhcp-match=set:uefi,option:client-arch,9" echo "" - echo "dhcp-boot=tag:bios,boot/undionly.kpxe" - echo "dhcp-boot=tag:uefi,boot/bootx64.efi" + echo "dhcp-boot=tag:bios,$biosboot" + echo "dhcp-boot=tag:uefi,$uefiboot" echo "dhcp-boot=tag:ipxe,$ipxeboot" } > ${dnsmasq_conf} - ipxeconf="$htmlroot$(echo "$htmlroot" | cut -d'/' -f4-)" - echo "----ipxeconf----> $ipxeconf" - } # ====================== server functions ======================== slurm_build() { @@ -1159,6 +958,111 @@ sync_file_single() { done info "同步完成!" } + +# rsync 同步函数,支持 ISO 文件和普通目录 +# 用法: rsync_sync_file [rsync_options] +# 参数: +# source - 源路径(文件或目录,支持 ISO 文件) +# destination - 目标路径 +# rsync_options - 可选,额外的 rsync 参数 +# 返回: +# 0 - 成功 +# 1 - 失败 +rsync_sync_file() { + local source="$1" + local destination="$2" + local rsync_opts="${3:--ah --info=progress2}" # 默认 rsync 选项 + local temp_mount="" + local is_iso=false + local exit_code=0 + + # 检查参数 + if [[ -z "$source" ]] || [[ -z "$destination" ]]; then + warn "必须指定源路径和目标路径" >&2 + warn "用法: rsync_sync_file [rsync_options]" >&2 + return 1 + fi + + # 检查源路径是否存在 + if [[ ! -e "$source" ]]; then + warn "源路径不存在: $source" >&2 + return 1 + fi + + # 检查是否为 ISO 文件 + if [[ -f "$source" && ( "$source" =~ \.(iso|ISO)$ ) ]]; then + is_iso=true + info "检测到 ISO 文件: $source" + + # 创建临时挂载点 + temp_mount=$(mktemp -d "/tmp/iso_mount_XXXXXX") + info "创建临时挂载点: $temp_mount" + + # 挂载 ISO 文件 + if mount -o loop "$source" "$temp_mount" 2>/dev/null; then + info "成功挂载 ISO 文件到: $temp_mount" + source="$temp_mount" + else + warn "无法挂载 ISO 文件,可能需要 root 权限" >&2 + rmdir "$temp_mount" 2>/dev/null + return 1 + fi + fi + + # 确保目标目录存在 + # 提取目标目录(如果是文件,取目录部分) + local dest_dir + if [[ "$destination" == */ ]]; then + dest_dir="$destination" # 目标 / 结尾, 视为目录 + else + dest_dir="$(dirname "$destination")" # 目标是文件, 取目录部分 + fi + + if [[ ! -d "$dest_dir" ]]; then + info "创建目标目录: $dest_dir" + mkdir -p "$dest_dir" || { + warn "无法创建目标目录: $dest_dir" >&2 + exit_code=1 + cleanup + } + fi + + # 确保源路径以斜杠结尾(rsync 目录同步规范) + local rsync_source="$source" + if [[ -d "$source" ]]; then + # 如果是目录,确保以 / 结尾,这样同步目录内容而不是目录本身 + rsync_source="${source}/" + fi + + # 执行 rsync 同步 + info "开始同步数据..." + info " 源路径: $source" + info " 目标路径: $destination" + info " rsync 选项: $rsync_opts" + + rsync $rsync_opts "$rsync_source" "$destination" + exit_code=$? + + if [[ $exit_code -eq 0 ]]; then + info "同步成功完成" + else + warn "rsync 同步失败,退出码: $exit_code" >&2 + fi + + # 清理:卸载临时挂载点 + cleanup() { + if [[ "$is_iso" == "true" ]] && [[ -n "$temp_mount" ]]; then + info "清理临时挂载点..." + umount "$temp_mount" 2>/dev/null || { + warn "无法卸载 $temp_mount,可能需要手动清理" >&2 + } + rmdir "$temp_mount" 2>/dev/null + info "已删除临时挂载点: $temp_mount" + fi + + return $exit_code + } +} # ====================== 帮助文档 ====================== show_help() { cat < [OPTIONS] -n, --nodes 指定节点(all 或 node1,node2,node3) 支持命令: - sunhpc pxe dhcp 部署dhcp服务 - sunhpc pxe tftp 部署tftp服务 sunhpc pxe http 部署http服务 sunhpc pxe dnsmasq 部署pxe服务 sunhpc exec 批量执行命令 + sunhpc init Execute the init op sunhpc sync file 执行同步任务 sunhpc list node 查看节点列表 sunhpc show vimrc 显示Vimrc变量 @@ -1251,38 +1154,44 @@ log_debug() { } # ====================== 子命令执行 ====================== -run_pxe_dhcp() { - log_debug "--------- run_pxe_dhcp----------------" - log_debug "当前节点:${GLOBAL_NODES[*]}" - log_debug "公共参数:${PUBLIC_ARGS[*]}" - log_debug "私有参数:${PRIVATE_ARGS[*]}" - - # -n 非空True,-z 为空True - [[ -n ${GLOBAL_OPTS[interface]} ]] && pxe_dhcp_build || \ - warn "请指定DHCP监听接口命名, 例如: sunhpc pxe dhcp -i eth0" -} -run_pxe_tftp() { - log_debug "--------- run_pxe_tftp----------------" - log_debug "当前节点:${GLOBAL_NODES[*]}" - log_debug "公共参数:${PUBLIC_ARGS[*]}" - log_debug "私有参数:${PRIVATE_ARGS[*]}" - pxe_tftp_build -} run_pxe_http() { - log_debug "--------- run_pxe_http----------------" - log_debug "当前节点:${GLOBAL_NODES[*]}" - log_debug "公共参数:${PUBLIC_ARGS[*]}" - log_debug "私有参数:${PRIVATE_ARGS[*]}" pxe_http_build } run_init() { - [[ -n "${GLOBAL_OPTS[interface]}" ]] && iface="${GLOBAL_OPTS[interface]}" || \ - error "必须指定一个网络接口,例如: -i/--interface eth1" + info "初次使用此工具,需要在当前目录创建一个配置文件." + info " 1、手动创建配置文件,e.g., touch $config_file" + info " 2、自动创建配置文件,e.g., " + info " sunhpc init -i -m /mnt/cdrom" + echo "" - [[ -n "${GLOBAL_OPTS[html]-}" ]] && html="${GLOBAL_OPTS[html]}" || \ - error "必须指定 html 路径,例如: --html /var/www/html" + getArgs interface iface e "必须指定一个网络接口,例如: -i/--interface eth1" + getArgs mntroot mntroot e "必须指定 mnt 路径,例如: -m/--mntroot /mnt/cdrom" + + treeinfo=$(find -L $mntroot -name ".treeinfo") + parse_treeinfo $treeinfo + + #for key in "${!global_treeinfo[@]}"; do echo "key: '$key', Value: '${global_treeinfo[$key]}'";done + + [[ -z "$treeinfo" ]] && warn "提供的 $mntroot 目录为空,或是不被支持的系统." + + nopkgs=() + pkgs=("ipxe-bootimgs") + filter_pkgs pkgs nopkgs + + local cmd=("dnf -y install ${nopkgs[*]}") + [[ -z ${nopkgs[@]} ]] || run_with_progress "DNF Install ipxe tools..." "$cmd" "$log_file" + + srv_root="/srv" + repos_root="$srv_root/repos" + + kernel_path="${global_treeinfo[images-x86_64.kernel]}" + initrd_path="${global_treeinfo[images-x86_64.initrd]}" + + sys_os_rels="${global_treeinfo[general.family]}" # RockyLinux + sys_os_vers="${global_treeinfo[general.version]}" # 9.7 + url_os_path="$sys_os_rels/$sys_os_vers" # http://ip/ 后面系统路径 rockylinux/9.7 ipaddr=${global_env[${iface}_ip]} netcidr=${global_env[${iface}_mask]} @@ -1293,26 +1202,83 @@ run_init() { start_ip="$i1.$i2.$i3.10" ender_ip="$i1.$i2.$i3.90" + mnt_dst="$repos_root/$url_os_path" + rsync_sync_file $mntroot $mnt_dst - tmpdir=$(find -L $html -name ".treeinfo") - tmpdir=$(dirname ${tmpdir#$html}) + lrepo_path="$repos_root/$sys_os_rels" - echo "iface---------> $iface" - echo "addr----------> $ipaddr" - echo "startip-------> $start_ip" - echo "enderip-------> $ender_ip" - echo "htmlroot------> $html" - echo "reporoot------> $tmpdir" - exit 1 + wwwroot="/var/www/html/linux" + [[ -e $wwwroot ]] || mkdir -p $wwwroot + + ks_src="$PWD/pkgs/" + ks_dst="$srv_root/htool/pkgs/" + [[ -e $ks_src ]] && rsync_sync_file $ks_src $ks_dst + + tftpboot="/srv/pxelinux/tftpboot" + + efi_src="$mnt_dst/EFI/BOOT/BOOTX64.EFI" + [[ -e $efi_src ]] && rsync_sync_file $efi_src $tftpboot/bootx64.efi \ + || warn "UEFI Boot file no found: $efi_src" + + ipxe_src="/usr/share/ipxe/undionly.kpxe" + [[ -e $ipxe_src ]] && rsync_sync_file $ipxe_src $tftpboot/boot/ \ + || warn "iPXE Boot file no found: $ipxe_tools" + + ks_args="net.ifnames=0 biosdevname=0" + ks_rhel="http://$ipaddr/linux/ks/rhel.ks" + ks_dfmt="http://$ipaddr/linux/ks/dfmt.sh" + ks_ipxe="http://$ipaddr/linux/ks/ipxe.sh" + ks_init="http://$ipaddr/linux/ks/init.sh" + ks_repo="http://$ipaddr/linux/$url_os_path" + vmlinuz="http://$ipaddr/linux/$url_os_path/$kernel_path" + initrds="http://$ipaddr/linux/$url_os_path/$initrd_path" { + echo "# ====================================================" + echo "# Generated by htool commands" + echo "# ====================================================" + echo "" echo "interface $iface" - echo "server $ipaddr" - echo "range $start_ip,$ender_ip" - echo "htmlroot $html" - echo "reporoot $tmpdir" - } > ${config_file} - #gen_config_file + echo "server $ipaddr" + echo "pkgsroot $ks_dst" + echo "wwwroot $wwwroot" + echo "tftpboot $tftpboot" + echo "startip $start_ip" + echo "endip $ender_ip" + echo "dns 223.5.5.5" + echo "osname $sys_os_rels" + echo "osvers $sys_os_vers" + echo "ospath $url_os_path" + echo "lrepo $lrepo_path" + echo "bios $tftpboot/boot/undionly.kpxe" + echo "uefi $tftpboot/boot/bootx64.efi" + echo "dfmt $ks_dfmt" + echo "ksaddr $ks_rhel" + echo "ipxeboot $ks_ipxe" + echo "repos $ks_repo" + echo "vmlinuz $vmlinuz" + echo "initrd $initrds" + } > $config_file + + pkgs_ipxe="$srv_root/htool/pkgs/ks/ipxe.sh" + sed -i "s|@osname@|$sys_os_rels|" $pkgs_ipxe + sed -i "s|@osrels@|"${sys_os_rels^}"|" $pkgs_ipxe + sed -i "s|@osvers@|$sys_os_vers|" $pkgs_ipxe + sed -i "s|@vmlinuz@|$vmlinuz|" $pkgs_ipxe + sed -i "s|@initrds@|$initrds|" $pkgs_ipxe + sed -i "s|@kargs@|$ks_args|" $pkgs_ipxe + sed -i "s|@repos@|$ks_repo|" $pkgs_ipxe + sed -i "s|@ksarg@|$ks_rhel|" $pkgs_ipxe + + pkgs_rhel="$srv_root/htool/pkgs/ks/rhel.ks" + sed -i "s|@url@|$ks_repo|" $pkgs_rhel + sed -i "s|@dfmt@|$ks_dfmt|" $pkgs_rhel + sed -i "s|@init@|$ks_init|" $pkgs_rhel + + + pkgs_init="${ks_dst}ks/init.sh" + sed -i "s|@osipaddr@|$ipaddr|" $pkgs_init + sed -i "s|@ospath@|$url_os_path|" $pkgs_init } run_exec() { @@ -1451,7 +1417,7 @@ create_sync_file() { dispatch() { # 优先显示帮助 if [[ -n "${GLOBAL_OPTS[h]}" || -n "${GLOBAL_OPTS[help]}" || ${#CMD_STACK[@]} -eq 0 ]]; then - show_help + [[ $helper -ne 1 ]] && show_help exit 0 fi @@ -1462,13 +1428,14 @@ dispatch() { case "${CMD_STACK[0]}" in pxe) case "${CMD_STACK[1]}" in - dhcp) run_pxe_dhcp ;; - tftp) run_pxe_tftp;; http) run_pxe_http;; dnsmasq) pxe_dnsmasq_build;; *) show_help;; esac ;; + init) + run_init + ;; exec) run_exec "${CMD_STACK[1]}" ;; @@ -1504,7 +1471,7 @@ dispatch() { *) show_help ;; esac ;; - *) show_help ;; + *) [[ $helper -ne 1 ]] && show_help ;; esac } @@ -1517,6 +1484,8 @@ parse_args() { -v) GLOBAL_OPTS[v]=1; GLOBAL_OPTS[verbose]=1; PUBLIC_ARGS+=("$arg"); shift ;; -d) GLOBAL_OPTS[d]=1; GLOBAL_OPTS[debug]=1; PUBLIC_ARGS+=("$arg"); shift ;; -h) GLOBAL_OPTS[h]=1; GLOBAL_OPTS[help]=1; PUBLIC_ARGS+=("$arg"); shift ;; + -m) GLOBAL_OPTS[mntroot]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; + -w) GLOBAL_OPTS[wwwroot]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; -f) GLOBAL_OPTS[file]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; -i) GLOBAL_OPTS[interface]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; -n) parse_nodes "$2"; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; @@ -1525,8 +1494,9 @@ parse_args() { --verbose) GLOBAL_OPTS[verbose]=1; GLOBAL_OPTS[v]=1; PUBLIC_ARGS+=("$arg"); shift ;; --debug) GLOBAL_OPTS[debug]=1; GLOBAL_OPTS[d]=1; PUBLIC_ARGS+=("$arg"); shift ;; --help) GLOBAL_OPTS[help]=1; GLOBAL_OPTS[h]=1; PUBLIC_ARGS+=("$arg"); shift ;; + --mntroot) GLOBAL_OPTS[mntroot]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; + --wwwroot) GLOBAL_OPTS[wwwroot]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; --file) GLOBAL_OPTS[file]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; - --html) GLOBAL_OPTS[html]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; --interface) GLOBAL_OPTS[interface]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; --nodes) parse_nodes "$2"; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;; @@ -1555,15 +1525,20 @@ main() { # 默认使用文件中的节点 GLOBAL_NODES=("${DEFAULT_NODES[@]}") - parse_args "$@" - # 获取基础信息存储变量 - if [[ -e $config_file ]]; then - load_conf_envs - else - run_init + if [[ ! -e $config_file ]]; then + info "$DSEP" + warn "配置文件不存在: $config_file " + warn "首次使用此工具需要使用下面命令进行初始化操作:" + warn " sunhpc init -i eth1 -m /mnt/cdrom" + info "$DSEP" + echo "" + helper=1 fi + [[ -e $config_file ]] && load_conf_envs + + parse_args "$@" dispatch }