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