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

#configuratorObject (readonly, protected)

Returns the value of attribute configurator.



244
245
246
# File 'lib/osctld/dist_config/distributions/base.rb', line 244

def configurator
  @configurator
end

#ctObject (readonly)

Returns the value of attribute ct.



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

def ct
  @ct
end

#ctrcObject (readonly)

Returns the value of attribute ctrc.



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

def ctrc
  @ctrc
end

#distributionObject (readonly)

Returns the value of attribute distribution.



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

def distribution
  @distribution
end

#versionObject (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):



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

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

#apply_hostnameObject

Configure hostname in a running system



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

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)


235
236
237
# File 'lib/osctld/dist_config/distributions/base.rb', line 235

def bin_path(_opts)
  '/bin'
end

#configurator_classObject



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

def configurator_class
  if self.class.const_defined?(:Configurator)
    cls = self.class::Configurator
    log(:debug, "Using #{cls} for #{ctrc.distribution}")
    cls
  else
    fail "define #{self.class}#configurator_class"
  end
end

#dns_resolvers(_opts = {}) ⇒ Object



211
212
213
214
215
# File 'lib/osctld/dist_config/distributions/base.rb', line 211

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

#log_typeObject



239
240
241
# File 'lib/osctld/dist_config/distributions/base.rb', line 239

def log_type
  ct.id
end

#network(_opts = {}) ⇒ Object



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

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)


220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/osctld/dist_config/distributions/base.rb', line 220

def passwd(opts)
  ret = ct_syscmd(
    ct,
    %w(chpasswd),
    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

Run by LXC post-mount hook on container start

Parameters:

  • opts (Hash)

Options Hash (opts):

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


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

def post_mount(opts)
  return unless volatile_is_systemd?

  ContainerControl::Commands::WithMountns.run!(
    ct,
    ns_pid: opts[:ns_pid],
    chroot: opts[:rootfs_mount],
    block: Proc.new 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.open('/run/udev/control', 'w') {}
      end

      true
    end,
  )
end

#pre_start(opts = {}) ⇒ Object

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



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
75
76
77
# File 'lib/osctld/dist_config/distributions/base.rb', line 39

def pre_start(opts = {})
  if 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
    if 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
  end
end

#remove_netif(opts) ⇒ Object

Called when a network interface is removed from a container

Parameters:

  • opts (Hash)

Options Hash (opts):



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

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):



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

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



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

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



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

def start(opts = {})
  if 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
end

#stop(opts) ⇒ Object

Gracefully stop the container

Parameters:

  • opts (Hash)

Options Hash (opts):

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


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

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

#unset_etc_hosts(opts = {}) ⇒ Object

Remove the osctld-generated notice from /etc/hosts



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

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)


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

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)


274
275
276
277
278
279
280
281
282
# File 'lib/osctld/dist_config/distributions/base.rb', line 274

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)



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/osctld/dist_config/distributions/base.rb', line 246

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