reinstall/reinstall.sh
2023-08-08 23:21:28 +08:00

1012 lines
33 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -eE
confhome=https://raw.githubusercontent.com/bin456789/reinstall/main
localtest_confhome=http://192.168.253.1
trap 'error line $LINENO return $?' ERR
this_script=$(realpath $0)
usage_and_exit() {
cat <<EOF
Usage: reinstall.sh centos-7/8/9
alma-8/9
rocky-8/9
fedora-37/38
ubuntu-20.04/22.04
alpine-3.16/3.17/3.18
debian-10/11/12
opensuse-15.4/15.5
arch
windows --iso=xxx --image-name=xxx
dd --img=xxx
EOF
exit 1
}
info() {
color='\e[32m'
plain='\e[0m'
upper=$(echo "$@" | tr '[:lower:]' '[:upper:]')
echo -e "$color***** $upper *****$plain"
}
error() {
color='\e[31m'
plain='\e[0m'
echo -e "${color}Error: $*${plain}"
# 如果从trap调用显示错误行
if [ "$1" = line ]; then
sed -n "$2"p $this_script
fi
}
error_and_exit() {
error "$@"
exit 1
}
curl() {
command curl --connect-timeout 5 --retry 3 --retry-delay 0 "$@"
}
is_in_china() {
if [ -z $_is_in_china ]; then
# https://geoip.fedoraproject.org/city # 不支持 ipv6
# https://geoip.ubuntu.com/lookup # 不支持 ipv6
curl -L http://www.cloudflare.com/cdn-cgi/trace | grep -qx 'loc=CN'
_is_in_china=$?
fi
return $_is_in_china
}
is_in_windows() {
[ "$(uname -o)" = Cygwin ] || [ "$(uname -o)" = Msys ]
}
is_in_alpine() {
[ -f /etc/alpine-release ]
}
is_in_arch() {
[ -f /etc/arch-release ]
}
is_use_cloud_image() {
[ -n "$cloud_image" ] && [ "$cloud_image" = 1 ]
}
is_use_dd() {
[ "$distro" = dd ]
}
is_os_in_btrfs() {
mount | grep -w 'on / type btrfs'
}
is_os_in_subvol() {
subvol=$(awk '($2=="/") { print $i }' /proc/mounts | grep -o 'subvol=[^ ]*' | cut -d= -f2)
[ "$subvol" != / ]
}
get_os_part() {
awk '($2=="/") { print $1 }' /proc/mounts
}
cp_to_btrfs_root() {
files=$*
mount_dir=/tmp/reinstall-btrfs-root
if ! grep -q $mount_dir /proc/mounts; then
mkdir -p $mount_dir
mount "$(get_os_part)" $mount_dir -t btrfs -o subvol=/
fi
cp -rf $files /tmp/reinstall-btrfs-root
}
is_host_has_ipv4_and_ipv6() {
install_pkg dig
# dig会显示cname结果cname结果以.结尾grep -v '\.$' 用于去除 cname 结果
res=$(dig +short $host A $host AAAA | grep -v '\.$')
# 有.表示有ipv4地址有:表示有ipv6地址
grep -q \. <<<$res && grep -q : <<<$res
}
get_host_by_url() {
cut -d/ -f3 <<<$1
}
set_github_proxy() {
case "$confhome" in
http*://raw.githubusercontent.com/*)
if is_in_china; then
confhome=https://ghproxy.com/$confhome
fi
;;
esac
}
test_url() {
url=$1
expect_type=$2
var_to_eval=$3
info test url
echo $url
tmp_file=/tmp/reinstall-img-test
http_code=$(command curl --connect-timeout 5 --retry 3 -Ls -r 0-1048575 -w "%{http_code}" -o $tmp_file $url)
if [ "$http_code" != 200 ] && [ "$http_code" != 206 ]; then
error "$url not accessible"
return 1
fi
if [ -n "$expect_type" ]; then
# gzip的mime有很多种写法
# centos7中显示为 x-gzip在其他系统中显示为 gzip可能还有其他
# 所以不用mime判断
# https://www.digipres.org/formats/sources/tika/formats/#application/gzip
# 有些 file 版本输出的是 # ISO 9660 CD-ROM filesystem data ,要去掉开头的井号
install_pkg file
real_type=$(file -b $tmp_file | sed 's/^# //' | cut -d' ' -f1 | tr '[:upper:]' '[:lower:]')
[ -n "$var_to_eval" ] && eval $var_to_eval=$real_type
if ! echo $expect_type | grep -wo "$real_type"; then
error "$url expect: $expect_type. real: $real_type."
return 1
fi
fi
}
add_community_repo_for_alpine() {
if ! grep -x 'http.*/community' /etc/apk/repositories; then
alpine_ver=$(cut -d. -f1,2 </etc/alpine-release)
echo http://dl-cdn.alpinelinux.org/alpine/v$alpine_ver/community >>/etc/apk/repositories
fi
}
is_virt() {
if is_in_windows; then
# https://github.com/systemd/systemd/blob/main/src/basic/virt.c
# https://sources.debian.org/src/hw-detect/1.159/hw-detect.finish-install.d/08hw-detect/
vmstr='VMware|Virtual|Virtualization|VirtualBox|VMW|Hyper-V|Bochs|QEMU|KVM|OpenStack|KubeVirt|innotek|Xen|Parallels|BHYVE'
for name in ComputerSystem BIOS BaseBoard; do
wmic $name | grep -Eiw $vmstr && return 0
done
wmic /namespace:'\\root\cimv2' PATH Win32_Fan | head -1 | grep -q -v Name
else
# aws t4g debian 11 systemd-detect-virt 为 none即使装了dmidecode
# virt-what: 未装 deidecode时结果为空装了deidecode后结果为aws
# 所以综合两个命令的结果来判断
if command -v systemd-detect-virt && systemd-detect-virt; then
return 0
fi
# debian 安装 virt-what 不会自动安装 dmidecode因此结果有误
install_pkg dmidecode virt-what
# virt-what 返回值始终是0所以用是否有输出作为判断
[ -n "$(virt-what)" ]
fi
}
setos() {
local step=$1
local distro=$2
local releasever=$3
info set $step $distro $releasever
setos_alpine() {
flavour=lts
if is_virt; then
# alpine aarch64 3.18 才有 virt 直连链接
if [ "$basearch" == aarch64 ]; then
install_pkg bc
(($(echo "$releasever >= 3.18" | bc))) && flavour=virt
else
flavour=virt
fi
fi
if [ "$localtest" = 1 ]; then
mirror=$confhome/alpine-netboot-3.18.0-x86_64/boot
eval ${step}_vmlinuz=$mirror/vmlinuz-$flavour
eval ${step}_initrd=$mirror/initramfs-$flavour
eval ${step}_repo=http://mirrors.tuna.tsinghua.edu.cn/alpine/v$releasever/main
eval ${step}_modloop=$mirror/modloop-$flavour
else
# 不要用https 因为甲骨文云arm initramfs阶段不会从硬件同步时钟导致访问https出错
if is_in_china; then
mirror=http://mirrors.tuna.tsinghua.edu.cn/alpine/v$releasever
else
mirror=http://dl-cdn.alpinelinux.org/alpine/v$releasever
fi
eval ${step}_vmlinuz=$mirror/releases/$basearch/netboot/vmlinuz-$flavour
eval ${step}_initrd=$mirror/releases/$basearch/netboot/initramfs-$flavour
eval ${step}_repo=$mirror/main
eval ${step}_modloop=$mirror/releases/$basearch/netboot/modloop-$flavour
fi
}
setos_debian() {
case "$releasever" in
12) codename=bookworm ;;
11) codename=bullseye ;;
10) codename=buster ;;
esac
if is_use_cloud_image; then
# cloud image
ci_mirror=https://cloud.debian.org/images/cloud
is_virt && ci_type=genericcloud || ci_type=generic
# 甲骨文 debian 10 amd64 genericcloud vnc 没有显示
[ "$releasever" -eq 10 ] && [ "$basearch_alt" = amd64 ] && ci_type=generic
eval ${step}_img=$ci_mirror/$codename/latest/debian-$releasever-$ci_type-$basearch_alt.qcow2
else
# 传统安装
if [ "$localtest" = 1 ]; then
mirror=$confhome/debian/install.amd
eval ${step}_vmlinuz=$mirror/vmlinuz
eval ${step}_initrd=$mirror/initrd.gz
else
if is_in_china; then
hostname=ftp.cn.debian.org
else
hostname=deb.debian.org
fi
mirror=http://$hostname/debian/dists/$codename/main/installer-$basearch_alt/current/images/netboot/debian-installer/$basearch_alt
eval ${step}_vmlinuz=$mirror/linux
eval ${step}_initrd=$mirror/initrd.gz
fi
eval ${step}_ks=$confhome/debian.cfg
fi
}
setos_ubuntu() {
if is_use_cloud_image; then
# cloud image
# TODO: Minimal 镜像
if is_in_china; then
ci_mirror=https://mirror.nju.edu.cn/ubuntu-cloud-images/releases
else
ci_mirror=https://cloud-images.ubuntu.com/releases/$releasever/release
fi
eval ${step}_img=$ci_mirror/ubuntu-$releasever-server-cloudimg-$basearch_alt.img
else
# 传统安装
if [ "$localtest" = 1 ]; then
mirror=$confhome/
else
if is_in_china; then
case "$basearch" in
"x86_64") mirror=https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/$releasever ;;
"aarch64") mirror=https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cdimage/releases/$releasever/release ;;
esac
else
case "$basearch" in
"x86_64") mirror=https://releases.ubuntu.com/$releasever ;;
"aarch64") mirror=https://cdimage.ubuntu.com/releases/$releasever/release ;;
esac
fi
fi
# iso
filename=$(curl -L $mirror | grep -oP "ubuntu-$releasever.*?-live-server-$basearch_alt.iso" | head -1)
iso=$mirror/$filename
eval ${step}_iso=$iso
# ks
eval ${step}_ks=$confhome/ubuntu.yaml
fi
}
setos_arch() {
cloud_image=1
# cloud image
if is_in_china; then
ci_mirror=https://mirrors.tuna.tsinghua.edu.cn/archlinux
else
ci_mirror=https://geo.mirror.pkgbuild.com
fi
# eval ${step}_img=$ci_mirror/images/latest/Arch-Linux-x86_64-basic.qcow2
eval ${step}_img=$ci_mirror/images/latest/Arch-Linux-x86_64-cloudimg.qcow2
}
setos_opensuse() {
cloud_image=1
if grep -iq Tumbleweed <<<"$releasever"; then
dir=tumbleweed
releasever=Tumbleweed
else
if ! grep -q '\.' <<<"$releasever"; then
releasever=$(curl https://download.opensuse.org/download/distribution/leap/?json |
grep -oP "(?<=\"name\":\")$releasever\.[0-9]*" | tail -1)
fi
dir=distribution/leap/$releasever
releasever=Leap-$releasever
fi
# cloud image
# 有专门的kvm镜像openSUSE-Leap-15.5-Minimal-VM.x86_64-kvm-and-xen.qcow2但里面没有cloud-init
# TODO: aria2 mata4问题
# TODO: Tumbleweed 网络问题/ssh主文件incloud子目录问题
if grep -q 15\.4 <<<"$releasever"; then
openstack=-OpenStack
fi
eval ${step}_img=https://mirrors.kernel.org/opensuse/$dir/appliances/openSUSE-$releasever-Minimal-VM.$basearch$openstack-Cloud.qcow2
# eval ${step}_img=https://download.opensuse.org/$dir/appliances/openSUSE-$releasever-Minimal-VM.$basearch-Cloud.qcow2
}
setos_windows() {
if [ -z "$iso" ] || [ -z "$image_name" ]; then
error_and_exit "Install Windows need --iso --image-name"
fi
# 防止常见错误
# --image-name 肯定大于等于3个单词
if [ "$(echo "$image_name" | wc -w)" -lt 3 ]; then
error_and_exit "--image-name wrong."
fi
eval "${step}_iso='$iso'"
eval "${step}_image_name='$image_name'"
}
# shellcheck disable=SC2154
setos_dd() {
if [ -z "$img" ]; then
error_and_exit "dd need --img"
fi
eval "${step}_img='$img'"
eval "${step}_img_type='$img_type'"
}
setos_redhat() {
if is_use_cloud_image; then
# ci
[ "$distro" = "centos" ] && [ "$releasever" = "7" ] && stream_suffix="" || stream_suffix="-stream"
if is_in_china; then
case $distro in
"centos") ci_mirror="https://mirror.nju.edu.cn/centos-cloud/centos" ;;
"alma") ci_mirror="https://mirror.nju.edu.cn/almalinux/$releasever/cloud/$basearch/images" ;;
"rocky") ci_mirror="https://mirror.nju.edu.cn/rocky/$releasever/images/$basearch" ;;
"fedora") ci_mirror="https://mirror.nju.edu.cn/fedora/releases/$releasever/Cloud/$basearch/images" ;;
esac
else
case $distro in
"centos") ci_mirror="https://cloud.centos.org/centos" ;;
"alma") ci_mirror="https://repo.almalinux.org/almalinux/$releasever/cloud/$basearch/images" ;;
"rocky") ci_mirror="https://download.rockylinux.org/pub/rocky/$releasever/images/$basearch" ;;
"fedora") ci_mirror="https://download.fedoraproject.org/pub/fedora/linux/releases/$releasever/Cloud/$basearch/images" ;;
esac
fi
case $distro in
"centos")
case $releasever in
"7") ci_image=$ci_mirror/$releasever$stream_suffix/images/CentOS-7-$basearch-GenericCloud.qcow2 ;;
"8" | "9") ci_image=$ci_mirror/$releasever$stream_suffix/$basearch/images/CentOS-Stream-GenericCloud-$releasever-latest.$basearch.qcow2 ;;
esac
;;
"alma")
# alma8 x86_64 有独立的uefi镜像
if [ "$releasever" = 8 ] && is_efi && [ "$basearch" = x86_64 ]; then
alma_efi=-UEFI
fi
ci_image=$ci_mirror/AlmaLinux-$releasever-GenericCloud$alma_efi-latest.$basearch.qcow2
;;
"rocky") ci_image=$ci_mirror/Rocky-$releasever-GenericCloud-Base.latest.$basearch.qcow2 ;;
"fedora")
filename=$(curl -L $ci_mirror | grep -oP "Fedora-Cloud-Base-$releasever.*?$basearch" | head -1)
# ci_image=$ci_mirror/$filename.raw.xz
ci_image=$ci_mirror/$filename.qcow2
;;
esac
eval ${step}_img=${ci_image}
else
# 传统安装
if [ "$localtest" = 1 ]; then
mirror=$confhome/$releasever/
else
case $distro in
"centos")
case $releasever in
"7") mirrorlist="http://mirrorlist.centos.org/?release=7&arch=$basearch&repo=os" ;;
"8") mirrorlist="http://mirrorlist.centos.org/?release=8-stream&arch=$basearch&repo=BaseOS" ;;
"9") mirrorlist="https://mirrors.centos.org/mirrorlist?repo=centos-baseos-9-stream&arch=$basearch" ;;
esac
;;
"alma") mirrorlist="https://mirrors.almalinux.org/mirrorlist/$releasever/baseos" ;;
"rocky") mirrorlist="https://mirrors.rockylinux.org/mirrorlist?arch=$basearch&repo=BaseOS-$releasever" ;;
"fedora") mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?arch=$basearch&repo=fedora-$releasever" ;;
esac
# rocky/centos9 需要删除第一行注释, alma 需要替换$basearch
for cur_mirror in $(curl -L $mirrorlist | sed "/^#/d" | sed "s,\$basearch,$basearch,"); do
host=$(get_host_by_url $cur_mirror)
if is_host_has_ipv4_and_ipv6 $host &&
test_url ${cur_mirror}images/pxeboot/vmlinuz; then
mirror=$cur_mirror
break
fi
done
if [ -z "$mirror" ]; then
error_and_exit "All mirror failed."
fi
eval "${step}_mirrorlist='${mirrorlist}'"
fi
eval ${step}_ks=$confhome/redhat.cfg
eval ${step}_vmlinuz=${mirror}images/pxeboot/vmlinuz
eval ${step}_initrd=${mirror}images/pxeboot/initrd.img
eval ${step}_squashfs=${mirror}images/install.img
if [ "$releasever" = 7 ]; then
eval ${step}_squashfs=${mirror}LiveOS/squashfs.img
fi
fi
}
eval ${step}_distro=$distro
case "$distro" in
ubuntu) setos_ubuntu ;;
alpine) setos_alpine ;;
debian) setos_debian ;;
arch) setos_arch ;;
opensuse) setos_opensuse ;;
windows) setos_windows ;;
dd) setos_dd ;;
*) setos_redhat ;;
esac
}
is_distro_like_redhat() {
[ "$1" = centos ] || [ "$1" = alma ] || [ "$1" = rocky ] || [ "$1" = fedora ]
}
# 检查是否为正确的系统名
verify_os_string() {
for os in \
'centos-7|8|9' \
'alma-8|9' \
'rocky-8|9' \
'fedora-37|38' \
'ubuntu-20.04|22.04' \
'alpine-3.16|3.17|3.18' \
'debian-10|11|12' \
'opensuse-15|15.4|15.5|tumbleweed' \
'arch-' \
'windows-' \
'dd-'; do
ds=$(echo $os | cut -d- -f1)
vers=$(echo $os | cut -d- -f2 | sed 's \. \\\. g')
finalos=$(echo "$@" | tr '[:upper:]' '[:lower:]' | sed -n -E "s,^($ds)[ :-]?($vers)$,\1:\2,p")
if [ -n "$finalos" ]; then
distro=$(echo $finalos | cut -d: -f1)
releasever=$(echo $finalos | cut -d: -f2)
return
fi
done
error "Please specify a proper os"
usage_and_exit
}
apt_install() {
[ -z "$apk_updated" ] && apt update && apk_updated=1
apt install -y $pkgs
}
install_pkg() {
cmds=$*
is_in_windows && return
need_install=false
for cmd in $cmds; do
if ! command -v $cmd >/dev/null; then
need_install=true
break
fi
done
if $need_install; then
if is_in_alpine; then
add_community_repo_for_alpine
fi
# cmds to pkgs
for cmd in $cmds; do
pkg=
case $cmd in
unsquashfs) command -v zypper && pkg=squashfs || pkg=squashfs-tools ;;
lsmem) pkg=util-linux ;;
nslookup | dig)
{ command -v apk && pkg="bind-tools"; } ||
{ command -v apt && pkg="bind9-dnsutils"; } ||
{ command -v pacman && pkg="bind"; } ||
{ command -v yum dnf zypper && pkg="bind-utils"; }
;;
*) pkg=$cmd ;;
esac
pkgs+=" $pkg"
done
# command -v 有先后顺序dnf放yum前面
pkg_mgr=$(command -v dnf yum apt pacman zypper apk | head -1 | awk -F/ '{print $NF}')
case $pkg_mgr in
dnf) dnf install -y --setopt=install_weak_deps=False $pkgs ;;
yum) yum install -y $pkgs ;;
apk) apk add $pkgs ;;
apt) apt_install $pkgs ;;
pacman) pacman -Syu --noconfirm --needed $pkgs ;;
zypper) zypper install -y $pkgs ;;
*) error_and_exit "Unexpected package manager: $pkg_mgr" ;;
esac
fi
}
check_ram() {
if is_in_windows; then
ram_size=$(wmic memorychip get capacity | tail +2 | awk '{sum+=$1} END {print sum/1024/1024}')
else
# lsmem最准确但centos7 arm 和alpine不能用
# arm 24g dmidecode 显示少了128m
# arm 24g lshw 显示23BiB
# ec2 t4g arm alpine 用 lsmem 和 dmidecode 都无效,要用 lshw但结果和free -m一致其他平台则没问题
install_pkg lsmem
ram_size=$(lsmem -b 2>/dev/null | grep 'Total online memory:' | awk '{ print $NF/1024/1024 }')
if [ -z $ram_size ]; then
install_pkg dmidecode
ram_size=$(dmidecode -t 17 | grep "Size.*[GM]B" | awk '{if ($3=="GB") s+=$2*1024; else s+=$2} END {print s}')
fi
if [ -z $ram_size ]; then
install_pkg lshw
# 不能忽略 -ialpine 显示的是 System memory
ram_str=$(lshw -c memory -short | grep -i 'System Memory' | awk '{print $3}')
ram_size=$(grep <<<$ram_str -o '[0-9]*')
grep <<<$ram_str GiB && ram_size=$((ram_size * 1024))
fi
fi
if [ -z $ram_size ] || [ $ram_size -le 0 ]; then
error_and_exit "Could not detect RAM size."
fi
case "$distro" in
alpine) ram_installer=0 ;; # 未测试
debian) ram_installer=384 ;;
*) ram_installer=1024 ;;
esac
ram_cloud_image=512
# ram 足够就用普通方法安装否则如果内存大于512就用 cloud image
# TODO: 测试 256 384 内存
if [ $ram_size -lt $ram_cloud_image ]; then
if [ $ram_size -ge $ram_cloud_image ]; then
info "RAM < $ram_installer MB. Switch to cloud image mode"
cloud_image=1
else
error_and_exit "Could not install $distro: RAM < $ram_cloud_image MB."
fi
fi
}
is_efi() {
if is_in_windows; then
bcdedit | grep -q '^path.*\.efi'
else
[ -d /sys/firmware/efi ]
fi
}
install_grub_win() {
grub_cfg=$1 # /cygdrive/$c/grub.cfg
# 下载 grub
info download grub
grub_ver=2.06
is_in_china && grub_url=https://mirrors.tuna.tsinghua.edu.cn/gnu/grub/grub-$grub_ver-for-windows.zip ||
grub_url=https://ftp.gnu.org/gnu/grub/grub-$grub_ver-for-windows.zip
echo $grub_url
curl -Lo /tmp/grub.zip $grub_url
# unzip -qo /tmp/grub.zip
7z x /tmp/grub.zip -o/tmp -r -y -xr!i386-efi -xr!locale -xr!themes -bso0
grub_exe_dir=$(readlink -f /tmp/grub-$grub_ver-for-windows)
# 设置 grub 内嵌的模块
grub_modules+=" normal minicmd ls echo test cat reboot halt linux chain search all_video configfile"
grub_modules+=" scsi part_msdos part_gpt fat ntfs ntfscomp ext2 lvm xfs lzopio xzio gzio zstd"
if ! is_efi; then
grub_modules+=" biosdisk"
fi
# 设置 grub prefix 为c盘根目录
# 运行 grub-probe 会改变cmd窗口字体
prefix=$($grub_exe_dir/grub-probe -t drive $c: | sed 's,.*PhysicalDrive,(hd,' | sed 's,\r,,')/
echo $prefix
# 安装 grub
if is_efi; then
# efi
info install grub for efi
# 挂载
if result=$(find /cygdrive/?/EFI/Microsoft/Boot/bootmgfw.efi 2>/dev/null); then
# 已经挂载
x=$(echo $result | cut -d/ -f3)
else
# 找到空盘符并挂载
for x in {a..z}; do
[ ! -e /cygdrive/$x ] && break
done
mountvol $x: /s
fi
# 文件夹命名为reinstall而不是grub因为可能机器已经安装了grubbcdedit名字同理
grub_dir=$x:\\EFI\\reinstall
mkdir -p $grub_dir
# grub-mkimage 可设置prefix也可嵌入配置文件官方不建议嵌入menuentry条目
# -c $grub_cfg_win
$grub_exe_dir/grub-mkimage -p $prefix -O x86_64-efi -o $grub_dir\\grubx64.efi $grub_modules
# 添加引导
# 脚本可能不是首次运行,所以先删除原来的
bcdedit /enum bootmgr | grep --text -B3 Reinstall | awk '{print $2}' | grep '{.*}' |
xargs -I {} cmd /c bcdedit /delete {}
id=$(bcdedit /copy '{bootmgr}' /d Reinstall | grep -o '{.*}')
bcdedit /set $id device partition=$x:
bcdedit /set $id path \\EFI\\reinstall\\grubx64.efi
bcdedit /set '{fwbootmgr}' bootsequence $id
else
# bios
info install grub for bios
# bootmgr 加载 gr2ldr 有64k限制
# 解决方法1 gr2ldr.mbr + gr2ldr
# 解决方法2 生成少于64K的 g2ldr + 动态模块
# gr2ldr.mbr
curl -LO http://ftp.cn.debian.org/debian/tools/win32-loader/stable/win32-loader.exe
7z x win32-loader.exe 'g2ldr.mbr' -o/tmp/win32-loader -r -y -bso0
find /tmp/win32-loader -name 'g2ldr.mbr' -exec cp {} /cygdrive/$c/ \;
# g2ldr
$grub_exe_dir/grub-mkimage -p "$prefix" -O i386-pc -o core.img $grub_modules
cat $grub_exe_dir/i386-pc/lnxboot.img core.img >/cygdrive/$c/g2ldr
# 添加引导
# 脚本可能不是首次运行,所以先删除原来的
id='{1c41f649-1637-52f1-aea8-f96bfebeecc8}'
bcdedit /enum all | grep --text $id && bcdedit /delete $id
bcdedit /create $id /d Reinstall /application bootsector
bcdedit /set $id device partition=$c:
bcdedit /set $id path \\g2ldr.mbr
bcdedit /displayorder $id /addlast
bcdedit /bootsequence $id /addfirst
fi
}
# 脚本入口
# 检查 root
if ! is_in_windows; then
if [ "$EUID" -ne 0 ]; then
info "Please run as root."
exit 1
fi
fi
# 整理参数
if ! opts=$(getopt -n $0 -o "" --long localtest,debug,sleep:,iso:,image-name:,img:,ci,cloud-image -- "$@"); then
usage_and_exit
fi
eval set -- "$opts"
# shellcheck disable=SC2034
while true; do
case "$1" in
--localtest)
localtest=1
confhome=$localtest_confhome
shift
;;
--debug)
set -x
shift
;;
--ci | --cloud-image)
cloud_image=1
shift
;;
--sleep)
sleep=$2
shift 2
;;
--img)
img=$2
shift 2
;;
--iso)
iso=$2
shift 2
;;
--image-name)
image_name=$2
shift 2
;;
--)
shift
break
;;
*)
echo "Unexpected option: $1."
usage_and_exit
;;
esac
done
# 验证目标系统字符串
verify_os_string "$@"
# win系统盘
if is_in_windows; then
c=$(echo $SYSTEMDRIVE | cut -c1)
fi
# 必备组件
install_pkg curl
# alpine 自带的 grep 是 busybox 里面的, 要下载完整版grep
if is_in_alpine; then
apk add grep
fi
# 检查内存
if ! { [ "$distro" = dd ] || [ "$distro" = windows ]; }; then
check_ram
fi
# alpine --ci 参数无效
if [ "$distro" = alpine ] && is_use_cloud_image; then
error_and_exit "can't install alpine with cloud image"
fi
# 检查硬件架构
# x86强制使用x64
basearch=$(uname -m)
[ $basearch = i686 ] && basearch=x86_64
case "$basearch" in
"x86_64") basearch_alt=amd64 ;;
"aarch64") basearch_alt=arm64 ;;
esac
# 设置 github 国内代理
set_github_proxy
# 以下目标系统不需要进入alpine
# debian
# el7 x86_64 >=1g
# el7 aarch64 >=1.5g
# el8/9/fedora 任何架构 >=2g
if ! is_use_cloud_image &&
{ [ "$distro" = "debian" ] ||
{ is_distro_like_redhat "$distro" && [ $releasever -eq 7 ] && [ $ram_size -ge 1024 ] && [ $basearch = "x86_64" ]; } ||
{ is_distro_like_redhat "$distro" && [ $releasever -eq 7 ] && [ $ram_size -ge 1536 ] && [ $basearch = "aarch64" ]; } ||
{ is_distro_like_redhat "$distro" && [ $releasever -ge 8 ] && [ $ram_size -ge 2048 ]; }; }; then
setos nextos $distro $releasever
else
# 安装alpine时使用指定的版本。 alpine作为中间系统时使用 3.18
[ "$distro" = "alpine" ] && alpine_releasever=$releasever || alpine_releasever=3.18
setos finalos $distro $releasever
setos nextos alpine $alpine_releasever
fi
# 测试链接
# 在 ubuntu 20.04 上file 命令检测 ubuntu 22.04 iso 结果不正确,所以去掉 iso 检测
if is_use_cloud_image; then
test_url $finalos_img 'xz|gzip|qemu' finalos_img_type
elif is_use_dd; then
test_url $finalos_img 'xz|gzip' finalos_img_type
elif [ -n "$finalos_img" ]; then
test_url $finalos_img
elif [ -n "$finalos_iso" ]; then
test_url $finalos_iso
fi
# shellcheck disable=SC2154
{
# 下载 nextos 内核
info download vmlnuz and initrd
echo $nextos_vmlinuz
curl -Lo /reinstall-vmlinuz $nextos_vmlinuz
echo $nextos_initrd
curl -Lo /reinstall-initrd $nextos_initrd
}
# 转换 finalos_a=1 为 finalos.a=1 ,排除 finalos_mirrorlist
build_finalos_cmdline() {
if vars=$(compgen -v finalos_); then
for key in $vars; do
value=${!key}
key=${key#finalos_}
if [ -n "$value" ] && [ $key != "mirrorlist" ]; then
finalos_cmdline+=" finalos.$key='$value'"
fi
done
fi
}
build_extra_cmdline() {
for key in localtest confhome sleep cloud_image; do
value=${!key}
if [ -n "$value" ]; then
extra_cmdline+=" extra.$key='$value'"
fi
done
# 指定最终安装系统的 mirrorlist链接有&在grub中是特殊字符所以要加引号
if [ -n "$finalos_mirrorlist" ]; then
extra_cmdline+=" extra.mirrorlist='$finalos_mirrorlist'"
elif [ -n "$nextos_mirrorlist" ]; then
extra_cmdline+=" extra.mirrorlist='$nextos_mirrorlist'"
fi
}
# shellcheck disable=SC2154
build_cmdline() {
if [ -n "$finalos_cmdline" ]; then
# 有 finalos_cmdline 表示需要两步安装
# 两步安装需要修改 alpine initrd
mod_alpine_initrd
# 可添加 pkgs=xxx,yyy 启动时自动安装
# apkovl=http://xxx.com/apkovl.tar.gz 可用arm https未测但应该不行
# apkovl=sda2:ext4:/apkovl.tar.gz 官方有写但不生效
cmdline="alpine_repo=$nextos_repo modloop=$nextos_modloop $extra_cmdline $finalos_cmdline"
else
if [ $distro = debian ]; then
cmdline="lowmem=+1 lowmem/low=1 auto=true priority=critical url=$nextos_ks $extra_cmdline"
else
# redhat
cmdline="root=live:$nextos_squashfs inst.ks=$nextos_ks $extra_cmdline"
fi
fi
}
# 脚本可能多次运行,先清理之前的残留
mkdir_clear() {
dir=$1
if [ -z "$dir" ] || [ "$dir" = / ]; then
return
fi
# alpine 没有 -R
# { umount $dir || umount -R $dir || true; } 2>/dev/null
rm -rf $dir
mkdir -p $dir
}
mod_alpine_initrd() {
# 修改 alpine 启动时运行我们的脚本
info mod alpine initrd
install_pkg gzip cpio
# 解压
# 先删除临时文件,避免之前运行中断有残留文件
tmp_dir=/tmp/reinstall
mkdir_clear $tmp_dir
cd $tmp_dir
zcat /reinstall-initrd | cpio -idm
# 预先下载脚本
curl -Lo $tmp_dir/trans.start $confhome/trans.sh
# virt 内核添加 ipv6 模块
if virt_dir=$(ls -d $tmp_dir/lib/modules/*-virt 2>/dev/null); then
ipv6_dir=$virt_dir/kernel/net/ipv6
mkdir -p $ipv6_dir
modloop_file=/tmp/modloop_file
modloop_dir=/tmp/modloop_dir
curl -Lo $modloop_file $nextos_modloop
if is_in_windows; then
# cygwin 没有 unsquashfs
7z e $modloop_file ipv6.ko -r -y -oa$ipv6_dir
else
install_pkg unsquashfs
mkdir_clear $modloop_dir
unsquashfs -f -d $modloop_dir $modloop_file 'modules/*/kernel/net/ipv6/ipv6.ko'
find $modloop_dir -name ipv6.ko -exec cp {} $ipv6_dir/ \;
fi
fi
# hack 运行 trans.start
# exec /bin/busybox switch_root $switch_root_opts $sysroot $chart_init "$KOPT_init" $KOPT_init_args # 3.17
# exec switch_root $switch_root_opts $sysroot $chart_init "$KOPT_init" $KOPT_init_args # 3.18
line_num=$(grep -E -n '^exec (/bin/busybox )?switch_root' init | cut -d: -f1)
line_num=$((line_num - 1))
# 1. alpine arm initramfs 时间问题 要添加 --no-check-certificate
# 2. 神奇的 aws t4g arm 会出现bad header错误 但甲骨文arm就正常所以只能预先下载
# Connecting to raw.githubusercontent.com (185.199.108.133:443)
# 60C0BB2FFAFF0000:error:0A00009C:SSL routines:ssl3_get_record:http request:ssl/record/ssl3_record.c:345:
# ssl_client: SSL_connect
# wget: bad header line: <20>
cat <<EOF | sed -i "${line_num}r /dev/stdin" init
# wget --no-check-certificate -O \$sysroot/etc/local.d/trans.start $confhome/trans.sh
cp /trans.start \$sysroot/etc/local.d/trans.start
chmod a+x \$sysroot/etc/local.d/trans.start
ln -s /etc/init.d/local \$sysroot/etc/runlevels/default/
EOF
# 重建
# 注意要用 cpio -H newc 不要用 cpio -c ,不同版本的 -c 作用不一样,很坑
# -c Use the old portable (ASCII) archive format
# -c Identical to "-H newc", use the new (SVR4)
# portable format.If you wish the old portable
# (ASCII) archive format, use "-H odc" instead.
find . | cpio -o -H newc | gzip -1 >/reinstall-initrd
cd -
}
build_finalos_cmdline
build_extra_cmdline
build_cmdline
info 'create grub config'
# linux grub
if ! is_in_windows; then
# 找出主配置文件含有menuentry|blscfg
# 如果是efi先搜索efi目录
# arch云镜像efi分区挂载在/efi
if is_efi; then
for dir in /boot/efi /efi; do
[ -d $dir ] && efi_dir+=" $dir"
done
fi
grub_cfg=$(
find $efi_dir /boot/grub* \
-type f -name grub.cfg \
-exec grep -E -l 'menuentry|blscfg' {} \; | xargs -0 | head -1
)
# 在x86 efi机器上不同版本的 grub 可能用 linux 或 linuxefi 加载内核
# 通过检测原有的条目有没有 linuxefi 字样就知道当前 grub 用哪一种
if [ -d /boot/loader/entries/ ]; then
entries="/boot/loader/entries/"
fi
if grep -q -r -E '^[[:blank:]]*linuxefi[[:blank:]]' $grub_cfg $entries; then
efi=efi
fi
fi
# 生成 custom.cfg (linux) 或者 grub.cfg (win)
is_in_windows && custom_cfg=/cygdrive/$c/grub.cfg || custom_cfg=$(dirname $grub_cfg)/custom.cfg
echo $custom_cfg
cat <<EOF | tee $custom_cfg
set timeout=5
menuentry "reinstall" {
insmod lvm
insmod xfs
search --no-floppy --file --set=root /reinstall-vmlinuz
linux$efi /reinstall-vmlinuz $cmdline
initrd$efi /reinstall-initrd
}
EOF
if is_in_windows; then
mv /reinstall-vmlinuz /cygdrive/$c/
mv /reinstall-initrd /cygdrive/$c/
install_grub_win $custom_cfg
else
if is_os_in_btrfs && is_os_in_subvol; then
cp_to_btrfs_root /reinstall-vmlinuz
cp_to_btrfs_root /reinstall-initrd
fi
$(command -v grub-reboot grub2-reboot) reinstall
fi
if is_use_cloud_image; then
info 'cloud image mode'
elif is_use_dd; then
info 'dd mode'
else
info 'installer mode'
fi
info 'Please reboot to begin the installation'