arvbox changes: yank Composer. Add link to documentation.
[arvados.git] / tools / arvbox / bin / arvbox
index a258b8e2329eeb2aa1c6b6707678e89d750849df..b3b9a5fcb441900535954012ebc0ee05f77bf10f 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: AGPL-3.0
@@ -19,11 +19,13 @@ if ! which docker >/dev/null 2>/dev/null ; then
 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
@@ -42,18 +44,21 @@ if test -z "$ARVADOS_ROOT" ; then
     ARVADOS_ROOT="$ARVBOX_DATA/arvados"
 fi
 
-if test -z "$SSO_ROOT" ; then
-    SSO_ROOT="$ARVBOX_DATA/sso-devise-omniauth-provider"
+if test -z "$WORKBENCH2_ROOT" ; then
+    WORKBENCH2_ROOT="$ARVBOX_DATA/workbench2"
 fi
 
-if test -z "$COMPOSER_ROOT" ; then
-    COMPOSER_ROOT="$ARVBOX_DATA/composer"
+if test -z "$ARVADOS_BRANCH" ; then
+    ARVADOS_BRANCH=main
 fi
 
-if test -z "$WORKBENCH2_ROOT" ; then
-    WORKBENCH2_ROOT="$ARVBOX_DATA/workbench2"
+if test -z "$WORKBENCH2_BRANCH" ; then
+    WORKBENCH2_BRANCH=main
 fi
 
+# Update this to the docker tag for the version on releases.
+DEFAULT_TAG=
+
 PG_DATA="$ARVBOX_DATA/postgres"
 VAR_DATA="$ARVBOX_DATA/var"
 PASSENGER="$ARVBOX_DATA/passenger"
@@ -62,9 +67,10 @@ 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 $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-
+    docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $ARVBOX_CONTAINER
 }
 
 gethost() {
@@ -80,7 +86,7 @@ gethost() {
 }
 
 getclusterid() {
-    docker exec $ARVBOX_CONTAINER cat /var/lib/arvados/api_uuid_prefix
+    docker exec $ARVBOX_CONTAINER cat $ARVADOS_CONTAINER_PATH/api_uuid_prefix
 }
 
 updateconf() {
@@ -97,16 +103,23 @@ EOF
     fi
 }
 
+listusers() {
+    docker exec -ti $ARVBOX_CONTAINER /usr/local/lib/arvbox/edit_users.py $ARVADOS_CONTAINER_PATH/cluster_config.yml $(getclusterid) list
+}
+
 wait_for_arvbox() {
     FF=/tmp/arvbox-fifo-$$
     mkfifo $FF
     docker logs -f $ARVBOX_CONTAINER > $FF &
     LOGPID=$!
     while read line ; do
-        if echo $line | grep "ok: down: ready:" >/dev/null ; then
+        if [[ $line =~ "ok: down: ready:" ]] ; then
             kill $LOGPID
-       else
-           echo $line
+            set +e
+            wait $LOGPID 2>/dev/null
+            set -e
+        else
+            echo $line
         fi
     done < $FF
     rm $FF
@@ -118,6 +131,26 @@ wait_for_arvbox() {
     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
@@ -127,39 +160,51 @@ run() {
     need_setup=1
 
     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
-        if test "$CONFIG" = test ; 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"
-            exit 0
+            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"
-            exit 1
+            return 1
         fi
     fi
 
     if test -n "$TAG"
     then
         if test $(echo $TAG | cut -c1-1) != '-' ; then
-           TAG=":$TAG"
+            TAG=":$TAG"
             shift
         else
+            if [[ $TAG = '-' ]] ; then
+                shift
+            fi
             unset TAG
         fi
     fi
 
-    if echo "$CONFIG" | grep '^public' ; then
+    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
-        iptemp=$(tempfile)
+        echo "Public arvbox will use address $localip"
+        iptemp=$(mktemp)
         echo $localip > $iptemp
         chmod og+r $iptemp
         PUBLIC="--volume=$iptemp:/var/run/localip_override
@@ -167,26 +212,27 @@ run() {
               --publish=3001:3001
               --publish=8000:8000
               --publish=8900:8900
-              --publish=9001:9001
+              --publish=9000:9000
               --publish=9002:9002
-              --publish=25100:25100
-              --publish=25107:25107
-              --publish=25108:25108
+              --publish=9004:9004
+              --publish=25101:25101
               --publish=8001:8001
-              --publish=8002:8002"
+              --publish=8002:8002
+              --publish=4202:4202
+              --publish=45000-45020:45000-45020"
     else
         PUBLIC=""
     fi
 
-    if echo "$CONFIG" | grep 'demo$' ; then
+    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 ARVBOX_CONTAINER to set a different name for your demo 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 /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
+            docker create -v /var/lib/postgresql -v $ARVADOS_CONTAINER_PATH --name $ARVBOX_CONTAINER-data arvados/arvbox-demo$TAG /bin/true
         fi
 
         docker run \
@@ -194,6 +240,7 @@ run() {
                --name=$ARVBOX_CONTAINER \
                --privileged \
                --volumes-from $ARVBOX_CONTAINER-data \
+               --label "org.arvados.arvbox_config=$CONFIG" \
                $PUBLIC \
                arvados/arvbox-demo$TAG
         updateconf
@@ -201,97 +248,112 @@ run() {
     else
         mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS" "$PIPCACHE" "$NPMCACHE" "$GOSTUFF" "$RLIBS"
 
-
         if ! test -d "$ARVADOS_ROOT" ; then
-            git clone https://github.com/curoverse/arvados.git "$ARVADOS_ROOT"
-        fi
-        if ! test -d "$SSO_ROOT" ; then
-            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git "$SSO_ROOT"
-        fi
-        if ! test -d "$COMPOSER_ROOT" ; then
-            git clone https://github.com/curoverse/composer.git "$COMPOSER_ROOT"
+            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://github.com/curoverse/arvados-workbench2.git "$WORKBENCH2_ROOT"
+            git clone https://git.arvados.org/arvados-workbench2.git "$WORKBENCH2_ROOT"
+           git -C "$ARVADOS_ROOT" checkout $WORKBENCH2_BRANCH
         fi
 
-        if test "$CONFIG" = test ; then
+        if [[ "$CONFIG" = test ]] ; then
 
             mkdir -p $VAR_DATA/test
 
             if test "$need_setup" = 1 ; then
-                docker run \
+                docker_run_dev \
                        --detach \
                        --name=$ARVBOX_CONTAINER \
                        --privileged \
-                       "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
-                       "--volume=$SSO_ROOT:/usr/src/sso:rw" \
-                       "--volume=$COMPOSER_ROOT:/usr/src/composer:rw" \
-                       "--volume=$WORKBENCH2_ROOT:/usr/src/workbench2:rw" \
-                       "--volume=$PG_DATA:/var/lib/postgresql:rw" \
-                       "--volume=$VAR_DATA:/var/lib/arvados:rw" \
-                       "--volume=$PASSENGER:/var/lib/passenger:rw" \
-                       "--volume=$GEMS:/var/lib/gems: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" \
-                      "--env=SVDIR=/etc/test-service" \
+                       "--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
 
-                docker exec -ti \
-                       $ARVBOX_CONTAINER \
-                       /usr/local/lib/arvbox/runsu.sh \
-                       /var/lib/arvbox/service/sso/run-service --only-setup
-
-                docker exec -ti \
-                       $ARVBOX_CONTAINER \
-                       /usr/local/lib/arvbox/runsu.sh \
-                       /var/lib/arvbox/service/api/run-service --only-setup
+            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 /var/lib/arvados/test \
-                   WORKSPACE=/usr/src/arvados \
-                   GEM_HOME=/var/lib/gems \
+                   --temp $ARVADOS_CONTAINER_PATH/test \
+                   $interactive \
                    "$@"
-        elif echo "$CONFIG" | grep 'dev$' ; then
-            docker run \
+        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 \
-                   "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
-                   "--volume=$SSO_ROOT:/usr/src/sso:rw" \
-                   "--volume=$COMPOSER_ROOT:/usr/src/composer:rw" \
-                   "--volume=$WORKBENCH2_ROOT:/usr/src/workbench2:rw" \
-                   "--volume=$PG_DATA:/var/lib/postgresql:rw" \
-                   "--volume=$VAR_DATA:/var/lib/arvados:rw" \
-                   "--volume=$PASSENGER:/var/lib/passenger:rw" \
-                   "--volume=$GEMS:/var/lib/gems: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" \
                    $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"
+            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
@@ -307,6 +369,7 @@ stop() {
 }
 
 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
@@ -317,24 +380,54 @@ build() {
         FORCE=-f
     fi
     GITHEAD=$(cd $ARVBOX_DOCKER && git log --format=%H -n1 HEAD)
-    docker build --build-arg=arvados_version=$GITHEAD $NO_CACHE -t arvados/arvbox-base:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
-    docker tag $FORCE arvados/arvbox-base:$GITHEAD arvados/arvbox-base:latest
+
+    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
-        docker build $NO_CACHE -t arvados/arvbox-demo:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.demo" "$ARVBOX_DOCKER"
-        docker tag $FORCE arvados/arvbox-demo:$GITHEAD arvados/arvbox-demo:latest
+        BUILDTYPE=demo
     else
-        docker build $NO_CACHE -t arvados/arvbox-dev:$GITHEAD -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
-        docker tag $FORCE arvados/arvbox-dev:$GITHEAD arvados/arvbox-dev:latest
+        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)
+        localdemo|publicdemo|dev|publicdev|test|devenv)
             true
             ;;
         *)
-            echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test"
+            echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test, devenv"
             exit 1
         ;;
     esac
@@ -361,11 +454,25 @@ case "$subcmd" in
         ;;
 
     sh*)
-        exec docker exec -ti -e LINES=$(tput lines) -e COLUMNS=$(tput cols) -e TERM=$TERM -e GEM_HOME=/var/lib/gems $ARVBOX_CONTAINER /bin/bash
+        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 GEM_HOME=/var/lib/gems /bin/bash -
+        exec docker exec -i $ARVBOX_CONTAINER /usr/bin/env /bin/bash -
         ;;
 
     stop)
@@ -385,6 +492,13 @@ case "$subcmd" in
         run $@
         ;;
 
+    update)
+        check $@
+        stop
+        update $@
+        run $@
+        ;;
+
     ip)
         getip
         ;;
@@ -400,10 +514,11 @@ case "$subcmd" in
     status)
         echo "Container: $ARVBOX_CONTAINER"
         if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
-           echo "Cluster id: $(getclusterid)"
+            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
@@ -426,6 +541,7 @@ case "$subcmd" in
                     exit 1
                 fi
                 set -x
+                chmod -R u+w "$ARVBOX_DATA"
                 rm -rf "$ARVBOX_DATA"
             else
                 if test "$1" != -f ; then
@@ -474,13 +590,22 @@ case "$subcmd" in
         else
             echo "Usage: $0 $subcmd <start|stop|restart> <service>"
             echo "Available services:"
-            exec docker execa $ARVBOX_CONTAINER ls /etc/service
+            exec docker exec $ARVBOX_CONTAINER ls /etc/service
         fi
         ;;
 
     clone)
         if test -n "$2" ; then
-            cp -r "$ARVBOX_BASE/$1" "$ARVBOX_BASE/$2"
+            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
@@ -489,27 +614,106 @@ case "$subcmd" in
         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 - <<EOF
+sv stop api
+sv stop controller
+sv stop websockets
+sv stop keepstore0
+sv stop keepstore1
+sv stop keepproxy
+cd /usr/src/arvados/services/api
+export DISABLE_DATABASE_ENVIRONMENT_CHECK=1
+export RAILS_ENV=development
+bin/bundle exec rake db:drop
+rm $ARVADOS_CONTAINER_PATH/api_database_setup
+rm $ARVADOS_CONTAINER_PATH/superuser_token
+sv start api
+sv start controller
+sv start websockets
+sv restart keepstore0
+sv restart keepstore1
+sv restart keepproxy
+EOF
+        ;;
+
+    adduser)
+if [[ -n "$2" ]] ; then
+          docker exec -ti $ARVBOX_CONTAINER /usr/local/lib/arvbox/edit_users.py $ARVADOS_CONTAINER_PATH/cluster_config.yml.override $(getclusterid) add $@
+          docker exec $ARVBOX_CONTAINER sv restart controller
+       else
+           echo "Usage: adduser <username> <email> [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 <username>"
+       fi
+        ;;
+
+    listusers)
+        listusers
+        ;;
+
     *)
-        echo "Arvados-in-a-box                      http://arvados.org"
+        echo "Arvados-in-a-box             https://doc.arvados.org/install/arvbox.html"
         echo
-        echo "build   <config>      build arvbox Docker image"
-        echo "rebuild <config>      build arvbox Docker image, no layer cache"
-        echo "start|run <config> [tag]  start $ARVBOX_CONTAINER container"
-        echo "open       open arvbox workbench in a web browser"
-        echo "shell      enter arvbox shell"
-        echo "ip         print arvbox docker container ip address"
-        echo "host       print arvbox published host"
-        echo "status     print some information about current arvbox"
-        echo "stop       stop arvbox container"
-        echo "restart <config>  stop, then run again"
-        echo "reboot  <config>  stop, build arvbox Docker image, run"
-        echo "reset      delete arvbox arvados data (be careful!)"
-        echo "destroy    delete all arvbox code and data (be careful!)"
-        echo "log <service> tail log of specified service"
-        echo "ls <options>  list directories inside arvbox"
-        echo "cat <files>   get contents of files inside arvbox"
-        echo "pipe       run a bash script piped in from stdin"
-        echo "sv <start|stop|restart> <service> change state of service inside arvbox"
-        echo "clone <from> <to>   clone an arvbox"
+        echo "start|run <config> [tag]   start $ARVBOX_CONTAINER container"
+        echo "stop               stop arvbox container"
+        echo "restart <config>   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  <config>   stop, pull latest image, run"
+        echo "build   <config>   build arvbox Docker image"
+        echo "reboot  <config>   stop, build arvbox Docker image, run"
+        echo "rebuild <config>   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 <service>      tail log of specified service"
+        echo "ls <options>       list directories inside arvbox"
+        echo "cat <files>        get contents of files inside arvbox"
+        echo "pipe               run a bash script piped in from stdin"
+        echo "sv <start|stop|restart> <service> "
+        echo "                   change state of service inside arvbox"
+        echo "clone <from> <to>  clone dev arvbox"
+        echo "adduser <username> <email> [password]"
+        echo "                   add a user login"
+        echo "removeuser <username>"
+        echo "                   remove user login"
+        echo "listusers          list user logins"
         ;;
 esac