confctl 2.0.0 → 2.2.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/CHANGELOG.md +19 -1
- data/Gemfile +6 -0
- data/README.md +2 -1
- data/docs/carrier.md +12 -0
- data/lib/confctl/cli/app.rb +19 -0
- data/lib/confctl/cli/cluster.rb +183 -47
- data/lib/confctl/cli/generation.rb +44 -15
- data/lib/confctl/cli/swpins/channel.rb +6 -4
- data/lib/confctl/cli/swpins/cluster.rb +6 -4
- data/lib/confctl/cli/swpins/core.rb +6 -4
- data/lib/confctl/generation/build.rb +42 -1
- data/lib/confctl/generation/build_list.rb +10 -0
- data/lib/confctl/generation/host.rb +9 -5
- data/lib/confctl/generation/host_list.rb +20 -5
- data/lib/confctl/generation/unified.rb +5 -0
- data/lib/confctl/generation/unified_list.rb +10 -0
- data/lib/confctl/machine.rb +4 -0
- data/lib/confctl/machine_control.rb +3 -2
- data/lib/confctl/machine_list.rb +4 -0
- data/lib/confctl/machine_status.rb +1 -1
- data/lib/confctl/nix.rb +63 -18
- data/lib/confctl/nix_copy.rb +5 -5
- data/lib/confctl/null_logger.rb +7 -0
- data/lib/confctl/swpins/change_set.rb +11 -4
- data/lib/confctl/swpins/specs/git.rb +23 -16
- data/lib/confctl/swpins/specs/git_rev.rb +1 -1
- data/lib/confctl/system_command.rb +3 -2
- data/lib/confctl/version.rb +1 -1
- data/libexec/auto-rollback.rb +106 -0
- data/man/man8/confctl.8 +109 -72
- data/man/man8/confctl.8.md +91 -54
- data/nix/evaluator.nix +26 -1
- data/nix/modules/cluster/default.nix +20 -0
- data/nix/modules/confctl/carrier/base.nix +8 -6
- data/nix/modules/confctl/carrier/netboot/build-netboot-server.rb +209 -50
- data/nix/modules/confctl/carrier/netboot/nixos.nix +5 -3
- data/nix/modules/confctl/kexec-netboot/default.nix +38 -0
- data/nix/modules/confctl/kexec-netboot/kexec-netboot.8.adoc +62 -0
- data/nix/modules/confctl/kexec-netboot/kexec-netboot.rb +455 -0
- data/nix/modules/confctl/overlays.nix +15 -0
- data/nix/modules/module-list.nix +1 -0
- data/nix/modules/system-list.nix +3 -1
- data/shell.nix +9 -2
- metadata +8 -6
@@ -0,0 +1,455 @@
|
|
1
|
+
#!@ruby@/bin/ruby
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'uri'
|
6
|
+
require 'optparse'
|
7
|
+
require 'tempfile'
|
8
|
+
|
9
|
+
class KexecNetboot
|
10
|
+
MACHINE_FQDN = '@machineFqdn@'.freeze
|
11
|
+
|
12
|
+
KEXEC = '@kexecTools@/bin/kexec'.freeze
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@server_url = nil
|
16
|
+
@machine_fqdn = nil
|
17
|
+
@machine_gen = nil
|
18
|
+
@variant_name = nil
|
19
|
+
@interactive = false
|
20
|
+
@append_params = ''
|
21
|
+
@exec = false
|
22
|
+
@unload = false
|
23
|
+
@machines_json = nil
|
24
|
+
@tmp_files = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
parse_arguments
|
29
|
+
|
30
|
+
if @unload && @exec
|
31
|
+
warn 'ERROR: use either --unload or --exec, not both'
|
32
|
+
exit 1
|
33
|
+
elsif @unload
|
34
|
+
return unload_kexec
|
35
|
+
elsif @exec
|
36
|
+
return exec_kexec
|
37
|
+
end
|
38
|
+
|
39
|
+
httproot = parse_httproot_from_cmdline
|
40
|
+
unless httproot
|
41
|
+
warn "ERROR: Could not find 'httproot=' parameter in /proc/cmdline"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
|
45
|
+
machines_url =
|
46
|
+
if @server_url
|
47
|
+
File.join(@server_url, 'machines.json')
|
48
|
+
else
|
49
|
+
derive_machines_json_url(httproot)
|
50
|
+
end
|
51
|
+
|
52
|
+
@machines_json = fetch_machines_json(machines_url)
|
53
|
+
|
54
|
+
machine_data = pick_machine(@machine_fqdn)
|
55
|
+
|
56
|
+
if machine_data.nil?
|
57
|
+
warn 'ERROR: No suitable machine found.'
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
generation = pick_generation(machine_data, @machine_gen)
|
62
|
+
|
63
|
+
if generation.nil?
|
64
|
+
warn 'ERROR: No generation found/selected.'
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
68
|
+
variant = pick_variant(generation, @variant_name)
|
69
|
+
|
70
|
+
combined_params = generation['kernel_params'].dup
|
71
|
+
|
72
|
+
if variant && variant['kernel_params']
|
73
|
+
combined_params.concat(variant['kernel_params'])
|
74
|
+
end
|
75
|
+
|
76
|
+
combined_params << @append_params
|
77
|
+
final_params = combined_params.join(' ')
|
78
|
+
|
79
|
+
if @interactive
|
80
|
+
puts
|
81
|
+
puts 'Selected configuration:'
|
82
|
+
puts " Machine: #{machine_data['fqdn']} (spin=#{machine_data['spin']})"
|
83
|
+
puts " Generation: #{generation['generation']} (#{generation['time_s']}, rev=#{generation['shortrev']}, kernel=#{generation['kernel_version']})"
|
84
|
+
if variant
|
85
|
+
puts " Variant: #{variant['name']} (#{variant['label']})"
|
86
|
+
else
|
87
|
+
puts ' Variant: none'
|
88
|
+
end
|
89
|
+
puts " Kernel params: #{final_params}"
|
90
|
+
puts
|
91
|
+
|
92
|
+
loop do
|
93
|
+
print 'Continue? [y/N]: '
|
94
|
+
|
95
|
+
case $stdin.readline.strip.downcase
|
96
|
+
when 'y'
|
97
|
+
puts
|
98
|
+
break
|
99
|
+
when 'n'
|
100
|
+
warn 'Aborting'
|
101
|
+
exit(false)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Download kernel + initrd
|
107
|
+
kernel_path = download_file(generation['boot_files']['bzImage'])
|
108
|
+
initrd_path = download_file(generation['boot_files']['initrd'])
|
109
|
+
|
110
|
+
# kexec -l
|
111
|
+
load_kexec(kernel_path, initrd_path, final_params)
|
112
|
+
|
113
|
+
# Cleanup
|
114
|
+
cleanup_downloads
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def parse_arguments
|
120
|
+
opt_parser = OptionParser.new do |opts|
|
121
|
+
opts.banner = "Usage: #{$0} [options]"
|
122
|
+
|
123
|
+
opts.on('-s', '--server-url URL', 'Specify URL of the netboot server') do |val|
|
124
|
+
@server_url = val
|
125
|
+
end
|
126
|
+
|
127
|
+
opts.on('-m', '--machine FQDN', 'Select machine by FQDN') do |val|
|
128
|
+
@machine_fqdn = val
|
129
|
+
end
|
130
|
+
|
131
|
+
opts.on('-g', '--generation N', 'Select generation by number or negative offset') do |val|
|
132
|
+
@machine_gen = val
|
133
|
+
end
|
134
|
+
|
135
|
+
opts.on('-v', '--variant NAME', 'Select a specific variant by name') do |val|
|
136
|
+
@variant_name = val
|
137
|
+
end
|
138
|
+
|
139
|
+
opts.on('-i', '--interactive', 'Enable interactive mode') do
|
140
|
+
@interactive = true
|
141
|
+
end
|
142
|
+
|
143
|
+
opts.on('-a', '--append PARAMS', 'Append parameters to kernel command line') do |val|
|
144
|
+
@append_params = val
|
145
|
+
end
|
146
|
+
|
147
|
+
opts.on('-e', '--exec', 'Run the currently loaded kernel') do
|
148
|
+
@exec = true
|
149
|
+
end
|
150
|
+
|
151
|
+
opts.on('-u', '--unload', 'Unload the current kexec target kernel and exit') do
|
152
|
+
@unload = true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
opt_parser.parse!(ARGV)
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_httproot_from_cmdline
|
160
|
+
cmdline = File.read('/proc/cmdline').strip
|
161
|
+
cmdline[/\bhttproot=([^\s]+)/, 1]
|
162
|
+
end
|
163
|
+
|
164
|
+
def derive_machines_json_url(httproot)
|
165
|
+
uri = URI.parse(httproot)
|
166
|
+
path_parts = uri.path.split('/').reject(&:empty?)
|
167
|
+
|
168
|
+
if path_parts.size >= 3
|
169
|
+
3.times { path_parts.pop }
|
170
|
+
end
|
171
|
+
|
172
|
+
new_path = "/#{path_parts.join('/')}"
|
173
|
+
new_path << '/' unless new_path.end_with?('/')
|
174
|
+
new_path << 'machines.json'
|
175
|
+
|
176
|
+
URI::HTTP.build(host: uri.host, port: uri.port, path: new_path).to_s
|
177
|
+
end
|
178
|
+
|
179
|
+
def fetch_machines_json(machines_url)
|
180
|
+
uri = URI.parse(machines_url)
|
181
|
+
response = Net::HTTP.get_response(uri)
|
182
|
+
|
183
|
+
unless response.is_a?(Net::HTTPSuccess)
|
184
|
+
warn "ERROR: Could not download #{machines_url}, HTTP #{response.code}"
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
|
188
|
+
begin
|
189
|
+
JSON.parse(response.body)
|
190
|
+
rescue JSON::ParserError => e
|
191
|
+
warn "ERROR: Could not parse JSON: #{e}"
|
192
|
+
exit 1
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def pick_machine(requested_fqdn)
|
197
|
+
machines = @machines_json['machines']
|
198
|
+
return if machines.nil? || machines.empty?
|
199
|
+
|
200
|
+
if requested_fqdn.nil? && @interactive
|
201
|
+
return interactive_pick_machine(machines)
|
202
|
+
end
|
203
|
+
|
204
|
+
requested_fqdn ||= MACHINE_FQDN
|
205
|
+
|
206
|
+
found = machines.detect { |m| m['fqdn'] == requested_fqdn }
|
207
|
+
|
208
|
+
if found.nil?
|
209
|
+
warn "Machine '#{requested_fqdn}' not found."
|
210
|
+
return
|
211
|
+
end
|
212
|
+
|
213
|
+
found
|
214
|
+
end
|
215
|
+
|
216
|
+
def interactive_pick_machine(machines)
|
217
|
+
format_str = "%5s %-30s %s\n"
|
218
|
+
default_machine = machines.detect { |m| m['fqdn'] == MACHINE_FQDN }
|
219
|
+
|
220
|
+
loop do
|
221
|
+
puts format(format_str, '', 'FQDN', 'SPIN')
|
222
|
+
|
223
|
+
machines.each_with_index do |m, idx|
|
224
|
+
current_mark = m['fqdn'] == default_machine['fqdn'] ? '*' : ''
|
225
|
+
puts format(format_str, "[#{idx + 1}]" + current_mark, m['fqdn'], m['spin'])
|
226
|
+
end
|
227
|
+
|
228
|
+
print 'Select a machine by number: '
|
229
|
+
input = $stdin.gets
|
230
|
+
return if input.nil?
|
231
|
+
|
232
|
+
idx = input.strip.to_i
|
233
|
+
|
234
|
+
if idx == 0 && default_machine
|
235
|
+
return default_machine
|
236
|
+
elsif idx.between?(1, machines.size)
|
237
|
+
return machines[idx - 1]
|
238
|
+
end
|
239
|
+
|
240
|
+
puts 'Invalid selection. Please try again.'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def pick_generation(machine_data, generation_input)
|
245
|
+
gens = machine_data['generations'] || []
|
246
|
+
return if gens.empty?
|
247
|
+
|
248
|
+
unless generation_input.nil?
|
249
|
+
parsed = parse_generation_input(generation_input, gens)
|
250
|
+
|
251
|
+
if parsed.nil?
|
252
|
+
warn "Requested generation '#{generation_input}' not found (or invalid)."
|
253
|
+
return
|
254
|
+
end
|
255
|
+
|
256
|
+
return parsed
|
257
|
+
end
|
258
|
+
|
259
|
+
if @interactive
|
260
|
+
loop do
|
261
|
+
current = gens.find { |x| x['current'] == true }
|
262
|
+
default_label = if current
|
263
|
+
"(default is #{current['generation']} - current)"
|
264
|
+
else
|
265
|
+
"(no current labeled, default is newest: #{gens[0]['generation']})"
|
266
|
+
end
|
267
|
+
|
268
|
+
puts "Available generations for #{machine_data['fqdn']}: #{default_label}"
|
269
|
+
list_generations(gens)
|
270
|
+
print 'Enter generation number (or negative offset) [ENTER for default]: '
|
271
|
+
input = $stdin.gets
|
272
|
+
return if input.nil?
|
273
|
+
|
274
|
+
line = input.strip
|
275
|
+
return current || gens[0] if line == ''
|
276
|
+
|
277
|
+
parsed = parse_generation_input(line, gens)
|
278
|
+
return parsed if parsed
|
279
|
+
|
280
|
+
puts "Invalid generation '#{line}'. Please try again."
|
281
|
+
end
|
282
|
+
else
|
283
|
+
current = gens.find { |x| x['current'] == true }
|
284
|
+
current || gens[0]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def list_generations(gens)
|
289
|
+
format_str = "%5s %-19s %-10s %s\n"
|
290
|
+
|
291
|
+
puts format(format_str, '', 'TIME', 'REVISION', 'KERNEL')
|
292
|
+
|
293
|
+
gens.each do |g|
|
294
|
+
current_mark = g['current'] ? '*' : ''
|
295
|
+
|
296
|
+
line = format(
|
297
|
+
format_str,
|
298
|
+
"[#{g['generation']}]" + current_mark,
|
299
|
+
g['time_s'],
|
300
|
+
g['shortrev'],
|
301
|
+
g['kernel_version']
|
302
|
+
)
|
303
|
+
puts line
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def parse_generation_input(input_str, gens)
|
308
|
+
begin
|
309
|
+
val = Integer(input_str)
|
310
|
+
rescue ArgumentError
|
311
|
+
return
|
312
|
+
end
|
313
|
+
|
314
|
+
if val >= 0
|
315
|
+
gens.find { |g| g['generation'] == val }
|
316
|
+
else
|
317
|
+
offset = -val
|
318
|
+
return if offset >= gens.size
|
319
|
+
|
320
|
+
gens[offset]
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def pick_variant(generation_data, desired_variant_name)
|
325
|
+
variants = generation_data['variants'] || []
|
326
|
+
return if variants.empty?
|
327
|
+
|
328
|
+
if desired_variant_name.nil? && @interactive
|
329
|
+
interactive_pick_variant(generation_data)
|
330
|
+
elsif desired_variant_name
|
331
|
+
v = variants.find { |x| x['name'] == desired_variant_name }
|
332
|
+
|
333
|
+
if v.nil?
|
334
|
+
warn "Requested variant '#{desired_variant_name}' not found in generation #{generation_data['generation']}."
|
335
|
+
return
|
336
|
+
end
|
337
|
+
|
338
|
+
v
|
339
|
+
else
|
340
|
+
variants.first
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def interactive_pick_variant(generation_data)
|
345
|
+
variants = generation_data['variants']
|
346
|
+
|
347
|
+
loop do
|
348
|
+
puts "Variants available for generation #{generation_data['generation']}:"
|
349
|
+
format_str = "%5s %s\n"
|
350
|
+
|
351
|
+
puts format(format_str, '', 'LABEL')
|
352
|
+
|
353
|
+
variants.each_with_index do |v, idx|
|
354
|
+
line = format(format_str, "[#{idx + 1}]", v['label'])
|
355
|
+
puts line
|
356
|
+
end
|
357
|
+
|
358
|
+
print "Choose variant by number [ENTER for '#{variants[0]['name']}']: "
|
359
|
+
|
360
|
+
input = $stdin.gets
|
361
|
+
return if input.nil?
|
362
|
+
|
363
|
+
line = input.strip
|
364
|
+
return variants[0] if line == ''
|
365
|
+
|
366
|
+
idx = line.to_i
|
367
|
+
return variants[idx - 1] if idx.between?(1, variants.size)
|
368
|
+
|
369
|
+
puts 'Invalid selection. Please try again.'
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def download_file(url)
|
374
|
+
uri = URI.parse(url)
|
375
|
+
basename = File.basename(uri.path)
|
376
|
+
tmp_file = Tempfile.new(basename)
|
377
|
+
|
378
|
+
puts "Downloading #{url} -> #{tmp_file.path}"
|
379
|
+
|
380
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
381
|
+
resp = http.get(uri.request_uri)
|
382
|
+
|
383
|
+
unless resp.is_a?(Net::HTTPSuccess)
|
384
|
+
warn "ERROR: Could not download #{url}, HTTP #{resp.code}"
|
385
|
+
exit 1
|
386
|
+
end
|
387
|
+
|
388
|
+
tmp_file.write(resp.body)
|
389
|
+
end
|
390
|
+
|
391
|
+
tmp_file.close
|
392
|
+
|
393
|
+
@tmp_files << tmp_file
|
394
|
+
tmp_file.path
|
395
|
+
end
|
396
|
+
|
397
|
+
def load_kexec(kernel_path, initrd_path, kernel_params_string)
|
398
|
+
cmd = [
|
399
|
+
KEXEC,
|
400
|
+
'-l',
|
401
|
+
kernel_path,
|
402
|
+
"--initrd=#{initrd_path}",
|
403
|
+
"--append=\"#{kernel_params_string}\""
|
404
|
+
].join(' ')
|
405
|
+
|
406
|
+
puts "Executing: #{cmd}"
|
407
|
+
system(cmd)
|
408
|
+
|
409
|
+
if $?.exitstatus == 0
|
410
|
+
puts 'kexec -l completed successfully.'
|
411
|
+
else
|
412
|
+
warn 'ERROR: kexec -l failed!'
|
413
|
+
exit 1
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def exec_kexec
|
418
|
+
cmd = [KEXEC, '-e'].join(' ')
|
419
|
+
|
420
|
+
puts "Executing: #{cmd}"
|
421
|
+
system(cmd)
|
422
|
+
|
423
|
+
if $?.exitstatus == 0
|
424
|
+
puts 'kexec -e completed successfully.'
|
425
|
+
else
|
426
|
+
warn 'ERROR: kexec -u failed!'
|
427
|
+
exit 1
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def unload_kexec
|
432
|
+
cmd = [KEXEC, '-u'].join(' ')
|
433
|
+
|
434
|
+
puts "Executing: #{cmd}"
|
435
|
+
system(cmd)
|
436
|
+
|
437
|
+
if $?.exitstatus == 0
|
438
|
+
puts 'kexec -u completed successfully.'
|
439
|
+
else
|
440
|
+
warn 'ERROR: kexec -u failed!'
|
441
|
+
exit 1
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def cleanup_downloads
|
446
|
+
@tmp_files.each do |f|
|
447
|
+
f.unlink
|
448
|
+
rescue StandardError => e
|
449
|
+
warn "Warning: Could not remove tmp file #{f.path}: #{e}"
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
loader = KexecNetboot.new
|
455
|
+
loader.run
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{ config, ... }:
|
2
|
+
{
|
3
|
+
nixpkgs.overlays = [
|
4
|
+
(self: super: {
|
5
|
+
# nixos-unstable removed pkgs.substituteAll, but nixos-25.05 does not
|
6
|
+
# include the new function pkgs.replaceVarsWith
|
7
|
+
confReplaceVarsWith =
|
8
|
+
{ replacements, ... } @ args:
|
9
|
+
if builtins.hasAttr "replaceVarsWith" self then
|
10
|
+
self.replaceVarsWith args
|
11
|
+
else
|
12
|
+
self.substituteAll ((builtins.removeAttrs args [ "replacements" ]) // replacements);
|
13
|
+
})
|
14
|
+
];
|
15
|
+
}
|
data/nix/modules/module-list.nix
CHANGED
data/nix/modules/system-list.nix
CHANGED
data/shell.nix
CHANGED
@@ -19,12 +19,19 @@ in stdenv.mkDerivation rec {
|
|
19
19
|
export GEM_HOME="$(pwd)/.gems"
|
20
20
|
BINDIR="$(ruby -e 'puts Gem.bindir')"
|
21
21
|
mkdir -p "$BINDIR"
|
22
|
+
|
22
23
|
export PATH="$BINDIR:$PATH"
|
23
24
|
export RUBYLIB="$GEM_HOME:$CONFCTL/lib"
|
24
25
|
export MANPATH="$CONFCTL/man:$(man --path)"
|
25
|
-
gem install --no-document bundler
|
26
|
+
gem install --no-document bundler
|
26
27
|
pushd "$CONFCTL"
|
27
|
-
|
28
|
+
|
29
|
+
# Purity disabled because of prism gem, which has a native extension.
|
30
|
+
# The extension has its header files in .gems, which gets stripped but
|
31
|
+
# cc wrapper in Nix. Without NIX_ENFORCE_PURITY=0, we get prism.h not found
|
32
|
+
# error.
|
33
|
+
NIX_ENFORCE_PURITY=0 bundle install
|
34
|
+
|
28
35
|
bundle exec rake md2man:man
|
29
36
|
popd
|
30
37
|
|
metadata
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: confctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub Skokan
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
10
|
date: 1980-01-01 00:00:00.000000000 Z
|
@@ -285,6 +284,7 @@ files:
|
|
285
284
|
- lib/confctl/nix_copy.rb
|
286
285
|
- lib/confctl/nix_format.rb
|
287
286
|
- lib/confctl/nix_literal_expression.rb
|
287
|
+
- lib/confctl/null_logger.rb
|
288
288
|
- lib/confctl/parallel_executor.rb
|
289
289
|
- lib/confctl/pattern.rb
|
290
290
|
- lib/confctl/settings.rb
|
@@ -307,6 +307,7 @@ files:
|
|
307
307
|
- lib/confctl/user_scripts.rb
|
308
308
|
- lib/confctl/utils/file.rb
|
309
309
|
- lib/confctl/version.rb
|
310
|
+
- libexec/auto-rollback.rb
|
310
311
|
- man/man8/confctl-options.nix.8
|
311
312
|
- man/man8/confctl-options.nix.8.md
|
312
313
|
- man/man8/confctl.8
|
@@ -325,18 +326,20 @@ files:
|
|
325
326
|
- nix/modules/confctl/carrier/netboot/nixos.nix
|
326
327
|
- nix/modules/confctl/cli.nix
|
327
328
|
- nix/modules/confctl/generations.nix
|
329
|
+
- nix/modules/confctl/kexec-netboot/default.nix
|
330
|
+
- nix/modules/confctl/kexec-netboot/kexec-netboot.8.adoc
|
331
|
+
- nix/modules/confctl/kexec-netboot/kexec-netboot.rb
|
328
332
|
- nix/modules/confctl/nix.nix
|
333
|
+
- nix/modules/confctl/overlays.nix
|
329
334
|
- nix/modules/confctl/swpins.nix
|
330
335
|
- nix/modules/module-list.nix
|
331
336
|
- nix/modules/system-list.nix
|
332
337
|
- shell.nix
|
333
338
|
- template/confctl-options.nix/main.erb
|
334
339
|
- template/confctl-options.nix/options.erb
|
335
|
-
homepage:
|
336
340
|
licenses:
|
337
341
|
- GPL-3.0-only
|
338
342
|
metadata: {}
|
339
|
-
post_install_message:
|
340
343
|
rdoc_options: []
|
341
344
|
require_paths:
|
342
345
|
- lib
|
@@ -351,8 +354,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
351
354
|
- !ruby/object:Gem::Version
|
352
355
|
version: '0'
|
353
356
|
requirements: []
|
354
|
-
rubygems_version: 3.
|
355
|
-
signing_key:
|
357
|
+
rubygems_version: 3.6.6
|
356
358
|
specification_version: 4
|
357
359
|
summary: Nix deployment management tool
|
358
360
|
test_files: []
|