confctl 2.2.3 → 3.0.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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.git-hooks/pre_commit/nixfmt.rb +13 -0
  3. data/.github/workflows/rspec.yml +64 -0
  4. data/.github/workflows/rubocop.yml +27 -0
  5. data/.github/workflows/tests.yml +139 -0
  6. data/.gitignore +12 -7
  7. data/.overcommit.yml +2 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +8 -1
  10. data/AGENTS.md +42 -0
  11. data/CHANGELOG.md +54 -0
  12. data/Gemfile +3 -2
  13. data/Gemfile.lock +198 -0
  14. data/README.md +166 -58
  15. data/Rakefile +5 -0
  16. data/confctl.gemspec +20 -2
  17. data/docs/carrier.md +3 -3
  18. data/docs/flake-inputs.md +159 -0
  19. data/docs/swpins-to-flakes.md +325 -0
  20. data/example/cluster/module-list.nix +2 -1
  21. data/example/cluster/nixos-machine/config.nix +12 -2
  22. data/example/cluster/nixos-machine/hardware.nix +6 -1
  23. data/example/cluster/vpsadminos-container/config.nix +6 -1
  24. data/example/cluster/vpsadminos-container/module.nix +4 -1
  25. data/example/cluster/vpsadminos-machine/config.nix +6 -1
  26. data/example/cluster/vpsadminos-machine/hardware.nix +6 -1
  27. data/example/cluster/vpsadminos-machine/module.nix +5 -2
  28. data/example/cluster/vpsfreecz-vps/config.nix +6 -1
  29. data/example/cluster/vpsfreecz-vps/module.nix +4 -1
  30. data/example/configs/swpins.nix +8 -3
  31. data/example/environments/base.nix +6 -1
  32. data/example/swpins/core.json +35 -0
  33. data/example-flake/.gitignore +2 -0
  34. data/example-flake/README.md +38 -0
  35. data/example-flake/cluster/cluster.nix +5 -0
  36. data/example-flake/cluster/module-list.nix +4 -0
  37. data/example-flake/cluster/nested/nixos-machine/config.nix +25 -0
  38. data/example-flake/cluster/nested/nixos-machine/hardware.nix +9 -0
  39. data/example-flake/cluster/nested/nixos-machine/module.nix +8 -0
  40. data/example-flake/cluster/nixos-machine/config.nix +25 -0
  41. data/example-flake/cluster/nixos-machine/hardware.nix +9 -0
  42. data/example-flake/cluster/nixos-machine/module.nix +8 -0
  43. data/example-flake/cluster/vpsadminos-container/config.nix +28 -0
  44. data/example-flake/cluster/vpsadminos-container/module.nix +8 -0
  45. data/example-flake/cluster/vpsadminos-machine/config.nix +27 -0
  46. data/example-flake/cluster/vpsadminos-machine/hardware.nix +9 -0
  47. data/example-flake/cluster/vpsadminos-machine/module.nix +8 -0
  48. data/example-flake/cluster/vpsfreecz-vps/config.nix +31 -0
  49. data/example-flake/cluster/vpsfreecz-vps/module.nix +8 -0
  50. data/example-flake/configs/confctl.nix +10 -0
  51. data/example-flake/data/default.nix +5 -0
  52. data/example-flake/data/ssh-keys.nix +7 -0
  53. data/example-flake/environments/base.nix +18 -0
  54. data/example-flake/flake.lock +75 -0
  55. data/example-flake/flake.nix +36 -0
  56. data/example-flake/modules/module-list.nix +13 -0
  57. data/example-flake/shell.nix +11 -0
  58. data/flake.lock +159 -0
  59. data/flake.nix +189 -0
  60. data/gemset.nix +1022 -0
  61. data/lib/confctl/cli/app.rb +145 -0
  62. data/lib/confctl/cli/attr_filters.rb +1 -1
  63. data/lib/confctl/cli/cluster.rb +681 -108
  64. data/lib/confctl/cli/command.rb +24 -2
  65. data/lib/confctl/cli/configuration.rb +171 -106
  66. data/lib/confctl/cli/generation.rb +65 -1
  67. data/lib/confctl/cli/inputs/channels.rb +190 -0
  68. data/lib/confctl/cli/inputs/machines.rb +83 -0
  69. data/lib/confctl/cli/inputs/root.rb +101 -0
  70. data/lib/confctl/cli/inputs.rb +5 -0
  71. data/lib/confctl/cli/log_view.rb +21 -10
  72. data/lib/confctl/cli/migrate/swpins_to_flakes.rb +866 -0
  73. data/lib/confctl/cli/migrate.rb +5 -0
  74. data/lib/confctl/cli/output_formatter.rb +5 -5
  75. data/lib/confctl/cli/swpins/base.rb +9 -0
  76. data/lib/confctl/cli/swpins/channel.rb +2 -5
  77. data/lib/confctl/cli/swpins/cluster.rb +2 -5
  78. data/lib/confctl/cli/swpins/core.rb +2 -5
  79. data/lib/confctl/config_type.rb +7 -0
  80. data/lib/confctl/flake_lock.rb +78 -0
  81. data/lib/confctl/flake_lock_diff.rb +36 -0
  82. data/lib/confctl/generation/build.rb +131 -24
  83. data/lib/confctl/generation/build_list.rb +4 -3
  84. data/lib/confctl/generation/unified.rb +14 -1
  85. data/lib/confctl/git_repo_mirror.rb +2 -2
  86. data/lib/confctl/health_checks/run_command.rb +3 -2
  87. data/lib/confctl/health_checks/systemd/properties.rb +1 -1
  88. data/lib/confctl/health_checks/systemd/property_list.rb +2 -2
  89. data/lib/confctl/inputs/commit_message.rb +125 -0
  90. data/lib/confctl/inputs/git_commit.rb +17 -0
  91. data/lib/confctl/inputs/nix_output_guard.rb +37 -0
  92. data/lib/confctl/inputs/setter.rb +179 -0
  93. data/lib/confctl/inputs/updater.rb +76 -0
  94. data/lib/confctl/inputs.rb +5 -0
  95. data/lib/confctl/inputs_info.rb +50 -0
  96. data/lib/confctl/line_buffer.rb +1 -1
  97. data/lib/confctl/machine.rb +18 -3
  98. data/lib/confctl/machine_control.rb +8 -2
  99. data/lib/confctl/machine_list.rb +2 -2
  100. data/lib/confctl/machine_status.rb +63 -20
  101. data/lib/confctl/nix/args.rb +48 -0
  102. data/lib/confctl/nix.rb +30 -437
  103. data/lib/confctl/nix_build_flake.rb +95 -0
  104. data/lib/confctl/nix_copy.rb +14 -2
  105. data/lib/confctl/nix_flake.rb +449 -0
  106. data/lib/confctl/nix_format.rb +3 -3
  107. data/lib/confctl/nix_legacy.rb +467 -0
  108. data/lib/confctl/swpins/change_set.rb +24 -2
  109. data/lib/confctl/swpins/specs/base.rb +2 -2
  110. data/lib/confctl/ui.rb +19 -0
  111. data/lib/confctl/version.rb +1 -1
  112. data/man/index.html +11 -0
  113. data/man/man8/confctl-options.nix.8.html +113 -0
  114. data/man/man8/confctl.8 +152 -21
  115. data/man/man8/confctl.8.html +355 -0
  116. data/man/man8/confctl.8.md +148 -17
  117. data/man/style.css +301 -0
  118. data/nix/evaluator.nix +94 -69
  119. data/nix/flake/mk-confctl-devshell.nix +85 -0
  120. data/nix/flake/mk-confctl-outputs.nix +522 -0
  121. data/nix/flake/mk-config-devshell.nix +168 -0
  122. data/nix/lib/default.nix +118 -65
  123. data/nix/lib/machine/default.nix +65 -45
  124. data/nix/lib/machine/info.nix +16 -5
  125. data/nix/lib/swpins/eval.nix +42 -29
  126. data/nix/lib/swpins/options.nix +6 -2
  127. data/nix/machines.nix +23 -15
  128. data/nix/modules/cluster/default.nix +104 -41
  129. data/nix/modules/confctl/carrier/base.nix +11 -4
  130. data/nix/modules/confctl/carrier/carrier-env.rb +2 -2
  131. data/nix/modules/confctl/carrier/netboot/build-netboot-server.rb +50 -13
  132. data/nix/modules/confctl/carrier/netboot/nixos.nix +56 -24
  133. data/nix/modules/confctl/configuration-info.nix +17 -0
  134. data/nix/modules/confctl/generations.nix +2 -2
  135. data/nix/modules/confctl/host.nix +13 -0
  136. data/nix/modules/confctl/inputs-info.nix +21 -0
  137. data/nix/modules/confctl/kexec-netboot/default.nix +13 -6
  138. data/nix/modules/confctl/kexec-netboot/kexec-netboot.8.adoc +3 -0
  139. data/nix/modules/confctl/kexec-netboot/kexec-netboot.rb +25 -16
  140. data/nix/modules/confctl/nix.nix +31 -1
  141. data/nix/modules/confctl/swpins.nix +13 -6
  142. data/nix/modules/module-list.nix +4 -2
  143. data/nix/modules/system-list.nix +4 -1
  144. data/nix/package.nix +37 -0
  145. data/shell.nix +39 -8
  146. data/skills/confctl-configuration-update/SKILL.md +228 -0
  147. data/skills/confctl-configuration-update/agents/openai.yaml +4 -0
  148. data/skills/confctl-release/SKILL.md +102 -0
  149. data/skills/confctl-release/agents/openai.yaml +4 -0
  150. data/spec/confctl/cli/cluster_health_check_spec.rb +43 -0
  151. data/spec/confctl/cli/cluster_skip_current_deploy_spec.rb +157 -0
  152. data/spec/confctl/cli/cluster_status_flake_spec.rb +83 -0
  153. data/spec/confctl/cli/inputs_set_output_spec.rb +70 -0
  154. data/spec/confctl/configuration_spec.rb +45 -0
  155. data/spec/confctl/inputs_spec.rb +104 -0
  156. data/spec/confctl/machine_control_spec.rb +112 -0
  157. data/spec/confctl/machine_list_spec.rb +47 -0
  158. data/spec/confctl/nix_flake_spec.rb +41 -0
  159. data/spec/confctl/swpins_spec.rb +106 -0
  160. data/spec/generation/build_modes_spec.rb +132 -0
  161. data/spec/inputs/commit_message_spec.rb +205 -0
  162. data/spec/inputs/nix_cached_fallback_spec.rb +50 -0
  163. data/spec/inputs/nix_output_guard_spec.rb +45 -0
  164. data/spec/spec_helper.rb +12 -0
  165. data/spec/support/cli_helper.rb +57 -0
  166. data/test-runner.sh +7 -0
  167. data/tests/all-tests.nix +30 -0
  168. data/tests/make-test.nix +15 -0
  169. data/tests/runner/extensions/confctl_helpers.rb +238 -0
  170. data/tests/runner/extensions/hostfwd_ports.rb +41 -0
  171. data/tests/suite/auto_rollback.nix +344 -0
  172. data/tests/suite/carrier/deploy.nix +751 -0
  173. data/tests/suite/carrier/netboot.nix +849 -0
  174. data/tests/suite/deploy/base.nix +806 -0
  175. data/tests/suite/deploy/flakes.nix +1 -0
  176. data/tests/suite/deploy/swpins.nix +1 -0
  177. metadata +104 -4
  178. data/nix/modules/confctl/overlays.nix +0 -15
@@ -0,0 +1,467 @@
1
+ require 'confctl/utils/file'
2
+ require 'digest'
3
+ require 'etc'
4
+ require 'json'
5
+ require 'securerandom'
6
+ require 'shellwords'
7
+ require 'tempfile'
8
+
9
+ module ConfCtl
10
+ class NixLegacy
11
+ # Create a new instance without access to {ConfCtl::Settings}, i.e. when
12
+ # called outside of cluster configuration directory.
13
+ # @return [Nix]
14
+ def self.stateless(show_trace: false, max_jobs: 'auto')
15
+ new(show_trace:, max_jobs:)
16
+ end
17
+
18
+ include Utils::File
19
+
20
+ def initialize(conf_dir: nil, show_trace: false, max_jobs: nil, cores: nil)
21
+ @conf_dir = conf_dir || ConfDir.path
22
+ @show_trace = show_trace
23
+ @max_jobs = max_jobs || Settings.instance.max_jobs
24
+ @cores = cores
25
+ @cmd = SystemCommand.new
26
+ end
27
+
28
+ def confctl_settings
29
+ out_link = File.join(
30
+ cache_dir,
31
+ 'build',
32
+ 'settings.json'
33
+ )
34
+
35
+ with_cache(out_link) do
36
+ with_argument({
37
+ confDir: conf_dir,
38
+ build: :confctl
39
+ }) do |arg|
40
+ cmd_args = [
41
+ 'nix-build',
42
+ '--arg', 'jsonArg', arg,
43
+ '--out-link', out_link,
44
+ (show_trace ? '--show-trace' : nil),
45
+ (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
46
+ (cores ? ['--cores', cores.to_s] : nil),
47
+ ConfCtl.nix_asset('evaluator.nix')
48
+ ].flatten.compact
49
+
50
+ cmd.run(*cmd_args)
51
+ end
52
+ end
53
+
54
+ demodulify(JSON.parse(File.read(out_link))['confctl'])
55
+ end
56
+
57
+ # Returns an array with options from all confctl modules
58
+ # @return [Array]
59
+ def module_options
60
+ options = nix_instantiate({
61
+ confDir: conf_dir,
62
+ build: :moduleOptions
63
+ })
64
+ end
65
+
66
+ # Returns an array with machine fqdns
67
+ # @return [Array<String>]
68
+ def list_machine_fqdns
69
+ nix_instantiate({
70
+ confDir: conf_dir,
71
+ build: :list
72
+ })['machines']
73
+ end
74
+
75
+ # Return machines and their config in a hash
76
+ # @return [Hash]
77
+ def list_machines
78
+ out_link = File.join(
79
+ cache_dir,
80
+ 'build',
81
+ 'machine-list.json'
82
+ )
83
+
84
+ with_cache(out_link) do
85
+ with_argument({
86
+ confDir: conf_dir,
87
+ build: :info
88
+ }, core_swpins: true) do |arg|
89
+ cmd_args = [
90
+ 'nix-build',
91
+ '--arg', 'jsonArg', arg,
92
+ '--out-link', out_link,
93
+ (show_trace ? '--show-trace' : nil),
94
+ (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
95
+ (cores ? ['--cores', cores.to_s] : nil),
96
+ ConfCtl.nix_asset('evaluator.nix')
97
+ ].flatten.compact
98
+
99
+ cmd.run(*cmd_args)
100
+ end
101
+ end
102
+
103
+ demodulify(JSON.parse(File.read(out_link)))
104
+ end
105
+
106
+ def list_swpins_channels
107
+ nix_instantiate({
108
+ confDir: conf_dir,
109
+ build: :listSwpinsChannels
110
+ })
111
+ end
112
+
113
+ # Evaluate swpins for host
114
+ # @return [Hash]
115
+ def eval_core_swpins
116
+ with_argument({
117
+ confDir: conf_dir,
118
+ build: :evalCoreSwpins
119
+ }) do |arg|
120
+ out_link = File.join(
121
+ cache_dir,
122
+ 'build',
123
+ 'core.swpins'
124
+ )
125
+
126
+ cmd_args = [
127
+ 'nix-build',
128
+ '--arg', 'jsonArg', arg,
129
+ '--out-link', out_link,
130
+ (show_trace ? '--show-trace' : nil),
131
+ (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
132
+ (cores ? ['--cores', cores.to_s] : nil),
133
+ ConfCtl.nix_asset('evaluator.nix')
134
+ ].flatten.compact
135
+
136
+ out, = cmd.run(*cmd_args).stdout
137
+
138
+ JSON.parse(File.read(out.strip))
139
+ end
140
+ end
141
+
142
+ # Evaluate swpins for hosts
143
+ # @param hosts [Array<String>]
144
+ # @return [Hash] host => swpins
145
+ def eval_host_swpins(hosts)
146
+ with_argument({
147
+ confDir: conf_dir,
148
+ build: :evalHostSwpins,
149
+ machines: hosts
150
+ }, core_swpins: true) do |arg|
151
+ out_link = File.join(
152
+ cache_dir,
153
+ 'build',
154
+ "#{Digest::SHA256.hexdigest(hosts.join(','))[0..11]}.swpins"
155
+ )
156
+
157
+ cmd_args = [
158
+ 'nix-build',
159
+ '--arg', 'jsonArg', arg,
160
+ '--out-link', out_link,
161
+ (show_trace ? '--show-trace' : nil),
162
+ (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
163
+ (cores ? ['--cores', cores.to_s] : nil),
164
+ ConfCtl.nix_asset('evaluator.nix')
165
+ ].flatten.compact
166
+
167
+ out, = cmd.run(*cmd_args)
168
+
169
+ JSON.parse(File.read(out.strip))
170
+ end
171
+ end
172
+
173
+ def eval_inputs_info(_host)
174
+ raise Error, 'inputs info is available only in flake configs'
175
+ end
176
+
177
+ def eval_inputs(_host)
178
+ raise Error, 'inputs are available only in flake configs'
179
+ end
180
+
181
+ def eval_json(_installable)
182
+ raise Error, 'nix eval is available only in flake configs'
183
+ end
184
+
185
+ # Build config.system.build.toplevel for selected hosts
186
+ #
187
+ # @param hosts [Array<Machine>]
188
+ # @param swpin_paths [Hash]
189
+ # @param host_swpin_specs [Hash]
190
+ # @param time [Time]
191
+ #
192
+ # @yieldparam type [:build, :fetch]
193
+ # @yieldparam progress [Integer]
194
+ # @yieldparam total [Integer]
195
+ # @yieldparam path [String]
196
+ #
197
+ # @return [Hash<String, Generation::Build>]
198
+ def build_attributes(hosts: [], swpin_paths: {}, host_swpin_specs: {}, time: nil, &block)
199
+ with_argument({
200
+ confDir: conf_dir,
201
+ build: :toplevel,
202
+ machines: hosts
203
+ }) do |arg|
204
+ time ||= Time.now
205
+ ret_generations = {}
206
+ out_link = File.join(cache_dir, 'build', "#{SecureRandom.hex(4)}.build")
207
+
208
+ cmd_args = [
209
+ '--arg', 'jsonArg', arg,
210
+ '--out-link', out_link,
211
+ (show_trace ? '--show-trace' : nil),
212
+ (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
213
+ (cores ? ['--cores', cores.to_s] : nil),
214
+ ConfCtl.nix_asset('evaluator.nix')
215
+ ].flatten.compact
216
+
217
+ nb = NixBuild.new(cmd_args, swpin_paths)
218
+ nb.run(&block)
219
+
220
+ begin
221
+ host_results = JSON.parse(File.read(out_link))
222
+ host_results.each do |host, result|
223
+ host_generations = Generation::BuildList.new(host)
224
+ generation = host_generations.find(result['attribute'], swpin_paths)
225
+
226
+ if generation.nil?
227
+ generation = Generation::Build.new(host)
228
+ generation.create(
229
+ result['attribute'],
230
+ result['autoRollback'],
231
+ swpin_paths,
232
+ host_swpin_specs[host],
233
+ date: time
234
+ )
235
+ generation.save
236
+ end
237
+
238
+ host_generations.current = generation
239
+ ret_generations[host] = generation
240
+ end
241
+ ensure
242
+ unlink_if_exists(out_link)
243
+ end
244
+
245
+ ret_generations
246
+ end
247
+ end
248
+
249
+ # @param machine [Machine]
250
+ # @param paths [Array<String>]
251
+ #
252
+ # @yieldparam progress [Integer]
253
+ # @yieldparam total [Integer]
254
+ # @yieldparam path [String]
255
+ #
256
+ # @return [Boolean]
257
+ def copy(machine, paths, &)
258
+ if machine.localhost?
259
+ true
260
+ elsif machine.carried?
261
+ carrier = machine.carrier_machine
262
+ cp = NixCopy.new(carrier.target_host, paths, port: carrier.target_port)
263
+ cp.run!(&).success?
264
+ else
265
+ cp = NixCopy.new(machine.target_host, paths, port: machine.target_port)
266
+ cp.run!(&).success?
267
+ end
268
+ end
269
+
270
+ # @param machine [Machine]
271
+ # @param generation [Generation::Build]
272
+ # @param action [String]
273
+ # @return [Boolean]
274
+ def activate(machine, generation, action)
275
+ args = [File.join(generation.toplevel, 'bin/switch-to-configuration'), action]
276
+
277
+ MachineControl.new(machine).execute!(*args).success?
278
+ end
279
+
280
+ # @param machine [Machine]
281
+ # @param generation [Generation::Build]
282
+ # @param action [String]
283
+ # @return [Boolean]
284
+ def activate_with_rollback(machine, generation, action)
285
+ check_file = File.join('/run', "confctl-confirm-#{SecureRandom.hex(3)}")
286
+ timeout = machine['autoRollback']['timeout']
287
+ logger = NullLogger.new
288
+
289
+ args = [
290
+ generation.auto_rollback,
291
+ '-t', timeout,
292
+ generation.toplevel,
293
+ action,
294
+ check_file
295
+ ]
296
+
297
+ activation_success = nil
298
+
299
+ activation_thread = Thread.new do
300
+ activation_success = MachineControl.new(machine).execute!(*args).success?
301
+ end
302
+
303
+ # Wait for the configuration to be switched
304
+ t = Time.now
305
+ mc = MachineControl.new(machine, logger:)
306
+
307
+ loop do
308
+ out, = mc.execute!('sh', '-c', "cat #{Shellwords.escape(check_file)} 2>/dev/null")
309
+ stripped = out.strip
310
+ break if stripped == 'switched' || ((t + timeout + 10) < Time.now && stripped != 'switching')
311
+
312
+ sleep(1)
313
+ end
314
+
315
+ # Confirm it
316
+ confirm_cmd = ['sh', '-c', "echo confirmed > #{Shellwords.escape(check_file)}"]
317
+
318
+ 10.times do
319
+ break if mc.execute!(*confirm_cmd).success?
320
+ end
321
+
322
+ activation_thread.join
323
+ activation_success
324
+ end
325
+
326
+ # @param machine [Machine]
327
+ # @param toplevel [String]
328
+ # @return [Boolean]
329
+ def set_profile(machine, toplevel)
330
+ args = [
331
+ 'nix-env',
332
+ '-p', machine.profile,
333
+ '--set', toplevel
334
+ ]
335
+
336
+ MachineControl.new(machine).execute!(*args).success?
337
+ end
338
+
339
+ # @param machine [Machine]
340
+ # @param toplevel [String]
341
+ # @return [Boolean]
342
+ def set_carried_profile(machine, toplevel)
343
+ args = [
344
+ 'carrier-env',
345
+ '-p', machine.profile,
346
+ '--set', toplevel
347
+ ]
348
+
349
+ MachineControl.new(machine.carrier_machine).execute!(*args).success?
350
+ end
351
+
352
+ # @param packages [Array<String>]
353
+ # @param command [String]
354
+ # @return [Boolean]
355
+ def run_command_in_shell(packages: [], command: nil)
356
+ args = ['nix-shell']
357
+
358
+ if packages.any?
359
+ args << '-p'
360
+ args.concat(packages)
361
+ end
362
+
363
+ args << '--command'
364
+ args << command
365
+
366
+ cmd.run!(*args, env: { 'shellHook' => nil }).success?
367
+ end
368
+
369
+ # @param machine [Machine]
370
+ # @yieldparam progress [NixCollectGarbage::Progress]
371
+ # @return [Boolean]
372
+ def collect_garbage(machine, &)
373
+ gc = NixCollectGarbage.new(machine)
374
+ gc.run!(&).success?
375
+ end
376
+
377
+ protected
378
+
379
+ attr_reader :conf_dir, :show_trace, :max_jobs, :cores, :cmd
380
+
381
+ # Execute block only if `out_link` does not exist or conf dir has changed
382
+ # @param out_link [String] out link path
383
+ def with_cache(out_link)
384
+ unchanged = false
385
+
386
+ if File.exist?(out_link)
387
+ unchanged = ConfDir.unchanged?
388
+
389
+ if unchanged
390
+ cache_mtime = ConfDir.state_mtime
391
+
392
+ if cache_mtime
393
+ begin
394
+ st = File.lstat(out_link)
395
+ rescue Errno::ENOENT
396
+ # pass
397
+ else
398
+ if st.mtime > cache_mtime
399
+ Logger.instance << "Using #{out_link}\n"
400
+ return
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
406
+
407
+ Logger.instance << "Building #{out_link}\n"
408
+
409
+ unless unchanged
410
+ Logger.instance << "Updating configuration cache\n"
411
+ ConfDir.update_state
412
+ end
413
+ yield
414
+ end
415
+
416
+ # @param hash [Hash]
417
+ # @param core_swpins [Boolean]
418
+ # @yieldparam file [String]
419
+ def with_argument(hash, core_swpins: false)
420
+ if core_swpins
421
+ paths = Swpins::Core.get.pre_evaluated_store_paths
422
+ hash[:coreSwpins] = paths if paths
423
+ end
424
+
425
+ f = Tempfile.new('confctl')
426
+ f.puts(hash.to_json)
427
+ f.close
428
+ yield(f.path)
429
+ ensure
430
+ f.unlink
431
+ end
432
+
433
+ def nix_instantiate(hash, **opts)
434
+ with_argument(hash, **opts) do |arg|
435
+ cmd_args = [
436
+ 'nix-instantiate',
437
+ '--eval',
438
+ '--json',
439
+ '--strict',
440
+ '--read-write-mode',
441
+ '--arg', 'jsonArg', arg,
442
+ (show_trace ? '--show-trace' : nil),
443
+ (max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
444
+ (cores ? ['--cores', cores.to_s] : nil),
445
+ ConfCtl.nix_asset('evaluator.nix')
446
+ ].flatten.compact
447
+
448
+ out, = cmd.run(*cmd_args).stdout
449
+
450
+ demodulify(JSON.parse(out))
451
+ end
452
+ end
453
+
454
+ def demodulify(value)
455
+ if value.is_a?(Array)
456
+ value.each { |item| demodulify(item) }
457
+ elsif value.is_a?(Hash)
458
+ value.delete('_module')
459
+ value.each_value { |v| demodulify(v) }
460
+ end
461
+ end
462
+
463
+ def cache_dir
464
+ ConfDir.cache_dir
465
+ end
466
+ end
467
+ end
@@ -1,10 +1,12 @@
1
+ require 'pathname'
2
+
1
3
  module ConfCtl
2
4
  class Swpins::ChangeSet
3
5
  class SpecOwner
4
6
  attr_reader :name, :path
5
7
  end
6
8
 
7
- SpecSet = Struct.new(:spec, :original_version, :original_info, keyword_init: true) do
9
+ SpecSet = Struct.new(:spec, :original_version, :original_info) do
8
10
  def name
9
11
  spec.name
10
12
  end
@@ -52,6 +54,16 @@ module ConfCtl
52
54
  def commit(type: :upgrade, changelog: false, editor: true)
53
55
  return if @owners.empty?
54
56
 
57
+ git_add = [
58
+ 'git',
59
+ 'add',
60
+ *changed_files
61
+ ]
62
+
63
+ unless Kernel.system(*git_add)
64
+ raise 'git add exited with non-zero status code'
65
+ end
66
+
55
67
  git_commit = [
56
68
  'git',
57
69
  'commit',
@@ -135,7 +147,17 @@ module ConfCtl
135
147
  end
136
148
 
137
149
  def changed_files
138
- @owners.each_key.map(&:path)
150
+ root_path = Pathname.new(ConfCtl::ConfDir.path)
151
+
152
+ @owners.each_key.map do |owner|
153
+ owner_path = Pathname.new(owner.path)
154
+
155
+ if owner_path.absolute?
156
+ owner_path.relative_path_from(root_path).to_s
157
+ else
158
+ owner.path
159
+ end
160
+ end
139
161
  end
140
162
 
141
163
  def same_changes?
@@ -129,8 +129,8 @@ module ConfCtl
129
129
  ret
130
130
  end
131
131
 
132
- def to_json(*args)
133
- as_json.to_json(*args)
132
+ def to_json(*)
133
+ as_json.to_json(*)
134
134
  end
135
135
 
136
136
  protected
data/lib/confctl/ui.rb ADDED
@@ -0,0 +1,19 @@
1
+ module ConfCtl
2
+ module Ui
3
+ module_function
4
+
5
+ # Return true when interactive TTY UI features should be enabled.
6
+ #
7
+ # Set CONFCTL_TTY=0 to force non-interactive output.
8
+ def tty?
9
+ return false if ENV['CONFCTL_TTY'] == '0'
10
+
11
+ $stdout.tty? && $stderr.tty?
12
+ end
13
+
14
+ def no_color?
15
+ v = ENV.fetch('NO_COLOR', nil)
16
+ !v.nil? && !v.strip.empty?
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module ConfCtl
2
- VERSION = '2.2.3'.freeze
2
+ VERSION = '3.0.0'.freeze
3
3
  end
data/man/index.html ADDED
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="generator" content="md2man 5.1.2 https://github.com/sunaku/md2man" />
6
+ <title>man/index &mdash; release-source</title>
7
+ <link rel="stylesheet" href="style.css"/>
8
+ <!--[if lt IE 9]><script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
9
+ </head>
10
+ <body><div class="container-fluid"><h2 id="man8">man8</h2><dl class="dl-horizontal"><dt><a href="man8/confctl.8.html">confctl(8)</a></dt><dd>Nix deployment management tool</dd></dl><dl class="dl-horizontal"><dt><a href="man8/confctl-options.nix.8.html">confctl-options(nix.8)</a></dt><dd>confctl configuration documentation</dd></dl></div></body>
11
+ </html>