changeset 1769:38072c439ed4

Implement non-gui version of 'thermostat setup'. Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-September/015680.html PR2581
author Severin Gehwolf <sgehwolf@redhat.com>
date Thu, 03 Sep 2015 15:19:46 +0200
parents c216ed271e1e
children 8e13b9b29f10
files distribution/config/devsetup.input distribution/scripts/thermostat-devsetup distribution/scripts/thermostat-setup integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/DevWebStorageTest.java setup/command/pom.xml setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupCommand.java setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/CLISetup.java setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/PasswordCredentialsReader.java setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/UsernameCredentialsReader.java setup/command/src/main/java/com/redhat/thermostat/setup/command/locale/LocaleResources.java setup/command/src/main/resources/com/redhat/thermostat/setup/locale/strings.properties setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/SetupCommandTest.java setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/CLISetupTest.java setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/CharArrayMatcher.java setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/PasswordCredentialsReaderTest.java setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/UsernameCredentialsReaderTest.java setup/distribution/thermostat-plugin.xml
diffstat 17 files changed, 1178 insertions(+), 322 deletions(-) [+]
line wrap: on
line diff
--- a/distribution/config/devsetup.input	Tue Sep 01 16:03:14 2015 +0200
+++ b/distribution/config/devsetup.input	Thu Sep 03 15:19:46 2015 +0200
@@ -2,3 +2,9 @@
 ${mongodb.dev.username}
 ${mongodb.dev.password}
 ${mongodb.dev.password}
+client-tester
+tester
+tester
+agent-tester
+tester
+tester
--- a/distribution/scripts/thermostat-devsetup	Tue Sep 01 16:03:14 2015 +0200
+++ b/distribution/scripts/thermostat-devsetup	Thu Sep 03 15:19:46 2015 +0200
@@ -54,17 +54,4 @@
 
 # Call the setup script
 $THERMOSTAT_SETUP < $DEV_INPUT
-retval=$?
-
-if [ $retval -ne 0 ]; then
-  echo "Development setup failed." 1>&2
-  exit $retval
-fi
-
-# Use example users/roles files:
-#  thermostat-users-example.properties
-#  thermostat-roles-example.properties
-cp -v $THERMOSTAT_HOME/etc/examples/thermostat-users-example.properties $THERMOSTAT_HOME/etc/thermostat-users.properties
-cp -v $THERMOSTAT_HOME/etc/examples/thermostat-roles-example.properties $THERMOSTAT_HOME/etc/thermostat-roles.properties
-# Use the agent-tester user as defined in thermostat-users-example.properties
-echo -e 'username=agent-tester\npassword=tester' > $USER_THERMOSTAT_HOME/etc/agent.auth
+exit $?
--- a/distribution/scripts/thermostat-setup	Tue Sep 01 16:03:14 2015 +0200
+++ b/distribution/scripts/thermostat-setup	Thu Sep 03 15:19:46 2015 +0200
@@ -35,289 +35,15 @@
 # to do so, delete this exception statement from your version.
 #
 #####################################################################
-#
-# Setup utility script in order to assist users in setting up
-# mongodb credentials and set web.xml accordingly.
-# 
-# Upon a new install of thermostat it will not run any command
-# until this setup utility has been run. Upon completion this
-# script creates a STAMP file in $USER_THERMOSTAT_HOME, namely:
-# $USER_THERMOSTAT_HOME/data/setup-complete.stamp
-# If the file was created by this utility, it will have content
-# similar to:
-# "Created by thermostat-setup on YYYY-MM-DD-hh:mm:ss"
-# 
-# Since this script merely uses thermostat commands for setting
-# up the mongodb user, this causes a chicken-and-egg problem.
-# We work around this by temporily creating an empty
-# setup-complete-file and replacing it by the real file upon
-# successful completion.
-
-# For debugging
-#set -xv
-
-tty_flags="$(stty -g)"
-
-# Interruptions are failures
-trap exitFail SIGINT
-
-displayThermostatBlurb() {
-  cat <<EOF
-
-Thermostat Basics:
-
-Thermostat is a database backed distributed monitoring
-solution with support for monitoring multiple JVM instances.
-The system is made up of three (3) components:
-
-  1. Agents which collect data.
-  2. Clients, which allow users to visualize collected data.
-  3. Storage layer which connects the above componets.
-
-The storage layer itself is two-tiered. The endpoint agents
-and clients communicate with is a web application which uses
-Mongodb as a backing database.
-
-This script helps with a setup where all three components
-run on a local system. In particular, it helps with the setup
-of the storage layer. First, a user is set up in mongodb which
-the web endpoint will use for database connections. Second,
-the web endpoint's servlet config is updated so as to use the
-credentials of this database user.
-
-Please see the Thermostat User Guide for more information on
-how to connect clients and agents to the pre-configured storage
-layer:
-http://icedtea.classpath.org/wiki/Thermostat/UserGuide
-
--------------------------------------------------------------
-
-EOF
-}
-
-setThermostatEnvironment() {
-  # Thermostat home
-  if [ "x$THERMOSTAT_HOME" = "x" ] ; then
-    THERMOSTAT_HOME="@thermostat.home@"
-  fi
+if [ x"$THERMOSTAT_INSTALL_DIR" = x ] ; then
+  THERMOSTAT_INSTALL_DIR="@thermostat.home@"
+fi
+# Not always are installation directory and thermostat home one and
+# the same location.
+if [ x"$THERMOSTAT_HOME" = x ] ; then
+  THERMOSTAT_HOME=${THERMOSTAT_INSTALL_DIR}
   export THERMOSTAT_HOME
-  # Thermostat user home
-  if [ "x$USER_THERMOSTAT_HOME" = "x" ]; then
-    USER_THERMOSTAT_HOME="$HOME/.thermostat"
-  fi
-  export USER_THERMOSTAT_HOME
-  THERMOSTAT="$THERMOSTAT_HOME/bin/thermostat"
-}
-
-setStampCompleteVars() {
-  scriptName="$(basename $0)"
-  timestamp="$(date '+%Y-%m-%d-%H:%M:%S %Z')"
-  SETUP_TMP_UNLOCK_CONTENT="Temporarily unlocked thermostat via $scriptName on $timestamp"
-  SETUP_UNLOCK_CONTENT_REGULAR="Created by $scriptName on $timestamp"
-  SETUP_UNLOCK_CONTENT_READ_ONLY_WEBXML="Created by $scriptName on $timestamp (web.xml read-only)"
-}
-
-exitFail() {
-  # Remove temp stamp file if we've created it
-  # in this script already, but failed somewhere
-  # down the line.
-  removeTempStampFile
-  echo 'Thermostat setup failed!' 1>&2
-  stty "$tty_flags"
-  exit 1
-}
-
-removeTempStampFile() {
-  if [ ! -z "$SETUP_TMP_UNLOCK_CONTENT" ] && 
-     [ ! -z "$SETUP_COMPLETE_FILE" ] && 
-     [ -e $SETUP_COMPLETE_FILE ] &&
-     grep -s "$SETUP_TMP_UNLOCK_CONTENT" "$SETUP_COMPLETE_FILE" > /dev/null; then
-    rm "$SETUP_COMPLETE_FILE"
-  fi
-}
-
-exitSuccess() {
-  # Remove temporary unlock file and create the actual setup-complete
-  # file.
-  removeTempStampFile
-  echo $SETUP_UNLOCK_CONTENT_REGULAR > "$SETUP_COMPLETE_FILE"
-  echo -e "\nThermostat setup complete!\n"
-  echo -e "Be sure to configure thermostat-users.properties and"
-  echo -e "thermostat-roles.properties before you attempt connections"
-  echo -e "with agent(s) and/or client(s)."
-  exit 0
-}
-
-exitSuccessNotWritable() {
-  # Remove temporary unlock file. Note, we intentionally
-  # do not clean up the sed files, since the user might
-  # want to use them still.
-  removeTempStampFile
-  # Create successful stamp file with note that
-  # web.xml might not have been properly set up.
-  echo $SETUP_UNLOCK_CONTENT_READ_ONLY_WEBXML > "$SETUP_COMPLETE_FILE"
-  echo "Thermostat setup complete!"
-  echo -e "\nBe sure to configure $TH_WEB_AUTH as mentioned above."
-  echo -e "\nThen, make sure to configure thermostat-users.properties and"
-  echo -e "thermostat-roles.properties before you attempt connections"
-  echo -e "with agent(s) and/or client(s)."
-  exit 0
-}
-
-unlockThermostat() {
-  setThermostatEnvironment
-  setStampCompleteVars
-  SETUP_COMPLETE_FILE="$USER_THERMOSTAT_HOME/data/setup-complete.stamp"
-  if [ -e "$SETUP_COMPLETE_FILE" ]; then
-    echo "File $SETUP_COMPLETE_FILE exists. Skipping setup!"
-    exit 0
-  fi
-  # Be sure to create the "data" parent directory if it does not
-  # yet exist.
-  local datadir="$(dirname $SETUP_COMPLETE_FILE)"
-  if [ ! -e "$datadir" ]; then
-    mkdir -p "$datadir"
-  fi
-  echo "$SETUP_TMP_UNLOCK_CONTENT" > "$SETUP_COMPLETE_FILE"
-}
-
-runSetup() {
-  unlockThermostat
-  setupMongodbUser
-  TH_WEB_AUTH="$THERMOSTAT_HOME/etc/web.auth"
-  
-  if [ ! -e $TH_WEB_AUTH ]; then
-    echo "File not found: $TH_WEB_AUTH" 1>&2
-    exitFail
-  fi
-
-  if [ ! -w $TH_WEB_AUTH ]; then
-    echo -e "\n\n$(readlink -f $TH_WEB_AUTH) is NOT writable."
-    mkdir -p "$USER_THERMOSTAT_HOME/etc/"
-    TH_WEB_AUTH="$USER_THERMOSTAT_HOME/etc/web.auth"
-    echo -e "Writing to $TH_WEB_AUTH\n"
-  fi
-  local success=0
-  echo "storage.username = $USERNAME" > "$TH_WEB_AUTH"
-  success=$(( $sedSuccess + $? ))
-  echo "storage.password = $PASSWORD" >> "$TH_WEB_AUTH"
-  success=$(( $sedSuccess + $? ))
-  chmod 640 "$TH_WEB_AUTH"
-  success=$(( $sedSuccess + $? ))
-  if [ $success -eq 0 ]; then
-    exitSuccess
-  else
-    echo "Automatic substitution of file $TH_WEB_AUTH failed!" 1>&2
-    exitFail
-  fi
-}
-
-readUsername() {
-  dUsername="$1"
-  prompt="Please enter the desired Mongodb username (press return in order to use '$dUsername'): "
-  read -p "$prompt" USERNAME 
-  if [ "x$USERNAME" = "x" ]; then
-    USERNAME="$dUsername"
-  fi
-  echo "Chosen username is '$USERNAME'"
-}
-
-# Read in the desired password, not permitting
-# empty passwords.
-readFirstPassword() {
-  prompt="Please enter the desired password for user '$USERNAME': "
-  while true; do
-    read -s -p "$prompt" PASSWORD 
-    if [ "x$PASSWORD" = "x" ]; then
-      echo -e "\nPassword must not be empty. Please try again."
-    else
-      echo # Formatting
-      break
-    fi
-  done
-}
-
-# Read in the password confirmation
-confirmReadPassword() {
-  firstTry="$1"
-  prompt="Please confirm password for user '$USERNAME': "
-  read -s -p "$prompt" confirmedPwd 
-  # Passwords cannot be empty
-  if [ "$firstTry" != "$confirmedPwd" ]; then
-    echo -e "\nPasswords did not match. Please try again."
-    PASSWORDS_MATCH="no"
-  else
-    PASSWORDS_MATCH="yes"
-  fi
-}
-
-readPassword() {
-  while true; do
-    readFirstPassword
-    confirmReadPassword "$PASSWORD"
-    if [ "x$PASSWORDS_MATCH" = "xyes" ]; then
-      break;
-    fi
-  done
-}
-
-setupMongodbUser() {
-  # Generate some form of a random default username
-  defaultName="thermostat-user-$(date +%s)"
-  echo -e "\nStarting Mongodb user setup ...\n"
-  echo -e "Next, you are required to enter the username/password,"
-  echo -e "which the web storage endpoint will use for mongodb"
-  echo -e "connections.\n"
-  readUsername "$defaultName"
-  readPassword
-  $THERMOSTAT_HOME/bin/thermostat storage --start --permitLocalhostException
-  MONGOD_RETVAL="$?"
-  if [ "$MONGOD_RETVAL" -ne 0 ] ; then
-    echo -e "\nMongodb user setup failed. Error starting storage." 1>&2
-    exitFail
-  fi
-  sleep 3
-  mkdir -p $USER_THERMOSTAT_HOME
-  touch $USER_THERMOSTAT_HOME/creds.js
-  chmod u=rw $USER_THERMOSTAT_HOME/creds.js
-  sed -e s/'\$USERNAME'/"$USERNAME"/g -e s/'\$PASSWORD'/"$PASSWORD"/g $THERMOSTAT_HOME/libs/create-user.js > $USER_THERMOSTAT_HOME/creds.js
-  mongo 127.0.0.1:27518/thermostat $USER_THERMOSTAT_HOME/creds.js
-  MONGO_SETUP_RETVAL="$?"
-  rm $USER_THERMOSTAT_HOME/creds.js
-  if [ "$MONGO_SETUP_RETVAL" -ne 0 ] ; then
-    echo -e "\nMongodb user setup failed." 1>&2
-    exitFail
-  fi
-  $THERMOSTAT_HOME/bin/thermostat storage --stop
-  MONGO_SETUP_RETVAL="$?"
-  if [ "$MONGO_SETUP_RETVAL" -ne 0 ] ; then
-    echo -e "\nMongodb user setup failed." 1>&2
-    exitFail
-  fi
-  touch "$USER_THERMOSTAT_HOME"/data/mongodb-user-done.stamp
-}
-
-doProceedLoop() {
-  DO_SETUP=1
-  while true; do
-    read -p "Ready to proceed? Please type 'yes' or 'no': " RESPONSE
-    if [ "x$RESPONSE" = "xyes" ]; then
-      break
-    elif [ "x$RESPONSE" = "xno" ]; then
-      DO_SETUP=0
-      break
-    else
-      echo "Unknown response. Please only type 'yes' or 'no'"
-    fi
-  done
-}
-
-displayThermostatBlurb 
-doProceedLoop
-
-if [ $DO_SETUP -eq 0 ]; then
-  echo "Exiting on user request. Bye!"
-  exit 0
-else
-  runSetup
 fi
+echo "THIS SCRIPT IS DEPRECATED! Please use 'thermostat setup -c' instead." 1>&2
+# Call the thermostat non-gui version of setup
+${THERMOSTAT_HOME}/bin/thermostat setup -c
--- a/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/DevWebStorageTest.java	Tue Sep 01 16:03:14 2015 +0200
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/DevWebStorageTest.java	Thu Sep 03 15:19:46 2015 +0200
@@ -119,7 +119,7 @@
         Spawn setup = spawnScript(THERMOSTAT_DEV_SETUP_SCRIPT, new String[] {});
         setup.expectClose();
         String stdout = setup.getCurrentStandardOutContents();
-        assertTrue(stdout.contains("Thermostat setup complete!"));
+        assertTrue(stdout.contains("Setup finished successfully."));
         assertTrue(stdout.contains("mongodevuser"));
     }
 }
--- a/setup/command/pom.xml	Tue Sep 01 16:03:14 2015 +0200
+++ b/setup/command/pom.xml	Thu Sep 03 15:19:46 2015 +0200
@@ -63,6 +63,7 @@
             <Private-Package>
               com.redhat.thermostat.setup.command.internal,
               com.redhat.thermostat.setup.command.internal.model,
+              com.redhat.thermostat.setup.command.internal.cli,
               com.redhat.thermostat.setup.command.locale,
             </Private-Package>
             <Bundle-Activator>com.redhat.thermostat.setup.command.internal.Activator</Bundle-Activator>
--- a/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupCommand.java	Tue Sep 01 16:03:14 2015 +0200
+++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/SetupCommand.java	Thu Sep 03 15:19:46 2015 +0200
@@ -38,6 +38,11 @@
 
 import java.awt.EventQueue;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -45,10 +50,13 @@
 import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.Console;
 import com.redhat.thermostat.common.cli.DependencyServices;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.internal.utils.laf.ThemeManager;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.setup.command.internal.SetupWindow;
+import com.redhat.thermostat.setup.command.internal.cli.CLISetup;
 import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup;
 import com.redhat.thermostat.setup.command.locale.LocaleResources;
 import com.redhat.thermostat.shared.config.CommonPaths;
@@ -59,13 +67,13 @@
 
     private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
     private static final String ORIG_CMD_ARGUMENT_NAME = "origArgs";
+    private static final String NON_GUI_OPTION_NAME = "nonGui";
     private static final Logger logger = LoggingUtils.getLogger(SetupCommand.class);
     private final DependencyServices dependentServices = new DependencyServices();
     private SetupWindow mainWindow;
     private CommonPaths paths;
     private Launcher launcher;
     private Keyring keyring;
-    private ThermostatSetup thermostatSetup;
     private String[] origArgsList;
 
     @Override
@@ -74,23 +82,46 @@
         if (args.hasArgument(ORIG_CMD_ARGUMENT_NAME)) {
             String origArgs = args.getArgument(ORIG_CMD_ARGUMENT_NAME);
             origArgsList = origArgs.split("\\|\\|\\|");
+            if (isSetupInvocation(origArgsList)) {
+                String[] optionArgs = Arrays.copyOfRange(origArgsList, 1, origArgsList.length);
+                args = mergeOriginalArgs(optionArgs, args);
+            }
         }
         
+        
+        this.paths = dependentServices.getService(CommonPaths.class);
+        requireNonNull(paths, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "CommonPaths"));
+        this.launcher = dependentServices.getService(Launcher.class);
+        requireNonNull(launcher, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Launcher"));
+        this.keyring = dependentServices.getService(Keyring.class);
+        requireNonNull(keyring, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Keyring"));
+        ThermostatSetup setup = createSetup();
+        if (args.hasArgument(NON_GUI_OPTION_NAME)) {
+            runCLISetup(setup, ctx.getConsole());
+        } else {
+            runGUISetup(setup);
+        }
+        
+        runOriginalCommand(origArgsList);
+    }
+
+    private Arguments mergeOriginalArgs(String[] origArgsList, Arguments args) {
+        logger.fine("Intercepted setup invocation 'setup' " + Arrays.asList(origArgsList).toString());
+        return new MergedSetupArguments(args, origArgsList);
+    }
+
+    private void runCLISetup(ThermostatSetup setup, Console console) throws CommandException {
+        CLISetup cliSetup = new CLISetup(setup, console);
+        cliSetup.run();
+    }
+
+    private void runGUISetup(ThermostatSetup setup) throws CommandException {
         try {
             setLookAndFeel();
-
-            this.paths = dependentServices.getService(CommonPaths.class);
-            requireNonNull(paths, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "CommonPaths"));
-            this.launcher = dependentServices.getService(Launcher.class);
-            requireNonNull(launcher, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Launcher"));
-            this.keyring = dependentServices.getService(Keyring.class);
-            requireNonNull(keyring, t.localize(LocaleResources.SERVICE_UNAVAILABLE_MESSAGE, "Keyring"));
-
-            createMainWindowAndRun();
+            createMainWindowAndRun(setup);
         } catch (InterruptedException | InvocationTargetException e) {
             throw new CommandException(t.localize(LocaleResources.SETUP_FAILED), e);
         }
-        runOriginalCommand(origArgsList);
     }
 
     private void runOriginalCommand(String[] args) {
@@ -100,13 +131,17 @@
         if (args.length == 0) {
             throw new AssertionError("Original command args were empty!");
         }
-        if (args[0].equals("setup")) {
+        if (isSetupInvocation(args)) {
             // Do not run setup recursively
             return;
         }
         logger.log(Level.FINE, "Running intercepted command '" + args[0] + "' after setup.");
         launcher.run(args, false);
     }
+    
+    private boolean isSetupInvocation(String[] args) {
+        return args[0].equals("setup");
+    }
 
     public void setPaths(CommonPaths paths) {
         dependentServices.addService(CommonPaths.class, paths);
@@ -130,14 +165,13 @@
         return false;
     }
 
-    //package-private for testing
-    void createMainWindowAndRun() throws CommandException {
-        thermostatSetup = ThermostatSetup.create(launcher, paths, keyring);
-        mainWindow = new SetupWindow(thermostatSetup);
+    // package-private for testing
+    void createMainWindowAndRun(ThermostatSetup setup) throws CommandException {
+        mainWindow = new SetupWindow(setup);
         mainWindow.run();
     }
 
-    //package-private for testing
+    // package-private for testing
     void setLookAndFeel() throws InvocationTargetException, InterruptedException {
         EventQueue.invokeAndWait(new Runnable() {
             @Override
@@ -147,5 +181,87 @@
             }
         });
     }
+    
+    // package-private for testing
+    ThermostatSetup createSetup() {
+        return ThermostatSetup.create(launcher, paths, keyring);
+    }
+    
+    static class MergedSetupArguments implements Arguments {
+
+        private static final String SINGLE_DASH = "-";
+        private static final String DOUBLE_DASH = "--";
+        private static final String NON_GUI_SHORT_OPT = "c";
+        
+        private final Arguments argsDelegate;
+        private final String[] origArgs;
+        private final Map<String, String> additionalOptions;
+        
+        MergedSetupArguments(Arguments args, String[] origArgs) {
+            this.origArgs = origArgs;
+            this.argsDelegate = Objects.requireNonNull(args);
+            this.additionalOptions = buildAdditionalOptions(origArgs);
+        }
+        
+        private Map<String, String> buildAdditionalOptions(String[] origArgs) {
+            Map<String, String> options = new HashMap<>();
+            for (int i = 0; i < origArgs.length; i++) {
+                String opt = origArgs[i];
+                String value = "NONE";
+                if (opt.startsWith(DOUBLE_DASH)) {
+                    if (i + 1 < origArgs.length && isOptionArg(origArgs, i)) {
+                        value = origArgs[i + 1];
+                        i++; // skip argument
+                    }
+                    options.put(opt.substring(2), value);
+                    continue;
+                } else if (opt.startsWith(SINGLE_DASH)) {
+                    // This is a poor-man's version of short-arg parsing for
+                    // non-gui setup option.
+                    String cleanedOp = opt.substring(1);
+                    if (cleanedOp.equals(NON_GUI_SHORT_OPT)) {
+                        options.put(NON_GUI_OPTION_NAME, Boolean.TRUE.toString());
+                    }
+                    continue;
+                } else {
+                    throw new AssertionError("Invalid option for setup: " + opt);
+                }
+            }
+            return options;
+        }
+        
+        private boolean isOptionArg(String[] origArgs, int i) {
+            return !(origArgs[i + 1].startsWith(SINGLE_DASH) || origArgs[i + 1].startsWith(DOUBLE_DASH));
+        }
+
+        @Override
+        public List<String> getNonOptionArguments() {
+            return argsDelegate.getNonOptionArguments();
+        }
+
+        @Override
+        public boolean hasArgument(String name) {
+            boolean delegateHasArgument = argsDelegate.hasArgument(name);
+            if (delegateHasArgument) {
+                return true;
+            }
+            return additionalOptions.keySet().contains(name);
+        }
+
+        @Override
+        public String getArgument(String name) {
+            String arg = argsDelegate.getArgument(name);
+            if (arg != null) {
+                return arg;
+            }
+            return additionalOptions.get(name);
+        }
+        
+        @Override
+        public String toString() {
+            return "setup [delegate=" + argsDelegate.toString() + ",origArgs=" + Arrays.asList(origArgs).toString() + "]";
+        }
+        
+    }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/CLISetup.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.ApplicationInfo;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup;
+import com.redhat.thermostat.setup.command.locale.LocaleResources;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+
+public class CLISetup {
+    
+    private static final Logger logger = LoggingUtils.getLogger(CLISetup.class);
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private final ThermostatSetup thermostatSetup;
+    private final Console console;
+    private final PrintWriter outWriter;
+    private final PrintWriter errWriter;
+    
+    public CLISetup(ThermostatSetup setup, Console console) {
+        this.thermostatSetup = setup;
+        this.console = console;
+        this.outWriter = new PrintWriter(console.getOutput());
+        this.errWriter = new PrintWriter(console.getError());
+    }
+    
+    public void run() throws CommandException {
+        runSetup();
+        println(LocaleResources.CLI_SETUP_FINISH_SUCCESS);
+    }
+
+    private void runSetup() throws CommandException {
+        try {
+            printBlurb();
+            boolean shouldContinue = readContinueAnswer();
+            if (!shouldContinue) {
+                throw new CommandException(t.localize(LocaleResources.SETUP_CANCELLED));
+            }
+            readMongodbCredentials();
+            if (thermostatSetup.isWebAppInstalled()) {
+                readThermostatUserCredentials();
+            }
+            thermostatSetup.commit();
+        } catch (IOException e) {
+            logger.log(Level.INFO, "Setup failed. ", e);
+            throw new CommandException(t.localize(LocaleResources.SETUP_FAILED), e);
+        }
+    }
+
+    // package-private for testing
+    void readThermostatUserCredentials() throws IOException {
+        println(LocaleResources.CLI_SETUP_THERMOSTAT_USER_CREDS_INTRO);
+        LocalizedString clientUsernamePrompt = t.localize(LocaleResources.CLI_SETUP_THERMOSTAT_CLIENT_USERNAME_PROMPT);
+        UsernameCredentialsReader clientUserReader = new UsernameCredentialsReader(console, clientUsernamePrompt);
+        String clientUsername = clientUserReader.read();
+        LocalizedString passwordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_PROMPT, clientUsername);
+        LocalizedString passwordPromptRepeat = t.localize(LocaleResources.CLI_SETUP_PASSWORD_REPEAT_PROMPT, clientUsername);
+        PasswordCredentialsReader clientPasswordReader = new PasswordCredentialsReader(console, passwordPrompt, passwordPromptRepeat);
+        char[] clientPassword = clientPasswordReader.readPassword();
+        thermostatSetup.createClientAdminUser(clientUsername, clientPassword);
+        
+        LocalizedString agentUsernamePrompt = t.localize(LocaleResources.CLI_SETUP_THERMOSTAT_AGENT_USERNAME_PROMPT);
+        UsernameCredentialsReader agentUserReader = new UsernameCredentialsReader(console, agentUsernamePrompt);
+        String agentUsername = agentUserReader.read();
+        passwordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_PROMPT, agentUsername);
+        passwordPromptRepeat = t.localize(LocaleResources.CLI_SETUP_PASSWORD_REPEAT_PROMPT, agentUsername);
+        PasswordCredentialsReader agentPasswordReader = new PasswordCredentialsReader(console, passwordPrompt, passwordPromptRepeat);
+        char[] agentPassword = agentPasswordReader.readPassword();
+        thermostatSetup.createAgentUser(agentUsername, agentPassword);
+    }
+
+    // package-private for testing
+    void readMongodbCredentials() throws IOException {
+        println(LocaleResources.CLI_SETUP_MONGODB_USER_CREDS_INTRO);
+        LocalizedString usernamePrompt = t.localize(LocaleResources.CLI_SETUP_MONGODB_USERNAME_PROMPT);
+        UsernameCredentialsReader usernameReader = new UsernameCredentialsReader(console, usernamePrompt);
+        String username = usernameReader.read();
+        LocalizedString passwordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_PROMPT, username);
+        LocalizedString confirmPasswordPrompt = t.localize(LocaleResources.CLI_SETUP_PASSWORD_REPEAT_PROMPT, username);
+        PasswordCredentialsReader passwordReader = new PasswordCredentialsReader(console, passwordPrompt, confirmPasswordPrompt);
+        char[] password = passwordReader.readPassword();
+        thermostatSetup.createMongodbUser(username, password);
+    }
+
+    /**
+     * 
+     * @return {@code true} if user wants to continue, {@code false} otherwise.
+     * 
+     * @throws IOException 
+     */
+    private boolean readContinueAnswer() throws IOException {
+        final String localizedProceedToken = t.localize(LocaleResources.CLI_SETUP_PROCEED_WORD).getContents();
+        final String localizedCancelToken = t.localize(LocaleResources.CLI_SETUP_CANCEL_WORD).getContents();
+        LocalizedString yes = t.localize(LocaleResources.CLI_SETUP_YES);
+        LocalizedString no = t.localize(LocaleResources.CLI_SETUP_NO);
+        print(LocaleResources.CLI_SETUP_PROCEED_QUESTION, yes.getContents(), no.getContents());
+        String input;
+        InputStream in = console.getInput();
+        final int maxTries = 100;
+        int currTry = 0;
+        do {
+            input = readLine(in);
+            if (input.equals(localizedCancelToken)) {
+                return false;
+            }
+            if (input.equals(localizedProceedToken)) {
+                return true;
+            }
+            printErr(LocaleResources.CLI_SETUP_UNKNOWN_RESPONSE, input, yes.getContents(), no.getContents());
+            currTry++;
+        } while (currTry < maxTries);
+        logger.log(Level.WARNING, "Tried " + maxTries + " times with invalid input. Cancelling.");
+        return false;
+    }
+    
+    private String readLine(InputStream in) throws IOException {
+        int c;
+        StringBuilder builder = new StringBuilder();
+        while ((c = in.read()) != -1) {
+            char token = (char)c;
+            if (token == '\n') {
+                break;
+            }
+            builder.append(token);
+        }
+        return builder.toString();
+    }
+
+    private void printBlurb() {
+        String userGuideURL = new ApplicationInfo().getUserGuide();
+        println(LocaleResources.CLI_SETUP_INTRO, userGuideURL);
+    }
+    
+    private void println(LocaleResources resource, String... strings) {
+        outWriter.println(t.localize(resource, strings).getContents());
+        outWriter.flush();
+    }
+    
+    private void print(LocaleResources resource, String... strings) {
+        outWriter.print(t.localize(resource, strings).getContents());
+        outWriter.flush();
+    }
+
+    private void printErr(LocaleResources resource, String... strings) {
+        errWriter.println(t.localize(resource, strings).getContents());
+        errWriter.flush();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/PasswordCredentialsReader.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Objects;
+
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.common.tools.StorageAuthInfoGetter;
+import com.redhat.thermostat.setup.command.internal.model.UserCredsValidator;
+import com.redhat.thermostat.setup.command.locale.LocaleResources;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+
+class PasswordCredentialsReader {
+
+    private static final LocalizedString UNUSED = new LocalizedString("ignored");
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private final Console console;
+    private final LocalizedString passwordPrompt;
+    private final LocalizedString confirmPasswordPrompt;
+    private final PrintWriter errWriter;
+    
+    PasswordCredentialsReader(Console console, LocalizedString passwordPrompt, LocalizedString confirmPasswordPrompt) {
+        this.console = console;
+        this.passwordPrompt = passwordPrompt;
+        this.confirmPasswordPrompt = confirmPasswordPrompt;
+        this.errWriter = new PrintWriter(console.getError());
+    }
+    
+    // Read in passwords twice. Once the initial password, a second time we
+    // expect the same content again for confirmation.
+    char[] readPassword() throws IOException {
+        char[] password;
+        char[] confirmation;
+        boolean isValid = false;
+        do {
+            StorageAuthInfoGetter getter = new StorageAuthInfoGetter(console, UNUSED, passwordPrompt);
+            password = getter.getPassword(null);
+            StorageAuthInfoGetter confirmGetter = new StorageAuthInfoGetter(console, UNUSED, confirmPasswordPrompt);
+            confirmation = confirmGetter.getPassword(null);
+            try {
+                verifyPassword(password, confirmation);
+                Arrays.fill(confirmation, '\0');
+                confirmation = null;
+                isValid = true;
+            } catch (InvalidPasswordException e) {
+                printError(LocaleResources.CLI_SETUP_PASSWORD_INVALID);
+                clearPasswords(password, confirmation);
+            } catch (PasswordMismatchException e) {
+                printError(LocaleResources.CLI_SETUP_PASSWORD_MISMATCH);
+                clearPasswords(password, confirmation);
+            }
+        } while (!isValid);
+        return password;
+    }
+    
+    private void clearPasswords(char[] password, char[] confirmation) {
+        Arrays.fill(password, '\0');
+        Arrays.fill(confirmation, '\0');
+        password = null;
+        confirmation = null;
+    }
+
+    private void printError(LocaleResources resource, String...strings) {
+        errWriter.println(t.localize(resource, strings).getContents());
+        errWriter.flush();
+    }
+    
+    private void verifyPassword(char[] password, char[] confirmation) throws InvalidPasswordException, PasswordMismatchException {
+        checkPasswordsMatch(password, confirmation);
+        UserCredsValidator validator = new UserCredsValidator();
+        try {
+            validator.validatePassword(password);
+            validator.validatePassword(confirmation);
+        } catch (IllegalArgumentException e) {
+            throw new InvalidPasswordException();
+        }
+    }
+
+    void checkPasswordsMatch(char[] first, char[] second) throws PasswordMismatchException {
+        // Auth getter never returns null. Use as precondition
+        Objects.requireNonNull(first);
+        Objects.requireNonNull(second);
+        
+        if (first.length != second.length) {
+            throw new PasswordMismatchException();
+        }
+        for (int i = 0; i < first.length; i++) {
+            if (first[i] != second[i]) {
+                throw new PasswordMismatchException();
+            }
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class PasswordMismatchException extends Exception {
+        // nothing
+    }
+    
+    @SuppressWarnings("serial")
+    private static class InvalidPasswordException extends Exception {
+        // nothing
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/cli/UsernameCredentialsReader.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.common.tools.StorageAuthInfoGetter;
+import com.redhat.thermostat.setup.command.internal.model.UserCredsValidator;
+import com.redhat.thermostat.setup.command.locale.LocaleResources;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+
+class UsernameCredentialsReader {
+
+    private static final LocalizedString UNUSED = new LocalizedString("ignored");
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private final Console console;
+    private final LocalizedString usernamePrompt;
+    private final PrintWriter errWriter;
+    
+    UsernameCredentialsReader(Console console, LocalizedString usernamePrompt) {
+        this.console = console;
+        this.usernamePrompt = usernamePrompt;
+        this.errWriter = new PrintWriter(console.getError());
+    }
+    
+    String read() throws IOException {
+        StorageAuthInfoGetter getter = new StorageAuthInfoGetter(console, usernamePrompt, UNUSED);
+        boolean isValid = false;
+        UserCredsValidator validator = new UserCredsValidator();
+        String username = null;
+        while (!isValid) {
+            username = getter.getUserName(null);
+            try {
+                validator.validateUsername(username);
+                isValid = true;
+            } catch (IllegalArgumentException e) {
+                printError(LocaleResources.CLI_SETUP_USERNAME_INVALID, username);
+                // continue loop
+            }
+        }
+        return username;
+    }
+
+    private void printError(LocaleResources resource, String...strings) {
+        errWriter.println(t.localize(resource, strings).getContents());
+        errWriter.flush();
+    }
+    
+}
--- a/setup/command/src/main/java/com/redhat/thermostat/setup/command/locale/LocaleResources.java	Tue Sep 01 16:03:14 2015 +0200
+++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/locale/LocaleResources.java	Thu Sep 03 15:19:46 2015 +0200
@@ -69,13 +69,32 @@
     USERNAME,
     PASSWORD,
     VERIFY_PASSWORD,
+    CLI_SETUP_INTRO,
+    CLI_SETUP_PROCEED_QUESTION,
+    CLI_SETUP_UNKNOWN_RESPONSE,
+    CLI_SETUP_PROCEED_WORD,
+    CLI_SETUP_CANCEL_WORD,
+    CLI_SETUP_YES,
+    CLI_SETUP_NO,
+    CLI_SETUP_PASSWORD_INVALID,
+    CLI_SETUP_PASSWORD_MISMATCH,
+    CLI_SETUP_USERNAME_INVALID,
+    CLI_SETUP_MONGODB_USER_CREDS_INTRO,
+    CLI_SETUP_MONGODB_USERNAME_PROMPT,
+    CLI_SETUP_PASSWORD_PROMPT,
+    CLI_SETUP_USERNAME_REPEAT,
+    CLI_SETUP_PASSWORD_REPEAT_PROMPT,
+    CLI_SETUP_THERMOSTAT_USER_CREDS_INTRO,
+    CLI_SETUP_THERMOSTAT_CLIENT_USERNAME_PROMPT, 
+    CLI_SETUP_THERMOSTAT_AGENT_USERNAME_PROMPT,
+    CLI_SETUP_FINISH_SUCCESS,
     ;
 
     static final String RESOURCE_BUNDLE =
             "com.redhat.thermostat.setup.locale.strings";
 
     public static Translate<LocaleResources> createLocalizer() {
-        return new Translate(RESOURCE_BUNDLE, LocaleResources.class);
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
     }
 
 }
--- a/setup/command/src/main/resources/com/redhat/thermostat/setup/locale/strings.properties	Tue Sep 01 16:03:14 2015 +0200
+++ b/setup/command/src/main/resources/com/redhat/thermostat/setup/locale/strings.properties	Thu Sep 03 15:19:46 2015 +0200
@@ -73,7 +73,7 @@
 
 SETUP_INTERRUPTED=Setup was interrupted.
 
-SETUP_CANCELLED=Setup was cancelled.
+SETUP_CANCELLED=Setup cancelled on user request.
 
 PASSWORD_MISMATCH=Passwords don't match!
 
@@ -87,4 +87,47 @@
 
 PASSWORD=Password:
 
-VERIFY_PASSWORD=Verify Password:
\ No newline at end of file
+VERIFY_PASSWORD=Verify Password:
+
+CLI_SETUP_INTRO=Thermostat Basics: \n\
+ Thermostat is a database backed distributed monitoring \n\
+ solution with support for monitoring multiple JVM instances. \n\
+ The system is made up of three (3) components: \n\
+ \n\
+   1. Agents which collect data. \n\
+   2. Clients, which allow users to visualize collected data. \n\
+   3. Storage layer which connects the above componets. \n\
+ \n\
+ The storage layer itself is two-tiered. The endpoint agents \n\
+ and clients communicate with is a web application which uses \n\
+ Mongodb as a backing database. \n\
+ \n\
+ This script helps with a setup where all three components \n\
+ run on a local system. First, a user is set up in mongodb which \n\
+ the web endpoint will use for database connections. Second, \n\
+ thermostat users for ''thermostat agent'' and thermostat clients \n\
+ are set up. \n\
+ \n\
+ Please see the Thermostat User Guide for more information: \n\
+ {0} \n\
+ \n\
+ -------------------------------------------------------------\n
+ 
+CLI_SETUP_PROCEED_QUESTION=Ready to proceed? Please type ''{0}'' or ''{1}'': 
+CLI_SETUP_UNKNOWN_RESPONSE=Unknown response ''{0}''. Please only type ''{1}'' or ''{2}''
+CLI_SETUP_PROCEED_WORD=yes
+CLI_SETUP_CANCEL_WORD=no
+CLI_SETUP_YES=yes
+CLI_SETUP_NO=no
+CLI_SETUP_PASSWORD_MISMATCH=Passwords did not match!
+CLI_SETUP_PASSWORD_INVALID=Chosen password invalid!
+CLI_SETUP_USERNAME_INVALID=Chosen username ''{0}'' invalid!
+CLI_SETUP_MONGODB_USER_CREDS_INTRO=----- Mongodb User Setup -----
+CLI_SETUP_MONGODB_USERNAME_PROMPT=Please enter the desired Mongodb username: 
+CLI_SETUP_USERNAME_REPEAT=Chosen username is ''{0}''. 
+CLI_SETUP_PASSWORD_PROMPT=Please enter the desired password for ''{0}'': 
+CLI_SETUP_PASSWORD_REPEAT_PROMPT=Please repeat password for ''{0}'': 
+CLI_SETUP_THERMOSTAT_USER_CREDS_INTRO=----- Thermostat User Setup -----
+CLI_SETUP_THERMOSTAT_CLIENT_USERNAME_PROMPT=Please enter the desired username for the Thermostat client: 
+CLI_SETUP_THERMOSTAT_AGENT_USERNAME_PROMPT=Please enter the desired username for the Thermostat agent: 
+CLI_SETUP_FINISH_SUCCESS=Setup finished successfully.
\ No newline at end of file
--- a/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/SetupCommandTest.java	Tue Sep 01 16:03:14 2015 +0200
+++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/SetupCommandTest.java	Thu Sep 03 15:19:46 2015 +0200
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.setup.command.internal;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.argThat;
@@ -45,7 +46,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
@@ -64,6 +67,8 @@
 import com.redhat.thermostat.common.cli.Console;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.setup.command.internal.SetupCommand;
+import com.redhat.thermostat.setup.command.internal.cli.CharArrayMatcher;
+import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup;
 import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
@@ -79,10 +84,12 @@
     private CommonPaths paths;
     private Launcher launcher;
     private Keyring keyring;
+    private ThermostatSetup thermostatSetup;
 
     @Before
     public void setUp() {
         paths = mock(CommonPaths.class);
+        when(paths.getUserClientConfigurationFile()).thenReturn(mock(File.class));
         ctxt = mock(CommandContext.class);
         mockArgs = mock(Arguments.class);
         console = mock(Console.class);
@@ -94,6 +101,7 @@
         error = new PrintStream(errorBaos);
         launcher = mock(Launcher.class);
         keyring = mock(Keyring.class);
+        thermostatSetup = mock(ThermostatSetup.class);
 
         when(ctxt.getArguments()).thenReturn(mockArgs);
         when(ctxt.getConsole()).thenReturn(console);
@@ -112,7 +120,7 @@
             }
 
             @Override
-            void createMainWindowAndRun() {
+            void createMainWindowAndRun(ThermostatSetup setup) {
                 //do nothing
             }
         };
@@ -133,7 +141,7 @@
             }
 
             @Override
-            void createMainWindowAndRun() {
+            void createMainWindowAndRun(ThermostatSetup setup) {
                 isSet[0] = true;
             }
         };
@@ -199,6 +207,32 @@
         verify(launcher, times(0)).run(argThat(new ArgsMatcher(new String[] { "setup" })), eq(false));
     }
     
+    @Test
+    public void verifySetupAsOrigCommandNonGui() throws CommandException {
+        cmd = createSetupCommand();
+        setServices();
+        
+        Arguments args = mock(Arguments.class);
+        CommandContext ctxt = mock(CommandContext.class);
+        when(ctxt.getArguments()).thenReturn(args);
+        when(args.hasArgument("origArgs")).thenReturn(true);
+        when(args.getArgument("origArgs")).thenReturn("setup|||-c");
+        when(ctxt.getConsole()).thenReturn(console);
+        when(thermostatSetup.isWebAppInstalled()).thenReturn(true);
+        when(console.getInput())
+            .thenReturn(new ByteArrayInputStream("yes\nmongo\nm\nm\nclient\nc\nc\nagent\na\na\n".getBytes()));
+        
+        cmd.run(ctxt);
+        verify(thermostatSetup).createAgentUser(eq("agent"), argThat(matchesPassword(new char[] { 'a' })));
+        verify(thermostatSetup).createClientAdminUser(eq("client"), argThat(matchesPassword(new char[] { 'c' })));
+        verify(thermostatSetup).createMongodbUser(eq("mongo"), argThat(matchesPassword(new char[] { 'm' })));
+        verify(launcher, times(0)).run(argThat(new ArgsMatcher(new String[] { "setup", "-c" })), eq(false));
+    }
+    
+    private CharArrayMatcher matchesPassword(char[] expected) {
+        return new CharArrayMatcher(expected);
+    }
+    
     private void doTestOriginalCmdRunsAfterSetup(String origArgs, String[] argsList) throws CommandException {
         cmd = createSetupCommand();
         setServices();
@@ -235,12 +269,41 @@
             }
 
             @Override
-            void createMainWindowAndRun() {
+            void createMainWindowAndRun(ThermostatSetup setup) {
                 //do nothing
             }
+            
+            @Override
+            ThermostatSetup createSetup() {
+                return thermostatSetup;
+            }
         };
     }
     
+    @Test
+    public void mergedSetupArgumentsTest() {
+        Arguments args = mock(Arguments.class);
+        when(args.hasArgument("nonGui")).thenReturn(false);
+        String[] origArgs = new String[] { "-c", "--nonGui" };
+        SetupCommand.MergedSetupArguments arguments = new SetupCommand.MergedSetupArguments(args, origArgs);
+        assertTrue(arguments.hasArgument("nonGui"));
+        // short version of nonGui
+        origArgs = new String[] { "-c" };
+        arguments = new SetupCommand.MergedSetupArguments(args, origArgs);
+        assertTrue(arguments.hasArgument("nonGui"));
+        // long version
+        origArgs = new String[] { "--nonGui" };
+        arguments = new SetupCommand.MergedSetupArguments(args, origArgs);
+        assertTrue(arguments.hasArgument("nonGui"));
+        
+        // unrelated option
+        when(args.getArgument("something")).thenReturn(null);
+        origArgs = new String[] { "--something", "someVal", "-c" };
+        arguments = new SetupCommand.MergedSetupArguments(args, origArgs);
+        assertEquals("someVal", arguments.getArgument("something"));
+        assertTrue(arguments.hasArgument("nonGui"));
+    }
+    
     private void setServices() {
         cmd.setPaths(paths);
         cmd.setLauncher(launcher);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/CLISetupTest.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.setup.command.internal.model.ThermostatSetup;
+
+public class CLISetupTest {
+
+    private CLISetup cliSetup;
+    private ThermostatSetup thermostatSetup;
+    private Console console;
+    private ByteArrayOutputStream berr;
+    private ByteArrayOutputStream bout;
+    
+    @Before
+    public void setup() {
+        thermostatSetup = mock(ThermostatSetup.class);
+        console = mock(Console.class);
+        berr = new ByteArrayOutputStream();
+        when(console.getError()).thenReturn(new PrintStream(berr));
+        bout = new ByteArrayOutputStream();
+        when(console.getOutput()).thenReturn(new PrintStream(bout));
+        cliSetup = new CLISetup(thermostatSetup, console);
+    }
+    
+    @Test
+    public void testCancelProceedLoop() throws IOException {
+        String input = "no\n";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        try {
+            cliSetup.run();
+            fail("Setup expected to throw CommandException on cancel");
+        } catch (CommandException e) {
+            assertEquals("Setup cancelled on user request.", e.getMessage());
+        }
+        String output = new String(bout.toByteArray());
+        assertTrue(output.contains("Ready to proceed?"));
+    }
+    
+    @Test
+    public void testProceedLoopWithMoreInput() throws IOException {
+        String input = "no\nsomethingMore\n";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        try {
+            cliSetup.run();
+            fail("Setup expected to throw CommandException on cancel");
+        } catch (CommandException e) {
+            assertEquals("Setup cancelled on user request.", e.getMessage());
+        }
+        String output = new String(bout.toByteArray());
+        assertTrue(output.contains("Ready to proceed?"));
+        // Verify that we can still read from the stream and we haven't read too
+        // much.
+        byte[] buf = new byte[input.length()];
+        int retval = mockInStream.read(buf, 3, input.length() - 3);
+        assertEquals("Read more bytes than are needed!", input.length() - 3, retval);
+        assertEquals("Expected 'e' from somethingMor(e)", 'e', (char)buf[input.length() - 2]);
+    }
+    
+    @Test
+    public void testUnknownInputProceedLoop() throws IOException {
+        String input = "somethingNotYes\nno\n";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        try {
+            cliSetup.run();
+            fail("Setup expected to throw CommandException on cancel");
+        } catch (CommandException e) {
+            assertEquals("Setup cancelled on user request.", e.getMessage());
+        }
+        String errBuf = new String(berr.toByteArray());
+        assertTrue("Expected unknown input msg! Got " + errBuf, errBuf.startsWith("Unknown response 'somethingNotYes'"));
+    }
+    
+    @Test
+    public void testNoInputProceedLoopCancels() throws InterruptedException {
+        String input = "";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        try {
+            cliSetup.run();
+            fail("Expected setup to cancel");
+        } catch (CommandException e) {
+            assertEquals("Setup cancelled on user request.", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void testReadMongodbCreds() throws IOException {
+        String input = "somevalidusername\nt\nt\n";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        cliSetup.readMongodbCredentials();
+        verify(thermostatSetup).createMongodbUser(eq("somevalidusername"), argThat(matchesPassword(new char[] { 't' })));
+        String output = new String(bout.toByteArray());
+        assertTrue("Expected user setup blurb", output.contains("Mongodb User Setup"));
+        assertTrue("Expected somevalidusername in output. Got: " + output, output.contains("somevalidusername"));
+        assertEquals("Expected no errors", "", new String(berr.toByteArray()));
+    }
+    
+    @Test
+    public void testReadMongodbCreds2() throws IOException, CommandException {
+        String input = "yes\nsomevalidusername\nt\nt\n";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        when(thermostatSetup.isWebAppInstalled()).thenReturn(false);
+        cliSetup.run();
+        verify(thermostatSetup).createMongodbUser(eq("somevalidusername"), argThat(matchesPassword(new char[] { 't' })));
+        String output = new String(bout.toByteArray());
+        assertTrue("Expected user setup blurb", output.contains("Mongodb User Setup"));
+        assertTrue("Expected somevalidusername in output. Got: " + output, output.contains("somevalidusername"));
+        assertEquals("Expected no errors", "", new String(berr.toByteArray()));
+    }
+    
+    @Test
+    public void testReadThermostatCreds() throws IOException {
+        String input = "client-user\nt\nt\nagent-user\nb\nb\n";
+        ByteArrayInputStream mockInStream = new ByteArrayInputStream(input.getBytes());
+        when(console.getInput()).thenReturn(mockInStream);
+        cliSetup.readThermostatUserCredentials();
+        verify(thermostatSetup).createAgentUser(eq("agent-user"), argThat(matchesPassword(new char[] { 'b' })));
+        verify(thermostatSetup).createClientAdminUser(eq("client-user"), argThat(matchesPassword(new char[] { 't' })));
+        String output = new String(bout.toByteArray());
+        assertTrue("Expected user setup blurb", output.contains("Thermostat User Setup"));
+        assertTrue("Expected client-user in output. Got: " + output, output.contains("client-user"));
+        assertTrue("Expected agent-user in output. Got: " + output, output.contains("agent-user"));
+        assertEquals("Expected no errors", "", new String(berr.toByteArray()));
+    }
+    
+    @Test
+    public void canCreateUsersFromStdInput() throws CommandException {
+        // simulate webapp being installed
+        when(thermostatSetup.isWebAppInstalled()).thenReturn(true);
+        
+        String input = "yes\nmongodb-user\nfoo\nfoo\nclient-user\nt\nt\nagent-user\nb\nb\n";
+        when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes()));
+        cliSetup.run();
+        verify(thermostatSetup).createMongodbUser(eq("mongodb-user"), argThat(matchesPassword(new char[] { 'f', 'o', 'o' })));
+        verify(thermostatSetup).createAgentUser(eq("agent-user"), argThat(matchesPassword(new char[] { 'b' })));
+        verify(thermostatSetup).createClientAdminUser(eq("client-user"), argThat(matchesPassword(new char[] { 't' })));
+        String output = new String(bout.toByteArray());
+        assertTrue("Expected user setup blurb", output.contains("Thermostat User Setup"));
+        assertTrue("Expected client-user in output. Got: " + output, output.contains("client-user"));
+        assertTrue("Expected agent-user in output. Got: " + output, output.contains("agent-user"));
+        assertTrue("Expected mongodb-user in output. Got: " + output, output.contains("mongodb-user"));
+        assertEquals("Expected no errors", "", new String(berr.toByteArray()));
+    }
+    
+    private CharArrayMatcher matchesPassword(char[] array) {
+        return new CharArrayMatcher(array);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/CharArrayMatcher.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+
+public class CharArrayMatcher extends BaseMatcher<char[]> {
+
+    private final char[] expected;
+    public CharArrayMatcher(char[] expected) {
+        this.expected = expected;
+    }
+    
+    @Override
+    public boolean matches(Object arg0) {
+        if (arg0 == null || arg0.getClass() != char[].class) {
+            return false;
+        }
+        char[] other = (char[])arg0;
+        boolean matches = (expected.length == other.length);
+        if (!matches) {
+            return false;
+        }
+        for (int i = 0; i < expected.length; i++) {
+            matches = matches && expected[i] == other[i];
+        }
+        return matches;
+    }
+
+    @Override
+    public void describeTo(Description arg0) {
+        arg0.appendText(new String(expected));
+    }
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/PasswordCredentialsReaderTest.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+
+public class PasswordCredentialsReaderTest {
+
+    private Console console;
+    private PasswordCredentialsReader credsReader;
+    private LocalizedString passwordPrompt = new LocalizedString("tell me the password: ");
+    private LocalizedString confirmPasswordPrompt = new LocalizedString("repeat the password: ");
+    private ByteArrayOutputStream berr;
+    private ByteArrayOutputStream bout;
+    
+    @Before
+    public void setup() {
+        console = mock(Console.class);
+        berr = new ByteArrayOutputStream();
+        when(console.getError()).thenReturn(new PrintStream(berr));
+        bout = new ByteArrayOutputStream();
+        when(console.getOutput()).thenReturn(new PrintStream(bout));
+        credsReader = new PasswordCredentialsReader(console, passwordPrompt, confirmPasswordPrompt);
+    }
+    
+    @Test
+    public void testPasswordsMismatch() throws IOException {
+        String input = "first\nfirst2\nsecond\nsecond\n";
+        when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes()));
+        char[] password = credsReader.readPassword();
+        assertEquals("second", new String(password));
+        assertEquals("Passwords did not match!\n", new String(berr.toByteArray()));
+        assertEquals("tell me the password: \nrepeat the password: \ntell me the password: \nrepeat the password: \n", new String(bout.toByteArray()));
+    }
+    
+    @Test
+    public void testPasswordInvalid() throws IOException {
+        String input = "\n\na\na\n";
+        when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes()));
+        char[] password = credsReader.readPassword();
+        assertEquals("a", new String(password));
+        assertEquals("Chosen password invalid!\n", new String(berr.toByteArray()));
+        assertEquals("tell me the password: \nrepeat the password: \ntell me the password: \nrepeat the password: \n", new String(bout.toByteArray()));
+    }
+    
+    @Test
+    public void canGetPassword() throws IOException {
+        String input = "bar\nbar\n";
+        when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes()));
+        char[] password = credsReader.readPassword();
+        assertArrayEquals(new char[] { 'b', 'a', 'r' }, password);
+        assertEquals("Expected no errors", "", new String(berr.toByteArray()));
+        assertEquals("tell me the password: \nrepeat the password: \n", new String(bout.toByteArray()));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/command/src/test/java/com/redhat/thermostat/setup/command/internal/cli/UsernameCredentialsReaderTest.java	Thu Sep 03 15:19:46 2015 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.setup.command.internal.cli;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+
+public class UsernameCredentialsReaderTest {
+
+    private Console console;
+    private UsernameCredentialsReader credsReader;
+    private LocalizedString userPrompt = new LocalizedString("tell me the username: ");
+    private ByteArrayOutputStream berr;
+    private ByteArrayOutputStream bout;
+    
+    @Before
+    public void setup() {
+        console = mock(Console.class);
+        berr = new ByteArrayOutputStream();
+        when(console.getError()).thenReturn(new PrintStream(berr));
+        bout = new ByteArrayOutputStream();
+        when(console.getOutput()).thenReturn(new PrintStream(bout));
+        credsReader = new UsernameCredentialsReader(console, userPrompt);
+    }
+    
+    @Test
+    public void canGetUsername() throws IOException {
+        String input = "foo-user\n";
+        when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes()));
+        String username = credsReader.read();
+        assertEquals("foo-user", username);
+        assertEquals("Expected no errors", "", new String(berr.toByteArray()));
+        assertEquals("tell me the username: foo-user\n", new String(bout.toByteArray()));
+    }
+    
+    @Test
+    public void testUsernameInvalidAtFirst() throws IOException {
+        String input = "\ntry-second-time\n";
+        when(console.getInput()).thenReturn(new ByteArrayInputStream(input.getBytes()));
+        String username = credsReader.read();
+        assertEquals("try-second-time", username);
+        assertEquals("Chosen username '' invalid!\n", new String(berr.toByteArray()));
+        assertEquals("tell me the username: \ntell me the username: try-second-time\n", new String(bout.toByteArray()));
+    }
+}
--- a/setup/distribution/thermostat-plugin.xml	Tue Sep 01 16:03:14 2015 +0200
+++ b/setup/distribution/thermostat-plugin.xml	Thu Sep 03 15:19:46 2015 +0200
@@ -52,6 +52,12 @@
           <required>false</required>
           <description>A string holding '|||'-separated values of original arguments if a command got intercepted with setup.</description>
         </option>
+        <option>
+          <long>nonGui</long>
+          <short>c</short>
+          <required>false</required>
+          <description>Don't use the graphical user interface, but use the command line instead for interactive questions.</description>
+        </option>
       </options>
       <environments>
         <environment>cli</environment>