]> git.donarmstrong.com Git - neurodebian.git/blob - tools/nd-aptenable
ENH: choose the best mirror using netselect and other enhancements
[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 # play safe
6 set -e
7 set -u
8
9 ############
10 # Defaults #
11 ############
12
13 nd_aptenable_version=0.1
14
15 nd_key_id=0x2649A5A9
16 nd_config_file=/etc/neurodebian/neurodebian.cfg
17 nd_mirror_origin=http://neuro.debian.net/debian
18 nd_mirror_default=$nd_mirror_origin # or may be AWS?
19
20 # To be set by cmdline args
21 ae_release=
22 ae_components=software,data
23 ae_flavor=
24 ae_mirror=best
25 ae_suffix=
26 ae_verbose=1
27 ae_overwrite=
28 ae_sources=
29 ae_dry_run=
30
31 ae_sudo=
32 exe_dir=$(dirname $0)
33
34 # TODOs:
35 # - apt priority! (so we could avoid automagic upgrades etc)
36 # - multiarch setups
37
38 ae_tempdir=$(mktemp -d)
39 trap "rm -rf \"$ae_tempdir\"" TERM INT EXIT
40
41
42 print_verbose()
43 {
44     level=$1; shift
45         if [ "$ae_verbose" -ge $level ]; then
46         # use stderr for printing within functions stdout of which might be used
47         echo -e "I: $*" >&2
48     fi
49 }
50
51 error()
52 {
53     code=$1; shift
54         echo -e "E: $*" >&2
55     exit $code
56 }
57
58 print_version()
59 {
60     cat << EOT
61 nd-aptenable $nd_aptenable_version
62
63 Copyright (C) 2014 Yaroslav Halchenko <debian@onerussian.com>
64
65 Licensed under GNU Public License version 3 or later.
66 This is free software; see the source for copying conditions.  There is NO
67 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
68
69 Written by Yaroslav Halchenko for the NeuroDebian project.
70
71 EOT
72 }
73
74 eval_dry()
75 {
76     if [ -z "$ae_dry_run" ]; then
77         eval "$ae_sudo $@"
78     else
79         echo "DRY: $@"
80     fi
81 }
82
83 print_help()
84 {
85     cat << EOT
86
87 Usage:  nd-aptenable [options]
88
89 Enables NeuroDebian repository for the current Debian or Ubuntu release.
90
91 Options:
92
93   -r, --release=RELEASE
94     Name of the Debian/Ubuntu release to be used.  If not specified,
95     it is deduced from the  apt-cache policy  output, by taking repository
96     of Debian or Ubuntu origin with highest priority.
97
98   -f, --flavor=full|libre
99     Which flavor of the repository should be enabled:
100      libre -- Only  main  component, containing only DFSG-compliant content.
101      full -- Includes main, contrib, and non-free.
102     If not specified -- deduced from the output of apt-cache policy
103
104   -c, --components=c1,c2,c3
105     Comma separated list of components to enable among:
106      software -- primary software repository
107      data -- data packages
108      devel -- "overlay" of development versions (like Debian "experimental").
109               Not sufficient on its own and available only from the main site
110     If not specified -- "software,data"
111
112   -m, --mirror=NAME|URL
113     Which mirror to use.  Could be a mirror code-name (as specified in
114     /etc/neurodebian/neurodebian.cfg), or a URL (TODO).
115
116   --overwrite,
117     If apt file already present, it would not be overriden (by default).
118     Use this option to overwrite.
119
120   --suffix=SUFFIX
121     Which suffix to add to the apt file, in case you are trying to enable
122     multiple repositories
123
124   --sources, --no-sources
125     Either to enable deb-src lines.  If not specified -- deduced based on ??? TODO
126
127   -n, --dry-run
128     Do not perform any changes -- generated configurations and commands will
129     simply be printed to stdout
130
131   -v, --verbose
132     Enable additional progress messages.  Could be used multiple times
133
134   -q, --quiet
135     Make operation quiet -- only error messages would be output
136
137   -h, --help
138     Print short description, usage summary and option list.
139
140   --version
141     Print version information and exit.
142
143 Exit status:
144
145   non-0 exit status in case of error.  Error exit code would depend
146   on which command has failed
147
148 Examples:
149
150   - Enable software and data components from the optimal (according to
151     netselect) mirror.  Some information about progress will be printed
152
153     nd-aptenable
154
155   - Quietly enable -devel repository for the current release, and place apt
156     configuration into /etc/apt/sources.list.d/neurodebian.sources-devel.list
157
158     nd-aptenable -q --suffix=-devel -c devel
159
160   - Force sid distribution, all the components, from the Japan mirror:
161
162     nd-aptenable -q --suffix=-de-sid-full -c software,data,devel -m jp
163
164 EOT
165 }
166
167 get_neurodebian_cfg()
168 {
169     # First we try to fetch the most recent version from the github
170     print_verbose 3 "Fetching config file from the github repository"
171     cfgfile_temp="$ae_tempdir/neurodebian.cfg"
172     wget -c -q -O$cfgfile_temp https://raw.githubusercontent.com/neurodebian/neurodebian/master/neurodebian.cfg \
173         && { echo $cfgfile_temp; } \
174         || { [ -e "$nd_config_file" ] && return "$nd_config_file"; }
175     # if not -- should blow up
176 }
177
178 get_mirrors()
179 {
180 #    echo "TODO: fetch uptodate neurodebian.cfg"
181     nd_config=`get_neurodebian_cfg`
182     $exe_dir/nd_querycfg --config-file="$nd_config" "mirrors" \
183     | sed -e 's,=, ,'g \
184     | while read mirror_name mirror_url; do
185         # verify that url is just a url
186         if echo "$mirror_url" | grep -v -e '^[a-z0-9:+]*://[-+_%.a-z0-9/]*$'; then
187             print_verbose 1 "Mirror $mirror_name has 'illegit' URL: $mirror_url.  Skipping"
188         fi
189         echo "$mirror_name $mirror_url"
190     done
191 }
192
193 netselect_mirror() {
194     # select "closest" mirror according to netselect.
195     print_verbose 2 "Selecting the 'best' mirror using netselect"
196     if ! which netselect >/dev/null; then
197         print_verbose 1 "netselect (apt-get install netselect) needed to select the 'best' mirror was not found"
198         print_verbose 1 "Selecting the default repository: $nd_mirror_default"
199         echo $nd_mirror_default
200     fi
201     get_mirrors | awk '{print $2;}' | $ae_sudo xargs netselect -D -s 1 | awk '{print $2;}'
202 }
203
204 get_mirror_url() {
205     # given mirror alias -- find its url
206     url=$(get_mirrors | awk "/^$1 /{print \$2;}")
207     if [ -z "$url" ]; then
208         error 9 "Cannot resolve mirror $1 to the URL"
209     fi
210     echo $url
211 }
212
213 get_apt_policy()
214 {
215     # Get apt-cache policy output in a single list for matching suites
216     # (could be a separated with \| or , for multiple choices, e.g.
217     #
218     # get_apt_policy Debian,Ubuntu
219     # or
220     # get_apt_policy NeuroDebian
221     suites="$1"
222     apt-cache policy | grep -B1 -e "o=\(${suites//,/\\|}\)" | tr '\n' ' ' | sed -e 's, -- ,\n,g' | grep -v -e '-\(updates\|security\)' | sort -nr
223 }
224
225 include_component()
226 {
227     echo "$ae_components" | tr ',' '\n' | grep -q "^$1\$"
228 }
229
230
231 #
232 # Commandline options handling
233 #
234
235 # Parse commandline options (taken from the getopt examples from the Debian util-linux package)
236 # Note that we use `"$@"' to let each command-line parameter expand to a
237 # separate word. The quotes around `$@' are essential!
238 # We need CLOPTS as the `eval set --' would nuke the return value of getopt.
239 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' -- "$@"`
240
241 if [ $? != 0 ] ; then
242   error 2 "Problem with parsing cmdline.  Terminating..."
243 fi
244
245 # Note the quotes around `$CLOPTS': they are essential!
246 eval set -- "$CLOPTS"
247
248 if [ `whoami` != "root" ]; then
249     ae_sudo=sudo
250 fi
251
252 while true ; do
253   case "$1" in
254           -r|--release) shift; ae_release="$1"; shift;;
255           -f|--flavor) shift;  ae_flavor="$1"; shift;;
256           -c|--components) shift; ae_components="$1"; shift;;
257       # TODO
258       -m|--mirror) shift;  ae_mirror="$1"; shift;;
259       # TODO
260          --print-mirrors)  get_mirrors; exit 0;;
261          --print-best-mirror)  netselect_mirror; exit 0;;
262       -n|--dry-run)        ae_dry_run=1; shift;;
263          --suffix) shift;  ae_suffix="$1"; shift;;
264          --overwrite)      ae_overwrite="$1"; shift;;
265          --sources)        ae_sources=1; shift;;
266          --no-sources)     ae_sources=0; shift;;
267           -q|--quiet)          ae_verbose=0; shift;;
268           -v|--verbose)        ae_verbose=$(($ae_verbose+1)); shift;;
269           -h|--help) print_help; exit 0;;
270           --version) print_version; exit 0;;
271           --) shift ; break ;;
272           *) error 1 "Internal error! ($1)";;
273   esac
274 done
275
276
277 if [ $# -gt 0 ] ; then
278     print_help >&2
279     exit 2
280 fi
281
282 # Inform!
283 [ -z "$ae_sudo" ] || print_verbose 1 "This script requires root access.  Since current user is not root, sudo will be used"
284
285 #
286 # Basic system/environment knowledge
287 #
288
289 ae_output_file=/etc/apt/sources.list.d/neurodebian.sources${ae_suffix}.list
290
291 apt_policy=$(get_apt_policy "Debian,Ubuntu" )
292
293 if [ -z "$ae_release" ]; then
294     ae_release=$(echo "$apt_policy" | head -1 | sed -e 's/.*,n=\([^,]*\),.*/\1/g')
295 fi
296
297 if [ -z "$ae_flavor" ]; then
298     ae_flavor=$(echo "$apt_policy" | grep -e ",n=$ae_release," | grep -qe 'c=\(non-free\|restricted\)' && echo "full" || echo "libre")
299 fi
300
301 #
302 # Determine which mirror to use
303 #
304
305 # TODO -- determine mirror URL
306 # Not necessary for -devel available only from the main site
307 if include_component software || include_component data; then
308     # for now just use default
309     if [ -z "$ae_mirror" ]; then # none specified
310         ae_mirror_url=$nd_mirror_origin
311     else
312         if ! [[ "$ae_mirror" =~ ".*://.*" ]]; then
313             # TODO -- determine from the abbreviation
314             case "$ae_mirror" in
315                 best)    ae_mirror_url=$(netselect_mirror);;
316                 default) ae_mirror_url=$nd_mirror_default;;
317                 origin)  ae_mirror_url=$nd_mirror_origin;;
318                 *)       ae_mirror_url=$(get_mirror_url "$ae_mirror");;
319             esac
320         else
321             ae_mirror_url="$ae_mirror" # it was some kind of a URL already
322         fi
323     fi
324 fi
325
326 #
327 # Prepare APT file
328 #
329
330 case $ae_flavor in
331  full)  apt_flavor="contrib non-free";;
332  libre) apt_flavor="";;
333  *) error 5 "Unknown value of flavor $apt_flavor.  Must be full or libre"
334 esac
335
336 if [ -z "$ae_sources" ] || [ $ae_sources -eq 0 ]; then
337     sources_comment="#"
338 else
339     sources_comment=""
340 fi
341
342 apt_list=
343
344 if include_component software; then
345     apt_list+="
346 # NeuroDebian software repository
347 deb     $ae_mirror_url $ae_release main $apt_flavor
348 ${sources_comment}deb-src $ae_mirror_url $ae_release main $apt_flavor
349 "
350 fi
351
352 if include_component data; then
353     apt_list+="
354 # NeuroDebian data repository
355 deb     $ae_mirror_url data main $apt_flavor
356 ${sources_comment}deb-src $ae_mirror_url data main $apt_flavor
357 "
358 fi
359
360 if include_component devel; then
361     apt_list+="
362 # NeuroDebian -devel repository
363 deb     http://neuro.debian.net/debian-devel $ae_release main $apt_flavor
364 ${sources_comment}deb-src http://neuro.debian.net/debian-devel $ae_release main $apt_flavor
365 "
366 fi
367
368 if [ -e "$ae_output_file" ] && [ -z "$ae_overwrite" ]; then
369     # error 3
370     print_verbose 1 "File $ae_output_file already exists, containing:\n\n`cat \"$ae_output_file\"`\n\nI: Use --overwrite option to regenerate with:\n\n$apt_list"
371     if get_apt_policy NeuroDebian >/dev/null; then
372         print_verbose 1 "NeuroDebian repositories are already available, thus skipping the rest."
373         print_verbose 1 "Rerun with --overwrite if you would like to reconfigure."
374         exit 0
375     fi
376 else
377     print_verbose 1 "Generating $ae_output_file"
378     if [ -z "$ae_dry_run" ]; then
379         echo "$apt_list" >| "$ae_output_file"
380     else
381         echo "DRY:"
382         echo "$apt_list"
383     fi
384 fi
385
386
387 #
388 # Assure present archive GPG key for APT system
389 #
390
391 # Figure out if key needs to be imported (if ran within package,
392 # should already be there due to neurodebian-keyring package)
393 if LANG=C eval $ae_sudo apt-key export $nd_key_id 2>&1 1>/dev/null | grep -qe "nothing exported"; then
394     print_verbose 1 "Fetching the key from the server"
395     eval_dry apt-key adv --recv-keys --keyserver pgp.mit.edu $nd_key_id
396 fi
397
398 #
399 # Finalizing (apt-get update etc)
400 #
401
402 print_verbose 1 "Updating APT listings, might take a few minutes"
403 if [ -z "$ae_dry_run" ]; then
404     apt_logfile=$(ae_tempdir)/apt.log
405     $ae_sudo apt-get update 1>"$apt_logfile" 2>&1 \
406         && rm -f "$apt_logfile" \
407         || {
408              cat "$apt_logfile"
409              error 5 "E: Update failed with exit code $? (above output logged into $apt_logfile)."
410              }
411 else
412     eval_dry apt-get update
413 fi
414
415 if [ "$ae_verbose" -ge 2 ]; then
416     print_verbose 2 "Currently enabled NeuroDebian suites/mirrors:"
417     get_apt_policy NeuroDebian
418 fi