diff --git a/Vagrantfile b/Vagrantfile
index 542ded3..bdbf648 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -30,9 +30,10 @@ end
def configure_router(i, config)
config.vm.define "router#{i}" do |router|
- router.vm.box = "generic/debian11"
+ router.vm.box = "generic/debian12"
+ router.vm.provision "shell", reboot: true, inline: "sudo systemctl enable systemd-networkd.service"
lip = $ip.clone
- router.vm.provision "shell", reboot: true, path:"dev/router-networking.sh", args: [lip]
+ router.vm.provision "shell", reboot: true, path:"dev/router-networking.sh", args: [lip, $host_only.to_s]
if $host_only == false then
if $bridge_interface != nil then
router.vm.network "public_network",
@@ -59,7 +60,7 @@ def configure_router(i, config)
end
configure_ram(router, $router_ram)
- configure_private_network(router, true)
+ configure_private_network(router, false)
router.vm.provision "shell" do |s|
s.inline = "hostnamectl set-hostname $1"
s.args = ["router"+i.to_s]
@@ -70,8 +71,21 @@ end
def configure_cluster_node(i, config)
config.vm.define "cluster#{i}" do |clustervm|
clustervm.vm.box = "NIAEFEUP/rocky-NInux"
- clustervm.vm.box_version = "0.4.1"
+ clustervm.vm.box_version = "0.5.1"
lip = $ip.clone
+
+ # We enable nested virtualization for vm build tests in vagrant
+ clustervm.vm.provider "virtualbox" do |vb|
+ vb.customize ['modifyvm', :id, '--nested-hw-virt', 'on']
+ end
+
+ clustervm.vm.provider :libvirt do |libvirt|
+ # Enable KVM nested virtualization
+ libvirt.nested = true
+ libvirt.cpu_mode = "host-model"
+ end
+
+
clustervm.vm.provision "shell" do |s|
s.path = "dev/node-networking.sh"
s.args = [lip]
diff --git a/ansible-inventory.example.ini b/ansible-inventory.example.ini
index 4ac348d..2cbe261 100644
--- a/ansible-inventory.example.ini
+++ b/ansible-inventory.example.ini
@@ -4,10 +4,11 @@
[routers]
#while unusual, you can define multiple routers.
-#You MUST always have only 1 router that is master, for VRRP
+#You MUST always have only 1 router that is master, for VRRP.
+# while bootstrapping you MUST leave this group out, but you will be asked to fill it in.
-#10.0.0.1 master=true
-#10.0.0.2 master=false
+#router1 ansible_ssh_host=10.0.0.1 master=true
+#router2 ansible_ssh_host=10.0.0.2 master=false
[controlplane]
# These are the kind of nodes that are responsible for managing
@@ -17,10 +18,13 @@
# if you wish, you can specify an alias for a node, or you can just specify
# the ip address as shown below:
-#node1 ansible_ssh_host=10.0.0.2
+# you need to always define the ansible_ssh_host and ansible_ssh_private_key because they will be changed automatically
+# you maybe to need to define the external interface (that will be given to the router, alongside the whole PCI device).
+
+
+#node1 ansible_ssh_host=10.0.0.2 ansible_ssh_private_key=/path/to/private_key
+#node1 ansible_ssh_host=10.0.0.2 ansible_ssh_private_key=/path/to/private_key external_interface=enp1f0
-#10.0.0.3
-#10.0.0.4
[workers]
# These kinds of nodes are also connected to Kubernetes cluster,
diff --git a/deploy-playbook.yaml b/deploy-playbook.yaml
index 85d21d9..3e09edf 100644
--- a/deploy-playbook.yaml
+++ b/deploy-playbook.yaml
@@ -1,13 +1,44 @@
---
+- name: Install Router VMs
+ ansible.builtin.import_playbook: router/build-router-vm-playbook.yaml
- name: Create a set new SSH key for clusters and routers
ansible.builtin.import_playbook: networking/add-ssh-key-to-nodes-playbook.yaml
-- name: Accept ssh keys for the first time
- ansible.builtin.import_playbook: networking/accept-ssh-keys-playbook.yaml
+- name: Networking - Setup static internal IPs
+ ansible.builtin.import_playbook: networking/dhcp-server-config-playbook.yaml
+- name: Wait for connection
+ hosts: all
+ connection: local
+ gather_facts: false
+ tasks:
+ - name: Wait for nodes to change dhcp address
+ ansible.builtin.wait_for:
+ port: 22
+ host: '{{ (ansible_ssh_host | default(ansible_host)) | default(inventory_hostname) }}'
+ search_regex: OpenSSH
+ delay: 10
+ timeout: 120
+- name: Pre-setup - enable NTP syncronization
+ hosts: all
+ tasks:
+ - name: "Enable NTP client"
+ become: true
+ ansible.builtin.command: /usr/bin/timedatectl set-ntp on
+ changed_when: true
+ - name: "Switch to UTC timezone"
+ become: true
+ community.general.timezone:
+ name: "UTC"
- name: Pre-setup - get correct interfaces
ansible.builtin.import_playbook: networking/get-interface-playbook.yaml
+- name: Pre-setup - Node re-enable NetworkManager
+ ansible.builtin.import_playbook: node/reenable-networkmanager-playbook.yaml
+- name: Networking - Router initial config
+ ansible.builtin.import_playbook: networking/router-setup-playbook.yaml
- name: Networking - Router BGP
ansible.builtin.import_playbook: networking/router-bgp-playbook.yaml
- name: Networking - VRRP
ansible.builtin.import_playbook: networking/router-vrrp-playbook.yaml
- name: Networking - Router Controlplane HA
ansible.builtin.import_playbook: networking/controlplane-ha-playbook.yaml
+- name: Nodes - Enable EPEL repositories
+ ansible.builtin.import_playbook: node/add-epel-repos-playbook.yaml
diff --git a/dev/node-networking.sh b/dev/node-networking.sh
index 8c013a4..e57fddd 100644
--- a/dev/node-networking.sh
+++ b/dev/node-networking.sh
@@ -1,6 +1,10 @@
#!/bin/sh
-sudo ip r del 0.0.0.0
-sudo nmcli device modify ens5 ipv4.never-default yes
-sudo nmcli con add type ethernet con-name main-network ifname ens6 ip4 10.10.0.$1/24 \
- gw4 10.10.0.254
-sudo nmcli con up main-network ifname ens6
\ No newline at end of file
+
+#TODO(luis): remove static ip configuration when dhcp server can be configured, to better replicate the physical node configuration
+# sudo systemctl disable dhcpcd
+# sudo systemctl enable --now NetworkManager
+sudo ip r del default || true
+# sudo nmcli device modify ens5 ipv4.never-default yes
+# sudo nmcli con add type ethernet con-name main-network ifname ens6 ip4 10.10.0.$1/24 \
+# gw4 10.10.0.254
+# sudo nmcli con up main-network ifname ens6
\ No newline at end of file
diff --git a/dev/router-networking.sh b/dev/router-networking.sh
index eb8ef78..7ddc938 100644
--- a/dev/router-networking.sh
+++ b/dev/router-networking.sh
@@ -1,21 +1,42 @@
#!/bin/sh
+apt-get purge -y ifupdown
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/10-router.conf
+echo "[Match]
+Name=eth0
+
+[Network]
+DHCP=yes
+DefaultRouteOnDevice=false
+" > /etc/systemd/network/01-vagrant.network
+
+if ["$2" -eq "true"]; then
+echo "Configuring host-only"
+echo "[Match]
+Name=eth1
+
+[Network]
+Address=10.69.0.2/24
+Gateway=10.69.0.1
+DefaultRouteOnDevice=true
+" > /etc/systemd/network/00-external.network
+else
+echo "Public network... fallback to dhcp"
+fi
+echo "[Match]
+Name=*
+
+[Network]
+DHCP=yes
+
+[Network]
+LinkLocalAddressing=yes
+IPv4LLRoute=true" > /etc/systemd/network/99-default-ipv4ll.network
+
echo "
-allow-hotplug eth0
-auto lo
-iface lo inet loopback
-iface eth0 inet dhcp
- post-up ip route del default dev eth0 || true
-
-auto eth2
-iface eth2 inet static
- address 10.10.0.$1
- netmask 255.255.255.0
-" >> /etc/network/interfaces
-
-nft add table nat
-nft add chain nat postrouting { type nat hook postrouting priority 100 \; }
-nft add rule nat postrouting ip saddr 10.10.0.0/24 oif eth1 masquerade
-nft list ruleset > /etc/nftables.conf
-systemctl enable nftables
\ No newline at end of file
+nameserver 1.1.1.1
+" >> /etc/resolvconf/resolv.conf.d/tail
+
+apt-get install -y avahi-daemon avahi-utils avahi-autoipd
+
+sed -i 's/publish-workstation=no/publish-workstation=yes/g' /etc/avahi/avahi-daemon.conf
\ No newline at end of file
diff --git a/networking/accept-ssh-keys-playbook.yaml b/networking/accept-ssh-keys-playbook.yaml
deleted file mode 100644
index 347e0bd..0000000
--- a/networking/accept-ssh-keys-playbook.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-- name: Trust all ssh hosts if they don't exist yet
- hosts: all
- gather_facts: false
- tasks:
- - name: Accept SSH key for each host
- connection: local
- ansible.builtin.known_hosts:
- state: present
- name: "{{ hostvars[inventory_hostname]['ansible_ssh_host'] }}"
- key: "{{ lookup('pipe', 'ssh-keyscan -T 10 -H -t ssh-ed25519 ' + hostvars[inventory_hostname]['ansible_ssh_host']) }}"
diff --git a/networking/add-ssh-key-to-nodes-playbook.yaml b/networking/add-ssh-key-to-nodes-playbook.yaml
index d173a4a..06fe028 100644
--- a/networking/add-ssh-key-to-nodes-playbook.yaml
+++ b/networking/add-ssh-key-to-nodes-playbook.yaml
@@ -39,7 +39,7 @@
when: inventory_hostname | regex_replace('\d', '') == 'cluster'
throttle: 1
block:
- - name: Propagate the publicg key
+ - name: Propagate the public key
become: true
ansible.posix.authorized_key:
user: ni
@@ -50,8 +50,8 @@
connection: local
ansible.builtin.lineinfile:
path: '{{ inventory_dir }}/ansible-inventory-dev.ini'
- regexp: '^{{ inventory_hostname }}(.*)ansible_ssh_private_key_file=(.*)( .*)?'
- line: '{{ inventory_hostname }}\1ansible_ssh_private_key_file={{ playbook_dir | dirname }}/.ssh/new_key\3'
+ regexp: '^{{ inventory_hostname }}(.*)ansible_ssh_private_key_file=(\S*) *(.*)'
+ line: '{{ inventory_hostname }}\1ansible_ssh_private_key_file={{ playbook_dir | dirname }}/.ssh/new_key \3'
backrefs: true
- name: Add the key and mofidy inventory if router
@@ -59,7 +59,7 @@
block:
- name: Propagate public key to this node
ansible.posix.authorized_key:
- user: vagrant
+ user: "{{ 'vagrant' if dev_cluster == 'true' else 'ni' }}"
state: present
key: "{{ new_ssh_key.stdout }}"
exclusive: "{{ not dev_cluster }}"
@@ -69,7 +69,7 @@
connection: local
ansible.builtin.lineinfile:
path: '{{ inventory_dir }}/ansible-inventory-dev.ini'
- regexp: '^{{ inventory_hostname }}(.*)ansible_ssh_private_key_file=(.*) +(.*)'
+ regexp: '^{{ inventory_hostname }}(.*)ansible_ssh_private_key_file=(\S*) *(.*)'
line: '{{ inventory_hostname }}\1ansible_ssh_private_key_file={{ playbook_dir | dirname }}/.ssh/new_key \3'
backrefs: true
diff --git a/networking/dhcp-server-config-playbook.yaml b/networking/dhcp-server-config-playbook.yaml
new file mode 100644
index 0000000..96ab330
--- /dev/null
+++ b/networking/dhcp-server-config-playbook.yaml
@@ -0,0 +1,162 @@
+# TODO: Delay ansible for the IP addresses of the nodes to be changed
+# TODO: Make this playbook only run once
+
+- name: Setup Kea config file
+ hosts: nodes
+ gather_facts: true
+ vars:
+ target_interface: 169.254.0.0
+ # target_interface: 10.10.0.0
+ tasks:
+ - name: Extract nodes MAC addresses
+ run_once: true # noqa: run-once[task]
+ ansible.builtin.set_fact:
+ mac_addresses: >
+ {{ (mac_addresses | default([])) + (ansible_play_hosts_all | map('extract', hostvars) | json_query('[].ansible_%s.macaddress' % item)) }}
+ when: >
+ ansible_play_hosts_all | map('extract', hostvars) | json_query('[].ansible_%s.ipv4' % item)
+ | selectattr('network', 'defined') | selectattr('network', '==', target_interface)
+ loop: "{{ ansible_play_hosts_all | map('extract', hostvars) | json_query('[].ansible_interfaces') | flatten | unique }}"
+
+ - name: Skip for empty macaddresess list
+ when: mac_addresses is undefined or not mac_addresses
+ ansible.builtin.meta: end_play
+
+ - name: Export MAC addresses to the routers
+ ansible.builtin.set_fact:
+ mac_addresses: "{{ mac_addresses }}"
+ delegate_to: "{{ item }}"
+ delegate_facts: true
+ with_items: "{{ groups['routers'] }}"
+
+- name: Setup internal IP for the routers
+ hosts: routers
+ vars:
+ target_network: 169.254.0.0
+ tasks:
+ - name: Get internal interfaces
+ ansible.builtin.set_fact:
+ router_interface: "{{ item }}"
+ cacheable: true # cache it to other playbooks in this run use it
+ when: (hostvars[inventory_hostname]['ansible_%s' % item] | default({})).get('ipv4', {}).get('network') == target_network
+ loop: "{{ ansible_interfaces }}"
+ - name: Get internal interface if router dhcp has been defined
+ ansible.builtin.set_fact:
+ router_interface: "{{ item }}"
+ cacheable: true # cache it to other playbooks in this run use it
+ # FIXME (luisd): remove hardcoded ip on refactor
+ when: >
+ router_interface is undefined and
+ (hostvars[inventory_hostname]['ansible_%s' % item] | default({})).get('ipv4', {}).get('network') == "10.10.0.0"
+ loop: "{{ ansible_interfaces }}"
+
+- name: Setup static IP
+ hosts: routers
+ tasks:
+ - name: Skip for empty macaddresess list
+ when: mac_addresses is undefined or not mac_addresses
+ ansible.builtin.meta: end_play
+
+ - name: Setup internal network config to vm
+ become: true
+ ansible.builtin.template:
+ src: templates/02-internal.network.j2
+ dest: /etc/systemd/network/02-internal.network
+ mode: "644"
+ - name: Restart network service on VM
+ become: true
+ ansible.builtin.systemd:
+ no_block: true
+ name: systemd-networkd
+ state: restarted
+ changed_when: true
+ ignore_unreachable: true
+ async: 10
+ poll: 0
+ - name: Update routers IP's
+ connection: local
+ ansible.builtin.lineinfile:
+ path: '{{ inventory_dir }}/ansible-inventory-dev.ini'
+ regexp: '^{{ inventory_hostname }}(.*)ansible_ssh_host=([^ ]*) (.*)'
+ line: '{{ inventory_hostname }}\1ansible_ssh_host=10.10.0.{{ (inventory_hostname[-1] | int + 1) | string }} \3'
+ backrefs: true
+ register: router_update
+ - name: Refresh inventory
+ ansible.builtin.meta: refresh_inventory
+ - name: Wait for systemd-networkd
+ when: router_update.changed # noqa: no-handler
+ ansible.builtin.wait_for:
+ port: 22
+ host: '{{ (ansible_ssh_host | default(ansible_host)) | default(inventory_hostname) }}'
+ search_regex: OpenSSH
+ delay: 10
+ timeout: 120
+ delegate_to: localhost
+ - name: Add nameserver to VM
+ when: router_update.changed # noqa: no-handler
+ become: true
+ ansible.builtin.lineinfile:
+ path: /etc/resolvconf/resolv.conf.d/tail
+ line: nameserver 1.1.1.1
+ mode: "644"
+ create: true
+
+- name: Run DHCP4 Server with Kea
+ hosts: routers
+ tasks:
+ - name: Install kea package
+ become: true
+ ansible.builtin.apt:
+ name: kea
+ update_cache: true
+ register: kea_install
+
+ - name: Delete default config if kea was installed # noqa: no-handler
+ when: kea_install.changed
+ become: true
+ ansible.builtin.file:
+ path: /etc/kea/kea-dhcp4.conf
+ state: absent
+
+ - name: Get old config file
+ ansible.builtin.command:
+ cmd: cat /etc/kea/kea-dhcp4.conf
+ failed_when: false # never fail because it will be handle in the next task
+ changed_when: true
+ register: old_config
+
+ - name: Write old mac addresses
+ ansible.builtin.set_fact:
+ mac_addresses: >
+ {{ (old_config.stdout | from_json | json_query('Dhcp4.subnet4[0].reservations[*]."hw-address"') | default([])) + (mac_addresses | default([])) }}
+ when: old_config.rc == 0
+
+ - name: Write config file
+ become: true
+ ansible.builtin.template:
+ src: templates/kea-dhcp4.conf.j2
+ dest: /etc/kea/kea-dhcp4.conf
+ mode: "644"
+ - name: Create leases file if not exists
+ become: true
+ ansible.builtin.file:
+ path: /var/lib/kea/dhcp4.leases
+ state: touch
+ mode: '0700'
+ - name: Update nodes IP's
+ connection: local
+ ansible.builtin.lineinfile:
+ path: '{{ inventory_dir }}/ansible-inventory-dev.ini'
+ regexp: '^{{ item }}(.*)ansible_ssh_host=([^ ]*) (.*)'
+ line: >
+ {{ item }}\1ansible_ssh_host=10.10.0.{{ (groups["nodes"].index(item) + 1 + (groups["routers"] | length) + 1) | string }} \3
+ backrefs: true
+ loop: "{{ groups['nodes'] }}"
+
+ - name: Restart kea dhcp server
+ become: true
+ ansible.builtin.systemd:
+ service: kea-dhcp4-server
+ state: restarted
+ - name: Refresh the inventory
+ ansible.builtin.meta: refresh_inventory
diff --git a/networking/get-interface-playbook.yaml b/networking/get-interface-playbook.yaml
index 5196618..eaeca7b 100644
--- a/networking/get-interface-playbook.yaml
+++ b/networking/get-interface-playbook.yaml
@@ -9,3 +9,8 @@
cacheable: true # cache it to other playbooks in this run use it
when: (hostvars[inventory_hostname]['ansible_%s' % item] | default({})).get('ipv4', {}).get('network') == target_network
loop: "{{ ansible_interfaces }}"
+
+ - name: Fail if there's no dhcp IP
+ when: not target_interface
+ ansible.builtin.fail:
+ msg: "Node {{ inventory_hostname }} failed to get a DHCP lease."
diff --git a/networking/router-setup-playbook.yaml b/networking/router-setup-playbook.yaml
new file mode 100644
index 0000000..5eac0ac
--- /dev/null
+++ b/networking/router-setup-playbook.yaml
@@ -0,0 +1,29 @@
+---
+
+- name: Networking - Router basic setup
+ hosts: routers
+ tasks:
+ - name: Enable Layer 3 forwarding
+ become: true
+ ansible.posix.sysctl:
+ sysctl_set: true
+ name: net.ipv4.ip_forward
+ value: 1
+ - name: Install nftables on router
+ become: true
+ ansible.builtin.apt:
+ name:
+ - nftables
+ state: present
+ - name: Copy nftables config
+ become: true
+ ansible.builtin.template:
+ src: templates/nftables.conf.j2
+ dest: /etc/nftables.conf
+ mode: "755"
+ - name: Restart nftables
+ become: true
+ ansible.builtin.systemd:
+ name: nftables
+ state: restarted
+ changed_when: true
diff --git a/networking/templates/02-internal.network.j2 b/networking/templates/02-internal.network.j2
new file mode 100644
index 0000000..e8701a6
--- /dev/null
+++ b/networking/templates/02-internal.network.j2
@@ -0,0 +1,6 @@
+[Match]
+Name={{router_interface}}
+
+[Network]
+Address=10.10.0.{{ (inventory_hostname[-1] | int + 1) | string }}/24
+DNS=1.1.1.1
diff --git a/networking/templates/kea-dhcp4.conf.j2 b/networking/templates/kea-dhcp4.conf.j2
new file mode 100644
index 0000000..cef03b8
--- /dev/null
+++ b/networking/templates/kea-dhcp4.conf.j2
@@ -0,0 +1,36 @@
+{
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [ "{{ router_interface }}" ],
+ "dhcp-socket-type": "raw"
+ },
+ "valid-lifetime": 4000,
+ "max-valid-lifetime": 7200,
+ "subnet4": [{
+ "pools": [ { "pool": "10.10.0.200-10.10.0.249" } ],
+ "subnet": "10.10.0.0/24",
+ "option-data": [
+ {
+ "name": "routers",
+ "data": "10.10.0.254"
+ },
+ {
+ "name": "domain-name-servers",
+ "data": "1.1.1.1, 1.0.0.1"
+ }
+ ],
+ "reservations":
+ [
+ {%for mac_address in mac_addresses %}
+ {
+ "hw-address": "{{mac_address}}",
+ "ip-address": "10.10.0.{{ (groups["routers"] | length) + loop.index + 1}}"
+ }
+ {% if loop.index != loop.length%}
+ ,
+ {% endif %}
+ {% endfor %}
+ ]
+ }]
+ }
+}
\ No newline at end of file
diff --git a/networking/templates/nftables.conf.j2 b/networking/templates/nftables.conf.j2
new file mode 100644
index 0000000..6d26d15
--- /dev/null
+++ b/networking/templates/nftables.conf.j2
@@ -0,0 +1,11 @@
+#!/usr/sbin nft -f
+
+flush ruleset
+
+
+table ip nat {
+ chain postrouting {
+ type nat hook postrouting priority srcnat; policy accept;
+ ip saddr 10.10.0.0/24 oif "{{ 'enp0s1f0' if dev_cluster == 'false' else 'eth1' }}" masquerade
+ }
+}
\ No newline at end of file
diff --git a/networking/templates/router-haproxy.cfg.j2 b/networking/templates/router-haproxy.cfg.j2
index ba7d5c5..d18efe4 100644
--- a/networking/templates/router-haproxy.cfg.j2
+++ b/networking/templates/router-haproxy.cfg.j2
@@ -49,4 +49,5 @@ backend apiserver
server {{nodename}} {{ hostvars[nodename]["ansible_"~
hostvars[nodename]["ansible_facts"]["target_interface"]]['ipv4']['address']
}}:6443 check
- {% endfor %}
\ No newline at end of file
+ {% endfor %}
+#---------------------------------------------------------------------
diff --git a/node/Dockerfile b/node/Dockerfile
index 6b8a6f7..bdc7bbd 100644
--- a/node/Dockerfile
+++ b/node/Dockerfile
@@ -1,3 +1,10 @@
FROM rockylinux:9
+RUN dnf install epel-release -y
+RUN dnf install lorax createrepo yum-utils -y
-RUN dnf install lorax -y
\ No newline at end of file
+RUN mkdir /NInux
+WORKDIR /NInux
+RUN yumdownloader --resolve dhcpcd avahi avahi-tools
+RUN createrepo -v /NInux
+
+WORKDIR /
\ No newline at end of file
diff --git a/node/add-epel-repos-playbook.yaml b/node/add-epel-repos-playbook.yaml
new file mode 100644
index 0000000..85bfc72
--- /dev/null
+++ b/node/add-epel-repos-playbook.yaml
@@ -0,0 +1,12 @@
+- name: Add EPEL repos to all nodes
+ hosts: nodes
+ tasks:
+ - name: Enable CRB
+ become: true
+ ansible.builtin.command: /usr/bin/dnf config-manager --set-enabled crb
+ changed_when: true # AFAIK there's no way to check if this is enabled or not
+ - name: Install EPEL repo
+ become: true
+ ansible.builtin.dnf:
+ name: "epel-release"
+ state: present
diff --git a/node/create-iso.sh b/node/create-iso.sh
index b66092f..9f6bea2 100755
--- a/node/create-iso.sh
+++ b/node/create-iso.sh
@@ -14,4 +14,4 @@ rm -rf ninux.iso
docker build . -t ninux-make-iso
-docker run -v .:/vol ninux-make-iso mkksiso /vol/ks.cfg /vol/rocky.iso /vol/ninux.iso
\ No newline at end of file
+docker run --privileged=true -v .:/vol ninux-make-iso mkksiso --add /NInux --ks /vol/ks.cfg /vol/rocky.iso /vol/ninux.iso
\ No newline at end of file
diff --git a/node/ks.cfg b/node/ks.cfg
index c428818..7445d8c 100644
--- a/node/ks.cfg
+++ b/node/ks.cfg
@@ -5,6 +5,7 @@
text
# Use CDROM installation media
cdrom
+repo --name=NInux --baseurl=file:///run/install/repo/NInux
%addon com_redhat_kdump --enable --reserve-mb='auto'
@@ -18,6 +19,10 @@ lang en_US.UTF-8
%packages
@^minimal-environment
@standard
+# TODO(luisd): Test if upgrades work
+avahi
+avahi-tools
+dhcpcd
%end
@@ -31,7 +36,7 @@ firstboot --enable
clearpart --all --drives=sda --initlabel
autopart --nohome
-bootloader
+bootloader --append="intel_iommu=on"
timesource --ntp-disable
# System timezone
@@ -67,4 +72,21 @@ install \
# TODO: fix this in the ansible side later
chmod +x /etc
+# Avahi related configs, for cluster bootstrapping in order to to use IPV4LL
+
+sed -i 's/publish-workstation=no/publish-workstation=yes/g' /etc/avahi/avahi-daemon.conf
+
+echo "[main]
+dhcp=dhcpcd" > /etc/NetworkManager/conf.d/dhcp-client.conf
+
+# Disable NetworkManager and use dhcpcd instead to use IPv4LL (NetworkManager configuration is buggy, at best)
+# we enable NetworkManager again when the full network is configured.
+
+# TODO(luisd): enable NetworkManager on ansible side after network configuration
+systemctl disable NetworkManager
+systemctl enable dhcpcd
+
+# Firewalld causes more problems than it help, on cluster bootstrapping. It interferes with RKE2 and mDNS,
+# making the cluster more difficult to deploy. In the future we can reenable-it and whitelist what's needed.
+systemctl disable firewalld
%end
\ No newline at end of file
diff --git a/node/packer.pkr.hcl b/node/packer.pkr.hcl
index 6720bc3..8d4ddb1 100644
--- a/node/packer.pkr.hcl
+++ b/node/packer.pkr.hcl
@@ -8,6 +8,10 @@ packer {
version = "~> 1"
source = "github.com/hashicorp/virtualbox"
}
+ qemu = {
+ version = ">= 1.0.10"
+ source = "github.com/hashicorp/qemu"
+ }
}
}
diff --git a/node/reenable-networkmanager-playbook.yaml b/node/reenable-networkmanager-playbook.yaml
new file mode 100644
index 0000000..1e46b5f
--- /dev/null
+++ b/node/reenable-networkmanager-playbook.yaml
@@ -0,0 +1,80 @@
+---
+
+- name: Stop dhcp kea server
+ hosts: routers
+ become: true
+ tasks:
+ - name: Delete old leases
+ become: true
+ ansible.builtin.command:
+ cmd: rm -f /var/lib/kea/kea-leases4.csv
+ changed_when: true
+
+ - name: Stop kea
+ ansible.builtin.systemd:
+ service: kea-dhcp4-server
+ state: stopped
+
+- name: Re-enable NetworkManager on nodes
+ hosts: nodes
+ become: true
+ tasks:
+ - name: Get service facts
+ ansible.builtin.service_facts:
+
+ - name: Remove dhcpcd if it's enabled and add NetworkManager
+ when: >
+ 'dhcpcd.service' in ansible_facts.services and ansible_facts.services['dhcpcd.service']['status'] == 'enabled'
+ block:
+ - name: Disable dhcpcd
+ ansible.builtin.systemd_service:
+ name: dhcpcd
+ enabled: false
+ state: stopped
+ - name: Enable NetworkManager
+ ansible.builtin.systemd_service:
+ name: NetworkManager
+ enabled: true
+ - name: Delete system-connections folder
+ ansible.builtin.file:
+ path: /etc/NetworkManager/system-connections
+ state: absent
+ - name: Create system-connections folder
+ ansible.builtin.file:
+ path: /etc/NetworkManager/system-connections
+ state: directory
+ mode: "744"
+ - name: Copy internal network template
+ ansible.builtin.template:
+ src: templates/internal-network.nmconnection.j2
+ dest: /etc/NetworkManager/system-connections/internal-network.nmconnection
+ mode: "600"
+ - name: Set flag for reboot
+ ansible.builtin.set_fact:
+ needs_reboot_dhcp: true
+
+- name: Start dhcp kea server
+ hosts: routers
+ become: true
+ tasks:
+ - name: Start kea
+ ansible.builtin.systemd:
+ service: kea-dhcp4-server
+ state: started
+
+
+- name: Restart node if necessary
+ hosts: nodes
+ tasks:
+ - name: Check if restart is necessary
+ when: needs_reboot_dhcp is defined
+ block:
+ - name: Get current kernel version for reboot
+ ansible.builtin.command:
+ cmd: uname -r
+ register: kernel_version
+ changed_when: false
+ - name: Reboot using kexec to apply the networkmanager changes
+ become: true
+ ansible.builtin.reboot:
+ reboot_command: "kexec /boot/vmlinuz-{{ kernel_version.stdout }} --initrd=/boot/initramfs-{{ kernel_version.stdout }}.img --reuse-cmdline"
diff --git a/node/templates/internal-network.nmconnection.j2 b/node/templates/internal-network.nmconnection.j2
new file mode 100644
index 0000000..646b5aa
--- /dev/null
+++ b/node/templates/internal-network.nmconnection.j2
@@ -0,0 +1,15 @@
+[connection]
+id={{ target_interface }}
+type=ethernet
+interface-name={{ target_interface }}
+
+[ethernet]
+
+[ipv4]
+method=auto
+
+[ipv6]
+addr-gen-mode=eui64
+method=auto
+
+[proxy]
diff --git a/requirements.yml b/requirements.yml
index d23b947..6ebe7ad 100644
--- a/requirements.yml
+++ b/requirements.yml
@@ -1,3 +1,6 @@
collections:
- ansible.posix
+ - community.libvirt
+ - ansible.utils
+ - community.general
- community.crypto
diff --git a/router/build-router-vm-playbook.yaml b/router/build-router-vm-playbook.yaml
new file mode 100644
index 0000000..e60870f
--- /dev/null
+++ b/router/build-router-vm-playbook.yaml
@@ -0,0 +1,161 @@
+- name: Build router VM in hosts that have the appropriate NICs
+ hosts: controlplane
+ tasks:
+ - name: Skip play if running in the development cluster
+ when: dev_cluster == "true"
+ ansible.builtin.meta: end_play
+ - name: Skip host if it doesnt have external_interface defined
+ when: external_interface is undefined
+ ansible.builtin.meta: end_host
+ - name: Import router vars
+ ansible.builtin.include_vars:
+ file: "router-vars.json"
+ - name: Check if libvirt is installed
+ ansible.builtin.service_facts:
+ - name: Check if router VM exists
+ become: true
+ community.libvirt.virt:
+ command: list_vms
+ register: routervm_stat
+ when: "'libvirtd.service' in ansible_facts.services"
+ # we only want to remove vfio if there isn't a router anymore in order to make this reproducible
+ - name: Remove vfio pci devices modprobe
+ become: true
+ when: '"libvirtd.service" in ansible_facts.services and "routerVM" not in routervm_stat.list_vms'
+ ansible.builtin.file:
+ path: /etc/modprobe.d/vfio.conf
+ state: absent
+ notify:
+ - Update initramfs and reboot
+ - name: Flush handlers
+ ansible.builtin.meta: flush_handlers
+ - name: Get PCI devices to find appropriate NICs
+ ansible.utils.cli_parse:
+ command: lspci -Dn
+ parser:
+ name: ansible.utils.textfsm
+ template_path: router/templates/lspci-parser.textfsm
+ set_fact: pci_devices
+ - name: Remove dhcpcd persistance
+ become: true
+ ansible.builtin.lineinfile:
+ path: /etc/dhcpcd.conf
+ regexp: "^persistent"
+ line: "#persistent"
+ notify:
+ - Restart dhcpcd
+ - name: Remove external_interface from dhcpcd
+ become: true
+ ansible.builtin.lineinfile:
+ path: /etc/dhcpcd.conf
+ regexp: "^denyinterfaces.*"
+ line: "denyinterfaces {{ external_interface }}"
+ notify:
+ - Restart dhcpcd
+ - name: Flush handlers
+ ansible.builtin.meta: flush_handlers
+
+ - name: Only include nodes that have suitable devices
+ when: >
+ (pci_devices | map(attribute="pciVendorDevice") | intersect(valid_nic_ids) | length) != 0
+ block:
+ - name: Assign one public IP per router
+ ansible.builtin.set_fact:
+ assigned_network: "{{ external_eth_config[play_hosts.index(inventory_hostname)] }}"
+ delegate_to: "{{ item }}"
+ with_items: "{{ ansible_play_hosts }}"
+
+ - name: Build new router VM
+ when: '"libvirtd.service" not in ansible_facts.services or "routerVM" not in routervm_stat.list_vms'
+ block:
+ - name: Get PCI ID per interface
+ ansible.builtin.set_fact:
+ device_by_pci_address: "{{ ansible_facts | json_query('@.* | [?pciid].{key: device, value: pciid}') | items2dict }}"
+ - name: Parse PCI ID of external interface
+ ansible.utils.cli_parse:
+ text: "{{ device_by_pci_address[external_interface] }}"
+ parser:
+ name: ansible.utils.textfsm
+ template_path: router/templates/pci-id-parser.textfsm
+ set_fact: external_interface_pci_device
+ - name: Filter pci_devices to include the same device as the external interface
+ ansible.builtin.set_fact:
+ pci_devices: "{{ pci_devices | selectattr('bus', 'equalto', external_interface_pci_device[0]['bus'])
+ | selectattr('device', 'equalto', external_interface_pci_device[0]['device'])
+ | selectattr('bus', 'equalto', external_interface_pci_device[0]['bus'])
+ }}"
+ - name: Set external interface up
+ become: true
+ ansible.builtin.command:
+ cmd: "ip link set up {{ external_interface }}"
+ changed_when: true
+ # This might fail a few times until the interface comes up
+ - name: Configure external ip to configure VM temporarily
+ become: true
+ retries: 3
+ delay: 5
+ ansible.builtin.command:
+ cmd: "ip addr add {{ assigned_network.ip + '/' + assigned_network.prefix }} dev {{ external_interface }}"
+ changed_when: true
+ - name: Configure default route
+ become: true
+ ansible.builtin.command:
+ cmd: "ip route add default dev {{ external_interface }} via {{ assigned_network.gateway }}"
+ changed_when: true
+ - name: Configure DNS
+ become: true
+ ansible.builtin.lineinfile:
+ dest: /etc/resolv.conf
+ line: "nameserver {{ assigned_network.nameservers[0] }}"
+ - name: Build VM disk image
+ ansible.builtin.include_tasks:
+ file: "vm-image-task.ansible.yaml"
+ - name: Enable vfio modules
+ become: true
+ community.general.modprobe:
+ state: present
+ persistent: present
+ name: "{{ item }}"
+ with_items:
+ - "vfio"
+ - "vfio_pci"
+ - "vfio_iommu_type1"
+ - name: Set device ids to load the vfio driver instead
+ become: true
+ ansible.builtin.template:
+ src: templates/vfio.conf.j2
+ dest: /etc/modprobe.d/vfio.conf
+ mode: "644"
+ notify:
+ - Update initramfs and reboot
+ - name: Flush handlers
+ ansible.builtin.meta: flush_handlers
+ - name: Create VM for router
+ become: true
+ community.libvirt.virt:
+ autostart: true
+ name: routerVM
+ command: define
+ xml: "{{ lookup('template', 'templates/libvirt-xml.j2') }}"
+ - name: Start VM
+ become: true
+ community.libvirt.virt:
+ name: routerVM
+ state: running
+ - name: Pause until user inputs router inventory settings
+ when: dev_cluster == "false"
+ ansible.builtin.pause:
+ prompt: "Please verify that the routers are properly up and add them to the corresponding inventory file"
+ - name: Refresh inventory
+ ansible.builtin.meta: refresh_inventory
+
+
+ handlers:
+ - name: Update initramfs and reboot
+ ansible.builtin.include_tasks:
+ file: "update-initramfs-tasks.ansible.yaml"
+ - name: Restart dhcpcd
+ become: true
+ ansible.builtin.systemd:
+ name: dhcpcd
+ state: restarted
diff --git a/router/router-vars.json b/router/router-vars.json
new file mode 100644
index 0000000..6fde611
--- /dev/null
+++ b/router/router-vars.json
@@ -0,0 +1,22 @@
+{
+ "valid_nic_ids": [
+ "8086:1528"
+ ],
+ "guest_nic_identifier": {
+ "domain": 0,
+ "bus": 0,
+ "device": 1,
+ "function": 0
+ },
+ "external_eth_config": [
+ {
+ "ip": "192.168.1.5",
+ "prefix": "24",
+ "gateway": "192.168.1.1",
+ "hw_addr": "c4:34:6b:5e:c6:45",
+ "nameservers": [
+ "1.1.1.1"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/router/templates/01-external.network.j2 b/router/templates/01-external.network.j2
new file mode 100644
index 0000000..718b805
--- /dev/null
+++ b/router/templates/01-external.network.j2
@@ -0,0 +1,12 @@
+[Match]
+Name=enp0s1f0
+
+[Link]
+MACAddress={{assigned_network.hw_addr}}
+
+[Network]
+Address={{assigned_network.ip}}/{{assigned_network.prefix}}
+Gateway={{assigned_network.gateway}}
+{% for dns in assigned_network.nameservers %}
+DNS={{ dns }}
+{% endfor %}
diff --git a/router/templates/99-ipv4ll.network b/router/templates/99-ipv4ll.network
new file mode 100644
index 0000000..2564fff
--- /dev/null
+++ b/router/templates/99-ipv4ll.network
@@ -0,0 +1,7 @@
+[Match]
+Name=*
+Type=ether
+
+[Network]
+LinkLocalAddressing=yes
+IPv4LLRoute=true
\ No newline at end of file
diff --git a/router/templates/libvirt-xml.j2 b/router/templates/libvirt-xml.j2
new file mode 100644
index 0000000..9de1151
--- /dev/null
+++ b/router/templates/libvirt-xml.j2
@@ -0,0 +1,183 @@
+
+ routerVM
+ 512
+ 512
+ 2
+
+ hvm
+
+
+
+
+
+
+
+
+
+
+
+
+ destroy
+ restart
+ restart
+
+
+
+
+
+ /usr/libexec/qemu-kvm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /dev/urandom
+
+
+
+{# FIXME(luisd): load accepted pci_vendor_devices from a variable #}
+{% for device in (pci_devices | selectattr("pciVendorDevice", "in",valid_nic_ids)) %}
+
+
+
+
+
+
+{% endfor %}
+
+
\ No newline at end of file
diff --git a/router/templates/lspci-parser.textfsm b/router/templates/lspci-parser.textfsm
new file mode 100644
index 0000000..4f47653
--- /dev/null
+++ b/router/templates/lspci-parser.textfsm
@@ -0,0 +1,8 @@
+Value domain ([0-9a-fA-F]{4})
+Value bus ([0-9a-fA-F]{2})
+Value device ([0-9a-fA-F]{2})
+Value function ([0-7])
+Value pciVendorDevice ([0-9a-fA-F]{4}:[0-9a-fA-F]{4})
+
+Start
+ ^${domain}:${bus}:${device}\.${function}.*: *${pciVendorDevice} -> Next.Record
\ No newline at end of file
diff --git a/router/templates/lspci-parser.yaml b/router/templates/lspci-parser.yaml
new file mode 100644
index 0000000..183a0a3
--- /dev/null
+++ b/router/templates/lspci-parser.yaml
@@ -0,0 +1,10 @@
+---
+- example: "0000:01:00.1 0200: 8086:1528 (rev 01)"
+ getval: '(?P[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]):(?P[0-9a-fA-F][0-9a-fA-F]):(?P[0-9a-fA-F][0-9a-fA-F])\.(?P[0-7])\s*([0-9]*)\s*:\s* (?P[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]).*'
+ result:
+ "{{ bus + ':' + device + '.' + function }}":
+ domain: "{{ domain }}"
+ bus: "{{ bus }}"
+ device: '{{ device }}'
+ function: "{{ function }}"
+ pci_vendor_device: "{{ pci_vendor_device }}"
\ No newline at end of file
diff --git a/router/templates/pci-id-parser.textfsm b/router/templates/pci-id-parser.textfsm
new file mode 100644
index 0000000..014efa6
--- /dev/null
+++ b/router/templates/pci-id-parser.textfsm
@@ -0,0 +1,8 @@
+Value domain ([0-9a-fA-F]{4})
+Value bus ([0-9a-fA-F]{2})
+Value device ([0-9a-fA-F]{2})
+Value function ([0-7])
+
+
+Start
+ ^${domain}:${bus}:${device}\.${function} -> Record
\ No newline at end of file
diff --git a/router/templates/vfio.conf.j2 b/router/templates/vfio.conf.j2
new file mode 100644
index 0000000..7f0031c
--- /dev/null
+++ b/router/templates/vfio.conf.j2
@@ -0,0 +1 @@
+options vfio-pci ids={{valid_nic_ids | join(',')}}
\ No newline at end of file
diff --git a/router/update-initramfs-tasks.ansible.yaml b/router/update-initramfs-tasks.ansible.yaml
new file mode 100644
index 0000000..16acd73
--- /dev/null
+++ b/router/update-initramfs-tasks.ansible.yaml
@@ -0,0 +1,16 @@
+- name: Update initramfs
+ become: true
+ ansible.builtin.command:
+ cmd: dracut -f -v
+ changed_when: true
+- name: Get current kernel version for reboot
+ ansible.builtin.command:
+ cmd: uname -r
+ register: kernel_version
+ changed_when: false
+- name: Reboot using kexec to apply the driver changes
+ become: true
+ ansible.builtin.reboot:
+ reboot_command: "kexec /boot/vmlinuz-{{ kernel_version.stdout }} --initrd=/boot/initramfs-{{ kernel_version.stdout }}.img --reuse-cmdline"
+- name: Re-gather facts because pci information might be outdated
+ ansible.builtin.gather_facts:
diff --git a/router/vm-image-task.ansible.yaml b/router/vm-image-task.ansible.yaml
new file mode 100644
index 0000000..e05cdb2
--- /dev/null
+++ b/router/vm-image-task.ansible.yaml
@@ -0,0 +1,162 @@
+- name: "Install virtualization utils"
+ become: true
+ ansible.builtin.dnf:
+ name:
+ - qemu-kvm
+ - libvirt
+ - virt-install
+ - guestfs-tools
+ state: present
+- name: "Enable libvirt service"
+ become: true
+ ansible.builtin.systemd:
+ name: libvirtd
+ state: "started"
+ enabled: true
+- name: "Create VM disks folder"
+ become: true
+ ansible.builtin.file:
+ recurse: true
+ state: directory
+ path: "/srv/vm/disks"
+ mode: "770"
+ group: qemu
+- name: "Create router disk folder"
+ become: true
+ ansible.builtin.file:
+ recurse: true
+ state: directory
+ path: "/srv/vm/disks/router"
+ mode: "770"
+ group: qemu
+- name: "Download router QCOW2 file"
+ retries: 3
+ become: true
+ ansible.builtin.get_url:
+ url: https://cloud.debian.org/images/cloud/bookworm/20240211-1654/debian-12-generic-amd64-20240211-1654.qcow2
+ dest: /srv/vm/disks/router/router.qcow2
+ checksum: sha512:b679398972ba45a60574d9202c4f97ea647dd3577e857407138b73b71a3c3c039804e40aac2f877f3969676b6c8a1ebdb4f2d67a4efa6301c21e349e37d43ef5
+ mode: "770"
+- name: "Resize router QCOW2 file"
+ become: true
+ ansible.builtin.command:
+ cmd: qemu-img resize /srv/vm/disks/router/router.qcow2 5G
+ changed_when: true
+
+- name: "Resize router QCOW2 partition"
+ block:
+ - name: "Change old file router name"
+ become: true
+ ansible.builtin.command:
+ cmd: cp /srv/vm/disks/router/router.qcow2 /srv/vm/disks/router/router.qcow2.old
+ creates: /srv/vm/disks/router/router.qcow2.old
+ changed_when: true
+ - name: "Resize filesystem partition for router"
+ become: true
+ ansible.builtin.command:
+ cmd: virt-resize --expand /dev/sda1 /srv/vm/disks/router/router.qcow2.old /srv/vm/disks/router/router.qcow2
+ changed_when: true
+ - name: "Delete old router qcow file"
+ become: true
+ ansible.builtin.file:
+ path: /srv/vm/disks/router/router.qcow2.old
+ state: absent
+
+- name: Create user on vm
+ become: true
+ ansible.builtin.command:
+ cmd: virt-customize -a /srv/vm/disks/router/router.qcow2 --run-command 'useradd -m -s /bin/bash -g sudo ni'
+ changed_when: true
+
+- name: Create password on user
+ become: true
+ ansible.builtin.command:
+ cmd: virt-customize -a /srv/vm/disks/router/router.qcow2 --run-command 'echo "ni:n1bl04t:)" | chpasswd'
+ changed_when: true
+
+- name: "Create mount directory"
+ become: true
+ ansible.builtin.file:
+ state: directory
+ path: "/mnt/router"
+ mode: "644"
+
+- name: Update package repos
+ become: true
+ ansible.builtin.command:
+ cmd: virt-customize -a /srv/vm/disks/router/router.qcow2 --run-command 'apt-get update'
+ changed_when: true
+
+- name: Install Avahi on VM
+ become: true
+ ansible.builtin.command:
+ cmd: virt-customize -a /srv/vm/disks/router/router.qcow2 --run-command 'apt-get install -y avahi-daemon avahi-utils'
+ changed_when: true
+
+
+- name: Add host private key files
+ become: true
+ ansible.builtin.command:
+ cmd: virt-customize -a /srv/vm/disks/router/router.qcow2 --run-command 'ssh-keygen -A'
+ changed_when: true
+
+- name: "VM file provisioning"
+ block:
+ - name: "Mount router image"
+ become: true
+ ansible.builtin.command: guestmount -a /srv/vm/disks/router/router.qcow2 -m /dev/sda3 /mnt/router
+ changed_when: true
+ register: image_create_dev
+ failed_when: image_create_dev.rc != 0
+ - name: Create .ssh folder on vm
+ become: true
+ ansible.builtin.file:
+ path: /mnt/router/home/{{ ansible_ssh_user }}/.ssh
+ state: directory
+ owner: "{{ ansible_ssh_user }}"
+ group: "{{ ansible_ssh_user }}"
+ mode: "755"
+ - name: Copy authorized_keys to vm
+ become: true
+ ansible.builtin.copy:
+ src: /home/ni/.ssh/authorized_keys
+ dest: /mnt/router/home/ni/.ssh/authorized_keys
+ remote_src: true
+ mode: "644"
+ - name: Add ni user to sudoers password
+ become: true
+ ansible.builtin.copy:
+ dest: /mnt/router/etc/sudoers.d/dont-prompt-ni_user-for-sudo-password
+ content: 'ni ALL=(ALL) NOPASSWD:ALL'
+ mode: "644"
+ - name: Copy avahi dhcp fallback to vm
+ become: true
+ ansible.builtin.copy:
+ src: templates/99-ipv4ll.network
+ dest: /mnt/router/etc/systemd/network/99-ipv4ll.network
+ mode: "644"
+ - name: Copy interface network config to vm
+ become: true
+ ansible.builtin.template:
+ src: templates/01-external.network.j2
+ dest: /mnt/router/etc/systemd/network/01-external.network
+ mode: "644"
+ - name: Enable avahi discoverability
+ become: true
+ ansible.builtin.lineinfile:
+ path: /mnt/router/etc/avahi/avahi-daemon.conf
+ regexp: '^publish-workstation='
+ line: publish-workstation=yes
+ always:
+ - name: "Umount router partition"
+ become: true
+ ansible.builtin.command:
+ cmd: guestunmount /mnt/router
+ failed_when: false
+ changed_when: true
+
+- name: Fix grub on VM
+ become: true
+ ansible.builtin.command:
+ cmd: virt-customize -a /srv/vm/disks/router/router.qcow2 --run-command 'grub-install /dev/sda'
+ changed_when: true