0a0d82e71b59c4c0290457e85aaf2b33480b28c2
[arvados.git] / tools / arvbox / bin / arvbox
1 #!/bin/bash
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 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $ARVBOX_CONTAINER
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 [[ $line =~ "ok: down: ready:" ]] ; then
107             kill $LOGPID
108             set +e
109             wait $LOGPID 2>/dev/null
110             set -e
111         else
112             echo $line
113         fi
114     done < $FF
115     rm $FF
116     echo
117     if test -n "$localip" ; then
118         echo "export ARVADOS_API_HOST=$localip:8000"
119     else
120         echo "export ARVADOS_API_HOST=$(gethost):8000"
121     fi
122 }
123
124 docker_run_dev() {
125     docker run \
126            "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
127            "--volume=$SSO_ROOT:/usr/src/sso:rw" \
128            "--volume=$COMPOSER_ROOT:/usr/src/composer:rw" \
129            "--volume=$WORKBENCH2_ROOT:/usr/src/workbench2:rw" \
130            "--volume=$PG_DATA:/var/lib/postgresql:rw" \
131            "--volume=$VAR_DATA:/var/lib/arvados:rw" \
132            "--volume=$PASSENGER:/var/lib/passenger:rw" \
133            "--volume=$GEMS:/var/lib/gems:rw" \
134            "--volume=$PIPCACHE:/var/lib/pip:rw" \
135            "--volume=$NPMCACHE:/var/lib/npm:rw" \
136            "--volume=$GOSTUFF:/var/lib/gopath:rw" \
137            "--volume=$RLIBS:/var/lib/Rlibs:rw" \
138            --label "org.arvados.arvbox_config=$CONFIG" \
139            "$@"
140 }
141
142 running_config() {
143     docker inspect $ARVBOX_CONTAINER -f '{{index .Config.Labels "org.arvados.arvbox_config"}}'
144 }
145
146 run() {
147     CONFIG=$1
148     TAG=$2
149
150     shift
151
152     need_setup=1
153
154     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
155         if [[ $(running_config) != "$CONFIG" ]] ; then
156             echo "Container $ARVBOX_CONTAINER is '$(running_config)' config but requested '$CONFIG'; use restart or reboot"
157             return 1
158         fi
159         if test "$CONFIG" = test -o "$CONFIG" = devenv ; then
160             need_setup=0
161         else
162             echo "Container $ARVBOX_CONTAINER is already running"
163             return 0
164         fi
165     fi
166
167     if test $need_setup = 1 ; then
168         if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then
169             echo "Container $ARVBOX_CONTAINER already exists but is not running; use restart or reboot"
170             return 1
171         fi
172     fi
173
174     if test -n "$TAG"
175     then
176         if test $(echo $TAG | cut -c1-1) != '-' ; then
177             TAG=":$TAG"
178             shift
179         else
180             if [[ $TAG = '-' ]] ; then
181                 shift
182             fi
183             unset TAG
184         fi
185     fi
186
187     if [[ "$CONFIG" =~ ^public ]] ; then
188         if test -n "$ARVBOX_PUBLISH_IP" ; then
189             localip=$ARVBOX_PUBLISH_IP
190         else
191             defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
192             localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
193         fi
194         iptemp=$(tempfile)
195         echo $localip > $iptemp
196         chmod og+r $iptemp
197         PUBLIC="--volume=$iptemp:/var/run/localip_override
198               --publish=443:443
199               --publish=3001:3001
200               --publish=8000:8000
201               --publish=8900:8900
202               --publish=9001:9001
203               --publish=9002:9002
204               --publish=25100:25100
205               --publish=25107:25107
206               --publish=25108:25108
207               --publish=8001:8001
208               --publish=8002:8002"
209     else
210         PUBLIC=""
211     fi
212
213     if [[ "$CONFIG" =~ demo$ ]] ; then
214         if test -d "$ARVBOX_DATA" ; then
215             echo "It looks like you already have a development container named $ARVBOX_CONTAINER."
216             echo "Set environment variable ARVBOX_CONTAINER to set a different name for your demo container"
217             exit 1
218         fi
219
220         if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then
221             docker create -v /var/lib/postgresql -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
222         fi
223
224         docker run \
225                --detach \
226                --name=$ARVBOX_CONTAINER \
227                --privileged \
228                --volumes-from $ARVBOX_CONTAINER-data \
229                --label "org.arvados.arvbox_config=$CONFIG" \
230                $PUBLIC \
231                arvados/arvbox-demo$TAG
232         updateconf
233         wait_for_arvbox
234     else
235         mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS" "$PIPCACHE" "$NPMCACHE" "$GOSTUFF" "$RLIBS"
236
237         if ! test -d "$ARVADOS_ROOT" ; then
238             git clone https://github.com/curoverse/arvados.git "$ARVADOS_ROOT"
239         fi
240         if ! test -d "$SSO_ROOT" ; then
241             git clone https://github.com/curoverse/sso-devise-omniauth-provider.git "$SSO_ROOT"
242         fi
243         if ! test -d "$COMPOSER_ROOT" ; then
244             git clone https://github.com/curoverse/composer.git "$COMPOSER_ROOT"
245         fi
246         if ! test -d "$WORKBENCH2_ROOT" ; then
247             git clone https://github.com/curoverse/arvados-workbench2.git "$WORKBENCH2_ROOT"
248         fi
249
250         if [[ "$CONFIG" = test ]] ; then
251
252             mkdir -p $VAR_DATA/test
253
254             if test "$need_setup" = 1 ; then
255                 docker_run_dev \
256                        --detach \
257                        --name=$ARVBOX_CONTAINER \
258                        --privileged \
259                        "--env=SVDIR=/etc/test-service" \
260                        arvados/arvbox-dev$TAG
261
262                 docker exec -ti \
263                        $ARVBOX_CONTAINER \
264                        /usr/local/lib/arvbox/runsu.sh \
265                        /usr/local/lib/arvbox/waitforpostgres.sh
266
267                 docker exec -ti \
268                        $ARVBOX_CONTAINER \
269                        /usr/local/lib/arvbox/runsu.sh \
270                        /var/lib/arvbox/service/sso/run-service --only-setup
271
272                 docker exec -ti \
273                        $ARVBOX_CONTAINER \
274                        /usr/local/lib/arvbox/runsu.sh \
275                        /var/lib/arvbox/service/api/run-service --only-setup
276             fi
277
278             interactive=""
279             if [[ -z "$@" ]] ; then
280                 interactive=--interactive
281             fi
282
283             docker exec -ti \
284                    -e LINES=$(tput lines) \
285                    -e COLUMNS=$(tput cols) \
286                    -e TERM=$TERM \
287                    -e WORKSPACE=/usr/src/arvados \
288                    -e GEM_HOME=/var/lib/gems \
289                    $ARVBOX_CONTAINER \
290                    /usr/local/lib/arvbox/runsu.sh \
291                    /usr/src/arvados/build/run-tests.sh \
292                    --temp /var/lib/arvados/test \
293                    $interactive \
294                    "$@"
295         elif [[ "$CONFIG" = devenv ]] ; then
296             if [[ $need_setup = 1 ]] ; then
297                 docker_run_dev \
298                     --detach \
299                     --name=${ARVBOX_CONTAINER} \
300                     "--env=SVDIR=/etc/devenv-service" \
301                     "--volume=$HOME:$HOME:rw" \
302                     --volume=/tmp/.X11-unix:/tmp/.X11-unix:rw \
303                     arvados/arvbox-dev$TAG
304             fi
305             exec docker exec --interactive --tty \
306                  -e LINES=$(tput lines) \
307                  -e COLUMNS=$(tput cols) \
308                  -e TERM=$TERM \
309                  -e "ARVBOX_HOME=$HOME" \
310                  -e "DISPLAY=$DISPLAY" \
311                  --workdir=$PWD \
312                  ${ARVBOX_CONTAINER} \
313                  /usr/local/lib/arvbox/devenv.sh "$@"
314         elif [[ "$CONFIG" =~ dev$ ]] ; then
315             docker_run_dev \
316                    --detach \
317                    --name=$ARVBOX_CONTAINER \
318                    --privileged \
319                    $PUBLIC \
320                    arvados/arvbox-dev$TAG
321             updateconf
322             wait_for_arvbox
323             echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
324             echo "The Arvados testing root certificate is $VAR_DATA/root-cert.pem"
325         else
326             echo "Unknown configuration '$CONFIG'"
327         fi
328     fi
329 }
330
331 update() {
332     CONFIG=$1
333     TAG=$2
334
335     if test -n "$TAG"
336     then
337         if test $(echo $TAG | cut -c1-1) != '-' ; then
338             TAG=":$TAG"
339             shift
340         else
341             unset TAG
342         fi
343     fi
344
345     if echo "$CONFIG" | grep 'demo$' ; then
346         docker pull arvados/arvbox-demo$TAG
347     else
348         docker pull arvados/arvbox-dev$TAG
349     fi
350 }
351
352 stop() {
353     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
354         docker stop $ARVBOX_CONTAINER
355     fi
356
357     VOLUMES=--volumes=true
358     if docker ps -a --filter "status=created" | grep -E "$ARVBOX_CONTAINER$" -q ; then
359         docker rm $VOLUMES $ARVBOX_CONTAINER
360     fi
361     if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then
362         docker rm $VOLUMES $ARVBOX_CONTAINER
363     fi
364 }
365
366 build() {
367     if ! test -f "$ARVBOX_DOCKER/Dockerfile.base" ;  then
368         echo "Could not find Dockerfile (expected it at $ARVBOX_DOCKER/Dockerfile.base)"
369         exit 1
370     fi
371     if docker --version |grep " 1\.[0-9]\." ; then
372         # Docker version prior 1.10 require -f flag
373         # -f flag removed in Docker 1.12
374         FORCE=-f
375     fi
376     GITHEAD=$(cd $ARVBOX_DOCKER && git log --format=%H -n1 HEAD)
377     docker build --build-arg=arvados_version=$GITHEAD $NO_CACHE -t arvados/arvbox-base:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
378     docker tag $FORCE arvados/arvbox-base:$GITHEAD arvados/arvbox-base:latest
379     if test "$1" = localdemo -o "$1" = publicdemo ; then
380         docker build $NO_CACHE -t arvados/arvbox-demo:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.demo" "$ARVBOX_DOCKER"
381         docker tag $FORCE arvados/arvbox-demo:$GITHEAD arvados/arvbox-demo:latest
382     else
383         docker build $NO_CACHE -t arvados/arvbox-dev:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
384         docker tag $FORCE arvados/arvbox-dev:$GITHEAD arvados/arvbox-dev:latest
385     fi
386 }
387
388 check() {
389     case "$1" in
390         localdemo|publicdemo|dev|publicdev|test|devenv)
391             true
392             ;;
393         *)
394             echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test, devenv"
395             exit 1
396         ;;
397     esac
398 }
399
400 subcmd="$1"
401 if test -n "$subcmd" ; then
402     shift
403 fi
404 case "$subcmd" in
405     build)
406         check $@
407         build $@
408         ;;
409
410     rebuild)
411         check $@
412         NO_CACHE=--no-cache build $@
413         ;;
414
415     start|run)
416         check $@
417         run $@
418         ;;
419
420     sh*)
421         exec docker exec --interactive --tty \
422                -e LINES=$(tput lines) \
423                -e COLUMNS=$(tput cols) \
424                -e TERM=$TERM \
425                -e GEM_HOME=/var/lib/gems \
426                $ARVBOX_CONTAINER /bin/bash
427         ;;
428
429     ash*)
430         exec docker exec --interactive --tty \
431                -e LINES=$(tput lines) \
432                -e COLUMNS=$(tput cols) \
433                -e TERM=$TERM \
434                -e GEM_HOME=/var/lib/gems \
435                -u arvbox \
436                -w /usr/src/arvados \
437                $ARVBOX_CONTAINER /bin/bash --login
438         ;;
439
440     pipe)
441         exec docker exec -i $ARVBOX_CONTAINER /usr/bin/env GEM_HOME=/var/lib/gems /bin/bash -
442         ;;
443
444     stop)
445         stop
446         ;;
447
448     restart)
449         check $@
450         stop
451         run $@
452         ;;
453
454     reboot)
455         check $@
456         stop
457         build $@
458         run $@
459         ;;
460
461     update)
462         check $@
463         stop
464         update $@
465         run $@
466         ;;
467
468     ip)
469         getip
470         ;;
471
472     host)
473         gethost
474         ;;
475
476     open)
477         exec xdg-open http://$(gethost)
478         ;;
479
480     status)
481         echo "Container: $ARVBOX_CONTAINER"
482         if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
483             echo "Cluster id: $(getclusterid)"
484             echo "Status: running"
485             echo "Container IP: $(getip)"
486             echo "Published host: $(gethost)"
487         else
488             echo "Status: not running"
489         fi
490         if test -d "$ARVBOX_DATA" ; then
491             echo "Data: $ARVBOX_DATA"
492         elif docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q ; then
493             echo "Data: $ARVBOX_CONTAINER-data"
494         else
495             echo "Data: none"
496         fi
497         ;;
498
499     reset|destroy)
500         stop
501         if test -d "$ARVBOX_DATA" ; then
502             if test "$subcmd" = destroy ; then
503                 if test "$1" != -f ; then
504                     echo "WARNING!  This will delete your entire arvbox ($ARVBOX_DATA)."
505                     echo "Use destroy -f if you really mean it."
506                     exit 1
507                 fi
508                 set -x
509                 rm -rf "$ARVBOX_DATA"
510             else
511                 if test "$1" != -f ; then
512                     echo "WARNING!  This will delete your arvbox data ($ARVBOX_DATA)."
513                     echo "Code and downloaded packages will be preserved."
514                     echo "Use reset -f if you really mean it."
515                     exit 1
516                 fi
517                 set -x
518                 rm -rf "$ARVBOX_DATA/postgres"
519                 rm -rf "$ARVBOX_DATA/var"
520             fi
521         else
522             if test "$1" != -f ; then
523                 echo "WARNING!  This will delete your data container $ARVBOX_CONTAINER-data.  Use -f if you really mean it."
524                 exit 1
525             fi
526             set -x
527             docker rm "$ARVBOX_CONTAINER-data"
528         fi
529         ;;
530
531     log)
532         if test -n "$1" ; then
533             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"
534         else
535             exec docker exec -ti $ARVBOX_CONTAINER tail $(docker exec -ti $ARVBOX_CONTAINER find -L /etc -path '/etc/service/*/log/main/current' -printf " %p")
536         fi
537         ;;
538
539     cat)
540         if test -n "$1" ; then
541             exec docker exec $ARVBOX_CONTAINER cat "$@"
542         else
543             echo "Usage: $0 $subcmd <files>"
544         fi
545         ;;
546
547     ls)
548         exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM ls "$@"
549         ;;
550
551     sv)
552         if test -n "$1" -a -n "$2" ; then
553             exec docker exec $ARVBOX_CONTAINER sv "$@"
554         else
555             echo "Usage: $0 $subcmd <start|stop|restart> <service>"
556             echo "Available services:"
557             exec docker execa $ARVBOX_CONTAINER ls /etc/service
558         fi
559         ;;
560
561     clone)
562         if test -n "$2" ; then
563             mkdir -p "$ARVBOX_BASE/$2"
564             cp -a "$ARVBOX_BASE/$1/passenger" \
565                "$ARVBOX_BASE/$1/gems" \
566                "$ARVBOX_BASE/$1/pip" \
567                "$ARVBOX_BASE/$1/npm" \
568                "$ARVBOX_BASE/$1/gopath" \
569                "$ARVBOX_BASE/$1/Rlibs" \
570                "$ARVBOX_BASE/$1/arvados" \
571                "$ARVBOX_BASE/$1/sso-devise-omniauth-provider" \
572                "$ARVBOX_BASE/$1/composer" \
573                "$ARVBOX_BASE/$1/workbench2" \
574                "$ARVBOX_BASE/$2"
575             echo "Created new arvbox $2"
576             echo "export ARVBOX_CONTAINER=$2"
577         else
578             echo "clone <from> <to>   clone an arvbox"
579             echo "available arvboxes: $(ls $ARVBOX_BASE)"
580         fi
581         ;;
582
583     root-cert)
584         CERT=$PWD/${ARVBOX_CONTAINER}-root-cert.crt
585         if test -n "$1" ; then
586             CERT="$1"
587         fi
588         docker exec $ARVBOX_CONTAINER cat /var/lib/arvados/root-cert.pem > "$CERT"
589         echo "Certificate copied to $CERT"
590         ;;
591
592     psql)
593         exec docker exec -ti $ARVBOX_CONTAINER bash -c 'PGPASSWORD=$(cat /var/lib/arvados/api_database_pw) exec psql --dbname=arvados_development --host=localhost --username=arvados'
594         ;;
595
596     *)
597         echo "Arvados-in-a-box             https://doc.arvados.org/install/arvbox.html"
598         echo
599         echo "start|run <config> [tag]   start $ARVBOX_CONTAINER container"
600         echo "stop               stop arvbox container"
601         echo "restart <config>   stop, then run again"
602         echo "status             print some information about current arvbox"
603         echo "ip                 print arvbox docker container ip address"
604         echo "host               print arvbox published host"
605         echo "shell              enter shell as root"
606         echo "ashell             enter shell as 'arvbox'"
607         echo "psql               enter postgres console"
608         echo "open               open arvbox workbench in a web browser"
609         echo "root-cert          get copy of root certificate"
610         echo "update  <config>   stop, pull latest image, run"
611         echo "build   <config>   build arvbox Docker image"
612         echo "reboot  <config>   stop, build arvbox Docker image, run"
613         echo "rebuild <config>   build arvbox Docker image, no layer cache"
614         echo "reset              delete arvbox arvados data (be careful!)"
615         echo "destroy            delete all arvbox code and data (be careful!)"
616         echo "log <service>      tail log of specified service"
617         echo "ls <options>       list directories inside arvbox"
618         echo "cat <files>        get contents of files inside arvbox"
619         echo "pipe               run a bash script piped in from stdin"
620         echo "sv <start|stop|restart> <service> "
621         echo "                   change state of service inside arvbox"
622         echo "clone <from> <to>  clone dev arvbox"
623         ;;
624 esac