From 8a03d767842e69b8b6820b77abca4d9b03f75b35 Mon Sep 17 00:00:00 2001 From: Jun Futagawa Date: Sat, 20 Jun 2026 13:08:19 +0900 Subject: [PATCH] fix: harden nmcli command execution --- README.md | 96 ++++++++++++++++++++++---------------- aqua-checksums.json | 34 ++++++++++++++ aqua.yaml | 16 +++++++ nmcli-cli-autoconnect-list | 11 ++--- nmcli-cli-autoconnect-set | 27 ++++++++--- nmcli-cli-bond-add | 33 +++++++++---- nmcli-cli-bond-delete | 54 ++++++++++++++++----- nmcli-cli-bridge-add | 43 +++++++++++------ nmcli-cli-bridge-delete | 55 ++++++++++++++++------ nmcli-cli-device-name | 29 +++++++++--- nmcli-cli-ipv4 | 43 +++++++++++------ nmcli-cli-ipv4-copy | 52 +++++++++++++++------ nmcli-cli-ipv6 | 41 ++++++++++------ nmcli-cli-ipv6-copy | 54 ++++++++++++++------- nmcli-cli-restart | 50 +++++++++++++++++--- nmcli-cli-slave-list | 11 ++--- nmcli-cli-vlan-add | 29 +++++++++--- nmcli-cli-vlan-delete | 27 ++++++++--- 18 files changed, 512 insertions(+), 193 deletions(-) create mode 100644 aqua-checksums.json create mode 100644 aqua.yaml diff --git a/README.md b/README.md index 2cb16e4..0b29918 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ nmcli-cli is a command line tool that wraps `nmcli` to use for simple use cases. +By default, each command prints shell-ready `nmcli` commands without applying +changes. Use `-x` to run them. + - Device - [nmcli-cli-device-name](#nmcli-cli-device-name) - IPv4 @@ -26,7 +29,7 @@ nmcli-cli is a command line tool that wraps `nmcli` to use for simple use cases. - [nmcli-cli-bridge-delete](#nmcli-cli-bridge-delete) - Examples - [Example: Add Bonding + VLAN + Bridge interface](#example-add-bonding--vlan--bridge-interface) - - [Example: Delete Brdige + VLAN + Bonding interface](#example-delete-brdige--vlan--bonding-interface) + - [Example: Delete Bridge + VLAN + Bonding interface](#example-delete-bridge--vlan--bonding-interface) ## Device @@ -93,9 +96,10 @@ Run examples: # [dhcp: echo only] # ================= # nmcli-cli-ipv4 eno1 dhcp +# echo only. nmcli connection modify "eno1" ipv4.dns "" nmcli connection modify "eno1" ipv4.gateway "" -nmcli connection modify "eno1" ipv4.addresses "" ipv4.method dhcp +nmcli connection modify eno1 ipv4.addresses "" ipv4.method auto # Next steps: # -> Restart the interface: @@ -114,7 +118,7 @@ nmcli connection modify "eno1" ipv4.addresses "" ipv4.method dhcp # echo only. nmcli connection modify "eno1" ipv4.dns "" nmcli connection modify "eno1" ipv4.gateway "" -nmcli connection modify "eno1" ipv4.addresses "" ipv4.method ignore +nmcli connection modify eno1 ipv4.addresses "" ipv4.method disabled # Next steps: # -> Restart the interface: @@ -171,8 +175,8 @@ Run examples: # nmcli-cli-ipv4-copy eno1 bond1 # echo only. nmcli connection modify "bond1" ipv4.addresses "192.168.1.101/24" ipv4.method manual -nmcli connection modify "bond1" ipv4.gateway "192.168.1.1,10.0.0.2" -nmcli connection modify "bond1" ipv4.dns "192.168.1.1" +nmcli connection modify bond1 ipv4.gateway 192.168.1.1 +nmcli connection modify bond1 ipv4.dns "192.168.1.1,10.0.0.2" # Next steps: # -> Change IP address @@ -183,29 +187,29 @@ nmcli connection modify "bond1" ipv4.dns "192.168.1.1" # -> Create a bond interface: # nmcli-cli-bond-add bond1 mode=... "bond1" "ens2f0" # -> Create a vlan interface: -# nmcli-cli-vlan-add ".100" 100 "bond1" +# nmcli-cli-vlan-add "bond1.100" 100 "bond1" # -> Create a bridge interface: -# nmcli-cli-bridge-add br1 "bond1" +# nmcli-cli-bridge-add br1 "br1" # [copy ipv4: run it] # =================== # nmcli-cli-ipv4-copy -x bond1 br1 Applying: nmcli connection modify "br1" ipv4.addresses "192.168.1.101/24" ipv4.method manual -Applying: nmcli connection modify "br1" ipv4.gateway "192.168.1.1,10.0.0.2" -Applying: nmcli connection modify "br1" ipv4.dns "192.168.1.1" +Applying: nmcli connection modify br1 ipv4.gateway 192.168.1.1 +Applying: nmcli connection modify br1 ipv4.dns "192.168.1.1,10.0.0.2" # Next steps: # -> Change IP address -# nmcli-cli-ipv4 "" ... +# nmcli-cli-ipv4 "br1" ... # -> Restart the interface: -# nmcli-cli-restart "" +# nmcli-cli-restart "br1" # # -> Create a bond interface: # nmcli-cli-bond-add bond1 mode=... "bond1" "ens2f0" # -> Create a vlan interface: -# nmcli-cli-vlan-add ".100" 100 "bond1" +# nmcli-cli-vlan-add "br1.100" 100 "br1" # -> Create a bridge interface: -# nmcli-cli-bridge-add br1 "bond1" +# nmcli-cli-bridge-add br1 "br1" ``` ## IPv6 @@ -215,7 +219,7 @@ Applying: nmcli connection modify "br1" ipv4.dns "192.168.1.1" ``` Usage: - nmcli-cli-ipv6 [-n] [-x] NAME dhcp|static|disable IP_SUBNET [GATEWAY] [DNS] + nmcli-cli-ipv6 [-n] [-x] NAME auto|dhcp|static|manual|link-local|disable|ignore IP_SUBNET [GATEWAY] [DNS] Options: -n No interface check (Default: check interface) @@ -313,41 +317,40 @@ Run examples: # ====================== # nmcli-cli-ipv6-copy eno1 bond1 # echo only. -nmcli connection modify "bond1" ipv6.addresses "2001:db8:1::101/48" -nmcli connection modify "bond1" ipv6.method manual +nmcli connection modify bond1 ipv6.addresses 2001:db8:1::101/48 ipv6.method manual nmcli connection modify "bond1" ipv6.gateway "2001:db8:1::1" nmcli connection modify "bond1" ipv6.dns "2001:db8:1::1,2001:db8:1::2" # Next steps: # -> Change IP address -# nmcli-cli-ipv4 "bond1" ... +# nmcli-cli-ipv6 "bond1" ... # -> Restart the interface: # nmcli-cli-restart "bond1" # # -> Create a bond interface: # nmcli-cli-bond-add bond1 mode=... "bond1" "ens2f0" # -> Create a vlan interface: -# nmcli-cli-vlan-add ".100" 100 "bond1" +# nmcli-cli-vlan-add "bond1.100" 100 "bond1" # -> Create a bridge interface: # nmcli-cli-bridge-add br1 "bond1" # [copy ipv6: run it] # =================== # nmcli-cli-ipv6-copy -x bond1 br1 -Applying: nmcli connection modify "bond1" ipv6.addresses "2001:db8:1::101/48" ipv6.method manual -Applying: nmcli connection modify "bond1" ipv6.gateway "2001:db8:1::1" -Applying: nmcli connection modify "bond1" ipv6.dns "2001:db8:1::1,2001:db8:1::2" +Applying: nmcli connection modify br1 ipv6.addresses 2001:db8:1::101/48 ipv6.method manual +Applying: nmcli connection modify br1 ipv6.gateway 2001:db8:1::1 +Applying: nmcli connection modify br1 ipv6.dns "2001:db8:1::1,2001:db8:1::2" # Next steps: # -> Change IP address -# nmcli-cli-ipv4 "br1" ... +# nmcli-cli-ipv6 "br1" ... # -> Restart the interface: # nmcli-cli-restart "br1" # # -> Create a bond interface: # nmcli-cli-bond-add bond1 mode=... "br1" "ens2f0" # -> Create a vlan interface: -# nmcli-cli-vlan-add ".100" 100 "br1" +# nmcli-cli-vlan-add "br1.100" 100 "br1" # -> Create a bridge interface: # nmcli-cli-bridge-add br1 "br1" ``` @@ -376,14 +379,17 @@ Run examples: # ==================== # nmcli-cli-restart eno1 # echo only. -nmcli connection down "eno1"; nmcli connection up "eno1" +systemd-run --on-active=2s --unit=nmcli-cli-restart-up-12345 "$(command -v nmcli)" connection up eno1 +nmcli connection down eno1 # [restart run it] # ================ # nmcli-cli-restart -x eno1 -Applying: nmcli connection down "eno1"; nmcli connection up "eno1" +Applying: systemd-run --on-active=2s --unit=nmcli-cli-restart-up-12345 /usr/bin/nmcli connection up eno1 +Running timer as unit: nmcli-cli-restart-up-12345.timer +Will run service as unit: nmcli-cli-restart-up-12345.service +Applying: nmcli connection down eno1 Connection 'eno1' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/555) -Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/556) ``` ### nmcli-cli-autoconnect-list @@ -600,18 +606,24 @@ Run examples: # nmcli-cli-bond-delete bond1 # echo only. nmcli connection delete "bond-slave-eno1" +nmcli connection modify "eno1" connection.autoconnect yes nmcli connection delete "bond-slave-eno3" +nmcli connection modify "eno3" connection.autoconnect yes nmcli connection delete "bond-slave-ens2f0" +nmcli connection modify "ens2f0" connection.autoconnect yes nmcli connection delete "bond1" # [delete: run it] # ================ Applying: nmcli connection delete "bond-slave-eno1" Connection 'bond-slave-ens38' (5de09d50-69fc-4672-9a12-413424f16647) successfully deleted. +Applying: nmcli connection modify eno1 connection.autoconnect yes Applying: nmcli connection delete "bond-slave-eno3" Connection 'bond-slave-ens39' (5c8b4d7a-7771-460c-a514-3bb8819a6470) successfully deleted. +Applying: nmcli connection modify eno3 connection.autoconnect yes Applying: nmcli connection delete "bond-slave-ens2f0" Connection 'bond-slave-ens40' (305d3404-0f46-43b5-a087-e4e194d9597e) successfully deleted. +Applying: nmcli connection modify ens2f0 connection.autoconnect yes Applying: nmcli connection delete "bond1" Connection 'bond1' (eaf6cc9a-0a7a-42cd-8b01-febc62d2f63d) successfully deleted. ``` @@ -648,11 +660,11 @@ nmcli connection add type vlan ipv4.method disabled ipv6.method ignore con-name # cat /proc/net/vlan/eno1.100 # # -> Set IP address -# nmcli-cli-ipv4 "eno1" ... -# nmcli-cli-ipv6 "eno1" ... +# nmcli-cli-ipv4 "eno1.100" ... +# nmcli-cli-ipv6 "eno1.100" ... # # -> Create a bridge interface: -# nmcli-cli-bridge-add br1 "eno1" +# nmcli-cli-bridge-add br1 "eno1.100" # [vlan add: dummy interface + VLAN ID 100 + echo only] @@ -663,14 +675,14 @@ nmcli connection add type vlan ipv4.method disabled ipv6.method ignore con-name # Next steps: # -> Check vlan status -# cat /proc/net/vlan/dummy1.100 +# cat /proc/net/vlan/vlan.100 # # -> Set IP address -# nmcli-cli-ipv4 "dummy1" ... -# nmcli-cli-ipv6 "dummy1" ... +# nmcli-cli-ipv4 "vlan.100" ... +# nmcli-cli-ipv6 "vlan.100" ... # # -> Create a bridge interface: -# nmcli-cli-bridge-add br1 "dummy1" +# nmcli-cli-bridge-add br1 "vlan.100" # [vlan add: Bonding + VLAN ID 100 + run it] # ========================================== @@ -683,11 +695,11 @@ Connection 'bond1.100' (2fe697fa-3ca9-4546-8d2d-b551ef47e8f4) successfully added # cat /proc/net/vlan/bond1.100 # # -> Set IP address -# nmcli-cli-ipv4 "bond1" ... -# nmcli-cli-ipv6 "bond1" ... +# nmcli-cli-ipv4 "bond1.100" ... +# nmcli-cli-ipv6 "bond1.100" ... # # -> Create a bridge interface: -# nmcli-cli-bridge-add br1 "bond1" +# nmcli-cli-bridge-add br1 "bond1.100" ``` ### nmcli-cli-vlan-delete @@ -718,7 +730,7 @@ nmcli connection delete "eno1.100" # [vlan delete: run it] # ===================== -# nmcli-cli-vlan-delete -x eno1.100 +# nmcli-cli-vlan-delete -x bond1.100 Applying: nmcli connection delete "bond1.100" Connection 'bond1.100' (2fe697fa-3ca9-4546-8d2d-b551ef47e8f4) successfully deleted. ``` @@ -730,7 +742,7 @@ Connection 'bond1.100' (2fe697fa-3ca9-4546-8d2d-b551ef47e8f4) successfully delet ``` Usage: - nmcli-cli-bridge-add [-n] [-x] NEW_BRDIGE_IF_NAME IF_SLAVE + nmcli-cli-bridge-add [-n] [-x] NEW_BRIDGE_IF_NAME IF_SLAVE Options: -n No interface check (Default: check interface) @@ -746,7 +758,7 @@ Run examples: ``` # [bridge add: echo only] # ======================= -nmcli-cli-bridge-add br1 eno1 +# nmcli-cli-bridge-add br1 eno1 # echo only. nmcli connection add type bridge autoconnect yes ipv4.method disabled ipv6.method ignore bridge.stp no bridge.forward-delay 0 con-name "br1" ifname "br1" nmcli connection modify "eno1" connection.slave-type bridge connection.master "br1" @@ -866,7 +878,7 @@ nmcli connection modify "br1.100" ipv6.gateway "2001:db8:1::1" nmcli connection modify "br1.100" ipv6.dns "2001:db8:1::1,2001:db8:1::2" ``` -### Example: Delete Brdige + VLAN + Bonding interface +### Example: Delete Bridge + VLAN + Bonding interface - Interfaces: br1.100 + bond1.100 + bond1 @@ -883,8 +895,11 @@ nmcli connection delete "bond1.100" # nmcli-cli-bond-delete -n bond1 # echo only. nmcli connection delete "bond-slave-eno1" +nmcli connection modify "eno1" connection.autoconnect yes nmcli connection delete "bond-slave-eno3" +nmcli connection modify "eno3" connection.autoconnect yes nmcli connection delete "bond-slave-ens2f0" +nmcli connection modify "ens2f0" connection.autoconnect yes nmcli connection delete "bond1" ``` @@ -895,4 +910,3 @@ MIT ## Author Jun Futagawa (jfut) - diff --git a/aqua-checksums.json b/aqua-checksums.json new file mode 100644 index 0000000..bae58fe --- /dev/null +++ b/aqua-checksums.json @@ -0,0 +1,34 @@ +{ + "checksums": [ + { + "id": "github_release/github.com/koalaman/shellcheck/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz", + "checksum": "56AFFDD8DE5527894DCA6DC3D7E0A99A873B0F004D7AABC30AE407D3F48B0A79", + "algorithm": "sha256" + }, + { + "id": "github_release/github.com/koalaman/shellcheck/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz", + "checksum": "3C89DB4EDCAB7CF1C27BFF178882E0F6F27F7AFDF54E859FA041FCA10FEBE4C6", + "algorithm": "sha256" + }, + { + "id": "github_release/github.com/koalaman/shellcheck/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz", + "checksum": "12B331C1D2DB6B9EB13CFCA64306B1B157A86EB69DB83023E261EAA7E7C14588", + "algorithm": "sha256" + }, + { + "id": "github_release/github.com/koalaman/shellcheck/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz", + "checksum": "8C3BE12B05D5C177A04C29E3C78CE89AC86F1595681CAB149B65B97C4E227198", + "algorithm": "sha256" + }, + { + "id": "github_release/github.com/koalaman/shellcheck/v0.11.0/shellcheck-v0.11.0.zip", + "checksum": "8A4E35AB0B331C85D73567B12F2A444DF187F483E5079CEFFA6BDA1FAA2E740E", + "algorithm": "sha256" + }, + { + "id": "registries/github_content/github.com/aquaproj/aqua-registry/v4.526.0/registry.yaml", + "checksum": "E2BB546D74F7E8D5276F00F2BC342E0CA3D7AC34B7AA442058CBFDBE318346E8", + "algorithm": "sha256" + } + ] +} diff --git a/aqua.yaml b/aqua.yaml new file mode 100644 index 0000000..a13784e --- /dev/null +++ b/aqua.yaml @@ -0,0 +1,16 @@ +--- +# aqua - Declarative CLI Version Manager +# https://aquaproj.github.io/ +# https://aquaproj.github.io/docs/reference/security/checksum/ +checksum: + enabled: true + require_checksum: true +registries: +# https://github.com/aquaproj/aqua-registry +- type: standard + ref: v4.526.0 # renovate: depName=aquaproj/aqua-registry + supported_envs: + - linux +# aqua update-checksum --prune +packages: +- name: koalaman/shellcheck@v0.11.0 diff --git a/nmcli-cli-autoconnect-list b/nmcli-cli-autoconnect-list index 4652e23..e80df06 100755 --- a/nmcli-cli-autoconnect-list +++ b/nmcli-cli-autoconnect-list @@ -2,7 +2,7 @@ # # nmcli-cli-autoconnect-list # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php @@ -35,12 +35,10 @@ autoconnect_list() { exit 1 fi - # list - LIST=$(nmcli connection | awk '{ print $1 }' | grep -v "^NAME$") - for INTERFACE in ${LIST} + # Read machine-friendly nmcli output so connection names may contain spaces. + nmcli -g NAME connection show | while IFS= read -r INTERFACE do - STATUS_LINE=$(nmcli connection show "${INTERFACE}" | grep connection.autoconnect:) - STATUS=$(echo "${STATUS_LINE}" | awk '{ print $2 }') + STATUS=$(nmcli -g connection.autoconnect connection show "${INTERFACE}") if [[ "${ON_OFF}" == "all" ]] || [[ "${STATUS}" == "${ON_OFF}" ]]; then echo "${INTERFACE}" fi @@ -55,4 +53,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-autoconnect-set b/nmcli-cli-autoconnect-set index 173c9b3..3f7e9cc 100755 --- a/nmcli-cli-autoconnect-set +++ b/nmcli-cli-autoconnect-set @@ -2,19 +2,35 @@ # # nmcli-cli-autoconnect-set # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -51,7 +67,7 @@ autoconnect_set() { local IF_NAME=${1:-} local ON_OFF=${2:-} - echo_or_execute nmcli connection modify \"${IF_NAME}\" connection.autoconnect \"${ON_OFF}\" + echo_or_execute nmcli connection modify "${IF_NAME}" connection.autoconnect "${ON_OFF}" } # Main @@ -89,4 +105,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-bond-add b/nmcli-cli-bond-add index cd8b1ab..bfd7f0f 100755 --- a/nmcli-cli-bond-add +++ b/nmcli-cli-bond-add @@ -2,19 +2,35 @@ # # nmcli-cli-bond-add # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -64,15 +80,15 @@ bond_add() { # Bonding echo_or_execute nmcli connection add type bond \ - bond.options \"${BOND_OPTIONS}\" \ + bond.options "${BOND_OPTIONS}" \ autoconnect yes ipv4.method disabled ipv6.method ignore \ - con-name \"${BOND_IF_NAME}\" ifname \"${BOND_IF_NAME}\" + con-name "${BOND_IF_NAME}" ifname "${BOND_IF_NAME}" # Add slave interfaces shift 2 for IF_SLAVE in "${@}"; do - echo_or_execute nmcli connection modify \"${IF_SLAVE}\" connection.autoconnect no - echo_or_execute nmcli connection add type bond-slave autoconnect yes ifname \"${IF_SLAVE}\" master \"${BOND_IF_NAME}\" + echo_or_execute nmcli connection modify "${IF_SLAVE}" connection.autoconnect no + echo_or_execute nmcli connection add type bond-slave autoconnect yes ifname "${IF_SLAVE}" master "${BOND_IF_NAME}" done cat << _EOF_ @@ -127,4 +143,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-bond-delete b/nmcli-cli-bond-delete index 1a02ffd..1b9b03f 100755 --- a/nmcli-cli-bond-delete +++ b/nmcli-cli-bond-delete @@ -2,19 +2,35 @@ # # nmcli-cli-bond-delete # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -48,18 +64,35 @@ check() { bond_delete() { local BOND_IF_NAME=${1:-} + local CONNECTION_LIST + local MASTER + local RET + local SLAVE_IF_NAME + local i # Find lower interfaces - INTERFACE_LIST=$(ls "/sys/class/net/${BOND_IF_NAME}/" | grep "^lower_" | sed 's|lower_||') + CONNECTION_LIST=$(nmcli -g NAME connection show) + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: failed to list connections (${RET})." >&2 + exit "${RET}" + fi - # Delete lower interfaces - for i in ${INTERFACE_LIST} + while IFS= read -r i do - echo_or_execute nmcli connection delete \"${i}\" - done + # Delete only slave profiles attached to the target master. + [[ -z "${i}" ]] && continue + MASTER=$(nmcli -g connection.master connection show "${i}") + [[ "${MASTER}" != "${BOND_IF_NAME}" ]] && continue + SLAVE_IF_NAME=$(nmcli -g connection.interface-name connection show "${i}") + echo_or_execute nmcli connection delete "${i}" + if [[ -n "${SLAVE_IF_NAME}" ]] && [[ "${SLAVE_IF_NAME}" != "--" ]]; then + echo_or_execute nmcli connection modify "${SLAVE_IF_NAME}" connection.autoconnect yes + fi + done <<< "${CONNECTION_LIST}" # Delete bond interface - echo_or_execute nmcli connection delete \"${BOND_IF_NAME}\" + echo_or_execute nmcli connection delete "${BOND_IF_NAME}" } # Main @@ -97,4 +130,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-bridge-add b/nmcli-cli-bridge-add index d107ea8..2245366 100755 --- a/nmcli-cli-bridge-add +++ b/nmcli-cli-bridge-add @@ -2,19 +2,35 @@ # # nmcli-cli-bridge-add # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -22,7 +38,7 @@ usage() { cat << _EOF_ Usage: - $0 [-n] [-x] NEW_BRDIGE_IF_NAME IF_SLAVE + $0 [-n] [-x] NEW_BRIDGE_IF_NAME IF_SLAVE Options: -n No interface check (Default: check interface) @@ -36,14 +52,14 @@ _EOF_ } check() { - local BRDIGE_IF_NAME=${1:-} + local BRIDGE_IF_NAME=${1:-} local IF_SLAVE=${2:-} # Check new bridge interface - nmcli connection show "${BRDIGE_IF_NAME}" > /dev/null 2>&1 + nmcli connection show "${BRIDGE_IF_NAME}" > /dev/null 2>&1 RET=$? if [[ ${RET} -eq 0 ]]; then - echo "Error: Bridge interface \"${BRDIGE_IF_NAME}\" already exists." + echo "Error: Bridge interface \"${BRIDGE_IF_NAME}\" already exists." exit 1 fi @@ -57,17 +73,17 @@ check() { } bridge_add() { - local BRDIGE_IF_NAME=${1:-} + local BRIDGE_IF_NAME=${1:-} local IF_SLAVE=${2:-} # Bridge: STP: No, Forward Delay: 0 echo_or_execute nmcli connection add type bridge \ autoconnect yes ipv4.method disabled ipv6.method ignore \ bridge.stp no bridge.forward-delay 0 \ - con-name \"${BRDIGE_IF_NAME}\" ifname \"${BRDIGE_IF_NAME}\" + con-name "${BRIDGE_IF_NAME}" ifname "${BRIDGE_IF_NAME}" # Slave - echo_or_execute nmcli connection modify \"${IF_SLAVE}\" connection.slave-type bridge connection.master \"${BRDIGE_IF_NAME}\" + echo_or_execute nmcli connection modify "${IF_SLAVE}" connection.slave-type bridge connection.master "${BRIDGE_IF_NAME}" cat << _EOF_ @@ -76,8 +92,8 @@ bridge_add() { # brctl show # # -> Set IP address -# nmcli-cli-ipv4 "${BRDIGE_IF_NAME}" ... -# nmcli-cli-ipv6 "${BRDIGE_IF_NAME}" ... +# nmcli-cli-ipv4 "${BRIDGE_IF_NAME}" ... +# nmcli-cli-ipv6 "${BRIDGE_IF_NAME}" ... _EOF_ } @@ -116,4 +132,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-bridge-delete b/nmcli-cli-bridge-delete index a35349f..9580e93 100755 --- a/nmcli-cli-bridge-delete +++ b/nmcli-cli-bridge-delete @@ -2,19 +2,35 @@ # # nmcli-cli-bridge-delete # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -35,31 +51,43 @@ _EOF_ } check() { - local BRDIGE_IF_NAME=${1:-} + local BRIDGE_IF_NAME=${1:-} # Check bridge interface - nmcli connection show "${BRDIGE_IF_NAME}" > /dev/null 2>&1 + nmcli connection show "${BRIDGE_IF_NAME}" > /dev/null 2>&1 RET=$? if [[ ${RET} -ne 0 ]]; then - echo "Error: Target bridge interface \"${BRDIGE_IF_NAME}\" not found." + echo "Error: Target bridge interface \"${BRIDGE_IF_NAME}\" not found." exit 1 fi } bridge_delete() { local BRIDGE_NAME=${1:-} + local CONNECTION_LIST + local MASTER + local RET + local i # Find lower interface - INTERFACE_LIST=$(ls "/sys/class/net/${BRIDGE_NAME}/" | grep "^lower_" | sed 's|lower_||') + CONNECTION_LIST=$(nmcli -g NAME connection show) + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: failed to list connections (${RET})." >&2 + exit "${RET}" + fi - # Delete lower interface - for i in ${INTERFACE_LIST} + while IFS= read -r i do - echo_or_execute nmcli connection modify \"${i}\" connection.master \"\" connection.slave-type \"\" - done + # Detach only slave profiles attached to the target bridge. + [[ -z "${i}" ]] && continue + MASTER=$(nmcli -g connection.master connection show "${i}") + [[ "${MASTER}" != "${BRIDGE_NAME}" ]] && continue + echo_or_execute nmcli connection modify "${i}" connection.master "" connection.slave-type "" + done <<< "${CONNECTION_LIST}" # Delete bridge interface - echo_or_execute nmcli connection delete \"${BRIDGE_NAME}\" + echo_or_execute nmcli connection delete "${BRIDGE_NAME}" } # Main @@ -97,4 +125,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-device-name b/nmcli-cli-device-name index db23518..89be877 100755 --- a/nmcli-cli-device-name +++ b/nmcli-cli-device-name @@ -2,19 +2,35 @@ # # nmcli-cli-device-name # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -51,12 +67,12 @@ device_name_set() { local DEVICE=$1 local NEW_IF_NAME=${2:-${DEVICE}} - CURRENT_NAME=$(nmcli device show "${DEVICE}" | grep "^GENERAL.CONNECTION:" | cut -d':' -f 2 | xargs) + CURRENT_NAME=$(nmcli -g GENERAL.CONNECTION device show "${DEVICE}") if [[ "${CURRENT_NAME}" = "--" ]]; then CURRENT_NAME=${DEVICE} fi - echo_or_execute nmcli connection modify \"${CURRENT_NAME}\" connection.id \"${NEW_IF_NAME}\" + echo_or_execute nmcli connection modify "${CURRENT_NAME}" connection.id "${NEW_IF_NAME}" } # Main @@ -94,4 +110,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-ipv4 b/nmcli-cli-ipv4 index c3630a9..00915c2 100755 --- a/nmcli-cli-ipv4 +++ b/nmcli-cli-ipv4 @@ -2,19 +2,35 @@ # # nmcli-cli-ipv4 # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -53,13 +69,13 @@ ipv4_set_dhcp() { local IF_NAME=${1:-} # DNS - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.dns \"\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.dns "" # Gateway - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.gateway \"\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.gateway "" # IP - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.addresses \"\" ipv4.method auto + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.addresses "" ipv4.method auto } ipv4_set_static() { @@ -69,16 +85,16 @@ ipv4_set_static() { local IF_DNS=${4:-} # IP - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.addresses \"${IF_IP_SUBNET}\" ipv4.method manual + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.addresses "${IF_IP_SUBNET}" ipv4.method manual # Option: Gateway if [[ ! -z "${IF_GATEWAY}" ]]; then - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.gateway \"${IF_GATEWAY}\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.gateway "${IF_GATEWAY}" fi # Option: DNS if [[ ! -z "${IF_DNS}" ]]; then - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.dns \"${IF_DNS}\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.dns "${IF_DNS}" fi } @@ -86,13 +102,13 @@ ipv4_set_disable() { local IF_NAME=${1:-} # DNS - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.dns \"\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.dns "" # Gateway - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.gateway \"\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.gateway "" # IP - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv4.addresses \"${IF_IP_SUBNET}\" ipv4.method disabled ipv6.method ignore + echo_or_execute nmcli connection modify "${IF_NAME}" ipv4.addresses "" ipv4.method disabled } ipv4_set() { @@ -163,4 +179,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-ipv4-copy b/nmcli-cli-ipv4-copy index 493c668..cea6c63 100755 --- a/nmcli-cli-ipv4-copy +++ b/nmcli-cli-ipv4-copy @@ -2,22 +2,45 @@ # # nmcli-cli-ipv4-copy # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } +nmcli_value() { + # Convert only nmcli's unset marker to an empty string. + local value=${1:-} + [[ "${value}" == "--" ]] && value="" + printf '%s' "${value}" +} + usage() { cat << _EOF_ Usage: @@ -44,7 +67,7 @@ check() { nmcli connection show "${SRC_IF_NAME}" > /dev/null 2>&1 RET=$? if [[ ${RET} -ne 0 ]]; then - echo "Error: Source interface "${SRC_IF_NAME}" not found." + echo "Error: Source interface \"${SRC_IF_NAME}\" not found." exit 1 fi @@ -52,7 +75,7 @@ check() { nmcli connection show "${DEST_IF_NAME}" > /dev/null 2>&1 RET=$? if [[ ${RET} -ne 0 ]]; then - echo "Error: Destination interface "${DEST_IF_NAME}" not found." + echo "Error: Destination interface \"${DEST_IF_NAME}\" not found." exit 1 fi } @@ -62,23 +85,23 @@ ipv4_copy() { local DEST_IF_NAME=${2:-${SRC_IF_NAME}} # IP + Method - SRC_IP_SUBNET=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv4.addresses:" | awk '{ print $2 }' | tr -d '-') - SRC_METHOD=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv4.method:" | awk '{ print $2 }' | tr -d '-') - echo_or_execute nmcli connection modify \"${DEST_IF_NAME}\" ipv4.addresses \"${SRC_IP_SUBNET}\" ipv4.method "${SRC_METHOD}" + SRC_IP_SUBNET=$(nmcli_value "$(nmcli -g ipv4.addresses connection show "${SRC_IF_NAME}")") + SRC_METHOD=$(nmcli_value "$(nmcli -g ipv4.method connection show "${SRC_IF_NAME}")") + echo_or_execute nmcli connection modify "${DEST_IF_NAME}" ipv4.addresses "${SRC_IP_SUBNET}" ipv4.method "${SRC_METHOD}" # Gateway - SRC_GATEWAY=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv4.gateway:" | awk '{ print $2 }' | tr -d '-') - echo_or_execute nmcli connection modify \"${DEST_IF_NAME}\" ipv4.gateway \"${SRC_GATEWAY}\" + SRC_GATEWAY=$(nmcli_value "$(nmcli -g ipv4.gateway connection show "${SRC_IF_NAME}")") + echo_or_execute nmcli connection modify "${DEST_IF_NAME}" ipv4.gateway "${SRC_GATEWAY}" # DNS - SRC_DNS=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv4.dns:" | awk '{ print $2 }' | tr -d '-') - echo_or_execute nmcli connection modify \"${DEST_IF_NAME}\" ipv4.dns \"${SRC_DNS}\" + SRC_DNS=$(nmcli_value "$(nmcli -g ipv4.dns connection show "${SRC_IF_NAME}")") + echo_or_execute nmcli connection modify "${DEST_IF_NAME}" ipv4.dns "${SRC_DNS}" cat << _EOF_ # Next steps: # -> Change IP address -# nmcli-cli-ipv4 "$DEST_IF_NAME{}" ... +# nmcli-cli-ipv4 "${DEST_IF_NAME}" ... # -> Restart the interface: # nmcli-cli-restart "${DEST_IF_NAME}" # @@ -126,4 +149,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-ipv6 b/nmcli-cli-ipv6 index e5eb8c5..f25ff1f 100755 --- a/nmcli-cli-ipv6 +++ b/nmcli-cli-ipv6 @@ -2,19 +2,35 @@ # # nmcli-cli-ipv6 # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -22,7 +38,7 @@ usage() { cat << _EOF_ Usage: - $0 [-n] [-x] NAME dhcp|static|disable IP_SUBNET [GATEWAY] [DNS] + $0 [-n] [-x] NAME auto|dhcp|static|manual|link-local|disable|ignore IP_SUBNET [GATEWAY] [DNS] Options: -n No interface check (Default: check interface) @@ -56,13 +72,13 @@ ipv6_set_auto_method() { local IF_METHOD=${2:-} # DNS - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv6.dns \"\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv6.dns "" # Gateway - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv6.gateway \"\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv6.gateway "" # IP - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv6.addresses \"\" ipv6.method "${IF_METHOD}" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv6.addresses "" ipv6.method "${IF_METHOD}" } ipv6_set_static() { @@ -72,16 +88,16 @@ ipv6_set_static() { local IF_DNS=${4:-} # IP - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv6.addresses \"${IF_IP_SUBNET}\" ipv6.method manual + echo_or_execute nmcli connection modify "${IF_NAME}" ipv6.addresses "${IF_IP_SUBNET}" ipv6.method manual # Option: Gateway if [[ ! -z "${IF_GATEWAY}" ]]; then - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv6.gateway \"${IF_GATEWAY}\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv6.gateway "${IF_GATEWAY}" fi # Option: DNS if [[ ! -z "${IF_DNS}" ]]; then - echo_or_execute nmcli connection modify \"${IF_NAME}\" ipv6.dns \"${IF_DNS}\" + echo_or_execute nmcli connection modify "${IF_NAME}" ipv6.dns "${IF_DNS}" fi } @@ -100,8 +116,6 @@ ipv6_set() { ipv6_set_auto_method "${IF_NAME}" "link-local" elif [[ "${IF_METHOD}" = "static" ]] || [[ "${IF_METHOD}" = "manual" ]]; then ipv6_set_static "${IF_NAME}" "${IF_IP_SUBNET}" "${IF_GATEWAY}" "${IF_DNS}" - elif [[ "${IF_METHOD}" = "link-local" ]]; then - ipv6_set_auto_method "${IF_NAME}" "link-local" elif [[ "${IF_METHOD}" = "disable" ]] || [[ "${IF_METHOD}" = "ignore" ]]; then ipv6_set_auto_method "${IF_NAME}" "ignore" else @@ -159,4 +173,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-ipv6-copy b/nmcli-cli-ipv6-copy index 0a84a72..3665c7e 100755 --- a/nmcli-cli-ipv6-copy +++ b/nmcli-cli-ipv6-copy @@ -2,22 +2,45 @@ # # nmcli-cli-ipv6-copy # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } +nmcli_value() { + # Convert only nmcli's unset marker to an empty string. + local value=${1:-} + [[ "${value}" == "--" ]] && value="" + printf '%s' "${value}" +} + usage() { cat << _EOF_ Usage: @@ -44,7 +67,7 @@ check() { nmcli connection show "${SRC_IF_NAME}" > /dev/null 2>&1 RET=$? if [[ ${RET} -ne 0 ]]; then - echo "Error: Source interface "${SRC_IF_NAME}" not found." + echo "Error: Source interface \"${SRC_IF_NAME}\" not found." exit 1 fi @@ -52,7 +75,7 @@ check() { nmcli connection show "${DEST_IF_NAME}" > /dev/null 2>&1 RET=$? if [[ ${RET} -ne 0 ]]; then - echo "Error: Destination interface "${DEST_IF_NAME}" not found." + echo "Error: Destination interface \"${DEST_IF_NAME}\" not found." exit 1 fi } @@ -62,30 +85,30 @@ ipv6_copy() { local DEST_IF_NAME=${2:-${SRC_IF_NAME}} # IP - SRC_IP_SUBNET=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv6.addresses:" | awk '{ print $2 }' | tr -d '-') - SRC_METHOD=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv6.method:" | awk '{ print $2 }' | tr -d '-') - echo_or_execute nmcli connection modify \"${DEST_IF_NAME}\" ipv6.addresses \"${SRC_IP_SUBNET}\" ipv6.method "${SRC_METHOD}" + SRC_IP_SUBNET=$(nmcli_value "$(nmcli -g ipv6.addresses connection show "${SRC_IF_NAME}")") + SRC_METHOD=$(nmcli_value "$(nmcli -g ipv6.method connection show "${SRC_IF_NAME}")") + echo_or_execute nmcli connection modify "${DEST_IF_NAME}" ipv6.addresses "${SRC_IP_SUBNET}" ipv6.method "${SRC_METHOD}" # Gateway - SRC_GATEWAY=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv6.gateway:" | awk '{ print $2 }' | tr -d '-') - echo_or_execute nmcli connection modify \"${DEST_IF_NAME}\" ipv6.gateway \"${SRC_GATEWAY}\" + SRC_GATEWAY=$(nmcli_value "$(nmcli -g ipv6.gateway connection show "${SRC_IF_NAME}")") + echo_or_execute nmcli connection modify "${DEST_IF_NAME}" ipv6.gateway "${SRC_GATEWAY}" # DNS - SRC_DNS=$(nmcli connection show "${SRC_IF_NAME}" | grep "^ipv6.dns:" | awk '{ print $2 }' | tr -d '-') - echo_or_execute nmcli connection modify \"${DEST_IF_NAME}\" ipv6.dns \"${SRC_DNS}\" + SRC_DNS=$(nmcli_value "$(nmcli -g ipv6.dns connection show "${SRC_IF_NAME}")") + echo_or_execute nmcli connection modify "${DEST_IF_NAME}" ipv6.dns "${SRC_DNS}" cat << _EOF_ # Next steps: # -> Change IP address -# nmcli-cli-ipv4 "${DEST_IF_NAME}" ... +# nmcli-cli-ipv6 "${DEST_IF_NAME}" ... # -> Restart the interface: # nmcli-cli-restart "${DEST_IF_NAME}" # # -> Create a bond interface: # nmcli-cli-bond-add bond1 mode=... "${DEST_IF_NAME}" "ens2f0" # -> Create a vlan interface: -# nmcli-cli-vlan-add "${IF_NAME}.100" 100 "${DEST_IF_NAME}" +# nmcli-cli-vlan-add "${DEST_IF_NAME}.100" 100 "${DEST_IF_NAME}" # -> Create a bridge interface: # nmcli-cli-bridge-add br1 "${DEST_IF_NAME}" _EOF_ @@ -126,4 +149,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-restart b/nmcli-cli-restart index 526c5fc..99442d9 100755 --- a/nmcli-cli-restart +++ b/nmcli-cli-restart @@ -2,19 +2,35 @@ # # nmcli-cli-restart # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -48,9 +64,32 @@ check() { restart() { local IF_NAME=${1:-} + local NMCLI + local UNIT_NAME # down and up - echo_or_execute "nmcli connection down \"${IF_NAME}\"; nmcli connection up \"${IF_NAME}\"" + # Schedule the recovery before disconnecting the current connection. + UNIT_NAME="nmcli-cli-restart-up-$$" + if [[ "${ECHO_OR_EXECUTE}" = "echo" ]]; then + # Keep command substitution literal so copied output resolves nmcli at runtime. + # shellcheck disable=SC2016 + printf 'systemd-run --on-active=2s --unit=%q "$(command -v nmcli)" connection up %q\n' "${UNIT_NAME}" "${IF_NAME}" + echo_or_execute nmcli connection down "${IF_NAME}" + else + NMCLI=$(command -v nmcli) + if [[ -z "${NMCLI}" ]]; then + echo "Error: nmcli command not found." >&2 + exit 1 + fi + + if ! command -v systemd-run > /dev/null 2>&1; then + echo "Error: systemd-run command not found." >&2 + exit 1 + fi + + echo_or_execute systemd-run --on-active=2s --unit="${UNIT_NAME}" "${NMCLI}" connection up "${IF_NAME}" + echo_or_execute nmcli connection down "${IF_NAME}" + fi } # Main @@ -88,4 +127,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-slave-list b/nmcli-cli-slave-list index 80fc352..21675d4 100755 --- a/nmcli-cli-slave-list +++ b/nmcli-cli-slave-list @@ -2,7 +2,7 @@ # # nmcli-cli-slave-list # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php @@ -22,14 +22,14 @@ _EOF_ slave_list() { local IF_NAME=${1:-} + local i # Find lower interfaces TARGET="/sys/class/net/${IF_NAME}/" if [[ -e "${TARGET}" ]]; then - INTERFACE_LIST=$(ls "${TARGET}" | grep "^lower_" | sed 's|lower_||') - for i in ${INTERFACE_LIST} - do - echo "${i}" + for i in "${TARGET}"/lower_*; do + [[ -e "${i}" ]] || continue + echo "${i##*/lower_}" done fi } @@ -42,4 +42,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-vlan-add b/nmcli-cli-vlan-add index 2bb07db..38100a5 100755 --- a/nmcli-cli-vlan-add +++ b/nmcli-cli-vlan-add @@ -2,19 +2,35 @@ # # nmcli-cli-vlan-add # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -63,8 +79,8 @@ vlan_add() { # Add vlan interface echo_or_execute nmcli connection add type vlan ipv4.method disabled ipv6.method ignore \ - con-name \"${VLAN_IF_NAME}\" ifname \"${VLAN_IF_NAME}\" dev \"${IF_NAME}\" \ - id ${VLAN_ID} + con-name "${VLAN_IF_NAME}" ifname "${VLAN_IF_NAME}" dev "${IF_NAME}" \ + id "${VLAN_ID}" cat << _EOF_ @@ -116,4 +132,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" - diff --git a/nmcli-cli-vlan-delete b/nmcli-cli-vlan-delete index fe00231..1add419 100755 --- a/nmcli-cli-vlan-delete +++ b/nmcli-cli-vlan-delete @@ -2,19 +2,35 @@ # # nmcli-cli-vlan-delete # -# Copyright (c) 2019 Jun Futagawa (jfut) +# Copyright (c) 2019-2026 Jun Futagawa (jfut) # # This software is released under the MIT License. # http://opensource.org/licenses/mit-license.php ECHO_OR_EXECUTE="echo" +print_command() { + # Print the nmcli command in a reusable shell-quoted form. + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' +} + echo_or_execute() { + local RET + if [[ -z "${ECHO_OR_EXECUTE}" ]]; then - echo "Applying: ${@}" - eval ${@} + printf 'Applying: ' + print_command "$@" + "$@" + RET=$? + if [[ ${RET} -ne 0 ]]; then + echo "Error: command failed (${RET})." >&2 + exit "${RET}" + fi else - ${ECHO_OR_EXECUTE} ${@} + print_command "$@" fi } @@ -52,7 +68,7 @@ vlan_delete() { local VLAN_IF_NAME=${1:-} # Delete vlan interface - echo_or_execute nmcli connection delete \"${VLAN_IF_NAME}\" + echo_or_execute nmcli connection delete "${VLAN_IF_NAME}" } # Main @@ -90,4 +106,3 @@ main() { } [[ ${#BASH_SOURCE[@]} = 1 ]] && main "${@}" -