Class: OsCtld::DistConfig::Distributions::Base

Inherits:
Object
  • Object
show all
Includes:
OsCtl::Lib::Utils::Log, OsCtl::Lib::Utils::System, Utils::SwitchUser
Defined in:
lib/osctld/dist_config/distributions/base.rb

Direct Known Subclasses

Arch, Debian, Gentoo, Guix, NixOS, OpenSuse, Other, RedHat, Slackware, Void

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::SwitchUser

#ct_attach, #ct_syscmd

Constructor Details

#initialize(ctrc) ⇒ Base

Returns a new instance of Base.

Parameters:



21
22
23
24
25
26
# File 'lib/osctld/dist_config/distributions/base.rb', line 21

def initialize(ctrc)
  @ctrc = ctrc
  @ct = ctrc.ct
  @distribution = ctrc.distribution
  @version = ctrc.version
end

Instance Attribute Details

#configurator ⇒ Object (readonly, protected)

Returns the value of attribute configurator.



263
264
265
# File 'lib/osctld/dist_config/distributions/base.rb', line 263

def configurator
  @configurator
end

#ct ⇒ Object (readonly)

Returns the value of attribute ct.



18
19
20
# File 'lib/osctld/dist_config/distributions/base.rb', line 18

def ct
  @ct
end

#ctrc ⇒ Object (readonly)

Returns the value of attribute ctrc.



18
19
20
# File 'lib/osctld/dist_config/distributions/base.rb', line 18

def ctrc
  @ctrc
end

#distribution ⇒ Object (readonly)

Returns the value of attribute distribution.



18
19
20
# File 'lib/osctld/dist_config/distributions/base.rb', line 18

def distribution
  @distribution
end

#version ⇒ Object (readonly)

Returns the value of attribute version.



18
19
20
# File 'lib/osctld/dist_config/distributions/base.rb', line 18

def version
  @version
end

Class Method Details

.distribution(n = nil) ⇒ Object



10
11
12
13
14
15
16
# File 'lib/osctld/dist_config/distributions/base.rb', line 10

def self.distribution(n = nil)
  if n
    DistConfig.register(n, self)
  else
    n
  end
end

Instance Method Details

#add_netif(opts) ⇒ Object

Called when a new network interface is added to a container

Parameters:

  • opts (Hash)

Options Hash (opts):



185
186
187
188
189
# File 'lib/osctld/dist_config/distributions/base.rb', line 185

def add_netif(opts)
  with_rootfs do
    configurator.add_netif(ct.netifs, opts[:netif])
  end
end

#apply_hostname ⇒ Object

Configure hostname in a running system



154
155
156
# File 'lib/osctld/dist_config/distributions/base.rb', line 154

def apply_hostname
  log(:warn, ct, "Unable to apply hostname on #{distribution}: not implemented")
end

#bin_path(_opts) ⇒ String

Return path to /bin or an alternative, where a shell is looked up

Returns:

  • (String)


249
250
251
# File 'lib/osctld/dist_config/distributions/base.rb', line 249

def bin_path(_opts)
  '/bin'
end

#chpasswd_command ⇒ Object



253
254
255
# File 'lib/osctld/dist_config/distributions/base.rb', line 253

def chpasswd_command
  %w[chpasswd]
end

#configurator_class ⇒ Object



28
29
30
31
32
33
34
# File 'lib/osctld/dist_config/distributions/base.rb', line 28

def configurator_class
  raise "define #{self.class}#configurator_class" unless self.class.const_defined?(:Configurator)

  cls = self.class::Configurator
  log(:debug, "Using #{cls} for #{ctrc.distribution}")
  cls
end

#dns_resolvers(opts = {}) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/osctld/dist_config/distributions/base.rb', line 210

def dns_resolvers(opts = {})
  resolvers = opts.fetch(:resolvers) { ct.dns_resolvers }

  # The start hook is already inside a rootfs helper, before guest init.
  # Live reload must run from the host frontend, not from that chroot.
  if @within_rootfs || !ct.running?
    with_rootfs { configurator.dns_resolvers(resolvers) }
    return
  end

  reload = with_rootfs { configurator.prepare_dns_resolvers }
  reload_network_manager_dns if reload
  with_rootfs { configurator.write_dns_resolvers(resolvers) }
end

#log_type ⇒ Object



257
258
259
# File 'lib/osctld/dist_config/distributions/base.rb', line 257

def log_type
  ct.id
end

#network(_opts = {}) ⇒ Object



176
177
178
179
180
# File 'lib/osctld/dist_config/distributions/base.rb', line 176

def network(_opts = {})
  with_rootfs do
    configurator.network(ct.netifs)
  end
end

#passwd(opts) ⇒ Object

Parameters:

  • opts (Hash) —

    options

Options Hash (opts):

  • user (String)
  • password (String)


233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/osctld/dist_config/distributions/base.rb', line 233

def passwd(opts)
  ret = ct_syscmd(
    ct,
    chpasswd_command,
    stdin: "#{opts[:user]}:#{opts[:password]}\n",
    run: true,
    valid_rcs: :all
  )

  return true if ret.success?

  log(:warn, ct, "Unable to set password: #{ret.output}")
end

#post_mount(opts) ⇒ Object

Called by LXC post-mount hook on container start

Parameters:

  • opts (Hash)

Options Hash (opts):

  • :ns_pid (Integer)
  • :rootfs_mount (String)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/osctld/dist_config/distributions/base.rb', line 80

def post_mount(opts)
  return unless volatile_is_systemd?

  ContainerControl::Commands::WithMountns.run!(
    ct,
    ns_pid: opts[:ns_pid],
    mnt_ns: opts[:mnt_ns],
    chroot: opts[:rootfs_mount],
    root_dir: opts[:root_dir],
    block: proc do
      # /run is mounted by {#pre_start}
      FileUtils.mkdir_p('/run/udev')

      # Create /run/udev/control if it isn't already there
      begin
        File.stat('/run/udev/control')
      rescue Errno::ENOENT
        File.new('/run/udev/control', 'w').close
      end

      true
    end
  )
end

#pre_start(_opts = {}) ⇒ Object

Called before container start, can be used to e.g. add temporary mounts



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/osctld/dist_config/distributions/base.rb', line 37

def pre_start(_opts = {})
  return unless volatile_is_systemd?
  # systemd by default does not monitor udev events in containers, which
  # means that there are no device units to depend on, e.g. for network
  # interfaces. systemd decides this by the  existence of socket
  # /run/udev/control and that /dev is devtmpfs. Our /dev cannot be
  # devtmpfs since that doesn't work in containers, and /run/udev/control
  # is created by a socket unit *after* systemd makes the decision to not
  # monitor udev events. After `systemctl daemon-reload`, it actually
  # starts to monitor udev events and the device units are created.
  #
  # See https://github.com/systemd/systemd/blob/729d2df8065ac90ac606e1fff91dc2d588b2795d/src/libsystemd/sd-device/device-monitor.c#L125
  #
  # We therefore mount /run as tmpfs before systemd is run and create
  # a stub for /run/udev/control, so that the check passes and udev events
  # are monitored from the start.
  #
  # /run has to be mounted by LXC from the container's user namespace, so
  # that it is owned by that user namespace. File /run/udev/control is
  # created later in {#post_mount}.

  # Check if /run isn't mounted already by user configuration
  return unless ct.mounts.detect { |mnt| %w[/run /run/].include?(mnt.mountpoint) }.nil?

  mem_limit = ct.find_memory_limit
  mnt_opts = %w[nosuid nodev mode=755 create=dir]
  mnt_opts << "size=#{mem_limit / 2}" if mem_limit

  ct.mounts.add(Mount::Entry.new(
                  'tmpfs',
                  '/run',
                  'tmpfs',
                  mnt_opts.join(','),
                  false,
                  temp: true,
                  in_config: true
                ))
end

#reload_network_manager_dns ⇒ Object (protected)



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/osctld/dist_config/distributions/base.rb', line 265

def reload_network_manager_dns
  # Query the running daemon, not a client version string. Exit 8 means
  # NM is stopped and will read the policy at its next normal start.
  result = ct_syscmd(ct, %w[nmcli -t -f VERSION general], valid_rcs: [0, 8])
  return if result.exitstatus == 8

  version = result.output.strip.match(/\A(\d+)\.(\d+)\./)
  raise 'unable to determine NetworkManager version' unless version

  if (version.captures.map(&:to_i) <=> [1, 22]) >= 0
    ct_syscmd(ct, %w[nmcli general reload conf,dns-rc], valid_rcs: [0, 8])
  else
    # The daemon already supports synchronous Reload(flags) in 1.18,
    # but nmcli gained general reload only in 1.22. Do not replace it
    # with asynchronous SIGHUP or restart the guest's connections.
    ct_syscmd(ct, [
                'dbus-send', '--system', '--print-reply', '--type=method_call',
                '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager',
                'org.freedesktop.NetworkManager.Reload', 'uint32:3'
              ])
  end
end

#remove_netif(opts) ⇒ Object

Called when a network interface is removed from a container

Parameters:

  • opts (Hash)

Options Hash (opts):



194
195
196
197
198
# File 'lib/osctld/dist_config/distributions/base.rb', line 194

def remove_netif(opts)
  with_rootfs do
    configurator.remove_netif(ct.netifs, opts[:netif])
  end
end

#rename_netif(opts) ⇒ Object

Called when an existing network interface is renamed

Parameters:

  • opts (Hash)

Options Hash (opts):



204
205
206
207
208
# File 'lib/osctld/dist_config/distributions/base.rb', line 204

def rename_netif(opts)
  with_rootfs do
    configurator.rename_netif(ct.netifs, opts[:netif], opts[:original_name])
  end
end

#set_hostname(opts = {}) ⇒ Object

Set container hostname

Parameters:

  • opts (Hash) (defaults to: {}) —

    options

Options Hash (opts):

  • :original (OsCtl::Lib::Hostname) —

    previous hostname



144
145
146
147
148
149
150
151
# File 'lib/osctld/dist_config/distributions/base.rb', line 144

def set_hostname(opts = {})
  with_rootfs do
    configurator.set_hostname(ct.hostname, old_hostname: opts[:original])
    configurator.update_etc_hosts(ct.hostname, old_hostname: opts[:original])
  end

  apply_hostname if ct.running?
end

#start(_opts = {}) ⇒ Object

Run just before the container is started



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/osctld/dist_config/distributions/base.rb', line 106

def start(_opts = {})
  return unless ct.hostname || ct.dns_resolvers || ctrc.dist_configure_network?

  net_configured = with_rootfs do
    ret = false

    set_hostname if ct.hostname
    dns_resolvers if ct.dns_resolvers

    if ctrc.dist_configure_network?
      network
      ret = true
    end

    ret
  end

  ctrc.dist_network_configured = true if net_configured
end

#stop(opts) ⇒ Object

Gracefully stop the container

Parameters:

  • opts (Hash)

Options Hash (opts):

  • :mode (:stop, :shutdown, :kill)
  • :message (String)
  • :timeout (Integer)


131
132
133
134
135
136
137
138
# File 'lib/osctld/dist_config/distributions/base.rb', line 131

def stop(opts)
  ContainerControl::Commands::Stop.run!(
    ct,
    opts[:mode],
    message: opts[:message],
    timeout: opts[:timeout]
  )
end

#unset_dns_resolvers(_opts = {}) ⇒ Object



225
226
227
228
# File 'lib/osctld/dist_config/distributions/base.rb', line 225

def unset_dns_resolvers(_opts = {})
  reload = with_rootfs { configurator.unset_dns_resolvers }
  reload_network_manager_dns if reload && !@within_rootfs && ct.running?
end

#unset_etc_hosts(_opts = {}) ⇒ Object

Remove the osctld-generated notice from /etc/hosts



170
171
172
173
174
# File 'lib/osctld/dist_config/distributions/base.rb', line 170

def unset_etc_hosts(_opts = {})
  with_rootfs do
    configurator.unset_etc_hosts
  end
end

#update_etc_hosts(opts = {}) ⇒ Object

Update hostname in /etc/hosts, optionally removing configuration of old hostname.

Parameters:

  • opts (Hash) (defaults to: {}) —

    options

Options Hash (opts):

  • :old_hostname (OsCtl::Lib::Hostname, nil)


163
164
165
166
167
# File 'lib/osctld/dist_config/distributions/base.rb', line 163

def update_etc_hosts(opts = {})
  with_rootfs do
    configurator.update_etc_hosts(ct.hostname, old_hostname: opts[:old_hostname])
  end
end

#volatile_is_systemd? ⇒ Boolean (protected)

Check if the container is using systemd as init

This method accesses the container's rootfs from the host, which is dangerous because of symlinks and we really shouldn't be doing it... but in this case, we only do readlink(), so it shouldn't do any harm.

Returns:

  • (Boolean)


316
317
318
319
320
321
322
323
324
# File 'lib/osctld/dist_config/distributions/base.rb', line 316

def volatile_is_systemd?
  return true if ctrc.distribution == 'nixos'

  begin
    File.readlink(File.join(ctrc.rootfs, 'sbin/init')).include?('systemd')
  rescue SystemCallError
    false
  end
end

#with_rootfs(&block) ⇒ Object (protected)



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/osctld/dist_config/distributions/base.rb', line 288

def with_rootfs(&block)
  if @within_rootfs
    block.call
  else
    ContainerControl::Commands::WithRootfs.run!(
      ctrc.ct,
      ctrc:,
      block: proc do
        @configurator = configurator_class.new(
          ct.id,
          '/',
          ct.distribution,
          ct.version
        )
        @within_rootfs = true
        block.call
      end
    )
  end
end