#!/bin/bash # Copyright (C) The Arvados Authors. All rights reserved. # # SPDX-License-Identifier: AGPL-3.0 set -e if ! test -d /sys/fs/cgroup ; then echo "Arvbox requires cgroups to be mounted at /sys/fs/cgroup in order to use" echo "Docker-in-Docker. Older operating systems that put cgroups in other" echo "places (such as /cgroup) are not supported." exit 1 fi if ! which docker >/dev/null 2>/dev/null ; then echo "Arvbox requires Docker. To install, run the following command as root:" echo "curl -sSL https://get.docker.com/ | sh" exit 1 fi if test -z "$ARVBOX_DOCKER" ; then set +e if which greadlink >/dev/null 2>/dev/null ; then ARVBOX_DOCKER=$(greadlink -f $(dirname $0)/../lib/arvbox/docker) else ARVBOX_DOCKER=$(readlink -f $(dirname $0)/../lib/arvbox/docker) fi set -e fi if test -z "$ARVBOX_CONTAINER" ; then ARVBOX_CONTAINER=arvbox fi if test -z "$ARVBOX_BASE" ; then ARVBOX_BASE="$HOME/.arvbox" fi if test -z "$ARVBOX_DATA" ; then ARVBOX_DATA="$ARVBOX_BASE/$ARVBOX_CONTAINER" fi if test -z "$ARVADOS_ROOT" ; then ARVADOS_ROOT="$ARVBOX_DATA/arvados" fi if test -z "$WORKBENCH2_ROOT" ; then WORKBENCH2_ROOT="$ARVBOX_DATA/workbench2" fi if test -z "$ARVADOS_BRANCH" ; then ARVADOS_BRANCH=main fi if test -z "$WORKBENCH2_BRANCH" ; then WORKBENCH2_BRANCH=main fi # Update this to the docker tag for the version on releases. DEFAULT_TAG=2.5.0 PG_DATA="$ARVBOX_DATA/postgres" VAR_DATA="$ARVBOX_DATA/var" PASSENGER="$ARVBOX_DATA/passenger" GEMS="$ARVBOX_DATA/gems" PIPCACHE="$ARVBOX_DATA/pip" NPMCACHE="$ARVBOX_DATA/npm" GOSTUFF="$ARVBOX_DATA/gopath" RLIBS="$ARVBOX_DATA/Rlibs" ARVADOS_CONTAINER_PATH="/var/lib/arvados-arvbox" getip() { docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $ARVBOX_CONTAINER } gethost() { set +e OVERRIDE=$(docker exec -i $ARVBOX_CONTAINER cat /var/run/localip_override 2>/dev/null) CODE=$? set -e if test "$CODE" = 0 ; then echo $OVERRIDE else getip fi } getclusterid() { docker exec $ARVBOX_CONTAINER cat $ARVADOS_CONTAINER_PATH/api_uuid_prefix } updateconf() { if test -f ~/.config/arvados/$ARVBOX_CONTAINER.conf ; then sed "s/ARVADOS_API_HOST=.*/ARVADOS_API_HOST=$(gethost):8000/" <$HOME/.config/arvados/$ARVBOX_CONTAINER.conf >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf.tmp mv ~/.config/arvados/$ARVBOX_CONTAINER.conf.tmp ~/.config/arvados/$ARVBOX_CONTAINER.conf else mkdir -p $HOME/.config/arvados cat >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf < $FF & LOGPID=$! while read line ; do if [[ $line =~ "ok: down: ready:" ]] ; then kill $LOGPID set +e wait $LOGPID 2>/dev/null set -e else echo $line fi done < $FF rm $FF echo if test -n "$localip" ; then echo "export ARVADOS_API_HOST=$localip:8000" else echo "export ARVADOS_API_HOST=$(gethost):8000" fi } docker_run_dev() { docker run \ "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \ "--volume=$WORKBENCH2_ROOT:/usr/src/workbench2:rw" \ "--volume=$PG_DATA:/var/lib/postgresql:rw" \ "--volume=$VAR_DATA:$ARVADOS_CONTAINER_PATH:rw" \ "--volume=$PASSENGER:/var/lib/passenger:rw" \ "--volume=$GEMS:/var/lib/arvados-arvbox/.gem:rw" \ "--volume=$PIPCACHE:/var/lib/pip:rw" \ "--volume=$NPMCACHE:/var/lib/npm:rw" \ "--volume=$GOSTUFF:/var/lib/gopath:rw" \ "--volume=$RLIBS:/var/lib/Rlibs:rw" \ --label "org.arvados.arvbox_config=$CONFIG" \ "$@" } running_config() { docker inspect $ARVBOX_CONTAINER -f '{{index .Config.Labels "org.arvados.arvbox_config"}}' } run() { CONFIG=$1 TAG=$2 shift need_setup=1 if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then if [[ $(running_config) != "$CONFIG" ]] ; then echo "Container $ARVBOX_CONTAINER is '$(running_config)' config but requested '$CONFIG'; use restart or reboot" return 1 fi if test "$CONFIG" = test -o "$CONFIG" = devenv ; then need_setup=0 else echo "Container $ARVBOX_CONTAINER is already running" return 0 fi fi if test $need_setup = 1 ; then if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then echo "Container $ARVBOX_CONTAINER already exists but is not running; use restart or reboot" return 1 fi fi if test -n "$TAG" then if test $(echo $TAG | cut -c1-1) != '-' ; then TAG=":$TAG" shift else if [[ $TAG = '-' ]] ; then shift fi unset TAG fi fi if test -z "$TAG" -a -n "$DEFAULT_TAG"; then TAG=":$DEFAULT_TAG" fi if [[ "$CONFIG" =~ ^public ]] ; then if test -n "$ARVBOX_PUBLISH_IP" ; then localip=$ARVBOX_PUBLISH_IP else defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }') localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/') fi echo "Public arvbox will use address $localip" iptemp=$(mktemp) echo $localip > $iptemp chmod og+r $iptemp PUBLIC="--volume=$iptemp:/var/run/localip_override --publish=443:443 --publish=3001:3001 --publish=8000:8000 --publish=8900:8900 --publish=9000:9000 --publish=9002:9002 --publish=9004:9004 --publish=25101:25101 --publish=8001:8001 --publish=8002:8002 --publish=4202:4202 --publish=45000-45020:45000-45020" else PUBLIC="" fi if [[ "$CONFIG" =~ demo$ ]] ; then if test -d "$ARVBOX_DATA" ; then echo "It looks like you already have a development container named $ARVBOX_CONTAINER." echo "Set environment variable ARVBOX_CONTAINER to set a different name for your demo container" exit 1 fi if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then docker create -v /var/lib/postgresql -v $ARVADOS_CONTAINER_PATH --name $ARVBOX_CONTAINER-data arvados/arvbox-demo$TAG /bin/true fi docker run \ --detach \ --name=$ARVBOX_CONTAINER \ --privileged \ --volumes-from $ARVBOX_CONTAINER-data \ --label "org.arvados.arvbox_config=$CONFIG" \ $PUBLIC \ arvados/arvbox-demo$TAG updateconf wait_for_arvbox else mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS" "$PIPCACHE" "$NPMCACHE" "$GOSTUFF" "$RLIBS" if ! test -d "$ARVADOS_ROOT" ; then git clone https://git.arvados.org/arvados.git "$ARVADOS_ROOT" git -C "$ARVADOS_ROOT" checkout $ARVADOS_BRANCH fi if ! test -d "$WORKBENCH2_ROOT" ; then git clone https://git.arvados.org/arvados-workbench2.git "$WORKBENCH2_ROOT" git -C "$ARVADOS_ROOT" checkout $WORKBENCH2_BRANCH fi if [[ "$CONFIG" = test ]] ; then mkdir -p $VAR_DATA/test if test "$need_setup" = 1 ; then docker_run_dev \ --detach \ --name=$ARVBOX_CONTAINER \ --privileged \ "--env=SVDIR=/etc/test-service" \ arvados/arvbox-dev$TAG docker exec -ti \ $ARVBOX_CONTAINER \ /usr/local/lib/arvbox/runsu.sh \ /usr/local/lib/arvbox/waitforpostgres.sh fi interactive="" if [[ -z "$@" ]] ; then interactive=--interactive fi docker exec -ti \ -e LINES=$(tput lines) \ -e COLUMNS=$(tput cols) \ -e TERM=$TERM \ -e WORKSPACE=/usr/src/arvados \ -e CONFIGSRC=$ARVADOS_CONTAINER_PATH/run_tests \ $ARVBOX_CONTAINER \ /usr/local/lib/arvbox/runsu.sh \ /usr/src/arvados/build/run-tests.sh \ --temp $ARVADOS_CONTAINER_PATH/test \ $interactive \ "$@" elif [[ "$CONFIG" = devenv ]] ; then if [[ $need_setup = 1 ]] ; then docker_run_dev \ --detach \ --name=${ARVBOX_CONTAINER} \ "--env=SVDIR=/etc/devenv-service" \ "--volume=$HOME:$HOME:rw" \ --volume=/tmp/.X11-unix:/tmp/.X11-unix:rw \ arvados/arvbox-dev$TAG fi exec docker exec --interactive --tty \ -e LINES=$(tput lines) \ -e COLUMNS=$(tput cols) \ -e TERM=$TERM \ -e "ARVBOX_HOME=$HOME" \ -e "DISPLAY=$DISPLAY" \ --workdir=$PWD \ ${ARVBOX_CONTAINER} \ /usr/local/lib/arvbox/devenv.sh "$@" elif [[ "$CONFIG" =~ dev$ ]] ; then docker_run_dev \ --detach \ --name=$ARVBOX_CONTAINER \ --privileged \ $PUBLIC \ arvados/arvbox-dev$TAG updateconf wait_for_arvbox echo "The Arvados source code is checked out at: $ARVADOS_ROOT" echo "The Arvados testing root certificate is $VAR_DATA/root-cert.pem" if [[ "$(listusers)" =~ ^\{\} ]] ; then echo "No users defined, use 'arvbox adduser' to add user logins" else echo "Use 'arvbox listusers' to see user logins" fi else echo "Unknown configuration '$CONFIG'" fi fi } update() { CONFIG=$1 TAG=$2 if test -n "$TAG" then if test $(echo $TAG | cut -c1-1) != '-' ; then TAG=":$TAG" shift else unset TAG fi fi if echo "$CONFIG" | grep 'demo$' ; then docker pull arvados/arvbox-demo$TAG else docker pull arvados/arvbox-dev$TAG fi } stop() { if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then docker stop $ARVBOX_CONTAINER fi VOLUMES=--volumes=true if docker ps -a --filter "status=created" | grep -E "$ARVBOX_CONTAINER$" -q ; then docker rm $VOLUMES $ARVBOX_CONTAINER fi if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then docker rm $VOLUMES $ARVBOX_CONTAINER fi } build() { export DOCKER_BUILDKIT=1 if ! test -f "$ARVBOX_DOCKER/Dockerfile.base" ; then echo "Could not find Dockerfile (expected it at $ARVBOX_DOCKER/Dockerfile.base)" exit 1 fi if docker --version |grep " 1\.[0-9]\." ; then # Docker version prior 1.10 require -f flag # -f flag removed in Docker 1.12 FORCE=-f fi GITHEAD=$(cd $ARVBOX_DOCKER && git log --format=%H -n1 HEAD) set +e if which greadlink >/dev/null 2>/dev/null ; then LOCAL_ARVADOS_ROOT=$(greadlink -f $(dirname $0)/../../../) else LOCAL_ARVADOS_ROOT=$(readlink -f $(dirname $0)/../../../) fi set -e # Get the go version we should use for bootstrapping GO_VERSION=`grep 'const goversion =' $LOCAL_ARVADOS_ROOT/lib/install/deps.go |awk -F'"' '{print $2}'` if test "$1" = localdemo -o "$1" = publicdemo ; then BUILDTYPE=demo else BUILDTYPE=dev fi if test "$ARVADOS_BRANCH" = "main" ; then ARVADOS_BRANCH=$GITHEAD fi docker build --build-arg=BUILDTYPE=$BUILDTYPE $NO_CACHE \ --build-arg=go_version=$GO_VERSION \ --build-arg=arvados_version=$ARVADOS_BRANCH \ --build-arg=workbench2_version=$WORKBENCH2_BRANCH \ --build-arg=workdir=/tools/arvbox/lib/arvbox/docker \ -t arvados/arvbox-base:$GITHEAD \ -f "$ARVBOX_DOCKER/Dockerfile.base" \ "$LOCAL_ARVADOS_ROOT" docker tag $FORCE arvados/arvbox-base:$GITHEAD arvados/arvbox-base:latest docker build $NO_CACHE \ --build-arg=go_version=$GO_VERSION \ --build-arg=arvados_version=$ARVADOS_BRANCH \ --build-arg=workbench2_version=$WORKBENCH2_BRANCH \ -t arvados/arvbox-$BUILDTYPE:$GITHEAD \ -f "$ARVBOX_DOCKER/Dockerfile.$BUILDTYPE" \ "$ARVBOX_DOCKER" docker tag $FORCE arvados/arvbox-$BUILDTYPE:$GITHEAD arvados/arvbox-$BUILDTYPE:latest } check() { case "$1" in localdemo|publicdemo|dev|publicdev|test|devenv) true ;; *) echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test, devenv" exit 1 ;; esac } subcmd="$1" if test -n "$subcmd" ; then shift fi case "$subcmd" in build) check $@ build $@ ;; rebuild) check $@ NO_CACHE=--no-cache build $@ ;; start|run) check $@ run $@ ;; sh*) exec docker exec --interactive --tty \ -e LINES=$(tput lines) \ -e COLUMNS=$(tput cols) \ -e TERM=$TERM \ $ARVBOX_CONTAINER /bin/bash ;; ash*) exec docker exec --interactive --tty \ -e LINES=$(tput lines) \ -e COLUMNS=$(tput cols) \ -e TERM=$TERM \ -u arvbox \ -w /usr/src/arvados \ $ARVBOX_CONTAINER /bin/bash --login ;; pipe) exec docker exec -i $ARVBOX_CONTAINER /usr/bin/env /bin/bash - ;; stop) stop ;; restart) check $@ stop run $@ ;; reboot) check $@ stop build $@ run $@ ;; update) check $@ stop update $@ run $@ ;; ip) getip ;; host) gethost ;; open) exec xdg-open http://$(gethost) ;; status) echo "Container: $ARVBOX_CONTAINER" if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then echo "Cluster id: $(getclusterid)" echo "Status: running" echo "Container IP: $(getip)" echo "Published host: $(gethost)" echo "Workbench: https://$(gethost)" else echo "Status: not running" fi if test -d "$ARVBOX_DATA" ; then echo "Data: $ARVBOX_DATA" elif docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q ; then echo "Data: $ARVBOX_CONTAINER-data" else echo "Data: none" fi ;; reset|destroy) stop if test -d "$ARVBOX_DATA" ; then if test "$subcmd" = destroy ; then if test "$1" != -f ; then echo "WARNING! This will delete your entire arvbox ($ARVBOX_DATA)." echo "Use destroy -f if you really mean it." exit 1 fi set -x chmod -R u+w "$ARVBOX_DATA" rm -rf "$ARVBOX_DATA" else if test "$1" != -f ; then echo "WARNING! This will delete your arvbox data ($ARVBOX_DATA)." echo "Code and downloaded packages will be preserved." echo "Use reset -f if you really mean it." exit 1 fi set -x rm -rf "$ARVBOX_DATA/postgres" rm -rf "$ARVBOX_DATA/var" fi else if test "$1" != -f ; then echo "WARNING! This will delete your data container $ARVBOX_CONTAINER-data. Use -f if you really mean it." exit 1 fi set -x docker rm "$ARVBOX_CONTAINER-data" fi ;; log) if test -n "$1" ; then 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" else exec docker exec -ti $ARVBOX_CONTAINER tail $(docker exec -ti $ARVBOX_CONTAINER find -L /etc -path '/etc/service/*/log/main/current' -printf " %p") fi ;; cat) if test -n "$1" ; then exec docker exec $ARVBOX_CONTAINER cat "$@" else echo "Usage: $0 $subcmd " fi ;; ls) exec docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM ls "$@" ;; sv) if test -n "$1" -a -n "$2" ; then exec docker exec $ARVBOX_CONTAINER sv "$@" else echo "Usage: $0 $subcmd " echo "Available services:" exec docker exec $ARVBOX_CONTAINER ls /etc/service fi ;; clone) if test -n "$2" ; then mkdir -p "$ARVBOX_BASE/$2" cp -a "$ARVBOX_BASE/$1/passenger" \ "$ARVBOX_BASE/$1/gems" \ "$ARVBOX_BASE/$1/pip" \ "$ARVBOX_BASE/$1/npm" \ "$ARVBOX_BASE/$1/gopath" \ "$ARVBOX_BASE/$1/Rlibs" \ "$ARVBOX_BASE/$1/arvados" \ "$ARVBOX_BASE/$1/workbench2" \ "$ARVBOX_BASE/$2" echo "Created new arvbox $2" echo "export ARVBOX_CONTAINER=$2" else echo "clone clone an arvbox" echo "available arvboxes: $(ls $ARVBOX_BASE)" fi ;; root-cert) CERT=$PWD/${ARVBOX_CONTAINER}-root-cert.crt if test -n "$1" ; then CERT="$1" fi docker exec $ARVBOX_CONTAINER cat $ARVADOS_CONTAINER_PATH/root-cert.pem > "$CERT" echo "Certificate copied to $CERT" ;; psql) exec docker exec -ti $ARVBOX_CONTAINER bash -c 'PGPASSWORD=$(cat $ARVADOS_CONTAINER_PATH/api_database_pw) exec psql --dbname=arvados_development --host=localhost --username=arvados' ;; checkpoint) exec docker exec -ti $ARVBOX_CONTAINER bash -c 'PGPASSWORD=$(cat $ARVADOS_CONTAINER_PATH/api_database_pw) exec pg_dump --host=localhost --username=arvados --clean arvados_development > $ARVADOS_CONTAINER_PATH/checkpoint.sql' ;; restore) exec docker exec -ti $ARVBOX_CONTAINER bash -c 'PGPASSWORD=$(cat $ARVADOS_CONTAINER_PATH/api_database_pw) exec psql --dbname=arvados_development --host=localhost --username=arvados --quiet --file=$ARVADOS_CONTAINER_PATH/checkpoint.sql' ;; hotreset) exec docker exec -i $ARVBOX_CONTAINER /usr/bin/env /bin/bash - < [password]" fi ;; removeuser) if [[ -n "$1" ]] ; then docker exec -ti $ARVBOX_CONTAINER /usr/local/lib/arvbox/edit_users.py $ARVADOS_CONTAINER_PATH/cluster_config.yml.override $(getclusterid) remove $@ docker exec $ARVBOX_CONTAINER sv restart controller else echo "Usage: removeuser " fi ;; listusers) listusers ;; *) echo "Arvados-in-a-box https://doc.arvados.org/install/arvbox.html" echo echo "start|run [tag] start $ARVBOX_CONTAINER container" echo "stop stop arvbox container" echo "restart stop, then run again" echo "status print some information about current arvbox" echo "ip print arvbox docker container ip address" echo "host print arvbox published host" echo "shell enter shell as root" echo "ashell enter shell as 'arvbox'" echo "psql enter postgres console" echo "open open arvbox workbench in a web browser" echo "root-cert get copy of root certificate" echo "update stop, pull latest image, run" echo "build build arvbox Docker image" echo "reboot stop, build arvbox Docker image, run" echo "rebuild build arvbox Docker image, no layer cache" echo "checkpoint create database backup" echo "restore restore checkpoint" echo "hotreset reset database and restart API without restarting container" echo "reset delete arvbox arvados data (be careful!)" echo "destroy delete all arvbox code and data (be careful!)" echo "log tail log of specified service" echo "ls list directories inside arvbox" echo "cat get contents of files inside arvbox" echo "pipe run a bash script piped in from stdin" echo "sv " echo " change state of service inside arvbox" echo "clone clone dev arvbox" echo "adduser [password]" echo " add a user login" echo "removeuser " echo " remove user login" echo "listusers list user logins" ;; esac