diff --git a/leap_cli.gemspec b/leap_cli.gemspec
index e54c88e0611edce38901ca8b7f07ba689a4b9ffb..12fc07fc83e0056b77f8fc6bc6767c243a3e5129 100644
--- a/leap_cli.gemspec
+++ b/leap_cli.gemspec
@@ -50,10 +50,7 @@ spec = Gem::Specification.new do |s|
   # note: gli version is also pinned in leap_cli.rb.
 
   # network gems
-  s.add_runtime_dependency('net-ssh', '~> 2.7')
-  # ^^ we can upgrade once we get off broken capistrano
-  # https://github.com/net-ssh/net-ssh/issues/145
-  s.add_runtime_dependency('capistrano', '~> 2.15')
+  s.add_runtime_dependency('sshkit', '~> 1.11')
 
   # crypto gems
   # s.add_runtime_dependency('gpgme')    # << does not build on debian jessie, so now optional.
diff --git a/lib/leap/platform.rb b/lib/leap/platform.rb
deleted file mode 100644
index 9112ef35ae48283d958153645ba0edac58e4df6a..0000000000000000000000000000000000000000
--- a/lib/leap/platform.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-module Leap
-
-  class Platform
-    class << self
-      #
-      # configuration
-      #
-
-      attr_reader :version
-      attr_reader :compatible_cli
-      attr_accessor :facts
-      attr_accessor :paths
-      attr_accessor :node_files
-      attr_accessor :monitor_username
-      attr_accessor :reserved_usernames
-
-      attr_accessor :hiera_dir
-      attr_accessor :hiera_path
-      attr_accessor :files_dir
-      attr_accessor :leap_dir
-      attr_accessor :init_path
-
-      attr_accessor :default_puppet_tags
-
-      def define(&block)
-        # some defaults:
-        @reserved_usernames = []
-        @hiera_dir  = '/etc/leap'
-        @hiera_path = '/etc/leap/hiera.yaml'
-        @leap_dir   = '/srv/leap'
-        @files_dir  = '/srv/leap/files'
-        @init_path  = '/srv/leap/initialized'
-        @default_puppet_tags = []
-
-        self.instance_eval(&block)
-
-        @version ||= Gem::Version.new("0.0")
-      end
-
-      def version=(version)
-        @version = Gem::Version.new(version)
-      end
-
-      def compatible_cli=(range)
-        @compatible_cli = range
-        @minimum_cli_version = Gem::Version.new(range.first)
-        @maximum_cli_version = Gem::Version.new(range.last)
-      end
-
-      #
-      # return true if the cli_version is compatible with this platform.
-      #
-      def compatible_with_cli?(cli_version)
-        cli_version = Gem::Version.new(cli_version)
-        cli_version >= @minimum_cli_version && cli_version <= @maximum_cli_version
-      end
-
-      #
-      # return true if the platform version is within the specified range.
-      #
-      def version_in_range?(range)
-        if range.is_a? String
-          range = range.split('..')
-        end
-        minimum_platform_version = Gem::Version.new(range.first)
-        maximum_platform_version = Gem::Version.new(range.last)
-        @version >= minimum_platform_version && @version <= maximum_platform_version
-      end
-
-      def major_version
-        if @version.segments.first == 0
-          @version.segments[0..1].join('.')
-        else
-          @version.segments.first
-        end
-      end
-
-      def method_missing(method, *args)
-        puts
-        puts "WARNING:"
-        puts "  leap_cli is out of date and does not understand `#{method}`."
-        puts "  called from: #{caller.first}"
-        puts "  please upgrade to a newer leap_cli"
-      end
-
-    end
-
-  end
-
-end
\ No newline at end of file
diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb
index fc8ab2b9c5722e7fe6268f4a64fd938d0d501772..c0e139e4b9e1b100fa2abd5be59e4f0aa965a497 100644
--- a/lib/leap_cli.rb
+++ b/lib/leap_cli.rb
@@ -1,6 +1,6 @@
 module LeapCli
-  module Commands; end  # for commands in leap_cli/commands
-  module Macro; end     # for macros in leap_platform/provider_base/lib/macros
+  module Commands; end  # for commands in leap_platform/lib/leap_cli/commands
+  module Macro; end     # for macros in leap_platform/lib/leap_cli/macros
 end
 
 $ruby_version = RUBY_VERSION.split('.').collect{ |i| i.to_i }.extend(Comparable)
@@ -11,11 +11,8 @@ $:.unshift(File.expand_path('../leap_cli/override',__FILE__))
 # for a few gems, things will break if using earlier versions.
 # enforce the compatible versions here:
 require 'rubygems'
-gem 'net-ssh', '~> 2.7'
 gem 'gli', '~> 2.12', '>= 2.12.0'
 
-require 'leap/platform'
-
 require 'leap_cli/version'
 require 'leap_cli/exceptions'
 
@@ -32,30 +29,13 @@ require 'leap_cli/core_ext/yaml'
 require 'leap_cli/log'
 require 'leap_cli/path'
 require 'leap_cli/util'
-require 'leap_cli/util/secret'
-require 'leap_cli/util/remote_command'
-require 'leap_cli/util/x509'
-require 'leap_cli/logger'
 require 'leap_cli/bootstrap'
 
-require 'leap_cli/ssh_key'
-require 'leap_cli/config/object'
-require 'leap_cli/config/node'
-require 'leap_cli/config/tag'
-require 'leap_cli/config/provider'
-require 'leap_cli/config/secrets'
-require 'leap_cli/config/object_list'
-require 'leap_cli/config/filter'
-require 'leap_cli/config/environment'
-require 'leap_cli/config/manager'
-
 require 'leap_cli/markdown_document_listener'
 
 #
 # allow everyone easy access to log() command.
 #
 module LeapCli
-  Util.send(:extend, LeapCli::LogCommand)
-  Config::Manager.send(:include, LeapCli::LogCommand)
   extend LeapCli::LogCommand
 end
diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb
index 9ccb3dd29e14c28a08323a718cad0be00b71bab5..f33aa4282e4b74c2aa9c5244c5f6a583d10647f0 100644
--- a/lib/leap_cli/bootstrap.rb
+++ b/lib/leap_cli/bootstrap.rb
@@ -39,6 +39,8 @@ module LeapCli
       if LeapCli.logger.log_level >= 2
         log_version
       end
+      add_platform_lib_to_path
+      load_platform_libraries
       load_commands(app)
       load_macros
     end
@@ -103,11 +105,10 @@ module LeapCli
       elsif !leapfile_optional?(argv)
         puts
         puts " ="
-        log :note, "There is no `Leapfile` in this directory, or any parent directory.\n"+
-                   " =       "+
+        log :NOTE, "There is no `Leapfile` in this directory, or any parent directory.\n"+
+                   " =      "+
                    "Without this file, most commands will not be available."
         puts " ="
-        puts
       end
     end
 
@@ -193,5 +194,26 @@ module LeapCli
       end
     end
 
+    #
+    # makes all the ruby libraries in the leap_platform/lib directory
+    # available for inclusion.
+    #
+    def add_platform_lib_to_path
+      if Path.platform
+        path = File.join(Path.platform, 'lib')
+        $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
+      end
+    end
+
+    #
+    # loads libraries that live in the platform and should
+    # always be available.
+    #
+    def load_platform_libraries
+      if Path.platform
+        require 'leap_cli/load_libraries'
+      end
+    end
+
   end
 end
diff --git a/lib/leap_cli/commands/common.rb b/lib/leap_cli/commands/common.rb
index 695a9f6a168269fe9f26089b79038638b7dc47d5..d49490ebd06f840ac602ff6d4794093f0251a3da 100644
--- a/lib/leap_cli/commands/common.rb
+++ b/lib/leap_cli/commands/common.rb
@@ -4,7 +4,6 @@ module LeapCli; module Commands
 
   extend LeapCli::LogCommand
   extend LeapCli::Util
-  extend LeapCli::Util::RemoteCommand
 
   def path(name)
     Path.named_path(name)
diff --git a/lib/leap_cli/config/environment.rb b/lib/leap_cli/config/environment.rb
deleted file mode 100644
index 398fd023002e35aad35c371ef0d6412dda3ed69c..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/environment.rb
+++ /dev/null
@@ -1,180 +0,0 @@
-#
-# All configurations files can be isolated into separate environments.
-#
-# Each config json in each environment inherits from the default environment,
-# which in term inherits from the "_base_" environment:
-#
-# _base_             -- base provider in leap_platform
-# '- default         -- environment in provider dir when no env is set
-#    '- production   -- example environment
-#
-
-module LeapCli; module Config
-
-  class Environment
-    # the String name of the environment
-    attr_accessor :name
-
-    # the shared Manager object
-    attr_accessor :manager
-
-    # hashes of {name => Config::Object}
-    attr_accessor :services, :tags, :partials
-
-    # a Config::Provider
-    attr_accessor :provider
-
-    # a Config::Object
-    attr_accessor :common
-
-    # shared, non-inheritable
-    def nodes; @@nodes; end
-    def secrets; @@secrets; end
-
-    def initialize(manager, name, search_dir, parent, options={})
-      @@nodes ||= nil
-      @@secrets ||= nil
-
-      @manager = manager
-      @name    = name
-
-      load_provider_files(search_dir, options)
-
-      if parent
-        @services.inherit_from! parent.services, self
-            @tags.inherit_from! parent.tags    , self
-        @partials.inherit_from! parent.partials, self
-          @common.inherit_from! parent.common
-        @provider.inherit_from! parent.provider
-      end
-
-      if @provider
-        @provider.set_env(name)
-        @provider.validate!
-      end
-    end
-
-    def load_provider_files(search_dir, options)
-      #
-      # load empty environment if search_dir doesn't exist
-      #
-      if search_dir.nil? || !Dir.exist?(search_dir)
-        @services = Config::ObjectList.new
-        @tags     = Config::ObjectList.new
-        @partials = Config::ObjectList.new
-        @provider = Config::Provider.new
-        @common   = Config::Object.new
-        return
-      end
-
-      #
-      # inheritable
-      #
-      if options[:scope]
-        scope = options[:scope]
-        @services = load_all_json(Path.named_path([:service_env_config, '*', scope],  search_dir), Config::Tag, options)
-        @tags     = load_all_json(Path.named_path([:tag_env_config, '*', scope],      search_dir), Config::Tag, options)
-        @partials = load_all_json(Path.named_path([:service_env_config, '_*', scope], search_dir), Config::Tag, options)
-        @provider = load_json(    Path.named_path([:provider_env_config, scope],      search_dir), Config::Provider, options)
-        @common   = load_json(    Path.named_path([:common_env_config, scope],        search_dir), Config::Object, options)
-      else
-        @services = load_all_json(Path.named_path([:service_config, '*'],  search_dir), Config::Tag, options)
-        @tags     = load_all_json(Path.named_path([:tag_config, '*'],      search_dir), Config::Tag, options)
-        @partials = load_all_json(Path.named_path([:service_config, '_*'], search_dir), Config::Tag, options)
-        @provider = load_json(    Path.named_path(:provider_config,        search_dir), Config::Provider, options)
-        @common   = load_json(    Path.named_path(:common_config,          search_dir), Config::Object, options)
-      end
-
-      # remove 'name' from partials, since partials get merged with nodes
-      @partials.values.each {|partial| partial.delete('name'); }
-
-      #
-      # shared: currently non-inheritable
-      # load the first ones we find, and only those.
-      #
-      if @@nodes.nil? || @@nodes.empty?
-        @@nodes = load_all_json(Path.named_path([:node_config, '*'], search_dir), Config::Node, options)
-      end
-      if @@secrets.nil? || @@secrets.empty?
-        @@secrets = load_json(Path.named_path(:secrets_config, search_dir), Config::Secrets, options)
-      end
-    end
-
-    #
-    # Loads a json template file as a Hash (used only when creating a new node .json
-    # file for the first time).
-    #
-    def template(template)
-      path = Path.named_path([:template_config, template], Path.provider_base)
-      if File.exist?(path)
-        return load_json(path, Config::Object)
-      else
-        return nil
-      end
-    end
-
-    private
-
-    def load_all_json(pattern, object_class, options={})
-      results = Config::ObjectList.new
-      Dir.glob(pattern).each do |filename|
-        next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/
-        obj = load_json(filename, object_class)
-        if obj
-          name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1')
-          obj['name'] ||= name
-          if options[:env]
-            obj.environment = options[:env]
-          end
-          results[name] = obj
-        end
-      end
-      results
-    end
-
-    def load_json(filename, object_class, options={})
-      if !File.exist?(filename)
-        return object_class.new(self)
-      end
-
-      Util::log :loading, filename, 3
-
-      #
-      # Read a JSON file, strip out comments.
-      #
-      # UTF8 is the default encoding for JSON, but others are allowed:
-      # https://www.ietf.org/rfc/rfc4627.txt
-      #
-      buffer = StringIO.new
-      File.open(filename, "rb", :encoding => 'UTF-8') do |f|
-        while (line = f.gets)
-          next if line =~ /^\s*\/\//
-          buffer << line
-        end
-      end
-
-      #
-      # force UTF-8
-      #
-      if $ruby_version >= [1,9]
-        string = buffer.string.force_encoding('utf-8')
-      else
-        string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string)
-      end
-
-      # parse json
-      begin
-        hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {}
-      rescue SyntaxError, JSON::ParserError => exc
-        Util::log 0, :error, 'in file "%s":' % filename
-        Util::log 0, exc.to_s, :indent => 1
-        return nil
-      end
-      object = object_class.new(self)
-      object.deep_merge!(hash)
-      return object
-    end
-
-  end # end Environment
-
-end; end
\ No newline at end of file
diff --git a/lib/leap_cli/config/filter.rb b/lib/leap_cli/config/filter.rb
deleted file mode 100644
index 2c80be809ac37fc9f5be531fc70666be7ae899cb..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/filter.rb
+++ /dev/null
@@ -1,178 +0,0 @@
-#
-# Many leap_cli commands accept a list of filters to select a subset of nodes for the command to
-# be applied to. This class is a helper for manager to run these filters.
-#
-# Classes other than Manager should not use this class.
-#
-# Filter rules:
-#
-# * A filter consists of a list of tokens
-# * A token may be a service name, tag name, environment name, or node name.
-# * Each token may be optionally prefixed with a plus sign.
-# * Multiple tokens with a plus are treated as an OR condition,
-#   but treated as an AND condition with the plus sign.
-#
-# For example
-#
-# * openvpn +development => all nodes with service 'openvpn' AND environment 'development'
-# * openvpn seattle => all nodes with service 'openvpn' OR tag 'seattle'.
-#
-# There can only be one environment specified. Typically, there are also tags
-# for each environment name. These name are treated as environments, not tags.
-#
-module LeapCli
-  module Config
-    class Filter
-
-      #
-      # filter -- array of strings, each one a filter
-      # options -- hash, possible keys include
-      #   :nopin -- disregard environment pinning
-      #   :local -- if false, disallow local nodes
-      #
-      # A nil value in the filters array indicates
-      # the default environment. This is in order to support
-      # calls like `manager.filter(environments)`
-      #
-      def initialize(filters, options, manager)
-        @filters = filters.nil? ? [] : filters.dup
-        @environments = []
-        @options = options
-        @manager = manager
-
-        # split filters by pulling out items that happen
-        # to be environment names.
-        if LeapCli.leapfile.environment.nil? || @options[:nopin]
-          @environments = []
-        else
-          @environments = [LeapCli.leapfile.environment]
-        end
-        @filters.select! do |filter|
-          if filter.nil?
-            @environments << nil unless @environments.include?(nil)
-            false
-          else
-            filter_text = filter.sub(/^\+/,'')
-            if is_environment?(filter_text)
-              if filter_text == LeapCli.leapfile.environment
-                # silently ignore already pinned environments
-              elsif (filter =~ /^\+/ || @filters.first == filter) && !@environments.empty?
-                LeapCli::Util.bail! do
-                  LeapCli::Util.log "Environments are exclusive: no node is in two environments." do
-                    LeapCli::Util.log "Tried to filter on '#{@environments.join('\' AND \'')}' AND '#{filter_text}'"
-                  end
-                end
-              else
-                @environments << filter_text
-              end
-              false
-            else
-              true
-            end
-          end
-        end
-
-        # don't let the first filter have a + prefix
-        if @filters[0] =~ /^\+/
-          @filters[0] = @filters[0][1..-1]
-        end
-      end
-
-      # actually run the filter, returns a filtered list of nodes
-      def nodes()
-        if @filters.empty?
-          return nodes_for_empty_filter
-        else
-          return nodes_for_filter
-        end
-      end
-
-      private
-
-      def nodes_for_empty_filter
-        node_list = @manager.nodes
-        if @environments.any?
-          node_list = node_list[ @environments.collect{|e|[:environment, env_to_filter(e)]} ]
-        end
-        if @options[:local] === false
-          node_list = node_list[:environment => '!local']
-        end
-        if @options[:disabled] === false
-          node_list = node_list[:environment => '!disabled']
-        end
-        node_list
-      end
-
-      def nodes_for_filter
-        node_list = Config::ObjectList.new
-        @filters.each do |filter|
-          if filter =~ /^\+/
-            keep_list = nodes_for_name(filter[1..-1])
-            node_list.delete_if do |name, node|
-              if keep_list[name]
-                false
-              else
-                true
-              end
-            end
-          else
-            node_list.merge!(nodes_for_name(filter))
-          end
-        end
-        node_list
-      end
-
-      private
-
-      #
-      # returns a set of nodes corresponding to a single name,
-      # where name could be a node name, service name, or tag name.
-      #
-      # For services and tags, we only include nodes for the
-      # environments that are active
-      #
-      def nodes_for_name(name)
-        if node = @manager.nodes[name]
-          return Config::ObjectList.new(node)
-        elsif @environments.empty?
-          if @manager.services[name]
-            return @manager.env('_all_').services[name].node_list
-          elsif @manager.tags[name]
-            return @manager.env('_all_').tags[name].node_list
-          else
-            LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments."
-            return Config::ObjectList.new
-          end
-        else
-          node_list = Config::ObjectList.new
-          if @manager.services[name]
-            @environments.each do |env|
-              node_list.merge!(@manager.env(env).services[name].node_list)
-            end
-          elsif @manager.tags[name]
-            @environments.each do |env|
-              node_list.merge!(@manager.env(env).tags[name].node_list)
-            end
-          else
-            LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments."
-          end
-          return node_list
-        end
-      end
-
-      #
-      # when pinning, we use the name 'default' to specify nodes
-      # without an environment set, but when filtering, we need to filter
-      # on :environment => nil.
-      #
-      def env_to_filter(environment)
-        environment == 'default' ? nil : environment
-      end
-
-      def is_environment?(text)
-        text == 'default' || @manager.environment_names.include?(text)
-      end
-
-    end
-  end
-end
diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb
deleted file mode 100644
index 80ccbade51c6e6569e0f22799b5481cb17f7c74b..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/manager.rb
+++ /dev/null
@@ -1,422 +0,0 @@
-# encoding: utf-8
-
-require 'json/pure'
-
-if $ruby_version < [1,9]
-  require 'iconv'
-end
-
-module LeapCli
-  module Config
-
-    #
-    # A class to manage all the objects in all the configuration files.
-    #
-    class Manager
-
-      def initialize
-        @environments = {} # hash of `Environment` objects, keyed by name.
-        Config::Object.send(:include, LeapCli::Macro)
-      end
-
-      ##
-      ## ATTRIBUTES
-      ##
-
-      #
-      # returns the Hash of the contents of facts.json
-      #
-      def facts
-        @facts ||= begin
-          content = Util.read_file(:facts)
-          if !content || content.empty?
-            content = "{}"
-          end
-          JSON.parse(content)
-        rescue SyntaxError, JSON::ParserError => exc
-          Util::bail! "Could not parse facts.json -- #{exc}"
-        end
-      end
-
-      #
-      # returns an Array of all the environments defined for this provider.
-      # the returned array includes nil (for the default environment)
-      #
-      def environment_names
-        @environment_names ||= begin
-          [nil] + (env.tags.field('environment') + env.nodes.field('environment')).compact.uniq
-        end
-      end
-
-      #
-      # Returns the appropriate environment variable
-      #
-      def env(env=nil)
-        @environments[env || 'default']
-      end
-
-      #
-      # The default accessors
-      #
-      # For these defaults, use 'default' environment, or whatever
-      # environment is pinned.
-      #
-      # I think it might be an error that these are ever used
-      # and I would like to get rid of them.
-      #
-      def services; env(default_environment).services; end
-      def tags;     env(default_environment).tags;     end
-      def partials; env(default_environment).partials; end
-      def provider; env(default_environment).provider; end
-      def common;   env(default_environment).common;   end
-      def secrets;  env(default_environment).secrets;  end
-      def nodes;    env(default_environment).nodes;    end
-      def template(*args)
-        self.env.template(*args)
-      end
-
-      def default_environment
-        LeapCli.leapfile.environment
-      end
-
-      ##
-      ## IMPORT EXPORT
-      ##
-
-      def add_environment(args)
-        if args[:inherit]
-          parent = @environments[args.delete(:inherit)]
-        else
-          parent = nil
-        end
-        @environments[args[:name]] = Environment.new(
-          self,
-          args.delete(:name),
-          args.delete(:dir),
-          parent,
-          args
-        )
-      end
-
-      #
-      # load .json configuration files
-      #
-      def load(options = {})
-        @provider_dir = Path.provider
-
-        # load base
-        add_environment(name: '_base_', dir: Path.provider_base)
-
-        # load provider
-        Util::assert_files_exist!(Path.named_path(:provider_config, @provider_dir))
-        add_environment(name: 'default', dir: @provider_dir,
-          inherit: '_base_', no_dots: true)
-
-        # create a special '_all_' environment, used for tracking
-        # the union of all the environments
-        add_environment(name: '_all_', inherit: 'default')
-
-        # load environments
-        environment_names.each do |ename|
-          if ename
-            log 3, :loading, '%s environment...' % ename
-            add_environment(name: ename, dir: @provider_dir,
-              inherit: 'default', scope: ename)
-          end
-        end
-
-        # apply inheritance
-        env.nodes.each do |name, node|
-          Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
-          env.nodes[name] = apply_inheritance(node)
-        end
-
-        # do some node-list post-processing
-        cleanup_node_lists(options)
-
-        # apply control files
-        env.nodes.each do |name, node|
-          control_files(node).each do |file|
-            begin
-              node.eval_file file
-            rescue ConfigError => exc
-              if options[:continue_on_error]
-                exc.log
-              else
-                raise exc
-              end
-            end
-          end
-        end
-      end
-
-      #
-      # save compiled hiera .yaml files
-      #
-      # if a node_list is specified, only update those .yaml files.
-      # otherwise, update all files, destroying files that are no longer used.
-      #
-      def export_nodes(node_list=nil)
-        updated_hiera = []
-        updated_files = []
-        existing_hiera = nil
-        existing_files = nil
-
-        unless node_list
-          node_list = env.nodes
-          existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], @provider_dir))
-          existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], @provider_dir))
-        end
-
-        node_list.each_node do |node|
-          filepath = Path.named_path([:node_files_dir, node.name], @provider_dir)
-          hierapath = Path.named_path([:hiera, node.name], @provider_dir)
-          Util::write_file!(hierapath, node.dump_yaml)
-          updated_files << filepath
-          updated_hiera << hierapath
-        end
-
-        if @disabled_nodes
-          # make disabled nodes appear as if they are still active
-          @disabled_nodes.each_node do |node|
-            updated_files << Path.named_path([:node_files_dir, node.name], @provider_dir)
-            updated_hiera << Path.named_path([:hiera, node.name], @provider_dir)
-          end
-        end
-
-        # remove files that are no longer needed
-        if existing_hiera
-          (existing_hiera - updated_hiera).each do |filepath|
-            Util::remove_file!(filepath)
-          end
-        end
-        if existing_files
-          (existing_files - updated_files).each do |filepath|
-            Util::remove_directory!(filepath)
-          end
-        end
-      end
-
-      def export_secrets(clean_unused_secrets = false)
-        if env.secrets.any?
-          Util.write_file!([:secrets_config, @provider_dir], env.secrets.dump_json(clean_unused_secrets) + "\n")
-        end
-      end
-
-      ##
-      ## FILTERING
-      ##
-
-      #
-      # returns a node list consisting only of nodes that satisfy the filter criteria.
-      #
-      # filter: condition [condition] [condition] [+condition]
-      # condition: [node_name | service_name | tag_name | environment_name]
-      #
-      # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR.
-      #
-      # args:
-      # filter -- array of filter terms, one per item
-      #
-      # options:
-      # :local -- if :local is false and the filter is empty, then local nodes are excluded.
-      # :nopin -- if true, ignore environment pinning
-      #
-      def filter(filters=nil, options={})
-        Filter.new(filters, options, self).nodes()
-      end
-
-      #
-      # same as filter(), but exits if there is no matching nodes
-      #
-      def filter!(filters, options={})
-        node_list = filter(filters, options)
-        Util::assert! node_list.any?, "Could not match any nodes from '#{filters.join ' '}'"
-        return node_list
-      end
-
-      #
-      # returns a single Config::Object that corresponds to a Node.
-      #
-      def node(name)
-        if name =~ /\./
-          # probably got a fqdn, since periods are not allowed in node names.
-          # so, take the part before the first period as the node name
-          name = name.split('.').first
-        end
-        env.nodes[name]
-      end
-
-      #
-      # returns a single node that is disabled
-      #
-      def disabled_node(name)
-        @disabled_nodes[name]
-      end
-
-      #
-      # yields each node, in sorted order
-      #
-      def each_node(&block)
-        env.nodes.each_node(&block)
-      end
-
-      def reload_node!(node)
-        env.nodes[node.name] = apply_inheritance!(node)
-      end
-
-      ##
-      ## CONNECTIONS
-      ##
-
-      class ConnectionList < Array
-        def add(data={})
-          self << {
-            "from" => data[:from],
-            "to" => data[:to],
-            "port" => data[:port]
-          }
-        end
-      end
-
-      def connections
-        @connections ||= ConnectionList.new
-      end
-
-      ##
-      ## PRIVATE
-      ##
-
-      private
-
-      #
-      # makes a node inherit options from appropriate the common, service, and tag json files.
-      #
-      def apply_inheritance(node, throw_exceptions=false)
-        new_node = Config::Node.new(nil)
-        node_env = guess_node_env(node)
-        new_node.set_environment(node_env, new_node)
-
-        # inherit from common
-        new_node.deep_merge!(node_env.common)
-
-        # inherit from services
-        if node['services']
-          node['services'].to_a.each do |node_service|
-            service = node_env.services[node_service]
-            if service.nil?
-              msg = 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
-              log 0, :error, msg
-              raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
-            else
-              new_node.deep_merge!(service)
-            end
-          end
-        end
-
-        # inherit from tags
-        if node.vagrant?
-          node['tags'] = (node['tags'] || []).to_a + ['local']
-        end
-        if node['tags']
-          node['tags'].to_a.each do |node_tag|
-            tag = node_env.tags[node_tag]
-            if tag.nil?
-              msg = 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag]
-              log 0, :error, msg
-              raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
-            else
-              new_node.deep_merge!(tag)
-            end
-          end
-        end
-
-        # inherit from node
-        new_node.deep_merge!(node)
-        return new_node
-      end
-
-      def apply_inheritance!(node)
-        apply_inheritance(node, true)
-      end
-
-      #
-      # Guess the environment of the node from the tag names.
-      #
-      # Technically, this is wrong: a tag that sets the environment might not be
-      # named the same as the environment. This code assumes that it is.
-      #
-      # Unfortunately, it is a chicken and egg problem. We need to know the nodes
-      # likely environment in order to apply the inheritance that will actually
-      # determine the node's properties.
-      #
-      def guess_node_env(node)
-        if node.vagrant?
-          return self.env("local")
-        else
-          environment = self.env(default_environment)
-          if node['tags']
-            node['tags'].to_a.each do |tag|
-              if self.environment_names.include?(tag)
-                environment = self.env(tag)
-              end
-            end
-          end
-          return environment
-        end
-      end
-
-      #
-      # does some final clean at the end of loading nodes.
-      # this includes removing disabled nodes, and populating
-      # the services[x].node_list and tags[x].node_list
-      #
-      def cleanup_node_lists(options)
-        @disabled_nodes = Config::ObjectList.new
-        env.nodes.each do |name, node|
-          if node.enabled || options[:include_disabled]
-            if node['services']
-              node['services'].to_a.each do |node_service|
-                env(node.environment).services[node_service].node_list.add(node.name, node)
-                env('_all_').services[node_service].node_list.add(node.name, node)
-              end
-            end
-            if node['tags']
-              node['tags'].to_a.each do |node_tag|
-                env(node.environment).tags[node_tag].node_list.add(node.name, node)
-                env('_all_').tags[node_tag].node_list.add(node.name, node)
-              end
-            end
-          elsif !options[:include_disabled]
-            log 2, :skipping, "disabled node #{name}."
-            env.nodes.delete(name)
-            @disabled_nodes[name] = node
-          end
-        end
-      end
-
-      #
-      # returns a list of 'control' files for this node.
-      # a control file is like a service or a tag JSON file, but it contains
-      # raw ruby code that gets evaluated in the context of the node.
-      # Yes, this entirely breaks our functional programming model
-      # for JSON generation.
-      #
-      def control_files(node)
-        files = []
-        [Path.provider_base, @provider_dir].each do |provider_dir|
-          [['services', :service_config], ['tags', :tag_config]].each do |attribute, path_sym|
-            node[attribute].each do |attr_value|
-              path = Path.named_path([path_sym, "#{attr_value}.rb"], provider_dir).sub(/\.json$/,'')
-              if File.exist?(path)
-                files << path
-              end
-            end
-          end
-        end
-        return files
-      end
-
-    end
-  end
-end
diff --git a/lib/leap_cli/config/node.rb b/lib/leap_cli/config/node.rb
deleted file mode 100644
index 65735d52f68d202b65d88d267adf977507272d7b..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/node.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Configuration for a 'node' (a server in the provider's infrastructure)
-#
-
-require 'ipaddr'
-
-module LeapCli; module Config
-
-  class Node < Object
-    attr_accessor :file_paths
-
-    def initialize(environment=nil)
-      super(environment)
-      @node = self
-      @file_paths = []
-    end
-
-    #
-    # returns true if this node has an ip address in the range of the vagrant network
-    #
-    def vagrant?
-      begin
-        vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network
-      rescue ArgumentError => exc
-        Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" }
-      end
-
-      begin
-        ip_address = IPAddr.new @node.get('ip_address')
-      rescue ArgumentError => exc
-        Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'"
-      end
-      return vagrant_range.include?(ip_address)
-    end
-
-    #
-    # Return a hash table representation of ourselves, with the key equal to the @node.name,
-    # and the value equal to the fields specified in *keys.
-    #
-    # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b'
-    #
-    # compare to Object#pick(*keys). This method is the sames as Config::ObjectList#pick_fields,
-    # but works on a single node.
-    #
-    # Example:
-    #
-    #  node.pick('domain.internal') =>
-    #
-    #    {
-    #      'node1': {
-    #        'domain_internal': 'node1.example.i'
-    #      }
-    #    }
-    #
-    def pick_fields(*keys)
-      {@node.name => self.pick(*keys)}
-    end
-
-    #
-    # can be overridden by the platform.
-    # returns a list of node names that should be tested before this node
-    #
-    def test_dependencies
-      []
-    end
-
-    # returns a string list of supported ssh host key algorithms for this node.
-    # or an empty string if it could not be determined
-    def supported_ssh_host_key_algorithms
-      @host_key_algo ||= SshKey.supported_host_key_algorithms(
-        Util.read_file([:node_ssh_pub_key, @node.name])
-      )
-    end
-
-  end
-
-end; end
diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb
deleted file mode 100644
index b117c2f08598c6141de27fd80bf3b080f07dd0a5..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/object.rb
+++ /dev/null
@@ -1,428 +0,0 @@
-# encoding: utf-8
-
-require 'erb'
-require 'json/pure'  # pure ruby implementation is required for our sorted trick to work.
-
-if $ruby_version < [1,9]
-  $KCODE = 'UTF8'
-end
-require 'ya2yaml' # pure ruby yaml
-
-module LeapCli
-  module Config
-
-    #
-    # This class represents the configuration for a single node, service, or tag.
-    # Also, all the nested hashes are also of this type.
-    #
-    # It is called 'object' because it corresponds to an Object in JSON.
-    #
-    class Object < Hash
-
-      attr_reader :env
-      attr_reader :node
-
-      def initialize(environment=nil, node=nil)
-        raise ArgumentError unless environment.nil? || environment.is_a?(Config::Environment)
-        @env = environment
-        # an object that is a node as @node equal to self, otherwise all the
-        # child objects point back to the top level node.
-        @node = node || self
-      end
-
-      def manager
-        @env.manager
-      end
-
-      #
-      # TODO: deprecate node.global()
-      #
-      def global
-        @env
-      end
-
-      def environment=(e)
-        self.store('environment', e)
-      end
-
-      def environment
-        self['environment']
-      end
-
-      def duplicate(env)
-        new_object = self.deep_dup
-        new_object.set_environment(env, new_object)
-      end
-
-      #
-      # export YAML
-      #
-      # We use pure ruby yaml exporter ya2yaml instead of SYCK or PSYCH because it
-      # allows us greater compatibility regardless of installed ruby version and
-      # greater control over how the yaml is exported (sorted keys, in particular).
-      #
-      def dump_yaml
-        evaluate(@node)
-        sorted_ya2yaml(:syck_compatible => true)
-      end
-
-      #
-      # export JSON
-      #
-      def dump_json(options={})
-        evaluate(@node)
-        if options[:format] == :compact
-          return self.to_json
-        else
-          excluded = {}
-          if options[:exclude]
-            options[:exclude].each do |key|
-              excluded[key] = self[key]
-              self.delete(key)
-            end
-          end
-          json_str = JSON.sorted_generate(self)
-          if excluded.any?
-            self.merge!(excluded)
-          end
-          return json_str
-        end
-      end
-
-      def evaluate(context=@node)
-        evaluate_everything(context)
-        late_evaluate_everything(context)
-      end
-
-      ##
-      ## FETCHING VALUES
-      ##
-
-      def [](key)
-        get(key)
-      end
-
-      # Overrride some default methods in Hash that are likely to
-      # be used as attributes.
-      alias_method :hkey, :key
-      def key; get('key'); end
-
-      #
-      # make hash addressable like an object (e.g. obj['name'] available as obj.name)
-      #
-      def method_missing(method, *args, &block)
-        get!(method)
-      end
-
-      def get(key)
-        begin
-          get!(key)
-        rescue NoMethodError
-          nil
-        end
-      end
-
-      # override behavior of #default() from Hash
-      def default
-        get!('default')
-      end
-
-      #
-      # Like a normal Hash#[], except:
-      #
-      # (1) lazily eval dynamic values when we encounter them. (i.e. strings that start with "= ")
-      #
-      # (2) support for nested references in a single string (e.g. ['a.b'] is the same as ['a']['b'])
-      #     the dot path is always absolute, starting at the top-most object.
-      #
-      def get!(key)
-        key = key.to_s
-        if self.has_key?(key)
-          fetch_value(key)
-        elsif key =~ /\./
-          # for keys with with '.' in them, we start from the root object (@node).
-          keys = key.split('.')
-          value = self.get!(keys.first)
-          if value.is_a? Config::Object
-            value.get!(keys[1..-1].join('.'))
-          else
-            value
-          end
-        else
-          raise NoMethodError.new(key, "No method '#{key}' for #{self.class}")
-        end
-      end
-
-      ##
-      ## COPYING
-      ##
-
-      #
-      # A deep (recursive) merge with another Config::Object.
-      #
-      # If prefer_self is set to true, the value from self will be picked when there is a conflict
-      # that cannot be merged.
-      #
-      # Merging rules:
-      #
-      # - If a value is a hash, we recursively merge it.
-      # - If the value is simple, like a string, the new one overwrites the value.
-      # - If the value is an array:
-      #   - If both old and new values are arrays, the new one replaces the old.
-      #   - If one of the values is simple but the other is an array, the simple is added to the array.
-      #
-      def deep_merge!(object, prefer_self=false)
-        object.each do |key,new_value|
-          if self.has_key?('+'+key)
-            mode = :add
-            old_value = self.fetch '+'+key, nil
-            self.delete('+'+key)
-          elsif self.has_key?('-'+key)
-            mode = :subtract
-            old_value = self.fetch '-'+key, nil
-            self.delete('-'+key)
-          elsif self.has_key?('!'+key)
-            mode = :replace
-            old_value = self.fetch '!'+key, nil
-            self.delete('!'+key)
-          else
-            mode = :normal
-            old_value = self.fetch key, nil
-          end
-
-          # clean up boolean
-          new_value = true  if new_value == "true"
-          new_value = false if new_value == "false"
-          old_value = true  if old_value == "true"
-          old_value = false if old_value == "false"
-
-          # force replace?
-          if mode == :replace && prefer_self
-            value = old_value
-
-          # merge hashes
-          elsif old_value.is_a?(Hash) || new_value.is_a?(Hash)
-            value = Config::Object.new(@env, @node)
-            old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if !old_value.nil?)
-            new_value.is_a?(Hash) ? value.deep_merge!(new_value, prefer_self) : (value[key] = new_value if !new_value.nil?)
-
-          # merge nil
-          elsif new_value.nil?
-            value = old_value
-          elsif old_value.nil?
-            value = new_value
-
-          # merge arrays when one value is not an array
-          elsif old_value.is_a?(Array) && !new_value.is_a?(Array)
-            (value = (old_value.dup << new_value).compact.uniq).delete('REQUIRED')
-          elsif new_value.is_a?(Array) && !old_value.is_a?(Array)
-            (value = (new_value.dup << old_value).compact.uniq).delete('REQUIRED')
-
-          # merge two arrays
-          elsif old_value.is_a?(Array) && new_value.is_a?(Array)
-            if mode == :add
-              value = (old_value + new_value).sort.uniq
-            elsif mode == :subtract
-              value = new_value - old_value
-            elsif prefer_self
-              value = old_value
-            else
-              value = new_value
-            end
-
-          # catch errors
-          elsif type_mismatch?(old_value, new_value)
-            raise 'Type mismatch. Cannot merge %s (%s) with %s (%s). Key is "%s", name is "%s".' % [
-              old_value.inspect, old_value.class,
-              new_value.inspect, new_value.class,
-              key, self.class
-            ]
-
-          # merge simple strings & numbers
-          else
-            if prefer_self
-              value = old_value
-            else
-              value = new_value
-            end
-          end
-
-          # save value
-          self[key] = value
-        end
-        self
-      end
-
-      def set_environment(env, node)
-        @env = env
-        @node = node
-        self.each do |key, value|
-          if value.is_a?(Config::Object)
-            value.set_environment(env, node)
-          end
-        end
-      end
-
-      #
-      # like a reverse deep merge
-      # (self takes precedence)
-      #
-      def inherit_from!(object)
-        self.deep_merge!(object, true)
-      end
-
-      #
-      # Make a copy of ourselves, except only including the specified keys.
-      #
-      # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b'
-      #
-      def pick(*keys)
-        keys.map(&:to_s).inject(self.class.new(@manager)) do |hsh, key|
-          value = self.get(key)
-          if !value.nil?
-            hsh[key.gsub('.','_')] = value
-          end
-          hsh
-        end
-      end
-
-      def eval_file(filename)
-        evaluate_ruby(filename, File.read(filename))
-      end
-
-      protected
-
-      #
-      # walks the object tree, eval'ing all the attributes that are dynamic ruby (e.g. value starts with '= ')
-      #
-      def evaluate_everything(context)
-        keys.each do |key|
-          obj = fetch_value(key, context)
-          if is_required_value_not_set?(obj)
-            Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"."
-          elsif obj.is_a? Config::Object
-            obj.evaluate_everything(context)
-          end
-        end
-      end
-
-      #
-      # some keys need to be evaluated 'late', after all the other keys have been evaluated.
-      #
-      def late_evaluate_everything(context)
-        if @late_eval_list
-          @late_eval_list.each do |key, value|
-            self[key] = context.evaluate_ruby(key, value)
-            if is_required_value_not_set?(self[key])
-              Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"."
-            end
-          end
-        end
-        values.each do |obj|
-          if obj.is_a? Config::Object
-            obj.late_evaluate_everything(context)
-          end
-        end
-      end
-
-      #
-      # evaluates the string `value` as ruby in the context of self.
-      # (`key` is just passed for debugging purposes)
-      #
-      def evaluate_ruby(key, value)
-        self.instance_eval(value, key, 1)
-      rescue ConfigError => exc
-        raise exc # pass through
-      rescue SystemStackError => exc
-        Util::log 0, :error, "while evaluating node '#{self.name}'"
-        Util::log 0, "offending key: #{key}", :indent => 1
-        Util::log 0, "offending string: #{value}", :indent => 1
-        Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1
-        raise SystemExit.new(1)
-      rescue FileMissing => exc
-        Util::bail! do
-          if exc.options[:missing]
-            Util::log :missing, exc.options[:missing].gsub('$node', self.name).gsub('$file', exc.path)
-          else
-            Util::log :error, "while evaluating node '#{self.name}'"
-            Util::log "offending key: #{key}", :indent => 1
-            Util::log "offending string: #{value}", :indent => 1
-            Util::log "error message: no file '#{exc}'", :indent => 1
-          end
-          raise exc if DEBUG
-        end
-      rescue AssertionFailed => exc
-        Util.bail! do
-          Util::log :failed, "assertion while evaluating node '#{self.name}'"
-          Util::log 'assertion: %s' % exc.assertion, :indent => 1
-          Util::log "offending key: #{key}", :indent => 1
-          raise exc if DEBUG
-        end
-      rescue SyntaxError, StandardError => exc
-        Util::bail! do
-          Util::log :error, "while evaluating node '#{self.name}'"
-          Util::log "offending key: #{key}", :indent => 1
-          Util::log "offending string: #{value}", :indent => 1
-          Util::log "error message: #{exc.inspect}", :indent => 1
-          raise exc if DEBUG
-        end
-      end
-
-      private
-
-      #
-      # fetches the value for the key, evaluating the value as ruby if it begins with '='
-      #
-      def fetch_value(key, context=@node)
-        value = fetch(key, nil)
-        if value.is_a?(String) && value =~ /^=/
-          if value =~ /^=> (.*)$/
-            value = evaluate_later(key, $1)
-          elsif value =~ /^= (.*)$/
-            value = context.evaluate_ruby(key, $1)
-          end
-          self[key] = value
-        end
-        return value
-      end
-
-      def evaluate_later(key, value)
-        @late_eval_list ||= []
-        @late_eval_list << [key, value]
-        '<evaluate later>'
-      end
-
-      #
-      # when merging, we raise an error if this method returns true for the two values.
-      #
-      def type_mismatch?(old_value, new_value)
-        if old_value.is_a?(Boolean) && new_value.is_a?(Boolean)
-          # note: FalseClass and TrueClass are different classes
-          # so we can't do old_value.class == new_value.class
-          return false
-        elsif old_value.is_a?(String) && old_value =~ /^=/
-          # pass through macros, since we don't know what the type will eventually be.
-          return false
-        elsif new_value.is_a?(String) && new_value =~ /^=/
-          return false
-        elsif old_value.class == new_value.class
-          return false
-        else
-          return true
-        end
-      end
-
-      #
-      # returns true if the value has not been changed and the default is "REQUIRED"
-      #
-      def is_required_value_not_set?(value)
-        if value.is_a? Array
-          value == ["REQUIRED"]
-        else
-          value == "REQUIRED"
-        end
-      end
-
-    end # class
-  end # module
-end # module
\ No newline at end of file
diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb
deleted file mode 100644
index f9299a611de0b9511757d1940da0c126e72295e3..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/object_list.rb
+++ /dev/null
@@ -1,209 +0,0 @@
-require 'tsort'
-
-module LeapCli
-  module Config
-    #
-    # A list of Config::Object instances (internally stored as a hash)
-    #
-    class ObjectList < Hash
-      include TSort
-
-      def initialize(config=nil)
-        if config
-          self.add(config['name'], config)
-        end
-      end
-
-      #
-      # If the key is a string, the Config::Object it references is returned.
-      #
-      # If the key is a hash, we treat it as a condition and filter all the Config::Objects using the condition.
-      # A new ObjectList is returned.
-      #
-      # Examples:
-      #
-      # nodes['vpn1']
-      #   node named 'vpn1'
-      #
-      # nodes[:public_dns => true]
-      #   all nodes with public dns
-      #
-      # nodes[:services => 'openvpn', 'location.country_code' => 'US']
-      #   all nodes with services containing 'openvpn' OR country code of US
-      #
-      # Sometimes, you want to do an OR condition with multiple conditions
-      # for the same field. Since hash keys must be unique, you can use
-      # an array representation instead:
-      #
-      # nodes[[:services, 'openvpn'], [:services, 'tor']]
-      #   nodes with openvpn OR tor service
-      #
-      # nodes[:services => 'openvpn'][:tags => 'production']
-      #   nodes with openvpn AND are production
-      #
-      def [](key)
-        if key.is_a?(Hash) || key.is_a?(Array)
-          filter(key)
-        else
-          super key.to_s
-        end
-      end
-
-      def exclude(node)
-        list = self.dup
-        list.delete(node.name)
-        return list
-      end
-
-      def each_node(&block)
-        self.keys.sort.each do |node_name|
-          yield self[node_name]
-        end
-      end
-
-      #
-      # filters this object list, producing a new list.
-      # filter is an array or a hash. see []
-      #
-      def filter(filter)
-        results = Config::ObjectList.new
-        filter.each do |field, match_value|
-          field = field.is_a?(Symbol) ? field.to_s : field
-          match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value
-          if match_value.is_a?(String) && match_value =~ /^!/
-            operator = :not_equal
-            match_value = match_value.sub(/^!/, '')
-          else
-            operator = :equal
-          end
-          each do |name, config|
-            value = config[field]
-            if value.is_a? Array
-              if operator == :equal && value.include?(match_value)
-                results[name] = config
-              elsif operator == :not_equal && !value.include?(match_value)
-                results[name] = config
-              end
-            else
-              if operator == :equal && value == match_value
-                results[name] = config
-              elsif operator == :not_equal && value != match_value
-                results[name] = config
-              end
-            end
-          end
-        end
-        results
-      end
-
-      def add(name, object)
-        self[name] = object
-      end
-
-      #
-      # converts the hash of configs into an array of hashes, with ONLY the specified fields
-      #
-      def fields(*fields)
-        result = []
-        keys.sort.each do |name|
-          result << self[name].pick(*fields)
-        end
-        result
-      end
-
-      #
-      # like fields(), but returns an array of values instead of an array of hashes.
-      #
-      def field(field)
-        field = field.to_s
-        result = []
-        keys.sort.each do |name|
-          result << self[name].get(field)
-        end
-        result
-      end
-
-      #
-      # pick_fields(field1, field2, ...)
-      #
-      # generates a Hash from the object list, but with only the fields that are picked.
-      #
-      # If there are more than one field, then the result is a Hash of Hashes.
-      # If there is just one field, it is a simple map to the value.
-      #
-      # For example:
-      #
-      #   "neighbors" = "= nodes_like_me[:services => :couchdb].pick_fields('domain.full', 'ip_address')"
-      #
-      # generates this:
-      #
-      #   neighbors:
-      #     couch1:
-      #       domain_full: couch1.bitmask.net
-      #       ip_address: "10.5.5.44"
-      #     couch2:
-      #       domain_full: couch2.bitmask.net
-      #       ip_address: "10.5.5.52"
-      #
-      # But this:
-      #
-      #   "neighbors": "= nodes_like_me[:services => :couchdb].pick_fields('domain.full')"
-      #
-      # will generate this:
-      #
-      #   neighbors:
-      #     couch1: couch1.bitmask.net
-      #     couch2: couch2.bitmask.net
-      #
-      def pick_fields(*fields)
-        self.values.inject({}) do |hsh, node|
-          value = self[node.name].pick(*fields)
-          if fields.size == 1
-            value = value.values.first
-          end
-          hsh[node.name] = value
-          hsh
-        end
-      end
-
-      #
-      # Applies inherit_from! to all objects.
-      #
-      # 'env' specifies what environment should be for
-      # each object in the list.
-      #
-      def inherit_from!(object_list, env)
-        object_list.each do |name, object|
-          if self[name]
-            self[name].inherit_from!(object)
-          else
-            self[name] = object.duplicate(env)
-          end
-        end
-      end
-
-      #
-      # topographical sort based on test dependency
-      #
-      def tsort_each_node(&block)
-        self.each_key(&block)
-      end
-
-      def tsort_each_child(node_name, &block)
-        if self[node_name]
-          self[node_name].test_dependencies.each do |test_me_first|
-            if self[test_me_first] # TODO: in the future, allow for ability to optionally pull in all dependencies.
-                                   # not just the ones that pass the node filter.
-              yield(test_me_first)
-            end
-          end
-        end
-      end
-
-      def names_in_test_dependency_order
-        self.tsort
-      end
-
-    end
-  end
-end
diff --git a/lib/leap_cli/config/provider.rb b/lib/leap_cli/config/provider.rb
deleted file mode 100644
index 0d8bc1f3f59dfce473eba4af8d46fadba71518e1..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/provider.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Configuration class for provider.json
-#
-
-module LeapCli; module Config
-  class Provider < Object
-    attr_reader :environment
-    def set_env(e)
-      if e == 'default'
-        @environment = nil
-      else
-        @environment = e
-      end
-    end
-    def provider
-      self
-    end
-    def validate!
-      # nothing here yet :(
-    end
-  end
-end; end
diff --git a/lib/leap_cli/config/secrets.rb b/lib/leap_cli/config/secrets.rb
deleted file mode 100644
index ca851c743718fe03c29da130663de29931ae5ae6..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/secrets.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-# encoding: utf-8
-#
-# A class for the secrets.json file
-#
-
-module LeapCli; module Config
-
-  class Secrets < Object
-    attr_reader :node_list
-
-    def initialize(manager=nil)
-      super(manager)
-      @discovered_keys = {}
-    end
-
-    # we can't use fetch() or get(), since those already have special meanings
-    def retrieve(key, environment)
-      environment ||= 'default'
-      self.fetch(environment, {})[key.to_s]
-    end
-
-    def set(*args, &block)
-      if block_given?
-        set_with_block(*args, &block)
-      else
-        set_without_block(*args)
-      end
-    end
-
-    # searches over all keys matching the regexp, checking to see if the value
-    # has been already used by any of them.
-    def taken?(regexp, value, environment)
-      self.keys.grep(regexp).each do |key|
-        return true if self.retrieve(key, environment) == value
-      end
-      return false
-    end
-
-    def set_without_block(key, value, environment)
-      set_with_block(key, environment) {value}
-    end
-
-    def set_with_block(key, environment, &block)
-      environment ||= 'default'
-      key = key.to_s
-      @discovered_keys[environment] ||= {}
-      @discovered_keys[environment][key] = true
-      self[environment] ||= {}
-      self[environment][key] ||= yield
-    end
-
-    #
-    # if clean is true, then only secrets that have been discovered
-    # during this run will be exported.
-    #
-    # if environment is also pinned, then we will clean those secrets
-    # just for that environment.
-    #
-    # the clean argument should only be used when all nodes have
-    # been processed, otherwise secrets that are actually in use will
-    # get mistakenly removed.
-    #
-    def dump_json(clean=false)
-      pinned_env = LeapCli.leapfile.environment
-      if clean
-        self.each_key do |environment|
-          if pinned_env.nil? || pinned_env == environment
-            env = self[environment]
-            if env.nil?
-              raise StandardError.new("secrets.json file seems corrupted. No such environment '#{environment}'")
-            end
-            env.each_key do |key|
-              unless @discovered_keys[environment] && @discovered_keys[environment][key]
-                self[environment].delete(key)
-              end
-            end
-            if self[environment].empty?
-              self.delete(environment)
-            end
-          end
-        end
-      end
-      super()
-    end
-  end
-
-end; end
diff --git a/lib/leap_cli/config/sources.rb b/lib/leap_cli/config/sources.rb
deleted file mode 100644
index aee860de8c035883f749bf138865ac5e175ef653..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/sources.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# encoding: utf-8
-#
-# A class for the sources.json file
-#
-
-module LeapCli
-  module Config
-    class Sources < Object
-    end
-  end
-end
diff --git a/lib/leap_cli/config/tag.rb b/lib/leap_cli/config/tag.rb
deleted file mode 100644
index 6bd8d1e91366a03284d4f0817ecc8953961dfd3f..0000000000000000000000000000000000000000
--- a/lib/leap_cli/config/tag.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-#
-# A class for node services or node tags.
-#
-#
-
-module LeapCli; module Config
-
-  class Tag < Object
-    attr_reader :node_list
-
-    def initialize(environment=nil)
-      super(environment)
-      @node_list = Config::ObjectList.new
-    end
-
-    # don't copy the node list pointer when this object is dup'ed.
-    def initialize_copy(orig)
-      super
-      @node_list = Config::ObjectList.new
-    end
-
-  end
-
-end; end
diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb
index ac4023744e2ddfe7e96c21a357fcf5e8c05654ea..10af224644309e1405fbd99ed6bd9f21261d8768 100644
--- a/lib/leap_cli/leapfile.rb
+++ b/lib/leap_cli/leapfile.rb
@@ -3,6 +3,8 @@
 #
 # It is akin to a Gemfile, Rakefile, or Capfile (e.g. it is a ruby file that gets eval'ed)
 #
+# Additional configuration options are defined in platform's leapfile_extensions.rb
+#
 
 module LeapCli
   def self.leapfile
@@ -10,17 +12,11 @@ module LeapCli
   end
 
   class Leapfile
-    attr_accessor :platform_directory_path
-    attr_accessor :provider_directory_path
-    attr_accessor :custom_vagrant_vm_line
-    attr_accessor :leap_version
-    attr_accessor :log
-    attr_accessor :vagrant_network
-    attr_accessor :vagrant_basebox
-    attr_accessor :environment
+    attr_reader :platform_directory_path
+    attr_reader :provider_directory_path
+    attr_reader :environment
 
     def initialize
-      @vagrant_network = '10.5.5.0/24'
     end
 
     #
@@ -61,19 +57,33 @@ module LeapCli
         #
         # load the platform
         #
-        platform_file = "#{@platform_directory_path}/platform.rb"
-        unless File.exist?(platform_file)
+        platform_class = "#{@platform_directory_path}/lib/leap/platform"
+        platform_definition = "#{@platform_directory_path}/platform.rb"
+        unless File.exist?(platform_definition)
           Util.bail! "ERROR: The file `#{platform_file}` does not exist. Please check the value of `@platform_directory_path` in `Leapfile` or `~/.leaprc`."
         end
-        require "#{@platform_directory_path}/platform.rb"
-        if !Leap::Platform.compatible_with_cli?(LeapCli::VERSION) ||
-           !Leap::Platform.version_in_range?(LeapCli::COMPATIBLE_PLATFORM_VERSION)
-          Util.bail! "This leap command (v#{LeapCli::VERSION}) " +
-                     "is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}).\n   " +
-                     "You need either leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last} or " +
-                     "platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}"
+        require platform_class
+        require platform_definition
+        begin
+          Leap::Platform.validate!(LeapCli::VERSION, LeapCli::COMPATIBLE_PLATFORM_VERSION, self)
+        rescue StandardError => exc
+          Util.bail! exc.to_s
+        end
+        leapfile_extensions = "#{@platform_directory_path}/lib/leap_cli/leapfile_extensions.rb"
+        if File.exist?(leapfile_extensions)
+          require leapfile_extensions
+        end
+
+        #
+        # validate
+        #
+        instance_variables.each do |var|
+          var = var.to_s.sub('@', '')
+          if !self.respond_to?(var)
+            LeapCli.log :warning, "the variable `#{var}` is set in .leaprc or Leapfile, but it is not supported."
+          end
         end
-        @valid = true
+        @valid = validate
         return @valid
       end
     end
@@ -123,9 +133,8 @@ module LeapCli
 
     def read_settings(file)
       if File.exist? file
-        Util::log 2, :read, file
+        LeapCli.log 2, :read, file
         instance_eval(File.read(file), file)
-        validate(file)
       end
     end
 
@@ -140,11 +149,16 @@ module LeapCli
       return search_dir
     end
 
-    PRIVATE_IP_RANGES = /(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/
+    # to be overridden
+    def validate
+      return true
+    end
 
-    def validate(file)
-      Util::assert! vagrant_network =~ PRIVATE_IP_RANGES do
-        Util::log 0, :error, "in #{file}: vagrant_network is not a local private network"
+    def method_missing(method, *args)
+      if method =~ /=$/
+        self.instance_variable_set('@' + method.to_s.sub('=',''), args.first)
+      else
+        self.instance_variable_get('@' + method.to_s)
       end
     end
 
diff --git a/lib/leap_cli/lib_ext/capistrano_connections.rb b/lib/leap_cli/lib_ext/capistrano_connections.rb
deleted file mode 100644
index c46455f2da6e6124c92aa91c810143fb8a881aec..0000000000000000000000000000000000000000
--- a/lib/leap_cli/lib_ext/capistrano_connections.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module Capistrano
-  class Configuration
-    module Connections
-      def failed!(server)
-        @failure_callback.call(server) if @failure_callback
-        Thread.current[:failed_sessions] << server
-      end
-
-      def call_on_failure(&block)
-        @failure_callback = block
-      end
-    end
-  end
-end
-
-
diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb
index 3bd4f456fc7d2e02de11987796ccc98e33c3526a..5203c97e208ce4b4c815498b9b675f3defeb94c3 100644
--- a/lib/leap_cli/log.rb
+++ b/lib/leap_cli/log.rb
@@ -21,7 +21,7 @@ module LeapCli
 
     # thread safe logger
     def new_logger
-      LeapCli::LeapLogger.new
+      logger.dup
     end
 
     # deprecated
@@ -39,6 +39,12 @@ module LeapCli
     #
     FILE_TITLES = [:updated, :created, :removed, :missing, :nochange, :loading]
 
+    # TODO: use these
+    IMPORTANT = 0
+    INFO      = 1
+    DEBUG     = 2
+    TRACE     = 3
+
     attr_reader :log_output_stream, :log_file
     attr_accessor :indent_level, :log_level, :log_in_color
 
@@ -69,17 +75,23 @@ module LeapCli
     # * Integer: the log level (0, 1, 2)
     # * Symbol: the prefix title to colorize. may be one of
     #   [:error, :warning, :info, :updated, :created, :removed, :no_change, :missing]
-    # * Hash: a hash of options. so far, only :indent is supported.
+    # * Hash: a hash of options.
+    #     :wrap -- if true, appy intend to each line in message.
+    #     :color -- apply color to message or prefix
+    #     :style -- apply style to message or prefix
     #
     def log(*args)
       level   = args.grep(Integer).first || 1
       title   = args.grep(Symbol).first
       message = args.grep(String).first
       options = args.grep(Hash).first || {}
+      host    = options[:host]
+      if title
+        title = title.to_s
+      end
       unless message && @log_level >= level
         return
       end
-      clear_prefix, colored_prefix = calculate_prefix(title, options)
 
       #
       # transform absolute path names
@@ -89,23 +101,51 @@ module LeapCli
       end
 
       #
-      # log to the log file, always
+      # apply filters
+      # LogFilter will not be defined if no platform was loaded.
+      #
+      if defined?(LeapCli::LogFilter)
+        if title
+          title, filter_flags = LogFilter.apply_title_filters(title)
+        else
+          message, filter_flags = LogFilter.apply_message_filters(message)
+          return if message.nil?
+        end
+        options = options.merge(filter_flags)
+      end
+
+      #
+      # set line prefix
+      #
+      prefix = prefix_str(host, title)
+
+      #
+      # write to the log file, always
       #
-      log_raw(:log, nil, clear_prefix) { message }
+      log_raw(:log, nil, prefix) { message }
 
       #
       # log to stdout, maybe in color
       #
       if @log_in_color
-        prefix = colored_prefix
         if options[:wrap]
           message = message.split("\n")
         end
-      else
-        prefix = clear_prefix
+        if options[:color]
+          if host
+            host = colorize(host, options[:color], options[:style])
+          elsif title
+            title = colorize(title, options[:color], options[:style])
+          else
+            message = colorize(message, options[:color], options[:style])
+          end
+        elsif title
+          title = colorize(title, :cyan, :bold)
+        end
+        # new colorized prefix:
+        prefix = prefix_str(host, title)
       end
-      indent = options[:indent]
-      log_raw(:stdout, indent, prefix) { message }
+      log_raw(:stdout, options[:indent], prefix) { message }
 
       #
       # run block indented, if given
@@ -117,6 +157,10 @@ module LeapCli
       end
     end
 
+    def debug(*args)
+      self.log(3, *args)
+    end
+
     #
     # Add a raw log entry, without any modifications (other than indent).
     # Content to be logged is yielded by the block.
@@ -134,7 +178,7 @@ module LeapCli
           if messages.any?
             timestamp = Time.now.strftime("%b %d %H:%M:%S")
             messages.each do |message|
-              message = message.strip
+              message = message.rstrip
               next if message.empty?
               @log_output_stream.print("#{timestamp} #{prefix} #{message}\n")
             end
@@ -154,7 +198,7 @@ module LeapCli
           end
           indent_str += prefix if prefix
           messages.each do |message|
-            message = message.strip
+            message = message.rstrip
             next if message.empty?
             STDOUT.print("#{indent_str}#{message}\n")
           end
@@ -172,6 +216,14 @@ module LeapCli
 
     private
 
+    def prefix_str(host, title)
+      prefix = ""
+      prefix += "[" + host + "] " if host
+      prefix += title + " " if title
+      prefix += " " if !prefix.empty? && prefix !~ / $/
+      return prefix
+    end
+
     EFFECTS = {
       :reset         => 0,  :nothing         => 0,
       :bright        => 1,  :bold            => 1,
@@ -202,49 +254,6 @@ module LeapCli
       :default => 49,
     }
 
-    def calculate_prefix(title, options)
-      clear_prefix = colored_prefix = ""
-      if title
-        prefix_options = case title
-          when :error     then ['error', :red, :bold]
-          when :fatal_error then ['fatal error:', :red, :bold]
-          when :warning   then ['warning:', :yellow, :bold]
-          when :info      then ['info', :cyan, :bold]
-          when :note      then ['NOTE:', :cyan, :bold]
-          when :updated   then ['updated', :cyan, :bold]
-          when :updating  then ['updating', :cyan, :bold]
-          when :created   then ['created', :green, :bold]
-          when :removed   then ['removed', :red, :bold]
-          when :nochange  then ['no change', :magenta]
-          when :loading   then ['loading', :magenta]
-          when :missing   then ['missing', :yellow, :bold]
-          when :skipping  then ['skipping', :yellow, :bold]
-          when :run       then ['run', :cyan, :bold]
-          when :running   then ['running', :cyan, :bold]
-          when :failed    then ['FAILED', :red, :bold]
-          when :completed then ['completed', :green, :bold]
-          when :ran       then ['ran', :green, :bold]
-          when :bail      then ['bailing out', :red, :bold]
-          when :invalid   then ['invalid', :red, :bold]
-          else [title.to_s, :cyan, :bold]
-        end
-        if options[:host]
-          clear_prefix = "[%s] %s " % [options[:host], prefix_options[0]]
-          colored_prefix = "[%s] %s " % [colorize(options[:host], prefix_options[1], prefix_options[2]), prefix_options[0]]
-        else
-          clear_prefix = "%s " % prefix_options[0]
-          colored_prefix = "%s " % colorize(prefix_options[0], prefix_options[1], prefix_options[2])
-        end
-      elsif options[:host]
-        clear_prefix = "[%s] " % options[:host]
-        if options[:color]
-          colored_prefix = "[%s] " % colorize(options[:host], options[:color])
-        else
-          colored_prefix = clear_prefix
-        end
-      end
-      return [clear_prefix, colored_prefix]
-    end
-
   end
-end
\ No newline at end of file
+end
+
diff --git a/lib/leap_cli/logger.rb b/lib/leap_cli/logger.rb
deleted file mode 100644
index 058322c889325f2f9117305242cb9d1555c50aa6..0000000000000000000000000000000000000000
--- a/lib/leap_cli/logger.rb
+++ /dev/null
@@ -1,237 +0,0 @@
-#
-# A drop in replacement for Capistrano::Logger that integrates better with LEAP CLI.
-#
-
-require 'capistrano/logger'
-
-#
-# from Capistrano::Logger
-# =========================
-#
-# IMPORTANT = 0
-# INFO      = 1
-# DEBUG     = 2
-# TRACE     = 3
-# MAX_LEVEL = 3
-# COLORS = {
-#   :none     => "0",
-#   :black    => "30",
-#   :red      => "31",
-#   :green    => "32",
-#   :yellow   => "33",
-#   :blue     => "34",
-#   :magenta  => "35",
-#   :cyan     => "36",
-#   :white    => "37"
-# }
-# STYLES = {
-#   :bright     => 1,
-#   :dim        => 2,
-#   :underscore => 4,
-#   :blink      => 5,
-#   :reverse    => 7,
-#   :hidden     => 8
-# }
-#
-
-module LeapCli
-  class Logger < Capistrano::Logger
-
-    def initialize(options={})
-      @options = options
-      @level = options[:level] || 0
-      @message_buffer = nil
-    end
-
-    def log(level, message, line_prefix=nil, options={})
-      if message !~ /\n$/ && level <= 2 && line_prefix.is_a?(String)
-        # in some cases, when the message doesn't end with a return, we buffer it and
-        # wait until we encounter the return before we log the message out.
-        @message_buffer ||= ""
-        @message_buffer += message
-        return
-      elsif @message_buffer
-        message = @message_buffer + message
-        @message_buffer = nil
-      end
-
-      options[:level] ||= level
-      [:stdout, :log].each do |mode|
-        LeapCli::log_raw(mode) do
-          message_lines(mode, message, line_prefix, options)
-        end
-      end
-    end
-
-    private
-
-    def message_lines(mode, message, line_prefix, options)
-      formatted_message, formatted_prefix, message_options = apply_formatting(mode, message, line_prefix, options)
-      if message_options[:level] <= self.level && formatted_message && formatted_message.chars.any?
-        if formatted_prefix
-          formatted_message.lines.collect { |line|
-            "[#{formatted_prefix}] #{line.sub(/\s+$/, '')}"
-          }
-        else
-          formatted_message.lines.collect {|line| line.sub(/\s+$/, '')}
-        end
-      else
-        nil
-      end
-    end
-
-    ##
-    ## FORMATTING
-    ##
-
-    #
-    # options for formatters:
-    #
-    # :match       =>  regexp for matching a log line
-    # :color       => what color the line should be
-    # :style       => what style the line should be
-    # :priority    => what order the formatters are applied in. higher numbers first.
-    # :match_level => only apply filter at the specified log level
-    # :level       => make this line visible at this log level or higher
-    # :replace     => replace the matched text
-    # :exit        => force the exit code to be this (does not interrupt program, just
-    #                 ensures a specific exit code when the program eventually exits)
-    #
-    @formatters = [
-      # TRACE
-      { :match => /command finished/,          :color => :white,   :style => :dim, :match_level => 3, :priority => -10 },
-      { :match => /executing locally/,         :color => :yellow,  :match_level => 3, :priority => -20 },
-
-      # DEBUG
-      #{ :match => /executing .*/,             :color => :green,   :match_level => 2, :priority => -10, :timestamp => true },
-      #{ :match => /.*/,                        :color => :yellow,  :match_level => 2, :priority => -30 },
-      { :match => /^transaction:/,             :level => 3 },
-
-      # INFO
-      { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red,     :match_level => 1, :priority => -10 },
-      { :match => /Permission denied/,         :color => :red,     :match_level => 1, :priority => -20 },
-      { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 },
-
-      # IMPORTANT
-      { :match => /^(E|e)rr ::/,                   :color => :red,     :match_level => 0, :priority => -10, :exit => 1},
-      { :match => /^ERROR:/,                   :color => :red,                        :priority => -10, :exit => 1},
-      { :match => /.*/,                        :color => :blue,    :match_level => 0, :priority => -20 },
-
-      # CLEANUP
-      { :match => /\s+$/,                      :replace => '', :priority => 0},
-
-      # DEBIAN PACKAGES
-      { :match => /^(Hit|Ign) /,                :color => :green,   :priority => -20},
-      { :match => /^Err /,                      :color => :red,     :priority => -20},
-      { :match => /^W(ARNING)?: /,              :color => :yellow,  :priority => -20},
-      { :match => /^E: /,                       :color => :red,     :priority => -20},
-      { :match => /already the newest version/, :color => :green,   :priority => -20},
-      { :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10},
-
-      # PUPPET
-      { :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10},
-      { :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/,          :level => 2, :color => :yellow, :priority => -10},
-      { :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10},
-      { :match => /^(W|w)arning: Scope.*$/,           :level => 2, :color => :yellow, :priority => -10},
-      { :match => /^(N|n)otice:/,                     :level => 1, :color => :cyan,   :priority => -20},
-      { :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15},
-      { :match => /^(W|w)arning:/,                    :level => 0, :color => :yellow, :priority => -20},
-      { :match => /^Duplicate declaration:/,          :level => 0, :color => :red,    :priority => -20},
-      { :match => /Finished catalog run/,             :level => 0, :color => :green,  :priority => -10},
-      { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green,  :priority => -10},
-      { :match => /^APPLY COMPLETE \(no changes\)/,   :level => 0, :color => :green,  :priority => -10},
-
-      # PUPPET FATAL ERRORS
-      { :match => /^(E|e)rr(or|):/,                :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Wrapped exception:/,           :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Failed to parse template/,     :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Execution of.*returned/,     :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Parameter matches failed:/,    :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Syntax error/,                 :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Cannot reassign variable/,     :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^Could not find template/,      :level => 0, :color => :red, :priority => -1, :exit => 1},
-      { :match => /^APPLY COMPLETE.*fail/,         :level => 0, :color => :red, :priority => -1, :exit => 1},
-
-      # TESTS
-      { :match => /^PASS: /,                :color => :green,   :priority => -20},
-      { :match => /^(FAIL|ERROR): /,        :color => :red,     :priority => -20},
-      { :match => /^(SKIP|WARN): /,         :color => :yellow,  :priority => -20},
-      { :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, :color => :blue, :priority => -20},
-
-      # LOG SUPPRESSION
-      { :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10},
-      { :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10}
-    ]
-
-    def self.sorted_formatters
-      # Sort matchers in reverse order so we can break if we found a match.
-      @sorted_formatters ||= @formatters.sort_by { |i| -(i[:priority] || i[:prio] || 0) }
-    end
-
-    @prefix_formatters = [
-      { :match => /(err|out) :: /,             :replace => '', :priority => 0},
-      { :match => /\s+$/,                      :replace => '', :priority => 0}
-    ]
-    def self.prefix_formatters; @prefix_formatters; end
-
-    def apply_formatting(mode, message, line_prefix = nil, options={})
-      message = message.dup
-      options = options.dup
-      if !line_prefix.nil?
-        if !line_prefix.is_a?(String)
-          line_prefix = line_prefix.to_s.dup
-        else
-          line_prefix = line_prefix.dup
-        end
-      end
-      color = options[:color] || :none
-      style = options[:style]
-
-      if line_prefix
-        self.class.prefix_formatters.each do |formatter|
-          if line_prefix =~ formatter[:match] && formatter[:replace]
-            line_prefix.gsub!(formatter[:match], formatter[:replace])
-          end
-        end
-      end
-
-      self.class.sorted_formatters.each do |formatter|
-        if (formatter[:match_level] == level || formatter[:match_level].nil?)
-          if message =~ formatter[:match]
-            options[:level] = formatter[:level] if formatter[:level]
-            color = formatter[:color] if formatter[:color]
-            style = formatter[:style] || formatter[:attribute] # (support original cap colors)
-
-            message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace]
-            message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil?
-            message.replace(message + formatter[:append])  unless formatter[:append].nil?
-            message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp]
-
-            if formatter[:exit]
-              LeapCli::Util.exit_status(formatter[:exit])
-            end
-
-            # stop formatting, unless formatter was just for string replacement
-            break unless formatter[:replace]
-          end
-        end
-      end
-
-      if color == :hide
-        return nil
-      elsif mode == :log || (color == :none && style.nil?) || !LeapCli.logger.log_in_color
-        return [message, line_prefix, options]
-      else
-        term_color = COLORS[color]
-        term_style = STYLES[style]
-        if line_prefix.nil?
-          message.replace format(message, term_color, term_style)
-        else
-          line_prefix.replace format(line_prefix, term_color, term_style).strip # format() appends a \n
-        end
-        return [message, line_prefix, options]
-      end
-    end
-
-  end
-end
diff --git a/lib/leap_cli/remote/leap_plugin.rb b/lib/leap_cli/remote/leap_plugin.rb
deleted file mode 100644
index e6305ae39329a7a4b79fcd0cfa68462bd4d8923a..0000000000000000000000000000000000000000
--- a/lib/leap_cli/remote/leap_plugin.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-#
-# these methods are made available in capistrano tasks as 'leap.method_name'
-# (see RemoteCommand::new_capistrano)
-#
-
-module LeapCli; module Remote; module LeapPlugin
-
-  def required_packages
-    "puppet rsync lsb-release locales"
-  end
-
-  def log(*args, &block)
-    LeapCli.logger.log(*args, &block)
-  end
-
-  #
-  # creates directories that are owned by root and 700 permissions
-  #
-  def mkdirs(*dirs)
-    raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any?
-    run dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join
-  end
-
-  #
-  # echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise.
-  #
-  def assert_initialized
-    begin
-      test_initialized_file = "test -f #{Leap::Platform.init_path}"
-      check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{required_packages} 2>&1 | grep -q -E '(deinstall|no packages)'"
-      run "#{test_initialized_file} && #{check_required_packages} && echo ok"
-    rescue Capistrano::CommandError => exc
-      LeapCli::Util.bail! do
-        exc.hosts.each do |host|
-          node = host.to_s.split('.').first
-          LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{node}'", :host => host
-        end
-      end
-    end
-  end
-
-  #
-  # bails out the deploy if the file /etc/leap/no-deploy exists.
-  # This kind of sucks, because it would be better to skip over nodes that have no-deploy set instead
-  # halting the entire deploy. As far as I know, with capistrano, there is no way to close one of the
-  # ssh connections in the pool and make sure it gets no further commands.
-  #
-  def check_for_no_deploy
-    begin
-      run "test ! -f /etc/leap/no-deploy"
-    rescue Capistrano::CommandError => exc
-      LeapCli::Util.bail! do
-        exc.hosts.each do |host|
-          LeapCli::Util.log "Can't continue because file /etc/leap/no-deploy exists", :host => host
-        end
-      end
-    end
-  end
-
-  #
-  # dumps debugging information
-  # #
-  def debug
-    run "#{Leap::Platform.leap_dir}/bin/debug.sh"
-  end
-
-  #
-  # dumps the recent deploy history to the console
-  #
-  def history(lines)
-    command = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')"
-    run command
-  end
-
-  #
-  # This is a hairy ugly hack, exactly the kind of stuff that makes ruby
-  # dangerous and too much fun for its own good.
-  #
-  # In most places, we run remote ssh without a current 'task'. This works fine,
-  # except that in a few places, the behavior of capistrano ssh is controlled by
-  # the options of the current task.
-  #
-  # We don't want to create an actual current task, because tasks are no fun
-  # and can't take arguments or return values. So, when we need to configure
-  # things that can only be configured in a task, we use this handy hack to
-  # fake the current task.
-  #
-  # This is NOT thread safe, but could be made to be so with some extra work.
-  #
-  def with_task(name)
-    task = @config.tasks[name]
-    @config.class.send(:alias_method, :original_current_task, :current_task)
-    @config.class.send(:define_method, :current_task, Proc.new(){ task })
-    begin
-      yield
-    ensure
-      @config.class.send(:remove_method, :current_task)
-      @config.class.send(:alias_method, :current_task, :original_current_task)
-    end
-  end
-
-  #
-  # similar to run(cmd, &block), but with:
-  #
-  # * exit codes
-  # * stdout and stderr are combined
-  #
-  def stream(cmd, &block)
-    command = '%s 2>&1; echo "exitcode=$?"' % cmd
-    run(command) do |channel, stream, data|
-      exitcode = nil
-      if data =~ /exitcode=(\d+)\n/
-        exitcode = $1.to_i
-        data.sub!(/exitcode=(\d+)\n/,'')
-      end
-      yield({:host => channel[:host], :data => data, :exitcode => exitcode})
-    end
-  end
-
-  #
-  # like stream, but capture all the output before returning
-  #
-  def capture(cmd, &block)
-    command = '%s 2>&1; echo "exitcode=$?" 2>&1;' % cmd
-    host_data = {}
-    run(command) do |channel, stream, data|
-      host_data[channel[:host]] ||= ""
-      if data =~ /exitcode=(\d+)\n/
-        exitcode = $1.to_i
-        data.sub!(/exitcode=(\d+)\n/,'')
-        host_data[channel[:host]] += data
-        yield({:host => channel[:host], :data => host_data[channel[:host]], :exitcode => exitcode})
-      else
-        host_data[channel[:host]] += data
-      end
-    end
-  end
-
-  #
-  # Run a command, with a nice status report and progress indicator.
-  # Only successful results are returned, errors are printed.
-  #
-  # For each successful run on each host, block is yielded with a hash like so:
-  #
-  # {:host => 'bluejay', :exitcode => 0, :data => 'shell output'}
-  #
-  def run_with_progress(cmd, &block)
-    ssh_failures = []
-    exitcode_failures = []
-    succeeded = []
-    task = LeapCli.logger.log_level > 1 ? :standard_task : :skip_errors_task
-    with_task(task) do
-      log :querying, 'facts' do
-        progress "   "
-        call_on_failure do |host|
-          ssh_failures << host
-          progress 'F'
-        end
-        capture(cmd) do |response|
-          if response[:exitcode] == 0
-            progress '.'
-            yield response
-          else
-            exitcode_failures << response
-            progress 'F'
-          end
-        end
-      end
-    end
-    puts "done"
-    if ssh_failures.any?
-      log :failed, 'to connect to nodes: ' + ssh_failures.join(' ')
-    end
-    if exitcode_failures.any?
-      log :failed, 'to run successfully:' do
-        exitcode_failures.each do |response|
-          log "[%s] exit %s - %s" % [response[:host], response[:exitcode], response[:data].strip]
-        end
-      end
-    end
-  rescue Capistrano::RemoteError => err
-    log :error, err.to_s
-  end
-
-  private
-
-  def progress(str='.')
-    print str
-    STDOUT.flush
-  end
-
-end; end; end
diff --git a/lib/leap_cli/remote/puppet_plugin.rb b/lib/leap_cli/remote/puppet_plugin.rb
deleted file mode 100644
index 5a6e9081c5e0d02f5cdb38b87b0f4a59ef47289e..0000000000000000000000000000000000000000
--- a/lib/leap_cli/remote/puppet_plugin.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# these methods are made available in capistrano tasks as 'puppet.method_name'
-# (see RemoteCommand::new_capistrano)
-#
-
-module LeapCli; module Remote; module PuppetPlugin
-
-  def apply(options)
-    run "#{Leap::Platform.leap_dir}/bin/puppet_command set_hostname apply #{flagize(options)}"
-  end
-
-  private
-
-  def flagize(hsh)
-    hsh.inject([]) {|str, item|
-      if item[1] === false
-        str
-      elsif item[1] === true
-        str << "--" + item[0].to_s
-      else
-        str << "--" + item[0].to_s + " " + item[1].inspect
-      end
-    }.join(' ')
-  end
-
-end; end; end
diff --git a/lib/leap_cli/remote/rsync_plugin.rb b/lib/leap_cli/remote/rsync_plugin.rb
deleted file mode 100644
index a6708f4d8494fd8736aa1d9b0f25ec93bfe53a2a..0000000000000000000000000000000000000000
--- a/lib/leap_cli/remote/rsync_plugin.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# these methods are made available in capistrano tasks as 'rsync.method_name'
-# (see RemoteCommand::new_capistrano)
-#
-
-autoload :RsyncCommand, 'rsync_command'
-
-module LeapCli; module Remote; module RsyncPlugin
-
-  #
-  # takes a block, yielded a server, that should return a hash with various rsync options.
-  # supported options include:
-  #
-  #   {:source => '', :dest => '', :flags => '', :includes => [], :excludes => []}
-  #
-  def update
-    rsync = RsyncCommand.new(:logger => logger)
-    rsync.asynchronously(find_servers) do |server|
-      options = yield server
-      next unless options
-      remote_user = server.user || fetch(:user, ENV['USER'])
-      src = options[:source]
-      dest = {:user => remote_user, :host => server.host, :path => options[:dest]}
-      options[:ssh] = ssh_options.merge(server.options[:ssh_options]||{})
-      options[:chdir] ||= Path.provider
-      rsync.exec(src, dest, options)
-    end
-    if rsync.failed?
-      LeapCli::Util.bail! do
-        LeapCli::Util.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}"
-      end
-    end
-  end
-
-end; end; end
diff --git a/lib/leap_cli/remote/tasks.rb b/lib/leap_cli/remote/tasks.rb
deleted file mode 100644
index d08d19a25b99198662f51bbef346b3216f7af7bf..0000000000000000000000000000000000000000
--- a/lib/leap_cli/remote/tasks.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# This file is evaluated just the same as a typical capistrano "deploy.rb"
-# For DSL manual, see https://github.com/capistrano/capistrano/wiki
-#
-
-MAX_HOSTS = 10
-
-task :install_authorized_keys, :max_hosts => MAX_HOSTS do
-  leap.log :updating, "authorized_keys" do
-    leap.mkdirs '/root/.ssh'
-    upload LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => '600'
-  end
-end
-
-#
-# for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy
-# will overwrite authorized_keys.
-#
-# why force the insecure vagrant key?
-#   if we don't do this, then first time initialization might fail if the user has many keys
-#   (ssh will bomb out before it gets to the vagrant key).
-#   and it really doesn't make sense to ask users to pin the insecure vagrant key in their
-#   .ssh/config files.
-#
-task :install_insecure_vagrant_key, :max_hosts => MAX_HOSTS do
-  leap.log :installing, "insecure vagrant key" do
-    leap.mkdirs '/root/.ssh'
-    upload LeapCli::Path.vagrant_ssh_pub_key_file, '/root/.ssh/authorized_keys2', :mode => '600'
-  end
-end
-
-task :install_prerequisites, :max_hosts => MAX_HOSTS do
-  bin_dir = File.join(Leap::Platform.leap_dir, 'bin')
-  node_init_path = File.join(bin_dir, 'node_init')
-
-  leap.log :running, "node_init script" do
-    leap.mkdirs bin_dir
-    upload LeapCli::Path.node_init_script, node_init_path, :mode => '500'
-    run node_init_path
-  end
-end
-
-#
-# just dummies, used to capture task options
-#
-
-task :skip_errors_task, :on_error => :continue, :max_hosts => MAX_HOSTS do
-end
-
-task :standard_task, :max_hosts => MAX_HOSTS do
-end
diff --git a/lib/leap_cli/ssh_key.rb b/lib/leap_cli/ssh_key.rb
deleted file mode 100644
index 257055735c3f6298e3a7152b0709db9ef87dc091..0000000000000000000000000000000000000000
--- a/lib/leap_cli/ssh_key.rb
+++ /dev/null
@@ -1,195 +0,0 @@
-#
-# A wrapper around OpenSSL::PKey::RSA instances to provide a better api for dealing with SSH keys.
-#
-# cipher 'ssh-ed25519' not supported yet because we are waiting for support in Net::SSH
-#
-
-require 'net/ssh'
-require 'forwardable'
-
-module LeapCli
-  class SshKey
-    extend Forwardable
-
-    attr_accessor :filename
-    attr_accessor :comment
-
-    # supported ssh key types, in order of preference
-    SUPPORTED_TYPES = ['ssh-rsa', 'ecdsa-sha2-nistp256']
-    SUPPORTED_TYPES_RE = /(#{SUPPORTED_TYPES.join('|')})/
-
-    ##
-    ## CLASS METHODS
-    ##
-
-    def self.load(arg1, arg2=nil)
-      key = nil
-      if arg1.is_a? OpenSSL::PKey::RSA
-        key = SshKey.new arg1
-      elsif arg1.is_a? String
-        if arg1 =~ /^ssh-/
-          type, data = arg1.split(' ')
-          key = SshKey.new load_from_data(data, type)
-        elsif File.exist? arg1
-          key = SshKey.new load_from_file(arg1)
-          key.filename = arg1
-        else
-          key = SshKey.new load_from_data(arg1, arg2)
-        end
-      end
-      return key
-    rescue StandardError
-    end
-
-    def self.load_from_file(filename)
-      public_key = nil
-      private_key = nil
-      begin
-        public_key = Net::SSH::KeyFactory.load_public_key(filename)
-      rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
-        begin
-          private_key = Net::SSH::KeyFactory.load_private_key(filename)
-        rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
-        end
-      end
-      public_key || private_key
-    end
-
-    def self.load_from_data(data, type='ssh-rsa')
-      public_key = nil
-      private_key = nil
-      begin
-        public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}")
-      rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
-        begin
-          private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}")
-        rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
-        end
-      end
-      public_key || private_key
-    end
-
-    #
-    # Picks one key out of an array of keys that we think is the "best",
-    # based on the order of preference in SUPPORTED_TYPES
-    #
-    # Currently, this does not take bitsize into account.
-    #
-    def self.pick_best_key(keys)
-      keys.select {|k|
-        SUPPORTED_TYPES.include?(k.type)
-      }.sort {|a,b|
-        SUPPORTED_TYPES.index(a.type) <=> SUPPORTED_TYPES.index(b.type)
-      }.first
-    end
-
-    #
-    # takes a string with one or more ssh keys, one key per line,
-    # and returns an array of SshKey objects.
-    #
-    # the lines should be in one of these formats:
-    #
-    # 1. <hostname> <key-type> <key>
-    # 2. <key-type> <key>
-    #
-    def self.parse_keys(string)
-      keys = []
-      lines = string.split("\n").grep(/^[^#]/)
-      lines.each do |line|
-        if line =~ / #{SshKey::SUPPORTED_TYPES_RE} /
-          # <hostname> <key-type> <key>
-          keys << line.split(' ')[1..2]
-        elsif line =~ /^#{SshKey::SUPPORTED_TYPES_RE} /
-          # <key-type> <key>
-          keys << line.split(' ')
-        end
-      end
-      return keys.map{|k| SshKey.load(k[1], k[0])}
-    end
-
-    #
-    # takes a string with one or more ssh keys, one key per line,
-    # and returns a string that specified the ssh key algorithms
-    # that are supported by the keys, in order of preference.
-    #
-    # eg: ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519
-    #
-    def self.supported_host_key_algorithms(string)
-      if string
-        self.parse_keys(string).map {|key|
-          key.type
-        }.join(',')
-      else
-        ""
-      end
-    end
-
-    ##
-    ## INSTANCE METHODS
-    ##
-
-    public
-
-    def initialize(rsa_key)
-      @key = rsa_key
-    end
-
-    def_delegator :@key, :fingerprint, :fingerprint
-    def_delegator :@key, :public?, :public?
-    def_delegator :@key, :private?, :private?
-    def_delegator :@key, :ssh_type, :type
-    def_delegator :@key, :public_encrypt, :public_encrypt
-    def_delegator :@key, :public_decrypt, :public_decrypt
-    def_delegator :@key, :private_encrypt, :private_encrypt
-    def_delegator :@key, :private_decrypt, :private_decrypt
-    def_delegator :@key, :params, :params
-    def_delegator :@key, :to_text, :to_text
-
-    def public_key
-      SshKey.new(@key.public_key)
-    end
-
-    def private_key
-      SshKey.new(@key.private_key)
-    end
-
-    #
-    # not sure if this will always work, but is seems to for now.
-    #
-    def bits
-      Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8
-    end
-
-    def summary
-      if self.filename
-        "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, File.basename(self.filename)]
-      else
-        "%s %s %s" % [self.type, self.bits, self.fingerprint]
-      end
-    end
-
-    def to_s
-      self.type + " " + self.key
-    end
-
-    def key
-      [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "")
-    end
-
-    def ==(other_key)
-      return false if other_key.nil?
-      return false if self.class != other_key.class
-      return self.to_text == other_key.to_text
-    end
-
-    def in_known_hosts?(*identifiers)
-      identifiers.each do |identifier|
-        Net::SSH::KnownHosts.search_for(identifier).each do |key|
-          return true if self == key
-        end
-      end
-      return false
-    end
-
-  end
-end
diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb
index 248a59c5dc765110c61e1d73f2b458f49c90d4e1..64b5c6351e73d1f4ac6aeefc29c120617440686b 100644
--- a/lib/leap_cli/util.rb
+++ b/lib/leap_cli/util.rb
@@ -10,6 +10,10 @@ module LeapCli
 
     @@exit_status = nil
 
+    def log(*args, &block)
+      LeapCli.log(*args, &block)
+    end
+
     ##
     ## QUITTING
     ##
@@ -44,7 +48,7 @@ module LeapCli
         log 0, *message
       end
       log 0, :bail, ""
-      raise SystemExit.new(@exit_status || 1)
+      raise SystemExit.new(exit_status || 1)
     end
 
     #
@@ -52,7 +56,7 @@ module LeapCli
     #
     def quit!(message='')
       puts(message)
-      raise SystemExit.new(@exit_status || 0)
+      raise SystemExit.new(exit_status || 0)
     end
 
     #
diff --git a/lib/leap_cli/util/remote_command.rb b/lib/leap_cli/util/remote_command.rb
deleted file mode 100644
index c2f1ace833834a3f923f9ee404ff20f98e76eb91..0000000000000000000000000000000000000000
--- a/lib/leap_cli/util/remote_command.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-module LeapCli; module Util; module RemoteCommand
-  extend self
-
-  #
-  # FYI
-  #  Capistrano::Logger::IMPORTANT = 0
-  #  Capistrano::Logger::INFO      = 1
-  #  Capistrano::Logger::DEBUG     = 2
-  #  Capistrano::Logger::TRACE     = 3
-  #
-  def ssh_connect(nodes, options={}, &block)
-    options ||= {}
-    node_list = parse_node_list(nodes)
-
-    cap = new_capistrano
-    cap.logger = LeapCli::Logger.new(:level => [LeapCli.logger.log_level,3].min)
-    user = options[:user] || 'root'
-    cap.set :user, user
-    cap.set :ssh_options, ssh_options # ssh options common to all nodes
-    cap.set :use_sudo, false          # we may want to change this in the future
-
-    # Allow password authentication when we are bootstraping a single node
-    # (and key authentication fails).
-    if options[:bootstrap] && node_list.size == 1
-      hostname = node_list.values.first.name
-      if options[:echo]
-        cap.set(:password) { ask "Root SSH password for #{user}@#{hostname}> " }
-      else
-        cap.set(:password) { Capistrano::CLI.password_prompt " * Typed password will be hidden (use --echo to make it visible)\nRoot SSH password for #{user}@#{hostname}> " }
-      end
-    end
-
-    node_list.each do |name, node|
-      cap.server node.domain.full, :dummy_arg, node_options(node, options[:ssh_options])
-    end
-
-    yield cap
-  rescue Capistrano::ConnectionError => exc
-    # not sure if this will work if english is not the locale??
-    if exc.message =~ /Too many authentication failures/
-      at_exit {ssh_config_help_message}
-    end
-    raise exc
-  end
-
-  private
-
-  #
-  # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
-  #
-  # Capistrano has some very evil behavior in it's ssh.rb:
-  #
-  #   ssh_options = Net::SSH.configuration_for(
-  #     server.host, ssh_options.fetch(:config, true)
-  #   ).merge(ssh_options)
-  #   # Once we've loaded the config, we don't need Net::SSH to do it again.
-  #   ssh_options[:config] = false
-  #
-  # Net:SSH is supposed to call Net::SSH.configuration_for, but Capistrano is doing it
-  # in advance and then disabling loading of configs.
-  #
-  # The result of this is the following: if you have IdentityFile in your ~/.ssh/config
-  # file, then the above code will transform the ssh_options by reading ~/.ssh/config
-  # and adding the keys specified via IdentityFile to ssh_options...
-  # AND IT WILL SET :keys_only TO TRUE.
-  #
-  # The problem is that :keys_only will disable Net:SSH's ability to use ssh-agent.
-  # With :keys_only set to true, it will not consult the ssh-agent at all.
-  #
-  # So nice of capistrano to parse ~/.ssh/config for us, but then add flags to the
-  # ssh_options that prevent's these options from being useful.
-  #
-  # The current hackaround is to force :keys_only to be false. This allows the config
-  # to be read and also allows ssh-agent to still be used.
-  #
-  def ssh_options
-    {
-      :keys_only => false, # Don't you dare change this.
-      :global_known_hosts_file => path(:known_hosts),
-      :user_known_hosts_file => '/dev/null',
-      :paranoid => true,
-      :verbose => net_ssh_log_level
-    }
-  end
-
-  def net_ssh_log_level
-    if DEBUG
-      case LeapCli.logger.log_level
-        when 1 then 3
-        when 2 then 2
-        when 3 then 1
-        else 0
-      end
-    else
-      nil
-    end
-  end
-
-  #
-  # For notes on advanced ways to set server-specific options, see
-  # http://railsware.com/blog/2011/11/02/advanced-server-definitions-in-capistrano/
-  #
-  # if, in the future, we want to do per-node password options, it would be done like so:
-  #
-  #  password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"}
-  #  return {:password => password_proc}
-  #
-  def node_options(node, ssh_options_override=nil)
-    {
-      :ssh_options => {
-        # :host_key_alias => node.name, << incompatible with ports in known_hosts
-        :host_name => node.ip_address,
-        :port => node.ssh.port
-      }.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override||{})
-    }
-  end
-
-  def new_capistrano
-    # load once the library files
-    @capistrano_enabled ||= begin
-      require 'capistrano'
-      require 'capistrano/cli'
-      require 'leap_cli/lib_ext/capistrano_connections'
-      require 'leap_cli/remote/leap_plugin'
-      require 'leap_cli/remote/puppet_plugin'
-      require 'leap_cli/remote/rsync_plugin'
-      Capistrano.plugin :leap, LeapCli::Remote::LeapPlugin
-      Capistrano.plugin :puppet, LeapCli::Remote::PuppetPlugin
-      Capistrano.plugin :rsync, LeapCli::Remote::RsyncPlugin
-      true
-    end
-
-    # create capistrano instance
-    cap = Capistrano::Configuration.new
-
-    # add tasks to capistrano instance
-    cap.load File.dirname(__FILE__) + '/../remote/tasks.rb'
-
-    return cap
-  end
-
-  def contingent_ssh_options_for_node(node)
-    opts = {}
-    if node.vagrant?
-      opts[:keys] = [vagrant_ssh_key_file]
-      opts[:keys_only] = true # only use the keys specified above, and ignore whatever keys the ssh-agent is aware of.
-      opts[:paranoid] = false # we skip host checking for vagrant nodes, because fingerprint is different for everyone.
-      if LeapCli.logger.log_level <= 1
-        opts[:verbose] = :error # suppress all the warnings about adding host keys to known_hosts, since it is not actually doing that.
-      end
-    end
-    if !node.supported_ssh_host_key_algorithms.empty?
-      opts[:host_key] = node.supported_ssh_host_key_algorithms
-    end
-    return opts
-  end
-
-end; end; end
\ No newline at end of file
diff --git a/lib/leap_cli/util/secret.rb b/lib/leap_cli/util/secret.rb
deleted file mode 100644
index 749b95957c111cb22102e59ea393af68964d4a62..0000000000000000000000000000000000000000
--- a/lib/leap_cli/util/secret.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# encoding: utf-8
-#
-# A simple secret generator
-#
-# Uses OpenSSL random number generator instead of Ruby's rand function
-#
-autoload :OpenSSL, 'openssl'
-
-module LeapCli; module Util
-  class Secret
-    CHARS = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a) - "i1loO06G".split(//u)
-    HEX = (0..9).to_a + ('a'..'f').to_a
-
-    #
-    # generate a secret with with no ambiguous characters.
-    #
-    # +length+ is in chars
-    #
-    # Only alphanumerics are allowed, in order to make these passwords work
-    # for REST url calls and to allow you to easily copy and paste them.
-    #
-    def self.generate(length = 16)
-      seed
-      OpenSSL::Random.random_bytes(length).bytes.to_a.collect { |byte|
-        CHARS[ byte % CHARS.length ]
-      }.join
-    end
-
-    #
-    # generates a hex secret, instead of an alphanumeric on.
-    #
-    # length is in bits
-    #
-    def self.generate_hex(length = 128)
-      seed
-      OpenSSL::Random.random_bytes(length/4).bytes.to_a.collect { |byte|
-        HEX[ byte % HEX.length ]
-      }.join
-    end
-
-    private
-
-    def self.seed
-      @pid ||= 0
-      pid = $$
-      if @pid != pid
-        now = Time.now
-        ary = [now.to_i, now.nsec, @pid, pid]
-        OpenSSL::Random.seed(ary.to_s)
-        @pid = pid
-      end
-    end
-
-  end
-end; end
diff --git a/lib/leap_cli/util/x509.rb b/lib/leap_cli/util/x509.rb
deleted file mode 100644
index 787fdfac7c410af904cc45964989a1e17bacbe80..0000000000000000000000000000000000000000
--- a/lib/leap_cli/util/x509.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-autoload :OpenSSL, 'openssl'
-autoload :CertificateAuthority, 'certificate_authority'
-
-require 'digest'
-require 'digest/md5'
-require 'digest/sha1'
-
-module LeapCli; module X509
-  extend self
-
-  #
-  # returns a fingerprint of a x509 certificate
-  #
-  def fingerprint(digest, cert_file)
-    if cert_file.is_a? String
-      cert = OpenSSL::X509::Certificate.new(Util.read_file!(cert_file))
-    elsif cert_file.is_a? OpenSSL::X509::Certificate
-      cert = cert_file
-    elsif cert_file.is_a? CertificateAuthority::Certificate
-      cert = cert_file.openssl_body
-    end
-    digester = case digest
-      when "MD5" then Digest::MD5.new
-      when "SHA1" then Digest::SHA1.new
-      when "SHA256" then Digest::SHA256.new
-      when "SHA384" then Digest::SHA384.new
-      when "SHA512" then Digest::SHA512.new
-    end
-    digester.hexdigest(cert.to_der)
-  end
-
-
-end; end
diff --git a/vendor/rsync_command/README.md b/vendor/rsync_command/README.md
index 4b53a5c1481db26dbde2a39a2ca3cf48e0df8bf8..5e44845b17d6fa51434034fe0fb9e10fae216982 100644
--- a/vendor/rsync_command/README.md
+++ b/vendor/rsync_command/README.md
@@ -11,13 +11,15 @@ Installation
 Usage
 ------------------------------------
 
-    rsync   = RsyncCommand.new(:logger => logger, :ssh => {:auth_methods => 'publickey'}, :flags => '-a')
-    source  = '/source/path'
+    rsync   = RsyncCommand.new(:ssh => {:auth_methods => 'publickey'}, :flags => '-a')
     servers = ['red', 'green', 'blue']
 
-    rsync.asynchronously(servers) do |server|
-      dest = {:user => 'root', :host => server, :path => '/dest/path'}
-      rsync.exec(source, dest)
+    rsync.asynchronously(servers) do |sync, server|
+      sync.user = 'root'
+      sync.host = server
+      sync.source = '/from'
+      sync.dest = '/to'
+      sync.exec
     end
 
     if rsync.failed?
diff --git a/vendor/rsync_command/lib/rsync_command.rb b/vendor/rsync_command/lib/rsync_command.rb
index 39e5945bf224d06b648d53d5e986ee865de33e15..bdcafe0a8d92299272a407d76fedd23f9b8407d0 100644
--- a/vendor/rsync_command/lib/rsync_command.rb
+++ b/vendor/rsync_command/lib/rsync_command.rb
@@ -4,6 +4,44 @@ require "rsync_command/thread_pool"
 
 require 'monitor'
 
+class RsyncRunner
+  attr_accessor :logger
+  attr_accessor :source, :dest, :flags, :includes, :excludes
+  attr_accessor :user, :host
+  attr_accessor :chdir, :ssh
+  def initialize(rsync_command)
+    @logger = nil
+    @source = ""
+    @dest   = ""
+    @flags  = ""
+    @includes = []
+    @excludes = []
+    @rsync_command = rsync_command
+  end
+  def log(*args)
+    @logger.log(*args)
+  end
+  def valid?
+    !@source.empty? || !@dest.empty?
+  end
+  def to_hash
+    fields = [:flags, :includes, :excludes, :logger, :ssh, :chdir]
+    fields.inject({}){|hsh, i|
+      hsh[i] = self.send(i); hsh
+    }
+  end
+  def exec
+    return unless valid?
+    dest = {
+      :user => self.user,
+      :host => self.host,
+      :path => self.dest
+    }
+    src = self.source
+    @rsync_command.exec_rsync(src, dest, self.to_hash)
+  end
+end
+
 class RsyncCommand
   attr_accessor :failures, :logger
 
@@ -21,15 +59,23 @@ class RsyncCommand
   def asynchronously(array, &block)
     pool = ThreadPool.new
     array.each do |item|
-      pool.schedule(item, &block)
+      pool.schedule(RsyncRunner.new(self), item, &block)
     end
     pool.shutdown
   end
 
+  #
+  # returns true if last exec returned a failure
+  #
+  def failed?
+    @failures && @failures.any?
+  end
+
   #
   # runs rsync, recording failures
   #
-  def exec(src, dest, options={})
+  def exec_rsync(src, dest, options={})
+    logger = options[:logger] || @logger
     @failures.synchronize do
       @failures.clear
     end
@@ -37,7 +83,7 @@ class RsyncCommand
     if options[:chdir]
       rsync_cmd = "cd '#{options[:chdir]}'; #{rsync_cmd}"
     end
-    @logger.debug rsync_cmd if @logger
+    logger.debug rsync_cmd if logger
     ok = system(rsync_cmd)
     unless ok
       @failures.synchronize do
@@ -46,13 +92,6 @@ class RsyncCommand
     end
   end
 
-  #
-  # returns true if last exec returned a failure
-  #
-  def failed?
-    @failures && @failures.any?
-  end
-
   #
   # build rsync command
   #
@@ -70,8 +109,6 @@ class RsyncCommand
     "rsync #{flags.compact.join(' ')} #{src} #{dest}"
   end
 
-  private
-
   #
   # Creates an rsync location if the +address+ is a hash with keys :user, :host, and :path
   # (each component is optional). If +address+ is a string, we just pass it through.