This commit is contained in:
root
2026-04-26 21:43:16 +08:00
parent 6e46c06edd
commit 374c98e192
5 changed files with 384 additions and 395 deletions

24
htool.conf Normal file
View File

@@ -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

View File

@@ -14,14 +14,14 @@ set -e
# 日志文件 # 日志文件
LOG_FILE="/var/log/sunhpc-init.log" LOG_FILE="/var/log/sunhpc-init.log"
FRONTEND_IP="172.16.9.254" FRONTEND_IP='@osipaddr@'
# HTTP 服务器地址 # HTTP 服务器地址
HTTP_SERVER="http://${FRONTEND_IP}" HTTP_SERVER="http://${FRONTEND_IP}"
# YUM 仓库配置 # YUM 仓库配置
YUM_REPO_NAME="sunhpc-local" YUM_REPO_NAME="sunhpc-local"
YUM_REPO_BASEURL="${HTTP_SERVER}/rocky/9.7" YUM_REPO_BASEURL="${HTTP_SERVER}/@ospath@"
YUM_REPO_GPGCHECK=0 YUM_REPO_GPGCHECK=0
# 主机名映射文件(在 HTTP 服务器上) # 主机名映射文件(在 HTTP 服务器上)
@@ -79,7 +79,7 @@ check_network() {
log_step "Checking network connectivity..." 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" log_info "Network connectivity: OK"
return 0 return 0
else else
@@ -164,8 +164,6 @@ $ip_addr $hostname
$FRONTEND_IP cluster $FRONTEND_IP cluster
# Optional: Add other compute nodes here # Optional: Add other compute nodes here
# 172.16.9.1 cn001
# 172.16.9.2 cn002
EOF EOF
# 尝试下载完整的集群 hosts 文件 # 尝试下载完整的集群 hosts 文件

View File

@@ -3,18 +3,18 @@
:main_menu :main_menu
menu iPXE boot menu menu iPXE boot menu
item rocky9 Install Rocky Linux 9 item @osname@ Install @osrels@ @osvers@
item local Boot from Local Disk item local Boot from Local Disk
choose --default rocky9 --timeout 5000 selected || goto local choose --default @osname@ --timeout 5000 selected || goto local
:rocky9 :@osname@
echo Booting RockyLinux from networking echo Booting @osrels@ from networking
kernel http://172.16.9.254/rocky/9.7/isolinux/vmlinuz \ kernel @vmlinuz@ \
net.ifnames=0 biosdevname=0 \ @kargs@ \
inst.repo=http://172.16.9.254/rocky/9.7 \ @repos@ \
inst.ks=http://172.16.9.254/ks/rhel.ks @ksarg@
initrd http://172.16.9.254/rocky/9.7/isolinux/initrd.img initrd @initrds@
boot boot
:local :local

View File

@@ -1,35 +1,28 @@
graphical graphical
%addon com_redhat_kdump --disable
%end
timezone Asia/Shanghai --utc timezone Asia/Shanghai --utc
keyboard --xlayouts='us' keyboard --xlayouts="us"
lang en_US.UTF-8 lang en_US.UTF-8
selinux --disabled 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 network --bootproto=dhcp --device=link --ipv6=auto --activate
# Partition clearing information # Partition clearing information
#zerombr # zerombr
#clearpart --all --initlabel # clearpart --all --initlabel
#autopart --type=lvm # 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 %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 %packages
@^minimal-environment @^minimal-environment
@standard
@development @development
@standard
vim vim
wget wget
curl curl
@@ -37,24 +30,23 @@ autofs
nfs-utils nfs-utils
nfs4-acl-tools nfs4-acl-tools
sssd-nfs-idmap sssd-nfs-idmap
pcp-pmda-nfsclient
%end %end
# Root password rootpw --plaintext admin_b101
rootpw --iscrypted $6$muqhPjb0F9IM2/Fg$pPaVF7DTjs/zz91vHMrcL8jPLQoLFCjUUxHkIZao9C6OFbBPof2AtmTRfvO4Ix.8al3dnMz8/aAbd88sHSQTK. user --name=dell --plaintext admin_b101 --gecos="dell"
user --name=kelvin --password=$6$kAz6MRJFIpIyhKuv$YZntcNpyoSYRMD6y5qmZIIBiklzaskqHWE4A0oXI8vX492bcL/.z6xF3MjVDgVJzZ0FaNDSy8BFeEhD9mfr67/ --iscrypted --gecos="kelvin" reboot
#reboot
%pre --interpreter=/bin/bash %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 chmod +x /tmp/dfmt.sh
/tmp/dfmt.sh > /tmp/diskinfo /tmp/dfmt.sh > /tmp/diskinfo
%end %end
%post %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 chmod +x /tmp/init.sh
/tmp/init.sh /tmp/init.sh
%end %end
%addon com_redhat_kdump --disable
%end

683
sunhpc
View File

@@ -74,40 +74,7 @@ DEFAULT_NODES=("localhost")
GLOBAL_NODES=() GLOBAL_NODES=()
declare -A GLOBAL_PORT declare -A GLOBAL_PORT
declare -A global_env declare -A global_env
declare -A global_treeinfo
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
}
load_os_envs() { load_os_envs() {
interfaces=$(ls /sys/class/net | grep -v lo) interfaces=$(ls /sys/class/net | grep -v lo)
@@ -132,6 +99,49 @@ load_os_envs() {
done 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(){
# 调用模式: getFileArgs myfile "true" 未指定文件时只返回错误码 # 调用模式: getFileArgs myfile "true" 未指定文件时只返回错误码
# 调用模式: result=$(getFileArgs myfile "true") 返回文件 # 调用模式: result=$(getFileArgs myfile "true") 返回文件
@@ -168,15 +178,44 @@ getFileArgs(){
fi fi
} }
# ====================== 核心函数:加载环境变量信息 ============== getArgs() {
load_conf_envs() { # 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 # 优先级1检查 GLOBAL_OPTS命令行参数
# 跳过空行、注释行 if [[ -n "${GLOBAL_OPTS[$key]}" ]]; then
[[ -z "$key" || "$key" == \#* ]] && continue value="${GLOBAL_OPTS[$key]}"
# 优先级2检查 global_env配置文件
elif [[ -n "${global_env[$key]}" ]]; then
value="${global_env[$key]}"
fi
global_env["$key"]="$val" # 检查 GLOBAL_OPTS 数组中是否存在该键
done < "$config_file" 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 "======================================" 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 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() { pxe_http_build() {
log_debug "当前节点:${GLOBAL_NODES[*]}" log_debug "当前节点:${GLOBAL_NODES[*]}"
log_debug "公共参数:${PUBLIC_ARGS[*]}" log_debug "公共参数:${PUBLIC_ARGS[*]}"
@@ -530,12 +394,12 @@ pxe_http_build() {
custom_conf="$http_root_conf/conf.d/sunhpc.conf" 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 ''
echo 'UseCanonicalName Off' echo 'UseCanonicalName Off'
echo "ServerName `hostname -f`" echo "ServerName `hostname -f`"
echo '' echo ''
echo "<Directory \"$rocky_dirs\">" echo '<Directory "/var/www/html/linux">'
echo " Options Indexes FollowSymLinks" echo " Options Indexes FollowSymLinks"
echo " AllowOverride None" echo " AllowOverride None"
echo " Require all granted" echo " Require all granted"
@@ -548,52 +412,12 @@ pxe_http_build() {
#systemctl is-enabled httpd &>/dev/null || systemctl enable httpd &>/dev/null #systemctl is-enabled httpd &>/dev/null || systemctl enable httpd &>/dev/null
info "HTTP 服务端口: 80,443"
if systemctl is-active httpd &>/dev/null ; then if systemctl is-active httpd &>/dev/null ; then
info "HTTP 服务已经配置成功..." info "HTTP 服务部署成功"
info "后续配置: " info "HTTP 服务端口: 80,443"
info " 拷贝系统ISO文件到 $rocky_repo_src"
info " # rsync -ah --info=progress2 /mnt/ /srv/repos/rocky/9.7/"
else else
warn "HTTP 服务部署失败,请查看服务信息 systemctl status httpd.service" warn "HTTP 服务部署失败,请查看服务信息 systemctl status httpd.service"
fi 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() { pxe_dnsmasq_build() {
@@ -608,53 +432,31 @@ pxe_dnsmasq_build() {
local cmd=("dnf -y install ${nopkgs[*]}") local cmd=("dnf -y install ${nopkgs[*]}")
[[ -z ${nopkgs[@]} ]] || run_with_progress "DNF 安装 dnsmasq 服务..." "$cmd" "$log_file" [[ -z ${nopkgs[@]} ]] || run_with_progress "DNF 安装 dnsmasq 服务..." "$cmd" "$log_file"
# -n 非空True-z 为空True getArgs interface iface e "必须指定一个网络接口,例如: -i/--interface eth1"
[[ -n ${global_env[interface]} ]] && iface=${global_env[interface]} || \ getArgs server ipaddr e "Check the configuration file $config_file: server <ipaddr>"
error "请指定DHCP监听接口命名, 编辑 $config_file 文件, 例如: interface eth1" getArgs tftpboot tftpboot e "Check the configuration file $config_file: tftpboot <fullpath>"
getArgs startip start_ip e "Check the configuration file $config_file: startip <ipaddr>"
getArgs endip ender_ip e "Check the configuration file $config_file: endip <ipaddr>"
getArgs dns dnsip e "Check the configuration file $config_file: dns <dnsaddr>"
[[ -n ${global_env[server]} ]] && ipaddr=${global_env[server]} || \ getArgs bios biosboot e "Check the configuration file $config_file: biosboot <fullpath>"
error "请指定监听接口IP地址, 编辑 $config_file 文件, 例如: server 172.16.9.254" getArgs uefi uefiboot e "Check the configuration file $config_file: uefiboot <fullpath>"
getArgs ipxeboot ipxeboot e "Check the configuration file $config_file: ipxeboot <url>"
[[ -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]}
boot_root="/srv/pxelinux/tftpboot" biosboot="${biosboot#$tftpboot/}"
uefiboot="${uefiboot#$tftpboot/}"
dnsmasq_conf="/etc/dnsmasq.d/sunhpc.conf" 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 "bind-interfaces"
echo "" echo ""
echo "enable-tftp" echo "enable-tftp"
echo "tftp-root=${boot_root}" echo "tftp-root=${tftpboot}"
echo "" echo ""
echo "# Gateway:3, DNS:6" 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=3,${ipaddr}"
echo "dhcp-option=6,$dnsip" echo "dhcp-option=6,$dnsip"
echo "" 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,7"
echo "dhcp-match=set:uefi,option:client-arch,9" echo "dhcp-match=set:uefi,option:client-arch,9"
echo "" echo ""
echo "dhcp-boot=tag:bios,boot/undionly.kpxe" echo "dhcp-boot=tag:bios,$biosboot"
echo "dhcp-boot=tag:uefi,boot/bootx64.efi" echo "dhcp-boot=tag:uefi,$uefiboot"
echo "dhcp-boot=tag:ipxe,$ipxeboot" echo "dhcp-boot=tag:ipxe,$ipxeboot"
} > ${dnsmasq_conf} } > ${dnsmasq_conf}
ipxeconf="$htmlroot$(echo "$htmlroot" | cut -d'/' -f4-)"
echo "----ipxeconf----> $ipxeconf"
} }
# ====================== server functions ======================== # ====================== server functions ========================
slurm_build() { slurm_build() {
@@ -1159,6 +958,111 @@ sync_file_single() {
done done
info "同步完成!" info "同步完成!"
} }
# rsync 同步函数,支持 ISO 文件和普通目录
# 用法: rsync_sync_file <source> <destination> [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 <source> <destination> [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() { show_help() {
cat <<EOF cat <<EOF
@@ -1173,11 +1077,10 @@ Usage: sunhpc <COMMAND> [OPTIONS]
-n, --nodes <list> 指定节点all 或 node1,node2,node3 -n, --nodes <list> 指定节点all 或 node1,node2,node3
支持命令: 支持命令:
sunhpc pxe dhcp 部署dhcp服务
sunhpc pxe tftp 部署tftp服务
sunhpc pxe http 部署http服务 sunhpc pxe http 部署http服务
sunhpc pxe dnsmasq 部署pxe服务 sunhpc pxe dnsmasq 部署pxe服务
sunhpc exec 批量执行命令 sunhpc exec 批量执行命令
sunhpc init Execute the init op
sunhpc sync file 执行同步任务 sunhpc sync file 执行同步任务
sunhpc list node 查看节点列表 sunhpc list node 查看节点列表
sunhpc show vimrc 显示Vimrc变量 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() { run_pxe_http() {
log_debug "--------- run_pxe_http----------------"
log_debug "当前节点:${GLOBAL_NODES[*]}"
log_debug "公共参数:${PUBLIC_ARGS[*]}"
log_debug "私有参数:${PRIVATE_ARGS[*]}"
pxe_http_build pxe_http_build
} }
run_init() { run_init() {
[[ -n "${GLOBAL_OPTS[interface]}" ]] && iface="${GLOBAL_OPTS[interface]}" || \ info "初次使用此工具,需要在当前目录创建一个配置文件."
error "必须指定一个网络接口,例如: -i/--interface eth1" info " 1、手动创建配置文件,e.g., touch $config_file"
info " 2、自动创建配置文件,e.g., "
info " sunhpc init -i <interface> -m /mnt/cdrom"
echo ""
[[ -n "${GLOBAL_OPTS[html]-}" ]] && html="${GLOBAL_OPTS[html]}" || \ getArgs interface iface e "必须指定一个网络接口,例如: -i/--interface eth1"
error "必须指定 html 路径,例如: --html /var/www/html" 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]} ipaddr=${global_env[${iface}_ip]}
netcidr=${global_env[${iface}_mask]} netcidr=${global_env[${iface}_mask]}
@@ -1293,26 +1202,83 @@ run_init() {
start_ip="$i1.$i2.$i3.10" start_ip="$i1.$i2.$i3.10"
ender_ip="$i1.$i2.$i3.90" 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") lrepo_path="$repos_root/$sys_os_rels"
tmpdir=$(dirname ${tmpdir#$html})
echo "iface---------> $iface" wwwroot="/var/www/html/linux"
echo "addr----------> $ipaddr" [[ -e $wwwroot ]] || mkdir -p $wwwroot
echo "startip-------> $start_ip"
echo "enderip-------> $ender_ip" ks_src="$PWD/pkgs/"
echo "htmlroot------> $html" ks_dst="$srv_root/htool/pkgs/"
echo "reporoot------> $tmpdir" [[ -e $ks_src ]] && rsync_sync_file $ks_src $ks_dst
exit 1
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 "interface $iface"
echo "server $ipaddr" echo "server $ipaddr"
echo "range $start_ip,$ender_ip" echo "pkgsroot $ks_dst"
echo "htmlroot $html" echo "wwwroot $wwwroot"
echo "reporoot $tmpdir" echo "tftpboot $tftpboot"
} > ${config_file} echo "startip $start_ip"
#gen_config_file 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() { run_exec() {
@@ -1451,7 +1417,7 @@ create_sync_file() {
dispatch() { dispatch() {
# 优先显示帮助 # 优先显示帮助
if [[ -n "${GLOBAL_OPTS[h]}" || -n "${GLOBAL_OPTS[help]}" || ${#CMD_STACK[@]} -eq 0 ]]; then if [[ -n "${GLOBAL_OPTS[h]}" || -n "${GLOBAL_OPTS[help]}" || ${#CMD_STACK[@]} -eq 0 ]]; then
show_help [[ $helper -ne 1 ]] && show_help
exit 0 exit 0
fi fi
@@ -1462,13 +1428,14 @@ dispatch() {
case "${CMD_STACK[0]}" in case "${CMD_STACK[0]}" in
pxe) pxe)
case "${CMD_STACK[1]}" in case "${CMD_STACK[1]}" in
dhcp) run_pxe_dhcp ;;
tftp) run_pxe_tftp;;
http) run_pxe_http;; http) run_pxe_http;;
dnsmasq) pxe_dnsmasq_build;; dnsmasq) pxe_dnsmasq_build;;
*) show_help;; *) show_help;;
esac esac
;; ;;
init)
run_init
;;
exec) exec)
run_exec "${CMD_STACK[1]}" run_exec "${CMD_STACK[1]}"
;; ;;
@@ -1504,7 +1471,7 @@ dispatch() {
*) show_help ;; *) show_help ;;
esac esac
;; ;;
*) show_help ;; *) [[ $helper -ne 1 ]] && show_help ;;
esac esac
} }
@@ -1517,6 +1484,8 @@ parse_args() {
-v) GLOBAL_OPTS[v]=1; GLOBAL_OPTS[verbose]=1; PUBLIC_ARGS+=("$arg"); shift ;; -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 ;; -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 ;; -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 ;; -f) GLOBAL_OPTS[file]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;;
-i) GLOBAL_OPTS[interface]=$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 ;; -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 ;; --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 ;; --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 ;; --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 ;; --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 ;; --interface) GLOBAL_OPTS[interface]=$2; PUBLIC_ARGS+=("$arg" "$2"); shift 2 ;;
--nodes) parse_nodes "$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[@]}") GLOBAL_NODES=("${DEFAULT_NODES[@]}")
parse_args "$@"
# 获取基础信息存储变量 # 获取基础信息存储变量
if [[ -e $config_file ]]; then if [[ ! -e $config_file ]]; then
load_conf_envs info "$DSEP"
else warn "配置文件不存在: $config_file "
run_init warn "首次使用此工具需要使用下面命令进行初始化操作:"
warn " sunhpc init -i eth1 -m /mnt/cdrom"
info "$DSEP"
echo ""
helper=1
fi fi
[[ -e $config_file ]] && load_conf_envs
parse_args "$@"
dispatch dispatch
} }