docker-sync 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/docker-sync.rb +4 -0
- data/lib/docker-sync/config/config_locator.rb +80 -0
- data/lib/docker-sync/config/config_serializer.rb +56 -0
- data/lib/docker-sync/config/global_config.rb +55 -0
- data/lib/docker-sync/config/project_config.rb +123 -0
- data/lib/docker-sync/preconditions/preconditions_linux.rb +24 -0
- data/lib/docker-sync/preconditions/preconditions_osx.rb +148 -0
- data/lib/docker-sync/preconditions/strategy.rb +57 -0
- data/lib/docker-sync/sync_manager.rb +16 -47
- data/lib/docker-sync/sync_process.rb +14 -22
- data/lib/docker-sync/sync_strategy/rsync.rb +3 -3
- data/lib/docker-sync/sync_strategy/unison.rb +3 -5
- data/lib/docker-sync/update_check.rb +18 -15
- data/lib/docker-sync/upgrade_check.rb +25 -7
- data/lib/docker-sync/watch_strategy/dummy.rb +0 -1
- data/lib/docker-sync/watch_strategy/fswatch.rb +2 -2
- data/lib/docker-sync/watch_strategy/unison.rb +0 -1
- data/tasks/daemon/daemon.thor +2 -2
- data/tasks/stack/stack.thor +12 -30
- data/tasks/sync/sync.thor +46 -28
- metadata +24 -5
- data/lib/docker-sync/config.rb +0 -120
- data/lib/docker-sync/config_template.rb +0 -17
- data/lib/docker-sync/preconditions.rb +0 -100
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1fcfca4a756b6eaaaa9a278bdea971d33e06117
|
4
|
+
data.tar.gz: aa304239c78e91cdabe5bbb59435d24bbcd9f485
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d50f3fafeb4b8cbd59c76e5735391f5becf785dbbc09ab01d8cd596d1d14cc9b62f1ecaad22c5e60296ebc61417675b3220bdd7de6f84bad3c3b283866545c8
|
7
|
+
data.tar.gz: 90303f06c9d494dea39ad881ecfdf3880960c62d8b6868c1f3783941a6804f738de17ba91a82b63e6de02152cca579eeb287439d12b91ba31f63e8fd8278ae18
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/docker-sync.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module DockerSync
|
4
|
+
# helps us loading our config files, GlobalConfig and ProjectConfig
|
5
|
+
module ConfigLocator
|
6
|
+
ERROR_MISSING_PROJECT_CONFIG =
|
7
|
+
'No docker-sync.yml configuration found in your path ( traversing up ) '\
|
8
|
+
'Did you define it for your project?'.freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :global_config_path
|
12
|
+
# @return [String] The path to the global config location
|
13
|
+
def current_global_config_path
|
14
|
+
path = global_config_path
|
15
|
+
path = File.expand_path('~/.docker-sync-global.yml') if path.nil?
|
16
|
+
path
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] the path to the project configuration found
|
20
|
+
def lookup_project_config_path
|
21
|
+
files = project_config_find
|
22
|
+
|
23
|
+
raise ERROR_MISSING_PROJECT_CONFIG if files.empty?
|
24
|
+
|
25
|
+
files.pop
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
|
31
|
+
# this has been ruthlessly stolen from Thor/runner.rb - please do not hunt me for that :)
|
32
|
+
# returns a list of file paths matching the docker-sync.yml file. The return the first one we find while traversing
|
33
|
+
# the folder tree up
|
34
|
+
# @return [Array]
|
35
|
+
def project_config_find(skip_lookup = false)
|
36
|
+
# Finds docker-sync.yml by traversing from your current directory down to the root
|
37
|
+
# directory of your system. If at any time we find a docker-sync.yml file, we stop.
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# ==== Example
|
41
|
+
#
|
42
|
+
# If we start at /Users/wycats/dev/thor ...
|
43
|
+
#
|
44
|
+
# 1. /Users/wycats/dev/thor
|
45
|
+
# 2. /Users/wycats/dev
|
46
|
+
# 3. /Users/wycats <-- we find a docker-sync.yml here, so we stop
|
47
|
+
#
|
48
|
+
# Suppose we start at c:\Documents and Settings\james\dev\docker-sync ...
|
49
|
+
#
|
50
|
+
# 1. c:\Documents and Settings\james\dev\docker-sync.yml
|
51
|
+
# 2. c:\Documents and Settings\james\dev
|
52
|
+
# 3. c:\Documents and Settings\james
|
53
|
+
# 4. c:\Documents and Settings
|
54
|
+
# 5. c:\ <-- no docker-sync.yml found!
|
55
|
+
#
|
56
|
+
docker_sync_files = []
|
57
|
+
|
58
|
+
unless skip_lookup
|
59
|
+
Pathname.pwd.ascend do |path|
|
60
|
+
docker_sync_files = globs_for_project_config(path).map { |g| Dir[g] }.flatten
|
61
|
+
break unless docker_sync_files.empty?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
docker_sync_files
|
66
|
+
end
|
67
|
+
|
68
|
+
# Where to look for docker-sync.yml files.
|
69
|
+
#
|
70
|
+
def globs_for_project_config(path)
|
71
|
+
path = escape_globs(path)
|
72
|
+
["#{path}/docker-sync.yml"]
|
73
|
+
end
|
74
|
+
|
75
|
+
def escape_globs(path)
|
76
|
+
path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'dotenv'
|
3
|
+
|
4
|
+
module DockerSync
|
5
|
+
module ConfigSerializer
|
6
|
+
class << self
|
7
|
+
# @param [String] config_path path to the yaml configuration to load
|
8
|
+
# @return [Object] returns a Yaml hashmap, expaneded by ENV vars
|
9
|
+
def default_deserializer_file(config_path)
|
10
|
+
config_string = File.read(config_path)
|
11
|
+
default_deserializer_string(config_string)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] config_string the configuration string inf yaml format
|
15
|
+
# @return [Object] a yaml hashmap
|
16
|
+
def default_deserializer_string(config_string)
|
17
|
+
deserialize_config( expand_env_variables(config_string) )
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Replaces our tokens, in this case all ENV variables we defined. Find those in the string an replace
|
23
|
+
# them with then values from our ENV, including the dotenv file
|
24
|
+
# @param [String] config_string
|
25
|
+
# @return [String]
|
26
|
+
def expand_env_variables(config_string)
|
27
|
+
load_dotenv
|
28
|
+
|
29
|
+
env_hash = {}
|
30
|
+
ENV.each {|k,v| env_hash[k.to_sym] = v }
|
31
|
+
config_string.gsub!('${', '%{')
|
32
|
+
config_string % env_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# deserializes the configuration string, right now as a yaml formatted string
|
37
|
+
# @param [String] config_string
|
38
|
+
# @return [Object]
|
39
|
+
def deserialize_config(config_string)
|
40
|
+
# noinspection RubyResolve
|
41
|
+
YAML.load(config_string)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Loads the dotenv file but also lets us overide the source not being .env but anything you put
|
45
|
+
# into the ENV variable DOCKER_SYNC_ENV_FILE
|
46
|
+
# @return [Object]
|
47
|
+
def load_dotenv
|
48
|
+
# TODO: ensure we do this once only
|
49
|
+
env_file = ENV.fetch('DOCKER_SYNC_ENV_FILE', '.env')
|
50
|
+
|
51
|
+
# noinspection RubyResolve
|
52
|
+
Dotenv.load(env_file)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
require 'docker-sync/config/config_locator'
|
5
|
+
require 'docker-sync/config/config_serializer'
|
6
|
+
|
7
|
+
module DockerSync
|
8
|
+
class GlobalConfig
|
9
|
+
extend Forwardable
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
# noinspection RubyStringKeysInHashInspection
|
13
|
+
DEFAULTS = {
|
14
|
+
'update_check' => true,
|
15
|
+
'update_last_check' => DateTime.new(2001, 1, 1).iso8601(9),
|
16
|
+
'update_enforce' => true,
|
17
|
+
'upgrade_status' => '',
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
attr_reader :config
|
21
|
+
private :config
|
22
|
+
|
23
|
+
def_delegators :@config, :[], :to_h
|
24
|
+
|
25
|
+
def self.load; instance end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
load_global_config
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_global_config
|
32
|
+
@config_path = DockerSync::ConfigLocator.current_global_config_path
|
33
|
+
if File.exist?(@config_path)
|
34
|
+
@config = DockerSync::ConfigSerializer.default_deserializer_file(@config_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
unless @config
|
38
|
+
@config = DEFAULTS.dup
|
39
|
+
@first_run = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def first_run?
|
44
|
+
@first_run
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [Object] updates
|
48
|
+
# Updates and saves the configuration back to the file
|
49
|
+
def update!(updates)
|
50
|
+
@config.merge!(updates)
|
51
|
+
|
52
|
+
File.open(@config_path, 'w') {|f| f.write @config.to_yaml }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'docker-sync/config/config_locator'
|
3
|
+
require 'docker-sync/config/config_serializer'
|
4
|
+
|
5
|
+
module DockerSync
|
6
|
+
class ProjectConfig
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
REQUIRED_CONFIG_VERSION = '2'.freeze
|
10
|
+
|
11
|
+
ERROR_MISSING_CONFIG_VERSION =
|
12
|
+
"Your docker-sync.yml file does not include a version: \"#{REQUIRED_CONFIG_VERSION}\""\
|
13
|
+
'(Add this if you migrated from docker-sync 0.1.x)'.freeze
|
14
|
+
|
15
|
+
ERROR_MISMATCH_CONFIG_VERSION =
|
16
|
+
'Your docker-sync.yml file does not match the required version '\
|
17
|
+
"(#{REQUIRED_CONFIG_VERSION}).".freeze
|
18
|
+
|
19
|
+
ERROR_MISSING_SYNCS = 'no syncs defined'.freeze
|
20
|
+
|
21
|
+
attr_reader :config, :config_path
|
22
|
+
private :config
|
23
|
+
|
24
|
+
def_delegators :@config, :[], :to_h
|
25
|
+
|
26
|
+
def initialize(config_path: nil, config_string: nil)
|
27
|
+
if config_string.nil?
|
28
|
+
config_path = DockerSync::ConfigLocator.lookup_project_config_path if config_path.nil?
|
29
|
+
load_project_config(config_path)
|
30
|
+
else
|
31
|
+
@config = DockerSync::ConfigSerializer.default_deserializer_string(config_string)
|
32
|
+
@config_path = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
validate_config!
|
36
|
+
normalize_config!
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_project_config(config_path = nil)
|
40
|
+
@config_path = config_path
|
41
|
+
return unless File.exist?(@config_path)
|
42
|
+
@config = DockerSync::ConfigSerializer.default_deserializer_file(@config_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def unison_required?
|
46
|
+
config['syncs'].any? { |name, sync_config|
|
47
|
+
sync_config['sync_strategy'] == 'unison' || sync_config['watch_strategy'] == 'unison'
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def rsync_required?
|
52
|
+
config['syncs'].any? { |name, sync_config|
|
53
|
+
sync_config['sync_strategy'] == 'rsync'
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def validate_config!
|
61
|
+
raise error_missing_given_config if config.nil?
|
62
|
+
raise ERROR_MISSING_CONFIG_VERSION unless config.key?('version')
|
63
|
+
raise ERROR_MISMATCH_CONFIG_VERSION unless config['version'].to_s == REQUIRED_CONFIG_VERSION
|
64
|
+
raise ERROR_MISSING_SYNCS unless config.key?('syncs')
|
65
|
+
|
66
|
+
validate_syncs_config!
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_syncs_config!
|
70
|
+
config['syncs'].each do |name, sync_config|
|
71
|
+
validate_sync_config(name, sync_config)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate_sync_config(name, sync_config)
|
76
|
+
config_mandatory = %w[src]
|
77
|
+
#TODO: Implement autodisovery for other strategies
|
78
|
+
config_mandatory.push('sync_host_port') if sync_config['sync_strategy'] == 'rsync'
|
79
|
+
config_mandatory.each do |key|
|
80
|
+
raise ("#{name} does not have #{key} configuration value set - this is mandatory") unless sync_config.key?(key)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def error_missing_given_config
|
85
|
+
"Config could not be loaded from #{config_path} - it does not exist"
|
86
|
+
end
|
87
|
+
|
88
|
+
def normalize_config!
|
89
|
+
config['syncs'].each do |name, sync_config|
|
90
|
+
config['syncs'][name] = normalize_sync_config(sync_config)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def normalize_sync_config(sync_config)
|
95
|
+
{
|
96
|
+
'sync_strategy' => sync_strategy_for(sync_config),
|
97
|
+
'watch_strategy' => watch_strategy_for(sync_config)
|
98
|
+
}.merge(sync_config)
|
99
|
+
end
|
100
|
+
|
101
|
+
def sync_strategy_for(sync_config)
|
102
|
+
case sync_config['sync_strategy']
|
103
|
+
when 'rsync' then 'rsync'
|
104
|
+
else 'unison'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def watch_strategy_for(sync_config)
|
109
|
+
if sync_config.key?('watch_strategy')
|
110
|
+
case sync_config['watch_strategy']
|
111
|
+
when 'fswatch' then 'fswatch'
|
112
|
+
when 'disable','dummy' then 'dummy'
|
113
|
+
else 'unison'
|
114
|
+
end
|
115
|
+
elsif sync_config['sync_strategy'] == 'rsync'
|
116
|
+
'fswatch'
|
117
|
+
else
|
118
|
+
'unison'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DockerSync
|
2
|
+
module Preconditions
|
3
|
+
class Linux
|
4
|
+
def check_all_preconditions(config)
|
5
|
+
end
|
6
|
+
|
7
|
+
def docker_available
|
8
|
+
end
|
9
|
+
|
10
|
+
def docker_running
|
11
|
+
end
|
12
|
+
|
13
|
+
def fswatch_available
|
14
|
+
end
|
15
|
+
|
16
|
+
def rsync_available
|
17
|
+
end
|
18
|
+
|
19
|
+
def unison_available
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
module DockerSync
|
3
|
+
module Preconditions
|
4
|
+
class Osx
|
5
|
+
def check_all_preconditions(config)
|
6
|
+
return unless should_run_precondition?
|
7
|
+
|
8
|
+
docker_available
|
9
|
+
docker_running
|
10
|
+
|
11
|
+
if config.unison_required?
|
12
|
+
unison_available
|
13
|
+
end
|
14
|
+
|
15
|
+
if config.rsync_required?
|
16
|
+
rsync_available
|
17
|
+
fswatch_available
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def docker_available
|
22
|
+
if (find_executable0 'docker').nil?
|
23
|
+
raise('Could not find docker binary in path. Please install it, e.g. using "brew install docker" or install docker-for-mac')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def docker_running
|
28
|
+
`docker ps`
|
29
|
+
if $?.exitstatus > 0
|
30
|
+
raise('No docker daemon seems to be running. Did you start your docker-for-mac / docker-machine?')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def rsync_available
|
35
|
+
if should_run_precondition?
|
36
|
+
if (find_executable0 'rsync').nil?
|
37
|
+
raise('Could not find rsync binary in path. Please install it, e.g. using "brew install rsync"')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unison_available
|
43
|
+
if should_run_precondition?
|
44
|
+
if (find_executable0 'unison').nil?
|
45
|
+
cmd1 = 'brew install unison"'
|
46
|
+
|
47
|
+
Thor::Shell::Basic.new.say_status 'warning', 'Could not find unison binary in $PATH. Trying to install now', :red
|
48
|
+
if Thor::Shell::Basic.new.yes?('I will install unison using brew for you? (y/N)')
|
49
|
+
system cmd1
|
50
|
+
else
|
51
|
+
raise('Please install it yourself using: brew install unison')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
unox_available
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def fswatch_available
|
60
|
+
if should_run_precondition?
|
61
|
+
if (find_executable0 'fswatch').nil?
|
62
|
+
cmd1 = 'brew install fswatch"'
|
63
|
+
|
64
|
+
Thor::Shell::Basic.new.say_status 'warning', 'No fswatch available. Install it by "brew install fswatch Trying to install now', :red
|
65
|
+
if Thor::Shell::Basic.new.yes?('I will install fswatch using brew for you? (y/N)')
|
66
|
+
system cmd1
|
67
|
+
else
|
68
|
+
raise('Please install it yourself using: brew install fswatch')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def should_run_precondition?(silent = false)
|
78
|
+
unless has_brew?
|
79
|
+
Thor::Shell::Basic.new.say_status 'inf', 'Not running any precondition checks since you have no brew and that is unsupported. Is all up to you know.', :white unless silent
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
return true
|
83
|
+
end
|
84
|
+
|
85
|
+
def has_brew?
|
86
|
+
return find_executable0 'brew'
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def unox_available
|
91
|
+
if should_run_precondition?
|
92
|
+
`brew list unox`
|
93
|
+
if $?.exitstatus > 0
|
94
|
+
unless (find_executable0 'unison-fsmonitor').nil?
|
95
|
+
# unox installed, but not using brew, we do not allow that anymore
|
96
|
+
Thor::Shell::Basic.new.say_status 'error', 'You install unison-fsmonitor (unox) not using brew. Please uninstall it and run docker-sync again, so we can install it for you', :red
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
cmd1 = 'brew tap eugenmayer/dockersync && brew install eugenmayer/dockersync/unox'
|
100
|
+
|
101
|
+
Thor::Shell::Basic.new.say_status 'warning', 'Could not find unison-fsmonitor (unox) binary in $PATH. Trying to install now', :red
|
102
|
+
if Thor::Shell::Basic.new.yes?('I will install unox through brew for you? (y/N)')
|
103
|
+
system cmd1
|
104
|
+
else
|
105
|
+
raise('Please install it yourself using: brew tap eugenmayer/dockersync && brew install unox')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def install_pip(package, test = nil)
|
112
|
+
test ? `python -c 'import #{test}'` : `python -c 'import #{package}'`
|
113
|
+
|
114
|
+
unless $?.success?
|
115
|
+
Thor::Shell::Basic.new.say_status 'warning', "Could not find #{package}. Will try to install it using pip", :red
|
116
|
+
if find_executable0('python') == '/usr/bin/python'
|
117
|
+
Thor::Shell::Basic.new.say_status 'ok', 'You seem to use the system python, we will need sudo below'
|
118
|
+
sudo = true
|
119
|
+
cmd2 = "sudo easy_install pip && sudo pip install #{package}"
|
120
|
+
else
|
121
|
+
Thor::Shell::Basic.new.say_status 'ok', 'You seem to have a custom python, using non-sudo commands'
|
122
|
+
sudo = false
|
123
|
+
cmd2 = "easy_install pip && pip install #{package}"
|
124
|
+
end
|
125
|
+
if sudo
|
126
|
+
question = "I will ask you for you root password to install #{package} by running (This will ask for sudo, since we use the system python)"
|
127
|
+
else
|
128
|
+
question = "I will now install #{package} for you by running"
|
129
|
+
end
|
130
|
+
|
131
|
+
Thor::Shell::Basic.new.say_status 'info', "#{question}: `#{cmd2}\n\n"
|
132
|
+
if Thor::Shell::Basic.new.yes?('Shall I continue? (y/N)')
|
133
|
+
system cmd2
|
134
|
+
if $?.exitstatus > 0
|
135
|
+
raise("Failed to install #{package}, please file an issue with the output of the error")
|
136
|
+
end
|
137
|
+
test ? `python -c 'import #{test}'` : `python -c 'import #{package}'`
|
138
|
+
unless $?.success?
|
139
|
+
raise("Somehow I could not successfully install #{package} even though I tried. Please report this issue.")
|
140
|
+
end
|
141
|
+
else
|
142
|
+
raise("Please install #{package} manually, see https://github.com/EugenMayer/docker-sync/wiki/1.-Installation")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|