]> git.donarmstrong.com Git - neurodebian.git/blob - tools/nd-configurerepo
longer key id, unified env variables
[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 overriden (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) ]]; then
343         print_verbose 1 "Package $pkg providing command $cmd is N/A. Skipping"
344         return 10;
345     fi
346     if echo "$apt_cache" | grep -q '^\s*\*\*\*'; then
347         print_verbose 1 "WARNING -- command $cmd is N/A but package $pkg is claimed to be installed"
348         [ -z "$fail" ] && return 11 || error $fail "Command $cmd is required to proceed"
349     fi
350     if [ "$ae_install" = "1" ]; then
351         print_verbose 1 "Installing $pkg package to get $cmd command"
352         eval_dry DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg"
353         return
354     else
355         print_verbose 1 "Command $cmd (from package $pkg) is N/A."
356         print_verbose 1 "Use with --install to get all necessary packages installed automatically"
357         [ -z "$fail" ] && return 12 || error $fail "Command $cmd is required to proceed"
358     fi
359 }
360
361 # if it was requested -- return without doing anything
362 [ -z "$ae_defun_only" ] || return
363
364 #
365 # Commandline options handling
366 #
367
368 # Parse commandline options (taken from the getopt examples from the Debian util-linux package)
369 # Note that we use `"$@"' to let each command-line parameter expand to a
370 # separate word. The quotes around `$@' are essential!
371 # We need CLOPTS as the `eval set --' would nuke the return value of getopt.
372 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' -- "$@"`
373
374 if [ $? != 0 ] ; then
375   error 2 "Problem with parsing cmdline.  Terminating..."
376 fi
377
378 # Note the quotes around `$CLOPTS': they are essential!
379 eval set -- "$CLOPTS"
380
381 if [ `whoami` != "root" ]; then
382     ae_sudo=sudo
383 fi
384
385 while true ; do
386   case "$1" in
387           -r|--release) shift; ae_release="$1"; shift;;
388           -f|--flavor) shift;  ae_flavor="$1"; shift;;
389           -c|--components) shift; ae_components="$1"; shift;;
390       -m|--mirror) shift;  ae_mirror="$1"; shift;;
391          --print-mirrors)  get_mirrors; exit 0;;
392          --print-best-mirror)  netselect_mirror; exit 0;;
393          --print-releases)  get_releases; exit 0;;
394          --print-release)  do_print_release=1; shift;;
395          --print-flavor)  do_print_flavor=1; shift;;
396       -n|--dry-run)        ae_dry_run=1; shift;;
397          --suffix) shift;  ae_suffix="$1"; shift;;
398          --overwrite)      ae_overwrite="$1"; shift;;
399          --do-not-update)  ae_update=""; shift;;
400          --sources)        ae_sources=1; shift;;
401          --no-sources)     ae_sources=0; shift;;
402          --install)        ae_install=1; shift;;
403           -q|--quiet)          ae_verbose=0; shift;;
404           -v|--verbose)        ae_verbose=$(($ae_verbose+1)); shift;;
405           -h|--help) print_help; exit 0;;
406           --version) print_version; exit 0;;
407           --) shift ; break ;;
408           *) error 1 "Internal error! ($1)";;
409   esac
410 done
411
412
413 if [ $# -gt 0 ] ; then
414     print_help >&2
415     exit 2
416 fi
417
418 # Inform!
419 [ -z "$ae_sudo" ] || print_verbose 1 "This script requires root access.  Since current user is not root, sudo will be used"
420
421 #
422 # Basic system/environment knowledge
423 #
424
425 ae_output_file=/etc/apt/sources.list.d/neurodebian.sources${ae_suffix}.list
426
427 apt_policy=$(get_apt_policy "Debian,Ubuntu" )
428
429 if [ -z "$ae_release" ]; then
430     ae_release=$(echo "$apt_policy" | head -1 | sed -e 's/.*,n=\([^,]*\),.*/\1/g')
431     if [ ! -z "$do_print_release" ]; then
432         echo $ae_release
433         exit 0
434     fi
435 fi
436
437 if [ -z "$ae_flavor" ]; then
438     ae_flavor=$(echo "$apt_policy" | grep -e ",n=$ae_release," | grep -qe 'c=\(non-free\|multiverse\)' && echo "full" || echo "libre")
439     if [ ! -z "$do_print_flavor" ]; then
440         echo $ae_flavor
441         exit 0
442     fi
443 fi
444
445 #
446 # Determine which mirror to use
447 #
448
449 # knowing mirror is not necessary for -devel available only from the main site
450 if is_component_included software || is_component_included data; then
451     # for now just use default
452     if [ -z "$ae_mirror" ]; then # none specified
453         ae_mirror_url=$nd_mirror_origin
454     else
455         if ! [[ "$ae_mirror" =~ .*://.* ]]; then
456             case "$ae_mirror" in
457                 best)    ae_mirror_url=$(netselect_mirror);;
458                 default) ae_mirror_url=$nd_mirror_default;;
459                 origin)  ae_mirror_url=$nd_mirror_origin;;
460                 *)       ae_mirror_url=$(get_mirror_url "$ae_mirror");;
461             esac
462         else
463             ae_mirror_url="$ae_mirror" # it was some kind of a URL already
464         fi
465     fi
466 fi
467
468 #
469 # Prepare APT file
470 #
471
472 case $ae_flavor in
473  full)  apt_flavor="contrib non-free";;
474  libre) apt_flavor="";;
475  *) error 5 "Unknown value of flavor $apt_flavor.  Must be full or libre"
476 esac
477
478 if [ -z "$ae_sources" ]; then
479     ae_sources=$(is_sources_enabled)
480 fi
481
482 if [ $ae_sources -eq 0 ]; then
483     sources_comment="#"
484 else
485     sources_comment=""
486 fi
487
488 apt_list=
489
490 if is_component_included software; then
491     apt_list+="
492 # NeuroDebian software repository
493 deb     $ae_mirror_url $ae_release main $apt_flavor
494 ${sources_comment}deb-src $ae_mirror_url $ae_release main $apt_flavor
495 "
496 fi
497
498 if is_component_included data; then
499     apt_list+="
500 # NeuroDebian data repository
501 deb     $ae_mirror_url data main $apt_flavor
502 ${sources_comment}deb-src $ae_mirror_url data main $apt_flavor
503 "
504 fi
505
506 if is_component_included devel; then
507     apt_list+="
508 # NeuroDebian -devel repository
509 deb     http://neuro.debian.net/debian-devel $ae_release main $apt_flavor
510 ${sources_comment}deb-src http://neuro.debian.net/debian-devel $ae_release main $apt_flavor
511 "
512 fi
513
514 if [ -e "$ae_output_file" ] && [ -z "$ae_overwrite" ]; then
515     if diff "$ae_output_file" <(echo "$apt_list") | grep -q .; then
516         # error 3
517         print_verbose 1 "File $ae_output_file already exists, containing:\n`cat \"$ae_output_file\"`\n\nI: New configuration is different:\n$apt_list"
518         if get_apt_policy NeuroDebian >/dev/null; then
519             print_verbose 1 "NeuroDebian repositories are already available, thus skipping the rest."
520             print_verbose 1 "Rerun with --overwrite if you would like to reconfigure."
521             exit 0
522         else
523             print_verbose 1 "NeuroDebian configuration is found but not yet available -- continuing with new configuration."
524         fi
525     else
526         print_verbose 1 "New configuration is identical to existing and NeuroDebian repository is already enabled."
527         print_verbose 1 "Skiping the rest. Rerun with --overwrite if you would like to reconfigure."
528         exit 0
529     fi
530 else
531     print_verbose 1 "Generating $ae_output_file"
532     if [ -z "$ae_dry_run" ]; then
533         echo "$apt_list" | $ae_sudo bash -c "cat - >| '$ae_output_file'"
534     else
535         echo "DRY:"
536         echo "$apt_list"
537     fi
538 fi
539
540
541 #
542 # Assure present archive GPG key for APT system
543 #
544
545 # Figure out if key needs to be imported (if ran within package,
546 # should already be there due to neurodebian-keyring package)
547 if LANG=C eval $ae_sudo apt-key export $nd_key_id 2>&1 1>/dev/null | grep -qe "nothing exported"; then
548     print_verbose 1 "Fetching the key from the server"
549     eval_dry apt-key adv --recv-keys --keyserver pgp.mit.edu $nd_key_id
550 fi
551
552
553 #
554 # Finalizing (apt-get update etc)
555 #
556
557 if [ ! -z "$ae_update" ]; then
558     print_verbose 1 "Updating APT listings, might take a few minutes"
559     if [ -z "$ae_dry_run" ]; then
560         apt_logfile="$ae_tempdir/apt.log"
561         $ae_sudo apt-get update 1>"$apt_logfile" 2>&1 \
562             && rm -f "$apt_logfile" \
563             || {
564                  apt_log=$(cat "$apt_logfile")
565                  echo "$apt_log"
566                  if echo "$apt_log" | grep -q "Malformed line [0-9]* in source list $ae_output_file"; then
567                      $ae_sudo mv "${ae_output_file}" "${ae_output_file}-failed.disabled"
568                      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"
569                  fi
570                  error 5 "Update failed with exit code $? (above output logged into $apt_logfile)."
571                  }
572     else
573         eval_dry apt-get update
574     fi
575 else
576     print_verbose 1 "apt-get update  was not run. Please run to take an effect of changes"
577 fi
578
579 if [ "$ae_verbose" -ge 2 ]; then
580     print_verbose 2 "Currently enabled NeuroDebian suites/mirrors:"
581     get_apt_policy NeuroDebian
582 fi