diff options
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/backup | 64 | ||||
| -rwxr-xr-x | bin/backup-clean | 132 | ||||
| -rwxr-xr-x | bin/byo | 13 | ||||
| -rwxr-xr-x | bin/dv | 653 | ||||
| -rwxr-xr-x | bin/fv | 6 | ||||
| -rwxr-xr-x | bin/gauth | 5 | ||||
| -rwxr-xr-x | bin/gemini | 132 | ||||
| -rwxr-xr-x | bin/hdmi | 6 | ||||
| -rwxr-xr-x | bin/icat | 2 | ||||
| -rwxr-xr-x | bin/idot | 4 | ||||
| -rwxr-xr-x | bin/lt | 1 | ||||
| -rwxr-xr-x | bin/md | 136 | ||||
| -rwxr-xr-x | bin/md2html | 193 | ||||
| -rwxr-xr-x | bin/migrate_vimki | 58 | ||||
| -rwxr-xr-x | bin/open | 11 | ||||
| -rwxr-xr-x | bin/p4a | 3 | ||||
| -rwxr-xr-x | bin/rdate | 11 | ||||
| -rwxr-xr-x | bin/start_godoc | 5 | ||||
| -rwxr-xr-x | bin/status2 | 27 | ||||
| -rwxr-xr-x | bin/update_hosts | 28 | ||||
| -rwxr-xr-x | bin/update_kernel_host | 13 | ||||
| -rwxr-xr-x | bin/vcat | 8 | ||||
| -rwxr-xr-x | bin/vimki | 56 | ||||
| -rwxr-xr-x | bin/vm | 286 | ||||
| -rwxr-xr-x | bin/w3 | 6 | ||||
| -rwxr-xr-x | bin/wag | 335 | ||||
| -rwxr-xr-x | bin/wol | 6 | ||||
| -rwxr-xr-x | bin/xt | 4 | ||||
| -rwxr-xr-x | bin/yoda | 4 |
29 files changed, 1483 insertions, 725 deletions
@@ -1,36 +1,54 @@ #!/bin/sh -# Incremental backup using rsync(1). -[ "$USER" = root ] || exec sudo "$0" "$@" +# A backup a day keeps the doctor away. -backup() { - date=$(date +%Y%m%d_%H%M%S) - last=$(rsync --list-only "$dest/" 2>/dev/null | cut -b 47- | tail -1) +usage() { + echo "Usage: $0 [-nv] [[[user@]host]:dir] - case $last in - ([12]*) opt_link=--link-dest=../$last;; - (*) opt_link=;; - esac +Incremental backup using rsync(1). If run as root, a full system +backup is performed. Otherwise, the user's home directory is backed. - rsync -HSxa$optv --exclude-from=$ignore $opt_link / /boot "$dest/$date" -} +Options: +-n dry-run +-v verbose -dest=/.history -ignore=/etc/backup/ignore +Files: +- $HOME/.backupignore exclude files from user backup (non root) +- /etc/backupignore exclude files from system backup (root) -while getopts :d:i:nv opt; do +Environment: +- BACKUP backup directory" +} + +while getopts :nv opt; do case $opt in - (d) dest="$OPTARG" ;; - (i) ignore="$OPTARG" ;; (n|v) optv="$opt$optv" ;; - (*) echo "Usage: $0 [-nv] [-d [host:]dir] [clean|diff]"; exit 1 ;; + (*) usage; exit 1 ;; esac done shift $((OPTIND - 1)) -[ "$1" ] && cmd=$1 && shift || cmd="" -case $cmd in -(""|save) backup ;; -(clean) exec backup-clean ${optv+-$optv} "$dest";; -(diff) exec diffdir "$@";; -esac +BACKUP=${1:-$BACKUP} +[ "$BACKUP" ] || { usage; exit 1; } + +[ "$USER" = root ] && + ignore=/etc/backupignore volumes='/ /boot' || + ignore="$HOME/.backupignore" volumes="$HOME" +[ -f "$ignore" ] && ignore="--exclude-from=$ignore" || ignore= + +now=$(date +%F) +opt_link='' +past=$(rsync --list-only "$BACKUP/" 2>/dev/null | awk '{print $NF}' | sort -r) +for f in $past +do + case $f in + ([0-9][0-9][0-9][0-9]-*) ;; + (*) continue ;; + esac + [ "$f" = "$now" ] && continue + opt_link="--link-dest=../$f" + break +done + +echo "# Backup $volumes to $BACKUP/$now" +exec rsync -HSXxa$optv $ignore $opt_link $volumes "$BACKUP/$now" diff --git a/bin/backup-clean b/bin/backup-clean index 30d25db..34e6d97 100755 --- a/bin/backup-clean +++ b/bin/backup-clean @@ -1,89 +1,67 @@ #!/bin/sh -# backup garbage collector -[ "$USER" = root ] || exec sudo "$0" "$@" -# Durations, in number of seconds. -hd=3600 # hour duration: 60s * 60 -h12=43200 # 12h: hd * 12 -dd=86400 # day duration: hd * 24 -wd=604800 # week duration: dd * 7 -md=2592000 # month duration: dd * 30 -yd=31557600 # year duration: dd * 365.25 +usage() { + echo "Usage: $0 [-nv] [[[user@]host:]dir] -now=$(date +'%Y%m%d_%H%M%S') +$0 removes old backups and keeps: +- backups for the current day +- 1 backup per past day for the current month +- 1 backup per past month for the current year +- 1 backup per past year -# The following works only for busybox date, not coreutils -# Kept for reference only. -#date2ts() { d=${1%_*} t=${1#*_}; date -d "$d${t%??}" +%s; } +Options: +-n dry run +-v verbose -# Convert date to timestamp, using date from GNU coreutils, -# works also with busybox date. -date2ts() { - t=$1; r=${t#????}; Y=${t%$r} # Year (with century) - t=$r; r=${t#??}; m=${t%$r} # Month - t=$r; r=${t#??}; d=${t%$r} # Day - t=${r#_}; r=${t#??}; H=${t%$r} # Hour - t=$r; r=${t#??}; M=${t%$r} # Minute - S=${t#??} # Second - date -d "$Y-$m-$d $H:$M:$S" +%s - #date -jf "%Y-%m-%d %H:%M:%S" "$Y-$m-$d $H:$M:$S" +%s # BSD, MacOS (not tested) +Environment variables: +- BACKUP - backup directory" } -ts2date() { date -d "@$1" +'%Y%m%d_%H%M%S'; } - -tsn=$(date2ts "$now") - -# Minimal retention delay in seconds, according to backup age, -# implemented using POSIX shell arithmetic. -retention_delay() { - d=$((tsn - $1)) - if [ $((d < h12)) = 1 ]; then - r=0 # keep all backups in the last 12 hours - elif [ $((d < dd)) = 1 ]; then - r=$hd # keep 1 backup per hour in the last day - elif [ $((d < wd)) = 1 ]; then - r=$dd # keep 1 backup per day in the last week - elif [ $((d < md)) = 1 ]; then - r=$wd # keep 1 backup per week in the last month - elif [ $((d < yd)) = 1 ]; then - r=$md # keep 1 backup per month in the last year - else - r=$yd # keep 1 backup per year in the previous years - fi - echo $r -} - -dest=/.history -while getopts :d:nv opt; do +while getopts :nv opt; do case $opt in - (d) tsn=$(date2ts "$OPTARG") ;; - (n) optn=1 ;; - (v) optv=1 ;; - (*) echo "Usage: $0 [-nv] [dir]"; exit 1 ;; + (n) optn=echo ;; + (v) optv=-t ;; + (*) usage; exit 1 ;; esac done -shift $((OPTIND - 1)) -[ "$1" ] && dest=$1 +shift $((OPTIND -1)) + +BACKUP=${1:-$BACKUP} +[ "$BACKUP" ] || { usage; exit 1; } -# Sorted list of backups, most recent first. -lbu=$(ls -rv "$dest") -lasy=${lbu##* +host=${BACKUP%:*} dir=${BACKUP#*:} +ls='ls -r' rm="xargs -r $optv $optn rm -rf" +[ "$host" = "$dir" ] || ls="ssh $host $ls" rm="ssh $host $rm" + +echo "# Cleaning backups on $BACKUP" + +$ls "$dir" | +awk -v now=$(date +%Y-%m-%d) -v dir="$dir" ' +BEGIN { + yn = substr(now, 1, 4) # Year now + mn = substr(now, 6, 2) # Month now + dn = substr(now, 9, 2) # Day now } -for d in $lbu; do - tsc=$(date2ts "$d") - if ! [ "$tsp" ]; then - [ "$optv" ] && echo "keep $dest/$d" - tsp=$tsc - continue - fi - mrd=$(retention_delay "$tsp") - dp=$((tsp - tsc)) - #if [ $((dp < mrd)) = 1 ]; then - if [ "$d" != "last" -a $((dp < mrd)) = 1 ]; then - [ "$optv" ] && echo "delete $dest/$d" - [ "$optn" ] || rm -rf "${dest:?}/$d" - else - [ "$optv" ] && echo "keep $dest/$d" - tsp=$tsc - fi -done +{ + yb = substr($0, 1, 4) # Year backup + mb = substr($0, 6, 2) # Month backup + db = substr($0, 9, 2) # Day backup + $0 = dir "/" $0 + + dy = yn - yb + dm = dy * 12 + mn - mb + # if (yb != yn) { + if (dm > 12) { + if (yb in yearly) print; else yearly[yb] = 1 + next + } + # if (mb != mn) { + dd = dm * 30 + dn - db + if (dd > 30) { + if (mb in monthly) print; else monthly[mb] = 1 + next + } + if (db != dn) { + if (db in dayly) print; else dayly[db] = 1 + } +}' | $rm @@ -2,23 +2,26 @@ # Backup to yoda [ "$USER" = root ] || exec sudo "$0" "$@" -usage='Usage: byo [-cdk] [/dev/sda2] +usage='Usage: byo [-cdkx] [/dev/sda2] Backup local disk to yoda external disk. Options: -c close and umount yoda -d dedupe yoda -k do not umount and close yoda + -x enable debug traces ' -while getopts :Ccdk opt; do +while getopts :Ccdkx opt; do case $opt in - (C|c|d|k) eval "opt$opt=$opt" ;; + (C|c|d|k|x) eval "opt$opt=$opt" ;; (*) printf %s "$usage"; exit 1 ;; esac done shift $((OPTIND - 1)) +[ "$optx" ] && set -x + dev=$1 yoda_uuid='8c463221-6bb7-414e-9060-c9570bb3a6bb' [ "$dev" ] || dev=$(blkid --uuid "$yoda_uuid") @@ -26,8 +29,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,556 +1,213 @@ #!/bin/sh -# Local dv (aka sim) +# - Usage: dv command [options] [args] +# - dv is a dumb version management tool. +# - Options: +# - -a num set ancestor level (default 1) +# - -n dry-run mode, no changes are made +# - -v verbose mode +# - Commands: -dv_version='dv-0.23 Copyright 2013-2014 Marc Vertes, Philippe Bergheaud' unset CDPATH export LC_ALL=C IFS=' ' - -anc() { - usage 'anc [Version]' 'print ancestor' && return - getdv .dv || die no item - case $1 in - (""|.|$PWD) - [ "$Opt_N" ] && Opt_N=$(($Opt_N - 1)) - [ "$Opt_N" ] && info anc $R2 $Opt_N || echo $R2 +version=dv-0.2 +dvlib=${DVLIB:-$HOME/dvlib} +script="$(cd "$(dirname "$0")" && pwd)/${0##*/}" +aopt=1 + +case $(uname -o) in + (Android|Linux) + diffopt='--color=always' + lsopt='-v --color' ;; - (*) info anc $1 ${Opt_N:-1};; - esac -} + (Darwin) + diffopt='--color=always' + lsopt='-G' + ;; + (*BSD) + ;; +esac -cache() { - isremote "$1" || return - o=$1 _d=$cacheprefix/$1; pd=${_d%/*}; ppd=${pd%/*} t=$pd/.${_d##*/} - [ -d "$ppd" ] || mkdir -p "$ppd" - [ -L "$ppd/.dv." ] || ln -s . "$ppd/.dv." - [ -f "$_d/.dv" ] && return - [ -d "$pd" ] || mkdir -p "$pd" - set -- $pd/*; shift $(($# - 1)) - [ -d "$1" ] && ropt="--link-dest=../${1##*/}" || ropt= - rsync -aDS $ropt "$o/" "$t" && mv "$t" "$_d" +# anc [-a num] [dir] - prints the current ancestor of dir or current. +anc() { + set -- "$(dvdir "$1")" + for _ in $(seq "$aopt"); do + set -- "$1/.dv/anc" + done + [ -L "$1" ] && readlink -f "$1" } -client() { - usage 'client [Version]' 'print clients' && return - getdv .dv || die no item +# clone [-nv] v1 [dir] - clones an iterm version into dir (default: item). +clone() { case $1 in - (""|.|$PWD) - while getpdir .dv .. - do - cd "$R1" - getdv ".dv" - [ "$OptM" ] && echo "$R2 -> $PWD" || echo "$R2" - ((Opt_N = Opt_N-1)) || break - done ;; - (*) info cli $1 ${Opt_N:-9999} | sort -u ;; + ('') die 'missing argument' ;; + (*[/.-]*) set -- "$(path "$1")" "$2" ;; + (*) set -- "$(head "$1")" "$2" ;; esac + [ "$2" ] || { set -- "$1" "${1##*/}"; set -- "$1" "${2%%-*}"; } + [ -d "$1" ] || die "not found: $1" + rsync ${vopt:+"$vopt"} ${nopt:+"$nopt"} -a "$1/" "$2/" + rm -f "$2/.dv/anc" && ln -s "$1" "$2/.dv/anc" } -clone() { - usage 'clone [-A Author] [-m Msg] Version [Dir]' \ - 'Copy version to dir' && return - [ "$1" ] || die missing argument - [ "$3" ] && die too many arguments - getpdir .dv . && root=$R1 && getdv $root/.dv && lib=$R1 item=${R2%/*} - new_version=${1##*/} - if [ "$new_version" = "$1" ] - then - getdv .dv && lib=$R1 new_item=${R2%/*} || die no item - else - v=${1%/$new_version} - new_item=${v##*/} - [ "$new_item" = "$v" ] || new_lib=${v%/$new_item} - fi - [ "$PWD" = "$root" ] && [ "$item" != "$new_item" ] && - die "already a clone of $item" - lib=${lib:-$new_lib} - [ "$lib" ] || die no lib - [ "$new_lib" -a "$new_lib" != "$lib" ] && die "already in lib: $lib" - if [ "$new_version" = 0 ] # Init item in lib - then - initlib "$lib" - getdvinfo "$lib" || die "could not lock $lib" - mkdir -p /tmp/dv.$$/$new_item/0 - dv_write $new_item/0 $new_item/0 >/tmp/dv.$$/$new_item/0/.dv - { cat /tmp/dv.$$/$new_item/0/.dv; echo; } >>/tmp/dv.$$/.dvinfo.0 - ln -f /tmp/dv.$$/.dvinfo.0 /tmp/dv.$$/.dvinfo.1 - rsync -a /tmp/dv.$$/ "$lib" - fi - if [ ! "$2" ] # Use the item name as default directory (a la git) - then - set -- "$1" "${1%/*}" - set -- "$1" "${2##*/}" - echo cloning into "$2" - fi - [ ! -d "$2" ] && mkdir "$2" - cd "$2" || die cannot chdir to "$2" - # Rename existing supplier directories to allow supplier symlinks - find . -name .dv | while read l - do - l=${l%/.dv} && [ "$l" = "." ] && continue - mv "$l" "$l.dvold" - done - # Sync from lib, starting from most client item, up to last supplier - d=. s=$new_item/$new_version - while [ "$d" ] - do - [ -L "$d" ] && { - rm -f "$d" - [ -d "$d.dvold" ] && mv "$d.dvold" "$d" - } - cache "$lib/$s" && pref=$cacheprefix/ || pref= - rsync -aDS "$pref$lib/$s/" "$d" - dv_header "$d" Anc "$s" - dv_header "$d" Lib "$lib" - # Find next symlink pointing to a supplier - d= l= - eval "$(find . -type l | while read -r l - do - f=$(readlink "$l") - case $f in - (*/.dv./*) echo "d=\"$l\" s=\"${f#*/.dv./}\"" - break;; - esac - done)" +# desc - prints the descendants of a version +desc() { + set -- "$(path "$1")" + set -- "$1" "${1%/*}" + set -- "$1" "${2##*/}" + for a in "$dvlib/$2"/*/.dv/anc*; do + [ "$a" -ef "$1" ] && echo "${a%/*/*}" done } -desc() { - usage 'desc [Version]' 'print descendants' && return - getdv .dv || die no item - case $1 in - (""|.|$PWD) ;; - (*) info desc $1 ;; - esac +die() { + echo "$@" >&2 + exit 1 } -die() { echo "$0: fatal: $@" >&2; exit 1; } - +# diff [-a num] [[v1] v2] - prints differences (default: current and ancestor). diff() { - usage 'diff [-x pat] [-F File] [V1 [V2]]' 'Print differences' && return - getpdir .dv && getdv "$R1/.dv" || die not in a clone - lib=$R1 item=${R2%/*} old=${R2#*/} - [ "$lib" = "" ] && [ -L "../../.dv." ] && lib=${PWD%/*/*} - if [ "$2" ] - then - case $2 in - (.) new=. new_is_clone=1 ;; - (*) new=$lib/$item/$2 ;; - esac - cache "$new" && np=$cacheprefix/ || np= - old=$lib/$item/$1 - elif [ "$1" ] - then - case $1 in - (.) new=. new_is_clone=1 ;; - (*) new=$lib/$item/$1 ;; - esac - cache "$new" && np=$cacheprefix/ || np= - getdv "$np$new/.dv" && old=${R1:-$lib}/$R2 + if [ "$2" ]; then + set -- "$(path "$2")" "$(path "$1")" + elif [ "$1" ]; then + set -- "$(path "$1")" "$(anc "$(path "$1")")" else - new=$PWD new_is_clone=1 - cache "$new" && np=$cacheprefix/ || np= - getdv "$np$new/.dv" && old=${R1:-$lib}/$R2 + set -- "$(dvdir)" "$(anc)" fi - cache "$old" && op=$cacheprefix/ || op= - [ "$Opts" ] || printf "old %s\nnew %s\n" "$old" "$new" - # Itemized diff for all suppliers, deepest first - cd "$np$new" && for f in $(find . -type f -name .dv | sort -r) - do - d=${f%/.dv} - [ "$d" = . ] && prefix= || prefix=${d#./}/ - getdv "$f" && cache "$lib/$R2" - [ "$new_is_clone" ] && dest=$np$d || dest=$prefix$d - diffdir "$op$lib/$R2" "$dest" - Optx="$Optx --exclude=${d##*/}" - done -} - -# Usage: diffdir oldpath newpath -# Print differences between 2 directories -diffdir() { - [ -f "$2/.dvignore" ] && xf=--exclude-from=$2/.dvignore || xf= - rsync -aDSniv $xf --delete --exclude=".dv.*/" $Optx "$2/" "$1" | - awk -v OptF="${OptF#./}" -v prefix=$prefix ' - NF == 0 {exit} - NR < 2 || /\/*\.dv$/ || /\/$/ {next} - # Match an itemized status for all versions of rsync -i - $1 !~ /^[<>ch.*][fdLDS+][.+?cstpoguaxz]+$/ {next} - {key = $1; file = substr($0, length(key) + 2)} - OptF && OptF != file {next} - key == "*deleting" {print "deleted " prefix file; next} - substr(key, 3, 7) == "+++++++" {print "created " prefix file; next} - { # Avoid false positive if only mtime is changed. - of = "'$1'/" file; gsub("'\''", "'\'\\\\\'\''", of) - nf = "'$2'/" file; gsub("'\''", "'\'\\\\\'\''", nf) - if (substr(key, 2, 1) == "L") { # Symlink - src = target = file - sub(/.* -> /, "", target); - sub(/ -> .*/, "", src); - "readlink '$2'/" src | getline otarget - if (target != otarget) - print "changed " src - } else if (system("cmp -s '\''" of "'\'\ \''" nf "'\''")) - print "changed " file - }' - # Or: scan key for file/link size, checksum or permission change - # rsync -c is required - #key ~ /[cps]/ { print "changed " prefix file }' + [ "$2" ] && command diff $diffopt -U 3 -x .dv "$2" "$1" | less -F } -dv_write() { - cat <<- EOT - From $(username) - Date: $(date +"%F %T %z") - Version: $1 - Anc: $2 - EOT - - [ "$OptA" ] && echo "Author: $OptA" - printf "\n%s\n" "$Optm" +# dvdir prints the dir containing .dv. +dvdir() { + set -- "$(realpath "${1:-.}")" + while [ "$1" ]; do + [ -d "$1/.dv" ] && echo "$1" && return + set -- "${1%/*}" + done } -dv_header() { - awk -v val="$3" '/^'$2':/ {print "'$2': " val; done = 1; next} - NF == 0 && done == 0 {print "'$2': " val; done = 1} - {print}' $1/.dv >$1/.dv.$$ && mv $1/.dv.$$ $1/.dv +# head - prints the branch head versions. +head() { + for v in "$dvlib/${1:-$(name)}/"*; do + [ "$(desc "$v")" ] || echo "$v" + done } -# Usage: getdv path -# Return lib in R1, anc in R2, version in R3 -getdv() { - R1= R2= - while read -r line - do +# help - prints this help text. +help() { + while read -r line; do case $line in - ("Lib: "*) R1=${line#Lib: };; - ("Anc: "*) R2=${line#Anc: };; - ("Version: "*) R3=${line#Version: };; + (\#*\ -\ *) ;; + (*) continue ;; esac - done <$1 - [ "$R1" -o "$R2" ] + set -- "${line% - *}" "${line#* - }" + [ "$1" = '#' ] || printf " %-26s" "${1#\# }" + echo "$2" + done < "$script" } -# Usage: getdvinfo lib -# Get exclusive write access to a dvlib, for new version commit -getdvinfo() { - [ -d /tmp/dv.$$ ] || mkdir /tmp/dv.$$ - [ "$Optn" ] && { rsync "$1/.dvinfo.1" /tmp/dv.$$/.dvinfo.0; return; } - t=0 - for i in 1 2 3 4 5 - do - rsync --remove-source-files "$1/.dvinfo.0" /tmp/dv.$$/ && break - t=$(($t + $i)) && sleep $t - done - test -f /tmp/dv.$$/.dvinfo.0 +# incv increments version. +incv() { + set -- "$1" "${1##*[-.a-z_A-Z]}" + set -- "${1%"$2"}" "$2" + echo "$1$(($2 + 1))" } -putdvinfo() { - [ "$Optn" ] && return - ln -f /tmp/dv.$$/.dvinfo.0 /tmp/dv.$$/.dvinfo.1 - rsync -a /tmp/dv.$$/.dvinfo.[01] "$1/" - isremote "$1" && cp /tmp/dv.$$/.dvinfo.0 "$cacheprefix/$1/" +# init - creates an initial clone in the current directory. +init() { + [ "$(dvdir)" ] && die "already initialized: $(dvdir)" + mkdir .dv } -# Usage: getpdir file [path] -# Return absolute parent dir of path or PWD containing file -getpdir() { - R1="$([ -d "${2:-.}" ] && cd "${2:-.}" && pwd)" - while [ "$R1" ] - do - [ -f "$R1/$1" ] && return || R1=${R1%/*} - done - return 1 -} +# ls [item] - lists dvlib. +ls() ( + cd "$dvlib" && command ls $lsopt "$@" +) -# Usage: getprd file [path] -# Return relative path to parent directory containing file -getprd() { - R2="$(cd "${2:-.}" && pwd)"; R2=${R2%/*} R1=.. - while [ "$R2" ] - do - [ -f "$R2/$1" ] && return || R2=${R2%/*} R1=$R1/.. - done - return 1 +# name prints the item name. +name() { + set -- "$(dvdir)" + [ "$1" ] && echo "${1##*/}" || die "not a dv item, no .dv found" } -info() { - usage info && return - case $2 in - (*:*) lib=${2%/*/*} ;; - (*) getpdir .dv && getdv $R1/.dv && lib=$R1 || die no dvlib found - esac - case $2 in # $2 specifies: - (*/*/*) v=${2%/*/*}; v=${2#$v/} ;; # lib/item/version - (*/*) v=$2 ;; # item/version - (*) v=${R2%/*}/$2 ;; # version +# path prints the version path, if exist. +path() { + case $1 in + (*/*|.) dvdir "$1"; return ;; + ([0-9]*) set -- "$(name)-$1" ;; esac - isremote "$lib" && { - p=$cacheprefix - [ -d "$p/$lib" ] || mkdir -p "$p/$lib" - [ "$Optl" ] || rsync -a "$lib/.dvinfo.1" "$p/$lib/" - } || p= - info_query $1 $v $3 <$p/$lib/.dvinfo.1 -} - -info_query() -{ - awk -v arg0=$1 -v arg1=$2 -v arg2=$3 ' - /^From / { - if (v) msg[v] = var["Body"] - delete var - var["From"] = substr($0, 6) - header = 1 - next - } - header == 1 && NF == 0 { - v = var["Version"]; a = var["Anc"]; s = var["Sup"] - if (a != v) { - anc[v] = a; desc[a] = desc[a] ? desc[a] " " v : v - } - n = split(s, as) - for (i = 1; i <= n; i++) { - cli[as[i]] = cli[as[i]] ? cli[as[i]] " " v : v - } - sup[v] = s; msg[v] = var["Msg"]; date[v] = var["Date"] - if (var["Root"]) root[v] = var["Root"] - header = 0 - next - } - header == 1 { - i = index($0, ":") - var[substr($0, 1, i-1)] = substr($0, i+2) - next - } - header == 0 { - if ($0 ~ />+From /) sub(/>/, "") - var["Body"] = var["Body"] ? var["Body"] "\n" $0 : $0 - } - END { - if (v) msg[v] = var["Body"] - if (arg0 == "graph") anc_graph() - else if (arg0 == "anc") query_anc(arg1, arg2) - else if (arg0 == "sup") query_sup(arg1, arg2) - else if (arg0 == "cli") query_cli(arg1, arg2) - else if (arg0 == "log") print msg[arg1] - else if (arg0 == "from") print from[arg1] - else if (arg0 == "date") print date[arg1] - else if (arg0 == "desc") printl(desc[arg1]) - } - function anc_graph() { - print "digraph G {" - for (v in anc) print "\"" anc[v] "\" -> \"" v "\"" - print "}" - } - function query_anc(v, n) { while (n-- > 0) v = anc[v]; print v } - function query_cli(v, n) { - while (n-- > 0 && v != "") { - num = split(v, av); v = "" - for (i = 1; i <= num; i++) { - printl(cli[av[i]]) - v = v ? v " " cli[av[i]] : cli[av[i]] - } - } - } - function query_sup(v, n) { - while (n-- > 0 && v != "") { - num = split(v, av); v = "" - for (i = 1; i <= num; i++) { - printl(sup[av[i]]) - v = v ? v " " sup[av[i]] : sup[av[i]] - } - } - } - function printl(l, i, n) { - n = split(l, al) - for (i = 1; i <= n; i++) - if (root[al[i]]) - print al[i] " -> '"$PWD/"'" root[al[i]] - else - print al[i] - }' + set -- "${1%%-*}" "${1#*-}" + [ -d "$dvlib/$1/$1-$2" ] && echo "$dvlib/$1/$1-$2" } -initlib() { - rsync --list-only "$1/.dv." >/dev/null 2>&1 && return - mkdir -p /tmp/dv.$$ - ln -s . /tmp/dv.$$/.dv. - >/tmp/dv.$$/.dvinfo.0 - >/tmp/dv.$$/.dvinfo.1 - chmod g+w /tmp/dv.$$/.dvinfo.[01] - rsync -a /tmp/dv.$$/ "$1" -} +# save [-nv] [v1] - creates a new version from clone. +save() { + [ "$1" ] && case $1 in (*[0-9]) ;; (*) die "invalid version: $1" ;; esac + set -- "$(dvdir)" "$(aopt=1 anc)" "$(name)" "${1#[a-zA-Z_]*-}" + set -- "$1" "$2" "$3" "${4:-$(incv "${2##*/[a-zA-Z_]*-}")}" + echo "$dvlib/$3/$3-$4" + command diff -q -x .dv "$2" "$1" >/dev/null 2>&1 && die 'no changes, abort' + [ "$nopt" ] && return -isremote() { case $1 in (*:*) return;; esac; return 1; } + # Update ancestor in clone then copy in dvlib. + rm -f "$1/.dv/anc" && [ "$2" ] && ln -s "../../${2##*/}" "$1/.dv/anc" + rsync ${vopt:+"$vopt"} -a ${2:+--link-dest="$2"} --mkpath "$1/" "$dvlib/$3/$3-$4/" -help_all() { - printf "$dv_version\nUsage: dv command [options] [args]\n" - Opth=1; for c in $Cmdlist; do $c; done + # After save, clone ancestor points to new saved version. + rm -f "$1/.dv/anc" && ln -s "$dvlib/$3/$3-$4" "$1/.dv/anc" } -newversion() { - [ "$OptV" ] && R1=$OptV && return - case $OptB in - ('') R1=$1 ;; - (*[-/]*) die 'illegal character [-/] in branch name' ;; - (*[0-9.]) die 'illegal end character [0-9.] in branch name' ;; - (*) case $1 in (*-${OptB}[1-9]*) R1=$1;; (*) R1=$1-${OptB}0;; esac ;; - esac - R2=${R1##*[!0-9]} - optb=$Optb - [ "$optb" ] && R3=.1 optb=${optb%b} || R3= R1=${R1%$R2}$(($R2 + 1)) - while [ "${optb}" ]; do optb=${optb%b} R3=.0$R3; done - R1=$R1$R3 - while grep -q "^Version: $item/$R1\$" /tmp/dv.$$/.dvinfo.0 - do - R2=${R1##*[!0-9]} - [ "$R2" ] && R1=${R1%$R2}$(($R2 - 1)).1 || R1=${R1}1 - done +# status - prints dv informations. +status() { + echo "lib: $dvlib" + set -- "$(dvdir)" "$(anc)" + [ "$1" ] || die "not a dv item, no .dv found" + echo "root: $1" + [ "$2" ] || die "no ancestor found, please save it first" + echo "anc: $2" + command diff -q -x .dv "$2" "$1" } -patch() { - usage 'patch [-x pat] [-F File] [Dir|Version]' \ - 'Print patch diff from ancestor' && return - diff "$@" | awk -v wdflag="-v${OptD:+3}${OptN:+1}${OptO:+2}" ' - { key = $1; file = substr($0, length(key) + 2) } - key == "old" { old = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } - key == "new" { new = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } - { - of = old "/" file; gsub("'\''", "'\'\\\\\'\''", of) - nf = new "/" file; gsub("'\''", "'\'\\\\\'\''", nf) - system("diff -Naup '\''" of "'\'\ \''" nf "'\''; echo") - }' +# sync [url] - synchronize a remote dvlib. +sync() { + set -- "${1:-$(cat "$dvlib/.url" 2>/dev/null)}" + [ "$1" ] || die "missing url" + [ "$(cat "$dvlib/.url" 2>/dev/null)" = "$1" ] || echo "$1" > "$dvlib/.url" + rsync ${vopt:+"$vopt"} ${nopt:+"$nopt"} -aH --mkpath "$1/" "$dvlib/" + rsync ${vopt:+"$vopt"} ${nopt:+"$nopt"} -aH --mkpath "$dvlib/" "$1/" } -save() { - usage 'save [-bln] [-A Author] [-m msg] [-x pat] [-B Branch|-V Version] [Dir]'\ - 'Save dir into a new version' && return - [ "$2" ] && die 'too many arguments' - getpdir .dv "$1" || die "not in a clone: ${1:-$PWD}" && root=$R1 - cd "$root" - getdv .dv && lib=$R1 && getdvinfo "$lib" - # Process supplier subdirs from the deepest up to "." (most client) - for d in $(find . -type f -name .dv | sort -r) - do - d=${d%/.dv} - cd "$root/$d" - OptF= save1 "$d" && nanc=$R1 && echo $nanc - [ "$d" = . ] && continue # not a supplier, done - - # Replace supplier subdir by symlink in lib - getprd .dv && s=$R1/../.dv./$nanc - t=${d##*/}; t=.dv.$t - mv "$root/$d" "$root/${d%/*}/$t" - ln -s "$s" "$root/$d" - done - putdvinfo "$lib" - # Restore supplier subdirs and remove links to lib, deepest first - find . -type d -name ".dv.*" | sort -r | while read d - do - t=${d##*/.dv.}; t=${d%/*}/$t - rm -f "$t" && mv "$d" "$t" - done +# version - prints the current version of dv. +version() { + echo "$version" } -save1() { - getdv .dv && lib=$R1 anc=$R2 && item=${anc%/*} old=${anc#*/} - [ "$(diffdir "$lib/$anc" .)" ] || { R1=$anc; return; } - newversion $old && new=$item/$R1 - [ "$Optn" ] || dv_write "$new" "$anc" >.dv - # Compute list of direct suppliers, for caching in .dvinfo.0 - lsup=$(find . -type l | while read -r l - do - case $l in (*/.dv.*) continue ;; esac - f=$(readlink "$l") - case $f in (*/.dv./*) printf "%s " ${f#*/.dv./} ;; esac - done) - [ ! "$Optn" ] && [ "$lsup" ] && dv_header . Sup "$lsup" - { cat .dv; echo; } >>/tmp/dv.$$/.dvinfo.0 - [ -f ".dvignore" ] && xf=--exclude-from=.dvignore || xf= - rsync -aDS$Optn$Optv --link-dest=../$old $xf --exclude=".dv.*/" \ - ./ "$lib/$new" - R1=$new - [ "$Optn" ] && return - isremote "$lib" && rsync -aDS --link-dest=../$old --exclude=".dv.*/" \ - ./ "$cacheprefix/$lib/$new" - dv_header . Anc "$new" - dv_header . Lib "$lib" -} - -supplier() { - usage 'supplier [Version]' 'print suppliers' && return - getdv .dv || die no item +# Main program starts here. +[ "$DVDEBUG" ] && set -x || case $1 in - (""|.|$PWD) # create a temporary dvinfo file, query it - V=$R2; find . -name .dv | - while read dv - do - [ "$dv" = "./.dv" ] && continue - # get the supplier - getdv "$dv"; v=$R2; dv=${dv%/.dv} - # get the client - getpdir .dv "${dv%/*}"; getdv "$R1/.dv" - # declare the client as supplier - printf "From \nVersion: %s\nSup: %s\n" "$v" "$R2" - [ "$OptM" ] && echo "Root: ${dv#./}"; echo - done | info_query cli $V ${Opt_N:-9999} ;; - (*) info sup $1 ${Opt_N:-9999} ;; - esac | sort -u -} - -usage() { [ "$Opth" ] && printf " %-34s %s\n" "$1" "$2"; } - -username() { - case $(uname -s) in - (Darwin) id -P | awk -v FS=: '{print $8}' ;; - (*) awk -v FS=[:,] '/^'$USER':/ {print $5; exit}' /etc/passwd ;; + (anc|clone|desc|diff|head|help|init|ls|path|save|status|sync|version) ;; + (*) help; exit 1;; esac -} - -wdiff() { - usage 'wdiff [-DNO] [-x pat] [-F File] [Dir|Version]' \ - 'Print word diffs from ancestor' && return - diff "$@" | - awk -v wdflag="-v${OptD:+3}${OptN:+1}${OptO:+2}" ' - { key = $1; file = substr($0, length(key) + 2) } - key == "old" { old = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } - key == "new" { new = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } - { - of = old "/" file; gsub("'\''", "'\'\\\\\'\''", of) - nf = new "/" file; gsub("'\''", "'\'\\\\\'\''", nf) - system("wd " wdflag " '\''" of "'\'\ \''" nf "'\''; echo") - }' | less -FCimnqGrX -j5 -h0 +/'\[0' -} - -cacheprefix=$HOME/.cache/dv -Cmdlist='anc client clone desc diff info patch save supplier wdiff' -while getopts :nvV opt # Parse global options -do +cmd="$1" +shift +while getopts :a:nv opt; do case $opt in - (V) echo "$dv_version"; exit ;; - (*) help_all; exit ;; + (a) aopt="$OPTARG" ;; + (n) nopt=-n ;; + (v) vopt=-v ;; + (*) help; exit ;; esac done shift $((OPTIND - 1)) -[ $1 ] && C=$1 && shift 1 || { help_all; exit 1; } -for c in $Cmdlist -do - case $c in - ($C|_$C) cmd=$c; break;; - ($C*) [ $cmd ] && die ambiguous command $C || cmd=$c;; - esac -done -while getopts :0123456789A:bB:F:hl:m:MnNOsvV:x: opt # Parse command options -do - case $opt in - ([0-9]) Opt_N=$Opt_N$opt;; - ([bhlnMNOsv]) eval Opt$opt=\${Opt$opt}$opt ;; - ([ABFmV]) eval Opt$opt=\$OPTARG ;; - (x) Optx="$Optx --exclude=$OPTARG" ;; - (*) Opth=1; $cmd; exit 1;; - esac -done -shift $(($OPTIND - 1)) -trap "rm -rf /tmp/dv.$$" EXIT -[ "$cmd" ] || die "no command \"$C\"" && $cmd "$@" +$cmd "$@" + +# Todo: +# * a function to squash a branch in the trunk +# * merge another ancestor (use anc0, anc1, ...) +# * vendor management +# * show graphs (dot) +# * sync: dvlib sync / mirror: resolve conflict by branching +# * rename version. +# * .dvignore +# * make dvlib versions immutable. @@ -0,0 +1,6 @@ +#!/bin/sh +# quick file viewer +#exec fzf --multi --preview-window=right:66% --preview 'cat {1}' +exec fzf --ansi --multi --preview-window=right:75% \ + --bind=left:preview-page-up --bind=right:preview-page-down \ + --preview 'bat --color always {1}' @@ -1,8 +1,9 @@ #!/bin/sh # Use backup from andOTP -gpg -qd ~/.otp_accounts.json.gpg | +#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 diff --git a/bin/gemini b/bin/gemini new file mode 100755 index 0000000..1bc3d57 --- /dev/null +++ b/bin/gemini @@ -0,0 +1,132 @@ +#!/usr/bin/env yaegi +package main + +import ( + "bufio" + "crypto/tls" + "fmt" + "io/ioutil" + "net/url" + "os" + "strconv" + "strings" +) + +func main() { + stdinReader := bufio.NewReader(os.Stdin) + var u string // URL + links := make([]string, 0, 100) + history := make([]string, 0, 100) + for { + fmt.Print("> ") + cmd, _ := stdinReader.ReadString('\n') + cmd = strings.TrimSpace(cmd) + // Command dispatch + switch strings.ToLower(cmd) { + case "": // Nothing + continue + case "q": // Quit + fmt.Println("Bye!") + os.Exit(0) + case "b": // Back + if len(history) < 2 { + fmt.Println("No history yet!") + continue + } + u = history[len(history)-2] + history = history[0 : len(history)-2] + default: + index, err := strconv.Atoi(cmd) + if err != nil { + // Treat this as a URL + u = cmd + if !strings.HasPrefix(u, "gemini://") { + u = "gemini://" + u + } + } else { + // Treat this as a menu lookup + u = links[index-1] + } + } + // Parse URL + parsed, err := url.Parse(u) + if err != nil { + fmt.Println("Error parsing URL!") + continue + } + // Connect to server + conn, err := tls.Dial("tcp", parsed.Host+":1965", &tls.Config{InsecureSkipVerify: true}) + if err != nil { + fmt.Println("Failed to connect: " + err.Error()) + continue + } + defer conn.Close() + // Send request + conn.Write([]byte(u + "\r\n")) + // Receive and parse response header + reader := bufio.NewReader(conn) + responseHeader, err := reader.ReadString('\n') + parts := strings.Fields(responseHeader) + status, err := strconv.Atoi(parts[0][0:1]) + meta := parts[1] + // Switch on status code + switch status { + case 1, 3, 6: + // No input, redirects or client certs + fmt.Println("Unsupported feature!") + case 2: + // Successful transaction + // text/* content only + if !strings.HasPrefix(meta, "text/") { + fmt.Println("Unsupported type " + meta) + continue + } + // Read everything + bodyBytes, err := ioutil.ReadAll(reader) + if err != nil { + fmt.Println("Error reading body") + continue + } + body := string(bodyBytes) + if meta == "text/gemini" { + // Handle Gemini map + links = make([]string, 0, 100) + preformatted := false + for _, line := range strings.Split(body, "\n") { + if strings.HasPrefix(line, "```") { + preformatted = !preformatted + } else if preformatted { + fmt.Println(line) + } else if strings.HasPrefix(line, "=>") { + line = line[2:] + bits := strings.Fields(line) + parsedLink, err := url.Parse(bits[0]) + if err != nil { + continue + } + link := parsed.ResolveReference(parsedLink).String() + var label string + if len(bits) == 1 { + label = link + } else { + label = strings.Join(bits[1:], " ") + } + links = append(links, link) + fmt.Printf("[%d] %s\n", len(links), label) + } else { + // This should really be wrapped, but there's + // no easy support for this in Go's standard + // library + fmt.Println(line) + } + } + } else { + // Just print any other kind of text + fmt.Print(body) + } + history = append(history, u) + case 4, 5: + fmt.Println("ERROR: " + meta) + } + } +} @@ -1,7 +1,7 @@ #!/bin/sh # Toggle auxiliary HDMI screen on/off when connected/disconnected. -opt="--auto --left-of eDP1" +opt="--auto --left-of eDP-1" #opt="--auto --right-of eDP-1" #opt="--auto --above eDP-1" @@ -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 '^DP-1 connected' || opt="--off" +xrandr --output DP-1 $opt @@ -2,6 +2,8 @@ # Display images directly in terminal. # Tested with xterm v361. Depends on imagemagick convert(1). +[ "$LC_TERMINAL" = iTerm2 ] && [ -x "$HOME/.iterm2/imgcat" ] && exec "$HOME/.iterm2/imgcat" "$@" + # maxsize prints the geomtry size of terminal window, with # a maximum value of 1000 pixels for width and height, or # 640x480 if terminal size can not be probed. diff --git a/bin/idot b/bin/idot deleted file mode 100755 index 44387e8..0000000 --- a/bin/idot +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -# dot -Gsize=9 -Nshape=box -Nstyle=rounded -Tsvg | convert -- - sixel:/dev/tty -# dot -Gsize=9 -Nfontname=Helvetica -Tsvg | convert -- - sixel:/-dev/tty -dot -Gsize=9 -Nfontname=Arial -Tpng | icat - /dev/tty @@ -36,6 +36,7 @@ ca() { pp() { pp_s=$1 pp_b=$2 R1='' while true; do + ! [ "$pp_s" ] && R1='' && return [ "$pp_b" = "$pp_s" ] && break pp_s=${pp_s%/*} [ "$R1" ] && R1=$R1/.. || R1=.. @@ -0,0 +1,136 @@ +#!/bin/sh + +# See https://spec.commonmark.org for reference +md2html() { + awk ' + function newblock(nb) { + if (text) print "<" block ">" text "</" block ">" + text = "" + block = nb ? nb : "p" + } + + function subinline(tgl, inl) { + while (match($0, tgl)) { + if (inline[ni] == inl) + ni -= sub(tgl, "</" inl ">") + else if (sub(tgl, "<" inl ">")) + inline[++ni] = inl + } + } + + function dolink(href, lnk) { + # Undo escaped html in uris + gsub(/&/, "\\&", href) + gsub(/</, "<", href) + gsub(/>/, ">", href) + # & can be tricky, and not standard: + gsub(/&/, "\\\\\\&", href) + gsub(/&/, "\\\\\\&", lnk) + return "<a href=\"" href "\">" lnk "</a>" + } + + BEGIN { ni = 0; nl = 0; text = ""; block = "p" } + + # Escape html + esc != "false" { gsub("&", "\\&"); gsub("<", "\\<"); gsub(">", "\\>") } + + # Horizontal rules (_ is not in markdown) + /^[ ]*([-*_] ?)+[ ]*$/ && text == "" { print "<hr>"; next } + + # Tables (not in markdown) + # Syntax: + # Right Align| Center Align |Left Align + /([ ]\|)|(\|[ ])/ { + if (block != "table") newblock("table") + nc = split($0, cells, "|") + $0 = "<tr>\n" + for (i = 1; i <= nc; i++) { + align = "left" + if (sub(/^[ ]+/, "", cells[i])) { + if (sub(/[ ]+$/, "", cells[i])) + align = "center" + else + align = "right" + } + sub(/[ ]+$/, "", cells[i]) + $0 = $0 "<td align=\"" align "\">" cells[i] "</td>\n" + } + $0 = $0 "</tr>" + } + + # Ordered and unordered (possibly nested) lists + /^[ ]*([*+-]|(([0-9]+[\.-]?)+))[ ]/ { + newblock("li") + nnl = 1 + while (match($0, /^[ ]/)) { sub(/^[ ]/,""); nnl++ } + while (nl > nnl) print "</" list[nl--] ">" + while (nl < nnl) { + list[++nl] = "ol" + if (match($0, /^[*+-]/)) list[nl] = "ul" + print "<" list[nl] ">" + } + sub(/^([*+-]|(([0-9]+[\.-]?)+))[ ]/,"") + } + + # Multi line list items + block == "li" { sub(/^( *)|( *)/,"") } + + # Code blocks + /^( | )/ { + if (block != "code") newblock("code") + sub(/^( | )/, "") + text = text $0 "\n" + next + } + + # Paragraph + /^$/ { newblock(); while(nl > 0) print "</" list[nl--] ">" } + + # Setex-style Headers (plus h3 with underscores) + /^=+$/ { block = "h" 1; next } + /^-+$/ { block = "h" 2; next } + /^_+$/ { block = "h" 3; next } + + # Atx-style headers + /^#/ { + newblock() + match($0, /#+/) + n = RLENGTH + if (n > 6) n = 6 + sub(/# */, "#") + text = substr($0, RLENGTH + 1) + block = "h" n + next + } + + { + # Images + while (match($0, /!\[[^\]]+\]\([^\)]+\)/)) { + split(substr($0, RSTART, RLENGTH), a, /(!\[)|\)|(\]\()/) + sub(/!\[[^\]]+\]\([^\)]+\)/, "<img src=\"" a[3] "\" alt=\"" a[2] "\">") + } + # Links + while (match($0, /\[[^\]]+\]\([^\)]+\)/)) { + split(substr($0, RSTART, RLENGTH), a, /[\[\)]|(\]\()/) + sub(/\[[^\]]+\]\([^\)]+\)/, dolink(a[3], a[2])) + } + # Auto links (uri matching is poor) + na = split($0, a, /(^\()|[ ]|([,\.\)]([ ]|$))/) + for (i = 1; i <= na; i++) + if (match(a[i], /^(((https?|ftp|file|news|irc):\/\/)|(mailto:)).+$/)) + sub(a[i], dolink(a[i], a[i])) + # Inline + subinline("(\\*\\*)|(__)", "strong") + subinline("\\*", "em") + subinline("`", "code") + text = text (text ? " " : "") $0 + } + + END { + while(ni > 0) text = text "</" inline[ni--] ">" + newblock() + while(nl > 0) print "</" list[nl--] ">" + }' "$1" +} + +md2html "$1" diff --git a/bin/md2html b/bin/md2html index 35e1d72..7be4aaa 100755 --- a/bin/md2html +++ b/bin/md2html @@ -1,43 +1,42 @@ #!/usr/bin/awk -f # md2html.awk -# by: Jesus Galan (yiyus) <yiyu.jgl@gmail>, May 2009 # Usage: # md2html file.md > file.html # Options: -v esc=false to not escape html -function newblock(nblock){ - if(text) - print "<" block ">" text "</" block ">"; - text = ""; - block = nblock ? nblock : "p"; +function newblock(nblock) { + if (text) + print "<" block ">" text "</" block ">" + text = "" + block = nblock ? nblock : "p" } -function subinline(tgl, inl){ - while(match($0, tgl)){ +function subinline(tgl, inl) { + while (match($0, tgl)){ if (inline[ni] == inl) - ni -= sub(tgl, "</" inl ">"); + ni -= sub(tgl, "</" inl ">") else if (sub(tgl, "<" inl ">")) - inline[++ni] = inl; + inline[++ni] = inl } } -function dolink(href, lnk){ +function dolink(href, lnk) { # Undo escaped html in uris - gsub(/&/, "\\&", href); - gsub(/</, "<", href); - gsub(/>/, ">", href); + gsub(/&/, "\\&", href) + gsub(/</, "<", href) + gsub(/>/, ">", href) # & can be tricky, and not standard: - gsub(/&/, "\\\\\\&", href); - gsub(/&/, "\\\\\\&", lnk); - return "<a href=\"" href "\">" lnk "</a>"; + gsub(/&/, "\\\\\\&", href) + gsub(/&/, "\\\\\\&", lnk) + return "<a href=\"" href "\">" lnk "</a>" } BEGIN { ni = 0; # inlines nl = 0; # nested lists - text = ""; - block = "p"; + text = "" + block = "p" } # Escape html @@ -49,128 +48,122 @@ esc != "false" { # Horizontal rules (_ is not in markdown) /^[ ]*([-*_] ?)+[ ]*$/ && text == "" { - print "<hr>"; - next; + print "<hr>" + next } # Tables (not in markdown) # Syntax: # Right Align| Center Align |Left Align /([ ]\|)|(\|[ ])/ { - if(block != "table") - newblock("table"); - nc = split($0, cells, "|"); - $0 = "<tr>\n"; - for(i = 1; i <= nc; i++){ - align = "left"; - if(sub(/^[ ]+/, "", cells[i])){ - if(sub(/[ ]+$/, "", cells[i])) - align = "center"; + if (block != "table") + newblock("table") + nc = split($0, cells, "|") + $0 = "<tr>\n" + for (i = 1; i <= nc; i++){ + align = "left" + if (sub(/^[ ]+/, "", cells[i])){ + if (sub(/[ ]+$/, "", cells[i])) + align = "center" else - align = "right"; + align = "right" } - sub(/[ ]+$/,"", cells[i]); - $0 = $0 "<td align=\"" align "\">" cells[i] "</td>\n"; + sub(/[ ]+$/,"", cells[i]) + $0 = $0 "<td align=\"" align "\">" cells[i] "</td>\n" } - $0 = $0 "</tr>"; + $0 = $0 "</tr>" } # Ordered and unordered (possibly nested) lists /^[ ]*([*+-]|(([0-9]+[\.-]?)+))[ ]/ { - newblock("li"); - nnl = 1; - while(match($0, /^[ ]/)){ - sub(/^[ ]/,""); - nnl++; + newblock("li") + nnl = 1 + while (match($0, /^[ ]/)){ + sub(/^[ ]/,"") + nnl++ } - while(nl > nnl) - print "</" list[nl--] ">"; - while(nl < nnl){ - list[++nl] = "ol"; - if(match($0, /^[*+-]/)) - list[nl] = "ul"; - print "<" list[nl] ">"; + while (nl > nnl) + print "</" list[nl--] ">" + while (nl < nnl){ + list[++nl] = "ol" + if (match($0, /^[*+-]/)) + list[nl] = "ul" + print "<" list[nl] ">" } - sub(/^([*+-]|(([0-9]+[\.-]?)+))[ ]/,""); + sub(/^([*+-]|(([0-9]+[\.-]?)+))[ ]/,"") } # Multi line list items block == "li" { - sub(/^( *)|( *)/,""); + sub(/^( *)|( *)/,"") } # Code blocks /^( | )/ { - if(block != "code") - newblock("code"); - sub(/^( | )/, ""); - text = text $0 "\n"; - next; + if (block != "code") + newblock("code") + sub(/^( | )/, "") + text = text $0 "\n" + next } -# Paragraph +# Paragraphs /^$/ { - newblock(); - while(nl > 0) - print "</" list[nl--] ">"; + newblock() + while (nl > 0) + print "</" list[nl--] ">" } -# Setex-style Headers -# (Plus h3 with underscores.) -/^=+$/ { - block = "h" 1; - next; -} - -/^-+$/ { - block = "h" 2; - next; +# Headers +/^#/ { + newblock() + match($0, /#+/) + n = RLENGTH + if (n > 6) + n = 6 + text = substr($0, RLENGTH + 1) + block = "h" n + next } -/^_+$/ { - block = "h" 3; - next; +# Alternate headers (underlined) +/^=+$/ { + block = "h" 1 + next } -# Atx-style headers -/^#/ { - newblock(); - match($0, /#+/); - n = RLENGTH; - if(n > 6) - n = 6; - text = substr($0, RLENGTH + 1); - block = "h" n; - next; +/^-+$/ { + block = "h" 2 + next } -// { +{ # Images - while(match($0, /!\[[^\]]+\]\([^\)]+\)/)){ - split(substr($0, RSTART, RLENGTH), a, /(!\[)|\)|(\]\()/); - sub(/!\[[^\]]+\]\([^\)]+\)/, "<img src=\"" a[3] "\" alt=\"" a[2] "\">"); + while (match($0, /!\[[^\]]+\]\([^\)]+\)/)){ + split(substr($0, RSTART, RLENGTH), a, /(!\[)|\)|(\]\()/) + sub(/!\[[^\]]+\]\([^\)]+\)/, "<img src=\"" a[3] "\" alt=\"" a[2] "\">") } # Links - while(match($0, /\[[^\]]+\]\([^\)]+\)/)){ - split(substr($0, RSTART, RLENGTH), a, /[\[\)]|(\]\()/); - sub(/\[[^\]]+\]\([^\)]+\)/, dolink(a[3], a[2])); + while (match($0, /\[[^\]]+\]\([^\)]+\)/)){ + split(substr($0, RSTART, RLENGTH), a, /[\[\)]|(\]\()/) + sub(/\[[^\]]+\]\([^\)]+\)/, dolink(a[3], a[2])) } # Auto links (uri matching is poor) - na = split($0, a, /(^\()|[ ]|([,\.\)]([ ]|$))/); - for(i = 1; i <= na; i++) - if(match(a[i], /^(((https?|ftp|file|news|irc):\/\/)|(mailto:)).+$/)) - sub(a[i], dolink(a[i], a[i])); + na = split($0, a, /(^\()|[ ]|([,\.\)]([ ]|$))/) + for (i = 1; i <= na; i++) + if (match(a[i], /^(((https?|ftp|file|news|irc):\/\/)|(mailto:)).+$/)) + sub(a[i], dolink(a[i], a[i])) # Inline - subinline("(\\*\\*)|(__)", "strong"); - subinline("\\*", "em"); - subinline("`", "code"); - text = text (text ? " " : "") $0; + subinline("(\\*\\*)|(__)", "strong") + subinline("\\*", "em") + subinline("`", "code") + text = text (text ? " " : "") $0 } END { - while(ni > 0) - text = text "</" inline[ni--] ">"; - newblock(); - while(nl > 0) - print "</" list[nl--] ">"; + while (ni > 0) + text = text "</" inline[ni--] ">" + newblock() + while (nl > 0) + print "</" list[nl--] ">" } diff --git a/bin/migrate_vimki b/bin/migrate_vimki new file mode 100755 index 0000000..15242e6 --- /dev/null +++ b/bin/migrate_vimki @@ -0,0 +1,58 @@ +#!/bin/sh + +# Migrate Wiki content from vimki to vimki2 format. + +fixfile() { + awk ' + { + for (i = 1; i <= NR; i++) { + if (match($i, /^[A-Z][A-Z_0-9]*[a-z][a-z0-9]*[_A-Z]/) == 0) continue + s = $i + l = "" + while (match(s, /^[A-Z][A-Z_0-9]*[a-z]+/) != 0) { + l = l substr(s, 1, RLENGTH) " " + s = substr(s, RLENGTH+1) + } + p = match(s, /[^A-Za-z0-9_]/) + if (p != 0) { + s2 = substr(s, 1, p-1) "]" substr(s, p) + s = s2 + } else s = s "]" + l = "[" l s + gsub(/[ \t_]+/, " ", l) + sub(/ *]/, "]", l) + $i = tolower(l) + } + print + } + ' "${1:-HomePage}" +} + +fixname() { + echo "$1" | awk '{ + s = $0 + l = "" + while (match(s, /^[A-Z][A-Z_0-9]*[a-z]+/) != 0) { + l = l substr(s, 1, RLENGTH) " " + s = substr(s, RLENGTH+1) + } + p = match(s, /[^A-Za-z0-9_]/) + if (p != 0) { + s2 = substr(s, 1, p-1) "]" substr(s, p) + s = s2 + } + l = l s + gsub(/[ \t_]+/, " ", l) + sub(/ *$/, "", l) + print tolower(l) + + }' +} + +mkdir -p $HOME/Wiki2 +cd $HOME/Wiki +for file in *; do + new=$(fixname "$file") + echo "$new" + fixfile "$file" > "$HOME/Wiki2/$new" +done diff --git a/bin/open b/bin/open deleted file mode 100755 index 0fa97a7..0000000 --- a/bin/open +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -exec >/tmp/open.out 2>&1 -set -x - -cmd=xdg-open -case $1 in -(*.mkv|*.mp4) cmd=vlc;; -esac - -exec $cmd "$@" @@ -0,0 +1,3 @@ +#!/bin/sh +adb forward tcp:8222 tcp:8022 +ssh ssh://u0_a250@localhost:8222 diff --git a/bin/rdate b/bin/rdate new file mode 100755 index 0000000..a69d137 --- /dev/null +++ b/bin/rdate @@ -0,0 +1,11 @@ +#!/bin/sh + +# Set date on remote machine from local one. This version works +# from MacOS to AlpineLinux. It is intended to re-sync clock of a +# virtual machine after host wakeup. + +# TODO: option to display the time difference between host and remote. +# hint: use 'date +%s' to get timestamp in seconds since epoch. + +ssh "$1" "sudo date $(date +%m%d%H%M%Y.%S)" + diff --git a/bin/start_godoc b/bin/start_godoc new file mode 100755 index 0000000..c6a716b --- /dev/null +++ b/bin/start_godoc @@ -0,0 +1,5 @@ +#!/bin/sh + +# Start a godoc server on port 6060 + +exec /home/marc/go/bin/godoc >/tmp/godoc.out 2>&1 & diff --git a/bin/status2 b/bin/status2 new file mode 100755 index 0000000..1b7ea55 --- /dev/null +++ b/bin/status2 @@ -0,0 +1,27 @@ +#!/bin/sh + +ip() { + while read -r w1 w2 w3; do + [ $w1 = /32 ] && [ $w2 = host ] && [ $ip != 127.0.0.1 ] && break + ip=$w2 + done < /proc/net/fib_trie +} + +power() { + while read -r line; do + case $line in POWER_SUPPLY_CAPACITY*) power=${line#*=}% && break;; esac + done < /sys/class/power_supply/BAT0/uevent +} + +status() { + power + ip + sound="$(pamixer --get-volume-human)" + clock="$(date +'%a %d %b %H:%M')" +} + +while true; do + status && echo "$ip , power $power , sound $sound , $clock" + sleep 17 +done | +root-tail -g '500x20-80-0' -fn '-*-fixed-medium-*-*-*-15-*-*-*-*-*-*-*' --color grey70 - diff --git a/bin/update_hosts b/bin/update_hosts new file mode 100755 index 0000000..74bf25b --- /dev/null +++ b/bin/update_hosts @@ -0,0 +1,28 @@ +#!/bin/sh -e + +# Update /etc/hosts with a well curated blacklist of malware, ads, porn, etc. +# Custom hosts are preserved. + +[ "$USER" = root ] || exec sudo "$0" "$@" + +echo 'Checking from https://github.com/StevenBlack/hosts:' +lsd=$(curl -s 'https://api.github.com/repos/StevenBlack/hosts/commits?path=hosts&page=1&per_page=1' | jq -r '.[0].commit.committer.date') +#echo "last source update: $(date -j -f "%FT%TZ" "$lsd")" +echo "last source update: $(date --date="$lsd")" +echo "last local update: $(date -r /etc/hosts)" +# [ $(date -j -f "%FT%TZ" "$lsd" +%s) -lt $(date -r /etc/hosts +%s) ] && echo 'Nothing to do' && exit +[ $(date --date="$lsd" +%s) -lt $(date -r /etc/hosts +%s) ] && echo 'Nothing to do' && exit + +cd /etc +cp -p hosts hosts.old +hosts=$(awk '/^# Custom host/, /^# End of custom host/' hosts.old) +curl -s 'https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts' | awk ' + BEGIN { hosts = ARGV[1]; ARGV[1] = "" } + /^# Custom host/ { print hosts; next } + /^# End of custom host/ { next } + { print } +' "$hosts" > hosts.new + +mv hosts.new hosts +[ "$(uname -s)" != Darwin ] || { dscacheutil -flushcache; killall -HUP mDNSResponder; } +diff -u /etc/hosts.old /etc/hosts | diffstat diff --git a/bin/update_kernel_host b/bin/update_kernel_host new file mode 100755 index 0000000..4f2cbbf --- /dev/null +++ b/bin/update_kernel_host @@ -0,0 +1,13 @@ +#!/bin/sh -ex + +# After 'apk upgrade', update kernel and initrd on VM host + +if [ -f /etc/arch-release ]; then + scp /boot/Image /boot/initramfs-linux.img marc@m1:.vm/a1 + exit +fi + +sudo cp /boot/initramfs-virt /tmp +sudo chmod a+r /tmp/initramfs-virt +gunzip < /boot/vmlinuz-virt > /tmp/vmlinux +scp /tmp/initramfs-virt /tmp/vmlinux marc@m1:.vm/vm1/ diff --git a/bin/vcat b/bin/vcat new file mode 100755 index 0000000..7c162d4 --- /dev/null +++ b/bin/vcat @@ -0,0 +1,8 @@ +#!/bin/sh + +# Use vim to cat a file. +# Useful if file is vim-encrypted (password silently expected). + +trap "stty '$(stty -g)'" EXIT +stty -echo +vim -es '+%p' '+:q!' "$1" diff --git a/bin/vimki b/bin/vimki new file mode 100755 index 0000000..e3e0f4d --- /dev/null +++ b/bin/vimki @@ -0,0 +1,56 @@ +#!/bin/sh + +# Batch operations on vimki + +# rename changes the link name and propagates the change in the wiki +rename1() { + [ "$(head -c 8 "$3")" = VimCrypt ] && return + gawk -v old="$1" -v new="$2" -v IGNORECASE=1 ' + { + gsub("[[]" old "[]]", "[" new "]") + print + } + ' "$3" > "$3.$$" && mv "$3.$$" "$3" +} + +link2rx() { + gawk -v rx="$1" -v IGNORECASE=1 ' + BEGIN { + gsub(/_/, "\\W+", rx) + gsub(/a/, "[aåäáàâã]", rx) + gsub(/c/, "[cç]", rx) + gsub(/e/, "[eéèêë]", rx) + gsub(/i/, "[iîíìï]", rx) + gsub(/o/, "[oôöóõò]", rx) + gsub(/u/, "[uùûüú]", rx) + gsub(/y/, "[yÿ]", rx) + gsub(/n/, "[nñ]", rx) + print rx + }' +} + +#shellcheck disable=SC2020 + +linkfile() { + echo "$1" | + tr '[:upper:]' '[:lower:]' | + tr 'åäáàâãçéèêëîíìïôöóõòùûüúÿñ' 'aaaaaaceeeeiiiiooooouuuuyn' | + awk '{gsub(/\W+/, "_"); print}' +} + +rename() { + rx=$(link2rx "$1") + # Parallelize renaming + echo *.md | xargs -n 1 -P 8 "$0" rename1 "$rx" "$2" + oldf="$(linkfile "$1").md" + [ -f "$oldf" ] && mv "$oldf" "$(linkfile "$2").md" +} + +fixfiles() { + for f in *; do + mv "$f" "$(linkfile "$f")" + done +} + +cd ~/Wiki || exit +"$@" @@ -0,0 +1,286 @@ +#!/bin/sh + +# Manage virtual machines +# +# Prereq: +# - curl +# - cdrtools (isoinfo) +# - expect +# - qemu +# - screen +# +# TODO: +# - 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=' +' + +version='vm-0.1' + +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 virtual machine' && return + [ "$1" ] || die "missing argument" + is_running "$1" && screen -r "vm!$1!" +} + +create_arch() { + usage 'create_arch [-s size] name' 'Create an archlinux disk image' && return + size=8g + while getopts :s: opt; do + case $opt in + s) size=$OPTARG ;; + *) Opth=2 create_arch; 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 + + mkfs.ext4 "$1.raw" + mkdir -p mnt + sudo mount "$1.raw" mnt + sudo pacstrap mnt base base-devel + sudo umount mnt +} + +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_arch; 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; } + +help() { + usage 'help' 'Print this help text' && return + printf "$version\n Manage virtual machines\n\nUsage: vm command [options] [args]\n" + Opth=1; for c in $Cmdlist; do $c; done +} + +init() { + mkdir -p "$dir" +} + +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 +} + +is_running() { screen -ls "vm!$1!" >/dev/null 2>&1; } + +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 +} + +new_ip() { + read index < $dir/index + index=$((index + 1)) + echo "$index" > $dir/index + echo "10.0.2.$index/24" +} + +new_macaddr() { printf 'de:ad:be:ef:%02x:%02x\n' $((RANDOM % 256)) $((RANDOM % 256)); } + +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 +} + +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" + ' +} + +# 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 + ' +} + +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 +} + +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 [-acd] name' 'start a virtual machine' && return + while getopts :acd opt; do + case $opt in + a) opta=1 ;; + c) boot=c ;; + d) boot=d ;; + esac + done + 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_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 virtual machine' && return + is_running "$1" && kill "$(pidof "$1")" +} + +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 "$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 +cmd=$C +[ "$cmd" ] || { help; exit 1; } && $cmd "$@" @@ -0,0 +1,6 @@ +#!/bin/sh + +cols=$(stty -a|awk -F "[ ;]" '{print $9; exit}') +[ $cols -gt 72 ] && stty cols 72 +w3m "${@:-https://ddg.gg/lite/}" +stty cols "$cols" @@ -0,0 +1,335 @@ +#!/bin/sh + +## wag is a tool to generate static web sites +## +## Commands: + +unset CDPATH +export LC_ALL=C IFS='
+' + +cmd=$(command -v "$0") + +dest=./public + +footer() { + : +} + +# front parses front matter +front() { + { + read a b && [ "$a" = "---" ] && [ "$b" = "" ] || return + while read a b; do + [ "$a" = "---" ] && [ "$b" = "" ] && break + eval "front_${a%:}=\"$b\"" + done + } < "$1" +} + +## gen generates site content from source files +gen() { + [ -d "$dest" ] || mkdir -p "$dest" + for f in *.md; do + [ "$f" = "*.md" ] && continue + g="$dest/${f%.md}.html" + [ "$f" -ot "$g" ] && continue + echo "f: $f $g" + front "$f" + { + header + md2html "$f" + footer + } > "$g" + done +} + +header() { + cat <<- EOT + <title>$front_title</title> + <meta charset="utf-8"> + <style>body {max-width: 52em; margin: 1em auto; padding: 1em}</style> + EOT +} + +## help prints this program documentation +help() { awk '/^## / {print substr($0, 4)}' "$cmd"; } + +lipsum='Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in +reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla +pariatur. Excepteur sint occaecat cupidatat non proident, sunt in +culpa qui officia deserunt mollit anim id est laborum.' + +# An overly simplified http request parser for static web sites. +http_request() { + read -r cmd uri proto + case $uri in */) uri="${uri}index.html" ;; esac + while true; do + read -r line || break + [ ${#line} = 0 ] && break + done + printf 'HTTP/1.1 200 OK\n\n' && cat "${uri#/}" +} + +md2h() { want="$2" got=$(echo "$1" | md2html -); } + +md2html() { + tmp=$(mktemp -u) + trap "rm -f '$tmp'" EXIT + { + cat "${1:--}" | tee "$tmp" | awk '/^[ ]*\[[^]]+\]:/' + cat "$tmp" + } | + awk ' + function newblock(nblock) { + if (text) + print "<" block ">" text "</" block ">" + text = "" + out = 1 + block = nblock ? nblock : "p" + } + + function subinline(tgl, inl) { + while (match($0, tgl)) { + if (inline[ni] == inl) + ni -= sub(tgl, "</" inl ">") + else if (sub(tgl, "<" inl ">")) + inline[++ni] = inl + } + } + + function dolink(href, lnk) { + # Undo escaped html in uris + gsub(/&/, "\\&", href) + gsub(/</, "<", href) + gsub(/>/, ">", href) + # & can be tricky, and not standard: + gsub(/&/, "\\\\\\&", href) + gsub(/&/, "\\\\\\&", lnk) + return "<a href=\"" href "\">" lnk "</a>" + } + + BEGIN { + ni = 0 # inlines + nl = 0 # nested lists + out = 0 # 0 if no output so far + text = "" + block = "p" + } + + # Skip front matter. + out == 0 && $0 == "---" { + do + getline + while ($0 != "---") + next + } + + # Escape HTML. + esc != "false" { + gsub("&", "\\&") + gsub("<", "\\<") + gsub(">", "\\>") + } + + # Internal references. + match($0, /^[ ]*\[[^]]+\]:/) > 0 { + k = substr($0, RSTART+1, RLENGTH-3) + v = substr($0, RLENGTH+1) + sub(/^[ ]/, "", v) + sub(/[ ]$/, "", v) + ref[substr($0, RSTART+1, RLENGTH-3)] = v + next + } + + # Horizontal rules. + /^[ ]*([-*_] ?)+[ ]*$/ && text == "" { + print "<hr>" + next + } + + # Tables. Syntax: + # Right Align| Center Align |Left Align + /([ ]\|)|(\|[ ])/ { + if (block != "table") + newblock("table") + nc = split($0, cells, "|") + $0 = "<tr>\n" + for (i = 1; i <= nc; i++) { + align = "left" + if (sub(/^[ ]+/, "", cells[i])) { + if (sub(/[ ]+$/, "", cells[i])) + align = "center" + else + align = "right" + } + sub(/[ ]+$/, "", cells[i]) + $0 = $0 "<td align=\"" align "\">" cells[i] "</td>\n" + } + $0 = $0 "</tr>" + } + + # Ordered and unordered (possibly nested) lists. + /^[ ]*([*+-]|(([0-9]+[.-]?)+))[ ]/ { + newblock("li") + nnl = 1 + while (match($0, /^[ ]/)) { + sub(/^[ ]/, "") + nnl++ + } + while (nl > nnl) + print "</" list[nl--] ">" + while (nl < nnl) { + list[++nl] = "ol" + if (match($0, /^[*+-]/)) + list[nl] = "ul" + print "<" list[nl] ">" + } + sub(/^([*+-]|(([0-9]+[.-]?)+))[ ]/, "") + } + + # Multi line list items. + block == "li" { + sub(/^( *)|( *)/, "") + } + + # Code blocks. + /^( | )/ { + if (block != "code") + newblock("code") + sub(/^( | )/, "") + text = text $0 "\n" + next + } + + # Paragraphs. + /^$/ { + newblock() + while (nl > 0) + print "</" list[nl--] ">" + } + + # Headers. + /^#+ / { + newblock() + match($0, /#+/) + n = RLENGTH + if (n > 6) + n = 6 + text = substr($0, RLENGTH + 1) + sub(/^ */, "", text) + block = "h" n + next + } + + # Alternate headers (underlined). + /^=+$/ { + block = "h" 1 + next + } + + /^-+$/ { + block = "h" 2 + next + } + + { + # Images. + while (match($0, /!\[[^]]+\]\([^)]+\)/)) { + split(substr($0, RSTART, RLENGTH), a, /(!\[)|\)|(\]\()/) + sub(/!\[[^]]+\]\([^)]+\)/, "<img src=\"" a[3] "\" alt=\"" a[2] "\">") + } + # Links. + while (match($0, /\[[^]]+\]\([^)]+\)/)) { + split(substr($0, RSTART, RLENGTH), a, /[[)]|(\]\()/) + sub(/\[[^]]+\]\([^)]+\)/, dolink(a[3], a[2])) + } + # Internal references. + while (match($0, /\[[^]]+\]/)) { + k = substr($0, RSTART+1, RLENGTH-2) + sub(/\[[^]]+\]/, dolink(ref[k], k)) + } + # Auto links (uri matching is poor). + na = split($0, a, /(^\()|[ ]|([,.)]([ ]|$))/) + for (i = 1; i <= na; i++) + if (match(a[i], /^(((https?|ftp|file|news|irc):\/\/)|(mailto:)).+$/)) + sub(a[i], dolink(a[i], a[i])) + # Inline. + subinline("(\\*\\*)|(__)", "strong") + subinline("\\*", "em") + subinline("`", "code") + text = text (text ? " " : "") $0 + } + + END { + while (ni > 0) + text = text "</" inline[ni--] ">" + newblock() + while (nl > 0) + print "</" list[nl--] ">" + }' +} + +#serve() { while true; do busybox nc -kl -p 1500 -e "$cmd http_request"; done; } +serve() { WAG_FUN=http_request busybox nc -kl -p 1500 -e "$cmd"; } + +test() { + fail=0 pass=0 skip=0 tfilter="$*" + + test_run md2h 'abc __def__ ghi' '<p>abc <strong>def</strong> ghi</p>' + test_run md2h 'abc **def** ghi' '<p>abc <strong>def</strong> ghi</p>' + test_run md2h 'abc *def* ghi' '<p>abc <em>def</em> ghi</p>' + test_run md2h 'abc ***def*** ghi' '<p>abc <strong><em>def</strong></em> ghi</p>' + test_run md2h 'abc `def` ghi' '<p>abc <code>def</code> ghi</p>' + test_run md2h '# h1' '<h1>h1</h1>' + test_run md2h '## h2' '<h2>h2</h2>' + test_run md2h 'h1 +==' '<h1>h1</h1>' + test_run md2h 'h2 +--' '<h2>h2</h2>' + test_run md2h 'abc [github] def' '<p>abc <a href="">github</a> def</p>' + test_run md2h 'abc [github](https://github.com) def' '<p>abc <a href="https://github.com">github</a> def</p>' + test_run md2h 'abc [github] def + +[github]: https://github.com' '<p>abc <a href="https://github.com">github</a> def</p>' + test_run md2h '--- +Title: front matter test +--- + +Hello [world]. + +--- +Bye. + +[world]: http://example.com' '<p>Hello <a href="http://example.com">world</a>.</p> +<hr> +<p>Bye.</p>' + + echo "Total: $((pass + fail + skip)), Passed: $pass, Failed: $fail, Skip: $skip" + return "$fail" +} + +test_run() { + eval "test_$1=\$((test_$1 + 1)); ti=\"\$test_$1\"" + [ "$tfilter" ] && + case "$1#$ti" in + $tfilter) ;; + *) skip=$((skip + 1)); return 0 ;; + esac + "$@" + [ "$got" = "$want" ] && { + pass=$((pass + 1)) + return 0 + } + fail=$((fail + 1)) + printf "%s FAIL\n Got: %s\n Want: %s\n" "$1#$ti" "$got" "$want" >&2 + return 1 +} + +[ "$WAG_FUN" ] && { $WAG_FUN; exit; } + +# Execute command line +[ "$1" ] || help && "$@" @@ -0,0 +1,6 @@ +#!/bin/sh +ip_plex=192.168.1.90 mac_plex=b0:83:fe:61:a7:aa +eval "mac=\$mac_$1 ip=\$ip_$1" +wakeonlan "$mac" +while ! nc -zw 2 "$ip" 22; do printf .; sleep 2; done +ssh "root@$ip" @@ -1,4 +1,6 @@ #!/bin/sh -exec xterm -sl 500 -j -cr red "$@" & +#exec xterm -sl 500 -j -cr red "$@" & +exec xterm "$@" & #cmd="urxvtc ${@:--T $HOSTNAME}" +#cmd="urxvtc $@" #eval "$cmd" || { [ $? = 2 ] && urxvtd -q -o -f && eval "$cmd"; } @@ -138,12 +138,13 @@ EOT cat > /mnt/etc/locale.gen << \EOT en_US.UTF-8 UTF-8 +en_GB.UTF-8 UTF-8 fr_FR.UTF-8 UTF-8 EOT echo yoda > /mnt/etc/hostname echo 'KEYMAP=fr-latin1' > /mnt/etc/vconsole.conf - echo 'LANG=en_US.UTF-8' > /mnt/etc/locale.conf + echo 'LANG=en_GB.UTF-8' > /mnt/etc/locale.conf ln -sf /usr/share/zoneinfo/Europe/Paris /mnt/etc/localtime cat > /mnt/etc/systemd/network/20-wired.network << \EOT @@ -171,6 +172,7 @@ EOT cat > /mnt/etc/systemd/resolved.conf << \EOT [Resolve] DNSSEC=no +DNSOverTLS=no EOT packages="linux linux-firmware intel-ucode amd-ucode sof-firmware \ |
