confctl 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 869735b23809f8bef14f7e1e39b57e7bcaf4a90a37c7e5f4c03064a526cc1c46
4
- data.tar.gz: 1badf4f41f9183cbc5c716d436d89b3ccae96519e2f37bf0b67c4ffe7fde89e7
3
+ metadata.gz: 602260a8486eef31801f7fcf2221f09164e4443e66ea94d6fe534f8f68c56fc2
4
+ data.tar.gz: 24b4b5dabe240f2e93e0a9784f9eda90bbf52cec2a82fab8e5c5644e323be8da
5
5
  SHA512:
6
- metadata.gz: 889dc492d0996711bff26a442532cb955dc5fcf5f25c281e25475b7a9e657f64270cc6c839e81b4a3880ab250c50e7a817c30bbfc6c8a37e0b5d7fb755bad212
7
- data.tar.gz: b894f7b1720684ad93af743e8fc55f221876c7665d7302682f8354865fdc9c54f17acb990d088981d6638d908624e4687e412dda141eede57556f03bf7f741d7
6
+ metadata.gz: 87f22fe62a9354b20e711fff429f0a34f6961f9c08489bd42f97ff1d8f9f350bc281e7eca069aecc25081ae71f2ea73f803aa06a0dc4c3d244adbfe10dbbaa86
7
+ data.tar.gz: 8f44626b3b0e9a97b37171cbc58e94a15b04d87f448110ff211bfa5bc34477d1ce9a0d9f344b19cab93ed8d23f657e44fab3d14c8bad71e62b5d65ea2d3d3d26
data/.editorconfig CHANGED
@@ -5,7 +5,7 @@ root = true
5
5
  charset = utf-8
6
6
  end_of_line = lf
7
7
 
8
- [*.{rb,erb,nix}]
8
+ [*.{rb,erb,md,nix}]
9
9
  indent_size = 2
10
10
  indent_style = space
11
11
  trim_trailing_whitespace = true
data/.gitignore CHANGED
@@ -5,4 +5,5 @@ man/*.html
5
5
  man/**/*.html
6
6
  man/man?/*.?
7
7
  html_doc
8
+ .gems
8
9
  .yardoc
data/.rubocop.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
+ TargetRubyVersion: 3.1.0
4
5
  NewCops: enable
5
6
  Exclude:
6
7
  - '*.rb'
data/CHANGELOG.md CHANGED
@@ -1,2 +1,19 @@
1
- # Sat Feb 17 2023 -- version 1.0.0
1
+ # Sun Nov 17 2024 -- version 2.0.0
2
+ - Distinguish machine `config` and `metaConfig` (breaking change)
3
+ - Support for machine carriers and netboot servers
4
+ - Optimized git fetch calls when updating software pins
5
+ - Added option `--cores` that is passed to nix-build
6
+ - Added RuboCop
7
+ - Bug fixes
8
+
9
+ ## Transition to `metaConfig`
10
+ The use of `config` has been ambiguous, it could either mean machine
11
+ configuration, i.e. the result of all configured NixOS/vpsAdminOS options,
12
+ or it could mean machine metadata from `module.nix`. Machine metadata
13
+ is now accessible as `metaConfig`.
14
+
15
+ - `confLib.findConfig` has been renamed to `confLib.findMetaConfig`
16
+ - `confLib.confLib.getClusterMachines` returns a list of machines with `metaConfig` attribute
17
+
18
+ # Sat Feb 17 2024 -- version 1.0.0
2
19
  - Initial release
data/README.md CHANGED
@@ -13,6 +13,7 @@ machines.
13
13
  configurations)
14
14
  * Query machine state, view changelogs and diffs
15
15
  * Run health checks
16
+ * Support for creating netboot servers, see [docs/carrier.md](docs/carrier.md)
16
17
 
17
18
  ## Requirements
18
19
 
@@ -40,19 +41,12 @@ stored:
40
41
  ```
41
42
  mkdir cluster-configuration
42
43
  ```
43
- 3. Prepare `shell.nix` in the new directory:
44
- - Create a `shell.nix` and import the same file from confctl:
44
+ 3. Create `shell.nix` and import the same file from confctl:
45
45
  ```
46
46
  cd cluster-configuration
47
47
  cat > shell.nix <<EOF
48
48
  import ../confctl/shell.nix
49
49
  EOF
50
- ```
51
-
52
- - Alternatively, you can symlink `shell.nix` from the confctl repository:
53
- ```
54
- cd cluster-configuration
55
- ln -s ../confctl/shell.nix shell.nix
56
50
  ```
57
51
 
58
52
  4. Enter the `nix-shell`. This will make confctl available and install its
@@ -489,7 +483,7 @@ Then you can use it in machine module as:
489
483
  Note that these modules are self-contained. They are not evaluated with the full
490
484
  set of NixOS modules. You have to import modules that you need.
491
485
 
492
- ## User-defined confctl commands
486
+ ## User-defined confctl commands
493
487
  User-defined Ruby scripts can be placed in directory `scripts`. Each script
494
488
  should create a subclass of `ConfCtl::UserScript` and call class-method `register`.
495
489
  Scripts can define their own `confctl` subcommands.
data/confctl.gemspec CHANGED
@@ -15,19 +15,19 @@ Gem::Specification.new do |s|
15
15
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
16
  s.license = 'GPL-3.0-only'
17
17
 
18
- s.required_ruby_version = ">= #{File.read('.ruby-version').strip}"
18
+ s.required_ruby_version = '>= 3.1.0'
19
19
 
20
- s.add_runtime_dependency 'curses'
21
- s.add_runtime_dependency 'gli', '~> 2.21.0'
22
- s.add_runtime_dependency 'json'
23
- s.add_runtime_dependency 'md2man'
24
- s.add_runtime_dependency 'rainbow', '~> 3.1.1'
25
- s.add_runtime_dependency 'rake'
26
- s.add_runtime_dependency 'require_all', '~> 2.0.0'
27
- s.add_runtime_dependency 'tty-command', '~> 0.10.1'
28
- s.add_runtime_dependency 'tty-cursor', '~> 0.7.1'
29
- s.add_runtime_dependency 'tty-pager', '~> 0.14.0'
30
- s.add_runtime_dependency 'tty-progressbar', '~> 0.18.2'
31
- s.add_runtime_dependency 'tty-spinner', '~> 0.9.3'
32
- s.add_runtime_dependency 'vpsfree-client', '~> 0.18.0'
20
+ s.add_dependency 'curses'
21
+ s.add_dependency 'gli', '~> 2.22.0'
22
+ s.add_dependency 'json'
23
+ s.add_dependency 'md2man'
24
+ s.add_dependency 'rainbow', '~> 3.1.1'
25
+ s.add_dependency 'rake'
26
+ s.add_dependency 'require_all', '~> 2.0.0'
27
+ s.add_dependency 'tty-command', '~> 0.10.1'
28
+ s.add_dependency 'tty-cursor', '~> 0.7.1'
29
+ s.add_dependency 'tty-pager', '~> 0.14.0'
30
+ s.add_dependency 'tty-progressbar', '~> 0.18.2'
31
+ s.add_dependency 'tty-spinner', '~> 0.9.3'
32
+ s.add_dependency 'vpsfree-client', '~> 0.19.0'
33
33
  end
data/docs/carrier.md ADDED
@@ -0,0 +1,138 @@
1
+ # confctl carriers
2
+ Carrier is a machine that can carry other machines. The concept was created
3
+ for the purpose of building netboot servers, but can be used in other settings
4
+ as well.
5
+
6
+ All machines must be defined within the cluster as usual. We can then designate
7
+ a machine that will serve as a carrier and provide a list of machines that it
8
+ will carry, e.g.:
9
+
10
+ ```nix
11
+ # File cluster/pxe-server/module.nix
12
+ cluster.pxe-server = {
13
+ # ...
14
+ carrier = {
15
+ enable = true;
16
+
17
+ # A list of machines found in the cluster/ directory that will be
18
+ # available on the netboot server. Note that you will have to create
19
+ # your own buildAttribute, so that the resulting path contains bzImage,
20
+ # initrd and possibly a machine.json file.
21
+ machines = [
22
+ {
23
+ machine = "node1";
24
+ buildAttribute = [ "system" "build" "dist" ];
25
+ }
26
+ ];
27
+ };
28
+ };
29
+ ```
30
+
31
+ Now the `node1` machine is available as two machines within the cluster:
32
+ `node1` and `pxe-server#node1`. We can build and deploy both:
33
+
34
+ * `confctl deploy node1` will deploy the target machine as defined by the configuration
35
+ * `confctl deploy pxe-server#node1` will build the machine and copy the result to `pxe-server`, its carrier
36
+
37
+ We need both commands to build a slightly different output, but based on the same
38
+ configuration. When deploying `node1`, confctl will build attribute
39
+ `config.system.build.toplevel`, where as deploying `pxe-server#node1` will build
40
+ attribute `config.system.build.dist`. This attribute is configured in `buildAttribute`
41
+ option in the example above. `config.system.build.dist` is defined within vpsAdminOS,
42
+ its output is a directory with kernel bzImage, initrd and the root filesystem
43
+ in a squashfs image, i.e. what we need for booting from network.
44
+
45
+ Custom build attributes can be created by the user. For example, this is how
46
+ `config.system.build.dist` would be defined for NixOS:
47
+
48
+ ```nix
49
+ # File cluster/nixos/config.nix
50
+ { config, pkgs, lib, confMachine, swpinsInfo, ... }:
51
+ let
52
+ # machine.json contains metadata about the machine that the carrier uses
53
+ # to assemble the netboot server
54
+ machineJson = pkgs.writeText "machine-${config.networking.hostName}.json" (builtins.toJSON {
55
+ # machine spin, nixos/vpsadminos
56
+ spin = "nixos";
57
+
58
+ # fully qualified domain name
59
+ fqdn = confMachine.host.fqdn;
60
+
61
+ # label used e.g. in user menus
62
+ label = confMachine.host.fqdn;
63
+
64
+ # path to the top-level derivation, needed for system boot
65
+ toplevel = builtins.unsafeDiscardStringContext config.system.build.toplevel;
66
+
67
+ # NixOS version and git revision
68
+ version = config.system.nixos.version;
69
+ revision = config.system.nixos.revision;
70
+
71
+ # MAC addresses for auto-detection
72
+ macs = confMachine.netboot.macs;
73
+
74
+ # Information used by confctl status
75
+ swpins-info = swpinsInfo;
76
+ });
77
+ in {
78
+ imports = [
79
+ <nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix>
80
+ ];
81
+
82
+ # Define custom build attribute
83
+ system.build.dist = pkgs.symlinkJoin {
84
+ name = "nixos-netboot";
85
+ paths = [
86
+ config.system.build.netbootRamdisk
87
+ config.system.build.kernel
88
+ config.system.build.netbootIpxeScript
89
+ ];
90
+
91
+ # Install machine.json
92
+ postBuild = ''
93
+ ln -s ${machineJson} $out/machine.json
94
+ '';
95
+ };
96
+
97
+ # other NixOS configuration
98
+ }
99
+ ```
100
+
101
+ The carrier must be configured to handle the carried machines. Netboot server
102
+ support is integrated within confctl and it only has to be enabled.
103
+
104
+ ```nix
105
+ # File cluster/pxe-server/config.nix
106
+ { config, ... }:
107
+ {
108
+ confctl.carrier.netboot = {
109
+ enable = true;
110
+
111
+ # IP address or hostname the netboot server will be available on
112
+ host = "192.168.100.5";
113
+
114
+ # IP ranges that will have access to the server
115
+ allowedIPv4Ranges = [
116
+ "192.168.100.0/24"
117
+ ];
118
+ };
119
+ }
120
+ ```
121
+
122
+ The netboot server is rebuilt whenever a machine is deployed to it.
123
+ The rebuild can be also run manually using command `build-netboot-server`.
124
+
125
+ ## Other uses
126
+ You can define your own commands to be run on the carrier machine when
127
+ images are deployed to it:
128
+
129
+ ```nix
130
+ # File cluster/pxe-server/config.nix
131
+ { config, ... }:
132
+ {
133
+ confctl.carrier.onChangeCommands = ''
134
+ # List profiles of carried machines
135
+ ls -l /nix/var/nix/profiles/confctl-*
136
+ '';
137
+ }
138
+ ```
@@ -546,6 +546,9 @@ module ConfCtl::Cli
546
546
  def nix_build_options(cmd)
547
547
  cmd.desc 'Maximum number of build jobs (see nix-build)'
548
548
  cmd.flag %w[j max-jobs], arg_name: 'number'
549
+
550
+ cmd.desc 'Number of CPU cores to be used (see nix-build)'
551
+ cmd.flag :cores, arg_name: 'number'
549
552
  end
550
553
  end
551
554
  end
@@ -110,7 +110,10 @@ module ConfCtl::Cli
110
110
  end
111
111
 
112
112
  def status
113
- machines = select_machines(args[0]).managed
113
+ machines = select_machines(args[0]).managed.select do |_host, machine|
114
+ machine.target_host || machine.carried?
115
+ end
116
+
114
117
  raise 'No machines to check' if machines.empty?
115
118
 
116
119
  ask_confirmation! do
@@ -244,7 +247,7 @@ module ConfCtl::Cli
244
247
  end
245
248
 
246
249
  def test_connection
247
- machines = select_machines_with_managed(args[0])
250
+ machines = select_machines_with_managed(args[0]).runnable
248
251
  raise 'No machines to test' if machines.empty?
249
252
 
250
253
  ask_confirmation! do
@@ -276,7 +279,7 @@ module ConfCtl::Cli
276
279
  end
277
280
 
278
281
  def ssh
279
- machines = select_machines_with_managed(args[0])
282
+ machines = select_machines_with_managed(args[0]).runnable
280
283
  raise 'No machines to ssh to' if machines.empty?
281
284
 
282
285
  if opts['input-string'] && opts['input-file']
@@ -295,7 +298,7 @@ module ConfCtl::Cli
295
298
  end
296
299
 
297
300
  def cssh
298
- machines = select_machines_with_managed(args[0])
301
+ machines = select_machines_with_managed(args[0]).runnable
299
302
  raise 'No machines to open cssh to' if machines.empty?
300
303
 
301
304
  ask_confirmation! do
@@ -508,38 +511,67 @@ module ConfCtl::Cli
508
511
  size: :auto,
509
512
  reserved_lines: 10
510
513
  ) do |lw|
511
- if opts['dry-activate-first']
512
- lw.sync_console do
513
- puts Rainbow(
514
- "Trying to activate configuration on #{host} " \
515
- "(#{machine.target_host})"
516
- ).yellow
517
- end
518
-
519
- raise "Error while activating configuration on #{host}" unless nix.activate(machine, toplevel, 'dry-activate')
514
+ if machine.carried?
515
+ deploy_carried_to_host(lw, nix, host, machine, toplevel, action)
516
+ else
517
+ deploy_standalone_to_host(lw, nix, host, machine, toplevel, action)
520
518
  end
521
519
 
520
+ lw.flush
521
+ end
522
+ end
523
+
524
+ def deploy_standalone_to_host(lw, nix, host, machine, toplevel, action)
525
+ if opts['dry-activate-first']
522
526
  lw.sync_console do
523
527
  puts Rainbow(
524
- "Activating configuration on #{host} (#{machine.target_host}): " \
525
- "#{action}"
528
+ "Trying to activate configuration on #{host} " \
529
+ "(#{machine.target_host})"
526
530
  ).yellow
527
531
  end
528
532
 
529
- return :skip if opts[:interactive] && !ask_confirmation(always: true)
533
+ raise "Error while activating configuration on #{host}" unless nix.activate(machine, toplevel, 'dry-activate')
534
+ end
530
535
 
531
- raise "Error while activating configuration on #{host}" unless nix.activate(machine, toplevel, action)
536
+ lw.sync_console do
537
+ puts Rainbow(
538
+ "Activating configuration on #{host} (#{machine.target_host}): " \
539
+ "#{action}"
540
+ ).yellow
541
+ end
532
542
 
533
- if %w[boot switch].include?(action) && !nix.set_profile(machine, toplevel)
534
- raise "Error while setting profile on #{host}"
535
- end
543
+ return :skip if opts[:interactive] && !ask_confirmation(always: true)
544
+
545
+ # rubocop:disable Style/GuardClause
546
+
547
+ unless nix.activate(machine, toplevel, action)
548
+ raise "Error while activating configuration on #{host}"
549
+ end
550
+
551
+ if %w[boot switch].include?(action) && !nix.set_profile(machine, toplevel)
552
+ raise "Error while setting profile on #{host}"
536
553
  end
554
+
555
+ # rubocop:enable Style/GuardClause
556
+ end
557
+
558
+ def deploy_carried_to_host(lw, nix, host, machine, toplevel, action)
559
+ return if action != 'switch'
560
+
561
+ # rubocop:disable Style/GuardClause
562
+ unless nix.set_carried_profile(machine, toplevel)
563
+ raise "Error while setting carried profile for #{host} on #{machine.carrier_machine}"
564
+ end
565
+ # rubocop:enable Style/GuardClause
537
566
  end
538
567
 
539
568
  def reboot_host(host, machine)
540
569
  if machine.localhost?
541
570
  puts Rainbow("Skipping reboot of #{host} as it is localhost").yellow
542
571
  return :skip
572
+ elsif machine.carried?
573
+ puts Rainbow("Skipping reboot of carried machine #{host}").yellow
574
+ return :skip
543
575
  end
544
576
 
545
577
  puts Rainbow("Rebooting #{host} (#{machine.target_host})").yellow
@@ -761,22 +793,16 @@ module ConfCtl::Cli
761
793
  machines.each do |host, machine|
762
794
  mc = ConfCtl::MachineControl.new(machine)
763
795
 
764
- begin
765
- puts "#{host}:" unless aggregate
796
+ puts "#{host}:" unless aggregate
766
797
 
767
- result = run_ssh_command_on_machine(mc, cmd)
798
+ result = run_ssh_command_on_machine(mc, cmd)
768
799
 
769
- if aggregate
770
- results[host] = result
771
- else
772
- puts result.out
773
- end
774
- rescue TTY::Command::ExitError => e
775
- if aggregate
776
- results[host] = e
777
- else
778
- puts e.message
779
- end
800
+ if aggregate
801
+ results[host] = result
802
+ elsif result.success?
803
+ puts result.out
804
+ else
805
+ puts result.err
780
806
  end
781
807
 
782
808
  puts unless aggregate
@@ -808,12 +834,8 @@ module ConfCtl::Cli
808
834
  tw.add do
809
835
  mc = ConfCtl::MachineControl.new(machine)
810
836
 
811
- begin
812
- result = run_ssh_command_on_machine(mc, cmd)
813
- results[host] = result
814
- rescue TTY::Command::ExitError => e
815
- results[host] = e
816
- end
837
+ result = run_ssh_command_on_machine(mc, cmd)
838
+ results[host] = result
817
839
 
818
840
  lw.sync_console { pb.advance }
819
841
  end
@@ -830,7 +852,13 @@ module ConfCtl::Cli
830
852
 
831
853
  results.each do |host, result|
832
854
  puts "#{host}:"
833
- puts result.out
855
+
856
+ if result.success?
857
+ puts result.out
858
+ else
859
+ puts result.err
860
+ end
861
+
834
862
  puts
835
863
  end
836
864
  end
@@ -844,7 +872,7 @@ module ConfCtl::Cli
844
872
  cmd_opts[:in] = opts['input-file']
845
873
  end
846
874
 
847
- mc.execute(*cmd, **cmd_opts)
875
+ mc.execute!(*cmd, **cmd_opts)
848
876
  end
849
877
 
850
878
  def process_aggregated_results(results)
@@ -903,7 +931,8 @@ module ConfCtl::Cli
903
931
  def do_build(machines)
904
932
  nix = ConfCtl::Nix.new(
905
933
  show_trace: opts['show-trace'],
906
- max_jobs: opts['max-jobs']
934
+ max_jobs: opts['max-jobs'],
935
+ cores: opts['cores']
907
936
  )
908
937
  hosts_swpin_paths = {}
909
938
 
@@ -996,7 +1025,7 @@ module ConfCtl::Cli
996
1025
  'Fetching [:bar] :current/:total (:percent)'
997
1026
  )
998
1027
 
999
- built_generations = nix.build_toplevels(
1028
+ built_generations = nix.build_attributes(
1000
1029
  hosts:,
1001
1030
  swpin_paths:,
1002
1031
  time:,
@@ -1,8 +1,11 @@
1
+ require_relative '../hook'
1
2
  require 'fileutils'
2
3
  require 'securerandom'
3
4
 
4
5
  module ConfCtl::Cli
5
6
  class Configuration < Command
7
+ ConfCtl::Hook.register :configuration_rediscover
8
+
6
9
  DIR_MODE = 0o755
7
10
  FILE_MODE = 0o644
8
11
 
@@ -238,6 +241,8 @@ module ConfCtl::Cli
238
241
 
239
242
  f.puts(']')
240
243
  end
244
+
245
+ ConfCtl::Hook.call(:configuration_rediscover)
241
246
  end
242
247
 
243
248
  protected
@@ -251,8 +256,8 @@ module ConfCtl::Cli
251
256
 
252
257
  entry_rel_path = File.join(*[rel_path, v].compact)
253
258
 
254
- if File.exist?(File.join(entry_abs_path, 'module.nix')) \
255
- && File.exist?(File.join(entry_abs_path, 'config.nix'))
259
+ if File.exist?(File.join(entry_abs_path, 'module.nix')) &&
260
+ File.exist?(File.join(entry_abs_path, 'config.nix'))
256
261
  ret << entry_rel_path
257
262
  end
258
263
 
@@ -78,10 +78,28 @@ module ConfCtl::Cli
78
78
  q.echo = false
79
79
  end.to_s
80
80
 
81
- @vpsadmin_client.authenticate(:basic, user:, password:)
81
+ @vpsadmin_client.authenticate(:token, user:, password:, lifetime: 'fixed', interval: 60) do |_action, params|
82
+ ret = {}
83
+
84
+ params.each do |name, desc|
85
+ ret[name] = read_auth_param(name, desc)
86
+ end
87
+
88
+ ret
89
+ end
90
+
82
91
  @vpsadmin_client
83
92
  end
84
93
 
94
+ def read_auth_param(name, p)
95
+ prompt = "#{p[:label] || name}: "
96
+
97
+ ask(prompt) do |q|
98
+ q.default = nil
99
+ q.echo = !p[:protected]
100
+ end
101
+ end
102
+
85
103
  def update_file(relpath, &)
86
104
  abs = File.join(data_dir, relpath)
87
105
  tmp = "#{abs}.new"
@@ -31,7 +31,7 @@ module ConfCtl::Cli
31
31
 
32
32
  return unless opts[:remote] && opts[:gc]
33
33
 
34
- machines_gc = machines.select do |host, _machine|
34
+ machines_gc = machines.runnable.select do |host, _machine|
35
35
  gens.detect { |gen| gen.host == host }
36
36
  end
37
37
 
@@ -68,7 +68,7 @@ module ConfCtl::Cli
68
68
 
69
69
  global = ConfCtl::Settings.instance.host_generations
70
70
 
71
- machines_gc = machines.select do |_host, machine|
71
+ machines_gc = machines.runnable.select do |_host, machine|
72
72
  gc = machine['buildGenerations']['collectGarbage']
73
73
 
74
74
  if gc.nil?
@@ -82,7 +82,7 @@ module ConfCtl::Cli
82
82
  end
83
83
 
84
84
  def collect_garbage
85
- machines = select_machines(args[0])
85
+ machines = select_machines(args[0]).runnable
86
86
 
87
87
  raise 'No machines to collect garbage on' if machines.empty?
88
88
 
@@ -295,6 +295,8 @@ module ConfCtl::Cli
295
295
  retvals = executor.run
296
296
  failed = retvals.compact
297
297
 
298
+ lw.flush
299
+
298
300
  raise "Gargabe collection failed on: #{failed.join(', ')}" if failed.any?
299
301
  end
300
302
  end
@@ -1,16 +1,16 @@
1
1
  module ConfCtl
2
2
  class Generation::HostList
3
3
  # @param mc [MachineControl]
4
+ # @param profile [String]
4
5
  # @return [Generation::HostList]
5
- def self.fetch(mc, profile: '/nix/var/nix/profiles/system')
6
- out, = mc.bash_script(<<-END
6
+ def self.fetch(mc, profile:)
7
+ out, = mc.bash_script(<<~END)
7
8
  realpath #{profile}
8
9
 
9
10
  for generation in `ls -d -1 #{profile}-*-link` ; do
10
11
  echo "$generation;$(readlink $generation);$(stat --format=%Y $generation)"
11
12
  done
12
13
  END
13
- )
14
14
 
15
15
  list = new(mc.machine.name)
16
16
  lines = out.strip.split("\n")
@@ -14,13 +14,13 @@ module ConfCtl
14
14
  @cmd = SystemCommand.new
15
15
  end
16
16
 
17
- def setup
17
+ def setup(ref: nil)
18
18
  File.stat(mirror_path)
19
19
  rescue Errno::ENOENT
20
20
  FileUtils.mkdir_p(mirror_path)
21
21
  git('clone', args: ['--mirror', url, mirror_path])
22
22
  else
23
- git_repo('fetch')
23
+ git_repo('fetch', opts: ['--no-show-forced-updates'], args: (ref ? ['origin', ref] : []))
24
24
  end
25
25
 
26
26
  def revision_parse(str)