diff options
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/byo | 4 | ||||
| -rwxr-xr-x | bin/gauth | 7 | ||||
| -rwxr-xr-x | bin/hdmi | 8 | ||||
| -rwxr-xr-x | bin/vm | 431 | ||||
| -rwxr-xr-x | bin/xt | 1 |
5 files changed, 212 insertions, 239 deletions
@@ -26,8 +26,8 @@ yoda_uuid='8c463221-6bb7-414e-9060-c9570bb3a6bb' dest=/mnt/backup/$(hostname) [ -b /dev/mapper/yoda ] && noclose=1 || cryptsetup open "$dev" yoda findmnt /dev/mapper/yoda /mnt >/dev/null && noumount=1 || mount /dev/mapper/yoda /mnt -time backup -v -d "$dest" -[ ! "$optC" ] || backup -v -d "$dest" clean +time backup -v "$dest" +# [ ! "$optC" ] || backup -v -d "$dest" clean [ ! "$optd" ] || time duperemove -drh --hashfile="$dest/.hashfile" "$dest" df -h / "$dest" ls -v "$dest" @@ -1,10 +1,9 @@ #!/bin/sh # Use backup from andOTP -#gpg -qd ~/otp_accounts.json.gpg.pgp | -#gpg -qd ~/.otp_accounts.json.gpg | -cat ~/otp_accounts.json | +# cat ~/.otp_accounts.json | +gpg -qd ~/.otp_accounts.json.gpg 2>/dev/null | jq -r '.[] | "\(.label) \(.secret)"' | while read -r l s; do echo "$l $(oathtool --totp -b "$s")" -done +done | column -t @@ -2,8 +2,8 @@ # Toggle auxiliary HDMI screen on/off when connected/disconnected. opt="--auto --left-of eDP1" -#opt="--auto --right-of eDP-1" -#opt="--auto --above eDP-1" +#opt="--auto --right-of eDP1" +#opt="--auto --above eDP1" # Also add the following rule to /etc/udev/rules.d/hdmi.rules # KERNEL=="card0", SUBSYSTEM=="drm", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/marc/.Xauthority", RUN+="/home/marc/bin/hdmi" @@ -11,5 +11,5 @@ opt="--auto --left-of eDP1" # wait for the screen to settle sleep 3 -xrandr | grep -q 'HDMI1 connected' || opt="--off" -xrandr --output HDMI1 $opt +xrandr | grep -q '^DP1 connected' || opt="--off" +xrandr --output DP1 $opt @@ -1,285 +1,258 @@ #!/bin/sh -# vm is a command line tool to manage and operate virtual machines. - -vm_version='vm-0.1' - +# Manage virtual machines +# +# Prereq: +# - curl +# - cdrtools (isoinfo) +# - expect +# - qemu +# - screen +# # TODO: -# - DONE: fetch, build and install vftool in .vm/vftool -# - DONE: setup a config file per VM -# - DONE: creation of hdd image -# - DONE: delete a VM -# - DONE: import iso, kernel, initrd from existing vm -# - when no vm is specified, apply command to last one -# - script install from CDROM iso: -# - patch alpine setup-disk -# - setup system, including static IP address -# - setup user account from host -# - setup ssh from host -# - time synchronization from host +# - extract bzImage and initrd.gz with isoinfo +# - ssh scripts to finish install (once ssh is ready) +# +# DONE: +# - setup vde and networking +# - setup ssh keys so it is possible to ssh in vm from host # - unset CDPATH export LC_ALL=C IFS=' ' -add() { - usage 'add [Options] name' 'Add a new VM' && return - while getopts :c:i:k:p:s: opt; do - case $opt in - [cikps]) eval "$opt=$OPTARG";; - *) Opth=1 add "$1"; exit ;; - esac - done - shift $((OPTIND - 1)) +version='vm-0.1' - [ -d "$dir/$1" ] && die "vm $1 already exists in $dir/$1" - init_alpine || die "could not init alpine iso" - [ "$1" = 'alpine-iso' ] && return - mkdir -p "$dir/$1" && cd "$dir/$1" || die "add $1 failed" - hdd=${p-$1.raw} - [ -f "$hdd" ] || dd if=/dev/zero of="$hdd" bs=1 count=0 seek="${s-32g}" 2>/dev/null - echo "hdd=\"$hdd\"" > config - [ "$c" ] && { - [ -d "../$c" ] || die "invalid directory: $dir/$c" - iso=$(getconf "$c" iso) - cp "../$c/vmlinux" "../$c/initrd" . - [ "$iso" ] && echo "iso=\"../$c/$iso\"" >> config - cat <<- EOT >> config - kernel=${k-vmlinux} - initrd=initrd - cpu=1 - ram=512 - arg="console=hvc0" - EOT - } -} +arch=$(uname -m) +sys=$(uname -s) +alpine_version='3.15.0' +pubkey=$HOME/.ssh/id_rsa.pub +dir="${VM_DIR:-$HOME/.vm}" console() { - usage 'console name' 'Attach a console to a VM' && return - [ "$1" ] || die 'console: name is missing' - cd "$dir/$1" || die "console $1 failed" - [ -f vftool.pid ] || die "vm $1 is not active" - screen -r "$1" + usage 'console name' 'Attach a console to a virtual machine' && return + [ "$1" ] || die "missing argument" + is_running "$1" && screen -r "vm!$1!" } -del() { - usage 'del name' 'Delete a VM' && return - case $1 in ''|*/*|.*) die "invalid VM name: $1" ;; esac - [ -d "$dir/$1" ] || die "$dir/$1 not found" - [ -f "$dir/$1/vftool.pid" ] && die "vm $1 is still active, stop it first" - rm -rf "${dir:?}/$1" +create() { + usage 'create [-s size] name' 'Create an alpinelinux disk image' && return + size=8g + while getopts :s: opt; do + case $opt in + s) size=$OPTARG ;; + *) Opth=2 create_alpine_image; return ;; + esac + done + shift $((OPTIND - 1)) + [ -d "$dir/$1" ] && die "create failed: $dir/$1 already exists" + mkdir "$dir/$1" + cd "$dir/$1" || die "create failed: invalid directory $dir/$1" + qemu-img create "$1.raw" "$size" || die "create failed" + mac=$(new_macaddr) + ip=$(new_ip) + hdd="$1.raw" + echo "hdd=$hdd +mac=$mac +ip=$ip" >> config + + # Before install do not use virtio, as devices may not recognized as bootable + # TODO: alternate way: extract kernel and initrd files and pass them directly to qemu + screen -S "vm!$1!" -d -m qemu-system-$arch -nographic \ + -cdrom ../alpine-iso/alpine-virt-$alpine_version-$arch.iso \ + -hdd "$hdd" -net nic,macaddr=$mac -net vde + setup_alpine "$1" + post_setup_alpine "$1" } die() { [ "$1" ] && echo "$0: $*" >&2; exit 1; } -edit() { - usage 'edit name' 'Edit a VM configuration' && return - [ -f "$dir/$1/config" ] || die "$dir/$1/config not found" - ${EDITOR-vi} "$dir/$1/config" -} - -exp() { - usage 'exp name' 'experiment on a VM' && return - cd "$dir/$1" || die "invalid VM: $1" - [ -f vftool.pid ] || die "vm $1 is not active" - - sleep 1 && screen -X stuff 'root -' - sleep 1 && screen -X stuff 'sed -i.bak "s/die..Bootloader/;; # die \"Bootloader/" /sbin/setup-disk -' - sleep 1 && screen -X stuff 'setup-alpine -e -' - sleep 1 && screen -X stuff 'none -' - sleep 1 && screen -X stuff "$1 -" - sleep 1 && screen -X stuff 'eth0 -' - sleep 1 && screen -X stuff '192.168.64.2 -' - sleep 1 && screen -X stuff '255.255.255.0 -' - sleep 1 && screen -X stuff '192.168.64.1 -' - sleep 1 && screen -X stuff 'n -' - sleep 3 && screen -X stuff ' -' - sleep 1 && screen -X stuff '192.168.64.1 -' - sleep 1 && screen -X stuff 'Europe/Paris -' - sleep 5 && screen -X stuff 'none -' - sleep 1 && screen -X stuff 'chrony -' - sleep 5 && screen -X stuff '1 -' - sleep 1 && screen -X stuff 'openssh -' - sleep 5 && screen -X stuff 'vda -' - sleep 3 && screen -X stuff 'sys -' - sleep 1 && screen -X stuff 'y -' - sleep 20 && screen -X stuff 'blkid /dev/vda3 -' -} - -finalize() { - tty=$(vftool_tty) - printf 'root\nuname -a\n' >> "$tty" -} - -getconf() { awk -F '=' -v k="$2" '$1 == k {print $2}' "$dir/$1/config"; } - help() { usage 'help' 'Print this help text' && return - printf '%s\n' "$vm_version\nManage virtual machines\nUsage: vm command [options] [args]" + printf "$version\n Manage virtual machines\n\nUsage: vm command [options] [args]\n" Opth=1; for c in $Cmdlist; do $c; done } -info() { - usage 'info name' 'Print informations on a VM' && return - echo 'not implemented yet' +init() { + mkdir -p "$dir" } -# CAUTION: be careful to preserve tabs in the following Makefile template string. - -alpine_makefile='# Generated by "vm". DO NOT EDIT. -# Check https://alpinelinux.org/downloads for possible upgrades -iso_url = https://dl-cdn.alpinelinux.org/alpine/v3.13/releases/aarch64/alpine-virt-3.13.3-aarch64.iso -iso = $(notdir $(iso_url)) -isoinfo ?= /opt/homebrew/bin/isoinfo - -all: initrd vmlinux config +init_alpine_iso() { + usage init_alpine_iso '' && return + mkdir -p "$dir/alpine-iso" + iso_url="https://dl-cdn.alpinelinux.org/alpine/v${alpine_version%.*}/releases/$arch" + iso="alpine-virt-$alpine_version-$arch.iso" + cd "$dir/alpine-iso" + [ -f "$iso" ] || curl -LO "$iso_url/$iso" || rm -f "$iso" + echo "iso=$iso +" > config + echo 10 > ../index +} -initrd: $(iso) $(isoinfo) - isoinfo -i $(iso) -J -x /boot/initramfs-virt > $@ +is_running() { screen -ls "vm!$1!" >/dev/null 2>&1; } -vmlinux: $(iso) $(isoinfo) - isoinfo -i $(iso) -J -x /boot/vmlinuz-virt | gunzip > $@ +ls() { + usage 'ls' 'list virtual machines' && return + init && cd "$dir" || die "could not change dir to $dir" + for i in */; do + i=${i%/} + [ "$i" = '*' ] && continue + is_running "$i" && state=active || state=stopped + printf "%-20s %s\n" "$i" "$state" + done +} -config: $(iso) - @echo "iso=$(iso)" >config - @echo "initrd=initrd" >>config - @echo "kernel=vmlinux" >>config - @echo "cpu=1" >> config - @echo "ram=512" >> config - @echo "arg=\"console=hvc0\"" >> config +new_ip() { + read index < $dir/index + index=$((index + 1)) + echo "$index" > $dir/index + echo "10.0.2.$index/24" +} -$(iso): - curl -LO $(iso_url) || { rm -f $@; false; } +new_macaddr() { printf 'de:ad:be:ef:%02x:%02x\n' $((RANDOM % 256)) $((RANDOM % 256)); } -$(isoinfo): - brew install cdrtools -' +pidof() { + usage 'pidof name' 'print the PID of a virtual machine' && return + p=$(screen -ls "vm!$1!" | awk 'NR==2 {print substr($1, 1, index($1, ".")-1)}') + [ "$p" ] && pgrep -P $p +} -init_alpine() { - mkdir -p "$dir/alpine-iso" && cd "$dir/alpine-iso" || die 'init alpine failed' - [ -f 'Makefile' ] || printf '%s' "$alpine_makefile" > Makefile - make -s all +post_setup_alpine() { + start "$1" + read proto key id < "$pubkey" + # echo ' + expect -c ' + set timeout -1 + spawn screen -x "vm!'$1'!" + expect { + " login: " { send "root\r"; exp_continue } + "Password: " { send "root\r"; exp_continue } + ":~# " + } + send "mkdir -pm 0700 .ssh\r" + expect ":~# " + send "echo '$proto' '$key' '$id' >.ssh/authorized_keys\r" + expect ":~# " + send "exit\r" + ' } -init_vftool() { - [ -x "$dir/vftool" ] && return - cd '/tmp' && - git clone --depth=1 'https://github.com/evansm7/vftool' && - cd 'vftool' && - make && - cp 'build/vftool' "$dir/vftool" - rm -rf '/tmp/vftool' +# setup_alpine automates alpine installation from iso to image. +# When done, base image system is ready, with storage and network up. +# No user nor ssh access configured yet. +# TODO: custom network (in case of no dhcp). +setup_alpine() { + usage 'setup_alpine' && return + expect -c ' + set timeout -1 + spawn screen -x "vm!'$1'!" + expect { + "localhost login: " { send "root\r"; exp_continue } + "localhost:~# " { send "SWAP_SIZE=0 setup-alpine\r"; exp_continue } + "Select keyboard layout: " { send "\r"; exp_continue } + "Enter system hostname" { send "'$1'\r"; exp_continue } + "Which one do you want to initialize?" { send "\r"; exp_continue } + "Ip address for eth0?" { send "'$ip'\r"; exp_continue } + "Gateway?" { send "10.0.2.2\r"; exp_continue } + "manual network configuration?" { send "\r"; exp_continue } + "DNS domain name?" { send "lan\r"; exp_continue } + "DNS nameserver(s)?" { send "10.0.2.2 1.1.1.1\r"; exp_continue } + "New password:" { send "root\r"; exp_continue } + "Retype password:" { send "root\r"; exp_continue } + "Which timezone are you in?" { send "\r"; exp_continue } + "HTTP/FTP proxy URL?" { send "\r"; exp_continue } + "Enter mirror number " { send "\r"; exp_continue } + "Which SSH server?" { send "\r"; exp_continue } + "Which disk(s) would you like to use?" { send "sda\r"; exp_continue } + "How would you like to use it?" { send "sys\r"; exp_continue } + "Erase the above disk(s) and continue?" { send "y\r"; exp_continue } + "Installation is complete" { send "poweroff\r" } + } + interact + ' } -log() { - usage 'log name' 'print logs of a VM' && return - [ "$1" ] || die "log failed: name missing" - cd "$dir/$1" || die "log $1 failed" - cat vftool.log.old vftool.log 2>/dev/null +start_vde() { + usage start_vde && return + sudo sh <<- EOT + vde_switch -tap tap0 -sock /tmp/vde.ctl -daemon -mod 666 + sleep 1 + ip address add 10.0.2.2/24 dev tap0 + ip link set tap0 up + echo 1 > /proc/sys/net/ipv4/ip_forward + iptables -t nat -A POSTROUTING -s 10.0.2.0/24 -o eth0 -j MASQUERADE + iptables -t nat -A POSTROUTING -s 10.0.2.0/24 -o wlan0 -j MASQUERADE + EOT + #slirpvde --dhcp --daemon } -ls() { - usage 'ls' 'list VMs' && return - [ -d "$dir" ] && cd "$dir" || return - for i in */; do - i=${i%/} - [ "$i" = '*' ] && continue - [ -f "$i/vftool.pid" ] && state=active || state=stopped - printf "%-20s %s\n" "$i" "$state" - done +stop_vde() { + # killall slirpvde + sudo sh <<- EOT + iptables -t nat -D POSTROUTING -s 10.0.2.0/24 -o eth0 -j MASQUERADE + iptables -t nat -D POSTROUTING -s 10.0.2.0/24 -o wlan0 -j MASQUERADE + ip link set tap0 down + killall vde_switch + EOT } start() { - usage 'start [-afs] name' 'Start a VM' && return - while getopts :afs opt; do + usage 'start [-acd] name' 'start a virtual machine' && return + while getopts :acd opt; do case $opt in - (a) a=1 ;; - (f) f=1 ;; - (s) s=1 ;; - (*) Opth=1 start "$1"; exit;; + a) opta=1 ;; + c) boot=c ;; + d) boot=d ;; esac done - shift $((OPTIND - 1)) - - [ "$1" ] || die "start failed: name missing" - init_vftool - cd "$dir/$1" || die "start $1 failed" - [ -f vftool.pid ] && die "Error: process $(cat vftool.pid) is active or $PWD/vftool.pid should be removed" - start_vm & sleep 2 - ! [ "$f" ] || finalize - ! [ "$a" ] || vm console "$1" - ! [ "$s" ] || exec ssh "$1" + shift $((OPTIND -1)) + [ "$1" ] || die 'start failed: name missing' + cd "$dir/$1" || die "start failed: invalid directory $dir/$1" + is_running "$1" || start_qemu "$1" + [ "$opta" ] && console "$1" } -start_vm() ( - [ -f vftool.log ] && cat vftool.log >> vftool.log.old - exec 1>vftool.log 2>&1 - . config || die "vm: could not source $PWD/config" - trap 'rm -f vftool.pid' EXIT - - "$dir/vftool" \ - ${kernel+-k "$kernel"} \ - ${initrd+-i "$initrd"} \ - ${hda+-d "$hda"} \ - ${hdb+-d "$hdb"} \ - ${iso+-c "$iso"} \ - ${cpu+-p "$cpu"} \ - ${ram+-m "$ram"} \ - -a "${arg-console=hvc0}" \ - >>vftool.log 2>&1 & sleep 1 - - echo "$!" >vftool.pid - screen -S "${PWD##*/}" -d -m "$(vftool_tty)" - wait +start_qemu() ( + opt='-nographic -cpu max' + [ "$sys" = Linux ] && opt="$opt -enable-kvm" + . ./config || die "could not source $PWD/config" + exec 1>>qemu.log 2>&1 + date +%F_%T + set -x + screen -S "vm!$1!" -d -m qemu-system-$arch $opt \ + ${smp+-smp $smp} \ + ${ram+-m $ram} \ + ${mac+-net nic,macaddr=$mac,model=virtio-net-pci} -net vde \ + ${hdd+-drive file="$hdd",if=virtio,media=disk,format=raw} \ + ${iso+-drive file="$iso",if=virtio,media=cdrom,format=raw} \ + ${boot+-boot $boot} ) stop() { - usage 'stop name' 'Stop a VM' && return - [ "$1" ] || die 'stop: name missing' - cd "$dir/$1" || die "stop $1 failed" - [ -f vftool.pid ] || die "stop: vm $1 is not active" - kill "$(cat vftool.pid)" || rm -f vftool.pid + usage 'stop name' 'stop a virtual machine' && return + is_running "$1" && kill "$(pidof "$1")" } -usage() { [ "$Opth" ] && printf " %-34s %s\n" "$1" "$2"; } +usage() { + case $Opth in + 1) printf " %-34s %s\n" "$1" "$2" ;; + 2) printf "$0 $1\n\t$2\n" ;; + *) return 1 ;; + esac +} version() { usage 'version' 'Print version' && return - echo "$vm_version" + echo "$version" } -vftool_tty() { grep -om 1 '\/dev\/tty.*' 'vftool.log'; } - -# Main starts here. -dir="$HOME/.vm" -Cmdlist='add console del edit exp info help ls log start stop version' +Cmdlist='console create help init_alpine_iso ls pidof setup_alpine start start_vde stop version' [ "$1" ] && C=$1 && shift 1 || { help; exit 1; } -for c in $Cmdlist; do - case $c in - ("$C") cmd=$c; break ;; - ("$C"*) [ "$cmd" ] && die "ambiguous command $C" || cmd=$c ;; - esac -done +#for c in $Cmdlist; do +# case $c in +# ("$C") cmd=$c; break ;; +# ("$C"*) [ "$cmd" ] && die "ambiguous command $C" || cmd=$c ;; +# esac +#done +cmd=$C [ "$cmd" ] || { help; exit 1; } && $cmd "$@" @@ -2,4 +2,5 @@ #exec xterm -sl 500 -j -cr red "$@" & exec xterm "$@" & #cmd="urxvtc ${@:--T $HOSTNAME}" +#cmd="urxvtc $@" #eval "$cmd" || { [ $? = 2 ] && urxvtd -q -o -f && eval "$cmd"; } |
