From 13a2cc6968f3b99a3183226008b530ed688865ba Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 07:28:40 +0200 Subject: [PATCH 1/9] nocloud: add minimal network-config v2 support --- lib/tiny-cloud/cloud/nocloud/init | 48 +++++++++++++++++++++++++++++++ tests/test_env.sh | 6 +++- tests/tiny-cloud-alpine.test | 21 ++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index 8795a4e..2060ac1 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -4,6 +4,12 @@ INIT_ACTIONS_BOOT="$(replace_word set_default_interfaces set_network_interfaces $INIT_ACTIONS_BOOT)" +get_network_config() { + IFS="/" + yx -f "$TINY_CLOUD_VAR/network-config" $1 2>/dev/null + unset IFS +} + set_resolv_conf() { # resolv.conf local nameservers="$(imds meta-data/resolv_conf/nameservers)" @@ -43,11 +49,53 @@ want_ephemeral_network() { return 1 } +set_network_config_v2() { + [ -s "$TINY_CLOUD_VAR/network-config" ] || return 1 + local version="$(get_network_config version)" + if [ "$version" != "2" ]; then + log -i -t "$phase" warning "$ACTION: unsupported NoCloud network-config version: ${version:-unknown}" + return 1 + fi + + local ethernets="$(get_network_config ethernets)" + if [ -z "$ethernets" ]; then + log -i -t "$phase" warning "$ACTION: NoCloud network-config v2 has no supported ethernets entries" + return 1 + fi + + printf "%s\n%s\n\n" \ + "auto lo" \ + "iface lo inet loopback" \ + > "$ETC/network/interfaces" + + local iface configured= + for iface in $ethernets; do + case "$(get_network_config ethernets/$iface/dhcp4)" in + true|yes) + printf "%s\n%s\n\t%s\n\n" \ + "auto $iface" \ + "iface $iface" \ + "use dhcp" >> "$ETC/network/interfaces" + configured=1 + ;; + esac + done + + if [ -z "$configured" ]; then + log -i -t "$phase" warning "$ACTION: NoCloud network-config v2 has no supported ethernets entries" + return 1 + fi + + return 0 +} + init__set_network_interfaces() { local interfaces="$(imds meta-data/network-interfaces)" mkdir -p "$ETC"/network if [ -n "$interfaces" ]; then printf "%s\n" "$interfaces" > "$ETC"/network/interfaces + elif set_network_config_v2; then + : elif ! [ -f "$ETC"/network/interfaces ]; then init__set_default_interfaces fi diff --git a/tests/test_env.sh b/tests/test_env.sh index d807959..76dc87d 100644 --- a/tests/test_env.sh +++ b/tests/test_env.sh @@ -36,7 +36,7 @@ fake_umount() { while ! [ -d "\$1" ]; do shift done - rm -f "\$1"/meta-data "\$1"/user-data + rm -f "\$1"/meta-data "\$1"/user-data "\$1"/network-config EOF } @@ -64,6 +64,10 @@ fake_userdata_nocloud() { fake_data_nocloud user-data } +fake_network_config_nocloud() { + fake_data_nocloud network-config +} + fake_metadata_aws() { cat > "169.254.169.254.yaml" export WGET_STRIP_PREFIX="/latest/meta-data" diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index b9024f3..059bc1e 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -11,6 +11,7 @@ export CLOUD=nocloud init_tests \ set_ephemeral_network_cmdline \ set_network_config_network_interfaces \ + set_network_config_v2_dhcp4 \ set_network_config_auto \ userdata_user_name \ userdata_user_homedir \ @@ -100,6 +101,26 @@ set_network_config_network_interfaces_body() { cat etc/resolv.conf } +set_network_config_v2_dhcp4_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + eth1: + dhcp4: true + EOF + + atf_check \ + -o match:"rc-update" \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto eth1" \ + -o match:"iface eth1" \ + -o match:"use dhcp" \ + cat etc/network/interfaces +} + set_network_config_auto_body() { fake_metadata_nocloud <<-EOF resolv_conf: From 90e028231e3c080a0936b0591f11a68a19f0c0a4 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 08:49:12 +0200 Subject: [PATCH 2/9] nocloud: configure macifrename for v2 renames --- lib/tiny-cloud/cloud/nocloud/init | 40 ++++++++++++++++++++-- tests/tiny-cloud-alpine.test | 57 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index 2060ac1..d7dbdd0 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -67,14 +67,23 @@ set_network_config_v2() { "auto lo" \ "iface lo inet loopback" \ > "$ETC/network/interfaces" + rm -f "$ETC/iftab" - local iface configured= + local iface configured= target_name macaddress for iface in $ethernets; do + target_name="$(get_network_config ethernets/$iface/set-name)" + macaddress="$(get_network_config ethernets/$iface/match/macaddress)" + [ -n "$target_name" ] || target_name="$iface" + + if [ -n "$target_name" ] && [ -n "$macaddress" ]; then + printf "%s %s\n" "$target_name" "$macaddress" >> "$ETC/iftab" + fi + case "$(get_network_config ethernets/$iface/dhcp4)" in true|yes) printf "%s\n%s\n\t%s\n\n" \ - "auto $iface" \ - "iface $iface" \ + "auto $target_name" \ + "iface $target_name" \ "use dhcp" >> "$ETC/network/interfaces" configured=1 ;; @@ -89,6 +98,30 @@ set_network_config_v2() { return 0 } +setup_macifrename() { + [ -s "$ETC/iftab" ] || return 0 + + if ! [ -e "$ETC/init.d/macifrename" ]; then + log -i -t "$phase" warning "$ACTION: macifrename service not installed; interface renames will not be applied" + return 0 + fi + + mkdir -p "$ETC/conf.d" + if [ -f "$ETC/conf.d/macifrename" ]; then + if grep -q '^MACIFRENAME_OPTS=' "$ETC/conf.d/macifrename"; then + sed -i -e 's|^MACIFRENAME_OPTS=.*|MACIFRENAME_OPTS="/etc/iftab"|' \ + "$ETC/conf.d/macifrename" + else + printf '%s\n' 'MACIFRENAME_OPTS="/etc/iftab"' >> "$ETC/conf.d/macifrename" + fi + else + printf '%s\n' 'MACIFRENAME_OPTS="/etc/iftab"' > "$ETC/conf.d/macifrename" + fi + + $MOCK rc-update add macifrename boot + $MOCK rc-service macifrename start +} + init__set_network_interfaces() { local interfaces="$(imds meta-data/network-interfaces)" mkdir -p "$ETC"/network @@ -99,6 +132,7 @@ init__set_network_interfaces() { elif ! [ -f "$ETC"/network/interfaces ]; then init__set_default_interfaces fi + setup_macifrename if ! grep -q dhcp "$ETC"/network/interfaces; then set_resolv_conf fi diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index 059bc1e..5d3dd5a 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -12,6 +12,8 @@ init_tests \ set_ephemeral_network_cmdline \ set_network_config_network_interfaces \ set_network_config_v2_dhcp4 \ + set_network_config_v2_match_macaddress_set_name_dhcp4 \ + set_network_config_v2_configures_macifrename_service \ set_network_config_auto \ userdata_user_name \ userdata_user_homedir \ @@ -121,6 +123,61 @@ set_network_config_v2_dhcp4_body() { cat etc/network/interfaces } +set_network_config_v2_match_macaddress_set_name_dhcp4_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + dhcp4: true + EOF + + atf_check \ + -o match:"rc-update" \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"use dhcp" \ + cat etc/network/interfaces + + atf_check \ + -o match:"^lima0 52:55:55:1d:2a:0b$" \ + cat etc/iftab +} + +set_network_config_v2_configures_macifrename_service_body() { + mkdir -p etc/conf.d etc/init.d + cat > etc/conf.d/macifrename <<-EOF + MACIFRENAME_OPTS="/etc/foo" + EOF + touch etc/init.d/macifrename + + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + dhcp4: true + EOF + + atf_check \ + -o match:"rc-update add macifrename boot" \ + -o match:"rc-service macifrename start" \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:'^MACIFRENAME_OPTS="/etc/iftab"$' \ + cat etc/conf.d/macifrename +} + set_network_config_auto_body() { fake_metadata_nocloud <<-EOF resolv_conf: From dfe114dbed3060e0ea88166a72025b7d8b7545a6 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 12:45:49 +0200 Subject: [PATCH 3/9] nocloud: add static IPv4 network-config support --- lib/tiny-cloud/cloud/nocloud/init | 20 +++++++++++++++++ tests/tiny-cloud-alpine.test | 37 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index d7dbdd0..9e50ba2 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -87,6 +87,26 @@ set_network_config_v2() { "use dhcp" >> "$ETC/network/interfaces" configured=1 ;; + *) + local address gateway nameservers server + address="$(get_network_config ethernets/$iface/addresses/1)" + [ -n "$address" ] || continue + printf "%s\n%s\n\taddress %s\n" \ + "auto $target_name" \ + "iface $target_name" \ + "$address" >> "$ETC/network/interfaces" + gateway="$(get_network_config ethernets/$iface/gateway4)" + if [ -n "$gateway" ]; then + printf "\tgateway %s\n" "$gateway" >> "$ETC/network/interfaces" + fi + printf "\n" >> "$ETC/network/interfaces" + nameservers="$(get_network_config ethernets/$iface/nameservers/addresses)" + for server in $nameservers; do + add_once "$ETC/resolv.conf" \ + "nameserver $(get_network_config ethernets/$iface/nameservers/addresses/$server)" + done + configured=1 + ;; esac done diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index 5d3dd5a..f8d683b 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -14,6 +14,7 @@ init_tests \ set_network_config_v2_dhcp4 \ set_network_config_v2_match_macaddress_set_name_dhcp4 \ set_network_config_v2_configures_macifrename_service \ + set_network_config_v2_static_ipv4 \ set_network_config_auto \ userdata_user_name \ userdata_user_homedir \ @@ -170,6 +171,7 @@ set_network_config_v2_configures_macifrename_service_body() { atf_check \ -o match:"rc-update add macifrename boot" \ -o match:"rc-service macifrename start" \ + -o match:"rc-update add sshd default" \ -e match:"set_network_interfaces: done" \ tiny-cloud boot @@ -178,6 +180,41 @@ set_network_config_v2_configures_macifrename_service_body() { cat etc/conf.d/macifrename } +set_network_config_v2_static_ipv4_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + addresses: + - 192.168.100.10/24 + gateway4: 192.168.100.1 + nameservers: + addresses: + - 8.8.8.8 + - 8.8.4.4 + EOF + + atf_check \ + -o ignore \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"address 192.168.100.10/24" \ + -o match:"gateway 192.168.100.1" \ + cat etc/network/interfaces + + atf_check \ + -o match:"^nameserver 8.8.8.8$" \ + -o match:"^nameserver 8.8.4.4$" \ + cat etc/resolv.conf +} + set_network_config_auto_body() { fake_metadata_nocloud <<-EOF resolv_conf: From 727963829ea482c80fb6d22766c2bee00353e6f0 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 12:45:56 +0200 Subject: [PATCH 4/9] nocloud: add static IPv6 network-config support --- lib/tiny-cloud/cloud/nocloud/init | 1 + tests/tiny-cloud-alpine.test | 36 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index 9e50ba2..dc84951 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -96,6 +96,7 @@ set_network_config_v2() { "iface $target_name" \ "$address" >> "$ETC/network/interfaces" gateway="$(get_network_config ethernets/$iface/gateway4)" + [ -n "$gateway" ] || gateway="$(get_network_config ethernets/$iface/gateway6)" if [ -n "$gateway" ]; then printf "\tgateway %s\n" "$gateway" >> "$ETC/network/interfaces" fi diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index f8d683b..5a9818a 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -15,6 +15,7 @@ init_tests \ set_network_config_v2_match_macaddress_set_name_dhcp4 \ set_network_config_v2_configures_macifrename_service \ set_network_config_v2_static_ipv4 \ + set_network_config_v2_static_ipv6 \ set_network_config_auto \ userdata_user_name \ userdata_user_homedir \ @@ -215,6 +216,41 @@ set_network_config_v2_static_ipv4_body() { cat etc/resolv.conf } +set_network_config_v2_static_ipv6_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + addresses: + - 2001:db8::10/64 + gateway6: 2001:db8::1 + nameservers: + addresses: + - 2001:4860:4860::8888 + - 2001:4860:4860::8844 + EOF + + atf_check \ + -o ignore \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"address 2001:db8::10/64" \ + -o match:"gateway 2001:db8::1" \ + cat etc/network/interfaces + + atf_check \ + -o match:"^nameserver 2001:4860:4860::8888$" \ + -o match:"^nameserver 2001:4860:4860::8844$" \ + cat etc/resolv.conf +} + set_network_config_auto_body() { fake_metadata_nocloud <<-EOF resolv_conf: From 584551e3311c1f1a04affe3caf83735dd10cedd0 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 12:46:05 +0200 Subject: [PATCH 5/9] nocloud: add IPv6 autoconfiguration support --- lib/tiny-cloud/cloud/nocloud/init | 80 ++++++++++++++++++++----------- tests/tiny-cloud-alpine.test | 50 +++++++++++++++++++ 2 files changed, 103 insertions(+), 27 deletions(-) diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index dc84951..af08c0e 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -71,6 +71,9 @@ set_network_config_v2() { local iface configured= target_name macaddress for iface in $ethernets; do + local stanza= dhcp4= dhcp6= accept_ra= + local address= gateway= nameservers= server= + target_name="$(get_network_config ethernets/$iface/set-name)" macaddress="$(get_network_config ethernets/$iface/match/macaddress)" [ -n "$target_name" ] || target_name="$iface" @@ -79,36 +82,59 @@ set_network_config_v2() { printf "%s %s\n" "$target_name" "$macaddress" >> "$ETC/iftab" fi - case "$(get_network_config ethernets/$iface/dhcp4)" in + dhcp4="$(get_network_config ethernets/$iface/dhcp4)" + dhcp6="$(get_network_config ethernets/$iface/dhcp6)" + accept_ra="$(get_network_config ethernets/$iface/accept-ra)" + + case "$dhcp4" in true|yes) - printf "%s\n%s\n\t%s\n\n" \ - "auto $target_name" \ - "iface $target_name" \ - "use dhcp" >> "$ETC/network/interfaces" - configured=1 - ;; - *) - local address gateway nameservers server - address="$(get_network_config ethernets/$iface/addresses/1)" - [ -n "$address" ] || continue - printf "%s\n%s\n\taddress %s\n" \ - "auto $target_name" \ - "iface $target_name" \ - "$address" >> "$ETC/network/interfaces" - gateway="$(get_network_config ethernets/$iface/gateway4)" - [ -n "$gateway" ] || gateway="$(get_network_config ethernets/$iface/gateway6)" - if [ -n "$gateway" ]; then - printf "\tgateway %s\n" "$gateway" >> "$ETC/network/interfaces" - fi - printf "\n" >> "$ETC/network/interfaces" - nameservers="$(get_network_config ethernets/$iface/nameservers/addresses)" - for server in $nameservers; do - add_once "$ETC/resolv.conf" \ - "nameserver $(get_network_config ethernets/$iface/nameservers/addresses/$server)" - done - configured=1 + stanza="$stanza\tuse dhcp\n" ;; esac + + case "$dhcp6" in + true|yes) + case "$dhcp4" in + true|yes) + : + ;; + *) + stanza="$stanza\tuse dhcp\n" + stanza="$stanza\tdhcp-program dhcpcd\n" + stanza="$stanza\tdhcp-opts -6\n" + ;; + esac + ;; + esac + + case "$accept_ra" in + true|yes) + stanza="$stanza\tuse ipv6-ra\n" + ;; + esac + + address="$(get_network_config ethernets/$iface/addresses/1)" + if [ -n "$address" ]; then + stanza="$stanza\taddress $address\n" + gateway="$(get_network_config ethernets/$iface/gateway4)" + [ -n "$gateway" ] || gateway="$(get_network_config ethernets/$iface/gateway6)" + if [ -n "$gateway" ]; then + stanza="$stanza\tgateway $gateway\n" + fi + nameservers="$(get_network_config ethernets/$iface/nameservers/addresses)" + for server in $nameservers; do + add_once "$ETC/resolv.conf" \ + "nameserver $(get_network_config ethernets/$iface/nameservers/addresses/$server)" + done + fi + + [ -n "$stanza" ] || continue + + printf "%s\n%s\n%b\n" \ + "auto $target_name" \ + "iface $target_name" \ + "$stanza" >> "$ETC/network/interfaces" + configured=1 done if [ -z "$configured" ]; then diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index 5a9818a..a3439af 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -16,6 +16,8 @@ init_tests \ set_network_config_v2_configures_macifrename_service \ set_network_config_v2_static_ipv4 \ set_network_config_v2_static_ipv6 \ + set_network_config_v2_dhcp6 \ + set_network_config_v2_accept_ra \ set_network_config_auto \ userdata_user_name \ userdata_user_homedir \ @@ -251,6 +253,54 @@ set_network_config_v2_static_ipv6_body() { cat etc/resolv.conf } +set_network_config_v2_dhcp6_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + dhcp6: true + EOF + + atf_check \ + -o ignore \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"use dhcp" \ + -o match:"dhcp-program dhcpcd" \ + -o match:"dhcp-opts -6" \ + cat etc/network/interfaces +} + +set_network_config_v2_accept_ra_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + accept-ra: true + EOF + + atf_check \ + -o ignore \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"use ipv6-ra" \ + cat etc/network/interfaces +} + set_network_config_auto_body() { fake_metadata_nocloud <<-EOF resolv_conf: From e7175bd9c15fe71923fc98a4297ae629b4d5ffd1 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 12:46:20 +0200 Subject: [PATCH 6/9] nocloud: add route-metric support --- lib/tiny-cloud/cloud/nocloud/init | 29 +++++++++++++--- tests/tiny-cloud-alpine.test | 56 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index af08c0e..12faab7 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -71,8 +71,9 @@ set_network_config_v2() { local iface configured= target_name macaddress for iface in $ethernets; do - local stanza= dhcp4= dhcp6= accept_ra= + local stanza= dhcp4= dhcp6= accept_ra= dhcp_program= dhcp_opts= local address= gateway= nameservers= server= + local route_metric= target_name="$(get_network_config ethernets/$iface/set-name)" macaddress="$(get_network_config ethernets/$iface/match/macaddress)" @@ -89,6 +90,11 @@ set_network_config_v2() { case "$dhcp4" in true|yes) stanza="$stanza\tuse dhcp\n" + route_metric="$(get_network_config ethernets/$iface/dhcp4-overrides/route-metric)" + if [ -n "$route_metric" ]; then + dhcp_program="dhcpcd" + dhcp_opts="$dhcp_opts -m $route_metric" + fi ;; esac @@ -96,12 +102,20 @@ set_network_config_v2() { true|yes) case "$dhcp4" in true|yes) - : + route_metric="$(get_network_config ethernets/$iface/dhcp6-overrides/route-metric)" + if [ -n "$route_metric" ]; then + dhcp_program="dhcpcd" + dhcp_opts="$dhcp_opts -m $route_metric" + fi ;; *) stanza="$stanza\tuse dhcp\n" - stanza="$stanza\tdhcp-program dhcpcd\n" - stanza="$stanza\tdhcp-opts -6\n" + dhcp_program="dhcpcd" + dhcp_opts="$dhcp_opts -6" + route_metric="$(get_network_config ethernets/$iface/dhcp6-overrides/route-metric)" + if [ -n "$route_metric" ]; then + dhcp_opts="$dhcp_opts -m $route_metric" + fi ;; esac ;; @@ -128,6 +142,13 @@ set_network_config_v2() { done fi + if [ -n "$dhcp_program" ]; then + stanza="$stanza\tdhcp-program $dhcp_program\n" + fi + if [ -n "$dhcp_opts" ]; then + stanza="$stanza\tdhcp-opts${dhcp_opts}\n" + fi + [ -n "$stanza" ] || continue printf "%s\n%s\n%b\n" \ diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index a3439af..52d0435 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -18,6 +18,8 @@ init_tests \ set_network_config_v2_static_ipv6 \ set_network_config_v2_dhcp6 \ set_network_config_v2_accept_ra \ + set_network_config_v2_dhcp4_route_metric \ + set_network_config_v2_dhcp6_route_metric \ set_network_config_auto \ userdata_user_name \ userdata_user_homedir \ @@ -301,6 +303,60 @@ set_network_config_v2_accept_ra_body() { cat etc/network/interfaces } +set_network_config_v2_dhcp4_route_metric_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + dhcp4: true + dhcp4-overrides: + route-metric: 200 + EOF + + atf_check \ + -o ignore \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"use dhcp" \ + -o match:"dhcp-program dhcpcd" \ + -o match:"dhcp-opts -m 200" \ + cat etc/network/interfaces +} + +set_network_config_v2_dhcp6_route_metric_body() { + fake_network_config_nocloud <<-EOF + version: 2 + ethernets: + net0: + match: + macaddress: 52:55:55:1d:2a:0b + set-name: lima0 + dhcp6: true + dhcp6-overrides: + route-metric: 100 + EOF + + atf_check \ + -o ignore \ + -e match:"set_network_interfaces: done" \ + tiny-cloud boot + + atf_check \ + -o match:"auto lima0" \ + -o match:"iface lima0" \ + -o match:"use dhcp" \ + -o match:"dhcp-program dhcpcd" \ + -o match:"dhcp-opts -6 -m 100" \ + cat etc/network/interfaces +} + set_network_config_auto_body() { fake_metadata_nocloud <<-EOF resolv_conf: From 2f0efe508fb2e2dfebff8088469ad371b4507687 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 12:46:26 +0200 Subject: [PATCH 7/9] doc: describe nocloud network-config v2 subset --- README.md | 7 +++++++ docs/tiny-cloud.8.scd | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/README.md b/README.md index 046c835..81db826 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,13 @@ instance: Other cloud specific and user-data handler specific actions may also occur. +For the NoCloud provider, Tiny Cloud also supports a subset of +`network-config` version 2. The currently supported keys are: +`version: 2`, `ethernets`, `dhcp4`, `dhcp6`, `accept-ra`, +`match.macaddress`, `set-name`, `addresses`, `gateway4`, `gateway6`, +`nameservers.addresses`, `dhcp4-overrides.route-metric`, and +`dhcp6-overrides.route-metric`. + Also included is a handy `imds` client script for easy access to an instance's IMDS data. diff --git a/docs/tiny-cloud.8.scd b/docs/tiny-cloud.8.scd index 243d1d8..6eee38c 100644 --- a/docs/tiny-cloud.8.scd +++ b/docs/tiny-cloud.8.scd @@ -74,6 +74,35 @@ Otherwise, the selected phase action list is loaded from the tiny-cloud init library. Each action is run in order. If an action fails, *tiny-cloud* exits with a non-zero status. +# NOCLOUD NETWORKING + +For the *nocloud* provider, *tiny-cloud* supports the legacy +*meta-data* *network-interfaces* and a subset of NoCloud +*network-config* version 2. + +Supported *network-config* keys are: + +- *version: 2* +- *ethernets* +- *dhcp4* +- *dhcp6* +- *accept-ra* +- *match.macaddress* +- *set-name* +- *addresses* +- *gateway4* +- *gateway6* +- *nameservers.addresses* +- *dhcp4-overrides.route-metric* +- *dhcp6-overrides.route-metric* + +When `match.macaddress` and `set-name` are used, *tiny-cloud* writes +_/etc/iftab_, configures _/etc/conf.d/macifrename_ to use it, and enables +and starts the `macifrename` OpenRC service if available. + +Unsupported `network-config` versions or unsupported v2 layouts are ignored +with a warning, and normal network fallback behavior applies. + # FILES _/etc/tiny-cloud.conf_ @@ -82,6 +111,16 @@ _/etc/tiny-cloud.conf_ _/etc/tiny-cloud.disabled_ When present, suppresses phase execution. +_/etc/network/interfaces_ + Generated network interface configuration. + +_/etc/iftab_ + MAC-based interface rename rules generated from NoCloud *network-config* + version 2. + +_/etc/conf.d/macifrename_ + OpenRC service configuration used when _/etc/iftab_ is generated. + _/var/lib/cloud/.bootstrap-complete_ Bootstrap completion marker. From 699240945a2a2aeb2ba4fcfe2e21ee8f5d229ec5 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Mon, 11 May 2026 16:55:39 +0200 Subject: [PATCH 8/9] Update TODO We now have network-config v2 support but may consider v1? --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index deb83ee..61fc803 100644 --- a/TODO.md +++ b/TODO.md @@ -18,7 +18,7 @@ * Support for multipart `user-data` payload? -* Support for `#network-config`? +* Support for `#network-config` v1? * Support LVM partitioning and non-`ext[234]` filesystems? From 86b4ec351a9f06aaad0fb35fe30be6ba8f3e9a2d Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Tue, 12 May 2026 16:01:18 +0200 Subject: [PATCH 9/9] refactor network-config handler Move network-config parsing and interface generation into a shared library so other cloud providers can reuse it instead of keeping the logic embedded in NoCloud init. This also creates a single dispatch point for future network-config versions, which makes adding v1 support a focused follow-up instead of another provider-specific expansion. --- Makefile | 1 + lib/tiny-cloud/cloud/nocloud/init | 152 +-------------------------- lib/tiny-cloud/init | 1 + lib/tiny-cloud/network-config | 165 ++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 149 deletions(-) create mode 100644 lib/tiny-cloud/network-config diff --git a/Makefile b/Makefile index 453d7a2..1d8c862 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ core: $(MAN1PAGES) $(MAN5PAGES) $(MAN8PAGES) install -Dm644 -t "$(PREFIX)"/usr/lib/tiny-cloud \ lib/tiny-cloud/common \ lib/tiny-cloud/init \ + lib/tiny-cloud/network-config \ lib/tiny-cloud/tiny-cloud.conf install -Dm644 -t "$(PREFIX)"/usr/lib/tiny-cloud/user-data \ lib/tiny-cloud/user-data/* diff --git a/lib/tiny-cloud/cloud/nocloud/init b/lib/tiny-cloud/cloud/nocloud/init index 12faab7..7487455 100644 --- a/lib/tiny-cloud/cloud/nocloud/init +++ b/lib/tiny-cloud/cloud/nocloud/init @@ -4,12 +4,6 @@ INIT_ACTIONS_BOOT="$(replace_word set_default_interfaces set_network_interfaces $INIT_ACTIONS_BOOT)" -get_network_config() { - IFS="/" - yx -f "$TINY_CLOUD_VAR/network-config" $1 2>/dev/null - unset IFS -} - set_resolv_conf() { # resolv.conf local nameservers="$(imds meta-data/resolv_conf/nameservers)" @@ -49,158 +43,18 @@ want_ephemeral_network() { return 1 } -set_network_config_v2() { - [ -s "$TINY_CLOUD_VAR/network-config" ] || return 1 - local version="$(get_network_config version)" - if [ "$version" != "2" ]; then - log -i -t "$phase" warning "$ACTION: unsupported NoCloud network-config version: ${version:-unknown}" - return 1 - fi - - local ethernets="$(get_network_config ethernets)" - if [ -z "$ethernets" ]; then - log -i -t "$phase" warning "$ACTION: NoCloud network-config v2 has no supported ethernets entries" - return 1 - fi - - printf "%s\n%s\n\n" \ - "auto lo" \ - "iface lo inet loopback" \ - > "$ETC/network/interfaces" - rm -f "$ETC/iftab" - - local iface configured= target_name macaddress - for iface in $ethernets; do - local stanza= dhcp4= dhcp6= accept_ra= dhcp_program= dhcp_opts= - local address= gateway= nameservers= server= - local route_metric= - - target_name="$(get_network_config ethernets/$iface/set-name)" - macaddress="$(get_network_config ethernets/$iface/match/macaddress)" - [ -n "$target_name" ] || target_name="$iface" - - if [ -n "$target_name" ] && [ -n "$macaddress" ]; then - printf "%s %s\n" "$target_name" "$macaddress" >> "$ETC/iftab" - fi - - dhcp4="$(get_network_config ethernets/$iface/dhcp4)" - dhcp6="$(get_network_config ethernets/$iface/dhcp6)" - accept_ra="$(get_network_config ethernets/$iface/accept-ra)" - - case "$dhcp4" in - true|yes) - stanza="$stanza\tuse dhcp\n" - route_metric="$(get_network_config ethernets/$iface/dhcp4-overrides/route-metric)" - if [ -n "$route_metric" ]; then - dhcp_program="dhcpcd" - dhcp_opts="$dhcp_opts -m $route_metric" - fi - ;; - esac - - case "$dhcp6" in - true|yes) - case "$dhcp4" in - true|yes) - route_metric="$(get_network_config ethernets/$iface/dhcp6-overrides/route-metric)" - if [ -n "$route_metric" ]; then - dhcp_program="dhcpcd" - dhcp_opts="$dhcp_opts -m $route_metric" - fi - ;; - *) - stanza="$stanza\tuse dhcp\n" - dhcp_program="dhcpcd" - dhcp_opts="$dhcp_opts -6" - route_metric="$(get_network_config ethernets/$iface/dhcp6-overrides/route-metric)" - if [ -n "$route_metric" ]; then - dhcp_opts="$dhcp_opts -m $route_metric" - fi - ;; - esac - ;; - esac - - case "$accept_ra" in - true|yes) - stanza="$stanza\tuse ipv6-ra\n" - ;; - esac - - address="$(get_network_config ethernets/$iface/addresses/1)" - if [ -n "$address" ]; then - stanza="$stanza\taddress $address\n" - gateway="$(get_network_config ethernets/$iface/gateway4)" - [ -n "$gateway" ] || gateway="$(get_network_config ethernets/$iface/gateway6)" - if [ -n "$gateway" ]; then - stanza="$stanza\tgateway $gateway\n" - fi - nameservers="$(get_network_config ethernets/$iface/nameservers/addresses)" - for server in $nameservers; do - add_once "$ETC/resolv.conf" \ - "nameserver $(get_network_config ethernets/$iface/nameservers/addresses/$server)" - done - fi - - if [ -n "$dhcp_program" ]; then - stanza="$stanza\tdhcp-program $dhcp_program\n" - fi - if [ -n "$dhcp_opts" ]; then - stanza="$stanza\tdhcp-opts${dhcp_opts}\n" - fi - - [ -n "$stanza" ] || continue - - printf "%s\n%s\n%b\n" \ - "auto $target_name" \ - "iface $target_name" \ - "$stanza" >> "$ETC/network/interfaces" - configured=1 - done - - if [ -z "$configured" ]; then - log -i -t "$phase" warning "$ACTION: NoCloud network-config v2 has no supported ethernets entries" - return 1 - fi - - return 0 -} - -setup_macifrename() { - [ -s "$ETC/iftab" ] || return 0 - - if ! [ -e "$ETC/init.d/macifrename" ]; then - log -i -t "$phase" warning "$ACTION: macifrename service not installed; interface renames will not be applied" - return 0 - fi - - mkdir -p "$ETC/conf.d" - if [ -f "$ETC/conf.d/macifrename" ]; then - if grep -q '^MACIFRENAME_OPTS=' "$ETC/conf.d/macifrename"; then - sed -i -e 's|^MACIFRENAME_OPTS=.*|MACIFRENAME_OPTS="/etc/iftab"|' \ - "$ETC/conf.d/macifrename" - else - printf '%s\n' 'MACIFRENAME_OPTS="/etc/iftab"' >> "$ETC/conf.d/macifrename" - fi - else - printf '%s\n' 'MACIFRENAME_OPTS="/etc/iftab"' > "$ETC/conf.d/macifrename" - fi - - $MOCK rc-update add macifrename boot - $MOCK rc-service macifrename start -} - init__set_network_interfaces() { local interfaces="$(imds meta-data/network-interfaces)" + local network_config="$TINY_CLOUD_VAR/network-config" mkdir -p "$ETC"/network if [ -n "$interfaces" ]; then printf "%s\n" "$interfaces" > "$ETC"/network/interfaces - elif set_network_config_v2; then + elif network_config_set "$network_config" "NoCloud"; then : elif ! [ -f "$ETC"/network/interfaces ]; then init__set_default_interfaces fi - setup_macifrename + network_config_setup_macifrename if ! grep -q dhcp "$ETC"/network/interfaces; then set_resolv_conf fi diff --git a/lib/tiny-cloud/init b/lib/tiny-cloud/init index 60cd0ec..fae75b4 100644 --- a/lib/tiny-cloud/init +++ b/lib/tiny-cloud/init @@ -6,6 +6,7 @@ : "${PREFIX:=/usr}" : "${LIBDIR:=$PREFIX/lib}" . "$LIBDIR/tiny-cloud/common" +. "$LIBDIR/tiny-cloud/network-config" : "${SKIP_INIT_ACTIONS:=}" diff --git a/lib/tiny-cloud/network-config b/lib/tiny-cloud/network-config new file mode 100644 index 0000000..d44b77b --- /dev/null +++ b/lib/tiny-cloud/network-config @@ -0,0 +1,165 @@ +# Tiny Cloud - Network Config Functions +# vim:set filetype=sh: +# shellcheck shell=sh + +network_config_get() { + local file="$1" path="$2" + IFS="/" + yx -f "$file" $path 2>/dev/null + unset IFS +} + +network_config_set_v2() { + local file="$1" source_name="${2:-network-config}" + + [ -s "$file" ] || return 1 + + local ethernets="$(network_config_get "$file" ethernets)" + if [ -z "$ethernets" ]; then + log -i -t "$phase" warning "$ACTION: $source_name network-config v2 has no supported ethernets entries" + return 1 + fi + + printf "%s\n%s\n\n" \ + "auto lo" \ + "iface lo inet loopback" \ + > "$ETC/network/interfaces" + rm -f "$ETC/iftab" + + local iface configured= target_name macaddress + for iface in $ethernets; do + local stanza= dhcp4= dhcp6= accept_ra= dhcp_program= dhcp_opts= + local address= gateway= nameservers= server= + local route_metric= + + target_name="$(network_config_get "$file" ethernets/$iface/set-name)" + macaddress="$(network_config_get "$file" ethernets/$iface/match/macaddress)" + [ -n "$target_name" ] || target_name="$iface" + + if [ -n "$target_name" ] && [ -n "$macaddress" ]; then + printf "%s %s\n" "$target_name" "$macaddress" >> "$ETC/iftab" + fi + + dhcp4="$(network_config_get "$file" ethernets/$iface/dhcp4)" + dhcp6="$(network_config_get "$file" ethernets/$iface/dhcp6)" + accept_ra="$(network_config_get "$file" ethernets/$iface/accept-ra)" + + case "$dhcp4" in + true|yes) + stanza="$stanza\tuse dhcp\n" + route_metric="$(network_config_get "$file" ethernets/$iface/dhcp4-overrides/route-metric)" + if [ -n "$route_metric" ]; then + dhcp_program="dhcpcd" + dhcp_opts="$dhcp_opts -m $route_metric" + fi + ;; + esac + + case "$dhcp6" in + true|yes) + case "$dhcp4" in + true|yes) + route_metric="$(network_config_get "$file" ethernets/$iface/dhcp6-overrides/route-metric)" + if [ -n "$route_metric" ]; then + dhcp_program="dhcpcd" + dhcp_opts="$dhcp_opts -m $route_metric" + fi + ;; + *) + stanza="$stanza\tuse dhcp\n" + dhcp_program="dhcpcd" + dhcp_opts="$dhcp_opts -6" + route_metric="$(network_config_get "$file" ethernets/$iface/dhcp6-overrides/route-metric)" + if [ -n "$route_metric" ]; then + dhcp_opts="$dhcp_opts -m $route_metric" + fi + ;; + esac + ;; + esac + + case "$accept_ra" in + true|yes) + stanza="$stanza\tuse ipv6-ra\n" + ;; + esac + + address="$(network_config_get "$file" ethernets/$iface/addresses/1)" + if [ -n "$address" ]; then + stanza="$stanza\taddress $address\n" + gateway="$(network_config_get "$file" ethernets/$iface/gateway4)" + [ -n "$gateway" ] || gateway="$(network_config_get "$file" ethernets/$iface/gateway6)" + if [ -n "$gateway" ]; then + stanza="$stanza\tgateway $gateway\n" + fi + nameservers="$(network_config_get "$file" ethernets/$iface/nameservers/addresses)" + for server in $nameservers; do + add_once "$ETC/resolv.conf" \ + "nameserver $(network_config_get "$file" ethernets/$iface/nameservers/addresses/$server)" + done + fi + + if [ -n "$dhcp_program" ]; then + stanza="$stanza\tdhcp-program $dhcp_program\n" + fi + if [ -n "$dhcp_opts" ]; then + stanza="$stanza\tdhcp-opts${dhcp_opts}\n" + fi + + [ -n "$stanza" ] || continue + + printf "%s\n%s\n%b\n" \ + "auto $target_name" \ + "iface $target_name" \ + "$stanza" >> "$ETC/network/interfaces" + configured=1 + done + + if [ -z "$configured" ]; then + log -i -t "$phase" warning "$ACTION: $source_name network-config v2 has no supported ethernets entries" + return 1 + fi + + return 0 +} + +network_config_set() { + local file="$1" source_name="${2:-network-config}" + + [ -s "$file" ] || return 1 + + local version="$(network_config_get "$file" version)" + case "$version" in + 2) + network_config_set_v2 "$file" "$source_name" + ;; + *) + log -i -t "$phase" warning "$ACTION: unsupported $source_name network-config version: ${version:-unknown}" + return 1 + ;; + esac +} + +network_config_setup_macifrename() { + [ -s "$ETC/iftab" ] || return 0 + + if ! [ -e "$ETC/init.d/macifrename" ]; then + log -i -t "$phase" warning "$ACTION: macifrename service not installed; interface renames will not be applied" + return 0 + fi + + mkdir -p "$ETC/conf.d" + if [ -f "$ETC/conf.d/macifrename" ]; then + if grep -q '^MACIFRENAME_OPTS=' "$ETC/conf.d/macifrename"; then + sed -i -e 's|^MACIFRENAME_OPTS=.*|MACIFRENAME_OPTS="/etc/iftab"|' \ + "$ETC/conf.d/macifrename" + else + printf '%s\n' 'MACIFRENAME_OPTS="/etc/iftab"' >> "$ETC/conf.d/macifrename" + fi + else + printf '%s\n' 'MACIFRENAME_OPTS="/etc/iftab"' > "$ETC/conf.d/macifrename" + fi + + $MOCK rc-update add macifrename boot + $MOCK rc-service macifrename start +}