#!/bin/sh # Enterprise Onion Toolkit #rsync_flags="-n" # testing # expected by tools and libraries cd `dirname $0` || exit 1 export EOTK_HOME=`pwd` # for invoking myself prog=`basename $0` self=$EOTK_HOME/$prog # meta version=1.1alpha # set project directory; this path is hard-replicated elsewhere project_dir=$EOTK_HOME/projects.d # mirror directory mirrors_dir=$EOTK_HOME/mirrors.d # onionbalance directory ob_dir=$EOTK_HOME/onionbalance.d ob_conf=$ob_dir/config.yaml ob_status_sock=$ob_dir/ob-status.sock ob_tor_conf=$ob_dir/tor.conf ob_tor_control_sock=$ob_dir/tor-control.sock # set path export PATH=$EOTK_HOME/opt.d:$EOTK_HOME/lib.d:$PATH ################################################################## # argument stripping flag_remote=false while : ; do case "x$1" in x--remote) flag_remote=true shift ;; x--debug) set -x shift ;; *) break ;; esac done # what are the hostnames of the remote workers? host_list=$EOTK_HOME/eotk-hosts.conf CloudHosts() { # use --remote hack to forcibly stop risk of recursion... if $flag_remote ; then echo "localhost" # this will be treated as a magic sentinel return 0 fi # todo: if eotk-hosts.conf exists, # cat it (strip comments?) # and return if [ -s $host_list ] ; then cat $host_list return 0 fi # else we are working just on this machine echo "localhost" # this will be treated as a magic sentinel } cloud_hosts=`CloudHosts` # saves multiple invocations need_to_run_locally=false for host in $cloud_hosts ; do test "x$host" = "xlocalhost" && need_to_run_locally=true done ################################################################## # print a formatted message to stdout Print() { echo "$prog:" "$@" } # print a formatted message to stderr Warn() { Print "$@" 1>&2 } # essentially the projects.d folder is a little database ListProjects() { ( test -d $project_dir || exit 1 cd $project_dir || exit 1 for d in *.d/ ; do echo `basename $d .d` done ) } # TODO(alecm) resolve potential clashes between project names and # various other directory names ending in ".d"; maybe use ".proj"? # push eotk directory to remote DestructivePush() { for host in $cloud_hosts ; do test "x$host" = "xlocalhost" && continue # skip self echo :::: push $host :::: rsync $rsync_flags \ -av \ --delete \ --delete-excluded \ --exclude="*.log" \ --exclude="*.pid" \ --exclude="*.sock" \ --exclude="*.yaml" \ --exclude="*~" \ --exclude="docs.d/" \ --exclude="mirrors.d/" \ --exclude="onionbalance.d/" \ --exclude="secrets.d/" \ --exclude=".git/" \ --exclude=".gitignore" \ --exclude="cached-certs" \ --exclude="cached-microdesc*" \ --exclude="configure*.log" \ --exclude="hostname" \ --exclude="lock" \ --exclude="onion_service_non_anonymous" \ --exclude="private_key" \ --exclude="state" \ ${EOTK_HOME}/ \ $host:${EOTK_HOME}/ done } # mirror remote back for log review, backup, etc Mirror() { for host in $cloud_hosts ; do test "x$host" = "xlocalhost" && continue # skip self echo :::: mirror $host :::: test -d $mirrors_dir || mkdir -p $mirrors_dir || exit 1 rsync $rsync_flags -av \ --delete \ --delete-excluded \ --exclude="cached-certs" \ --exclude="cached-microdesc*" \ $host:${EOTK_HOME}/ $mirrors_dir/$host/ done } # run a command in the context of the local projects directory RunLocally() { action=$1 shift project=$1 shift echo :::: $action $project $@ :::: sh "$project_dir/$project.d/$action.sh" "$@" } # $1=action, rest = project names RunLocallyOverProjects() { action=$1 shift # remaining arguments are projects if [ "x$1" = "x" ] ; then # test for no args Print error: missing project name, try: $prog projects for a list, or -a for all return 1 elif [ "x$1" = "x-a" ] ; then # test for / expand the "-a" flag projects=`ListProjects` else # do what we are told projects="$*" fi # loop for project in $projects ; do RunLocally $action $project done } # run a command on remote machines, or possibly locally InvokeRemotely() { for host in $cloud_hosts ; do test "x$host" = "xlocalhost" && continue # skip self echo :::: invoking $host $* :::: ssh "$host" "$self --remote $*" done } # implement kludge to insert freshly-generated onions into a "*.tconf" # template-configuration, so people don't have to type so much... Populate() { if [ -t 0 ] ; then # if stdin is a terminal, provide diags dots=true else dots=false fi cat "$@" | while read line ; do case "$line" in *%NEW_ONION%*) # legacy / shorter / more common onion=secrets.d/`$self genkey` echo "$line" | sed -e "s!%NEW_ONION%!$onion!" ;; *%NEW_HARD_ONION%*) # same as NEW_ONION onion=secrets.d/`$self genkey` echo "$line" | sed -e "s!%NEW_HARD_ONION%!$onion!" ;; *%NEW_SOFT_ONION%*) onion=`$self genkey` onion=`basename $onion .key` echo "$line" | sed -e "s!%NEW_SOFT_ONION%!$onion!" ;; *) echo "$line" ;; esac if $dots ; then echo ".\c" >/dev/tty fi done if $dots ; then echo "" >/dev/tty fi } # get a config file and re/populate the projects directory with it Configure() { log=configure$$.log for file in "$@" ; do echo :::: configure $file :::: case "$file" in *.conf) : happy bunnies ;; *.tconf) file2=`basename $file .tconf`.conf if [ -s $file2 ] ; then Print info: $file: using existing $file2 else Print info: $file: populating $file2 with onions, please be patient... Populate $file >$file2 fi file="$file2" ;; *) Print error: bad config file suffix, was expecting: .conf, .tconf exit 1 ;; esac if ! $EOTK_HOME/lib.d/do-configure.pl "$file" ; then Print error: failure processing $file: see $log exit 1 fi done 2>$log Print done. logfile is $log } # argument 'parser' - ha! cmd="$1" # we may need the remaining args shift case "$cmd" in version) Print $version $EOTK_HOME `uname -a` test -d .git && git show -s --oneline ;; projects|proj) ListProjects ;; populate|pop) Populate "$@" ;; configure|config|conf) Configure "$@" ;; genkey|gen) secrets=secrets.d test -d $secrets || mkdir -p $secrets || exit 1 ( cd $secrets generate-onion-key.sh ) ;; test) $self version # recurse! InvokeRemotely version ;; ## ACTIONS start) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects start "$@" InvokeRemotely start "$@" ;; stop) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects stop "$@" InvokeRemotely stop "$@" ;; bounce|restart|reload) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects bounce "$@" InvokeRemotely bounce "$@" ;; nxreload) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects nxreload "$@" InvokeRemotely nxreload "$@" ;; debugon) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects debugon "$@" InvokeRemotely debugon "$@" ;; debugoff) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects debugoff "$@" InvokeRemotely debugoff "$@" ;; harvest|onions) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects harvest "$@" InvokeRemotely harvest "$@" ;; status) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects status "$@" InvokeRemotely status "$@" ;; maps|map) # project, or "-a" $need_to_run_locally && RunLocallyOverProjects maps "$@" InvokeRemotely maps "$@" ;; delete) # project, or "-a" Print $cmd not yet implemented, sorry. ;; ## DIAGS ps) ps auxww | egrep '\b(eotk)\b' InvokeRemotely ps ;; ## ONIONBALANCE push) Print push is destructive and has been renamed, see the documentation ;; # this used to be called 'push' but got renamed because oops. # DO NOT USE THIS CASUALLY, LEARN FROM MY MISTAKES remote-nuke-and-push|rnap) $self stop -a DestructivePush ;; ob-config|obconfig) if [ "x$1" = "x" ] ; then Print error: missing project name, try: $prog projects for a list, or -a for all exit 1 fi # TODO: check if they are extant first, and if so, exit with error # ob storage test -d $ob_dir || mkdir -p $ob_dir || exit 1 # make tor conf ( echo DataDirectory $ob_dir echo ControlPort unix:$ob_tor_control_sock echo PidFile $ob_dir/tor.pid echo Log info file $ob_dir/tor.log echo SafeLogging 1 echo HeartbeatPeriod 60 minutes echo RunAsDaemon 1 echo "#" onionbalance # echo SocksPort unix:$ob_dir/tor-socks.sock # curl 7.38 does not like this echo SocksPort 127.0.0.1:9050 # meh echo CookieAuthentication 1 echo MaxClientCircuitsPending 1024 ) > $ob_tor_conf # make ob conf ( echo STATUS_SOCKET_LOCATION: $ob_status_sock echo INITIAL_DELAY: 60 echo REFRESH_INTERVAL: 600 echo DESCRIPTOR_UPLOAD_PERIOD: 600 echo DESCRIPTOR_VALIDITY_PERIOD: 1800 echo PUBLISH_CHECK_INTERVAL: 900 echo LOG_LEVEL: debug $self map "$@" | grep -v "^::::" | do-obconfig.pl ) > $ob_conf ;; ob-start|obstart) Print starting Tor tor -f $ob_tor_conf >$ob_dir/tor-startup.log 2>&1 Print starting OnionBalance onionbalance \ -s $ob_tor_control_sock \ -c $ob_conf $ob_dir/onionbalance.log 2>&1 & # bg ob_pid=$! echo $ob_pid >$ob_dir/ob.pid ;; ob-stop|obstop) for pidfile in $ob_dir/ob.pid $ob_dir/tor.pid ; do test -s $pidfile || continue pid=`cat $pidfile` Print sending SIGTERM to $pid in $pidfile kill $pid done rm -f $ob_dir/ob.pid ;; ob-status|obstatus) ( cd $ob_dir || exit 1 pidfiles=`find . -name "*.pid"` if [ "x$pidfiles" != "x" ] ; then ps -p `cat $pidfiles` fi echo "" socat - unix-connect:$ob_dir/ob-status.sock ) ;; ## FREEZE/BACKUP mirror|pull) Mirror ;; freeze|backup) Mirror ( ds=`date "+%Y%m%d%H%M%S"` cd $mirrors_dir || exit 1 for directory in */ ; do test -d "$directory" || exit 1 # did */ expand? dir=`basename $directory` # strip trailing / echo :::: freeze $dir :::: tar cf - $dir | bzip2 > $dir-$ds.tar.bz2 done ) ;; *) Print usage: see README.md for documentation exit 1 ;; esac exit 0