diff --git a/plugins/guests/devuan/cap/change_host_name.rb b/plugins/guests/devuan/cap/change_host_name.rb new file mode 100644 index 00000000000..b44eae59686 --- /dev/null +++ b/plugins/guests/devuan/cap/change_host_name.rb @@ -0,0 +1,109 @@ +require "log4r" +require 'vagrant/util/guest_hosts' +require 'vagrant/util/guest_inspection' +require_relative "../../linux/cap/network_interfaces" + +module VagrantPlugins + module GuestDevuan + module Cap + class ChangeHostName + + extend Vagrant::Util::GuestInspection::Linux + extend Vagrant::Util::GuestHosts::Linux + + def self.change_host_name(machine, name) + @logger = Log4r::Logger.new("vagrant::guest::devuan::changehostname") + comm = machine.communicate + + if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) + network_with_hostname = machine.config.vm.networks.map {|_, c| c if c[:hostname] }.compact[0] + if network_with_hostname + replace_host(comm, name, network_with_hostname[:ip]) + else + add_hostname_to_loopback_interface(comm, name) + end + + basename = name.split(".", 2)[0] + comm.sudo <<-EOH.gsub(/^ {14}/, '') + # Set the hostname + echo '#{basename}' > /etc/hostname + + # Update mailname + echo '#{name}' > /etc/mailname + + EOH + + if hostnamectl?(comm) + comm.sudo("hostnamectl set-hostname '#{basename}'") + else + comm.sudo <<-EOH.gsub(/^ {14}/, '') + hostname -F /etc/hostname + # Restart hostname services + if test -f /etc/init.d/hostname; then + /etc/init.d/hostname start || true + fi + + if test -f /etc/init.d/hostname.sh; then + /etc/init.d/hostname.sh start || true + fi + EOH + end + + restart_command = nil + if systemd?(comm) + if systemd_networkd?(comm) + @logger.debug("Attempting to restart networking with systemd-networkd") + restart_command = "systemctl restart systemd-networkd.service" + elsif systemd_controlled?(comm, "NetworkManager.service") + @logger.debug("Attempting to restart networking with NetworkManager") + restart_command = "systemctl restart NetworkManager.service" + end + end + + if restart_command + comm.sudo(restart_command) + else + restart_each_interface(machine, @logger) + end + end + end + + protected + + # Due to how most Devuan systems and older Ubuntu systems handle restarting + # networking, we cannot simply run the networking init script or use the ifup/down + # tools to restart all interfaces to renew the machines DHCP lease when setting + # its hostname. This method is a workaround for those older systems that + # cannoy reliably restart networking. It restarts each individual interface + # on its own instead. + # + # @param [Vagrant::Machine] machine + # @param [Log4r::Logger] logger + def self.restart_each_interface(machine, logger) + comm = machine.communicate + interfaces = VagrantPlugins::GuestLinux::Cap::NetworkInterfaces.network_interfaces(machine) + nettools = true + if systemd?(comm) + logger.debug("Attempting to restart networking with systemctl") + nettools = false + else + logger.debug("Attempting to restart networking with ifup/down nettools") + end + + interfaces.each do |iface| + logger.debug("Restarting interface #{iface} on guest #{machine.name}") + if nettools + # Ubuntu 16.04+ returns an error when downing an interface that + # does not exist. The `|| true` preserves the behavior that older + # Ubuntu versions exhibit and Vagrant expects (GH-7155) + restart_command = "ifdown #{iface} || true;ifup #{iface} || true" + else + restart_command = "systemctl stop ifup@#{iface}.service;systemctl start ifup@#{iface}.service" + end + comm.sudo(restart_command) + end + end + end + end + end +end diff --git a/plugins/guests/devuan/cap/configure_networks.rb b/plugins/guests/devuan/cap/configure_networks.rb new file mode 100644 index 00000000000..bcad8fb1651 --- /dev/null +++ b/plugins/guests/devuan/cap/configure_networks.rb @@ -0,0 +1,208 @@ +require "tempfile" + +require_relative "../../../../lib/vagrant/util/template_renderer" + +module VagrantPlugins + module GuestDevuan + module Cap + class ConfigureNetworks + include Vagrant::Util + extend Vagrant::Util::GuestInspection::Linux + extend Vagrant::Util::Retryable + + NETPLAN_DEFAULT_VERSION = 2 + NETPLAN_DIRECTORY = "/etc/netplan".freeze + NETWORKD_DIRECTORY = "/etc/systemd/network".freeze + + + def self.configure_networks(machine, networks) + #puts "ASDF configuring networks" + comm = machine.communicate + interfaces = machine.guest.capability(:network_interfaces) + + if systemd?(comm) + if netplan?(comm) + configure_netplan(machine, interfaces, comm, networks) + elsif systemd_networkd?(comm) + configure_networkd(machine, interfaces, comm, networks) + else + configure_nettools(machine, interfaces, comm, networks) + end + else + configure_nettools(machine, interfaces, comm, networks) + end + end + + # Configure networking using netplan + def self.configure_netplan(machine, interfaces, comm, networks) + ethernets = {}.tap do |e_nets| + networks.each do |network| + e_config = {}.tap do |entry| + if network[:type].to_s == "dhcp" + entry["dhcp4"] = true + else + mask = network[:netmask] + if mask && IPAddr.new(network[:ip]).ipv4? + begin + mask = IPAddr.new(mask).to_i.to_s(2).count("1") + rescue IPAddr::Error + # ignore and use given value + end + end + entry["addresses"] = [[network[:ip], mask].compact.join("/")] + end + if network[:gateway] + entry["gateway4"] = network[:gateway] + end + end + e_nets[interfaces[network[:interface]]] = e_config + end + end + + # By default, netplan expects the renderer to be systemd-networkd, + # but if any device is managed by NetworkManager, then we use that renderer + # ref: https://netplan.io/reference + if systemd_networkd?(comm) + renderer = "networkd" + ethernets.keys.each do |k| + if nm_controlled?(comm, k) + render = "NetworkManager" + if !nmcli?(comm) + raise Vagrant::Errors::NetworkManagerNotInstalled, device: k + end + break + end + end + elsif nmcli?(comm) + renderer = "NetworkManager" + else + raise Vagrant::Errors::NetplanNoAvailableRenderers + end + + np_config = {"network" => {"version" => NETPLAN_DEFAULT_VERSION, + "renderer" => renderer, "ethernets" => ethernets}} + + remote_path = upload_tmp_file(comm, np_config.to_yaml) + dest_path = "#{NETPLAN_DIRECTORY}/50-vagrant.yaml" + comm.sudo(["mv -f '#{remote_path}' '#{dest_path}'", + "chown root:root '#{dest_path}'", + "chmod 0644 '#{dest_path}'", + "netplan apply"].join("\n")) + end + + # Configure guest networking using networkd + def self.configure_networkd(machine, interfaces, comm, networks) + root_device = interfaces.first + networks.each do |network| + dev_name = interfaces[network[:interface]] + net_conf = [] + net_conf << "[Match]" + net_conf << "Name=#{dev_name}" + net_conf << "[Network]" + if network[:type].to_s == "dhcp" + net_conf << "DHCP=yes" + else + mask = network[:netmask] + if mask && IPAddr.new(network[:ip]).ipv4? + begin + mask = IPAddr.new(mask).to_i.to_s(2).count("1") + rescue IPAddr::Error + # ignore and use given value + end + end + address = [network[:ip], mask].compact.join("/") + net_conf << "DHCP=no" + net_conf << "Address=#{address}" + net_conf << "Gateway=#{network[:gateway]}" if network[:gateway] + end + + remote_path = upload_tmp_file(comm, net_conf.join("\n")) + dest_path = "#{NETWORKD_DIRECTORY}/50-vagrant-#{dev_name}.network" + comm.sudo(["mkdir -p #{NETWORKD_DIRECTORY}", + "mv -f '#{remote_path}' '#{dest_path}'", + "chown root:root '#{dest_path}'", + "chmod 0644 '#{dest_path}'"].join("\n")) + end + + comm.sudo(["systemctl restart systemd-networkd.service"].join("\n")) + end + + # Configure guest networking using net-tools + def self.configure_nettools(machine, interfaces, comm, networks) + commands = [] + entries = [] + root_device = interfaces.first + networks.each do |network| + network[:device] = interfaces[network[:interface]] + + entry = TemplateRenderer.render("guests/devuan/network_#{network[:type]}", + options: network.merge(:root_device => root_device), + ) + entries << entry + end + + content = entries.join("\n") + remote_path = "/tmp/vagrant-network-entry" + upload_tmp_file(comm, content, remote_path) + + #puts "ASDF" + #puts content + #puts "ASDF" + + networks.each do |network| + # Ubuntu 16.04+ returns an error when downing an interface that + # does not exist. The `|| true` preserves the behavior that older + # Ubuntu versions exhibit and Vagrant expects (GH-7155) + commands << "/sbin/ifdown '#{network[:device]}' || true" + commands << "/sbin/ip addr flush dev '#{network[:device]}'" + end + + # Reconfigure /etc/network/interfaces. + commands << <<-EOH.gsub(/^ {12}/, "") + # Remove any previous network modifications from the interfaces file + sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre + sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tac | sed -e '/^#VAGRANT-END/,$ d' | tac > /tmp/vagrant-network-interfaces.post + cat \\ + /tmp/vagrant-network-interfaces.pre \\ + /tmp/vagrant-network-entry \\ + /tmp/vagrant-network-interfaces.post \\ + > /etc/network/interfaces + rm -f /tmp/vagrant-network-interfaces.pre + rm -f /tmp/vagrant-network-entry + rm -f /tmp/vagrant-network-interfaces.post + EOH + + comm.sudo(commands.join("\n")) + network_up_commands = [] + + # Bring back up each network interface, reconfigured. + networks.each do |network| + network_up_commands << "/sbin/ifup '#{network[:device]}'" + end + retryable(on: Vagrant::Errors::VagrantError, sleep: 2, tries: 2) do + comm.sudo(network_up_commands.join("\n")) + end + end + + # Simple helper to upload content to guest temporary file + # + # @param [Vagrant::Plugin::Communicator] comm + # @param [String] content + # @return [String] remote path + def self.upload_tmp_file(comm, content, remote_path=nil) + if remote_path.nil? + remote_path = "/tmp/vagrant-network-entry-#{Time.now.to_i}" + end + Tempfile.open("vagrant-devuan-configure-networks") do |f| + f.binmode + f.write(content) + f.fsync + f.close + comm.upload(f.path, remote_path) + end + remote_path + end + end + end + end +end diff --git a/plugins/guests/devuan/cap/nfs.rb b/plugins/guests/devuan/cap/nfs.rb new file mode 100644 index 00000000000..2d579d85ded --- /dev/null +++ b/plugins/guests/devuan/cap/nfs.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module GuestDevuan + module Cap + class NFS + def self.nfs_client_install(machine) + comm = machine.communicate + comm.sudo <<-EOH.gsub(/^ {12}/, '') + apt-get -yqq update + apt-get -yqq install nfs-common portmap + exit $? + EOH + end + end + end + end +end diff --git a/plugins/guests/devuan/cap/rsync.rb b/plugins/guests/devuan/cap/rsync.rb new file mode 100644 index 00000000000..6b1aa80888a --- /dev/null +++ b/plugins/guests/devuan/cap/rsync.rb @@ -0,0 +1,15 @@ +module VagrantPlugins + module GuestDevuan + module Cap + class RSync + def self.rsync_install(machine) + comm = machine.communicate + comm.sudo <<-EOH.gsub(/^ {14}/, '') + apt-get -yqq update + apt-get -yqq install rsync + EOH + end + end + end + end +end diff --git a/plugins/guests/devuan/cap/smb.rb b/plugins/guests/devuan/cap/smb.rb new file mode 100644 index 00000000000..60e2939f931 --- /dev/null +++ b/plugins/guests/devuan/cap/smb.rb @@ -0,0 +1,17 @@ +module VagrantPlugins + module GuestDevuan + module Cap + class SMB + def self.smb_install(machine) + comm = machine.communicate + if !comm.test("test -f /sbin/mount.cifs") + comm.sudo <<-EOH.gsub(/^ {14}/, '') + apt-get -yqq update + apt-get -yqq install cifs-utils + EOH + end + end + end + end + end +end diff --git a/plugins/guests/devuan/guest.rb b/plugins/guests/devuan/guest.rb new file mode 100644 index 00000000000..cb415d4be16 --- /dev/null +++ b/plugins/guests/devuan/guest.rb @@ -0,0 +1,10 @@ +require_relative '../linux/guest' + +module VagrantPlugins + module GuestDevuan + class Guest < VagrantPlugins::GuestLinux::Guest + # Name used for guest detection + GUEST_DETECTION_NAME = "devuan".freeze + end + end +end diff --git a/plugins/guests/devuan/plugin.rb b/plugins/guests/devuan/plugin.rb new file mode 100644 index 00000000000..9d0278344e6 --- /dev/null +++ b/plugins/guests/devuan/plugin.rb @@ -0,0 +1,40 @@ +require "vagrant" + +module VagrantPlugins + module GuestDevuan + class Plugin < Vagrant.plugin("2") + name "Devuan guest" + description "Devuan guest support." + + guest(:devuan, :linux) do + require_relative "guest" + Guest + end + + guest_capability(:devuan, :configure_networks) do + require_relative "cap/configure_networks" + Cap::ConfigureNetworks + end + + guest_capability(:devuan, :change_host_name) do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + + guest_capability(:devuan, :nfs_client_install) do + require_relative "cap/nfs" + Cap::NFS + end + + guest_capability(:devuan, :rsync_install) do + require_relative "cap/rsync" + Cap::RSync + end + + guest_capability(:devuan, :smb_install) do + require_relative "cap/smb" + Cap::SMB + end + end + end +end diff --git a/templates/guests/devuan/network_dhcp.erb b/templates/guests/devuan/network_dhcp.erb new file mode 100644 index 00000000000..d618e875051 --- /dev/null +++ b/templates/guests/devuan/network_dhcp.erb @@ -0,0 +1,13 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +auto <%= options[:device] %> +iface <%= options[:device] %> inet dhcp +<% if !options[:use_dhcp_assigned_default_route] %> + post-up ip route del default dev $IFACE || true +<% else %> + # We need to disable eth0, see GH-2648 + post-up ip route del default dev <%= options[:root_device] %> || true + post-up dhclient $IFACE + pre-down ip route add default dev <%= options[:root_device] %> +<% end %> +#VAGRANT-END diff --git a/templates/guests/devuan/network_static.erb b/templates/guests/devuan/network_static.erb new file mode 100644 index 00000000000..bf9b9c6b357 --- /dev/null +++ b/templates/guests/devuan/network_static.erb @@ -0,0 +1,10 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +auto <%= options[:device] %> +iface <%= options[:device] %> inet static + address <%= options[:ip] %> + netmask <%= options[:netmask] %> +<% if options[:gateway] %> + gateway <%= options[:gateway] %> +<% end %> +#VAGRANT-END diff --git a/templates/guests/devuan/network_static6.erb b/templates/guests/devuan/network_static6.erb new file mode 100644 index 00000000000..288da4396f6 --- /dev/null +++ b/templates/guests/devuan/network_static6.erb @@ -0,0 +1,10 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +auto <%= options[:device] %> +iface <%= options[:device] %> inet6 static + address <%= options[:ip] %> + netmask <%= options[:netmask] %> +<% if options[:gateway] %> + gateway <%= options[:gateway] %> +<% end %> +#VAGRANT-END