|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -o errexit -o pipefail |
| 3 | + |
| 4 | +# --- Helper functions |
| 5 | + |
| 6 | +log() { echo "$*" >&2; } |
| 7 | +die() { log "ERROR: $*"; exit 1; } |
| 8 | + |
| 9 | +# --- Basic asserts |
| 10 | + |
| 11 | +type -p arping find ip jq ovs-vsctl &>/dev/null |
| 12 | + |
| 13 | +{% for iface in ovs.iface.keys() %} |
| 14 | +# {{ iface }} |
| 15 | +log 'Asserting {{ iface }} exists' |
| 16 | +ip link show dev '{{ iface }}' &>/dev/null || die 'Interface {{ iface }} does not exist' |
| 17 | +{% endfor %} |
| 18 | + |
| 19 | +# --- Bridge creation |
| 20 | + |
| 21 | +{% for br, v in ovs.br.items() %} |
| 22 | +# {{ br }} |
| 23 | +if ovs-vsctl br-exists '{{ br }}' &>/dev/null; then |
| 24 | + log 'WARNING: Bridge {{ br }} already exists, skipping creation' |
| 25 | +else |
| 26 | + log 'Creating OVS bridge {{ br }}' |
| 27 | + ovs-vsctl add-br '{{ br }}' || die 'Failed to create bridge' |
| 28 | +fi |
| 29 | +{% for port in v.ports %} |
| 30 | +{% if port in ovs.iface %} |
| 31 | +if ovs-vsctl port-to-br '{{ port }}' &>/dev/null; then |
| 32 | + log 'WARNING: Port {{ port }} already exists, skipping creation' |
| 33 | +else |
| 34 | + log 'Adding port {{ port }} to {{ br }}' |
| 35 | + ovs-vsctl add-port '{{ br }}' '{{ port }}' || die 'Failed to add port' |
| 36 | +fi |
| 37 | +{% endif %} |
| 38 | +{% if port in ovs.bond %} |
| 39 | +if ovs-vsctl port-to-br '{{ port }}' &>/dev/null; then |
| 40 | + log 'WARNING: Bond {{ port }} already exists, skipping creation' |
| 41 | +else |
| 42 | + log 'Adding bond {{ port }} to {{ br }}' |
| 43 | + ovs-vsctl add-bond '{{ br }}' '{{ port }}' \ |
| 44 | + {{ ovs.bond[port].ifaces | map('regex_replace', '^(.*)$', "'\g<1>'") | join(' ') }} \ |
| 45 | + bond_mode='{{ ovs.bond[port].mode | d('active-backup') }}' \ |
| 46 | + other_config:bond-primary='{{ ovs.bond[port].ifaces | first }}' || die 'Failed to add bond' |
| 47 | +fi |
| 48 | +{% endif %} |
| 49 | +{% endfor %} |
| 50 | +{% if v.vlan is defined and (v.vlan is number or v.vlan is string) %} |
| 51 | +if [[ "$(ovs-vsctl get port '{{ br }}' tag)" == '{{ v.vlan }}' ]]; then |
| 52 | + log 'WARNING: VLAN tag {{ v.vlan }} already set on {{ br }}, skipping' |
| 53 | +else |
| 54 | + log 'Setting VLAN tag {{ v.vlan }} on {{ br }}' |
| 55 | + ovs-vsctl set port '{{ br }}' tag='{{ v.vlan }}' || die 'Failed to set VLAN tag' |
| 56 | +fi |
| 57 | +{% endif %} |
| 58 | +{% endfor %} |
| 59 | + |
| 60 | +# --- Networking cleanup |
| 61 | + |
| 62 | +if type -p systemctl &>/dev/null && systemctl is-active --quiet NetworkManager; then |
| 63 | + log 'Stopping and disabling NetworkManager' |
| 64 | + systemctl disable NetworkManager || log 'WARNING: Failed to disable NetworkManager' |
| 65 | + systemctl stop NetworkManager || log 'WARNING: Failed to stop NetworkManager' |
| 66 | +fi |
| 67 | + |
| 68 | +if type -p netplan &>/dev/null && netplan status -f json | jq -re '."netplan-global-state"."online"' &>/dev/null; then |
| 69 | + log 'Disabling Netplan' |
| 70 | + find /etc/netplan/ -type f -name '*.yaml' -exec mv {} {}.bak \; |
| 71 | + netplan apply || log 'WARNING: Failed to disable Netplan' |
| 72 | +fi |
| 73 | + |
| 74 | +{% for vlan in ovs.br.values() | selectattr('vlan', 'defined') | map(attribute='vlan') %} |
| 75 | +{% for iface in ovs.iface.keys() %} |
| 76 | +# {{ iface }}.{{ vlan }} |
| 77 | +if ip link show '{{ iface }}.{{ vlan }}' &>/dev/null; then |
| 78 | + log 'Deleting VLAN interface {{ iface }}.{{ vlan }}' |
| 79 | + ip link delete '{{ iface }}.{{ vlan }}' || log 'WARNING: Failed to delete {{ iface }}.{{ vlan }}' |
| 80 | +fi |
| 81 | +{% endfor %} |
| 82 | +{% endfor %} |
| 83 | + |
| 84 | +# --- IP / MTU configuration |
| 85 | + |
| 86 | +{% for dev, v in ((ovs.iface.items() | list) + (ovs.br.items() | list)) %} |
| 87 | +# {{ dev }} |
| 88 | +log 'Flushing {{ dev }}' |
| 89 | +ip addr flush dev '{{ dev }}' || die 'Failed to flush {{ dev }}' |
| 90 | +{% for a in v.addrs | d([]) %} |
| 91 | +log 'Adding IP address {{ a.cidr }} to {{ dev }}' |
| 92 | +ip addr add '{{ a.cidr }}' dev '{{ dev }}' {{ "metric '{}'".format(a.metric) if a.metric is defined else "" }} || die 'Failed to add IP address' |
| 93 | +{% endfor %} |
| 94 | +{% if v.mtu is defined and (v.mtu is number or v.mtu is string) %} |
| 95 | +if [[ "$(ovs-vsctl get Interface '{{ dev }}' mtu_request)" == '{{ v.mtu }}' ]]; then |
| 96 | + log 'WARNING: MTU {{ v.mtu }} already set on {{ dev }}, skipping' |
| 97 | +else |
| 98 | + log 'Setting MTU {{ v.mtu }} on {{ dev }}' |
| 99 | + ovs-vsctl set Interface '{{ dev }}' mtu_request='{{ v.mtu }}' || die 'Failed to set MTU' |
| 100 | +fi |
| 101 | +{% endif %} |
| 102 | +log 'Bringing up {{ dev }}' |
| 103 | +ip link set dev '{{ dev }}' up || die 'Failed to bring up {{ dev }}' |
| 104 | +{% endfor %} |
| 105 | + |
| 106 | +# --- Default route |
| 107 | + |
| 108 | +{% set gw = (((ovs.iface.values() | list) + (ovs.br.values() | list)) | selectattr('gw', 'defined') | first).gw | d(None) %} |
| 109 | +{% if gw is not none %} |
| 110 | +if ip --json route show default | jq -re '. != []' &>/dev/null; then |
| 111 | + log 'WARNING: Default route already exists' |
| 112 | +else |
| 113 | + log 'Adding default route via {{ gw }}' |
| 114 | + ip route add default via '{{ gw }}' || die 'Failed to add default route' |
| 115 | +fi |
| 116 | +{% endif %} |
| 117 | + |
| 118 | +# --- Nameserver configuration |
| 119 | + |
| 120 | +if type -p systemctl resolvectl &>/dev/null && systemctl is-active --quiet systemd-resolved; then |
| 121 | +{% for dev, v in ((ovs.iface.items() | list) + (ovs.br.items() | list)) %} |
| 122 | + # {{ dev }} |
| 123 | +{% if v.dns is defined and v.dns is sequence %} |
| 124 | + log 'Setting nameservers for {{ dev }}' |
| 125 | + resolvectl dns '{{ dev }}' {{ v.dns | map('regex_replace', '^(.*)$', "'\g<1>'") | join(' ') }} || die 'Failed to setup nameservers' |
| 126 | +{% endif %} |
| 127 | +{% endfor %} |
| 128 | +fi |
| 129 | + |
| 130 | +# --- Networking refresh |
| 131 | + |
| 132 | +{% for br, v in ovs.br.items() %} |
| 133 | +# {{ br }} |
| 134 | +{% for a in v.addrs | d([]) %} |
| 135 | +log 'Sending gratuitous ARP for {{ a.cidr }} on {{ br }}' |
| 136 | +arping -c 3 -A -I '{{ br }}' '{{ a.cidr.split('/') | first }}' || log 'WARNING: arping failed' |
| 137 | +{% endfor %} |
| 138 | +{% endfor %} |
| 139 | + |
| 140 | +# --- |
| 141 | + |
| 142 | +log 'OVS network configuration completed successfully' |
| 143 | +exit 0 |
0 commit comments