docker-sync 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|