You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2022/07/29 13:53:36 UTC

[brooklyn-server] 03/06: update other files to use new BashCommandsConfigurable rather than statics

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit e2084ef0da333d6d7ff39e45cc77869b237e01e7
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Fri Jul 29 02:08:56 2022 +0100

    update other files to use new BashCommandsConfigurable rather than statics
---
 .../core/effector/ssh/SshEffectorTasks.java        |  34 ++++---
 .../LocalhostMachineProvisioningLocation.java      |   2 +-
 .../brooklyn/util/core/file/ArchiveUtils.java      |  15 ++-
 .../util/core/internal/ssh/ShellAbstractTool.java  |   1 +
 .../brooklyn/util/core/task/ssh/SshTasks.java      |  21 ++--
 .../ssh/internal/AbstractSshExecTaskFactory.java   |   4 +-
 .../util/core/task/system/ProcessTaskFactory.java  |   2 +
 .../util/core/task/system/ProcessTaskStub.java     |  11 ++-
 .../util/core/task/system/ProcessTaskWrapper.java  |   2 +-
 .../internal/AbstractProcessTaskFactory.java       |  14 ++-
 .../system/internal/SystemProcessTaskFactory.java  |   4 +-
 .../persist/deserializingClassRenames.properties   |   4 +-
 .../core/effector/ssh/SshEffectorTasksTest.java    |   5 +-
 .../util/core/ssh/BashCommandsIntegrationTest.java | 109 ++++++++++-----------
 .../brooklyn/location/jclouds/JcloudsLocation.java |  43 ++++----
 .../policy/jclouds/os/CreateUserPolicy.java        |  11 ++-
 .../location/jclouds/JcloudsSuseLiveTest.java      |   4 +-
 ...eJcloudsLocationUserLoginAndConfigLiveTest.java |   5 +-
 .../brooklyn/entity/brooklynnode/BrooklynNode.java |   4 +-
 .../entity/brooklynnode/BrooklynNodeSshDriver.java |   7 +-
 .../entity/java/JavaSoftwareProcessSshDriver.java  |   7 +-
 .../brooklyn/entity/machine/MachineInitTasks.java  |  31 +++---
 .../entity/machine/SetHostnameCustomizer.java      |  12 ++-
 .../entity/machine/SetLimitsCustomizer.java        |   4 +-
 .../base/AbstractSoftwareProcessSshDriver.java     |  26 ++---
 .../base/VanillaSoftwareProcessSshDriver.java      |   8 +-
 .../system_service/InitdServiceInstaller.java      |  14 +--
 .../entity/AbstractMultiDistroLiveTest.java        |   5 +-
 .../test/mysql/DynamicToyMySqlEntityBuilder.java   |  21 ++--
 .../system_service/SystemServiceEnricherTest.java  |   4 +-
 .../util/ssh/IptablesCommandsFirewalldTest.java    |  21 ++--
 .../brooklyn/util/ssh/IptablesCommandsTest.java    |  20 ++--
 32 files changed, 269 insertions(+), 206 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
index bf6f0b783b..1ea2b816b6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
@@ -56,7 +56,7 @@ import org.apache.brooklyn.util.core.task.ssh.internal.PlainSshExecTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -253,20 +253,24 @@ public class SshEffectorTasks {
     }
 
 
+    @Deprecated /** @deprecated  since 1.1 supply bash context */
+    public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(final String pidFile) {
+        return codePidFromFileRunning(BashCommandsConfigurable.newInstance(), pidFile);
+    }
     /** task which returns 0 if pid in the given file is running;
      * method accepts wildcards so long as they match a single file on the remote end
      * <p>
      * returns 1 if no matching file, 
      * 1 if matching file but no matching process,
      * and 2 if 2+ matching files */
-    public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(final String pidFile) {
-        return ssh(BashCommands.chain(
+    public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(BashCommandsConfigurable bash, final String pidFile) {
+        return ssh(bash.chain(
                 // this fails, but isn't an error
-                BashCommands.requireTest("-f "+pidFile, "The PID file "+pidFile+" does not exist."),
+                bash.requireTest("-f "+pidFile, "The PID file "+pidFile+" does not exist."),
                 // this fails and logs an error picked up later
-                BashCommands.requireTest("`ls "+pidFile+" | wc -w` -eq 1", "ERROR: there are multiple matching PID files"),
+                bash.requireTest("`ls "+pidFile+" | wc -w` -eq 1", "ERROR: there are multiple matching PID files"),
                 // this fails and logs an error picked up later
-                BashCommands.require("cat "+pidFile, "ERROR: the PID file "+pidFile+" cannot be read (permissions?)."),
+                bash.require("cat "+pidFile, "ERROR: the PID file "+pidFile+" cannot be read (permissions?)."),
                 // finally check the process
                 "ps -p `cat "+pidFile+"`")).summary("PID file "+pidFile+" is-running check (exit code)")
                 .allowingNonZeroExitCode()
@@ -279,18 +283,26 @@ public class SshEffectorTasks {
                     }
                 });
     }
-    
+
+    @Deprecated /** @deprecated  since 1.1 supply bash context */
+    public static SshEffectorTaskFactory<?> requirePidFromFileRunning(String pidFile) {
+        return requirePidFromFileRunning(BashCommandsConfigurable.newInstance(), pidFile);
+    }
     /** task which fails if the pid in the given file is not running (or if there is no such PID file);
      * method accepts wildcards so long as they match a single file on the remote end (fails if 0 or 2+ matching files) */
-    public static SshEffectorTaskFactory<?> requirePidFromFileRunning(String pidFile) {
-        return codePidFromFileRunning(pidFile)
+    public static SshEffectorTaskFactory<?> requirePidFromFileRunning(BashCommandsConfigurable bash, String pidFile) {
+        return codePidFromFileRunning(bash, pidFile)
                 .summary("PID file "+pidFile+" is-running check (required)")
                 .requiringExitCodeZero("Process with PID from file "+pidFile+" is required to be running");
     }
 
-    /** as {@link #codePidFromFileRunning(String)} but returning boolean */
+    @Deprecated /** @deprecated  since 1.1 supply bash context */
     public static SshEffectorTaskFactory<Boolean> isPidFromFileRunning(String pidFile) {
-        return codePidFromFileRunning(pidFile).summary("PID file "+pidFile+" is-running check (boolean)").
+        return isPidFromFileRunning(BashCommandsConfigurable.newInstance(), pidFile);
+    }
+    /** as {@link #codePidFromFileRunning(BashCommandsConfigurable, String)} but returning boolean */
+    public static SshEffectorTaskFactory<Boolean> isPidFromFileRunning(BashCommandsConfigurable bash, String pidFile) {
+        return codePidFromFileRunning(bash, pidFile).summary("PID file "+pidFile+" is-running check (boolean)").
                 returning(new Function<ProcessTaskWrapper<?>, Boolean>() {
                     @Override
                     public Boolean apply(@Nullable ProcessTaskWrapper<?> input) { return ((Integer)0).equals(input.getExitCode()); }
diff --git a/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java
index 8656f5dd9b..e0537baacc 100644
--- a/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostMachineProvisioningLocation.java
@@ -51,10 +51,10 @@ import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.process.ProcessTool;
 import org.apache.brooklyn.util.core.mutex.MutexSupport;
 import org.apache.brooklyn.util.core.mutex.WithMutexes;
-import org.apache.brooklyn.util.exceptions.UserFacingException;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
index ea103f2b5c..2f16f0baed 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/file/ArchiveUtils.java
@@ -45,7 +45,8 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.StackTraceSimplifier;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
@@ -109,19 +110,23 @@ public class ArchiveUtils {
         }
     }
 
+    @Deprecated /** @deprecated since 1.1 use {@link IptablesCommandsConfigurable} */
+    public static List<String> installCommands(String fileName) {
+        return installCommands(BashCommandsConfigurable.newInstance(), fileName);
+    }
     /**
      * Returns the list of commands used to install support for an archive with the given name.
      */
-    public static List<String> installCommands(String fileName) {
+    public static List<String> installCommands(BashCommandsConfigurable bash, String fileName) {
         List<String> commands = new LinkedList<String>();
         switch (ArchiveType.of(fileName)) {
             case TAR:
             case TGZ:
             case TBZ:
-                commands.add(BashCommands.INSTALL_TAR);
+                commands.add(bash.INSTALL_TAR);
                 break;
             case ZIP:
-                commands.add(BashCommands.INSTALL_UNZIP);
+                commands.add(bash.INSTALL_UNZIP);
                 break;
             case JAR:
             case WAR:
@@ -281,7 +286,7 @@ public class ArchiveUtils {
             }
 
             // extract, now using task if available
-            MutableList<String> commands = MutableList.copyOf(installCommands(destFile))
+            MutableList<String> commands = MutableList.copyOf(installCommands(BrooklynOsCommands.bash(machine.getManagementContext()), destFile))
                     .appendAll(extractCommands(destFile, tmpDir, destDir, false, keepArchiveAfterUnpacking));
             if (DynamicTasks.getTaskQueuingContext()!=null) {
                 result = DynamicTasks.queue(SshTasks.newSshExecTaskFactory(machine, commands.toArray(new String[0])).summary("extracting archive").requiringExitCodeZero()).get();
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java b/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java
index befef2e33d..eb1e9cbbb2 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/internal/ssh/ShellAbstractTool.java
@@ -37,6 +37,7 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
index d732177051..16066e9967 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
@@ -42,8 +42,10 @@ import org.apache.brooklyn.core.location.AbstractLocation;
 import org.apache.brooklyn.core.location.internal.LocationInternal;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
@@ -52,7 +54,7 @@ import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.net.Urls;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
@@ -178,10 +180,17 @@ public class SshTasks {
         }
         
         final String id = Identifiers.makeRandomId(6);
-        return newSshExecTaskFactory(machine, 
-                BashCommands.dontRequireTtyForSudo(),
-                // strange quotes are to ensure we don't match against echoed stdin
-                BashCommands.sudo("echo \"sudo\"-is-working-"+id))
+        return newSshExecTaskFactory(machine, "(commands to modify sudo config to allow tty)")
+                .commandModifier(x -> {
+                    Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current());
+                    BashCommandsConfigurable bash;
+                    if (entity!=null) bash = BrooklynOsCommands.bash(entity);
+                    else bash = BrooklynOsCommands.bash(machine.getManagementContext());
+                    return MutableList.of(
+                        bash.dontRequireTtyForSudo(),
+                        // strange quotes are to ensure we don't match against echoed stdin
+                        bash.sudo("echo \"sudo\"-is-working-"+id));
+            })
             .summary("patch /etc/sudoers to disable requiretty")
             .configure(SshTool.PROP_ALLOCATE_PTY, true)
             .allowingNonZeroExitCode()
@@ -216,7 +225,7 @@ public class SshTasks {
         return new Function<ProcessTaskWrapper<?>, String>() {
           @Override
           public String apply(@Nullable ProcessTaskWrapper<?> input) {
-            if (logger!=null) logger.info(input+" COMMANDS:\n"+Strings.join(input.getCommands(),"\n"));
+            if (logger!=null) logger.info(input+" COMMANDS:\n"+Strings.join(input.getCommands(true),"\n"));
             if (logger!=null) logger.info(input+" STDOUT:\n"+input.getStdout());
             if (logger!=null) logger.info(input+" STDERR:\n"+input.getStderr());
             if (requireZero && input.getExitCode()!=0) 
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
index e61bd7cd01..4e9e08e0c0 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/internal/AbstractSshExecTaskFactory.java
@@ -60,9 +60,9 @@ public abstract class AbstractSshExecTaskFactory<T extends AbstractProcessTaskFa
             protected void run(ConfigBag config) {
                 Preconditions.checkNotNull(getMachine(), "machine");
                 if (Boolean.FALSE.equals(this.runAsScript)) {
-                    this.exitCode = getMachine().execCommands(config.getAllConfig(), getSummary(), commands, shellEnvironment);
+                    this.exitCode = getMachine().execCommands(config.getAllConfig(), getSummary(), getCommands(true), shellEnvironment);
                 } else { // runScript = null or TRUE
-                    this.exitCode = getMachine().execScript(config.getAllConfig(), getSummary(), commands, shellEnvironment);
+                    this.exitCode = getMachine().execScript(config.getAllConfig(), getSummary(), getCommands(true), shellEnvironment);
                 }
             }
             @Override
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
index b7e8e3aab9..a46436d188 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskFactory.java
@@ -24,6 +24,7 @@ import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskStub.ScriptReturnType;
 
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
@@ -46,6 +47,7 @@ public interface ProcessTaskFactory<T> extends SimpleProcessTaskFactory<ProcessT
     ProcessTaskFactory<T> runAsCommand();
     ProcessTaskFactory<T> runAsScript();
     ProcessTaskFactory<T> runAsRoot();
+    ProcessTaskFactory<T> commandModifier(Function<List<String>, List<String>> modifier);
     @Override ProcessTaskFactory<T> environmentVariable(String key, String val);
     @Override ProcessTaskFactory<T> environmentVariables(Map<String,String> vars);
     @Override ProcessTaskFactory<T> summary(String summary);
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
index 471fe54965..522dec4d40 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskStub.java
@@ -39,6 +39,7 @@ public class ProcessTaskStub {
     
     // config data
     protected String summary;
+    protected Function<List<String>,List<String>> commandModifier;
     protected final ConfigBag config = ConfigBag.newInstance();
     
     public static enum ScriptReturnType { CUSTOM, EXIT_CODE, STDOUT_STRING, STDOUT_BYTES, STDERR_STRING, STDERR_BYTES }
@@ -53,12 +54,13 @@ public class ProcessTaskStub {
     protected final List<Function<ProcessTaskWrapper<?>, Void>> completionListeners = new ArrayList<Function<ProcessTaskWrapper<?>,Void>>();
 
     public ProcessTaskStub() {}
-    
+
     protected ProcessTaskStub(ProcessTaskStub source) {
-        commands.addAll(source.getCommands());
+        commands.addAll(source.getCommands(false));
         machine = source.getMachine();
         summary = source.getSummary();
         config.copy(source.getConfig());
+        commandModifier = source.commandModifier;
         returnResultTransformation = source.returnResultTransformation;
         returnType = source.returnType;
         runAsScript = source.runAsScript;
@@ -89,7 +91,10 @@ public class ProcessTaskStub {
     }
 
     public List<String> getCommands() {
-        return ImmutableList.copyOf(commands);
+        return getCommands(false);
+    }
+    public List<String> getCommands(boolean modified) {
+        return ImmutableList.copyOf(modified && commandModifier!=null ? commandModifier.apply(commands) : commands);
     }
  
     public List<Function<ProcessTaskWrapper<?>, Void>> getCompletionListeners() {
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java
index 4185ca2923..e19bc40f0e 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/ProcessTaskWrapper.java
@@ -145,7 +145,7 @@ public abstract class ProcessTaskWrapper<RET> extends ProcessTaskStub implements
             log.warn(message+" (throwing)");
             logProblemDetails("STDERR", stderrForReading(), 1024);
             logProblemDetails("STDOUT", stdoutForReading(), 1024);
-            logProblemDetails("STDIN", Streams.byteArrayOfString(Strings.join(commands,"\n")), 4096);
+            logProblemDetails("STDIN", Streams.byteArrayOfString(Strings.join(getCommands(true),"\n")), 4096);
             if (optionalCause!=null) throw new IllegalStateException(message, optionalCause);
             throw new IllegalStateException(message);
         }
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
index f1a22e6e8e..b76a228eaf 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/AbstractProcessTaskFactory.java
@@ -19,8 +19,10 @@
 package org.apache.brooklyn.util.core.task.system.internal;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
+import java.util.function.Supplier;
 
 import org.apache.brooklyn.api.location.MachineLocation;
 import org.slf4j.Logger;
@@ -61,6 +63,10 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
         return self();
     }
 
+    public List<String> getCommands() {
+        return this.commands;
+    }
+
     @Override
     public T add(Iterable<String> commandsToAdd) {
         Iterables.addAll(this.commands, commandsToAdd);
@@ -183,7 +189,7 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
         TaskBuilder<Object> tb = TaskBuilder.builder().dynamic(false).displayName(displayName);
         
         tb.tag(BrooklynTaskTags.tagForStream(BrooklynTaskTags.STREAM_STDIN, 
-                Streams.byteArrayOfString(Strings.join(commands, "\n"))));
+                Streams.byteArrayOfString(Strings.join(getCommands(true), "\n"))));
         tb.tag(BrooklynTaskTags.tagForEnvStream(BrooklynTaskTags.STREAM_ENV, shellEnvironment));
 
         return tb;
@@ -196,6 +202,12 @@ public abstract class AbstractProcessTaskFactory<T extends AbstractProcessTaskFa
         return self();
     }
 
+    /** allows caller to supply a command modifier, used to modify the commands set up. */
+    public T commandModifier(Function<List<String>, List<String>> commandModifier) {
+        this.commandModifier = commandModifier;
+        return self();
+    }
+
     @Override
     public <V> T configure(ConfigKey<V> key, V value) {
         config.configure(key, value);
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
index 3ecbbe6458..b15c9df3c0 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/system/internal/SystemProcessTaskFactory.java
@@ -93,9 +93,9 @@ public class SystemProcessTaskFactory<T extends SystemProcessTaskFactory<T,RET>,
         @Override
         protected void run(ConfigBag config) {
             if (Boolean.FALSE.equals(this.runAsScript)) {
-                this.exitCode = newExecWithLoggingHelpers().execCommands(config.getAllConfig(), getSummary(), getCommands(), getShellEnvironment());
+                this.exitCode = newExecWithLoggingHelpers().execCommands(config.getAllConfig(), getSummary(), getCommands(true), getShellEnvironment());
             } else { // runScript = null or TRUE
-                this.exitCode = newExecWithLoggingHelpers().execScript(config.getAllConfig(), getSummary(), getCommands(), getShellEnvironment());
+                this.exitCode = newExecWithLoggingHelpers().execScript(config.getAllConfig(), getSummary(), getCommands(true), getShellEnvironment());
             }
         }
         @Override
diff --git a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
index 857941732e..115a8e5806 100644
--- a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
+++ b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
@@ -1345,8 +1345,8 @@ brooklyn.util.os.Os
 brooklyn.util.pool.BasicPool                                                     : org.apache.brooklyn.util.pool.BasicPool
 brooklyn.util.pool.Lease                                                         : org.apache.brooklyn.util.pool.Lease
 brooklyn.util.pool.Pool                                                          : org.apache.brooklyn.util.pool.Pool
-brooklyn.util.ssh.BashCommands                                                   : org.apache.brooklyn.util.ssh.BashCommands
-brooklyn.util.ssh.IptablesCommands                                               : org.apache.brooklyn.util.ssh.IptablesCommands
+brooklyn.util.ssh.BashCommands                                                   : org.apache.brooklyn.util.ssh.BashCommandsConfigurable
+brooklyn.util.ssh.IptablesCommands                                               : org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable
 brooklyn.util.stream.DelegatingPrintStream                                       : org.apache.brooklyn.util.stream.DelegatingPrintStream
 brooklyn.util.stream.IllegalOutputStream                                         : org.apache.brooklyn.util.stream.IllegalOutputStream
 brooklyn.util.stream.KnownSizeInputStream                                        : org.apache.brooklyn.util.stream.KnownSizeInputStream
diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java
index 0e22470ea0..327fe74152 100644
--- a/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasksTest.java
@@ -35,6 +35,7 @@ import org.apache.brooklyn.util.core.task.ssh.SshPutTaskWrapper;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
 import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -188,9 +189,9 @@ public class SshEffectorTasksTest {
     public void testRunningPidFromFile() throws IOException {
         File f = File.createTempFile("testBrooklynPid", ".pid");
         Files.write( (""+getMyPid()).getBytes(), f );
-        ProcessTaskWrapper<Integer> t = submit(SshEffectorTasks.codePidFromFileRunning(f.getPath()));
+        ProcessTaskWrapper<Integer> t = submit(SshEffectorTasks.codePidFromFileRunning(BashCommandsConfigurable.newInstance(), f.getPath()));
         Assert.assertEquals(t.getTask().getUnchecked(), (Integer)0);
-        ProcessTaskWrapper<Boolean> t2 = submit(SshEffectorTasks.isPidFromFileRunning(f.getPath()));
+        ProcessTaskWrapper<Boolean> t2 = submit(SshEffectorTasks.isPidFromFileRunning(BashCommandsConfigurable.newInstance(), f.getPath()));
         Assert.assertTrue(t2.getTask().getUnchecked());
     }
 
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java
index 311bce7d38..d6699ced71 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ssh/BashCommandsIntegrationTest.java
@@ -18,23 +18,14 @@
  */
 package org.apache.brooklyn.util.core.ssh;
 
-import static java.lang.String.format;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
 import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.DisableOnWindows;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.task.BasicExecutionContext;
@@ -43,7 +34,7 @@ import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
@@ -54,13 +45,17 @@ import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
-import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
 
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.Files;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static java.lang.String.format;
+import static org.testng.Assert.*;
 
 public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
 
@@ -83,7 +78,9 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     private File localRepoEntityBasePath;
     private String localRepoEntityVersionPath;
     private File localRepoEntityFile;
-    
+
+    private final BashCommandsConfigurable bashTestInstance = BashCommandsConfigurable.newInstance();
+
     @BeforeMethod(alwaysRun=true)
     public void setUp() throws Exception {
         super.setUp();
@@ -136,7 +133,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testRemoveRequireTtyFromSudoersFile() throws Exception {
-        String cmds = BashCommands.dontRequireTtyForSudo();
+        String cmds = bashTestInstance.dontRequireTtyForSudo();
 
         
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
@@ -160,7 +157,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     public void testSudo() throws Exception {
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         ByteArrayOutputStream errStream = new ByteArrayOutputStream();
-        String cmd = sudo("whoami");
+        String cmd = bashTestInstance.sudo("whoami");
         int exitcode = loc.execCommands(ImmutableMap.of("out", outStream, "err", errStream), "test", ImmutableList.of(cmd));
         String outstr = new String(outStream.toByteArray());
         String errstr = new String(errStream.toByteArray());
@@ -170,7 +167,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     }
     
     public void testDownloadUrl() throws Exception {
-        List<String> cmds = BashCommands.commandsToDownloadUrlsAs(
+        List<String> cmds = bashTestInstance.commandsToDownloadUrlsAs(
                 ImmutableList.of(sourceFileUrl1), 
                 destFile.getAbsolutePath());
         int exitcode = loc.execCommands("test", cmds);
@@ -182,7 +179,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testDownloadFirstSuccessfulFile() throws Exception {
-        List<String> cmds = BashCommands.commandsToDownloadUrlsAs(
+        List<String> cmds = bashTestInstance.commandsToDownloadUrlsAs(
                 ImmutableList.of(sourceNonExistantFileUrl, sourceFileUrl1, sourceFileUrl2), 
                 destFile.getAbsolutePath());
         int exitcode = loc.execCommands("test", cmds);
@@ -196,7 +193,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     public void testDownloadToStdout() throws Exception {
         ProcessTaskWrapper<String> t = SshTasks.newSshExecTaskFactory(loc, 
                 "cd "+destFile.getParentFile().getAbsolutePath(),
-                BashCommands.downloadToStdout(Arrays.asList(sourceFileUrl1))+" | sed s/my/your/")
+                bashTestInstance.downloadToStdout(Arrays.asList(sourceFileUrl1))+" | sed s/my/your/")
             .requiringZeroAndReturningStdout().newTask();
 
         String result = exec.submit(t).get();
@@ -207,7 +204,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testAlternativesWhereFirstSucceeds() throws Exception {
         ProcessTaskWrapper<Integer> t = SshTasks.newSshExecTaskFactory(loc)
-                .add(BashCommands.alternatives(Arrays.asList("echo first", "exit 88")))
+                .add(bashTestInstance.alternatives(Arrays.asList("echo first", "exit 88")))
                 .newTask();
 
         Integer returnCode = exec.submit(t).get();
@@ -222,7 +219,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testAlternatives() throws Exception {
         ProcessTaskWrapper<Integer> t = SshTasks.newSshExecTaskFactory(loc)
-                .add(BashCommands.alternatives(Arrays.asList("asdfj_no_such_command_1", "exit 88")))
+                .add(bashTestInstance.alternatives(Arrays.asList("asdfj_no_such_command_1", "exit 88")))
                 .newTask();
 
         Integer returnCode = exec.submit(t).get();
@@ -234,7 +231,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireTestHandlesFailure() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireTest("-f "+sourceNonExistantFile.getPath(),
+            .add(bashTestInstance.requireTest("-f "+sourceNonExistantFile.getPath(),
                     "The requested file does not exist")).newTask();
 
         exec.submit(t).get();
@@ -247,7 +244,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireTestHandlesSuccess() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireTest("-f "+sourceFile1.getPath(),
+            .add(bashTestInstance.requireTest("-f "+sourceFile1.getPath(),
                     "The requested file does not exist")).newTask();
 
         exec.submit(t).get();
@@ -259,7 +256,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireFileHandlesFailure() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireFile(sourceNonExistantFile.getPath())).newTask();
+            .add(bashTestInstance.requireFile(sourceNonExistantFile.getPath())).newTask();
 
         exec.submit(t).get();
         assertNotEquals(t.getExitCode(), 0);
@@ -273,7 +270,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireFileHandlesSuccess() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireFile(sourceFile1.getPath())).newTask();
+            .add(bashTestInstance.requireFile(sourceFile1.getPath())).newTask();
 
         exec.submit(t).get();
         assertEquals(t.getExitCode(), (Integer)0);
@@ -284,7 +281,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs an ssh server listening on port 22 on localhost")
     public void testRequireFailureExitsImmediately() throws Exception {
         ProcessTaskWrapper<?> t = SshTasks.newSshExecTaskFactory(loc)
-            .add(BashCommands.requireTest("-f "+sourceNonExistantFile.getPath(),
+            .add(bashTestInstance.requireTest("-f "+sourceNonExistantFile.getPath(),
                     "The requested file does not exist"))
             .add("echo shouldnae come here").newTask();
 
@@ -299,7 +296,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testPipeMultiline() throws Exception {
         String output = execRequiringZeroAndReturningStdout(loc,
-                BashCommands.pipeTextTo("hello world\n"+"and goodbye\n", "wc")).get();
+                bashTestInstance.pipeTextTo("hello world\n"+"and goodbye\n", "wc")).get();
 
         assertEquals(Strings.replaceAllRegex(output, "\\s+", " ").trim(), "3 4 25");
     }
@@ -308,7 +305,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileContentsWhenAbortingOnFail() throws Exception {
         String fileContent = "mycontents";
-        String cmd = BashCommands.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, true);
+        String cmd = bashTestInstance.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, true);
 
         int exitcode = loc.execCommands("test", ImmutableList.of(cmd));
         assertEquals(exitcode, 1);
@@ -322,7 +319,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileContentsWhenNotAbortingOnFail() throws Exception {
         String fileContent = "mycontents";
-        String cmd = BashCommands.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, false);
+        String cmd = bashTestInstance.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.ONE_SECOND, false);
 
         String output = execRequiringZeroAndReturningStdout(loc, cmd).get();
         assertTrue(output.contains("Couldn't find"), "output="+output);
@@ -337,7 +334,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     public void testWaitForFileContentsWhenContentsAppearAfterStart() throws Exception {
         String fileContent = "mycontents";
 
-        String cmd = BashCommands.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.THIRTY_SECONDS, false);
+        String cmd = bashTestInstance.waitForFileContents(destFile.getAbsolutePath(), fileContent, Duration.THIRTY_SECONDS, false);
         ProcessTaskWrapper<String> t = execRequiringZeroAndReturningStdout(loc, cmd);
         exec.submit(t);
         
@@ -353,7 +350,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileExistsWhenAbortingOnFail() throws Exception {
-        String cmd = BashCommands.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, true);
+        String cmd = bashTestInstance.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, true);
 
         int exitcode = loc.execCommands("test", ImmutableList.of(cmd));
         assertEquals(exitcode, 0);
@@ -366,7 +363,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
     @Test(groups="Integration")
     @DisableOnWindows(reason = "Needs a bash shell available on localhost")
     public void testWaitForFileExistsWhenNotAbortingOnFail() throws Exception {
-        String cmd = BashCommands.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, false);
+        String cmd = bashTestInstance.waitForFileExists(destFile.getAbsolutePath(), Duration.ONE_SECOND, false);
 
         String output = execRequiringZeroAndReturningStdout(loc, cmd).get();
         assertFalse(output.contains("Couldn't find"), "output="+output);
@@ -382,7 +379,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         ServerSocket serverSocket = openServerSocket();
         try {
             int port = serverSocket.getLocalPort();
-            String cmd = BashCommands.waitForPortFree(port, Duration.ONE_SECOND, true);
+            String cmd = bashTestInstance.waitForPortFree(port, Duration.ONE_SECOND, true);
     
             int exitcode = loc.execCommands("test", ImmutableList.of(cmd));
             assertEquals(exitcode, 1);
@@ -402,7 +399,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         ServerSocket serverSocket = openServerSocket();
         try {
             int port = serverSocket.getLocalPort();
-            String cmd = BashCommands.waitForPortFree(port, Duration.ONE_SECOND, false);
+            String cmd = bashTestInstance.waitForPortFree(port, Duration.ONE_SECOND, false);
     
             String output = execRequiringZeroAndReturningStdout(loc, cmd).get();
             assertTrue(output.contains(port+" still in use"), "output="+output);
@@ -423,7 +420,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         try {
             int port = serverSocket.getLocalPort();
     
-            String cmd = BashCommands.waitForPortFree(port, Duration.THIRTY_SECONDS, false);
+            String cmd = bashTestInstance.waitForPortFree(port, Duration.THIRTY_SECONDS, false);
             ProcessTaskWrapper<String> t = execRequiringZeroAndReturningStdout(loc, cmd);
             exec.submit(t);
             
@@ -470,15 +467,15 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         LocalManagementContextForTests mgmt = new LocalManagementContextForTests();
         SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocalhostMachineProvisioningLocation.spec()).obtain();
 
-        execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts /etc/hosts-orig-testSetHostname")).get();
-        execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/hostname", sudo("cp /etc/hostname /etc/hostname-orig-testSetHostname"))).get();
-        execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/sysconfig/network", sudo("cp /etc/sysconfig/network /etc/sysconfig/network-orig-testSetHostname"))).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts /etc/hosts-orig-testSetHostname")).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/hostname", bashTestInstance.sudo("cp /etc/hostname /etc/hostname-orig-testSetHostname"))).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/sysconfig/network", bashTestInstance.sudo("cp /etc/sysconfig/network /etc/sysconfig/network-orig-testSetHostname"))).get();
         
         String origHostname = getHostnameNoArgs(loc);
         assertTrue(Strings.isNonBlank(origHostname));
         
         try {
-            List<String> cmd = (includeDomain) ? BashCommands.setHostname(newHostname, newDomain) : BashCommands.setHostname(newHostname);
+            List<String> cmd = (includeDomain) ? bashTestInstance.setHostname(newHostname, newDomain) : bashTestInstance.setHostname(newHostname);
             execRequiringZeroAndReturningStdout(loc, cmd).get();
 
             String actualHostnameUnqualified = getHostnameUnqualified(loc);
@@ -500,10 +497,10 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
             log.info("result="+result);
             
         } finally {
-            execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts-orig-testSetHostname /etc/hosts")).get();
-            execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/hostname-orig-testSetHostname", sudo("cp /etc/hostname-orig-testSetHostname /etc/hostname"))).get();
-            execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/sysconfig/network-orig-testSetHostname", sudo("cp /etc/sysconfig/network-orig-testSetHostname /etc/sysconfig/network"))).get();
-            execRequiringZeroAndReturningStdout(loc, sudo("hostname "+origHostname)).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts-orig-testSetHostname /etc/hosts")).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/hostname-orig-testSetHostname", bashTestInstance.sudo("cp /etc/hostname-orig-testSetHostname /etc/hostname"))).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.ifFileExistsElse0("/etc/sysconfig/network-orig-testSetHostname", bashTestInstance.sudo("cp /etc/sysconfig/network-orig-testSetHostname /etc/sysconfig/network"))).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("hostname "+origHostname)).get();
         }
     }
 
@@ -513,14 +510,14 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
         LocalManagementContextForTests mgmt = new LocalManagementContextForTests();
         SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocalhostMachineProvisioningLocation.spec()).obtain();
 
-        execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts /etc/hosts-orig-testModifyEtcHosts")).get();
+        execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts /etc/hosts-orig-testModifyEtcHosts")).get();
         int numLinesOrig = Integer.parseInt(execRequiringZeroAndReturningStdout(loc, "wc -l /etc/hosts").get().trim().split("\\s")[0]);
         
         try {
-            String cmd = BashCommands.prependToEtcHosts("1.2.3.4", "myhostnamefor1234.at.start", "myhostnamefor1234b");
+            String cmd = bashTestInstance.prependToEtcHosts("1.2.3.4", "myhostnamefor1234.at.start", "myhostnamefor1234b");
             execRequiringZeroAndReturningStdout(loc, cmd).get();
             
-            String cmd2 = BashCommands.appendToEtcHosts("5.6.7.8", "myhostnamefor5678.at.end", "myhostnamefor5678");
+            String cmd2 = bashTestInstance.appendToEtcHosts("5.6.7.8", "myhostnamefor5678.at.end", "myhostnamefor5678");
             execRequiringZeroAndReturningStdout(loc, cmd2).get();
             
             String grepFirst = execRequiringZeroAndReturningStdout(loc, "grep -n myhostnamefor1234 /etc/hosts").get();
@@ -532,7 +529,7 @@ public class BashCommandsIntegrationTest extends BrooklynMgmtUnitTestSupport {
             assertTrue(grepLast.startsWith((numLinesOrig+2)+":") && grepLast.contains("5.6.7.8 myhostnamefor5678.at.end myhostnamefor5678"), "last="+grepLast);
             assertEquals(numLinesOrig + 2, numLinesAfter, "lines orig="+numLinesOrig+", after="+numLinesAfter);
         } finally {
-            execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts-orig-testModifyEtcHosts /etc/hosts")).get();
+            execRequiringZeroAndReturningStdout(loc, bashTestInstance.sudo("cp /etc/hosts-orig-testModifyEtcHosts /etc/hosts")).get();
         }
     }
     
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
index 7b1b4be93d..764b9021e1 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java
@@ -26,7 +26,6 @@ import org.apache.brooklyn.core.location.*;
 import org.apache.brooklyn.core.location.MachineLifecycleUtils.MachineStatus;
 import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis;
 import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import static org.apache.brooklyn.util.ssh.BashCommands.sbinPath;
 import static org.jclouds.compute.predicates.NodePredicates.withIds;
 import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
 
@@ -90,6 +89,7 @@ import org.apache.brooklyn.util.core.ClassLoaderUtils;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
@@ -112,10 +112,10 @@ import org.apache.brooklyn.util.net.Cidr;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.net.Protocol;
 import org.apache.brooklyn.util.repeat.Repeater;
-import org.apache.brooklyn.util.ssh.BashCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.KeyValueParser;
 import org.apache.brooklyn.util.text.StringPredicates;
@@ -404,9 +404,12 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 : (OsFamily.WINDOWS == confFamily);
     }
 
+    private BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getManagementContext()); }
+    private IptablesCommandsConfigurable iptablesCommands() { return new IptablesCommandsConfigurable(bashCommands()); }
+
     public boolean isLocationFirewalldEnabled(SshMachineLocation location) {
         int result = location.execCommands("checking if firewalld is active",
-                ImmutableList.of(IptablesCommands.firewalldServiceIsActive()));
+                ImmutableList.of(iptablesCommands().firewalldServiceIsActive()));
         return result == 0;
     }
 
@@ -939,8 +942,8 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                                 (SshMachineLocation)machineLocation,
                                 "using urandom instead of random",
                                 Arrays.asList(
-                                        BashCommands.sudo("mv /dev/random /dev/random-real"),
-                                        BashCommands.sudo("ln -s /dev/urandom /dev/random")));
+                                        bashCommands().sudo("mv /dev/random /dev/random-real"),
+                                        bashCommands().sudo("ln -s /dev/urandom /dev/random")));
                     }
                 }
 
@@ -955,10 +958,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                         executeCommandThrowingOnError(
                                 (SshMachineLocation)machineLocation,
                                 "Generate hostname " + node.getName(),
-                                ImmutableList.of(BashCommands.chainGroup(
-                                        String.format("echo '127.0.0.1 %s' | ( %s )", node.getName(), BashCommands.sudo("tee -a /etc/hosts")),
-                                        "{ " + BashCommands.sudo("sed -i \"s/HOSTNAME=.*/HOSTNAME=" + node.getName() + "/g\" /etc/sysconfig/network") + " || true ; }",
-                                        BashCommands.sudo("hostname " + node.getName()))));
+                                ImmutableList.of(bashCommands().chainGroup(
+                                        String.format("echo '127.0.0.1 %s' | ( %s )", node.getName(), bashCommands().sudo("tee -a /etc/hosts")),
+                                        "{ " + bashCommands().sudo("sed -i \"s/HOSTNAME=.*/HOSTNAME=" + node.getName() + "/g\" /etc/sysconfig/network") + " || true ; }",
+                                        bashCommands().sudo("hostname " + node.getName()))));
                     }
                 }
 
@@ -979,14 +982,14 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
                             if (isLocationFirewalldEnabled((SshMachineLocation)machineLocation)) {
                                 for (Integer port : inboundPorts) {
-                                    iptablesRules.add(IptablesCommands.addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                                    iptablesRules.add(iptablesCommands().addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                                  }
                             } else {
                                 iptablesRules = Lists.newArrayList();
                                 for (Integer port : inboundPorts) {
-                                   iptablesRules.add(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                                   iptablesRules.add(iptablesCommands().insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                                 }
-                                iptablesRules.add(IptablesCommands.saveIptablesRules());
+                                iptablesRules.add(iptablesCommands().saveIptablesRules());
                             }
                             List<String> batch = Lists.newArrayList();
                             // Some entities, such as Riak (erlang based) have a huge range of ports, which leads to a script that
@@ -1010,7 +1013,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                             executeCommandWarningOnError(
                                     (SshMachineLocation)machineLocation,
                                     "List iptables rules",
-                                    ImmutableList.of(IptablesCommands.listIptablesRule()));
+                                    ImmutableList.of(iptablesCommands().listIptablesRule()));
                         }
                     }
                 }
@@ -1025,9 +1028,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
                         List<String> cmds = ImmutableList.<String>of();
                         if (isLocationFirewalldEnabled((SshMachineLocation)machineLocation)) {
-                            cmds = ImmutableList.of(IptablesCommands.firewalldServiceStop(), IptablesCommands.firewalldServiceStatus());
+                            cmds = ImmutableList.of(iptablesCommands().firewalldServiceStop(), iptablesCommands().firewalldServiceStatus());
                         } else {
-                            cmds = ImmutableList.of(IptablesCommands.iptablesServiceStop(), IptablesCommands.iptablesServiceStatus());
+                            cmds = ImmutableList.of(iptablesCommands().iptablesServiceStop(), iptablesCommands().iptablesServiceStatus());
                         }
                         executeCommandWarningOnError(
                                 (SshMachineLocation)machineLocation,
@@ -1825,7 +1828,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 SshMachineLocation sshLoc = createTemporarySshMachineLocation(managementHostAndPort, initialCredentials, config);
                 try {
                     // BROOKLYN-188: for SUSE, need to specify the path (for groupadd, useradd, etc)
-                    Map<String, ?> env = ImmutableMap.of("PATH", sbinPath());
+                    Map<String, ?> env = ImmutableMap.of("PATH", bashCommands().sbinPath());
 
                     int exitcode = sshLoc.execScript(execProps, "create-user", commands, env);
 
@@ -3067,7 +3070,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                     MutableMap.of("out", outStream, "err", errStream),
                     "get public AWS hostname",
                     ImmutableList.of(
-                            BashCommands.INSTALL_CURL,
+                            bashCommands().INSTALL_CURL,
                             "echo `curl --silent --retry 20 http://169.254.169.254/latest/meta-data/public-hostname`; exit"));
             String outString = new String(outStream.toByteArray());
             String[] outLines = outString.split("\n");
diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java b/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
index 555d7cb866..abf0c8d8a0 100644
--- a/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
+++ b/locations/jclouds/src/main/java/org/apache/brooklyn/policy/jclouds/os/CreateUserPolicy.java
@@ -29,13 +29,13 @@ import org.apache.brooklyn.api.sensor.SensorEventListener;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.AbstractEntity;
-import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
-import static org.apache.brooklyn.util.ssh.BashCommands.sbinPath;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.jclouds.compute.config.AdminAccessConfiguration;
 import org.jclouds.scriptbuilder.functions.InitAdminAccess;
@@ -149,14 +149,15 @@ public class CreateUserPolicy extends AbstractPolicy implements SensorEventListe
         String cmd = adminAccess.render(scriptOsFamily);
 
         // Exec command to create the user
-        int result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", sbinPath()));
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(entity);
+        int result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", bash.sbinPath()));
         if (result != 0) {
             throw new IllegalStateException("Failed to auto-generate user, using command "+cmd);
         }
 
         // Exec command to grant password-access to sshd (which may have been disabled earlier).
         cmd = new SshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")).render(scriptOsFamily);
-        result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", sbinPath()));
+        result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "create-user-"+user, ImmutableList.of(cmd), ImmutableMap.of("PATH", bash.sbinPath()));
         if (result != 0) {
             throw new IllegalStateException("Failed to enable ssh-login-with-password, using command "+cmd);
         }
@@ -168,7 +169,7 @@ public class CreateUserPolicy extends AbstractPolicy implements SensorEventListe
                             user+" ALL = (ALL) NOPASSWD:ALL\n"+
                             "END_OF_JCLOUDS_FILE\n",
                     "chmod 0440 /etc/sudoers");
-            result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "add-user-to-sudoers-"+user, cmds, ImmutableMap.of("PATH", sbinPath()));
+            result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true), "add-user-to-sudoers-"+user, cmds, ImmutableMap.of("PATH", bash.sbinPath()));
             if (result != 0) {
                 throw new IllegalStateException("Failed to auto-generate user, using command "+cmds);
             }
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java
index 8fe3dc2e6d..b36dbb9b39 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsSuseLiveTest.java
@@ -25,7 +25,7 @@ import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.Streams;
 import org.testng.annotations.Test;
 
@@ -36,7 +36,7 @@ import com.google.common.collect.ImmutableMap;
  * Extra-special tests for deploying SUSE VMs, because we've had so many problems. For example:
  * <ul>
  *   <li>{@code groupadd -f}:  You are using an undocumented option (-f); and exits with 9
- *   <li>path does not by default contain groupadd etc (see {@link BashCommands#sbinPath()}
+ *   <li>path does not by default contain groupadd etc (see {@link BashCommandsConfigurable#sbinPath()}
  * </ul>
  */
 public class JcloudsSuseLiveTest extends AbstractJcloudsLiveTest {
diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
index e9755ad92b..230bc78f4a 100644
--- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
+++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/SimpleJcloudsLocationUserLoginAndConfigLiveTest.java
@@ -26,7 +26,8 @@ import java.util.Map;
 import org.apache.brooklyn.api.location.NoMachinesAvailableException;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -214,7 +215,7 @@ public class SimpleJcloudsLocationUserLoginAndConfigLiveTest extends AbstractJcl
                 "grantUserSudo", false,
                 "waitForSshable", 30*1000));
 
-        int exitCode = execWithExitCode(m, ImmutableList.of(BashCommands.sudo("echo yes")));
+        int exitCode = execWithExitCode(m, ImmutableList.of(BrooklynOsCommands.bash(m.getManagementContext()).sudo("echo yes")));
         Assert.assertFalse(exitCode == 0, "exit code for sudo command should not have been 0");
     }
 
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java
index 027085f6cf..3fb424d779 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -45,7 +45,7 @@ import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.net.Networking;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -99,7 +99,7 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
     ConfigKey<String> MANAGEMENT_PASSWORD =
             ConfigKeys.newStringConfigKey("brooklynnode.managementPassword", "Password for MANAGEMENT_USER", null);
 
-    /** useful e.g. with {@link BashCommands#generateKeyInDotSshIdRsaIfNotThere() } */
+    /** useful e.g. with {@link BashCommandsConfigurable#generateKeyInDotSshIdRsaIfNotThere() } */
     @SetFromFlag("extraCustomizationScript")
     ConfigKey<String> EXTRA_CUSTOMIZATION_SCRIPT = ConfigKeys.newStringConfigKey("brooklynnode.customization.extraScript",
         "Optional additional script commands to run as part of customization; this might e.g. ensure id_rsa is set up",
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index 80e717e199..67a0d8689e 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -38,12 +38,13 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.file.ArchiveBuilder;
 import org.apache.brooklyn.util.core.file.ArchiveUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
@@ -153,9 +154,9 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
                 getMachine().copyTo(distroStream, getInstallDir()+"/"+saveAs);
             }
         } else {
-            commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
+            commands.addAll(BrooklynOsCommands.bash(getEntity()).commandsToDownloadUrlsAs(urls, saveAs));
         }
-        commands.add(BashCommands.INSTALL_TAR);
+        commands.add(BrooklynOsCommands.bash(getEntity()).INSTALL_TAR);
         commands.add("tar xzfv " + saveAs);
         
         newScript(INSTALLING).
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
index 7e0d1c2b0c..636d1d35e3 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
@@ -34,6 +34,7 @@ import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,7 +60,7 @@ import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
 
@@ -291,7 +292,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
                 return true;
             }
         }
-        return tryJavaInstall(requiredVersion, BashCommands.installJava(requiredJavaMinor)) == 0;
+        return tryJavaInstall(requiredVersion, BrooklynOsCommands.bash(getEntity()).installJava(requiredJavaMinor)) == 0;
     }
 
     /**
@@ -416,7 +417,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
                     // http://mail.openjdk.java.net/pipermail/net-dev/2012-July/004603.html
                     String newHostname = "br-"+getEntity().getId().toLowerCase();
                     log.info("Detected likelihood of Java hostname bug with hostname length "+len+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block();
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(getEntity()).setHostname(newHostname, null))).block();
                 }
             } else {
                 log.debug("Hostname length could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing Java hostname bug check");
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
index 1ded4208de..c1bbe1e63a 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineInitTasks.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,10 +38,10 @@ import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.ssh.BashCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.apache.brooklyn.util.text.Strings;
 
 /**
@@ -69,6 +70,8 @@ public class MachineInitTasks {
         });
     }
 
+    private IptablesCommandsConfigurable iptablesCommands(SshMachineLocation m) { return new IptablesCommandsConfigurable(BrooklynOsCommands.bash(m.getManagementContext())); }
+
     protected void stopIptablesImpl(final SshMachineLocation machine) {
 
         log.info("Stopping iptables for {} at {}", entity(), machine);
@@ -78,9 +81,9 @@ public class MachineInitTasks {
         Task<Integer> checkFirewall = checkLocationFirewall(machine);
 
         if (checkFirewall.getUnchecked() == 0) {
-            cmds = ImmutableList.of(IptablesCommands.firewalldServiceStop(), IptablesCommands.firewalldServiceStatus());
+            cmds = ImmutableList.of(iptablesCommands(machine).firewalldServiceStop(), iptablesCommands(machine).firewalldServiceStatus());
         } else {
-            cmds = ImmutableList.of(IptablesCommands.iptablesServiceStop(), IptablesCommands.iptablesServiceStatus());
+            cmds = ImmutableList.of(iptablesCommands(machine).iptablesServiceStop(), iptablesCommands(machine).iptablesServiceStatus());
         }
 
 
@@ -89,7 +92,7 @@ public class MachineInitTasks {
 
 
     /**
-     * See docs in {@link BashCommands#dontRequireTtyForSudo()}
+     * See docs in {@link BashCommandsConfigurable#dontRequireTtyForSudo()}
      */
     public Task<Boolean> dontRequireTtyForSudoAsync(final SshMachineLocation machine) {
         return DynamicTasks.queue(SshTasks.dontRequireTtyForSudo(machine, true).newTask().asTask());
@@ -120,11 +123,11 @@ public class MachineInitTasks {
 
             if (checkFirewall.getUnchecked() == 0) {
                 for (Integer port : inboundPorts) {
-                    iptablesRules.add(IptablesCommands.addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+                    iptablesRules.add(iptablesCommands(machine).addFirewalldRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
                  }
             } else {
-                iptablesRules = createIptablesRulesForNetworkInterface(inboundPorts);
-                iptablesInstallCommands = IptablesCommands.saveIptablesRules();
+                iptablesRules = createIptablesRulesForNetworkInterface(inboundPorts, machine);
+                iptablesInstallCommands = iptablesCommands(machine).saveIptablesRules();
             }
 
             insertIptablesRules(iptablesRules, iptablesInstallCommands, machine);
@@ -136,7 +139,7 @@ public class MachineInitTasks {
      * Returns a queued {@link Task} which checks if location firewall is enabled.
      */
     public Task<Integer> checkLocationFirewall(final SshMachineLocation machine) {
-        return subTaskHelperAllowingNonZeroExitCode("check if firewall is active", machine, IptablesCommands.firewalldServiceIsActive());
+        return subTaskHelperAllowingNonZeroExitCode("check if firewall is active", machine, iptablesCommands(machine).firewalldServiceIsActive());
     }
 
     /**
@@ -185,7 +188,7 @@ public class MachineInitTasks {
      * Returns a queued {@link Task} which lists the iptables rules.
      */
     private Task<Integer> listIptablesRules(final SshMachineLocation machine) {
-        return subTaskHelperRequiringZeroExitCode("list rules", machine, IptablesCommands.listIptablesRule());
+        return subTaskHelperRequiringZeroExitCode("list rules", machine, iptablesCommands(machine).listIptablesRule());
     }
 
     private Task<Integer> subTaskHelperRequiringZeroExitCode(String taskName, SshMachineLocation machine, String... comands) {
@@ -202,10 +205,10 @@ public class MachineInitTasks {
         return DynamicTasks.queue(taskFactory).asTask();
     }
     
-    private List<String> createIptablesRulesForNetworkInterface(Iterable<Integer> ports) {
+    private List<String> createIptablesRulesForNetworkInterface(Iterable<Integer> ports, SshMachineLocation machine) {
         List<String> iptablesRules = Lists.newArrayList();
         for (Integer port : ports) {
-           iptablesRules.add(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
+           iptablesRules.add(iptablesCommands(machine).insertIptablesRule(Chain.INPUT, Protocol.TCP, port, Policy.ACCEPT));
         }
         return iptablesRules;
      }
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
index 28f97ee5bc..128ad9b66c 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
@@ -30,11 +30,12 @@ import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks.SshEffectorTaskFactory;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.core.text.TemplateProcessor;
 import org.apache.brooklyn.util.net.Networking;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -172,11 +173,12 @@ public class SetHostnameCustomizer extends BasicMachineLocationCustomizer {
         
         boolean hasDomain = Strings.isNonBlank(domainFixed);
         String fqdn = hasDomain ? hostName+"."+domainFixed : hostName;
-        
+
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(machine.getManagementContext());
         exec(machine, true, 
-                BashCommands.sudo(String.format("sed -i.bak -e '1i127.0.0.1 %s %s' -e '/^127.0.0.1/d' /etc/hosts", fqdn, hostName)),
-                BashCommands.sudo(String.format("sed -i.bak -e 's/^HOSTNAME=.*$/HOSTNAME=%s/' /etc/sysconfig/network", fqdn)),
-                BashCommands.sudo(String.format("hostname %s", fqdn)));
+                bash.sudo(String.format("sed -i.bak -e '1i127.0.0.1 %s %s' -e '/^127.0.0.1/d' /etc/hosts", fqdn, hostName)),
+                bash.sudo(String.format("sed -i.bak -e 's/^HOSTNAME=.*$/HOSTNAME=%s/' /etc/sysconfig/network", fqdn)),
+                bash.sudo(String.format("hostname %s", fqdn)));
 
         return hostName;
     }
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java
index e3997ff921..612e5f289b 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java
@@ -19,7 +19,6 @@
 package org.apache.brooklyn.entity.machine;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static org.apache.brooklyn.util.ssh.BashCommands.sudo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,6 +35,7 @@ import org.apache.brooklyn.core.objs.BasicConfigurableObject;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.ssh.BashCommands;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -122,7 +122,7 @@ public class SetLimitsCustomizer extends BasicMachineLocationCustomizer implemen
         try {
             List<String> cmds = new ArrayList<>();
             for (String content : contents) {
-                cmds.add(sudo(String.format("echo \"%s\" | tee -a %s", content, file)));
+                cmds.add(BashCommands.sudo(String.format("echo \"%s\" | tee -a %s", content, file)));
             }
             exec((SshMachineLocation)machine, true, cmds.toArray(new String[cmds.size()]));
         } catch (Exception e) {
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
index acf59bfd47..697fa6bdf0 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java
@@ -36,22 +36,20 @@ import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.feed.ConfigToAttributes;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.entity.software.base.lifecycle.NaiveScriptRunner;
 import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool;
-import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer;
 import org.apache.brooklyn.util.core.mutex.WithMutexes;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.stream.KnownSizeInputStream;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.StringPredicates;
@@ -364,6 +362,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
         return result;
     }
 
+    protected BashCommandsConfigurable bashCommands() { return BrooklynOsCommands.bash(getEntity()); }
+
     public void checkNoHostnameBug() {
         try {
             ProcessTaskWrapper<Integer> hostnameTask = DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname; echo AFTMARKER")).block();
@@ -373,7 +373,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
                 if (hostname.equals("(none)")) {
                     String newHostname = "br-"+getEntity().getId().toLowerCase();
                     log.info("Detected no-hostname bug with hostname "+hostname+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block();
+                    DynamicTasks.queue(SshEffectorTasks.ssh(bashCommands().setHostname(newHostname, null))).block();
                 }
             } else {
                 log.debug("Hostname could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing no-hostname bug check");
@@ -534,8 +534,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
                 // new way, preferred?
                 if (processOwner != null) {
                     s.body.append(
-                            BashCommands.sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
-                            "ps -p $(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ")"
+                            bashCommands().sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
+                            "ps -p $(" + bashCommands().sudoAsUser(processOwner, "cat "+pidFile) + ")"
                     );
                 } else {
                     s.body.append(
@@ -566,17 +566,17 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
                         "fi",
                         "rm -f " + pidFile);
                 if (processOwner != null) {
-                    s.body.append(BashCommands.sudoAsUser(processOwner, stopCommand));
+                    s.body.append(bashCommands().sudoAsUser(processOwner, stopCommand));
                 } else {
                     s.body.append(stopCommand);
                 }
             } else if (KILLING.equals(phase)) {
                 if (processOwner != null) {
                     s.body.append(
-                            "export PID=$(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ")",
+                            "export PID=$(" + bashCommands().sudoAsUser(processOwner, "cat "+pidFile) + ")",
                             "test -n \"$PID\" || exit 0",
-                            BashCommands.sudoAsUser(processOwner, "kill -9 $PID"),
-                            BashCommands.sudoAsUser(processOwner, "rm -f "+pidFile)
+                            bashCommands().sudoAsUser(processOwner, "kill -9 $PID"),
+                            bashCommands().sudoAsUser(processOwner, "rm -f "+pidFile)
                     );
                 } else {
                     s.body.append(
@@ -589,8 +589,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
             } else if (RESTARTING.equals(phase)) {
                 if (processOwner != null) {
                     s.footer.prepend(
-                            BashCommands.sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
-                            "ps -p $(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ") || exit 1"
+                            bashCommands().sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1",
+                            "ps -p $(" + bashCommands().sudoAsUser(processOwner, "cat "+pidFile) + ") || exit 1"
                     );
                 } else {
                     s.footer.prepend(
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
index 07afb74da5..2cc3575075 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java
@@ -32,10 +32,11 @@ import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.file.ArchiveUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 
@@ -90,8 +91,9 @@ public class VanillaSoftwareProcessSshDriver extends AbstractSoftwareProcessSshD
             downloadedFilename = resolver.getFilename();
 
             List<String> commands = new LinkedList<String>();
-            commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, downloadedFilename));
-            commands.addAll(ArchiveUtils.installCommands(downloadedFilename));
+            BashCommandsConfigurable bash = BrooklynOsCommands.bash(getEntity());
+            commands.addAll(bash.commandsToDownloadUrlsAs(urls, downloadedFilename));
+            commands.addAll(ArchiveUtils.installCommands(bash, downloadedFilename));
 
             int result = newScript(ImmutableMap.of(INSTALL_INCOMPLETE, true), INSTALLING)
                     .failOnNonZeroResultCode(false)
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java b/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
index 97d51213ef..6c47b9b20b 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/system_service/InitdServiceInstaller.java
@@ -37,13 +37,14 @@ import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ssh.SshPutTaskWrapper;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
 import org.apache.brooklyn.util.core.text.TemplateProcessor;
 import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 
 
 public class InitdServiceInstaller implements SystemServiceInstaller {
@@ -79,12 +80,13 @@ public class InitdServiceInstaller implements SystemServiceInstaller {
         SshPutTaskWrapper putServiceTask = SshTasks.newSshPutTaskFactory(sshMachine, tmpServicePath)
                 .contents(service)
                 .newTask();
+        BashCommandsConfigurable bash = BrooklynOsCommands.bash(sshMachine.getManagementContext());
         ProcessTaskWrapper<Integer> installServiceTask = SshTasks.newSshExecTaskFactory(sshMachine,
-                BashCommands.chain(
-                    BashCommands.sudo("mv " + tmpServicePath + " " + servicePath),
-                    BashCommands.sudo("chmod 0755 " + servicePath),
-                    BashCommands.sudo("chkconfig --add " + serviceName),
-                    BashCommands.sudo("chkconfig " + serviceName + " on")))
+                bash.chain(
+                    bash.sudo("mv " + tmpServicePath + " " + servicePath),
+                    bash.sudo("chmod 0755 " + servicePath),
+                    bash.sudo("chkconfig --add " + serviceName),
+                    bash.sudo("chkconfig " + serviceName + " on")))
             .requiringExitCodeZero()
             .newTask();
 
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
index ff29408e36..38a6cb37b7 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractMultiDistroLiveTest.java
@@ -34,7 +34,8 @@ import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -142,7 +143,7 @@ public abstract class AbstractMultiDistroLiveTest extends BrooklynAppLiveTestSup
         Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), new Runnable() {
             @Override
             public void run() {
-                assertExecSsh(server, ImmutableList.of(BashCommands.installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url));
+                assertExecSsh(server, ImmutableList.of(BrooklynOsCommands.bash(server).installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url));
             }});
     }
 }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
index 5fda8bbbef..98ce92a8a9 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/mysql/DynamicToyMySqlEntityBuilder.java
@@ -18,8 +18,11 @@
  */
 package org.apache.brooklyn.entity.software.base.test.mysql;
 
-import java.io.File;
-
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Iterables;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntityInitializer;
 import org.apache.brooklyn.api.entity.EntityLocal;
@@ -34,21 +37,17 @@ import org.apache.brooklyn.entity.stock.BasicStartable;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation.LocalhostMachine;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.file.BrooklynOsCommands;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
-import org.apache.brooklyn.util.ssh.BashCommands;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Predicates;
-import com.google.common.base.Splitter;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.Iterables;
+import java.io.File;
 
 public class DynamicToyMySqlEntityBuilder {
 
@@ -109,10 +108,10 @@ public class DynamicToyMySqlEntityBuilder {
                         SshEffectorTasks.ssh(
                             "mkdir "+dir(entity),
                             "cd "+dir(entity),
-                            BashCommands.downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
+                            BrooklynOsCommands.bash(entity).downloadToStdout(downloadUrl(entity, isLocalhost(machineS)))+" | tar xvz"
                         ).summary("download mysql").returning(SshTasks.returningStdoutLoggingInfo(log, true)));
                 if (isLinux(machineS)) {
-                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.installPackage("libaio1")));
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BrooklynOsCommands.bash(entity).installPackage("libaio1")));
                 }
                 DynamicTasks.queue(
                         SshEffectorTasks.put(".my.cnf")
@@ -128,7 +127,7 @@ public class DynamicToyMySqlEntityBuilder {
             protected void postStartCustom(ConfigBag parameters) {
                 // if it's still up after 5s assume we are good
                 Time.sleep(Duration.FIVE_SECONDS);
-                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(dir(entity)+"/*/data/*.pid")).get()) {
+                if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(BrooklynOsCommands.bash(entity()), dir(entity)+"/*/data/*.pid")).get()) {
                     // but if it's not up add a bunch of other info
                     log.warn("MySQL did not start: "+dir(entity));
                     ProcessTaskWrapper<Integer> info = DynamicTasks.queue(SshEffectorTasks.ssh(
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
index 7cf1f3fa54..9a95ce0d4a 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
@@ -31,13 +31,13 @@ import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
 import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
 import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcessImpl;
 import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcessSshDriver;
-import org.apache.brooklyn.entity.system_service.SystemServiceEnricher;
+import org.apache.brooklyn.util.ssh.BashCommands;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 import org.apache.brooklyn.location.jclouds.JcloudsLocation;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.ssh.BashCommandsConfigurable;
 import org.apache.brooklyn.util.time.Duration;
 
 import com.google.common.base.Suppliers;
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java
index 78d202b536..7056d3ae3e 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsFirewalldTest.java
@@ -19,9 +19,8 @@
 package org.apache.brooklyn.util.ssh;
 
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -66,39 +65,41 @@ public class IptablesCommandsFirewalldTest {
             + "else echo \"( { which systemctl && systemctl is-active firewalld ; } || /usr/bin/systemctl is-active firewalld )\" | "
             + "sudo -E -n -S -s -- bash ; fi )";
 
+    static IptablesCommandsConfigurable testInstance = new IptablesCommandsConfigurable(BashCommandsConfigurable.newInstance());
+
     @Test
     public void testAddFirewalldRule() {
-        Assert.assertEquals(IptablesCommands.addFirewalldRule(Chain.INPUT,
+        Assert.assertEquals(testInstance.addFirewalldRule(Chain.INPUT,
                 Protocol.TCP, 3306, Policy.ACCEPT), addFirewalldRule);
     }
 
     @Test
     public void testFirewalldService() {
-        Assert.assertEquals(IptablesCommands.firewalldService("status"), firewalldService);
+        Assert.assertEquals(testInstance.firewalldService("status"), firewalldService);
     }
 
     @Test
     public void testFirewalldServiceRestart() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceRestart(), firewalldServiceRestart);
+        Assert.assertEquals(testInstance.firewalldServiceRestart(), firewalldServiceRestart);
     }
 
     @Test
     public void testFirewalldServiceStart() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceStart(), firewalldServiceStart);
+        Assert.assertEquals(testInstance.firewalldServiceStart(), firewalldServiceStart);
     }
 
     @Test
     public void testFirewalldServiceStatus() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceStatus(), firewalldServiceStatus);
+        Assert.assertEquals(testInstance.firewalldServiceStatus(), firewalldServiceStatus);
     }
 
     @Test
     public void testFirewalldServiceStop() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceStop(), firewalldServiceStop);
+        Assert.assertEquals(testInstance.firewalldServiceStop(), firewalldServiceStop);
     }
 
     @Test
     public void testFirewalldServiceIsActive() {
-        Assert.assertEquals(IptablesCommands.firewalldServiceIsActive(), firewalldServiceIsActive);
+        Assert.assertEquals(testInstance.firewalldServiceIsActive(), firewalldServiceIsActive);
     }
 }
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java
index 29d80be05e..be5843a76b 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/ssh/IptablesCommandsTest.java
@@ -19,9 +19,8 @@
 package org.apache.brooklyn.util.ssh;
 
 import org.apache.brooklyn.util.net.Protocol;
-import org.apache.brooklyn.util.ssh.IptablesCommands;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Chain;
-import org.apache.brooklyn.util.ssh.IptablesCommands.Policy;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Chain;
+import org.apache.brooklyn.util.ssh.IptablesCommandsConfigurable.Policy;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -52,37 +51,40 @@ public class IptablesCommandsTest {
             "(( echo \"WARNING: no known/successful package manager to install iptables-persistent, may fail subsequently\" | tee /dev/stderr ) || true) ) " +
             "&& ( if test \"$UID\" -eq 0; then ( /etc/init.d/iptables-persistent save ); else sudo -E -n -S -- /etc/init.d/iptables-persistent save; fi ) ) )";
 
+    static IptablesCommandsConfigurable testInstance = new IptablesCommandsConfigurable(BashCommandsConfigurable.newInstance());
+
     @Test
     public void testCleanUpIptablesRules() {
-        Assert.assertEquals(IptablesCommands.cleanUpIptablesRules(), cleanUptptablesRules);
+        Assert.assertEquals(testInstance.cleanUpIptablesRules(), cleanUptptablesRules);
     }
 
     @Test
     public void testInsertIptablesRules() {
-        Assert.assertEquals(IptablesCommands.insertIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.insertIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
                 insertIptablesRule);
     }
 
     @Test
     public void testAppendIptablesRules() {
-        Assert.assertEquals(IptablesCommands.appendIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.appendIptablesRule(Chain.INPUT, "eth0", Protocol.TCP, 3306, Policy.ACCEPT),
                 appendIptablesRule);
     }
 
     @Test
     public void testInsertIptablesRulesForAllInterfaces() {
-        Assert.assertEquals(IptablesCommands.insertIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.insertIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
                 insertIptablesRuleAll);
     }
 
     @Test
     public void testAppendIptablesRulesForAllInterfaces() {
-        Assert.assertEquals(IptablesCommands.appendIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
+        Assert.assertEquals(testInstance.appendIptablesRule(Chain.INPUT, Protocol.TCP, 3306, Policy.ACCEPT),
                 appendIptablesRuleAll);
     }
 
     @Test
     public void testSaveIptablesRules() {
-        Assert.assertEquals(IptablesCommands.saveIptablesRules(), saveIptablesRules);
+        Assert.assertEquals(testInstance.saveIptablesRules(), saveIptablesRules);
     }
+
 }