#!/bin/sh PREFIX= . "$PREFIX/lib/libalpine.sh" MBR=${MBR:-"/usr/share/syslinux/mbr.bin"} ROOTFS=${ROOTFS:-ext4} BOOTFS=${BOOTFS:-ext4} in_list() { local i="$1" shift while [ $# -gt 0 ]; do [ "$i" = "$1" ] && return 0 shift done return 1 } # wrapper to only show given device _blkid() { blkid | grep "^$1:" } # if given device have an UUID display it, otherwise return the device uuid_or_device() { local i= case "$1" in /dev/md*) echo "$1" && return 0;; esac for i in $(_blkid "$1"); do case "$i" in UUID=*) eval $i;; esac done if [ -n "$UUID" ]; then echo "UUID=$UUID" else echo "$1" fi } # generate an fstab from a given mountpoint. Convert to UUID if possible enumerate_fstab() { local mnt="$1" local fs_spec= fs_file= fs_vfstype= fs_mntops= fs_freq= fs_passno= [ -z "$mnt" ] && return local escaped_mnt=$(echo $mnt | sed 's:/:\\/:g') awk "\$2 ~ /^$escaped_mnt/ {print \$0}" /proc/mounts | \ sed "s:$mnt:/:g; s: :\t:g" | sed 's:/\+:/:g' | \ while read fs_spec fs_file fs_vfstype fs_mntops fs_freq fs_passno; do echo -e "$(uuid_or_device $fs_spec)\t${fs_file}\t${fs_vfstype}\t${fs_mntops} ${fs_freq} ${fs_passno}" done } is_vmware() { grep -q VMware /proc/scsi/scsi 2>/dev/null \ || grep -q VMware /proc/ide/hd*/model 2>/dev/null } is_xen() { [ -d /proc/xen ] } # return true (0) if given device is lvm is_lvm() { lvs "$1" >/dev/null 2>&1 } # Find the disk device from given partition disk_from_part() { # we need convert cciss/c0d0* cciss!c0d0*... local i= part=$(echo ${1#/dev/} | sed 's:/:!:g') for i in /sys/block/*/$part; do i=${i%/*} # ...and back from cciss!c0d0 to cciss/c0d0 if [ -b "/dev/${i##*/}" ]; then echo "/dev/${i##*/}" | sed 's:!:/:g' return 0 fi done return 1 } unpack_apkovl() { local ovl="$1" local dest="$2" local suffix=${ovl##*.} local i ovlfiles=/tmp/ovlfiles if [ "$suffix" = "gz" ]; then if ! tar -C "$dest" --numeric-owner -zxvf "$ovl" > $ovlfiles; then echo -n "Continue anyway? [Y/n]: " read i case "$i" in n*|N*) return 1;; esac fi return 0 fi apk add -q openssl if ! openssl list-cipher-commands | grep "^$suffix$" > /dev/null; then errstr="Cipher $suffix is not supported" return 1 fi local count=0 # beep echo -e "\007" while [ $count -lt 3 ]; do openssl enc -d -$suffix -in "$ovl" | tar --numeric-owner \ -C "$dest" -zxv >$ovlfiles 2>/dev/null && return 0 count=$(( $count + 1 )) done ovlfiles= return 1 } # find filesystem of given mounted dir find_mount_fs() { local mount_point="$1" awk "\$2 == \"$mount_point\" {print \$3}" /proc/mounts | tail -n 1 } # find device for given mounted dir find_mount_dev() { local mnt="$1" awk "\$2 == \"$mnt\" { print \$1 }" /proc/mounts | tail -n 1 } supported_boot_fs() { local supported="ext2 ext3 ext4 btrfs" local fs= for fs in $supported; do [ "$fs" = "$1" ] && return 0 done echo "$1 is not supported. Only supported are: $supported" >&2 return 1 } find_volume_group() { local lv=${1##*/} lvs --noheadings "$1" | awk "\$1 == \"$lv\" {print \$2}" } find_pvs_in_vg() { local vg="$1" pvs --noheadings | awk "\$2 == \"$vg\" {print \$1}" } install_mounted_root() { local mnt="$1" mnt_boot="$1" boot_fs= root_fs= local initfs_features="ata base ide scsi usb virtio" local pvs= dev= rootdev= bootdev= raidopt= rootdev=$(find_mount_dev "$mnt") if [ -z "$rootdev" ]; then echo "$mnt does not seem to be a mount point" >&2 return 1 fi root_fs=$(find_mount_fs "$mnt") initfs_features="$initfs_features $root_fs" if is_lvm "$rootdev"; then initfs_features="$initfs_features lvm" local vg=$(find_volume_group "$rootdev") pvs=$(find_pvs_in_vg $vg) fi bootdev=$(find_mount_dev "$mnt"/boot) if [ -z "$bootdev" ]; then bootdev=$rootdev else mnt_boot="$mnt"/boot bootdev=$(find_mount_dev "$mnt_boot") fi boot_fs=$(find_mount_fs "$mnt_boot") supported_boot_fs "$boot_fs" || return 1 if [ -e "/sys/block/${bootdev#/dev/}/md" ]; then raidopt="--raid" fi for dev in $rootdev $pvs; do [ -e "/sys/block/${dev#/dev/}/md" ] || continue local md=${dev#/dev/} initfs_features="$(echo $initfs_features | sed 's/raid//') raid" local level=$(cat /sys/block/$md/md/level) case "$level" in raid1) raidmod="$raidmod,$level";; raid[456]) raidmod="$raidmod,raid456";; esac done if [ -n "$VERBOSE" ]; then echo "Root device: $rootdev" echo "Root filesystem: $root_fs" echo "Boot device: $bootdev" echo "Boot filesystem: $boot_fs" fi if [ -z "$APKOVL" ]; then ovlfiles=/tmp/ovlfiles lbu package - | tar -C "$mnt" -zxv > "$ovlfiles" else echo "Restoring backup from $APKOVL to $rootdev..." unpack_apkovl "$APKOVL" "$mnt" || return 1 fi # generate mkinitfs.conf mkdir -p "$mnt"/etc/mkinitfs/files.d echo "features=\"$initfs_features\"" > "$mnt"/etc/mkinitfs/mkinitfs.conf if [ -n "$raidmod" ]; then echo "/sbin/mdadm" > "$mnt"/etc/mkinitfs/files.d/raid echo "/etc/mdadm.conf" >> "$mnt"/etc/mkinitfs/files.d/raid fi # generate the fstab if [ -f "$mnt"/etc/fstab ]; then mv "$mnt"/etc/fstab "$mnt"/etc/fstab.old fi enumerate_fstab "$mnt" >> "$mnt"/etc/fstab cat >>"$mnt"/etc/fstab </dev/null) pkgs="$pkgs acct linux-$KERNEL_FLAVOR alpine-base" local repos=$(sed -e 's/\#.*//' /etc/apk/repositories) local repoflags= for i in $repos; do repoflags="$repoflags --repository $i" done apk add --root "$mnt" $apkflags --overlay-from-stdin \ $repoflags $pkgs <$ovlfiles>/dev/null || return 1 echo "" # make things bootable if is_vmware; then pax_nouderef="pax_nouderef " else pax_nouderef= fi if is_xen; then # create a menu.lst mkdir -p "$mnt"/boot/grub cat >"$mnt"/boot/grub/menu.lst <"$mnt"/boot/extlinux.conf <&2 return 1 fi # check so its not an md device [ -e /sys/block/$b/md ] && return 1 return 0 } find_disks() { local p= for p in $(awk '$1 ~ /[0-9]+/ {print $4}' /proc/partitions); do is_available_disk $p && echo -n " $p" done } stop_all_raid() { local rd for rd in /dev/md*; do [ -b $rd ] && mdadm --stop $rd done } # install needed programs init_progs() { local raidpkg= [ -n "$USE_RAID" ] && raidpkg="mdadm" apk_add -q sfdisk e2fsprogs lvm2 $raidpkg $@ } show_disk_info() { local disk= for disk in $@; do # TODO: extend ifo with size, model etc... echo " $disk" done } confirm_erase() { local answer= echo "WARNING: The following disk(s) will be erased:" show_disk_info $@ echo -n "WARNING: Erase the above disk(s) and continue? [y/N]: " read answer case "$answer" in y*|Y*) return 0;; esac return 1 } # setup disk dev in $1 for LVM usage. # usage: setup_partitions size1,type1 [size2,type2 ...] setup_partitions() { local diskdev="$1" shift echo "Initializing partitions on $diskdev..." # new disks does not have an DOS signature in sector 0 # this makes sfdisk complain. We can workaround this by letting # fdisk create that DOS signature, by just do a "w", a write. # http://bugs.alpinelinux.org/issues/show/145 echo "w" | fdisk $diskdev >/dev/null # fix the MBR while here so extlinux can boot cat "$MBR" > $diskdev local start=0 local line= # create new partitions ( for line in "$@"; do echo "$start,$line" start= done ) | sfdisk -q -L -uM $diskdev >>/tmp/sfdisk.out || return 1 # create device nodes if not exist mdev -s } # find the bootable partition on given disk find_boot_partition() { sfdisk -d $1 | awk '/bootable/ {print $1}' } # find the partition(s) for LVM # this is not marked as bootable and is type 8e find_lvm_partition() { local type=8e sfdisk -d $1 | grep -v bootable | awk "/Id=$type/ {print \$1}" } # set up boot device. We only use raid1 for boot devices if any raid setup_boot_dev() { local disk= bootdev= local part=$(for disk in $@; do find_boot_partition $disk; done) set -- $part bootdev=$1 [ -z "$bootdev" ] && return 1 echo "Creating file systems..." if [ -n "$USE_RAID" ]; then local missing= local num=$# if [ $# -eq 1 ]; then missing="missing" num=2 fi mdadm --create /dev/md0 --level=1 --raid-devices=$num \ --metadata=0.90 --quiet --run $@ $missing || return 1 bootdev=/dev/md0 fi mkfs.$BOOTFS -q $bootdev BOOT_DEV="$bootdev" } # $1 = index # $2 = partition type # $3... = disk devices find_nth_non_boot_parts() { local idx=$1 local id=$2 local disk= shift shift for disk in $@; do sfdisk -d $disk | grep -v bootable \ | awk "/Id=$id/ { i++; if (i==$idx) print \$1 }" done } setup_non_boot_raid_dev() { local md_dev=$1 local idx=${md_dev#/dev/md} shift local level=1 local numdevs=$# local missing= local raid_parts=$(find_nth_non_boot_parts $idx "fd" $@) set -- $raid_parts # how many disks do we have? case $# in 0) echo "No Raid partitions found" >&2; return 1;; 1) level=1; missing="missing"; num=2;; 2) level=1; missing= ; num=2;; *) level=5; missing= ; num=$#;; esac mdadm --create /dev/md$idx --level=$level --raid-devices=$num \ --quiet --run $@ $missing || return 1 } # setup device for lvm, create raid array if needed setup_lvm_volume_group() { local vgname="$1" shift local lvmdev= if [ -n "$USE_RAID" ]; then setup_non_boot_raid_dev /dev/md1 $@ || return 1 lvmdev=/dev/md1 else lvmdev=$(find_lvm_partition $1) fi # be quiet on success local errmsg=$(dd if=/dev/zero of=$lvmdev bs=1k count=1 2>&1) \ || echo "$errmsg" pvcreate --quiet $lvmdev \ && vgcreate --quiet $vgname $lvmdev >/dev/null } # set up swap on given device(s) setup_swap_dev() { local swap_dev= sed -i -e '/swap/d' /etc/fstab for swap_dev in "$@"; do mkswap $swap_dev >/dev/null echo -e "$swap_dev\tswap\t\tswap\tdefaults 0 0" >> /etc/fstab done swapon -a rc-update --quiet add swap boot } # setup and enable swap on given volumegroup if needed setup_lvm_swap() { local vgname="$1" local swapname=lv_swap if [ -z "$SWAP_SIZE" ] || [ "$SWAP_SIZE" -eq 0 ]; then return fi lvcreate --quiet -n $swapname -L ${SWAP_SIZE}MB $vgname setup_swap_dev /dev/$vgname/$swapname } # if /var is mounted, move out data and umount it reset_var() { [ -z "$(find_mount_dev /var)" ] && return 0 mkdir /.var mv /var/* /.var/ 2>/dev/null umount /var && rm -rf /var && mv /.var /var && rm -rf /var/lost+found } # set up /var on given device setup_var() { local var_dev="$1" local varfs=ext4 echo "Creating file systems..." mkfs.$varfs -q $var_dev >/dev/null || return 1 sed -i -e '/[[:space:]]\/var[[:space:]]/d' /etc/fstab echo -e "${var_dev}\t/var\t\t${varfs}\tdefaults 1 2" >> /etc/fstab mv /var /.var mkdir /var mount /var mv /.var/* /var/ rmdir /.var /etc/init.d/syslog --quiet restart } setup_mdadm_conf() { if [ -n "$USE_RAID" ]; then mdadm --detail --scan > /etc/mdadm.conf rc-update --quiet add mdadm-raid boot fi } data_only_disk_install_lvm() { local diskdev= local vgname=vg0 local var_dev=/dev/$vgname/lv_var local lvm_part_type="8e" local raid_part_type="fd" local part_type=$lvm_part_type local size= init_progs || return 1 confirm_erase $@ || return 1 if [ "$USE_RAID" ]; then part_type=$raid_part_type stop_all_raid fi for diskdev in "$@"; do setup_partitions $diskdev "$size,$part_type" || return 1 done setup_lvm_volume_group $vgname $@ || return 1 setup_lvm_swap $vgname lvcreate --quiet -n ${var_dev##*/} -l 100%FREE $vgname setup_mdadm_conf setup_var $var_dev } data_only_disk_install() { local diskdev= local var_dev=/dev/$vgname/lv_var local var_part_type="8e" local swap_part_type=82 local size= local swap_dev= var_dev= init_progs || return 1 confirm_erase $@ || return 1 if [ "$USE_RAID" ]; then var_part_type="fd" swap_part_type="fd" stop_all_raid fi for diskdev in "$@"; do setup_partitions $diskdev \ "$SWAP_SIZE,$swap_part_type" \ "$size,$var_part_type" || return 1 done if [ "$USE_RAID" ]; then [ $SWAP_SIZE -gt 0 ] && setup_non_boot_raid_dev /dev/md1 $@ setup_non_boot_raid_dev /dev/md2 $@ || return 1 swap_dev=/dev/md1 var_dev=/dev/md2 else swap_dev=$(find_nth_non_boot_parts 1 82 $@) var_dev=$(find_nth_non_boot_parts 1 83 $@) fi [ $SWAP_SIZE -gt 0 ] && setup_swap_dev $swap_dev setup_var $var_dev } # setup setup_root() { local root_dev="$1" boot_dev="$2" mkfs.$ROOTFS -q "$root_dev" mkdir -p /mnt mount -t $ROOTFS $root_dev /mnt || return 1 if [ -n "$boot_dev" ]; then mkdir -p /mnt/boot mount -t $BOOTFS $boot_dev /mnt/boot || return 1 fi setup_mdadm_conf install_mounted_root /mnt || return 1 unmount_partitions /mnt swapoff -a echo "" echo "Installation is complete. Please reboot." } native_disk_install_lvm() { local diskdev= vgname=vg0 local lvm_part_type="8e" local raid_part_type="fd" local boot_part_type="83" local boot_size=${BOOT_SIZE:-100} local lvm_size= local root_dev=/dev/$vgname/lv_root init_progs syslinux || return 1 confirm_erase $@ || return 1 if [ -n "$USE_RAID" ]; then boot_part_type="fd" lvm_part_type="fd" stop_all_raid fi for diskdev in "$@"; do setup_partitions $diskdev \ "$boot_size,$boot_part_type,*" \ "$lvm_size,$lvm_part_type" || return 1 done # will find BOOT_DEV for us setup_boot_dev $@ setup_lvm_volume_group $vgname $@ || return 1 setup_lvm_swap $vgname lvcreate --quiet -n ${root_dev##*/} -l 100%FREE $vgname setup_root $root_dev $BOOT_DEV } native_disk_install() { local root_part_type="83" swap_part_type="82" boot_part_type="83" local boot_size=${BOOT_SIZE:-100} local swap_size=${SWAP_SIZE} local root_size= local root_dev= boot_dev= swap_dev= init_progs syslinux || return 1 confirm_erase $@ || return 1 if [ -n "$USE_RAID" ]; then boot_part_type="fd" root_part_type="fd" swap_part_type="fd" stop_all_raid fi for diskdev in "$@"; do setup_partitions $diskdev \ "$boot_size,$boot_part_type,*" \ "$swap_size,$swap_part_type" \ "$root_size,$root_part_type" \ || return 1 done # will find BOOT_DEV for us setup_boot_dev $@ if [ "$USE_RAID" ]; then [ $SWAP_SIZE -gt 0 ] && setup_non_boot_raid_dev /dev/md1 $@ setup_non_boot_raid_dev /dev/md2 $@ || return 1 swap_dev=/dev/md1 root_dev=/dev/md2 else swap_dev=$(find_nth_non_boot_parts 1 82 $@) root_dev=$(find_nth_non_boot_parts 1 83 $@) fi [ $SWAP_SIZE -gt 0 ] && setup_swap_dev $swap_dev setup_root $root_dev $BOOT_DEV } diskselect_help() { cat <<__EOF__ The disk you select can be used for either traditional disk install data-only install. The disk will be erased. Select 'none' if you want run diskless. __EOF__ } diskmode_help() { cat <<__EOF__ You can select between 'sys' or 'data'. * sys This mode is a traditional disk install. A /boot partition, / (root) and swap will be created. Use this mode for development boxes, desktops, virtual servers, etc. * data This mode uses your disk(s) for data-only. The system itself will run from tmpfs (RAM). Use this mode if you only want to use the disk(s) for a mailspool, databases, logs, etc. __EOF__ } # ask for a root or data disk # returns answer in global variable $answer ask_disk() { local prompt="$1" local help_func="$2" shift 2 answer= while ! in_list "$answer" $@ "none" "abort"; do echo "Available disks are: $@" echon "$prompt [$1] " default_read answer $1 case "$answer" in 'abort') exit 0;; 'none') return 0;; '?') $help_func;; *) if ! [ -b "/dev/$answer" ]; then echo "/dev/$answer is not a block device" >&2 answer= fi;; esac done } usage() { cat <<__EOF__ usage: setup-disk [-hqr] [-k kernelflavor] [-m MODE] [-o apkovl] [-s SWAPSIZE] [MOUNTPOINT | DISKDEV...] Install alpine on harddisk. If MOUNTPOINT is specified, then do a traditional disk install with MOUNTPOINT as root. If DISKDEV is specified, then use the specified disk(s) without asking. If multiple disks are specified then set them up in a RAID array. If there are mode than 2 disks, then use raid level 5 instead of raid level 1. options: -h Show this help -m Use disk for MODE without asking, where MODE is either 'data' or 'root' -o Restore system from given apkovl file -k Use kernelflavor instead of $KERNEL_FLAVOR -q Exit quietly if no disks are found -r Enable software raid1 with single disk -s Use SWAPSIZE MB instead of $SWAP_SIZE MB for swap (Use 0 to disable swap) -v Be more verbose about what is happening __EOF__ exit 1 } KERNEL_FLAVOR=grsec case "$(uname -r)" in *-vs[0-9]*) KERNEL_FLAVOR=vserver;; *-pae) KERNEL_FLAVOR=pae;; esac DISK_MODE= USE_LVM= SWAP_SIZE=$(find_swap_size) # Parse args while getopts "hk:Lm:o:qrs:v" opt; do case $opt in m) DISK_MODE="$OPTARG";; k) KERNEL_FLAVOR="$OPTARG";; L) USE_LVM="_lvm";; o) APKOVL="$OPTARG";; q) QUIET=1;; r) USE_RAID=1;; s) SWAP_SIZE="$OPTARG";; v) VERBOSE=1;; *) usage;; esac done shift $(( $OPTIND - 1)) if [ -d "$1" ]; then # install to given mounted root install_mounted_root "${1%/}" exit $? fi reset_var swapoff -a # stop all volume groups in use vgchange --ignorelockingfailure -a n >/dev/null 2>&1 if [ -n "$USE_RAID" ]; then stop_all_raid fi disks=$(find_disks) diskdevs= # no disks so lets exit quietly. if [ -z "$disks" ]; then [ -z "$QUIET" ] && echo "No disks found." >&2 exit 0 fi if [ $# -gt 0 ]; then # check that they are for i in "$@"; do j=$(readlink -f "$i" | sed 's:^/dev/::; s:/:!:g') if ! [ -e "/sys/block/$j/device" ]; then echo "$i is not a suitable for partitioning" exit 1 fi diskdevs="$diskdevs /dev/$j" done else ask_disk "Which disk would you like to use? (or '?' for help or 'none')" \ diskselect_help $disks if [ "$answer" != none ]; then diskdevs=/dev/$answer fi fi if [ -n "$diskdevs" ] && [ -z "$DISK_MODE" ]; then answer= disk_is_or_disks_are="disk is" it_them="it" if [ $# -gt 1 ]; then disk_is_or_disks_are="disks are" it_them="them" fi while true; do echo "The following $disk_is_or_disks_are selected:" show_disk_info $diskdevs echon "How would you like to use $it_them? ('sys', 'data' or '?' for help) [?] " default_read answer '?' case "$answer" in '?') diskmode_help;; sys|data) break;; esac done DISK_MODE="$answer" fi set -- $diskdevs if [ $# -gt 1 ]; then USE_RAID=1 fi dmesg -n1 # native disk install case "$DISK_MODE" in sys) native_disk_install$USE_LVM $diskdevs;; data) data_only_disk_install$USE_LVM $diskdevs;; *) echo "Not a valid install mode: $DISK_MODE" >&2; exit 1;; esac