#!/bin/sh # simple script to set up a new vserver guest # test the first argument against the remaining ones, return success on a match isin() { local _a=$1 _b shift for _b; do [ "$_a" = "$_b" ] && return 0 done return 1 } # remove all occurrences of first argument from list formed by # the remaining arguments rmel() { local _a=$1 _b shift for _b; do [ "$_a" != "$_b" ] && echo -n "$_b " done } # Issue a read into the global variable $resp. _ask() { local _redo=0 read resp case "$resp" in !) echo "Type 'exit' to return to setup." sh _redo=1 ;; !*) eval "${resp#?}" _redo=1 ;; esac return $_redo } # Ask for user input. # # $1 = the question to ask the user # $2 = the default answer # # Save the user input (or the default) in $resp. # # Allow the user to escape to shells ('!') or execute commands # ('!foo') before entering the input. ask() { local _question=$1 _default=$2 while :; do echo -n "$_question " [ -z "$_default" ] || echo -n "[$_default] " _ask && : ${resp:=$_default} && break done } # Ask for user input until a non-empty reply is entered. # # $1 = the question to ask the user # $2 = the default answer # # Save the user input (or the default) in $resp. ask_until() { resp= while [ -z "$resp" ] ; do ask "$1" "$2" done } # Ask for the user to select one value from a list, or 'done'. # # $1 = name of the list items (disk, cd, etc.) # $2 = question to ask # $3 = list of valid choices # $4 = default choice, if it is not specified use the first item in $3 # # N.B.! $3 and $4 will be "expanded" using eval, so be sure to escape them # if they contain spooky stuff # # At exit $resp holds selected item, or 'done' ask_which() { local _name=$1 _query=$2 _list=$3 _def=$4 _dynlist _dyndef while :; do # Put both lines in ask prompt, rather than use a # separate 'echo' to ensure the entire question is # re-ask'ed after a '!' or '!foo' shell escape. eval "_dynlist=\"$_list\"" eval "_dyndef=\"$_def\"" # Clean away whitespace and determine the default set -o noglob set -- $_dyndef; _dyndef="$1" set -- $_dynlist; _dynlist="$*" set +o noglob [ $# -lt 1 ] && resp=done && return : ${_dyndef:=$1} echo "Available ${_name}s are: $_dynlist." echo -n "Which one $_query? (or 'done') " [ -n "$_dyndef" ] && echo -n "[$_dyndef] " _ask || continue [ -z "$resp" ] && resp="$_dyndef" # Quote $resp to prevent user from confusing isin() by # entering something like 'a a'. isin "$resp" $_dynlist done && break echo "'$resp' is not a valid choice." done } # Ask for user input until a non-empty reply is entered. # # $1 = the question to ask the user # $2 = the default answer # # Save the user input (or the default) in $resp. ask_until() { resp= while [ -z "$resp" ] ; do ask "$1" "$2" done } # http://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names valid_hostname() { # check length if [ $(echo "$1" | wc -c) -gt 255 ]; then echo "Hostname '$1' is too long." return 1 fi # check that it only contains valid chars if ! [ -z "$(echo $1 | sed 's/[0-9.a-z-]//g')" ]; then echo "Hostname must only contain letters (a-z), digits (0-9) or -" return 1 fi # must not start with - case "$1" in -*) echo "Hostname must not start with a '-'"; return 1;; esac return 0 } # return last ipv4 address and mask # # $1 = interface # last_ipv4_addr_mask() { local _iface=$1 ip addr show dev $_iface | sort | awk '$1 == "inet" {print $2}' | tail -n1 } valid_ip_and_prefix() { [ "$1" ] || return 0 ipcalc -s -m $1 >/dev/null 2>&1 && ! ipcalc -s -m $1/0 >/dev/null 2>&1 } # ask for hostname # # $1 = default # # retrusn hostname in global var $resp # ask_hostname() { while true; do ask "Hostname for new vserver:" $1 if [ -d /etc/vservers/$resp ]; then echo "/etc/vservers/$resp already exist" continue fi if [ -d /vservers/$resp ]; then echo "/vservers/$resp already exist" continue fi valid_hostname $resp && break done } available_ifaces() { local iflist= ifpath= iface= i= sorted_ifindexes=$( for i in /sys/class/net/*/ifindex; do [ -e "$i" ] || continue echo -e "$(cat $i)\t$i"; done | sort -n | awk '{print $2}') for i in $sorted_ifindexes; do ifpath=${i%/*} iface=${ifpath##*/} # skip interfaces that are part of a bond or bridge if [ -d "$ifpath"/master/bonding ] || [ -d "$ifpath"/brport ]; then continue fi iflist="${iflist}${iflist:+ }$iface" done echo $iflist } ask_ifaceopts() { # get ip address(es) resp= local ifaceopts= _def= _iface= local ifaces=$(available_ifaces) local last_iface=$(echo $ifaces | sed 's/.* //') while [ "$resp" != "done" ]; do if [ -z "$ifaces" ] || [ "$ifaces" = "lo " ] || [ -n "$ifaceopts" ]; then _def="done" else _def=$(echo $ifaces | sed 's/.* //') fi ask_which "network interface" "to use for $_hostname" \ "$ifaces" $_def [ "$resp" = "done" ] && break _iface=$resp ifaces=$(rmel $_iface $ifaces) # suggested ip by last digit + 1 _last_ip_mask=$(last_ipv4_addr_mask $_iface) _last_ip=${_last_ip_mask%/*} _last_ip_digit=${_last_ip##*.} _ip=${_last_ip%.*}.$((($_last_ip_digit + 1) % 256)) _mask=${_last_ip_mask#*/} while true; do ask "Enter IP address/mask for $_iface:" $_ip/$_mask valid_ip_and_prefix "$resp" 2>&1 && break echo "$resp is not a valid IPv4 address/mask" done _ip_mask=$resp ifaceopts="$ifaceopts --interface $_iface:$_ip_mask" # suggest context from last digit in first ip address if [ -z "$_context" ]; then _ip=${_ip_mask%/*} _last_digit=${_ip##*.} _context=$((10000 + $_last_digit)) fi done resp="$ifaceopts" } ask_context() { # get context id while true; do ask "Enter context id for $_hostname:" $_context if echo "$resp" | egrep -q "[0-9]+"; then [ $resp -ge 0 ] && [ $resp -lt 65535 ] && break fi echo "Context id must be a 0-65534 number" done } ask_guest_arch() { # get guest arch while true; do ask "Enter guest machine architecture (i686 or x86_64):" $_arch case "$resp" in i[3-6]86|x86_64) break;; esac echo "Only i[3-6]86 and x86_64 is supported" done } ask_template() { local temp= apk_arch= arch_opt= # get template while true; do ask "Enter template file (or empty for generate a new):" \ $_template if [ -z "$resp" ] || [ -r "$resp" ]; then break fi echo "Can not read $resp" done if [ "$_guest_arch" != "$_arch" ]; then case "$_guest_arch" in i?86) apk_arch=x86;; *) apk_arch=$_guest_arch;; esac arch_opt="-a $apk_arch" fi temp=$resp if [ -z "$temp" ]; then temp=/vservers/template-$_guest_arch.tar.gz echo "Generating template..." if setup-vs-template $arch_opt -q -o $temp; then echo "ok" else echo "Failed to create template" exit 1 fi fi resp=$temp } usage() { echo "Usage: ${0##*/} [-h] [HOSTNAME...]" exit 1 } _arch="$(uname -m)" while getopts "h" opt; do case "$opt" in h) usage;; ?) usage;; esac done shift $(($OPTIND - 1)) if [ "$(whoami)" != "root" ]; then echo "Need to be root. Sorry." exit 1 fi while true; do ask_hostname $1 _hostname=$resp ask_ifaceopts _ifaceopts=$resp ask_context _context=$resp if [ "$_arch" = "x86_64" ]; then ask_guest_arch _guest_arch=$resp fi ask_template _template=$resp vserver $_hostname build \ --hostname $_hostname \ $_ifaceopts \ --context $_context \ $arch_opt \ -m template -- -t "$_template" -d alpine \ && cp /etc/resolv.conf /vservers/$_hostname/etc/ \ && cp /etc/apk/repositories /vservers/$_hostname/etc/apk/ \ || exit 1 if [ "$_arch" != "$_guest_arch" ]; then echo "linux_32bit" >> /etc/vservers/$_hostname/personality echo "$_guest_arch" > /etc/vservers/$_hostname/uts/machine fi shift [ $# -le 0 ] && exit 0 done