Files
netbird/client/ui/scripts/mdm-toggle.sh
2026-06-16 11:33:10 +02:00

254 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Dev helper for testing NetBird's MDM policy on macOS.
#
# Edits /Library/Managed Preferences/io.netbird.client.plist (the same path the
# OS would populate from a real Configuration Profile). The MDM ticker observes
# the change within ≤60s; the daemon restarts the engine itself in-process.
#
# Usage:
# ./mdm-toggle.sh tui # interactive terminal UI
# ./mdm-toggle.sh show # print current plist
# ./mdm-toggle.sh clear # wipe (delete) the plist
# ./mdm-toggle.sh restart # kick the daemon
# ./mdm-toggle.sh logs [-f] # tail daemon log filtered for MDM lines
# ./mdm-toggle.sh verify # plist + recent MDM log lines
#
# Known keys (see client/mdm/policy.go):
# managementURL preSharedKey wireguardPort rosenpassEnabled rosenpassPermissive
# disableClientRoutes disableServerRoutes allowServerSSH disableAutoConnect
# blockInbound disableMetricsCollection splitTunnelMode splitTunnelApps
# disableProfiles disableNetworks disableUpdateSettings disableAdvancedView
set -euo pipefail
PLIST="/Library/Managed Preferences/io.netbird.client.plist"
LAUNCHD_LABEL="system/netbird"
DAEMON_LOG="/var/log/netbird/client.log"
LOG_GREP='MDM|mdm|policy|engine restart|config_changed|managed|RegisterUILog'
die() { echo "error: $*" >&2; exit 1; }
require_sudo() {
if [[ $EUID -ne 0 ]]; then
exec sudo --preserve-env=HOME "$0" "$@"
fi
}
ensure_plist() {
if [[ ! -f "$PLIST" ]]; then
mkdir -p "$(dirname "$PLIST")"
/usr/libexec/PlistBuddy -c "Save" "$PLIST" >/dev/null 2>&1 || \
/bin/cat >"$PLIST" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict/></plist>
EOF
chown root:wheel "$PLIST"
chmod 0644 "$PLIST"
fi
}
restart_daemon() {
echo "kicking daemon ($LAUNCHD_LABEL)..."
launchctl kickstart -k "$LAUNCHD_LABEL"
}
cmd_show() {
if [[ ! -f "$PLIST" ]]; then
echo "(no plist at $PLIST — MDM not active)"
return
fi
echo "# $PLIST"
/usr/libexec/PlistBuddy -c "Print" "$PLIST"
}
cmd_clear() {
if [[ -f "$PLIST" ]]; then
rm -f "$PLIST"
echo "removed $PLIST"
else
echo "(already absent)"
fi
}
cmd_logs() {
local follow=0
[[ "${1:-}" == "-f" || "${1:-}" == "--follow" ]] && follow=1
[[ -f "$DAEMON_LOG" ]] || die "daemon log not found at $DAEMON_LOG"
if [[ $follow -eq 1 ]]; then
echo "# tailing $DAEMON_LOG (Ctrl-C to stop) — filter: $LOG_GREP"
tail -F "$DAEMON_LOG" | grep -E --line-buffered --color=always "$LOG_GREP"
else
echo "# last MDM-relevant lines from $DAEMON_LOG"
grep -E --color=always "$LOG_GREP" "$DAEMON_LOG" | tail -30
fi
}
cmd_verify() {
echo "=== plist ==="
if [[ -f "$PLIST" ]]; then
/usr/libexec/PlistBuddy -c "Print" "$PLIST"
else
echo "(no plist at $PLIST — MDM not active)"
fi
echo
echo "=== daemon log (last 20 MDM-relevant lines) ==="
if [[ -f "$DAEMON_LOG" ]]; then
grep -E --color=always "$LOG_GREP" "$DAEMON_LOG" | tail -20 \
|| echo "(no MDM-related entries yet — wait up to 60s for next ticker fire)"
else
echo "(daemon log not found at $DAEMON_LOG)"
fi
}
cmd_tui() {
local KEYS=(
managementURL preSharedKey wireguardPort splitTunnelMode splitTunnelApps
rosenpassEnabled rosenpassPermissive disableClientRoutes disableServerRoutes
allowServerSSH disableAutoConnect blockInbound disableMetricsCollection
disableAdvancedView disableProfiles disableNetworks disableUpdateSettings
)
local TYPES=(
string string integer string array
bool bool bool bool
bool bool bool bool
bool bool bool bool
)
# ANSI helpers; bail to plain text when stdout isn't a tty.
local C_RESET="" C_BOLD="" C_DIM="" C_GREEN="" C_RED="" C_YELLOW="" C_CYAN=""
if [[ -t 1 ]]; then
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
C_GREEN=$'\033[32m'; C_RED=$'\033[31m'; C_YELLOW=$'\033[33m'; C_CYAN=$'\033[36m'
fi
read_value() {
local key="$1"
[[ -f "$PLIST" ]] || { echo ""; return; }
/usr/libexec/PlistBuddy -c "Print :$key" "$PLIST" 2>/dev/null || true
}
render_value() {
local raw="$1" type="$2"
if [[ -z "$raw" ]]; then
printf '%s—%s' "$C_DIM" "$C_RESET"
return
fi
case "$type" in
bool)
if [[ "$raw" == "true" ]]; then
printf '%s✔ true%s' "$C_GREEN" "$C_RESET"
else
printf '%s✘ false%s' "$C_RED" "$C_RESET"
fi ;;
array)
printf '%s[%s]%s' "$C_CYAN" "$(echo "$raw" | tr '\n' ' ' | tr -s ' ')" "$C_RESET" ;;
*)
printf '%s%s%s' "$C_YELLOW" "$raw" "$C_RESET" ;;
esac
}
render_screen() {
clear
printf '%s════ NetBird MDM Tester ════%s\n' "$C_BOLD" "$C_RESET"
printf '%splist:%s %s %sdaemon log:%s %s\n\n' \
"$C_DIM" "$C_RESET" "$PLIST" "$C_DIM" "$C_RESET" "$DAEMON_LOG"
local i
for i in "${!KEYS[@]}"; do
local key="${KEYS[$i]}" type="${TYPES[$i]}" raw
raw="$(read_value "$key")"
printf ' %s[%2d]%s %-30s %s%s(%s)%s\n' \
"$C_BOLD" $((i+1)) "$C_RESET" "$key" \
"$(render_value "$raw" "$type")" \
"$C_DIM" "$type" "$C_RESET"
done
printf '\n'
printf ' %s[k]%s kick daemon %s[c]%s clear plist %s[l]%s show recent log %s[r]%s refresh %s[q]%s quit\n' \
"$C_BOLD" "$C_RESET" "$C_BOLD" "$C_RESET" "$C_BOLD" "$C_RESET" "$C_BOLD" "$C_RESET" "$C_BOLD" "$C_RESET"
printf '\n'
}
edit_field() {
local idx="$1"
if ! [[ "$idx" =~ ^[0-9]+$ ]] || (( idx < 1 || idx > ${#KEYS[@]} )); then
echo " (out of range)"; sleep 0.6; return
fi
local key="${KEYS[$((idx-1))]}" type="${TYPES[$((idx-1))]}" cur
cur="$(read_value "$key")"
ensure_plist
case "$type" in
bool)
# Cycle: unset → true → false → unset
case "$cur" in
"") /usr/libexec/PlistBuddy -c "Add :$key bool true" "$PLIST" ;;
"true") /usr/libexec/PlistBuddy -c "Delete :$key" "$PLIST"
/usr/libexec/PlistBuddy -c "Add :$key bool false" "$PLIST" ;;
"false") /usr/libexec/PlistBuddy -c "Delete :$key" "$PLIST" ;;
esac ;;
array)
echo " current: $cur"
read -r -p " new array (space-separated, empty to unset): " line
/usr/libexec/PlistBuddy -c "Delete :$key" "$PLIST" 2>/dev/null || true
if [[ -n "$line" ]]; then
/usr/libexec/PlistBuddy -c "Add :$key array" "$PLIST"
local i=0 v
for v in $line; do
/usr/libexec/PlistBuddy -c "Add :$key:$i string $v" "$PLIST"
i=$((i+1))
done
fi ;;
*)
echo " current: $cur"
read -r -p " new $type (empty to unset): " line
/usr/libexec/PlistBuddy -c "Delete :$key" "$PLIST" 2>/dev/null || true
if [[ -n "$line" ]]; then
/usr/libexec/PlistBuddy -c "Add :$key $type $line" "$PLIST"
fi ;;
esac
}
show_recent_log() {
clear
printf '%s── recent MDM-related daemon log lines ──%s\n' "$C_BOLD" "$C_RESET"
if [[ -f "$DAEMON_LOG" ]]; then
grep -E --color=always "$LOG_GREP" "$DAEMON_LOG" | tail -25 \
|| echo "(no MDM-related entries yet)"
else
echo "(daemon log not found at $DAEMON_LOG)"
fi
echo
read -r -p "press enter to return " _
}
echo "starting MDM tester (Ctrl-C or q to quit)"
while true; do
render_screen
read -r -p "> " choice || break
case "$choice" in
q|quit|exit) break ;;
k|kick) restart_daemon; sleep 0.6 ;;
c|clear) cmd_clear; sleep 0.6 ;;
l|logs) show_recent_log ;;
r|"") : ;; # redraw
*) edit_field "$choice" ;;
esac
done
echo "bye"
}
sub="${1:-}"; shift || true
case "$sub" in
show) cmd_show "$@"; exit 0 ;;
logs) require_sudo "$sub" "$@"; cmd_logs "$@"; exit 0 ;;
verify) require_sudo "$sub" "$@"; cmd_verify "$@"; exit 0 ;;
tui) require_sudo "$sub" "$@"; cmd_tui "$@"; exit 0 ;;
restart) require_sudo "$sub" "$@"; restart_daemon; exit 0 ;;
clear) require_sudo "$sub" "$@"; cmd_clear "$@"; exit 0 ;;
""|-h|--help|help)
sed -n '2,18p' "$0"
;;
*) die "unknown subcommand: $sub (try --help)" ;;
esac