#! /bin/bash . /usr/lib/network/globals NETCTL_VERSION=notpackaged usage() { cat << END Usage: netctl {COMMAND} [PROFILE] [--help|--version] Commands: list List available profiles store Save which profiles are active restore Load saved profiles stop-all Stops all profiles start [PROFILE] Start a profile stop [PROFILE] Stop a profile restart [PROFILE] Restart a profile switch-to [PROFILE] Switch to a profile status [PROFILE] Show runtime status of a profile enable [PROFILE] Enable the systemd unit for a profile disable [PROFILE] Disable the systemd unit for a profile reenable [PROFILE] Reenable the systemd unit for a profile END } sd_escape() { local IFS='' # Prevent a recursion loop on backspaces set "${@//\\/\\x5c}" while [[ "$*" =~ [^[:alnum:].:_/\\] ]]; do set "${@//$BASH_REMATCH/$(printf '\\x%x' "'$BASH_REMATCH")}" done printf '%s\n' "${@//\//-}" } # Wrapper around systemctl to convert profile names to unit names sd_call() { local command=$1 shift set $(sd_escape "$@") systemctl $command $(printf 'netctl@%s.service\n' "$@") } list() { local indicators=( '*' ' ' ) list_profiles | while read -r Profile; do sd_call "is-active --quiet" "$Profile" &> /dev/null # Make the return variable boolean [[ $? -eq 0 ]]; printf '%s %s\n' "${indicators[$?]}" "$Profile" done } store() { mkdir -p "$(dirname "$STATE_FILE")" list_profiles | while read -r Profile; do if sd_call "is-active --quiet" "$Profile" &> /dev/null; then printf "%s\n" "$Profile" fi done > "$STATE_FILE" } restore() { if [[ ! -r $STATE_FILE ]]; then exit_error "Could not read state file '$STATE_FILE'" elif [[ ! -s $STATE_FILE ]]; then report_debug "No profiles to restore in state file '$STATE_FILE'" else mapfile -t Profiles < "$STATE_FILE" do_debug sd_call start "${Profiles[@]}" fi } stop_all() { # We cannot pipe to mapfile, as the end of a pipe is inside a subshell mapfile -t Profiles < <(list_profiles) [[ $Profiles ]] && do_debug sd_call stop "${Profiles[@]}" 2> \ >(grep -Fv "not loaded" >&2) } switch_to() { cd "$PROFILE_DIR" if [[ ! -r $1 ]]; then exit_error "Profile '$1' does not exist or is not readable" fi # We assume interface names are not quoted # Using read removes leading whitespace read InterfaceLine < \ <(grep -om1 "^[[:space:]]*Interface=[[:alnum:]:._-]\+" "$1") if [[ -z $InterfaceLine ]]; then exit_error "Profile '$1' does not specify an interface" fi mapfile -t AllProfiles < <(list_profiles) mapfile -t Profiles < <(grep -Fl "$InterfaceLine" "${AllProfiles[@]}") [[ $Profiles ]] && do_debug sd_call stop "${Profiles[@]}" 2> \ >(grep -Fv "not loaded" >&2) do_debug sd_call start "$1" } unit_enable() { local unit="/etc/systemd/system/netctl@$(sd_escape "$1").service" if [[ -e $unit ]]; then report_error "A unit file for profile '$1' already exists" return 1 fi load_profile "$1" echo ".include /usr/lib/systemd/system/netctl@.service" > "$unit" echo -e "\n[Unit]" >> "$unit" [[ -n $Description ]] && echo "Description=$Description" >> "$unit" : ${BindsToInterfaces=$Interface} printf 'BindsTo=sys-subsystem-net-devices-%s.device\n' \ $(sd_escape "${BindsToInterfaces[@]}") >> "$unit" printf 'After=sys-subsystem-net-devices-%s.device\n' \ $(sd_escape "${BindsToInterfaces[@]}") >> "$unit" if [[ -n $After ]]; then printf 'After="netctl@%s.service"\n' \ $(sd_escape "${After[@]}") >> "$unit" fi echo "ln -s '$unit' '${unit/system\//system/multi-user.target.wants/}'" ln -s "$unit" "${unit/system\//system/multi-user.target.wants/}" systemctl daemon-reload } unit_disable() { local unit="/etc/systemd/system/netctl@$(sd_escape "$1").service" if sd_call "is-enabled --quiet" "$1" &> /dev/null; then sd_call disable "$1" fi if [[ ! -f $unit ]]; then report_error "No regular unit file found for profile '$1'" return 1 fi do_debug rm "$unit" systemctl daemon-reload } case $# in 1) case $1 in --version) report_notice "netctl version $NETCTL_VERSION";; --help) usage;; list) list;; store|restore) ensure_root "$(basename "$0")" "$1";; stop-all) stop_all;; *) exit_error "$(usage)";; esac;; 2) case $1 in start|stop|restart|status) sd_call "$1" "$2";; switch-to) ensure_root "$(basename "$0")" switch_to "$2";; enable|disable) ensure_root "$(basename "$0")" "unit_$1" "$2";; reenable) ensure_root "$(basename "$0")" unit_disable "$2" unit_enable "$2";; *) exit_error "$(usage)";; esac;; *) exit_error "$(usage)";; esac # vim: ft=sh ts=4 et sw=4: