#!/bin/bash #emacs: -*- mode: shell-script; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- #ex: set sts=4 ts=4 sw=4 et: # Depends: apt, python, wget # Recommends: netselect # play safe set -e set -u ############ # Defaults # ############ nd_aptenable_version=0.1 nd_key_id=0x2649A5A9 nd_config_file=/etc/neurodebian/neurodebian.cfg nd_mirror_origin=http://neuro.debian.net/debian nd_mirror_default=$nd_mirror_origin # or may be AWS? # To be set by cmdline args ae_release= ae_components=software,data ae_flavor= ae_mirror=best ae_suffix= ae_verbose=1 ae_overwrite= ae_sources= ae_dry_run= ae_sudo= exe_dir=$(dirname $0) # TODOs: # - apt priority! (so we could avoid automagic upgrades etc) # - multiarch setups ae_tempdir=$(mktemp -d) trap "rm -rf \"$ae_tempdir\"" TERM INT EXIT print_verbose() { level=$1; shift if [ "$ae_verbose" -ge $level ]; then # use stderr for printing within functions stdout of which might be used echo -e "I: $*" >&2 fi } error() { code=$1; shift echo -e "E: $*" >&2 exit $code } print_version() { cat << EOT nd-aptenable $nd_aptenable_version Copyright (C) 2014 Yaroslav Halchenko Licensed under GNU Public License version 3 or later. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Written by Yaroslav Halchenko for the NeuroDebian project. EOT } eval_dry() { if [ -z "$ae_dry_run" ]; then eval "$ae_sudo $@" else echo "DRY: $@" fi } print_help() { cat << EOT Usage: nd-aptenable [options] Enables NeuroDebian repository for the current Debian or Ubuntu release. Options: -r, --release=RELEASE Name of the Debian/Ubuntu release to be used. If not specified, it is deduced from the apt-cache policy output, by taking repository of Debian or Ubuntu origin with highest priority. -f, --flavor=full|libre Which flavor of the repository should be enabled: libre -- Only main component, containing only DFSG-compliant content. full -- Includes main, contrib, and non-free. If not specified -- deduced from the output of apt-cache policy -c, --components=c1,c2,c3 Comma separated list of components to enable among: software -- primary software repository data -- data packages devel -- "overlay" of development versions (like Debians' "experimental"). Not sufficient on its own and available only from the main site If not specified -- "software,data" -m, --mirror=NAME|URL Which mirror to use. Could be a mirror code-name (as specified in /etc/neurodebian/neurodebian.cfg), or a URL. --overwrite, If apt file already present, it would not be overriden (by default). Use this option to overwrite. --suffix=SUFFIX Which suffix to add to the apt file, in case you are trying to enable multiple repositories --sources, --no-sources Either to enable deb-src lines. If none specified -- would be enabled if sources for a core package (apt) are available. -n, --dry-run Do not perform any changes -- generated configurations and commands will simply be printed to stdout -v, --verbose Enable additional progress messages. Could be used multiple times -q, --quiet Make operation quiet -- only error messages would be output -h, --help Print short description, usage summary and option list. --version Print version information and exit. Exit status: non-0 exit status in case of error. Error exit code would depend on which command has failed Examples: - Enable software and data components from the optimal (according to netselect) mirror. Some information about progress will be printed nd-aptenable - Quietly enable -devel repository for the current release, and place apt configuration into /etc/apt/sources.list.d/neurodebian.sources-devel.list nd-aptenable -q --suffix=-devel -c devel - Force sid distribution, all the components, from the Japan mirror: nd-aptenable -q --suffix=-de-sid-full -c software,data,devel -m jp EOT } get_neurodebian_cfg() { # First we try to fetch the most recent version from the github print_verbose 3 "Fetching config file from the github repository" cfgfile_temp="$ae_tempdir/neurodebian.cfg" wget -c -q -O$cfgfile_temp https://raw.githubusercontent.com/neurodebian/neurodebian/master/neurodebian.cfg \ && { echo $cfgfile_temp; } \ || { [ -e "$nd_config_file" ] && return "$nd_config_file"; } # if not -- should blow up } query_cfg_section() { config_file="$1" section="$2" python -c "from ConfigParser import SafeConfigParser as SP; cfg = SP(); cfg.read('$config_file'); print('\n'.join([' '.join(x) for x in cfg.items('$section')]))" } get_mirrors() { nd_config=`get_neurodebian_cfg` # $exe_dir/nd_querycfg -F" " --config-file="$nd_config" "mirrors" \ query_cfg_section "$nd_config" "mirrors" \ | while read mirror_name mirror_url; do # verify that url is just a url if echo "$mirror_url" | grep -v -e '^[a-z0-9:+]*://[-+_%.a-z0-9/]*$'; then print_verbose 1 "Mirror $mirror_name has 'illegit' URL: $mirror_url. Skipping" fi echo "$mirror_name $mirror_url" done } netselect_mirror() { # select "closest" mirror according to netselect. print_verbose 2 "Selecting the 'best' mirror using netselect" if ! which netselect >/dev/null; then print_verbose 1 "netselect (apt-get install netselect) needed to select the 'best' mirror was not found" print_verbose 1 "Selecting the default repository: $nd_mirror_default" echo $nd_mirror_default else get_mirrors | awk '{print $2;}' | $ae_sudo xargs netselect -D -s 1 | awk '{print $2;}' fi } get_mirror_url() { # given mirror alias -- find its url url=$(get_mirrors | awk "/^$1 /{print \$2;}") if [ -z "$url" ]; then error 9 "Cannot resolve mirror $1 to the URL" fi echo $url } get_apt_policy() { # Get apt-cache policy output in a single list for matching suites # (could be a separated with \| or , for multiple choices, e.g. # # get_apt_policy Debian,Ubuntu # or # get_apt_policy NeuroDebian suites="$1" apt-cache policy | grep -B1 -e "o=\(${suites//,/\\|}\)" | tr '\n' ' ' | sed -e 's, -- ,\n,g' | grep -v -e '-\(updates\|security\)' | sort -nr } include_component() { echo "$ae_components" | tr ',' '\n' | grep -q "^$1\$" } # # Commandline options handling # # Parse commandline options (taken from the getopt examples from the Debian util-linux package) # Note that we use `"$@"' to let each command-line parameter expand to a # separate word. The quotes around `$@' are essential! # We need CLOPTS as the `eval set --' would nuke the return value of getopt. CLOPTS=`getopt -o h,r:,m:,f:,c:,q,v,n --long help,version,quiet,verbose,mirror:,release:,flavor:,components:,suffix:,overwrite,sources,no-sources,dry-run,print-mirrors,print-best-mirror -n 'nd-aptenable' -- "$@"` if [ $? != 0 ] ; then error 2 "Problem with parsing cmdline. Terminating..." fi # Note the quotes around `$CLOPTS': they are essential! eval set -- "$CLOPTS" if [ `whoami` != "root" ]; then ae_sudo=sudo fi while true ; do case "$1" in -r|--release) shift; ae_release="$1"; shift;; -f|--flavor) shift; ae_flavor="$1"; shift;; -c|--components) shift; ae_components="$1"; shift;; -m|--mirror) shift; ae_mirror="$1"; shift;; --print-mirrors) get_mirrors; exit 0;; --print-best-mirror) netselect_mirror; exit 0;; -n|--dry-run) ae_dry_run=1; shift;; --suffix) shift; ae_suffix="$1"; shift;; --overwrite) ae_overwrite="$1"; shift;; --sources) ae_sources=1; shift;; --no-sources) ae_sources=0; shift;; -q|--quiet) ae_verbose=0; shift;; -v|--verbose) ae_verbose=$(($ae_verbose+1)); shift;; -h|--help) print_help; exit 0;; --version) print_version; exit 0;; --) shift ; break ;; *) error 1 "Internal error! ($1)";; esac done if [ $# -gt 0 ] ; then print_help >&2 exit 2 fi # Inform! [ -z "$ae_sudo" ] || print_verbose 1 "This script requires root access. Since current user is not root, sudo will be used" # # Basic system/environment knowledge # ae_output_file=/etc/apt/sources.list.d/neurodebian.sources${ae_suffix}.list apt_policy=$(get_apt_policy "Debian,Ubuntu" ) if [ -z "$ae_release" ]; then ae_release=$(echo "$apt_policy" | head -1 | sed -e 's/.*,n=\([^,]*\),.*/\1/g') fi if [ -z "$ae_flavor" ]; then ae_flavor=$(echo "$apt_policy" | grep -e ",n=$ae_release," | grep -qe 'c=\(non-free\|restricted\)' && echo "full" || echo "libre") fi # # Determine which mirror to use # # knowing mirror is not necessary for -devel available only from the main site if include_component software || include_component data; then # for now just use default if [ -z "$ae_mirror" ]; then # none specified ae_mirror_url=$nd_mirror_origin else if ! [[ "$ae_mirror" =~ ".*://.*" ]]; then case "$ae_mirror" in best) ae_mirror_url=$(netselect_mirror);; default) ae_mirror_url=$nd_mirror_default;; origin) ae_mirror_url=$nd_mirror_origin;; *) ae_mirror_url=$(get_mirror_url "$ae_mirror");; esac else ae_mirror_url="$ae_mirror" # it was some kind of a URL already fi fi fi # # Prepare APT file # case $ae_flavor in full) apt_flavor="contrib non-free";; libre) apt_flavor="";; *) error 5 "Unknown value of flavor $apt_flavor. Must be full or libre" esac if [ -z "$ae_sources" ]; then apt-cache showsrc apt >&/dev/null && ae_sources=1 || ae_sources=0 fi if [ $ae_sources -eq 0 ]; then sources_comment="#" else sources_comment="" fi apt_list= if include_component software; then apt_list+=" # NeuroDebian software repository deb $ae_mirror_url $ae_release main $apt_flavor ${sources_comment}deb-src $ae_mirror_url $ae_release main $apt_flavor " fi if include_component data; then apt_list+=" # NeuroDebian data repository deb $ae_mirror_url data main $apt_flavor ${sources_comment}deb-src $ae_mirror_url data main $apt_flavor " fi if include_component devel; then apt_list+=" # NeuroDebian -devel repository deb http://neuro.debian.net/debian-devel $ae_release main $apt_flavor ${sources_comment}deb-src http://neuro.debian.net/debian-devel $ae_release main $apt_flavor " fi if [ -e "$ae_output_file" ] && [ -z "$ae_overwrite" ]; then if diff "$ae_output_file" <(echo "$apt_list") | grep -q .; then # error 3 print_verbose 1 "File $ae_output_file already exists, containing:\n`cat \"$ae_output_file\"`\n\nI: New configuration is different:\n$apt_list" if get_apt_policy NeuroDebian >/dev/null; then print_verbose 1 "NeuroDebian repositories are already available, thus skipping the rest." print_verbose 1 "Rerun with --overwrite if you would like to reconfigure." exit 0 else print_verbose 1 "NeuroDebian configuration is found but not yet available -- continuing with new configuration." fi else print_verbose 1 "New configuration is identical to existing and NeuroDebian repository is already enabled." print_verbose 1 "Skiping the rest. Rerun with --overwrite if you would like to reconfigure." exit 0 fi else print_verbose 1 "Generating $ae_output_file" if [ -z "$ae_dry_run" ]; then echo "$apt_list" | $ae_sudo bash -c "cat - >| '$ae_output_file'" else echo "DRY:" echo "$apt_list" fi fi # # Assure present archive GPG key for APT system # # Figure out if key needs to be imported (if ran within package, # should already be there due to neurodebian-keyring package) if LANG=C eval $ae_sudo apt-key export $nd_key_id 2>&1 1>/dev/null | grep -qe "nothing exported"; then print_verbose 1 "Fetching the key from the server" eval_dry apt-key adv --recv-keys --keyserver pgp.mit.edu $nd_key_id fi # # Finalizing (apt-get update etc) # print_verbose 1 "Updating APT listings, might take a few minutes" if [ -z "$ae_dry_run" ]; then apt_logfile="$ae_tempdir/apt.log" $ae_sudo apt-get update 1>"$apt_logfile" 2>&1 \ && rm -f "$apt_logfile" \ || { cat "$apt_logfile" error 5 "E: Update failed with exit code $? (above output logged into $apt_logfile)." } else eval_dry apt-get update fi if [ "$ae_verbose" -ge 2 ]; then print_verbose 2 "Currently enabled NeuroDebian suites/mirrors:" get_apt_policy NeuroDebian fi