]> git.donarmstrong.com Git - neurodebian.git/blob - tools/nd-configurerepo
Also for stats report which repo and which job number use our setup
[neurodebian.git] / tools / nd-configurerepo
1 #!/bin/bash
2 #emacs: -*- mode: shell-script; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
3 #ex: set sts=4 ts=4 sw=4 et:
4
5 # Depends:    apt (assumed to be present), python, wget
6 # Recommends: netselect
7
8 # play safe
9 set -e
10 set -u
11
12 ############
13 # Defaults #
14 ############
15
16 nd_aptenable_version=0.1
17
18 nd_key_id=0xA5D32F012649A5A9
19 nd_config_url=https://raw.githubusercontent.com/neurodebian/neurodebian/master/neurodebian.cfg
20 nd_config_file=/etc/neurodebian/neurodebian.cfg
21 nd_mirror_origin=http://neuro.debian.net/debian
22 nd_mirror_default=$nd_mirror_origin # or may be AWS?
23
24 # To be set by cmdline args or via env variables with prefix NEURODEBIAN_
25 ae_release=${NEURODEBIAN_RELEASE:-}
26 ae_components=${NEURODEBIAN_COMPONENTS:-software,data}
27 ae_flavor=${NEURODEBIAN_FLAVOR:-}
28 ae_mirror=${NEURODEBIAN_MIRROR:-best}
29 ae_suffix=${NEURODEBIAN_SUFFIX:-}
30 ae_verbose=${NEURODEBIAN_VERBOSE:-1}
31 ae_overwrite=${NEURODEBIAN_OVERWRITE:-}
32 ae_sources=${NEURODEBIAN_SOURCES:-}
33 ae_install=${NEURODEBIAN_INSTALL:-}
34 ae_update=${NEURODEBIAN_UPDATE:-1}
35 ae_dry_run=${NEURODEBIAN_DRY_RUN:-}
36 ae_defun_only=${NEURODEBIAN_DEFUN_ONLY:-} # mode to source this file as a "library"
37
38 ae_sudo=
39 exe_dir=$(dirname $0)
40 do_print_release=
41 do_print_flavor=
42
43 # TODOs:
44 # - apt priority! (so we could avoid automagic upgrades etc)
45 # - multiarch setups
46
47 if [ -z "${NEURODEBIAN_TEMPDIR:-}" ]; then
48     ae_tempdir=$(mktemp -d)
49     trap "rm -rf \"$ae_tempdir\"" TERM INT EXIT
50 else
51     # reuse the same directory/fetched configuration if was specified
52     ae_tempdir="${NEURODEBIAN_TEMPDIR:-}"
53 fi
54
55
56 nd_config_file_fresh="$ae_tempdir/neurodebian.cfg"
57
58 print_verbose()
59 {
60     level=$1; shift
61         if [ "$ae_verbose" -ge $level ]; then
62         # use stderr for printing within functions stdout of which might be used
63         echo -n "I: " >&2
64         i=1; while [ $i -lt $level ]; do echo -ne " ">&2; i=$(($i+1)); done
65         echo -e "$*" >&2
66     fi
67 }
68
69 error()
70 {
71     code=$1; shift
72         echo -e "E: $*" >&2
73     exit $code
74 }
75
76 print_version()
77 {
78     cat << EOT
79 nd-configurerepo $nd_aptenable_version
80
81 Copyright (C) 2014 Yaroslav Halchenko <debian@onerussian.com>
82
83 Licensed under GNU Public License version 3 or later.
84 This is free software; see the source for copying conditions.  There is NO
85 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
86
87 Written by Yaroslav Halchenko for the NeuroDebian project.
88
89 EOT
90 }
91
92 eval_dry()
93 {
94     if [ -z "$ae_dry_run" ]; then
95         if eval "$ae_sudo $@" 1>|"$ae_tempdir/eval.log" 2>&1; then
96             rm "$ae_tempdir/eval.log"
97         else
98             error $? "Command $@ failed with exit code $?.  Output was: `cat $ae_tempdir/eval.log`"
99         fi
100     else
101         echo "DRY: $@" >&2
102     fi
103 }
104
105 print_help()
106 {
107     cat << EOT
108
109 Usage:  nd-configurerepo [options]
110
111 Enables NeuroDebian repository for the current Debian or Ubuntu release.
112
113 Options:
114
115   -r, --release=RELEASE
116     Name of the Debian/Ubuntu release to be used. If not specified,
117     it is deduced from the 'apt-cache policy' output, by taking repository
118     of Debian or Ubuntu origin with highest priority.
119
120   --print-releases
121     Print a list of releases present in NeuroDebian repository.
122
123   --print-release
124     Print the release deduced from the output of apt-cache policy.
125
126   -f, --flavor=full|libre
127     Which flavor of the repository should be enabled:
128
129       libre  Only 'main' component, containing only DFSG-compliant content.
130       full   Includes 'main', 'contrib', and 'non-free'.
131
132     If not specified -- deduced from the output of apt-cache policy.
133
134   --print-flavor
135     Print the flavor deduced from the output of apt-cache policy.
136
137   -c, --components=c1,c2,c3
138     Comma separated list of components to enable among:
139
140      software  primary software repository
141      data      data packages
142      devel     "overlay" of development versions (like Debians' "experimental").
143                Not sufficient on its own and available only from the main site
144
145     If not specified -- "software,data"
146
147   -m, --mirror=NAME|URL
148     Which mirror to use. Could be a mirror code-name (as specified in
149     /etc/neurodebian/neurodebian.cfg), or a URL.
150
151   --print-mirrors
152     Return a list (with abbreviation) of known NeuroDebian mirrors.
153
154   --overwrite,
155     If apt file already present, it would not be overridden (by default).
156     Use this option to overwrite.
157
158   --suffix=SUFFIX
159     Which suffix to add to the apt file, in case you are trying to enable
160     multiple repositories
161
162   --sources, --no-sources
163     Either to enable deb-src lines. If none specified -- would be enabled if
164     sources for a core package (apt) are available.
165
166   -n, --dry-run
167     Do not perform any changes -- generated configurations and commands will
168     simply be printed to stdout
169
170   --install
171     If found absent, all necessary tools (wget, netselect) if available will
172     be apt-get installed
173
174   -v, --verbose
175     Enable additional progress messages. Could be used multiple times
176
177   -q, --quiet
178     Make operation quiet -- only error messages would be output
179
180   -h, --help
181     Print short description, usage summary and option list.
182
183   --version
184     Print version information and exit.
185
186 Exit status:
187
188   non-0 exit status in case of error.
189   Error exit code would depend on which command has failed.
190
191 Examples:
192   nd-configurerepo
193     Enable software and data components from the optimal (according to
194     netselect) mirror.  Some information about progress will be printed
195
196   nd-configurerepo -q --suffix=-devel -c devel
197     Quietly enable -devel repository for the current release, and place apt
198     configuration into /etc/apt/sources.list.d/neurodebian.sources-devel.list
199
200   nd-configurerepo -q --suffix=-de-sid-full -c software,data,devel -m jp
201     Force sid distribution, all the components, from the Japan mirror
202 EOT
203 }
204
205 get_neurodebian_cfg()
206 {
207     if [ -s "$nd_config_file_fresh" ]; then
208         print_verbose 3 "Config file $nd_config_file_fresh exists -- not fetching"
209         echo "$nd_config_file_fresh"
210         return 0
211     fi
212     # First we try to fetch the most recent version from the github
213     print_verbose 3 "Fetching config file from the github repository"
214     assure_command_from_package wget wget 1
215     wget --no-check-certificate -c -q -O$nd_config_file_fresh \
216         $nd_config_url \
217         && { echo "$nd_config_file_fresh"; } \
218         || { [ -e "$nd_config_file" ] \
219              && echo "$nd_config_file" \
220              || error 10 "Neither could fetch $nd_config_url, nor found $nd_config_file"; }
221 }
222
223 query_cfg_section()
224 {
225     config_file="$1"
226     section="$2"
227     print_verbose 3 "Querying config $config_file section $section"
228     assure_command_from_package python python-minimal 1
229     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')]))"
230 }
231
232 get_mirrors()
233 {
234     nd_config=`get_neurodebian_cfg`
235 #    $exe_dir/nd_querycfg -F" " --config-file="$nd_config" "mirrors" \
236     n=""
237     query_cfg_section "$nd_config" "mirrors" \
238     | while read mirror_name mirror_url; do
239         # verify that url is just a url
240         if echo "$mirror_url" | grep -v -e '^[a-z0-9:+]*://[-+_%.a-z0-9/]*$'; then
241             print_verbose 1 "Mirror $mirror_name has 'illegit' URL: $mirror_url.  Skipping"
242         fi
243         [ -z "$n" ] || echo -ne "${ND_IFS:-\n}"; n+=1
244         echo -n "$mirror_name $mirror_url"
245     done
246 }
247
248 get_releases()
249 {
250     nd_config=`get_neurodebian_cfg`
251     n=""
252     query_cfg_section "$nd_config" "release files" \
253     | while read release_name release_url; do
254         # verify that url is just a url
255         if [ "$release_name" = "data" ]; then
256             # skip data
257             continue
258         fi
259         [ -z "$n" ] || echo -ne "${ND_IFS:-\n}"; n+=1
260         echo -n "$release_name"
261     done
262 }
263
264 get_package_version()
265 {
266     pkg_version=$(apt-cache policy "$1" | awk '/^ *Installed:/{print $2;}')
267     [ "$pkg_version" != '(none)' ] || pkg_version=''
268     echo "$pkg_version"
269 }
270
271 netselect_mirror() {
272     # select "closest" mirror according to netselect.
273     print_verbose 2 "Selecting the 'best' mirror using netselect"
274     assure_command_from_package netselect
275     if ! which netselect >&/dev/null; then
276         print_verbose 1 "netselect (apt-get install netselect) needed to select the 'best' mirror was not found"
277         print_verbose 1 "Selecting the default repository: $nd_mirror_default"
278         echo $nd_mirror_default
279     else
280         # squeeze version doesn't have -D yet to force output of the URL not IP, but for our mirrors ATM it shouldn't matter
281         netselect_opts="-s 1"
282         netselect_version="$(get_package_version netselect)"
283         if dpkg --compare-versions "$netselect_version" ge 0.3.ds1-17; then
284             netselect_opts+=" -D"
285         fi
286         if dpkg --compare-versions "$netselect_version" ge 0.3.ds1-15; then
287             netselect_opts+=" -I"
288         fi
289         best_mirror=$(get_mirrors | awk '{print $2;}' | eval $ae_sudo xargs netselect $netselect_opts | awk '{print $2;}')
290         if [ -z "$best_mirror" ]; then
291             print_verbose 1 "Failed to select mirror using netselect. Selecting default one ($nd_mirror_default)"
292             echo "$nd_mirror_default"
293         else
294             print_verbose 2 "Best mirror: $best_mirror"
295             echo $best_mirror
296         fi
297     fi
298 }
299
300 get_mirror_url()
301 {
302     # given mirror alias -- find its url
303     url=$(get_mirrors | awk "/^$1 /{print \$2;}")
304     if [ -z "$url" ]; then
305         error 9 "Cannot resolve mirror $1 to the URL"
306     fi
307     echo $url
308 }
309
310 get_apt_policy()
311 {
312     # Get apt-cache policy output in a single list for matching suites
313     # (could be a separated with \| or , for multiple choices, e.g.
314     #
315     # get_apt_policy Debian,Ubuntu
316     # or
317     # get_apt_policy NeuroDebian
318     suites="$1"
319     $ae_sudo apt-cache policy | grep -B1 -e "o=\(${suites//,/\\|}\)" | tr '\n' ' ' | sed -e 's, -- ,\n,g' | grep -v -e '-\(updates\|security\)' | sort -nr
320 }
321
322 is_component_included()
323 {
324     echo "$ae_components" | tr ',' '\n' | grep -q "^$1\$"
325 }
326
327 is_sources_enabled()
328 {
329     apt-cache showsrc apt >&/dev/null && echo 1 || echo 0
330 }
331
332 assure_command_from_package()
333 {
334     cmd=$1
335     pkg=${2:-$cmd}
336     fail=${3:-}
337
338     which "$cmd" >&/dev/null && return 0
339
340     # if absent -- check availability of the package
341     apt_cache=$(LANG=C apt-cache policy "$pkg" 2>&1)
342     if [[ "$apt_cache" =~ Unable\ to\ locate\ package ]] || [[ "$apt_cache" =~ Candidate:\ (none) ]] \
343        || [[ "$apt_cache" =~ is\ not\ available ]] ; then
344         print_verbose 1 "Package $pkg providing command $cmd is N/A. Skipping"
345         return 10;
346     fi
347     if echo "$apt_cache" | grep -q '^\s*\*\*\*'; then
348         print_verbose 1 "WARNING -- command $cmd is N/A but package $pkg is claimed to be installed"
349         [ -z "$fail" ] && return 11 || error $fail "Command $cmd is required to proceed"
350     fi
351     if [ "$ae_install" = "1" ]; then
352         print_verbose 1 "Installing $pkg package to get $cmd command"
353         eval_dry DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg"
354         return
355     else
356         print_verbose 1 "Command $cmd (from package $pkg) is N/A."
357         print_verbose 1 "Use with --install to get all necessary packages installed automatically"
358         [ -z "$fail" ] && return 12 || error $fail "Command $cmd is required to proceed"
359     fi
360 }
361
362 # if it was requested -- return without doing anything
363 [ -z "$ae_defun_only" ] || return
364
365 #
366 # Commandline options handling
367 #
368
369 # Parse commandline options (taken from the getopt examples from the Debian util-linux package)
370 # Note that we use `"$@"' to let each command-line parameter expand to a
371 # separate word. The quotes around `$@' are essential!
372 # We need CLOPTS as the `eval set --' would nuke the return value of getopt.
373 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,install,dry-run,do-not-update,print-releases,print-release,print-mirrors,print-best-mirror,print-flavor -n 'nd-configurerepo' -- "$@"`
374
375 if [ $? != 0 ] ; then
376   error 2 "Problem with parsing cmdline.  Terminating..."
377 fi
378
379 # Note the quotes around `$CLOPTS': they are essential!
380 eval set -- "$CLOPTS"
381
382 if [ `whoami` != "root" ]; then
383     ae_sudo=sudo
384 fi
385
386 while true ; do
387   case "$1" in
388           -r|--release) shift; ae_release="$1"; shift;;
389           -f|--flavor) shift;  ae_flavor="$1"; shift;;
390           -c|--components) shift; ae_components="$1"; shift;;
391       -m|--mirror) shift;  ae_mirror="$1"; shift;;
392          --print-mirrors)  get_mirrors; exit 0;;
393          --print-best-mirror)  netselect_mirror; exit 0;;
394          --print-releases)  get_releases; exit 0;;
395          --print-release)  do_print_release=1; shift;;
396          --print-flavor)  do_print_flavor=1; shift;;
397       -n|--dry-run)        ae_dry_run=1; shift;;
398          --suffix) shift;  ae_suffix="$1"; shift;;
399          --overwrite)      ae_overwrite="$1"; shift;;
400          --do-not-update)  ae_update=""; shift;;
401          --sources)        ae_sources=1; shift;;
402          --no-sources)     ae_sources=0; shift;;
403          --install)        ae_install=1; shift;;
404           -q|--quiet)          ae_verbose=0; shift;;
405           -v|--verbose)        ae_verbose=$(($ae_verbose+1)); shift;;
406           -h|--help) print_help; exit 0;;
407           --version) print_version; exit 0;;
408           --) shift ; break ;;
409           *) error 1 "Internal error! ($1)";;
410   esac
411 done
412
413
414 if [ $# -gt 0 ] ; then
415     print_help >&2
416     exit 2
417 fi
418
419 # Inform!
420 [ -z "$ae_sudo" ] || print_verbose 1 "This script requires root access.  Since current user is not root, sudo will be used"
421
422 #
423 # Basic system/environment knowledge
424 #
425
426 ae_output_file=/etc/apt/sources.list.d/neurodebian.sources${ae_suffix}.list
427
428 apt_policy=$(get_apt_policy "Debian,Ubuntu" )
429
430 if [ -z "$ae_release" ]; then
431     ae_release=$(echo "$apt_policy" | head -1 | sed -e 's/.*,n=\([^,]*\),.*/\1/g')
432     if [ ! -z "$do_print_release" ]; then
433         echo $ae_release
434         exit 0
435     fi
436 fi
437
438 if [ -z "$ae_flavor" ]; then
439     ae_flavor=$(echo "$apt_policy" | grep -e ",n=$ae_release," | grep -qe 'c=\(non-free\|multiverse\)' && echo "full" || echo "libre")
440     if [ ! -z "$do_print_flavor" ]; then
441         echo $ae_flavor
442         exit 0
443     fi
444 fi
445
446 #
447 # Determine which mirror to use
448 #
449
450 # knowing mirror is not necessary for -devel available only from the main site
451 if is_component_included software || is_component_included data; then
452     # for now just use default
453     if [ -z "$ae_mirror" ]; then # none specified
454         ae_mirror_url=$nd_mirror_origin
455     else
456         if ! [[ "$ae_mirror" =~ .*://.* ]]; then
457             case "$ae_mirror" in
458                 best)    ae_mirror_url=$(netselect_mirror);;
459                 default) ae_mirror_url=$nd_mirror_default;;
460                 origin)  ae_mirror_url=$nd_mirror_origin;;
461                 *)       ae_mirror_url=$(get_mirror_url "$ae_mirror");;
462             esac
463         else
464             ae_mirror_url="$ae_mirror" # it was some kind of a URL already
465         fi
466     fi
467 fi
468
469 #
470 # Prepare APT file
471 #
472
473 case $ae_flavor in
474  full)  apt_flavor="contrib non-free";;
475  libre) apt_flavor="";;
476  *) error 5 "Unknown value of flavor $apt_flavor.  Must be full or libre"
477 esac
478
479 if [ -z "$ae_sources" ]; then
480     ae_sources=$(is_sources_enabled)
481 fi
482
483 if [ $ae_sources -eq 0 ]; then
484     sources_comment="#"
485 else
486     sources_comment=""
487 fi
488
489 apt_list=
490
491 if is_component_included software; then
492     apt_list+="
493 # NeuroDebian software repository
494 deb     $ae_mirror_url $ae_release main $apt_flavor
495 ${sources_comment}deb-src $ae_mirror_url $ae_release main $apt_flavor
496 "
497 fi
498
499 if is_component_included data; then
500     apt_list+="
501 # NeuroDebian data repository
502 deb     $ae_mirror_url data main $apt_flavor
503 ${sources_comment}deb-src $ae_mirror_url data main $apt_flavor
504 "
505 fi
506
507 if is_component_included devel; then
508     apt_list+="
509 # NeuroDebian -devel repository
510 deb     http://neuro.debian.net/debian-devel $ae_release main $apt_flavor
511 ${sources_comment}deb-src http://neuro.debian.net/debian-devel $ae_release main $apt_flavor
512 "
513 fi
514
515 if [ -e "$ae_output_file" ] && [ -z "$ae_overwrite" ]; then
516     if diff "$ae_output_file" <(echo "$apt_list") | grep -q .; then
517         # error 3
518         print_verbose 1 "File $ae_output_file already exists, containing:\n`cat \"$ae_output_file\"`\n\nI: New configuration is different:\n$apt_list"
519         if get_apt_policy NeuroDebian >/dev/null; then
520             print_verbose 1 "NeuroDebian repositories are already available, thus skipping the rest."
521             print_verbose 1 "Rerun with --overwrite if you would like to reconfigure."
522             exit 0
523         else
524             print_verbose 1 "NeuroDebian configuration is found but not yet available -- continuing with new configuration."
525         fi
526     else
527         print_verbose 1 "New configuration is identical to existing and NeuroDebian repository is already enabled."
528         print_verbose 1 "Skiping the rest. Rerun with --overwrite if you would like to reconfigure."
529         exit 0
530     fi
531 else
532     print_verbose 1 "Generating $ae_output_file"
533     if [ -z "$ae_dry_run" ]; then
534         echo "$apt_list" | $ae_sudo bash -c "cat - >| '$ae_output_file'"
535     else
536         echo "DRY:"
537         echo "$apt_list"
538     fi
539 fi
540
541
542 #
543 # Assure present archive GPG key for APT system
544 #
545
546 # Figure out if key needs to be imported (if ran within package,
547 # should already be there due to neurodebian-archive-keyring package)
548 if LANG=C eval $ae_sudo apt-key export $nd_key_id 2>&1 1>/dev/null | grep -qe "nothing exported"; then
549     print_verbose 1 "Fetching the key from the server"
550     eval_dry apt-key adv --recv-keys --keyserver pgp.mit.edu $nd_key_id
551 fi
552
553
554 #
555 # Finalizing (apt-get update etc)
556 #
557
558 if [ ! -z "$ae_update" ]; then
559     print_verbose 1 "Updating APT listings, might take a few minutes"
560     if [ -z "$ae_dry_run" ]; then
561         apt_logfile="$ae_tempdir/apt.log"
562         $ae_sudo apt-get update 1>"$apt_logfile" 2>&1 \
563             && rm -f "$apt_logfile" \
564             || {
565                  apt_log=$(cat "$apt_logfile")
566                  echo "$apt_log"
567                  if echo "$apt_log" | grep -q "Malformed line [0-9]* in source list $ae_output_file"; then
568                      $ae_sudo mv "${ae_output_file}" "${ae_output_file}-failed.disabled"
569                      error 6 "Update failed to possible errorneous APT listing file produced by this script.  Generated $ae_output_file renamed to ${ae_output_file}-failed.disabled to not interfer"
570                  fi
571                  error 5 "Update failed with exit code $? (above output logged into $apt_logfile)."
572                  }
573     else
574         eval_dry apt-get update
575     fi
576 else
577     print_verbose 1 "apt-get update  was not run. Please run to take an effect of changes"
578 fi
579
580 if [ "$ae_verbose" -ge 2 ]; then
581     print_verbose 2 "Currently enabled NeuroDebian suites/mirrors:"
582     get_apt_policy NeuroDebian
583 fi