confctl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +11 -0
  3. data/.gitignore +8 -0
  4. data/.overcommit.yml +6 -0
  5. data/.rubocop.yml +67 -0
  6. data/.rubocop_todo.yml +5 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +2 -0
  9. data/Gemfile +2 -0
  10. data/LICENSE.txt +674 -0
  11. data/README.md +522 -0
  12. data/Rakefile +40 -0
  13. data/bin/confctl +4 -0
  14. data/confctl.gemspec +33 -0
  15. data/example/.gitignore +2 -0
  16. data/example/README.md +38 -0
  17. data/example/cluster/cluster.nix +7 -0
  18. data/example/cluster/module-list.nix +3 -0
  19. data/example/cluster/nixos-machine/config.nix +15 -0
  20. data/example/cluster/nixos-machine/hardware.nix +4 -0
  21. data/example/cluster/nixos-machine/module.nix +8 -0
  22. data/example/cluster/vpsadminos-container/config.nix +22 -0
  23. data/example/cluster/vpsadminos-container/module.nix +8 -0
  24. data/example/cluster/vpsadminos-machine/config.nix +22 -0
  25. data/example/cluster/vpsadminos-machine/hardware.nix +4 -0
  26. data/example/cluster/vpsadminos-machine/module.nix +8 -0
  27. data/example/cluster/vpsfreecz-vps/config.nix +25 -0
  28. data/example/cluster/vpsfreecz-vps/module.nix +8 -0
  29. data/example/configs/confctl.nix +10 -0
  30. data/example/configs/swpins.nix +28 -0
  31. data/example/data/default.nix +5 -0
  32. data/example/data/ssh-keys.nix +7 -0
  33. data/example/environments/base.nix +13 -0
  34. data/example/modules/module-list.nix +13 -0
  35. data/example/shell.nix +11 -0
  36. data/example/swpins/channels/nixos-unstable.json +35 -0
  37. data/example/swpins/channels/vpsadminos-staging.json +35 -0
  38. data/lib/confctl/cli/app.rb +551 -0
  39. data/lib/confctl/cli/attr_filters.rb +51 -0
  40. data/lib/confctl/cli/cluster.rb +1248 -0
  41. data/lib/confctl/cli/command.rb +206 -0
  42. data/lib/confctl/cli/configuration.rb +296 -0
  43. data/lib/confctl/cli/gen_data.rb +97 -0
  44. data/lib/confctl/cli/generation.rb +335 -0
  45. data/lib/confctl/cli/log_view.rb +267 -0
  46. data/lib/confctl/cli/output_formatter.rb +288 -0
  47. data/lib/confctl/cli/swpins/base.rb +40 -0
  48. data/lib/confctl/cli/swpins/channel.rb +73 -0
  49. data/lib/confctl/cli/swpins/cluster.rb +80 -0
  50. data/lib/confctl/cli/swpins/core.rb +86 -0
  51. data/lib/confctl/cli/swpins/utils.rb +55 -0
  52. data/lib/confctl/cli/swpins.rb +5 -0
  53. data/lib/confctl/cli/tag_filters.rb +30 -0
  54. data/lib/confctl/cli.rb +5 -0
  55. data/lib/confctl/conf_cache.rb +105 -0
  56. data/lib/confctl/conf_dir.rb +88 -0
  57. data/lib/confctl/erb_template.rb +37 -0
  58. data/lib/confctl/exceptions.rb +3 -0
  59. data/lib/confctl/gcroot.rb +30 -0
  60. data/lib/confctl/generation/build.rb +145 -0
  61. data/lib/confctl/generation/build_list.rb +106 -0
  62. data/lib/confctl/generation/host.rb +35 -0
  63. data/lib/confctl/generation/host_list.rb +81 -0
  64. data/lib/confctl/generation/unified.rb +117 -0
  65. data/lib/confctl/generation/unified_list.rb +63 -0
  66. data/lib/confctl/git_repo_mirror.rb +79 -0
  67. data/lib/confctl/health_checks/base.rb +66 -0
  68. data/lib/confctl/health_checks/run_command.rb +179 -0
  69. data/lib/confctl/health_checks/systemd/properties.rb +84 -0
  70. data/lib/confctl/health_checks/systemd/property_check.rb +31 -0
  71. data/lib/confctl/health_checks/systemd/property_list.rb +20 -0
  72. data/lib/confctl/health_checks.rb +5 -0
  73. data/lib/confctl/hook.rb +35 -0
  74. data/lib/confctl/line_buffer.rb +53 -0
  75. data/lib/confctl/logger.rb +151 -0
  76. data/lib/confctl/machine.rb +107 -0
  77. data/lib/confctl/machine_control.rb +172 -0
  78. data/lib/confctl/machine_list.rb +108 -0
  79. data/lib/confctl/machine_status.rb +135 -0
  80. data/lib/confctl/module_options.rb +95 -0
  81. data/lib/confctl/nix.rb +382 -0
  82. data/lib/confctl/nix_build.rb +108 -0
  83. data/lib/confctl/nix_collect_garbage.rb +64 -0
  84. data/lib/confctl/nix_copy.rb +49 -0
  85. data/lib/confctl/nix_format.rb +124 -0
  86. data/lib/confctl/nix_literal_expression.rb +15 -0
  87. data/lib/confctl/parallel_executor.rb +43 -0
  88. data/lib/confctl/pattern.rb +9 -0
  89. data/lib/confctl/settings.rb +50 -0
  90. data/lib/confctl/std_line_buffer.rb +40 -0
  91. data/lib/confctl/swpins/change_set.rb +151 -0
  92. data/lib/confctl/swpins/channel.rb +62 -0
  93. data/lib/confctl/swpins/channel_list.rb +47 -0
  94. data/lib/confctl/swpins/cluster_name.rb +94 -0
  95. data/lib/confctl/swpins/cluster_name_list.rb +15 -0
  96. data/lib/confctl/swpins/core.rb +137 -0
  97. data/lib/confctl/swpins/deployed_info.rb +23 -0
  98. data/lib/confctl/swpins/spec.rb +20 -0
  99. data/lib/confctl/swpins/specs/base.rb +184 -0
  100. data/lib/confctl/swpins/specs/directory.rb +51 -0
  101. data/lib/confctl/swpins/specs/git.rb +135 -0
  102. data/lib/confctl/swpins/specs/git_rev.rb +24 -0
  103. data/lib/confctl/swpins.rb +17 -0
  104. data/lib/confctl/system_command.rb +10 -0
  105. data/lib/confctl/user_script.rb +13 -0
  106. data/lib/confctl/user_scripts.rb +41 -0
  107. data/lib/confctl/utils/file.rb +21 -0
  108. data/lib/confctl/version.rb +3 -0
  109. data/lib/confctl.rb +43 -0
  110. data/man/man8/confctl-options.nix.8 +1334 -0
  111. data/man/man8/confctl-options.nix.8.md +1340 -0
  112. data/man/man8/confctl.8 +660 -0
  113. data/man/man8/confctl.8.md +654 -0
  114. data/nix/evaluator.nix +160 -0
  115. data/nix/lib/default.nix +83 -0
  116. data/nix/lib/machine/default.nix +74 -0
  117. data/nix/lib/machine/info.nix +5 -0
  118. data/nix/lib/swpins/eval.nix +71 -0
  119. data/nix/lib/swpins/options.nix +94 -0
  120. data/nix/machines.nix +31 -0
  121. data/nix/modules/cluster/default.nix +459 -0
  122. data/nix/modules/confctl/cli.nix +21 -0
  123. data/nix/modules/confctl/generations.nix +84 -0
  124. data/nix/modules/confctl/nix.nix +28 -0
  125. data/nix/modules/confctl/swpins.nix +55 -0
  126. data/nix/modules/module-list.nix +19 -0
  127. data/shell.nix +42 -0
  128. data/template/confctl-options.nix/main.erb +45 -0
  129. data/template/confctl-options.nix/options.erb +15 -0
  130. metadata +353 -0
@@ -0,0 +1,206 @@
1
+ require 'gli'
2
+ require 'rainbow'
3
+
4
+ module ConfCtl::Cli
5
+ class Command
6
+ def self.run(gli_cmd, klass, method)
7
+ proc do |global_opts, opts, args|
8
+ log = ConfCtl::Logger.instance
9
+ log.open(gli_cmd.name_for_help.join('-'))
10
+ log.cli(
11
+ gli_cmd.name_for_help,
12
+ global_opts,
13
+ opts,
14
+ args
15
+ )
16
+
17
+ cmd = klass.new(global_opts, opts, args)
18
+ cmd.run_method(method)
19
+
20
+ if log.keep?
21
+ log.close
22
+ else
23
+ log.close_and_unlink
24
+ end
25
+ end
26
+ end
27
+
28
+ attr_reader :gopts, :opts, :args
29
+
30
+ def initialize(global_opts, opts, args)
31
+ @gopts = global_opts
32
+ @opts = opts
33
+ @args = args
34
+ @use_color = determine_color
35
+ @use_pager = determine_pager
36
+ end
37
+
38
+ # @param v [Array] list of required arguments
39
+ # @param optional [Array] list of optional arguments
40
+ # @param strict [Boolean] do not allow more arguments than specified
41
+ def require_args!(*required, optional: [], strict: true)
42
+ if args.count < required.count
43
+ arg = required[args.count]
44
+ raise GLI::BadCommandLine, "missing argument <#{arg}>"
45
+
46
+ elsif strict && args.count > (required.count + optional.count)
47
+ unknown = args[(required.count + optional.count)..]
48
+
49
+ msg = ''
50
+
51
+ msg << if unknown.count > 1
52
+ 'unknown arguments: '
53
+ else
54
+ 'unknown argument: '
55
+ end
56
+
57
+ msg << unknown.join(' ')
58
+
59
+ msg << ' (note that options must come before arguments)' if unknown.detect { |v| v.start_with?('-') }
60
+
61
+ raise GLI::BadCommandLine, msg
62
+ end
63
+ end
64
+
65
+ def use_color?
66
+ @use_color
67
+ end
68
+
69
+ def use_pager?
70
+ @use_pager
71
+ end
72
+
73
+ def ask_confirmation(always: false)
74
+ return true if !always && opts[:yes]
75
+
76
+ yield if block_given?
77
+
78
+ loop do
79
+ $stdout.write("\nContinue? [y/N]: ")
80
+ $stdout.flush
81
+
82
+ case $stdin.readline.strip.downcase
83
+ when 'y'
84
+ puts
85
+ return true
86
+ when 'n'
87
+ puts
88
+ return false
89
+ end
90
+ end
91
+ end
92
+
93
+ def ask_confirmation!(...)
94
+ raise 'Aborted' unless ask_confirmation(...)
95
+ end
96
+
97
+ # @param options [Hash<String, String>] key => description
98
+ # @param default [String] default option key
99
+ # @return [String] selection option key
100
+ def ask_action(options:, default:)
101
+ yield if block_given?
102
+
103
+ loop do
104
+ $stdout.puts("\nOptions:\n")
105
+
106
+ options.each do |key, desc|
107
+ $stdout.puts(" [#{key}] #{desc}")
108
+ end
109
+
110
+ keys = options.keys.map do |k|
111
+ if k == default
112
+ k.upcase
113
+ else
114
+ k
115
+ end
116
+ end.join('/')
117
+
118
+ $stdout.write("\nAction: [#{keys}]: ")
119
+ $stdout.flush
120
+
121
+ answer = $stdin.readline.strip.downcase
122
+
123
+ if options.has_key?(answer)
124
+ puts
125
+ return answer
126
+ end
127
+ end
128
+ end
129
+
130
+ def run_method(method)
131
+ self.method(method).call
132
+ end
133
+
134
+ protected
135
+
136
+ def run_command(klass, method)
137
+ c = klass.new(gopts, opts, args)
138
+ c.run_method(method)
139
+ end
140
+
141
+ def determine_color
142
+ case gopts[:color]
143
+ when 'always'
144
+ Rainbow.enabled = true
145
+ true
146
+ when 'never'
147
+ Rainbow.enabled = false
148
+ false
149
+ when 'auto'
150
+ Rainbow.enabled
151
+ end
152
+ end
153
+
154
+ def determine_pager
155
+ ENV.fetch('PAGER', nil) && ENV['PAGER'].strip != ''
156
+ end
157
+
158
+ def select_machines(pattern)
159
+ machines = ConfCtl::MachineList.new(show_trace: opts['show-trace'])
160
+
161
+ attr_filters = AttrFilters.new(opts[:attr])
162
+ tag_filters = TagFilters.new(opts[:tag])
163
+
164
+ machines.select do |host, d|
165
+ (pattern.nil? || ConfCtl::Pattern.match?(pattern, host)) \
166
+ && attr_filters.pass?(d) \
167
+ && tag_filters.pass?(d)
168
+ end
169
+ end
170
+
171
+ def select_machines_with_managed(pattern)
172
+ selected = select_machines(pattern)
173
+
174
+ case opts[:managed]
175
+ when 'n', 'no'
176
+ selected.unmanaged
177
+ when 'a', 'all'
178
+ selected
179
+ else
180
+ selected.managed
181
+ end
182
+ end
183
+
184
+ def list_machines(machines, prepend_cols: [])
185
+ cols =
186
+ if opts[:output]
187
+ opts[:output].split(',')
188
+ else
189
+ ConfCtl::Settings.instance.list_columns
190
+ end
191
+
192
+ cols = prepend_cols + cols if prepend_cols
193
+
194
+ rows = machines.map do |_host, machine|
195
+ cols.to_h { |c| [c, machine[c]] }
196
+ end
197
+
198
+ OutputFormatter.print(
199
+ rows,
200
+ cols,
201
+ header: !opts['hide-header'],
202
+ layout: :columns
203
+ )
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,296 @@
1
+ require 'fileutils'
2
+ require 'securerandom'
3
+
4
+ module ConfCtl::Cli
5
+ class Configuration < Command
6
+ DIR_MODE = 0o755
7
+ FILE_MODE = 0o644
8
+
9
+ def init
10
+ dir = File.realpath(Dir.pwd)
11
+
12
+ Dir.entries(dir).each do |v|
13
+ raise 'init must be called in an empty directory' unless %w[. .. shell.nix .confctl .gems
14
+ .gitignore].include?(v)
15
+ end
16
+
17
+ mkdir('cluster')
18
+
19
+ mkfile('cluster/module-list.nix') do |f|
20
+ f.puts(<<~END
21
+ (import ./cluster.nix) ++ [
22
+ # Place for custom modules
23
+ ]
24
+ END
25
+ )
26
+ end
27
+
28
+ mkfile('cluster/cluster.nix') do |f|
29
+ f.puts(<<~END
30
+ # This file is generated by confctl, changes will be lost
31
+ []
32
+ END
33
+ )
34
+ end
35
+
36
+ mkdir('configs')
37
+ mkfile('configs/confctl.nix') do |f|
38
+ f.puts(<<~END
39
+ { config, ... }:
40
+ {
41
+ confctl = {
42
+ # listColumns = {
43
+ # "name"
44
+ # "spin"
45
+ # "host.fqdn"
46
+ # };
47
+ };
48
+ }
49
+ END
50
+ )
51
+ end
52
+
53
+ mkfile('configs/swpins.nix') do |f|
54
+ f.puts(<<~END
55
+ { config, ... }:
56
+ let
57
+ nixpkgsBranch = branch: {
58
+ type = "git-rev";
59
+
60
+ git-rev = {
61
+ url = "https://github.com/NixOS/nixpkgs";
62
+ update.ref = "refs/heads/${branch}";
63
+ };
64
+ };
65
+
66
+ vpsadminosBranch = branch: {
67
+ type = "git-rev";
68
+
69
+ git-rev = {
70
+ url = "https://github.com/vpsfreecz/vpsadminos";
71
+ update.ref = "refs/heads/${branch}";
72
+ };
73
+ };
74
+ in {
75
+ confctl.swpins.channels = {
76
+ nixos-unstable = { nixpkgs = nixpkgsBranch "nixos-unstable"; };
77
+
78
+ # nixos-stable = { nixpkgs = nixpkgsBranch "nixos-20.09"; };
79
+
80
+ # vpsadminos-staging = { vpsadminos = vpsadminosBranch "staging"; };
81
+ };
82
+ }
83
+ END
84
+ )
85
+ end
86
+
87
+ mkdir('data')
88
+
89
+ mkfile('data/default.nix') do |f|
90
+ f.puts(<<~END
91
+ { lib }:
92
+ {
93
+ # Place to load custom data sets
94
+ sshKeys = import ./ssh-keys.nix;
95
+ }
96
+ END
97
+ )
98
+ end
99
+
100
+ mkfile('data/ssh-keys.nix') do |f|
101
+ f.puts(<<~END
102
+ rec {
103
+ admins = [
104
+ # someone
105
+ ];
106
+
107
+ someone = "...ssh public key...";
108
+ }
109
+ END
110
+ )
111
+ end
112
+
113
+ mkdir('environments')
114
+
115
+ mkfile('environments/base.nix') do |f|
116
+ f.puts(<<~END
117
+ { config, pkgs, confData, ... }:
118
+ {
119
+ time.timeZone = "Europe/Amsterdam";
120
+
121
+ services.openssh.enable = true;
122
+
123
+ environment.systemPackages = with pkgs; [
124
+ vim
125
+ screen
126
+ ];
127
+
128
+ users.users.root.openssh.authorizedKeys.keys = with confData.sshKeys; admins;
129
+ }
130
+ END
131
+ )
132
+ end
133
+
134
+ mkdir('modules')
135
+ mkfile('modules/module-list.nix') do |f|
136
+ f.puts(<<~END
137
+ rec {
138
+ shared = [
139
+ # Modules not dependent on spin
140
+ ];
141
+
142
+ nixos = shared ++ [
143
+ # Modules only for NixOS
144
+ ];
145
+
146
+ vpsadminos = shared ++ [
147
+ # Modules only for vpsAdminOS
148
+ ];
149
+ }
150
+ END
151
+ )
152
+ end
153
+
154
+ mkdir('swpins')
155
+ mkdir('swpins/channels')
156
+ end
157
+
158
+ def add
159
+ require_args!('name')
160
+
161
+ name = args[0]
162
+ dir = File.join('cluster', name)
163
+ depth = name.count('/')
164
+
165
+ raise "#{dir} already exists" if Dir.exist?(dir)
166
+
167
+ mkdir_p(dir)
168
+
169
+ mkfile(File.join(dir, 'module.nix')) do |f|
170
+ f.puts(<<~END
171
+ { config, ... }:
172
+ {
173
+ cluster."#{name}" = {
174
+ spin = "nixos";
175
+ swpins.channels = [ "nixos-unstable" ];
176
+ host = { name = "machine"; domain = "example.com"; };
177
+ };
178
+ }
179
+ END
180
+ )
181
+ end
182
+
183
+ mkfile(File.join(dir, 'config.nix')) do |f|
184
+ f.puts(<<~END
185
+ { config, pkgs, lib, ... }:
186
+ {
187
+ imports = [
188
+ #{'../' * (depth + 2)}environments/base.nix
189
+ ];
190
+
191
+ # ... standard NixOS configuration ...
192
+
193
+ networking.hostName = "#{name.gsub('/', '-')}";
194
+ }
195
+ END
196
+ )
197
+ end
198
+
199
+ rediscover
200
+ end
201
+
202
+ def rename
203
+ require_args!('old-name', 'new-name')
204
+
205
+ src = args[0]
206
+ dst = args[1]
207
+
208
+ src_path = File.join('cluster', src)
209
+ dst_path = File.join('cluster', dst)
210
+
211
+ if !Dir.exist?(src_path)
212
+ raise "'#{src}' not found"
213
+ elsif Dir.exist?(dst_path)
214
+ raise "'#{dst}' already exists"
215
+ end
216
+
217
+ dst_dir = File.dirname(dst_path)
218
+ mkdir_p(dst_dir)
219
+ mv(src_path, dst_path)
220
+
221
+ src_swpins = File.join('swpins/cluster', "#{src.gsub('/', ':')}.json")
222
+ dst_swpins = File.join('swpins/cluster', "#{dst.gsub('/', ':')}.json")
223
+ mv(src_swpins, dst_swpins) if File.exist?(src_swpins)
224
+
225
+ rediscover
226
+ end
227
+
228
+ def rediscover
229
+ hosts = discover_dir('cluster').sort
230
+
231
+ replace_file('cluster/cluster.nix') do |f|
232
+ f.puts('# This file is generated by confctl, changes will be lost')
233
+ f.puts('[')
234
+
235
+ hosts.each do |host|
236
+ f.puts(" ./#{host}/module.nix")
237
+ end
238
+
239
+ f.puts(']')
240
+ end
241
+ end
242
+
243
+ protected
244
+
245
+ def discover_dir(dir_path, rel_path = nil)
246
+ ret = []
247
+
248
+ Dir.entries(dir_path).each do |v|
249
+ entry_abs_path = File.join(dir_path, v)
250
+ next if %w[. ..].include?(v) || !File.directory?(entry_abs_path)
251
+
252
+ entry_rel_path = File.join(*[rel_path, v].compact)
253
+
254
+ if File.exist?(File.join(entry_abs_path, 'module.nix')) \
255
+ && File.exist?(File.join(entry_abs_path, 'config.nix'))
256
+ ret << entry_rel_path
257
+ end
258
+
259
+ ret.concat(discover_dir(entry_abs_path, entry_rel_path))
260
+ end
261
+
262
+ ret
263
+ end
264
+
265
+ def mkdir(name)
266
+ puts "mkdir #{name}"
267
+ Dir.mkdir(name, DIR_MODE)
268
+ end
269
+
270
+ def mkdir_p(name)
271
+ puts "mkdir #{name}"
272
+ FileUtils.mkdir_p(name, mode: DIR_MODE)
273
+ end
274
+
275
+ def mkfile(name)
276
+ puts "mkfile #{name}"
277
+ f = File.open(name, 'w', FILE_MODE)
278
+ yield(f)
279
+ f.close
280
+ end
281
+
282
+ def replace_file(name, &)
283
+ puts "replace #{name}"
284
+ replacement = "#{name}.new-#{SecureRandom.hex(3)}"
285
+
286
+ File.open(replacement, 'w', FILE_MODE, &)
287
+
288
+ File.rename(replacement, name)
289
+ end
290
+
291
+ def mv(old_name, new_name)
292
+ puts "mv #{old_name} #{new_name}"
293
+ File.rename(old_name, new_name)
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,97 @@
1
+ require 'highline/import'
2
+ require 'vpsfree/client'
3
+ require 'confctl/conf_dir'
4
+
5
+ module ConfCtl::Cli
6
+ class GenData < Command
7
+ def vpsadmin_all
8
+ vpsadmin_containers
9
+ vpsadmin_network
10
+ end
11
+
12
+ def vpsadmin_containers
13
+ api = vpsadmin_client
14
+
15
+ machines = ConfCtl::MachineList.new
16
+ data = {}
17
+
18
+ machines.each_value do |m|
19
+ next if m['container'].nil?
20
+
21
+ ct = api.vps.show(
22
+ m['container.id'],
23
+ meta: { includes: 'node__location__environment' }
24
+ )
25
+
26
+ ct_fqdn = [
27
+ m['host.name'],
28
+ m['host.location'],
29
+ m['host.domain']
30
+ ].compact.join('.')
31
+
32
+ data[ct_fqdn] = {
33
+ node: {
34
+ id: ct.node.id,
35
+ name: ct.node.name,
36
+ location: ct.node.location.domain,
37
+ domain: ct.node.location.environment.domain,
38
+ fqdn: "#{ct.node.domain_name}.#{ct.node.location.environment.domain}"
39
+ }
40
+ }
41
+ end
42
+
43
+ update_file('vpsadmin/containers.nix') do |f|
44
+ f.puts(ConfCtl::NixFormat.to_nix(data))
45
+ end
46
+ end
47
+
48
+ def vpsadmin_network
49
+ vpsadmin_network_containers
50
+ end
51
+
52
+ def vpsadmin_network_containers
53
+ api = vpsadmin_client
54
+ networks = api.network.list
55
+ data = {}
56
+
57
+ [4, 6].each do |ip_v|
58
+ data["ipv#{ip_v}"] = networks.select { |net| net.ip_version == ip_v }.map do |net|
59
+ { address: net.address, prefix: net.prefix }
60
+ end
61
+ end
62
+
63
+ update_file('vpsadmin/networks/containers.nix') do |f|
64
+ f.puts(ConfCtl::NixFormat.to_nix(data))
65
+ end
66
+ end
67
+
68
+ protected
69
+
70
+ def vpsadmin_client
71
+ return @vpsadmin_client if @vpsadmin_client
72
+
73
+ @vpsadmin_client = VpsFree::Client.new
74
+
75
+ user = ask('User name: ') { |q| q.default = nil }.to_s
76
+ password = ask('Password: ') do |q|
77
+ q.default = nil
78
+ q.echo = false
79
+ end.to_s
80
+
81
+ @vpsadmin_client.authenticate(:basic, user:, password:)
82
+ @vpsadmin_client
83
+ end
84
+
85
+ def update_file(relpath, &)
86
+ abs = File.join(data_dir, relpath)
87
+ tmp = "#{abs}.new"
88
+
89
+ File.open(tmp, 'w', &)
90
+ File.rename(tmp, abs)
91
+ end
92
+
93
+ def data_dir
94
+ @data_dir ||= File.join(ConfCtl::ConfDir.path, 'data')
95
+ end
96
+ end
97
+ end