#!/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= vendor= model= d= for disk in $@; do d=${disk##*/} vendor=$(cat /sys/block/$d/device/vendor 2>/dev/null) model=$(cat /sys/block/$d/device/model 2>/dev/null) size=$(awk '{gb = ($1 * 512)/1000000000; printf "%.1f GB\n", gb}' /sys/block/$d/size 2>/dev/null) echo " $disk ($size $vendor $model)" 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 a traditional disk install or for a data-only install. The disk will be erased. Enter 'none' if you want to run diskless. __EOF__ } diskmode_help() { cat <<__EOF__ You can select between 'sys' or 'data'. sys: This mode is a traditional disk install. The following partitions will be created on the disk: /boot, / (filesystem root) and swap. This mode may be used for development boxes, desktops, virtual servers, etc. data: This mode uses your disk(s) for data storage, not for the operating system. 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 -L Use LVM to manage partitions -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 else DISK_MODE="none" 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;; none) exit 0;; *) echo "Not a valid install mode: $DISK_MODE" >&2; exit 1;; esac