diff --git a/.secrets.env b/.secrets.env new file mode 100644 index 0000000..84ef985 --- /dev/null +++ b/.secrets.env @@ -0,0 +1,4 @@ +SNIPEIT_APIKEY= +SNIPEIT_DOMAIN= +REPO= +BRANCH= \ No newline at end of file diff --git a/README.md b/README.md index 45a56d3..8f5c2fa 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ -# compose +# compose your linux +This repo contains all files to modify the system after it has been installed. + +Please make sure to create the file `.secrets.env` with the following content: + +``` +SNIPEIT_APIKEY= # https://snipe-it.readme.io/reference/generating-api-tokens +SNIPEIT_DOMAIN= # orga.z31.it +REPO= # The URL of the Repo were your want to get the angestoepselt-info info Script, like https://codeberg.org/angestoepselt/compose +BRANCH= # the Branach of the REPO, default at codeberg.org is main +``` + +``` + Options: + INFO: + -h|--help print this info + -v|--version show version of script + + -a|--all running all task below + + -u|--update perform an basic system update + -i|--install install all packages for this system + -r|--register Register PC in snipe-IT Asset Management + -c|--configure running basic system config settings + -d|--files_download download useful files to your HomeDirectory + -m|--modify_dm paint your Desktop Manager with angestoepselt design + --checkinfo Check if PC is already in snipe-IT present + --clean clean up trash, like files and from system tasks like packagemanger +``` + +--- + + +### Take care of snipeIT Custom fields at system_register + +``` +{ + "status_id": "2", + "name": "$Model", + "model_id": "$model", + "serial": "$serialno", + "_snipeit_mac_address_1": "$HW_ADDRESS", + "_snipeit_betriebssystem_2": "$Distro", + "_snipeit_festplatte_4": "$HDD", + "_snipeit_prozessor_5": "$CPU", + "_snipeit_arbeitsspeicher_6": "$Memory", + "_snipeit_optisches_laufwerk_7": "$Optical_Drive", + "_snipeit_display_8": "$display", + "_snipeit_akku_9": "$Battery_life", + "_snipeit_anydeskid_10": "$Anydesk_ID" +} +``` + +### fetch all informations about Custom Fields with an API request +``` +export $(cat .secrets-env) && \ +curl --silent --request GET --url "https://$SNIPEIT_DOMAIN/api/v1/fields" --header 'accept: application/json' --header 'authorization: Bearer '$SNIPEIT_APIKEY'' | jq .rows[].db_column_name +``` \ No newline at end of file diff --git a/angestoepselt-info b/angestoepselt-info new file mode 100644 index 0000000..f09f801 --- /dev/null +++ b/angestoepselt-info @@ -0,0 +1,249 @@ +#!/bin/bash +# This ist a small script which collect system information of a Linux PC and print it as json +# created by matthias of angestöpselt e.V. +# created at 2022-02-17 + +version=1.0 +charity=angestoepselt +os=Linux + +read_uname() { + read -ra uname <<< "$(uname -srm)" + + kernel_name="${uname[0]}" + kernel_version="${uname[1]}" + kernel_machine="${uname[2]}" +} + +get_memory() { + while IFS=":" read -r a b; do + case $a in + "MemTotal") ((mem_used+=${b/kB})); mem_total="${b/kB}" ;; + "Shmem") ((mem_used+=${b/kB})) ;; + "MemFree" | "Buffers" | "Cached" | "SReclaimable") + mem_used="$((mem_used-=${b/kB}))" + ;; + + "MemAvailable") + mem_avail=${b/kB} + ;; + esac + done < /proc/meminfo + + mem_total="$((mem_total / 1024))" + mem_total=$(awk '{printf "%.2f", $1 / $2}' <<< "$mem_total 1024") + mem_label=GiB + + memory="${mem_total}${mem_label:-MiB}" +} + +get_cpu() { + + # Get CPU name. + cpu_file="/proc/cpuinfo" + + + cpu="$(awk -F '\\s*: | @' \ + '/model name|Hardware|Processor|^cpu model|chip type|^cpu type/ { + cpu=$2; if ($1 == "Hardware") exit } END { print cpu }' "$cpu_file")" + + + # Remove un-needed patterns from cpu output. + cpu="${cpu//(TM)}" + cpu="${cpu//(tm)}" + cpu="${cpu//(R)}" + cpu="${cpu//(r)}" + cpu="${cpu//CPU}" + cpu="${cpu//Processor}" + cpu="${cpu//Dual-Core}" + cpu="${cpu//Quad-Core}" + cpu="${cpu//Six-Core}" + cpu="${cpu//Eight-Core}" + cpu="${cpu//[1-9][0-9]-Core}" + cpu="${cpu//[0-9]-Core}" + cpu="${cpu//, * Compute Cores}" + cpu="${cpu//Core / }" + cpu="${cpu//(\"AuthenticAMD\"*)}" + cpu="${cpu//with Radeon * Graphics}" + cpu="${cpu//, altivec supported}" + cpu="${cpu//FPU*}" + cpu="${cpu//Chip Revision*}" + cpu="${cpu//Technologies, Inc}" + cpu="${cpu//Core2/Core 2}" + + # Remove CPU brand from the output. + cpu="${cpu/AMD}" + cpu="${cpu/Intel}" + cpu="${cpu/Core? Duo}" + cpu="${cpu/Qualcomm}" + cpu="${cpu//[[:space:]]}" +} + +get_disk() { + type -p df &>/dev/null || + { err "Disk requires 'df' to function. Install 'df' to get disk info."; return; } + + df_version=$(df --version 2>&1) + + # Create an array called 'disks' where each element is a separate line from + # df's output. We then unset the first element which removes the column titles. + IFS=$'\n' read -d "" -ra disks <<< "$(df "${df_flags[@]}" "${disk_show[@]:-/}")" + unset "disks[0]" + + # Stop here if 'df' fails to print disk info. + [[ ${disks[*]} ]] || { + err "Disk: df failed to print the disks, make sure the disk_show array is set properly." + return + } + + for disk in "${disks[@]}"; do + # Create a second array and make each element split at whitespace this time. + IFS=" " read -ra disk_info <<< "$disk" + disk=$((disk_info[${#disk_info[@]} - 5] / 1024/ 1024))G + + done +} + +get_model() { + + if [[ -d /system/app/ && -d /system/priv-app ]]; then + model="$(getprop ro.product.brand) $(getprop ro.product.model)" + + elif [[ -f /sys/devices/virtual/dmi/id/board_vendor || + -f /sys/devices/virtual/dmi/id/board_name ]]; then + model=$(< /sys/devices/virtual/dmi/id/board_vendor) + model+=" $(< /sys/devices/virtual/dmi/id/board_name)" + + elif [[ -f /sys/devices/virtual/dmi/id/product_name || + -f /sys/devices/virtual/dmi/id/product_version ]]; then + model=$(< /sys/devices/virtual/dmi/id/product_name) + model+=" $(< /sys/devices/virtual/dmi/id/product_version)" + + elif [[ -f /sys/firmware/devicetree/base/model ]]; then + model=$(< /sys/firmware/devicetree/base/model) + + elif [[ -f /tmp/sysinfo/model ]]; then + model=$(< /tmp/sysinfo/model) + fi + + # Remove dummy OEM info. + model=${model//To be filled by O.E.M.} + model=${model//To Be Filled*} + model=${model//OEM*} + model=${model//Not Applicable} + model=${model//System Product Name} + model=${model//System Version} + model=${model//Undefined} + model=${model//Default string} + model=${model//Not Specified} + model=${model//Type1ProductConfigId} + model=${model//INVALID} + model=${model//All Series} + model=${model//�} + + case $model in + "Standard PC"*) model="KVM/QEMU (${model})" ;; + OpenBSD*) model="vmm ($model)" ;; + esac +} + +get_vendor() { + sys_vendor=$(cat /sys/devices/virtual/dmi/id/sys_vendor) +} + +get_distro() { + source /etc/os-release + distro="${NAME} ${VERSION_ID}" +} + +get_serial() { + [ `id -u` -eq 0 ] && serialno=$(sudo cat /sys/devices/virtual/dmi/id/product_serial) +} + +get_category() { + category=$(hostnamectl | grep Chassis | cut -c 21-) +} + +get_odd() { + [[ $(lsblk --include 11 -n) ]] && odd="1" || odd="0" +} + +get_mac() { + mac=$(ip link | sed -n "/BROADCAST.*state UP/{n;p}" | tail -1 | tr -s " " | cut -d" " -f3) +} + + +get_anydesk_id() { + dpkg-query -l "anydesk" &>/dev/null && anydesk_status="TRUE" || anydesk_status="FALSE" + if [ "$anydesk_status" == "TRUE" ]; then + # fetching the id of anydesk consumes most of the time when calling the script. Room for improvement + andydesk_id=$(/usr/bin/anydesk --get-id) + fi +} + +get_battery() { + for bat in /sys/class/power_supply/*; do + if [[ $bat =~ BAT ]]; then + source $bat/uevent + energy_full_design="$(< "${bat}/energy_full_design")" + energy_full="$(< "${bat}/energy_full")" + battery=`expr ${energy_full} \* 100 / ${energy_full_design}`% + fi + done +} + +get_resolution() { + resolution=$(xdpyinfo | awk '/dimensions/ {print $2}') +} + +get_display() { + display=`xrandr | awk '/ connected/{print sqrt( ($(NF-2)/10)^2 + ($NF/10)^2 )/2.54}' | cut -c -2 | head -n 1` +} + +get_desktopmanager() { + echo $XDG_CURRENT_DESKTOP +} + +# fetch informations +read_uname +get_memory +get_cpu +get_disk +get_model +get_vendor +get_distro +get_serial +get_category +get_odd +get_mac +get_desktopmanager +get_anydesk_id + + +# fetch additional laptop infos +if [[ $category == "laptop" ]]; then + get_battery + get_resolution + get_display +fi + +# print summary in json + +printf '{\n' +printf ' %s\n' "\"Charity\": \"${charity}\"", +printf ' %s\n' "\"Distro\": \"${distro}\"", +printf ' %s\n' "\"Vendor\": \"${sys_vendor}\"", +printf ' %s\n' "\"Model\": \"${model}\"", +printf ' %s\n' "\"Serial\": \"${serialno}\"", +printf ' %s\n' "\"Platform\": \"${category}\"", +printf ' %s\n' "\"CPU\": \"${cpu}\"", +printf ' %s\n' "\"Memory\": \"${memory}\"", +printf ' %s\n' "\"HDD\": \"${disk}\"", +printf ' %s\n' "\"HW_ADDRESS\": \"${mac}\"", +printf ' %s\n' "\"Anydesk_ID\": \"${andydesk_id}\"", +printf ' %s\n' "\"Resolution\": \"${resolution}\"", +printf ' %s\n' "\"Displaysize\": \"${display}\"", +printf ' %s\n' "\"Battery_life\": \"${battery}\"", +printf ' %s\n' "\"Optical_Drive\": \"${odd}\"", +printf ' %s\n' "\"Version\": \"${version}\"" +printf '}\n' diff --git a/late_command.sh b/late_command.sh new file mode 100644 index 0000000..51dfa3d --- /dev/null +++ b/late_command.sh @@ -0,0 +1,515 @@ +#!/bin/bash +# Install and configure a Ubuntu-PC for the charity organization angestöpselt e.V. | angestoepselt.de +# created by matthias of angestöpselt e.V. +# created at 2022-02-17 + +version=0.2 + +set -o errexit +exec 100>/tmp/z31.lock || exit 1 +flock 100 || exit 1 + +# Speed up script by not using unicode. +LC_ALL=C +LANG=C + + +# some static variables +DEBIAN_FRONTEND=noninteractive +current_user=$(id 1000 | awk -F '[()]' '{print $2}') +lockfile=/tmp/z31.lock +sysinfofile=$HOME/.config/systeminfo.json +random_tmpdir=$(mktemp) +SCRIPT_DIR="$(dirname "$0")" +TIME=$(date +%Y%m%d%H%M) +LOGTIME=$(date "+%Y-%m-%d %H:%M:%S") +LOGFILE="$SCRIPT_DIR/$TIME-z31.log" + +# Set variables for stdout print +COLOR_N='\e[0m' +COLOR_GREEN='\e[1;32m' +COLOR_RED='\e[1;31m' +COLOR_BLUE='\e[1;34m' +COLOR_WHITE='\e[1;97m' +OK="[${COLOR_GREEN}ok${COLOR_N}]" +FAIL="[${COLOR_RED}fail${COLOR_N}]" +INFO="[${COLOR_BLUE}info${COLOR_N}]" +SKIP="[${COLOR_BLUE}skip${COLOR_N}]" +PROGRESS="[${COLOR_BLUE}..${COLOR_N}]" + +# log actual command and it's stderr to logfile in one go +runcmd_stdout() { + echo "+ $1" >>"$LOGFILE" + bash -c -o pipefail "$1" 2>>"$LOGFILE" | tee -a "$LOGFILE" +} + +runcmd() { + echo "+ $1" >>"$LOGFILE" + bash -c -o pipefail "$1" >>"$LOGFILE" 2>&1 +} + +printprog() { + echo -ne "${PROGRESS} $*\r" +} + +printok() { + echo -e "\r${OK} $*" +} + +printfail() { + echo -e "\r${FAIL} $*" +} + +printinfo() { + echo -e "${INFO} $*" +} + +printskip() { + echo -e "\r${SKIP} $*" +} + +ErrorHandling() { + set -eu + printfail "Something went wrong, exiting. Check $LOGFILE for more details." +} + +system_update() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "update cache" + runcmd "sudo /usr/bin/apt-get update -qq" + printok "update cache" + + printprog "upgrade system" + runcmd "sudo /usr/bin/apt-get dist-upgrade -y -qq" + printok "upgrade system" +} + +system_install_base() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "add anydesk repo to source.list.d" + + if [ -f "/etc/apt/sources.list.d/anydesk-stable.list" ]; then + printskip "add anydesk repo to source.list.d" + else + runcmd "sudo /usr/bin/wget -qO - https://keys.anydesk.com/repos/DEB-GPG-KEY | sudo apt-key add - && \ + echo "deb http://deb.anydesk.com/ all main" > /etc/apt/sources.list.d/anydesk-stable.list" + printok "add anydesk repo to source.list.d" + fi + + echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections + + printprog "install apt packages (this can take some time)" + runcmd "sudo /usr/bin/apt-get install --yes -qq -o Dpkg::Options::=--force-confdef --ignore-missing \ + ubuntu-restricted-extras \ + apt-transport-https \ + vlc \ + gdebi \ + gparted \ + tlp \ + language-pack-de-base \ + libreoffice \ + libreoffice-l10n-de \ + libreoffice-help-de \ + libreoffice-help-en-gb \ + libreoffice-l10n-en-gb \ + firefox \ + firefox-locale-de \ + firefox-locale-en \ + thunderbird \ + thunderbird-locale-de \ + thunderbird-locale-en \ + thunderbird-locale-en-gb \ + gimp \ + anydesk \ + libqt5qml5 \ + language-selector-common \ + openoffice.org-hyphenation \ + language-pack-kde-ar \ + language-pack-kde-uk \ + language-pack-kde-ru \ + language-pack-kde-fa" + printok "install apt packages" +} + +system_install_edubuntu() { + set -o pipefail + trap ErrorHandling ERR INT + + # Edubuntu packages (official support until 18.04) https://wiki.ubuntuusers.de/Edubuntu_Programme + printprog "install Edubuntu packages (this can take some time)" + runcmd "sudo /usr/bin/apt-get install --yes -qq -o Dpkg::Options::=--force-confdef --ignore-missing \ + blinken \ + calibre \ + cantor \ + chemtool \ + dia \ + einstein \ + fritzing \ + gamine \ + gcompris \ + goldendict \ + inkscape \ + kalgebra \ + kalzium \ + kanagram \ + kbruch \ + kgeography \ + khangman \ + kig \ + klettres \ + kmplot \ + kstars \ + ktouch \ + ktuberling \ + kturtle \ + kwordquiz \ + laby \ + lightspeed \ + lybniz \ + marble \ + melting \ + parley \ + pencil2d \ + ri-li \ + rocs \ + step \ + tuxmath \ + tuxpaint \ + tuxtype \ + yorick" + printok "install Edubuntu packages" +} + +system_clean() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "clean up apt cache and packages" + runcmd "sudo /usr/bin/apt-get autoremove --yes" + printok "clean up apt cache and packages" +} + +system_configure() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "set timezone" + runcmd "timedatectl set-timezone Europe/Berlin" + printok "set timezone" +} + +system_files_download() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "" + runcmd "" + printok "" +} + +system_modify_dm() { + set -o pipefail + trap ErrorHandling ERR INT + # TODO: detect dm of device and choose modification + # cat /etc/X11/default-display-manager > /usr/sbin/gdm3 (Gnome Desktop Manager) + # goals to cover + # - Wallpaper + # - + printprog "" + runcmd "" + printok "" +} + + +system_checkinfo() { + set -o pipefail + trap ErrorHandling ERR INT + + # this part checks if the device is already registered in snipe-IT + # is already registered it returns 1 + # this is a new device it returns 0 + + if [ ! -f $sysinfofile ]; then + printprog "write systeminfo to $sysinfofile" + runcmd "mkdir /home/$current_user/.config/angestoepselt && /bin/bash /usr/local/bin/angestoepselt-info > $sysinfofile" + printok "write systeminfo to $sysinfofile" + fi + + printprog "read systeminfo" + runcmd "jq -e . $sysinfofile >/dev/null 2>&1" + printok "read systeminfo" + + HW_ADDRESS=$(cat $sysinfofile | jq -r .HW_ADDRESS) + ASSET_TAG=$(cat $sysinfofile | jq -r .Asset_Tag) + + ## start localcheck + if [[ $ASSET_TAG =~ ^[0-9]+$ ]]; then + printinfo "already in Snipe $ASSET_TAG" + return 1 + elif + [ -f $SCRIPT_DIR/mac_white.list ] && grep -Fxq "$HW_ADDRESS" mac_white.list + then + unset HW_ADDRESS + printinfo "Device is using a charity owned USB Ethernet-Adapter" + return 1 + ## end localcheck + else + ## start snipe-it check + printprog "check presence of snipe-IT API" + runcmd "curl -o /dev/null --silent https://$SNIPEIT_DOMAIN" + printok "check presence of snipe-IT API" + + printinfo "fetch info from Snipe-IT" + result=$(curl --silent --request GET \ + --url "https://$SNIPEIT_DOMAIN/api/v1/hardware?limit=1&search=${HW_ADDRESS//[:]/%3A}" \ + --header 'accept: application/json' \ + --header 'authorization: Bearer '$SNIPEIT_APIKEY'') + + [[ $(echo $result | jq '(has("error"))') == true ]] && { printinfo "$(echo $result | jq .error)"; return 1; } + system_check_result=$(echo $result | jq .total) # doesn't work + asset_tag=$(echo $result | jq -r .rows[].asset_tag) + + if [ -n "$asset_tag" ]; then + printinfo "Device already present in Snipe-IT: RE$asset_tag" + + printprog "add ASSET_TAG to $sysinfofile" + runcmd "jq '. |= . + {"Asset_Tag": \"'${asset_tag}'\"}' $sysinfofile > $random_tmpdir/systeminfo.json.tmp && mv $random_tmpdir/systeminfo.json.tmp $sysinfofile" + printok "add ASSET_TAG to $sysinfofile" + else + printok "Device not in Snipe-IT" + system_checkinfo_value="newdevice" + return 0 + fi + ## end snipe-it check + fi +} + +system_register() { + set -o pipefail + trap ErrorHandling ERR INT + + if [[ "$system_checkinfo_value" != "newdevice" ]]; then + printskip "register Device at Snipe-IT" + fi + + # is snipe-IT reachable? + printprog "check presence of snipe-IT API" + runcmd "curl -o /dev/null --silent https://$SNIPEIT_DOMAIN" + printok "check presence of snipe-IT API" + + # convert json to variables, like key=value + eval "$(jq -r '. | to_entries | .[] | .key + "=" + (.value | @sh)'< $sysinfofile)" + + printinfo "fetching variables for register $Platform at Snipe-IT" + + # get platform. at the moment this looks for a laptop in string. If ist not a laptop it is a desktop PC (model 2) + [[ "$Platform" == *"laptop"* ]] && { model=1; display="$Displaysize @ $Resolution"; } || model=2 + # clean up anydesk variable. > room for improvement, only integer should be in the var. This can be handled in the angestoepselt-info script + [[ "$Anydesk_ID" == *"SERVICE_NOT_RUNNING"* ]] && unset Anydesk_ID + # cannont locate issue with true and false, simple workaround to fix it + [[ "$Optical_Drive" == "FALSE" ]] && Optical_Drive=nein || Optical_Drive=ja + + + # change this variables used in this post to your needs, read the README of this repo and read ahead in the Snipe-IT API docs for more Informations + # https://snipe-it.readme.io/reference/updating-custom-fields + post_data() +{ + cat < $random_tmpdir/systeminfo.json.tmp && mv $random_tmpdir/systeminfo.json.tmp $sysinfofile" + printok "add PC to $sysinfofile" + else + printfail "$post_message" + fi +} + +script_check_root() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "check root privileges" + runcmd "[ $(id -u) -eq 0 ]" + printok "check root privileges" +} + +script_check_snipesecrets() { + set -o pipefail + trap ErrorHandling ERR INT + + printprog "check .secrets.env" + runcmd "[ -f $SCRIPT_DIR/.secrets.env ] && export $(cat $SCRIPT_DIR/.secrets.env)" + printok "check .secrets.env" +} + +script_prerequisites() { + set -o pipefail + trap ErrorHandling ERR INT + + printinfo "install requirements" + + printprog "install required packages" + runcmd "sudo /usr/bin/apt-get -qq --yes install jq moreutils dialog html2ps python3 python3-pip curl wkhtmltopdf" + printok "install required packages" + + printprog "install angestoepselt-info" + runcmd "sudo /usr/bin/wget –quiet $REPO/raw/branch/master/angestoepselt-info -O /usr/local/bin/angestoepselt-info && sudo chmod +x /usr/local/bin/angestoepselt-info" + printok "install angestoepselt-info" + +} + + +usage() { + printf "%s" "\ +Usage: $0 --option + + Options: + INFO: + -h|--help print this info + -v|--version show version of script + + -a|--all running all task below + + -u|--update perform an basic system update + -i|--install install all packages for this system + -r|--register Register PC in snipe-IT Asset Management + -c|--configure running basic system config settings + -d|--files_download download useful files to your HomeDirectory + -m|--modify_dm paint your Desktop Manager with angestoepselt design + --checkinfo Check if PC is already in snipe-IT present + --clean clean up trash, like files and from system tasks like packagemanger + +" + exit 1 +} + +POSITIONAL_ARGS=() + +while [[ $# -gt 0 ]]; do +case $1 in + -h|--help) + usage + ;; + -u|--update) + script_check_root + system_update + shift + shift + ;; + -i|--install) + script_check_root + system_install_base + system_install_edubuntu + shift + shift + ;; + --clean) + script_check_root + system_clean + shift + shift + ;; + --charity) + charity=${OPTARG:=angestoepselt} + shift + shift + ;; + -c|--configure) + script_check_root + system_configure + shift + shift + ;; + -d|--files_download) + system_files_download + shift + shift + ;; + -m|--modify_dm) + script_check_root + system_modify_dm + shift + shift + ;; + --checkinfo) + script_check_snipesecrets + system_checkinfo + shift + shift + ;; + -r|--register) + script_check_snipesecrets + system_register + shift + shift + ;; + -a|--all) + script_check_root + system_update + system_install + system_clean + system_configure + system_files_download + system_modify_dm + script_check_snipesecrets + system_checkinfo + system_register + exit 0 + ;; + -v|--version) + printf '%s\n' "$0 v$version" + exit 1 + ;; + -*|--*) + echo "Unknown option $1" + usage + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + shift + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +if [[ -n $1 ]]; then + echo "Last line of file specified as non-opt/last argument:" + tail -1 "$1" +fi diff --git a/mac_white.list b/mac_white.list new file mode 100644 index 0000000..b3905ae --- /dev/null +++ b/mac_white.list @@ -0,0 +1,2 @@ +# mac white list for USB Adapter +# these mac will be ignored during snipe check