confctl 1.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.
- checksums.yaml +7 -0
- data/.editorconfig +11 -0
- data/.gitignore +8 -0
- data/.overcommit.yml +6 -0
- data/.rubocop.yml +67 -0
- data/.rubocop_todo.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +674 -0
- data/README.md +522 -0
- data/Rakefile +40 -0
- data/bin/confctl +4 -0
- data/confctl.gemspec +33 -0
- data/example/.gitignore +2 -0
- data/example/README.md +38 -0
- data/example/cluster/cluster.nix +7 -0
- data/example/cluster/module-list.nix +3 -0
- data/example/cluster/nixos-machine/config.nix +15 -0
- data/example/cluster/nixos-machine/hardware.nix +4 -0
- data/example/cluster/nixos-machine/module.nix +8 -0
- data/example/cluster/vpsadminos-container/config.nix +22 -0
- data/example/cluster/vpsadminos-container/module.nix +8 -0
- data/example/cluster/vpsadminos-machine/config.nix +22 -0
- data/example/cluster/vpsadminos-machine/hardware.nix +4 -0
- data/example/cluster/vpsadminos-machine/module.nix +8 -0
- data/example/cluster/vpsfreecz-vps/config.nix +25 -0
- data/example/cluster/vpsfreecz-vps/module.nix +8 -0
- data/example/configs/confctl.nix +10 -0
- data/example/configs/swpins.nix +28 -0
- data/example/data/default.nix +5 -0
- data/example/data/ssh-keys.nix +7 -0
- data/example/environments/base.nix +13 -0
- data/example/modules/module-list.nix +13 -0
- data/example/shell.nix +11 -0
- data/example/swpins/channels/nixos-unstable.json +35 -0
- data/example/swpins/channels/vpsadminos-staging.json +35 -0
- data/lib/confctl/cli/app.rb +551 -0
- data/lib/confctl/cli/attr_filters.rb +51 -0
- data/lib/confctl/cli/cluster.rb +1248 -0
- data/lib/confctl/cli/command.rb +206 -0
- data/lib/confctl/cli/configuration.rb +296 -0
- data/lib/confctl/cli/gen_data.rb +97 -0
- data/lib/confctl/cli/generation.rb +335 -0
- data/lib/confctl/cli/log_view.rb +267 -0
- data/lib/confctl/cli/output_formatter.rb +288 -0
- data/lib/confctl/cli/swpins/base.rb +40 -0
- data/lib/confctl/cli/swpins/channel.rb +73 -0
- data/lib/confctl/cli/swpins/cluster.rb +80 -0
- data/lib/confctl/cli/swpins/core.rb +86 -0
- data/lib/confctl/cli/swpins/utils.rb +55 -0
- data/lib/confctl/cli/swpins.rb +5 -0
- data/lib/confctl/cli/tag_filters.rb +30 -0
- data/lib/confctl/cli.rb +5 -0
- data/lib/confctl/conf_cache.rb +105 -0
- data/lib/confctl/conf_dir.rb +88 -0
- data/lib/confctl/erb_template.rb +37 -0
- data/lib/confctl/exceptions.rb +3 -0
- data/lib/confctl/gcroot.rb +30 -0
- data/lib/confctl/generation/build.rb +145 -0
- data/lib/confctl/generation/build_list.rb +106 -0
- data/lib/confctl/generation/host.rb +35 -0
- data/lib/confctl/generation/host_list.rb +81 -0
- data/lib/confctl/generation/unified.rb +117 -0
- data/lib/confctl/generation/unified_list.rb +63 -0
- data/lib/confctl/git_repo_mirror.rb +79 -0
- data/lib/confctl/health_checks/base.rb +66 -0
- data/lib/confctl/health_checks/run_command.rb +179 -0
- data/lib/confctl/health_checks/systemd/properties.rb +84 -0
- data/lib/confctl/health_checks/systemd/property_check.rb +31 -0
- data/lib/confctl/health_checks/systemd/property_list.rb +20 -0
- data/lib/confctl/health_checks.rb +5 -0
- data/lib/confctl/hook.rb +35 -0
- data/lib/confctl/line_buffer.rb +53 -0
- data/lib/confctl/logger.rb +151 -0
- data/lib/confctl/machine.rb +107 -0
- data/lib/confctl/machine_control.rb +172 -0
- data/lib/confctl/machine_list.rb +108 -0
- data/lib/confctl/machine_status.rb +135 -0
- data/lib/confctl/module_options.rb +95 -0
- data/lib/confctl/nix.rb +382 -0
- data/lib/confctl/nix_build.rb +108 -0
- data/lib/confctl/nix_collect_garbage.rb +64 -0
- data/lib/confctl/nix_copy.rb +49 -0
- data/lib/confctl/nix_format.rb +124 -0
- data/lib/confctl/nix_literal_expression.rb +15 -0
- data/lib/confctl/parallel_executor.rb +43 -0
- data/lib/confctl/pattern.rb +9 -0
- data/lib/confctl/settings.rb +50 -0
- data/lib/confctl/std_line_buffer.rb +40 -0
- data/lib/confctl/swpins/change_set.rb +151 -0
- data/lib/confctl/swpins/channel.rb +62 -0
- data/lib/confctl/swpins/channel_list.rb +47 -0
- data/lib/confctl/swpins/cluster_name.rb +94 -0
- data/lib/confctl/swpins/cluster_name_list.rb +15 -0
- data/lib/confctl/swpins/core.rb +137 -0
- data/lib/confctl/swpins/deployed_info.rb +23 -0
- data/lib/confctl/swpins/spec.rb +20 -0
- data/lib/confctl/swpins/specs/base.rb +184 -0
- data/lib/confctl/swpins/specs/directory.rb +51 -0
- data/lib/confctl/swpins/specs/git.rb +135 -0
- data/lib/confctl/swpins/specs/git_rev.rb +24 -0
- data/lib/confctl/swpins.rb +17 -0
- data/lib/confctl/system_command.rb +10 -0
- data/lib/confctl/user_script.rb +13 -0
- data/lib/confctl/user_scripts.rb +41 -0
- data/lib/confctl/utils/file.rb +21 -0
- data/lib/confctl/version.rb +3 -0
- data/lib/confctl.rb +43 -0
- data/man/man8/confctl-options.nix.8 +1334 -0
- data/man/man8/confctl-options.nix.8.md +1340 -0
- data/man/man8/confctl.8 +660 -0
- data/man/man8/confctl.8.md +654 -0
- data/nix/evaluator.nix +160 -0
- data/nix/lib/default.nix +83 -0
- data/nix/lib/machine/default.nix +74 -0
- data/nix/lib/machine/info.nix +5 -0
- data/nix/lib/swpins/eval.nix +71 -0
- data/nix/lib/swpins/options.nix +94 -0
- data/nix/machines.nix +31 -0
- data/nix/modules/cluster/default.nix +459 -0
- data/nix/modules/confctl/cli.nix +21 -0
- data/nix/modules/confctl/generations.nix +84 -0
- data/nix/modules/confctl/nix.nix +28 -0
- data/nix/modules/confctl/swpins.nix +55 -0
- data/nix/modules/module-list.nix +19 -0
- data/shell.nix +42 -0
- data/template/confctl-options.nix/main.erb +45 -0
- data/template/confctl-options.nix/options.erb +15 -0
- metadata +353 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'cgi'
|
|
2
|
+
|
|
3
|
+
module ConfCtl
|
|
4
|
+
class ModuleOptions
|
|
5
|
+
class Option
|
|
6
|
+
attr_reader :name, :description, :type, :default, :example, :declarations
|
|
7
|
+
|
|
8
|
+
def initialize(nixos_opt)
|
|
9
|
+
@name = nixos_opt['name']
|
|
10
|
+
@description = nixos_opt['description'] || 'This option has no description.'
|
|
11
|
+
@type = nixos_opt['type']
|
|
12
|
+
@default = extract_expression(nixos_opt['default'])
|
|
13
|
+
@example = extract_expression(nixos_opt['example'])
|
|
14
|
+
@declarations = nixos_opt['declarations'].map do |v|
|
|
15
|
+
raise "unable to place module '#{v}'" unless %r{/confctl/([^$]+)} =~ v
|
|
16
|
+
|
|
17
|
+
"<confctl/#{::Regexp.last_match(1)}>"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def md_description
|
|
22
|
+
tagless = description
|
|
23
|
+
.gsub(%r{<literal>([^<]+)</literal>}, '`\1`')
|
|
24
|
+
.gsub(%r{<option>([^<]+)</option>}, '`\1`')
|
|
25
|
+
|
|
26
|
+
CGI.unescapeHTML(tagless)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def nix_default
|
|
30
|
+
nixify(default)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def nix_example
|
|
34
|
+
example && nixify(example)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
def extract_expression(v)
|
|
40
|
+
if v.is_a?(Hash)
|
|
41
|
+
case v['_type']
|
|
42
|
+
when 'literalExpression'
|
|
43
|
+
NixLiteralExpression.new(v['text'])
|
|
44
|
+
else
|
|
45
|
+
raise "Unsupported expression type #{v['_type'].inspect}"
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
v
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def nixify(v)
|
|
53
|
+
ConfCtl::NixFormat.to_nix(v)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Array<ModuleOptions::Option>]
|
|
58
|
+
attr_reader :options
|
|
59
|
+
|
|
60
|
+
# @param nix [Nix, nil]
|
|
61
|
+
def initialize(nix: nil)
|
|
62
|
+
@nix = nix || Nix.new
|
|
63
|
+
@options = []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def read
|
|
67
|
+
@options = nix.module_options.map do |opt|
|
|
68
|
+
Option.new(opt)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def confctl_settings
|
|
73
|
+
options.select do |opt|
|
|
74
|
+
opt.name.start_with?('confctl.') \
|
|
75
|
+
&& !opt.name.start_with?('confctl.swpins.')
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def swpin_settings
|
|
80
|
+
options.select { |opt| opt.name.start_with?('confctl.swpins.') }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def machine_settings
|
|
84
|
+
options.select { |opt| opt.name.start_with?('cluster.') }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def service_settings
|
|
88
|
+
options.select { |opt| opt.name.start_with?('services.') }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
protected
|
|
92
|
+
|
|
93
|
+
attr_reader :nix
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/confctl/nix.rb
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
require 'confctl/utils/file'
|
|
2
|
+
require 'etc'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require 'tempfile'
|
|
6
|
+
|
|
7
|
+
module ConfCtl
|
|
8
|
+
class Nix
|
|
9
|
+
# Create a new instance without access to {ConfCtl::Settings}, i.e. when
|
|
10
|
+
# called outside of cluster configuration directory.
|
|
11
|
+
# @return [Nix]
|
|
12
|
+
def self.stateless(show_trace: false, max_jobs: 'auto')
|
|
13
|
+
new(show_trace:, max_jobs:)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
include Utils::File
|
|
17
|
+
|
|
18
|
+
def initialize(conf_dir: nil, show_trace: false, max_jobs: nil)
|
|
19
|
+
@conf_dir = conf_dir || ConfDir.path
|
|
20
|
+
@show_trace = show_trace
|
|
21
|
+
@max_jobs = max_jobs || Settings.instance.max_jobs
|
|
22
|
+
@cmd = SystemCommand.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def confctl_settings
|
|
26
|
+
out_link = File.join(
|
|
27
|
+
cache_dir,
|
|
28
|
+
'build',
|
|
29
|
+
'settings.json'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
with_cache(out_link) do
|
|
33
|
+
with_argument({
|
|
34
|
+
confDir: conf_dir,
|
|
35
|
+
build: :confctl
|
|
36
|
+
}) do |arg|
|
|
37
|
+
cmd_args = [
|
|
38
|
+
'nix-build',
|
|
39
|
+
'--arg', 'jsonArg', arg,
|
|
40
|
+
'--out-link', out_link,
|
|
41
|
+
(show_trace ? '--show-trace' : nil),
|
|
42
|
+
(max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
|
|
43
|
+
ConfCtl.nix_asset('evaluator.nix')
|
|
44
|
+
].flatten.compact
|
|
45
|
+
|
|
46
|
+
cmd.run(*cmd_args)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
demodulify(JSON.parse(File.read(out_link))['confctl'])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns an array with options from all confctl modules
|
|
54
|
+
# @return [Array]
|
|
55
|
+
def module_options
|
|
56
|
+
options = nix_instantiate({
|
|
57
|
+
confDir: conf_dir,
|
|
58
|
+
build: :moduleOptions
|
|
59
|
+
})
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns an array with machine fqdns
|
|
63
|
+
# @return [Array<String>]
|
|
64
|
+
def list_machine_fqdns
|
|
65
|
+
nix_instantiate({
|
|
66
|
+
confDir: conf_dir,
|
|
67
|
+
build: :list
|
|
68
|
+
})['machines']
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Return machines and their config in a hash
|
|
72
|
+
# @return [Hash]
|
|
73
|
+
def list_machines
|
|
74
|
+
out_link = File.join(
|
|
75
|
+
cache_dir,
|
|
76
|
+
'build',
|
|
77
|
+
'machine-list.json'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
with_cache(out_link) do
|
|
81
|
+
with_argument({
|
|
82
|
+
confDir: conf_dir,
|
|
83
|
+
build: :info
|
|
84
|
+
}, core_swpins: true) do |arg|
|
|
85
|
+
cmd_args = [
|
|
86
|
+
'nix-build',
|
|
87
|
+
'--arg', 'jsonArg', arg,
|
|
88
|
+
'--out-link', out_link,
|
|
89
|
+
(show_trace ? '--show-trace' : nil),
|
|
90
|
+
(max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
|
|
91
|
+
ConfCtl.nix_asset('evaluator.nix')
|
|
92
|
+
].flatten.compact
|
|
93
|
+
|
|
94
|
+
cmd.run(*cmd_args)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
demodulify(JSON.parse(File.read(out_link)))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def list_swpins_channels
|
|
102
|
+
nix_instantiate({
|
|
103
|
+
confDir: conf_dir,
|
|
104
|
+
build: :listSwpinsChannels
|
|
105
|
+
})
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Evaluate swpins for host
|
|
109
|
+
# @return [Hash]
|
|
110
|
+
def eval_core_swpins
|
|
111
|
+
with_argument({
|
|
112
|
+
confDir: conf_dir,
|
|
113
|
+
build: :evalCoreSwpins
|
|
114
|
+
}) do |arg|
|
|
115
|
+
out_link = File.join(
|
|
116
|
+
cache_dir,
|
|
117
|
+
'build',
|
|
118
|
+
'core.swpins'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
cmd_args = [
|
|
122
|
+
'nix-build',
|
|
123
|
+
'--arg', 'jsonArg', arg,
|
|
124
|
+
'--out-link', out_link,
|
|
125
|
+
(show_trace ? '--show-trace' : nil),
|
|
126
|
+
(max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
|
|
127
|
+
ConfCtl.nix_asset('evaluator.nix')
|
|
128
|
+
].flatten.compact
|
|
129
|
+
|
|
130
|
+
out, = cmd.run(*cmd_args).stdout
|
|
131
|
+
|
|
132
|
+
JSON.parse(File.read(out.strip))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Evaluate swpins for host
|
|
137
|
+
# @param host [String]
|
|
138
|
+
# @return [Hash]
|
|
139
|
+
def eval_host_swpins(host)
|
|
140
|
+
with_argument({
|
|
141
|
+
confDir: conf_dir,
|
|
142
|
+
build: :evalHostSwpins,
|
|
143
|
+
machines: [host]
|
|
144
|
+
}, core_swpins: true) do |arg|
|
|
145
|
+
out_link = File.join(
|
|
146
|
+
cache_dir,
|
|
147
|
+
'build',
|
|
148
|
+
"#{ConfCtl.safe_host_name(host)}.swpins"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
cmd_args = [
|
|
152
|
+
'nix-build',
|
|
153
|
+
'--arg', 'jsonArg', arg,
|
|
154
|
+
'--out-link', out_link,
|
|
155
|
+
(show_trace ? '--show-trace' : nil),
|
|
156
|
+
(max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
|
|
157
|
+
ConfCtl.nix_asset('evaluator.nix')
|
|
158
|
+
].flatten.compact
|
|
159
|
+
|
|
160
|
+
out, = cmd.run(*cmd_args)
|
|
161
|
+
|
|
162
|
+
JSON.parse(File.read(out.strip))[host]
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Build config.system.build.toplevel for selected hosts
|
|
167
|
+
#
|
|
168
|
+
# @param hosts [Array<String>]
|
|
169
|
+
# @param swpin_paths [Hash]
|
|
170
|
+
# @param host_swpin_specs [Hash]
|
|
171
|
+
# @param time [Time]
|
|
172
|
+
#
|
|
173
|
+
# @yieldparam type [:build, :fetch]
|
|
174
|
+
# @yieldparam progress [Integer]
|
|
175
|
+
# @yieldparam total [Integer]
|
|
176
|
+
# @yieldparam path [String]
|
|
177
|
+
#
|
|
178
|
+
# @return [Hash<String, Generation::Build>]
|
|
179
|
+
def build_toplevels(hosts: [], swpin_paths: {}, host_swpin_specs: {}, time: nil, &block)
|
|
180
|
+
with_argument({
|
|
181
|
+
confDir: conf_dir,
|
|
182
|
+
build: :toplevel,
|
|
183
|
+
machines: hosts
|
|
184
|
+
}) do |arg|
|
|
185
|
+
time ||= Time.now
|
|
186
|
+
ret_generations = {}
|
|
187
|
+
out_link = File.join(cache_dir, 'build', "#{SecureRandom.hex(4)}.build")
|
|
188
|
+
|
|
189
|
+
cmd_args = [
|
|
190
|
+
'--arg', 'jsonArg', arg,
|
|
191
|
+
'--out-link', out_link,
|
|
192
|
+
(show_trace ? '--show-trace' : nil),
|
|
193
|
+
(max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
|
|
194
|
+
ConfCtl.nix_asset('evaluator.nix')
|
|
195
|
+
].flatten.compact
|
|
196
|
+
|
|
197
|
+
nb = NixBuild.new(cmd_args, swpin_paths)
|
|
198
|
+
nb.run(&block)
|
|
199
|
+
|
|
200
|
+
begin
|
|
201
|
+
host_toplevels = JSON.parse(File.read(out_link))
|
|
202
|
+
host_toplevels.each do |host, toplevel|
|
|
203
|
+
host_generations = Generation::BuildList.new(host)
|
|
204
|
+
generation = host_generations.find(toplevel, swpin_paths)
|
|
205
|
+
|
|
206
|
+
if generation.nil?
|
|
207
|
+
generation = Generation::Build.new(host)
|
|
208
|
+
generation.create(
|
|
209
|
+
toplevel,
|
|
210
|
+
swpin_paths,
|
|
211
|
+
host_swpin_specs[host],
|
|
212
|
+
date: time
|
|
213
|
+
)
|
|
214
|
+
generation.save
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
host_generations.current = generation
|
|
218
|
+
ret_generations[host] = generation
|
|
219
|
+
end
|
|
220
|
+
ensure
|
|
221
|
+
unlink_if_exists(out_link)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
ret_generations
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# @param machine [Machine]
|
|
229
|
+
# @param toplevel [String]
|
|
230
|
+
#
|
|
231
|
+
# @yieldparam progress [Integer]
|
|
232
|
+
# @yieldparam total [Integer]
|
|
233
|
+
# @yieldparam path [String]
|
|
234
|
+
#
|
|
235
|
+
# @return [Boolean]
|
|
236
|
+
def copy(machine, toplevel, &)
|
|
237
|
+
if machine.localhost?
|
|
238
|
+
true
|
|
239
|
+
else
|
|
240
|
+
cp = NixCopy.new(machine.target_host, toplevel)
|
|
241
|
+
cp.run!(&).success?
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @param machine [Machine]
|
|
246
|
+
# @param toplevel [String]
|
|
247
|
+
# @param action [String]
|
|
248
|
+
# @return [Boolean]
|
|
249
|
+
def activate(machine, toplevel, action)
|
|
250
|
+
args = [File.join(toplevel, 'bin/switch-to-configuration'), action]
|
|
251
|
+
|
|
252
|
+
MachineControl.new(machine).execute!(*args).success?
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# @param machine [Machine]
|
|
256
|
+
# @param toplevel [String]
|
|
257
|
+
# @return [Boolean]
|
|
258
|
+
def set_profile(machine, toplevel)
|
|
259
|
+
args = [
|
|
260
|
+
'nix-env',
|
|
261
|
+
'-p', '/nix/var/nix/profiles/system',
|
|
262
|
+
'--set', toplevel
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
MachineControl.new(machine).execute!(*args).success?
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @param packages [Array<String>]
|
|
269
|
+
# @param command [String]
|
|
270
|
+
# @return [Boolean]
|
|
271
|
+
def run_command_in_shell(packages: [], command: nil)
|
|
272
|
+
args = ['nix-shell']
|
|
273
|
+
|
|
274
|
+
if packages.any?
|
|
275
|
+
args << '-p'
|
|
276
|
+
args.concat(packages)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
args << '--command'
|
|
280
|
+
args << command
|
|
281
|
+
|
|
282
|
+
cmd.run!(*args, env: { 'shellHook' => nil }).success?
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# @param machine [Machine]
|
|
286
|
+
# @yieldparam progress [NixCollectGarbage::Progress]
|
|
287
|
+
# @return [Boolean]
|
|
288
|
+
def collect_garbage(machine, &)
|
|
289
|
+
gc = NixCollectGarbage.new(machine)
|
|
290
|
+
gc.run!(&).success?
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
protected
|
|
294
|
+
|
|
295
|
+
attr_reader :conf_dir, :show_trace, :max_jobs, :cmd
|
|
296
|
+
|
|
297
|
+
# Execute block only if `out_link` does not exist or conf dir has changed
|
|
298
|
+
# @param out_link [String] out link path
|
|
299
|
+
def with_cache(out_link)
|
|
300
|
+
unchanged = false
|
|
301
|
+
|
|
302
|
+
if File.exist?(out_link)
|
|
303
|
+
unchanged = ConfDir.unchanged?
|
|
304
|
+
|
|
305
|
+
if unchanged
|
|
306
|
+
cache_mtime = ConfDir.state_mtime
|
|
307
|
+
|
|
308
|
+
if cache_mtime
|
|
309
|
+
begin
|
|
310
|
+
st = File.lstat(out_link)
|
|
311
|
+
rescue Errno::ENOENT
|
|
312
|
+
# pass
|
|
313
|
+
else
|
|
314
|
+
if st.mtime > cache_mtime
|
|
315
|
+
Logger.instance << "Using #{out_link}\n"
|
|
316
|
+
return
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
Logger.instance << "Building #{out_link}\n"
|
|
324
|
+
|
|
325
|
+
unless unchanged
|
|
326
|
+
Logger.instance << "Updating configuration cache\n"
|
|
327
|
+
ConfDir.update_state
|
|
328
|
+
end
|
|
329
|
+
yield
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# @param hash [Hash]
|
|
333
|
+
# @param core_swpins [Boolean]
|
|
334
|
+
# @yieldparam file [String]
|
|
335
|
+
def with_argument(hash, core_swpins: false)
|
|
336
|
+
if core_swpins
|
|
337
|
+
paths = Swpins::Core.get.pre_evaluated_store_paths
|
|
338
|
+
hash[:coreSwpins] = paths if paths
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
f = Tempfile.new('confctl')
|
|
342
|
+
f.puts(hash.to_json)
|
|
343
|
+
f.close
|
|
344
|
+
yield(f.path)
|
|
345
|
+
ensure
|
|
346
|
+
f.unlink
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def nix_instantiate(hash, **opts)
|
|
350
|
+
with_argument(hash, **opts) do |arg|
|
|
351
|
+
cmd_args = [
|
|
352
|
+
'nix-instantiate',
|
|
353
|
+
'--eval',
|
|
354
|
+
'--json',
|
|
355
|
+
'--strict',
|
|
356
|
+
'--read-write-mode',
|
|
357
|
+
'--arg', 'jsonArg', arg,
|
|
358
|
+
(show_trace ? '--show-trace' : nil),
|
|
359
|
+
(max_jobs ? ['--max-jobs', max_jobs.to_s] : nil),
|
|
360
|
+
ConfCtl.nix_asset('evaluator.nix')
|
|
361
|
+
].flatten.compact
|
|
362
|
+
|
|
363
|
+
out, = cmd.run(*cmd_args).stdout
|
|
364
|
+
|
|
365
|
+
demodulify(JSON.parse(out))
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def demodulify(value)
|
|
370
|
+
if value.is_a?(Array)
|
|
371
|
+
value.each { |item| demodulify(item) }
|
|
372
|
+
elsif value.is_a?(Hash)
|
|
373
|
+
value.delete('_module')
|
|
374
|
+
value.each_value { |v| demodulify(v) }
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def cache_dir
|
|
379
|
+
ConfDir.cache_dir
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module ConfCtl
|
|
2
|
+
class NixBuild
|
|
3
|
+
# @param args [Array<String>]
|
|
4
|
+
# @param swpin_paths [Hash<String>]
|
|
5
|
+
def initialize(args, swpin_paths)
|
|
6
|
+
@args = args
|
|
7
|
+
@swpin_paths = swpin_paths
|
|
8
|
+
@build_progress = 0
|
|
9
|
+
@build_total = 0
|
|
10
|
+
@fetch_progress = 0
|
|
11
|
+
@fetch_total = 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @yieldparam type [:build, :fetch]
|
|
15
|
+
# @yieldparam progress [Integer]
|
|
16
|
+
# @yieldparam total [Integer]
|
|
17
|
+
# @yieldparam path [String]
|
|
18
|
+
def run(&block)
|
|
19
|
+
cmd = SystemCommand.new
|
|
20
|
+
|
|
21
|
+
line_buf = StdLineBuffer.new do |_out, err|
|
|
22
|
+
parse_line(err, &block) if err && block
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ret = cmd.run(
|
|
26
|
+
'nix-build',
|
|
27
|
+
*args,
|
|
28
|
+
env: { 'NIX_PATH' => build_nix_path(swpin_paths) },
|
|
29
|
+
&line_buf.feed_block
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
line_buf.flush
|
|
33
|
+
ret
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
attr_reader :args, :swpin_paths
|
|
39
|
+
|
|
40
|
+
def parse_line(line)
|
|
41
|
+
# Beware that nix-build can fetch/build stuff even before those two
|
|
42
|
+
# summary lines are printed. Therefore we report progress with total=0
|
|
43
|
+
# (indeterminate) until the total becomes known.
|
|
44
|
+
|
|
45
|
+
# Nix >= around 2.11
|
|
46
|
+
case line
|
|
47
|
+
when /^this derivation will be built:/
|
|
48
|
+
@build_total = 1
|
|
49
|
+
return
|
|
50
|
+
when /^these (\d+) derivations will be built:/
|
|
51
|
+
@build_total = ::Regexp.last_match(1).to_i
|
|
52
|
+
return
|
|
53
|
+
when /^this path will be fetched /
|
|
54
|
+
@fetch_total = 1
|
|
55
|
+
return
|
|
56
|
+
when /^these (\d+) paths will be fetched /
|
|
57
|
+
@fetch_total = ::Regexp.last_match(1).to_i
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Nix < around 2.11, we can drop this in the future
|
|
62
|
+
if /^these derivations will be built:/ =~ line
|
|
63
|
+
@in_derivation_list = true
|
|
64
|
+
@in_fetch_list = false
|
|
65
|
+
return
|
|
66
|
+
elsif /^these paths will be fetched / =~ line
|
|
67
|
+
@in_derivation_list = false
|
|
68
|
+
@in_fetch_list = true
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if @in_derivation_list
|
|
73
|
+
if %r{^\s+/nix/store/} =~ line
|
|
74
|
+
@build_total += 1
|
|
75
|
+
return
|
|
76
|
+
else
|
|
77
|
+
@in_derivation_list = false
|
|
78
|
+
@build_total += @build_progress
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
elsif @in_fetch_list
|
|
82
|
+
if %r{^\s+/nix/store/} =~ line
|
|
83
|
+
@fetch_total += 1
|
|
84
|
+
return
|
|
85
|
+
else
|
|
86
|
+
@in_fetch_list = false
|
|
87
|
+
@fetch_total += @fetch_progress
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if /^building '([^']+)/ =~ line
|
|
92
|
+
@build_progress += 1
|
|
93
|
+
yield(:build, @build_progress, @build_total, ::Regexp.last_match(1))
|
|
94
|
+
elsif /^copying path '([^']+)/ =~ line
|
|
95
|
+
@fetch_progress += 1
|
|
96
|
+
yield(:fetch, @fetch_progress, @fetch_total, ::Regexp.last_match(1))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def build_nix_path(swpins)
|
|
101
|
+
paths = []
|
|
102
|
+
paths << "confctl=#{ConfCtl.root}"
|
|
103
|
+
paths.concat(swpins.map { |k, v| "#{k}=#{v}" })
|
|
104
|
+
paths.concat(Settings.instance.nix_paths)
|
|
105
|
+
paths.join(':')
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module ConfCtl
|
|
2
|
+
# Run nix-collect-garbage on {Machine}
|
|
3
|
+
class NixCollectGarbage
|
|
4
|
+
class Progress
|
|
5
|
+
def initialize(line)
|
|
6
|
+
@line = line
|
|
7
|
+
@path = false
|
|
8
|
+
parse
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def path?
|
|
12
|
+
!@path.nil?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [String, nil]
|
|
16
|
+
attr_reader :path
|
|
17
|
+
|
|
18
|
+
def to_s
|
|
19
|
+
@line
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def parse
|
|
25
|
+
return unless %r{^deleting '(/nix/store/[^']+)'$} =~ @line
|
|
26
|
+
|
|
27
|
+
@path = ::Regexp.last_match(1)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param machine [String]
|
|
32
|
+
def initialize(machine)
|
|
33
|
+
@machine = machine
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @yieldparam progress [Progress]
|
|
37
|
+
# @return [TTY::Command::Result]
|
|
38
|
+
def run!
|
|
39
|
+
mc = MachineControl.new(machine)
|
|
40
|
+
|
|
41
|
+
line_buf = StdLineBuffer.new do |out, err|
|
|
42
|
+
next unless block_given?
|
|
43
|
+
|
|
44
|
+
# %d store paths deleted, %f MiB freed
|
|
45
|
+
yield(Progress.new(out)) if out
|
|
46
|
+
|
|
47
|
+
# finding garbage collector roots...
|
|
48
|
+
# removing stale link from '...'
|
|
49
|
+
# deleting garbage...
|
|
50
|
+
# deleting '/nix/store/...'
|
|
51
|
+
# ...
|
|
52
|
+
yield(Progress.new(err)) if err
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
ret = mc.execute!('nix-collect-garbage', &line_buf.feed_block)
|
|
56
|
+
line_buf.flush
|
|
57
|
+
ret
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
protected
|
|
61
|
+
|
|
62
|
+
attr_reader :machine
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module ConfCtl
|
|
2
|
+
class NixCopy
|
|
3
|
+
# @param target [String]
|
|
4
|
+
# @param store_path [String]
|
|
5
|
+
def initialize(target, store_path)
|
|
6
|
+
@target = target
|
|
7
|
+
@store_path = store_path
|
|
8
|
+
@total = nil
|
|
9
|
+
@progress = 0
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @yieldparam progress [Integer]
|
|
13
|
+
# @yieldparam total [Integer]
|
|
14
|
+
# @yieldparam path [String]
|
|
15
|
+
def run!(&block)
|
|
16
|
+
cmd = SystemCommand.new
|
|
17
|
+
|
|
18
|
+
line_buf = StdLineBuffer.new do |_out, err|
|
|
19
|
+
parse_line(err, &block) if err && block
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ret = cmd.run!(
|
|
23
|
+
'nix-copy-closure',
|
|
24
|
+
'--to', "root@#{target}",
|
|
25
|
+
store_path,
|
|
26
|
+
&line_buf.feed_block
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
line_buf.flush
|
|
30
|
+
ret
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
attr_reader :target, :store_path
|
|
36
|
+
|
|
37
|
+
def parse_line(line)
|
|
38
|
+
if @total.nil? && /^copying (\d+) paths/ =~ line
|
|
39
|
+
@total = ::Regexp.last_match(1).to_i
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return unless /^copying path '([^']+)/ =~ line
|
|
44
|
+
|
|
45
|
+
@progress += 1
|
|
46
|
+
yield(@progress, @total, ::Regexp.last_match(1))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|