#!/bin/bash # I, Ian Kelling, follow the GNU license recommendations at # https://www.gnu.org/licenses/license-recommendations.en.html. They # recommend that small programs, < 300 lines, be licensed under the # Apache License 2.0. This file contains or is part of one or more small # programs. If a small program grows beyond 300 lines, I plan to switch # its license to GPL. # Copyright 2024 Ian Kelling # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # usage $0 [-c] [off] # off: Turn off static ip. # -c config only, don't tell networkmanager to change anything # -f force interface reup if ! test "$BASH_VERSION"; then echo "error: shell is not bash" >&2; exit 1; fi shopt -s inherit_errexit 2>/dev/null ||: # ignore fail in bash < 4.4 set -eE -o pipefail trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR m() { printf "%s\n" "$*"; "$@"; } set-dynamic() { reup=false if [[ $cur_state == activated ]]; then reup=true fi if [[ $cur_method != auto ]]; then args+=(ipv4.method auto) fi if [[ $cur_ip != -- ]]; then args+=(-ipv4.addresses "$ipv4_addresses") fi if [[ $cur_dns != -- ]]; then args+=(-ipv4.dns "$ipv4_dns") fi if [[ $cur_gateway != -- ]]; then # undocumented in t11 man nmcli. guessed randomly args+=(ipv4.gateway 0.0.0.0) fi if (( ${#args[@]} >= 1 )); then m nmcli con mod "$nm_con" "${args[@]}" if $reup; then m nmcli con up "$nm_con" fi else echo "$0: found expected state, nothing to do." fi exit 0 set-nm } detect-net() { # this assumes we have wifi up if [[ $(timeout 1 dig +short @10.2.0.1 -x 10.2.0.2 2>&1 ||:) == kd.b8.nz. ]] \ && ip n show 10.2.0.1 | grep . &>/dev/null; then net=home elif ip r show default | grep 'via 10.0.3.1 dev wlan0' &>/dev/null && [[ $(timeout 1 dig +short @10.0.3.1 -x 10.0.3.1) == cmc1.lan. ]]; then net=work else echo "$0: error could not detect network" exit 1 fi } set-nm() { m nmcli con mod "$nm_con" ipv4.method manual ipv4.addresses $ip ipv4.gateway $gateway ipv4.dns $dns state=$(nmcli con show "$nm_con" 2>/dev/null | awk '$1 == "GENERAL.STATE:" {print $2}') if [[ $state == activated ]]; then m nmcli con up "$nm_con" fi } get-ip() { case $net in home) while read -r ip_suf host mac; do if [[ ! $ip_suf || $ip_suf == \#* ]]; then continue fi if [[ $mac != usb ]]; then continue fi if [[ $host == ${HOSTNAME}c ]]; then ip=10.2.0.$ip_suf/16 gateway=10.2.0.1 dns=8.8.8.4,8.8.8.8 break fi done

/dev/null; then if [[ $cur_method != manual ]]; then echo "$0: error. Need to be on wired network to get our ip" exit 1 fi set-dynamic sleep 10 fi myip=$(timeout 1 dig +short @192.168.0.25 $HOSTNAME.office.fsf.org) if [[ ! $myip ]]; then echo "$0: error: didnt detect home network and failed to get office ip" exit 1 fi dns=192.168.0.10,192.168.0.25 gateway=192.168.0.1 ip=$myip/24 ;; esac } get-cur-val() { local key key=$1 printf "%s\n" "$tmpstr" | awk '$1 == "'$key':" {print $2}' } get-cur() { tmpstr=$(nmcli con show "$nm_con" 2>/dev/null) cur_method=$(get-cur-val ipv4.method) cur_ip=$(get-cur-val ipv4.addresses) cur_gateway=$(get-cur-val ipv4.gateway) cur_dns=$(get-cur-val ipv4.dns) cur_state=$(get-cur-val GENERAL.STATE) } ## begin arg parsing ## force=false off=false while [[ $1 ]]; do case $1 in -f) force=true ;; off) off=true ;; *) echo "$0: error unexpected argument: $1" >&2 exit 1 ;; esac shift done ## end arg parsing ## ## begin common setup / detection ## shopt -s nullglob wiredx=1 declare -a args # device that has an eth0, but we aren't using it because it is # broken. We could just hardcode a mac comparison with `cat # /sys/class/net/eth0/address` but this is cooler. if [[ -e /sys/class/net/eth0 ]]; then bus_info=$(ethtool -i eth0 | awk '$1 == "bus-info:" { print $2 }') if [[ $bus_info != usb* ]]; then wiredx=2 fi fi eth_dev=eth$(( wiredx - 1 )) nm_con=$(nmcli device show $eth_dev | \ awk '$1 == "GENERAL.CONNECTION:" {out=$2; for(i=3;i<=NF;i++){out=out" "$i}; print out}' ||:) if [[ ! $nm_con || $nm_con == -- ]]; then nm_con="Wired connection $wiredx" fi if ! nmcli con | grep -q "^$nm_con " &>/dev/null; then # Note: we could support creation through a file or via # nmcli, but right now I'm ok with just having plugged in a device once # since this os was installed. echo "error: no existing connection: $nm_con found in output of nmcli con" exit 0 fi if ! type -p dig &>/dev/null; then apt-get install dig fi get-cur ## end common setup / detection ## if $off; then set-dynamic exit 0 fi detect-net get-ip if ! $force && [[ "$cur_method $cur_gateway $cur_dns $cur_ip" == "manual $gateway $dns $ip" ]]; then echo "$0: found expected state, nothing to do." exit 0 fi set-nm # example of down cli #nmcli con mod 'Wired connection 1' ipv4.method auto -ipv4.addresses 10.2.0.9/16 ipv4.gateway 0.0.0.0 -ipv4.dns "8.8.8.4,8.8.8.8" # FYI: the result of running, for example # nmcli con mod "Wired connection 1" \ # ipv4.method manual \ # ipv4.addresses "10.2.0.23/24" \ # ipv4.gateway "10.2.0.1" \ # ipv4.dns "8.8.8.4,8.8.8.8" # creates a fille named "/etc/NetworkManager/system-connections/Wired connection 1.nmconnection", # below. # # The nmcli man page says you should just edit files in that dir and # then run nmcli con reload to reread them all to load your changes, but # I've found that to be unreliable, the systemd journal would say # something like "reload happened" then nothing would change in the # connect that the file clearly modifies, so I switched over to using # the command line and just ignoring those files. # # I see no reason to keep the same file name, or a bunch of # setting that seem irrelevant, and empty sections don't seem to do # anything according to the man page. # # [connection] # id=Wired connection 1 # uuid=b0fb7694-dfe6-31a1-81fa-7c17b61515a7 # type=ethernet # interface-name=eth1 # timestamp=1715728264 # [ethernet] # [ipv4] # address1=10.2.0.23/16,10.2.0.1 # dns=8.8.8.4;8.8.8.8; # method=manual # [ipv6] # addr-gen-mode=stable-privacy # method=auto # [proxy]