#!/bin/bash # Configurables: TMP=${TMP:-/tmp/SBo} OUTPUT=${OUTPUT:-/tmp} BUILDLOG=${BUILDLOG:-build.log} DEFAULT_MAKEFLAGS="-j8" # End of configurables. It's probably best not to configure TMP or # OUTPUT here (use the environment instead) anyway. Also it's probably # convenient to add build.log to .git/info/exclude. # If we're not running as root, re-exec as root, with args. # Anything sbrun expects to possibly inherit from the caller's environment # must be explicity set here as sudo will strip them from the environment # before executing anything. if [ "$(id -u)" != "0" ]; then exec sudo \ TMP="$TMP" \ OUTPUT="$OUTPUT" \ MAKEFLAGS="$MAKEFLAGS" \ BUILDLOG="$BUILDLOG" \ "$0" "$@" fi # Inherit MAKEFLAGS from env, if present. MAKEFLAGS="${MAKEFLAGS:-$DEFAULT_MAKEFLAGS}" # Defaults, changed by -options. NETWORK="no" TRACK="yes" STRACE="" CLEANUP="no" LOGDIR="" NSENTER="" TRACKFS="" SELF=$(basename $0) # unshare and nsenter use this. It's theoretically better to use # an unpredictable filename (not one based on the PID), but anyone # able to mess with /mnt already has root access. NONET_PATH=/mnt/nonet.$SELF.$$ # Possible future options: # -f Run script with fakeroot. (still need root/sudo for unshare/nsenter, # and trackfs won't track failed writes, maybe this isn't that useful?) # -i Install package after it's built. Could count as scope creep. long_help() { # note: root's pager is used, not the user's, since we use sudo. # not going to care about this one. cat <". -x Run the script with "sh -x", enables shell command tracing. -c Clean up (remove) source and package directories after the build completes. This option overrides \$TMP from the environment. -h, --help Show short usage message and exit. -H Show long help message (you're reading it now) and exit. All arguments not beginning with - are passed as part of the SlackBuild script's environment. Options beginning with - must occur before environment variables. Example: $SELF -j1 SDL2=no DOCS=yes Leave off the -j1 to use the default number of jobs. After the SlackBuild exits, any files written to outside of \$TMP, \$OUTPUT, /tmp, /var/tmp, or /root/.ccache (collectively referred to as "the sandbox") are logged to stdout. See trackfs(1) for details of the log format, but any write outside the sandbox means a bug in the SlackBuild and should be reported to its maintainer. The exit status of $SELF is the exit status of the SlackBuild. Note: the current directory needs to be writable, since the log and (with -s/-S) strace output are written there. ====================================================================== The rest of this help message is a long-winded discussion that doesn't include any more usage information. Feel free to stop reading at any time :) ====================================================================== Why does sbrun exist? Why not use sbopkg or sbotools? sbrun is targeted more towards a SlackBuild developer/maintainer than an end user. My workflow is to edit the script in one terminal and repeatedly execute it in another. If you're editing a SlackBuild, you keep running it over & over again. So I wrote a 1-liner shell script that did this: sudo sh ./\$( pwd | sed 's,.*/,,' ).SlackBuild I have /sbin:/usr/sbin in my user's PATH so sudo works fine, but it's a PITA to pass environment variables using the above script (sudo strips them out of the env). So I made the script take arguments, and treat those as env vars (place them between 'sudo' and 'sh'). Then I added MAKEFLAGS support to it (-jN option). Then I found out some of my builds were writing outside of $TMP (due to either my own mistakes, or upstream bugs) and decided I needed a way to reliably detect that, hence the trackfs stuff. Also someone on the mailing list posted output from running a SlackBuild under bubblewrap, which prevented network access the script was trying to do. Which seems like a nifty feature to have, but bubblewrap is overkill for just running a shell script. The strace and sh -x options were added because those are things I do fairly often with buggy SlackBuilds. The elapsed time display is a nice convenience (I complain that "This thing takes 2 hours to build!" when really it's closer to 1 hour). The -c option is the only "end user" option, really. I mostly use it for building dependencies maintained by other people, not my own stuff. Basically, sbrun started out as a lazy typist's tool, and grew into something more generally useful. It's lower-level than sbotools or sbopkg, but more convenient than just executing ./*.SlackBuild. It requires no initial setup or configuration (it's a self-contained shell script), except you have to install trackfs. Since it's *not* intended to replace sbotools or sbopkg, sbrun doesn't do any of these things: - download source files. - check source file md5sums. - allow building multiple packages at once (queue files). - install/upgrade/remove packages (it *just* builds them). - dependency resolution. - sync the repo, or even have any concept of a repo (it only deals with a single SlackBuild script, in the current directory). - anything to do with .info files. Nothing about sbrun is SBo-specific, it'll work with Pat's or AlienBOB's or anyone else's scripts (which is why it's called sbrun and not sborun). Finally, a helpful hint: If you use git to push to SBo, you can't add anything to .gitignore since it's tracked by git. But you can use .git/info/exclude for the same purpose. Add build.log and maybe strace.out there. EOF } show_help() { cat <". -x Run the script with "sh -x", enables shell command tracing. -c Clean up (remove) source and package dirs after build completes. -h, --help Show short usage message (you're reading it now) and exit. -H Show long help message and exit. variable=value Passed to script as environment variables. EOF } # maybe add 2>/dev/null to these, but for now I wanna know if they fail. cleanup_nonet() { if [ "$NETWORK" = "no" ]; then umount $NONET_PATH rm -f $NONET_PATH fi } cleanup_log() { [ -n "$LOGDIR" ] && rm -rf "$LOGDIR" } cleanup_build() { [ "$CLEANUP" = "yes" ] && ( rm -rf "$TMP" ; rm -f $BUILDLOG ) } # Add a dir to $PATH, if not already present. This is actually kinda # pointless, it would work just as well to always add dirs to PATH # even if they're redundant. ensure_path() { if ! echo "$PATH" | sed 's,:,\n,g' | grep -q "^$1\$"; then #echo "$1 not in PATH, adding" export PATH="$1:$PATH" fi } # Handle ^C gracefully. TODO: the exit status should be 128 plus # the number of the signal received. The hard-coded 130 means SIGINT, # the ^C signal, but we trap other signals too. Maybe use: # http://stackoverflow.com/questions/2175647/is-it-possible-to-detect-which-trap-signal-in-bash signal_handler() { cleanup_log cleanup_nonet cleanup_build exit 130 } # Print a number of seconds as either MM:SS (if less than 1 hour) # or HH:MM:SS (if >= 1 hour). This function could almost be replaced # by: TZ=GMT printf '%(%H:%M:%S)T\n' "$1" # ...except print_hms doesn't display the hours if they're 00, and using # printf that way won't handle durations longer than 23:59:59 because # it's trying to print a time of day (24:00:00 would be 00:00:00 of the # next day). Hopefully no SlackBuild takes over a day to run, but you # never know... print_hms() { local sec="$1" hrs min hrs=$(( $sec / 3600 )) sec=$(( $sec % 3600 )) min=$(( $sec / 60 )) sec=$(( $sec % 60 )) if [ "$hrs" -gt "0" ]; then printf '%02d:%02d:%02d\n' $hrs $min $sec else printf '%02d:%02d\n' $min $sec fi } # perl-flavoured error messenger warn() { echo "$SELF:" "$@" 1>&2 echo "$SELF:" "$@" >> $BUILDLOG } # Suicide squad, attack! die() { warn "$@" exit 1 } ### main() # if these are in $PATH, 99.99% of all SBo builds will run # correctly under sudo. Or maybe even 100%. At least, I can't # remember running into problems, for quite a few years now. ensure_path /sbin ensure_path /usr/sbin ensure_path /usr/share/texmf/bin # parse -options while printf -- "$1" | grep -q ^-; do case "$1" in -j*) MAKEFLAGS="$1" ;; -n) NETWORK=yes ;; -t) TRACK=no ;; -s) TRACK=no; STRACE=-f ;; -S) TRACK=no; STRACE=-ff ;; -x) X="-x" ;; -c) CLEANUP="yes" ;; -h|-help|--help) show_help ; exit 0 ;; -H) long_help ; exit 0 ;; -*) show_help ; exit 1 ;; esac shift done # warn and die append to the log, make sure it starts out empty. # This is the only place we use tee $BUILDLOG (everything else appends). { echo -n "== $SELF starting up at " date echo -n "== directory: " pwd echo "== command: $0" "$@" echo } | tee $BUILDLOG # The easy way to remove the source and PKG dirs after the # script runs is to guarantee they'll be the only things in # $TMP. Normally, we don't create the $TMP dir, so we can # catch 'script fails to create $TMP dir' errors. But with -c, # we don't care about troubleshooting so much, and mktemp is # the way to go. if [ "$CLEANUP" = "yes" ]; then TMP="$( mktemp -d /tmp/sbrun.build.XXXXXX )" if [ -z "$TMP" ] || [ ! -d "$TMP" ]; then die "Can't create temp build dir in /tmp, bailing" fi fi # $ENV is only for showing to the user ENV="MAKEFLAGS=$MAKEFLAGS" export MAKEFLAGS TMP OUTPUT # Add rest of args to environment. The echo|cut and eval stuff allows # spaces to occur in the values. There is probably a better modern-bash # way to do this, but (to me anyway) it'll be less readable. for arg; do ENV="$ENV $arg" #eval export "$arg" # works but doesn't allow spaces var="$( echo "$arg" | cut -d= -f1 )" val="$( echo "$arg" | cut -d= -f2 )" eval "export $var='$val'" done # I wasn't gonna trap signals, but I can't break myself of the habit # of hitting ^C. # TODO: we should be trapping more signals here... # TODO: find out why trackfs sometimes segfaults when I hit ^C. No # harm done (it was already killed by SIGINT), just irritating. trap signal_handler INT TERM if [ "$TRACK" = "yes" ]; then if ! /bin/which trackfs &>/dev/null; then warn "File tracking enabled, but trackfs not installed!" die "Install system/trackfs or re-run $SELF with -t." fi LOGDIR="$( mktemp -d /tmp/sbrun.XXXXXX )" if [ -z "$LOGDIR" ] || [ ! -d "$LOGDIR" ]; then die "Can't create temp log dir in /tmp, bailing" fi LOG=$LOGDIR/log # Fun fact: trackfs uses GNU-style -- to mean "no more options", # but it's undocumented in the man page and --help output. # The readlink stuff is here in case $TMP or $OUTPUT has a symlink # in its path: trackfs will log the real path, with the links resolved. TRACKFS=\ "trackfs -l $LOG \ -I$( readlink -f "$TMP" )\\* \ -I$( readlink -f "$OUTPUT")\\* \ -I/tmp\\* \ -I/var/tmp\* \ -I/root/.ccache\\* \ --" fi if [ "$STRACE" != "" ]; then TRACKFS="strace $STRACE -ostrace.out" fi SCRIPT="./$( pwd | sed 's,.*/,,' ).SlackBuild" if [ ! -e "$SCRIPT" ]; then die "$SCRIPT not found, bailing" fi { echo "Running $SCRIPT, logging to $BUILDLOG" echo "Environment: $ENV" echo "File tracking: $TRACK" echo "Network access: $NETWORK" if [ "$STRACE" != "" ]; then echo "strace log: strace.out" fi echo } | tee -a $BUILDLOG # Set up no-network namespace. This isn't foolproof, there are probably # ways for a script being run by root to escape the namespace, but # a script that did that would hopefully never get approved by our # beloved moderators. if [ "$NETWORK" = "no" ]; then touch $NONET_PATH unshare --net=$NONET_PATH ifconfig lo 127.0.0.1 up NSENTER="nsenter --net=$NONET_PATH" fi START_TIME="$( date +%s )" # Actually run the script. Note that the 'tee' command isn't being # tracked by trackfs. eval $NSENTER $TRACKFS sh $X $SCRIPT 2>&1 | tee -a $BUILDLOG RET=$? END_TIME="$( date +%s )" echo "$SCRIPT exit status: $RET" | tee -a $BUILDLOG { echo -n "Elapsed time: " print_hms $(( $END_TIME - $START_TIME )) } | tee -a $BUILDLOG cleanup_nonet if [ "$TRACK" = "yes" ]; then if [ -s $LOG ]; then warn "WARNING: files altered outside the sandbox:" cat $LOG 1>&2 cat $LOG >> $BUILDLOG fi cleanup_log fi cleanup_build # Our return status is that of the SlackBuild. exit $RET