Merge remote-tracking branch 'origin/master' into 14484-collection-record-update
[arvados.git] / tools / arvbox / bin / arvbox
1 #!/bin/sh
2 # Copyright (C) The Arvados Authors. All rights reserved.
3 #
4 # SPDX-License-Identifier: AGPL-3.0
5
6 set -e
7
8 if ! test -d /sys/fs/cgroup ; then
9      echo "Arvbox requires cgroups to be mounted at /sys/fs/cgroup in order to use"
10      echo "Docker-in-Docker.  Older operating systems that put cgroups in other"
11      echo "places (such as /cgroup) are not supported."
12      exit 1
13 fi
14
15 if ! which docker >/dev/null 2>/dev/null ; then
16   echo "Arvbox requires Docker.  To install, run the following command as root:"
17   echo "curl -sSL https://get.docker.com/ | sh"
18   exit 1
19 fi
20
21 if test -z "$ARVBOX_DOCKER" ; then
22     if which greadlink >/dev/null 2>/dev/null ; then
23         ARVBOX_DOCKER=$(greadlink -f $(dirname $0)/../lib/arvbox/docker)
24     else
25         ARVBOX_DOCKER=$(readlink -f $(dirname $0)/../lib/arvbox/docker)
26     fi
27 fi
28
29 if test -z "$ARVBOX_CONTAINER" ; then
30     ARVBOX_CONTAINER=arvbox
31 fi
32
33 if test -z "$ARVBOX_BASE" ; then
34     ARVBOX_BASE="$HOME/.arvbox"
35 fi
36
37 if test -z "$ARVBOX_DATA" ; then
38     ARVBOX_DATA="$ARVBOX_BASE/$ARVBOX_CONTAINER"
39 fi
40
41 if test -z "$ARVADOS_ROOT" ; then
42     ARVADOS_ROOT="$ARVBOX_DATA/arvados"
43 fi
44
45 if test -z "$SSO_ROOT" ; then
46     SSO_ROOT="$ARVBOX_DATA/sso-devise-omniauth-provider"
47 fi
48
49 if test -z "$COMPOSER_ROOT" ; then
50     COMPOSER_ROOT="$ARVBOX_DATA/composer"
51 fi
52
53 if test -z "$WORKBENCH2_ROOT" ; then
54     WORKBENCH2_ROOT="$ARVBOX_DATA/workbench2"
55 fi
56
57 PG_DATA="$ARVBOX_DATA/postgres"
58 VAR_DATA="$ARVBOX_DATA/var"
59 PASSENGER="$ARVBOX_DATA/passenger"
60 GEMS="$ARVBOX_DATA/gems"
61 PIPCACHE="$ARVBOX_DATA/pip"
62 NPMCACHE="$ARVBOX_DATA/npm"
63 GOSTUFF="$ARVBOX_DATA/gopath"
64 RLIBS="$ARVBOX_DATA/Rlibs"
65
66 getip() {
67     docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-
68 }
69
70 gethost() {
71     set +e
72     OVERRIDE=$(docker exec -i $ARVBOX_CONTAINER cat /var/run/localip_override 2>/dev/null)
73     CODE=$?
74     set -e
75     if test "$CODE" = 0 ; then
76        echo $OVERRIDE
77     else
78         getip
79     fi
80 }
81
82 getclusterid() {
83     docker exec $ARVBOX_CONTAINER cat /var/lib/arvados/api_uuid_prefix
84 }
85
86 updateconf() {
87     if test -f ~/.config/arvados/$ARVBOX_CONTAINER.conf ; then
88         sed "s/ARVADOS_API_HOST=.*/ARVADOS_API_HOST=$(gethost):8000/" <$HOME/.config/arvados/$ARVBOX_CONTAINER.conf >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf.tmp
89         mv ~/.config/arvados/$ARVBOX_CONTAINER.conf.tmp ~/.config/arvados/$ARVBOX_CONTAINER.conf
90     else
91         mkdir -p $HOME/.config/arvados
92         cat >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf <<EOF
93 ARVADOS_API_HOST=$(gethost):8000
94 ARVADOS_API_TOKEN=
95 ARVADOS_API_HOST_INSECURE=true
96 EOF
97     fi
98 }
99
100 wait_for_arvbox() {
101     FF=/tmp/arvbox-fifo-$$
102     mkfifo $FF
103     docker logs -f $ARVBOX_CONTAINER > $FF &
104     LOGPID=$!
105     while read line ; do
106         if echo $line | grep "ok: down: ready:" >/dev/null ; then
107             kill $LOGPID
108         else
109             echo $line
110         fi
111     done < $FF
112     rm $FF
113     echo
114     if test -n "$localip" ; then
115         echo "export ARVADOS_API_HOST=$localip:8000"
116     else
117         echo "export ARVADOS_API_HOST=$(gethost):8000"
118     fi
119 }
120
121 docker_run_dev() {
122     docker run \
123            "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
124            "--volume=$SSO_ROOT:/usr/src/sso:rw" \
125            "--volume=$COMPOSER_ROOT:/usr/src/composer:rw" \
126            "--volume=$WORKBENCH2_ROOT:/usr/src/workbench2:rw" \
127            "--volume=$PG_DATA:/var/lib/postgresql:rw" \
128            "--volume=$VAR_DATA:/var/lib/arvados:rw" \
129            "--volume=$PASSENGER:/var/lib/passenger:rw" \
130            "--volume=$GEMS:/var/lib/gems:rw" \
131            "--volume=$PIPCACHE:/var/lib/pip:rw" \
132            "--volume=$NPMCACHE:/var/lib/npm:rw" \
133            "--volume=$GOSTUFF:/var/lib/gopath:rw" \
134            "--volume=$RLIBS:/var/lib/Rlibs:rw" \
135            "$@"
136 }
137
138 run() {
139     CONFIG=$1
140     TAG=$2
141
142     shift
143
144     need_setup=1
145
146     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
147         if test "$CONFIG" = test ; then
148             need_setup=0
149         else
150             echo "Container $ARVBOX_CONTAINER is already running"
151             exit 0
152         fi
153     fi
154
155     if test $need_setup = 1 ; then
156         if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then
157             echo "Container $ARVBOX_CONTAINER already exists but is not running; use restart or reboot"
158             exit 1
159         fi
160     fi
161
162     if test -n "$TAG"
163     then
164         if test $(echo $TAG | cut -c1-1) != '-' ; then
165             TAG=":$TAG"
166             shift
167         else
168             unset TAG
169         fi
170     fi
171
172     if echo "$CONFIG" | grep '^public' ; then
173         if test -n "$ARVBOX_PUBLISH_IP" ; then
174             localip=$ARVBOX_PUBLISH_IP
175         else
176             defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
177             localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
178         fi
179         iptemp=$(tempfile)
180         echo $localip > $iptemp
181         chmod og+r $iptemp
182         PUBLIC="--volume=$iptemp:/var/run/localip_override
183               --publish=443:443
184               --publish=3001:3001
185               --publish=8000:8000
186               --publish=8900:8900
187               --publish=9001:9001
188               --publish=9002:9002
189               --publish=25100:25100
190               --publish=25107:25107
191               --publish=25108:25108
192               --publish=8001:8001
193               --publish=8002:8002"
194     else
195         PUBLIC=""
196     fi
197
198     if echo "$CONFIG" | grep 'demo$' ; then
199         if test -d "$ARVBOX_DATA" ; then
200             echo "It looks like you already have a development container named $ARVBOX_CONTAINER."
201             echo "Set ARVBOX_CONTAINER to set a different name for your demo container"
202             exit 1
203         fi
204
205         if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then
206             docker create -v /var/lib/postgresql -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
207         fi
208
209         docker run \
210                --detach \
211                --name=$ARVBOX_CONTAINER \
212                --privileged \
213                --volumes-from $ARVBOX_CONTAINER-data \
214                $PUBLIC \
215                arvados/arvbox-demo$TAG
216         updateconf
217         wait_for_arvbox
218     else
219         mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS" "$PIPCACHE" "$NPMCACHE" "$GOSTUFF" "$RLIBS"
220
221
222         if ! test -d "$ARVADOS_ROOT" ; then
223             git clone https://github.com/curoverse/arvados.git "$ARVADOS_ROOT"
224         fi
225         if ! test -d "$SSO_ROOT" ; then
226             git clone https://github.com/curoverse/sso-devise-omniauth-provider.git "$SSO_ROOT"
227         fi
228         if ! test -d "$COMPOSER_ROOT" ; then
229             git clone https://github.com/curoverse/composer.git "$COMPOSER_ROOT"
230         fi
231         if ! test -d "$WORKBENCH2_ROOT" ; then
232             git clone https://github.com/curoverse/arvados-workbench2.git "$WORKBENCH2_ROOT"
233         fi
234
235         if test "$CONFIG" = test ; then
236
237             mkdir -p $VAR_DATA/test
238
239             if test "$need_setup" = 1 ; then
240                 docker_run_dev \
241                        --detach \
242                        --name=$ARVBOX_CONTAINER \
243                        --privileged \
244                        "--env=SVDIR=/etc/test-service" \
245                        arvados/arvbox-dev$TAG
246
247                 docker exec -ti \
248                        $ARVBOX_CONTAINER \
249                        /usr/local/lib/arvbox/runsu.sh \
250                        /usr/local/lib/arvbox/waitforpostgres.sh
251
252                 docker exec -ti \
253                        $ARVBOX_CONTAINER \
254                        /usr/local/lib/arvbox/runsu.sh \
255                        /var/lib/arvbox/service/sso/run-service --only-setup
256
257                 docker exec -ti \
258                        $ARVBOX_CONTAINER \
259                        /usr/local/lib/arvbox/runsu.sh \
260                        /var/lib/arvbox/service/api/run-service --only-setup
261             fi
262
263             docker exec -ti \
264                    $ARVBOX_CONTAINER \
265                    /usr/local/lib/arvbox/runsu.sh \
266                    /usr/src/arvados/build/run-tests.sh \
267                    --temp /var/lib/arvados/test \
268                    WORKSPACE=/usr/src/arvados \
269                    GEM_HOME=/var/lib/gems \
270                    "$@"
271         elif echo "$CONFIG" | grep 'dev$' ; then
272             docker_run_dev \
273                    --detach \
274                    --name=$ARVBOX_CONTAINER \
275                    --privileged \
276                    $PUBLIC \
277                    arvados/arvbox-dev$TAG
278             updateconf
279             wait_for_arvbox
280             echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
281             echo "The Arvados testing root certificate is $VAR_DATA/root-cert.pem"
282         else
283             echo "Unknown configuration '$CONFIG'"
284         fi
285     fi
286 }
287
288 update() {
289     CONFIG=$1
290     TAG=$2
291
292     if test -n "$TAG"
293     then
294         if test $(echo $TAG | cut -c1-1) != '-' ; then
295             TAG=":$TAG"
296             shift
297         else
298             unset TAG
299         fi
300     fi
301
302     if echo "$CONFIG" | grep 'demo$' ; then
303         docker pull arvados/arvbox-demo$TAG
304     else
305         docker pull arvados/arvbox-dev$TAG
306     fi
307 }
308
309 stop() {
310     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
311         docker stop $ARVBOX_CONTAINER
312     fi
313
314     VOLUMES=--volumes=true
315     if docker ps -a --filter "status=created" | grep -E "$ARVBOX_CONTAINER$" -q ; then
316         docker rm $VOLUMES $ARVBOX_CONTAINER
317     fi
318     if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then
319         docker rm $VOLUMES $ARVBOX_CONTAINER
320     fi
321 }
322
323 build() {
324     if ! test -f "$ARVBOX_DOCKER/Dockerfile.base" ;  then
325         echo "Could not find Dockerfile (expected it at $ARVBOX_DOCKER/Dockerfile.base)"
326         exit 1
327     fi
328     if docker --version |grep " 1\.[0-9]\." ; then
329         # Docker version prior 1.10 require -f flag
330         # -f flag removed in Docker 1.12
331         FORCE=-f
332     fi
333     GITHEAD=$(cd $ARVBOX_DOCKER && git log --format=%H -n1 HEAD)
334     docker build --build-arg=arvados_version=$GITHEAD $NO_CACHE -t arvados/arvbox-base:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
335     docker tag $FORCE arvados/arvbox-base:$GITHEAD arvados/arvbox-base:latest
336     if test "$1" = localdemo -o "$1" = publicdemo ; then
337         docker build $NO_CACHE -t arvados/arvbox-demo:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.demo" "$ARVBOX_DOCKER"
338         docker tag $FORCE arvados/arvbox-demo:$GITHEAD arvados/arvbox-demo:latest
339     else
340         docker build $NO_CACHE -t arvados/arvbox-dev:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
341         docker tag $FORCE arvados/arvbox-dev:$GITHEAD arvados/arvbox-dev:latest
342     fi
343 }
344
345 check() {
346     case "$1" in
347         localdemo|publicdemo|dev|publicdev|test)
348             true
349             ;;
350         *)
351             echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test"
352             exit 1
353         ;;
354     esac
355 }
356
357 subcmd="$1"
358 if test -n "$subcmd" ; then
359     shift
360 fi
361 case "$subcmd" in
362     build)
363         check $@
364         build $@
365         ;;
366
367     rebuild)
368         check $@
369         NO_CACHE=--no-cache build $@
370         ;;
371
372     start|run)
373         check $@
374         run $@
375         ;;
376
377     sh*)
378         exec docker exec -ti \
379                -e LINES=$(tput lines) \
380                -e COLUMNS=$(tput cols) \
381                -e TERM=$TERM \
382                -e GEM_HOME=/var/lib/gems \
383                $ARVBOX_CONTAINER /bin/bash
384         ;;
385
386     pipe)
387         exec docker exec -i $ARVBOX_CONTAINER /usr/bin/env GEM_HOME=/var/lib/gems /bin/bash -
388         ;;
389
390     stop)
391         stop
392         ;;
393
394     restart)
395         check $@
396         stop
397         run $@
398         ;;
399
400     reboot)
401         check $@
402         stop
403         build $@
404         run $@
405         ;;
406
407     update)
408         check $@
409         stop
410         update $@
411         run $@
412         ;;
413
414     ip)
415         getip
416         ;;
417
418     host)
419         gethost
420         ;;
421
422     open)
423         exec xdg-open http://$(gethost)
424         ;;
425
426     status)
427         echo "Container: $ARVBOX_CONTAINER"
428         if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
429             echo "Cluster id: $(getclusterid)"
430             echo "Status: running"
431             echo "Container IP: $(getip)"
432             echo "Published host: $(gethost)"
433         else
434             echo "Status: not running"
435         fi
436         if test -d "$ARVBOX_DATA" ; then
437             echo "Data: $ARVBOX_DATA"
438         elif docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q ; then
439             echo "Data: $ARVBOX_CONTAINER-data"
440         else
441             echo "Data: none"
442         fi
443         ;;
444
445     reset|destroy)
446         stop
447         if test -d "$ARVBOX_DATA" ; then
448             if test "$subcmd" = destroy ; then
449                 if test "$1" != -f ; then
450                     echo "WARNING!  This will delete your entire arvbox ($ARVBOX_DATA)."
451                     echo "Use destroy -f if you really mean it."
452                     exit 1
453                 fi
454                 set -x
455                 rm -rf "$ARVBOX_DATA"
456             else
457                 if test "$1" != -f ; then
458                     echo "WARNING!  This will delete your arvbox data ($ARVBOX_DATA)."
459                     echo "Code and downloaded packages will be preserved."
460                     echo "Use reset -f if you really mean it."
461                     exit 1
462                 fi
463                 set -x
464                 rm -rf "$ARVBOX_DATA/postgres"
465                 rm -rf "$ARVBOX_DATA/var"
466             fi
467         else
468             if test "$1" != -f ; then
469                 echo "WARNING!  This will delete your data container $ARVBOX_CONTAINER-data.  Use -f if you really mean it."
470                 exit 1
471             fi
472             set -x
473             docker rm "$ARVBOX_CONTAINER-data"
474         fi
475         ;;
476
477     log)
478         if test -n "$1" ; then
479             exec docker exec -ti -e LINES=$(tput lines) -e COLUMNS=$(tput cols) -e TERM=$TERM $ARVBOX_CONTAINER less --follow-name -R +GF "/etc/service/$1/log/main/current"
480         else
481             exec docker exec -ti $ARVBOX_CONTAINER tail $(docker exec -ti $ARVBOX_CONTAINER find -L /etc -path '/etc/service/*/log/main/current' -printf " %p")
482         fi
483         ;;
484
485     cat)
486         if test -n "$1" ; then
487             exec docker exec $ARVBOX_CONTAINER cat "$@"
488         else
489             echo "Usage: $0 $subcmd <files>"
490         fi
491         ;;
492
493     ls)
494         exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM ls "$@"
495         ;;
496
497     sv)
498         if test -n "$1" -a -n "$2" ; then
499             exec docker exec $ARVBOX_CONTAINER sv "$@"
500         else
501             echo "Usage: $0 $subcmd <start|stop|restart> <service>"
502             echo "Available services:"
503             exec docker execa $ARVBOX_CONTAINER ls /etc/service
504         fi
505         ;;
506
507     clone)
508         if test -n "$2" ; then
509             cp -r "$ARVBOX_BASE/$1" "$ARVBOX_BASE/$2"
510             echo "Created new arvbox $2"
511             echo "export ARVBOX_CONTAINER=$2"
512         else
513             echo "clone <from> <to>   clone an arvbox"
514             echo "available arvboxes: $(ls $ARVBOX_BASE)"
515         fi
516         ;;
517
518     root-cert)
519         CERT=$PWD/${ARVBOX_CONTAINER}-root-cert.pem
520         if test -n "$1" ; then
521             CERT="$1"
522         fi
523         docker exec $ARVBOX_CONTAINER cat /var/lib/arvados/root-cert.pem > "$CERT"
524         echo "Certificate copied to $CERT"
525         ;;
526
527     devenv)
528         set -x
529         if docker ps -a --filter "status=exited" | grep -E "${ARVBOX_CONTAINER}-devenv$" -q ; then
530             docker start ${ARVBOX_CONTAINER}-devenv
531         elif ! (docker ps -a --filter "status=running" | grep -E "${ARVBOX_CONTAINER}-devenv$" -q) ; then
532             docker_run_dev \
533                  --detach \
534                  --name=${ARVBOX_CONTAINER}-devenv \
535                  "--env=SVDIR=/etc/devenv-service" \
536                  "--volume=$HOME:$HOME:rw" \
537                  --volume=/tmp/.X11-unix:/tmp/.X11-unix:rw \
538                  arvados/arvbox-dev$TAG
539         fi
540
541         exec docker exec --interactive --tty \
542              -e LINES=$(tput lines) \
543              -e COLUMNS=$(tput cols) \
544              -e TERM=$TERM \
545              -e "ARVBOX_HOME=$HOME" \
546              -e "DISPLAY=$DISPLAY" \
547              --workdir=$PWD \
548              ${ARVBOX_CONTAINER}-devenv \
549              /usr/local/lib/arvbox/devenv.sh "$@"
550         ;;
551
552     devenv-stop)
553         docker stop ${ARVBOX_CONTAINER}-devenv
554         ;;
555
556     devenv-reset)
557         docker stop ${ARVBOX_CONTAINER}-devenv
558         docker rm ${ARVBOX_CONTAINER}-devenv
559         ;;
560
561     *)
562         echo "Arvados-in-a-box                      http://arvados.org"
563         echo
564         echo "start|run <config> [tag]  start $ARVBOX_CONTAINER container"
565         echo "stop       stop arvbox container"
566         echo "restart <config>  stop, then run again"
567         echo "status     print some information about current arvbox"
568         echo "ip         print arvbox docker container ip address"
569         echo "host       print arvbox published host"
570         echo "shell      enter arvbox shell"
571         echo "open       open arvbox workbench in a web browser"
572         echo "root-cert  get copy of root certificate"
573         echo "update  <config> stop, pull latest image, run"
574         echo "build   <config> build arvbox Docker image"
575         echo "reboot  <config> stop, build arvbox Docker image, run"
576         echo "rebuild <config> build arvbox Docker image, no layer cache"
577         echo "reset      delete arvbox arvados data (be careful!)"
578         echo "destroy    delete all arvbox code and data (be careful!)"
579         echo "log <service> tail log of specified service"
580         echo "ls <options>  list directories inside arvbox"
581         echo "cat <files>   get contents of files inside arvbox"
582         echo "pipe       run a bash script piped in from stdin"
583         echo "sv <start|stop|restart> <service> change state of service inside arvbox"
584         echo "clone <from> <to>   clone an arvbox"
585         ;;
586 esac