#!/bin/bash # Install and configure a Linux Mint 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/setup_system.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/setup_system.lock sysinfofolder=/var/angestoepselt sysinfofile=systeminfo.json random_tmpfile=$(mktemp) SCRIPT_DIR="$(dirname "$0")" TIME=$(date +%Y%m%d%H%M) LOGTIME=$(date "+%Y-%m-%d %H:%M:%S") LOGFILE="$SCRIPT_DIR/$TIME-setup_system.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 "/usr/bin/apt-get update -qq" printok "update cache" printprog "upgrade system" runcmd "/usr/bin/apt-get dist-upgrade -y -qq" printok "upgrade system" } install_if_not_exist() { if /usr/bin/dpkg -s "$1" &>/dev/null; then PKG_EXIST=$(/usr/bin/dpkg -s "$1" | grep "install ok installed") if [[ -n "$PKG_EXIST" ]]; then return fi fi /usr/bin/apt-get install --no-install-recommends --yes "$1" } system_install_rustdesk() { set -o pipefail trap ErrorHandling ERR INT # get lastest version of Rustdesk RUSTDESK_VERSION=$(curl -sL https://api.github.com/repos/rustdesk/rustdesk/releases/latest | jq -r ".tag_name") printprog "downloading rustdesk version $RUSTDESK_VERSION" runcmd "/usr/bin/wget \ https://github.com/rustdesk/rustdesk/releases/download/$RUSTDESK_VERSION/rustdesk-$RUSTDESK_VERSION-x86_64.deb \ -O /tmp/rustdesk-$RUSTDESK_VERSION-x86_64.deb" printok "downloading rustdesk $RUSTDESK_VERSION" printprog "install rustdesk" runcmd "/usr/bin/apt-get install -fy /tmp/rustdesk-$RUSTDESK_VERSION-x86_64.deb" printok "install rustdesk" printprog "add rustdesk ID to $sysinfofile" RDP_ID=$(/usr/bin/rustdesk --get-id) runcmd "/usr/bin/jq --arg rdp_id "$RDP_ID" '. + {RDP_ID: $rdp_id}' "$sysinfofolder/$sysinfofile" > tmp.json && mv tmp.json "$sysinfofolder/$sysinfofile"" printok "add rustdesk ID to $sysinfofile" } system_install_base() { set -o pipefail trap ErrorHandling ERR INT printprog "install apt packages (this can take some time)" runcmd "distro=$(if echo " una bookworm vanessa focal jammy bullseye vera uma " | grep -q " $(lsb_release -sc) "; then lsb_release -sc; else echo focal; fi) ; \ /usr/bin/wget -O- https://deb.librewolf.net/keyring.gpg | gpg --dearmor -o /usr/share/keyrings/librewolf.gpg \ ; \ /usr/bin/tee /etc/apt/sources.list.d/librewolf.sources << EOF > /dev/null Types: deb URIs: https://deb.librewolf.net Suites: $distro Components: main Architectures: amd64 Signed-By: /usr/share/keyrings/librewolf.gpg EOF ; \ /usr/bin/apt-get install --yes -qq -o Dpkg::Options::=--force-confdef --ignore-missing \ 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 \ librewolf \ thunderbird \ thunderbird-locale-de \ thunderbird-locale-en \ thunderbird-locale-en-gb \ gimp \ 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 "/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 "/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 "" } snipeit_add_asset_to_sysinfofile() { set -o pipefail trap ErrorHandling ERR INT mkdir $random_tmpdir printprog "add ASSET_TAG to $sysinfofile" runcmd "/usr/bin/jq --arg asset_tag "$asset_tag" '. + {Asset_Tag: $asset_tag}' "$sysinfofolder/$sysinfofile" > "$random_tmpfile" && mv "$random_tmpfile" "$sysinfofolder/$sysinfofile"" printok "add ASSET_TAG to $sysinfofile" } snipeit_instance_check() { set -o pipefail trap ErrorHandling ERR INT # is snipe-IT reachable? printprog "check presence of snipe-IT API" runcmd "/usr/bin/curl -o /dev/null --silent https://$SNIPEIT_DOMAIN" printok "check presence of snipe-IT API" } system_pcinfo_file() { set -o pipefail trap ErrorHandling ERR INT install_if_not_exist jq install_if_not_exist jc # Werte auslesen und in variable speichern dmidecode_output=$(jc dmidecode) lshw_output=$(lshw -json) [ -f /usr/bin/rustdesk ] && RDP_ID=$(/usr/bin/rustdesk --get-id) os_name=$(lsb_release -sd) # for now I only want the size of the first disk /dev/sda disk_size=$(lsblk -J | jq -r '.blockdevices[] | select(.name == "sda") | .size') optical_drive=$(lsblk -J | jq -e '.blockdevices[] | select(.type == "rom" or .type == "cdrom")' > /dev/null 2>&1 && echo "ja" || echo "nein") type=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Chassis Information") | .values.type') manufacturer=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "System Information") | .values.manufacturer') product_name=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "System Information") | .values.product_name') serial_number=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "System Information") | .values.serial_number') family=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "System Information") | .values.family') memory_size=$(echo "$lshw_output" | jq '.children[]?.children[]? | select(.id == "memory") | .size / (1024 * 1024 * 1024)') memory_type=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Memory Device" and .values.bank_locator == "BANK 0") | .values.type') cpu_family=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Processor Information") | .values.family') cpu_manufacturer=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Processor Information") | .values.manufacturer') cpu_version_raw=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Processor Information") | .values.version') cpu_core_count=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Processor Information") | .values.core_count') cpu_thread_count=$(echo "$dmidecode_output" | jq -r '.[] | select(.description == "Processor Information") | .values.thread_count') network_hwaddress=$(echo "$lshw_output" | jq -r '.children[]?.children[]?.children[]? | select(.id == "network") | .serial') # Formatiere Variablen für eine schöne Ausgabe # Im Moment ist es nur auf einem Intel Laptop getestet mit einem Intel Core i5 cpu_version=$(echo "${cpu_version_raw%" CPU"*}") # Laptop spezifischen Werte, Batterie, Display battery_dir="/sys/class/power_supply/BAT0" # Überprüfen, ob das Verzeichnis existiert (d.h. ob eine Batterie vorhanden ist) if [ -d "$battery_dir" ]; then charge_full_design=$(cat "$battery_dir/charge_full_design") charge_full=$(cat "$battery_dir/charge_full") # Bestimme auch Display Größe und Auflösung sofern ein Akku vorhanden ist display_resolution=$(xdpyinfo | awk '/dimensions/ {print $2}') display_size_raw=$(xrandr | awk '/ connected/{print sqrt( ($(NF-2)/10)^2 + ($NF/10)^2 )/2.54}' | head -n 1) display_size=$(LC_ALL=C /usr/bin/printf "%.*f\n" 2 $display_size_raw) # Sicherstellen, dass beide Werte numerisch sind if [[ "$charge_full_design" =~ ^[0-9]+$ ]] && [[ "$charge_full" =~ ^[0-9]+$ ]]; then if [ "$charge_full_design" -ne 0 ]; then bat_capacity=$((100 * charge_full / charge_full_design)) fi fi else bat_capacity="no_battery" display_resolution="n/a" display_size="n/a" fi if [[ -f "$sysinfofolder/$sysinfofile" ]]; then printskip "$sysinfofile existiert bereits" return 0 else printprog "create systeminfo File" # Erstelle das JSON-Objekt /usr/bin/jq -n \ --arg type "$type" \ --arg manufacturer "$manufacturer" \ --arg os_name "$os_name" \ --arg product_name "$product_name" \ --arg serial_number "$serial_number" \ --arg family "$family" \ --arg memory_size "$memory_size" \ --arg memory_type "$memory_type" \ --arg cpu_family "$cpu_family" \ --arg cpu_manufacturer "$cpu_manufacturer" \ --arg cpu_version "$cpu_version" \ --arg cpu_core_count "$cpu_core_count" \ --arg cpu_thread_count "$cpu_thread_count" \ --arg disk_size "$disk_size" \ --arg optical_drive "$optical_drive" \ --arg network_hwaddress "$network_hwaddress" \ --arg bat_capacity "$bat_capacity" \ --arg display_resolution "$display_resolution" \ --arg display_size "$display_size" \ --arg RDP_ID "$RDP_ID" \ '{ type: $type, manufacturer: $manufacturer, os_name: $os_name, product_name: $product_name, serial_number: $serial_number, family: $family, memory_size: $memory_size, memory_type: $memory_type, cpu_family: $cpu_family, cpu_manufacturer: $cpu_manufacturer, cpu_version: $cpu_version, cpu_core_count: $cpu_core_count, cpu_thread_count: $cpu_thread_count, disk_size: $disk_size, optical_drive: $optical_drive, network_hwaddress: $network_hwaddress, bat_capacity: $bat_capacity, display_resolution: $display_resolution, display_size: $display_size, RDP_ID: $RDP_ID }' > "$sysinfofolder/$sysinfofile" printok "create systeminfo File" return 0 fi } system_pcinfo() { 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 printprog "read systeminfo" runcmd "/usr/bin/jq -e . $sysinfofolder/$sysinfofile >/dev/null 2>&1" printok "read systeminfo" network_hwaddress=$(cat $sysinfofolder/$sysinfofile | /usr/bin/jq -r .network_hwaddress) ASSET_TAG=$(cat $sysinfofolder/$sysinfofile | /usr/bin/jq -r .Asset_Tag) ## start localcheck if [[ $ASSET_TAG =~ ^[0-9]+$ ]]; then printinfo "already in Snipe-IT: RE$ASSET_TAG" exit 0 elif [ -f $SCRIPT_DIR/mac_white.list ] && grep -Fxq "$network_hwaddress" mac_white.list then unset network_hwaddress printinfo "Device is using a charity owned USB Ethernet-Adapter" return 1 ## end localcheck else ## start snipe-it check printinfo "fetch info from Snipe-IT" result=$(/usr/bin/curl --silent --request GET \ --url "https://$SNIPEIT_DOMAIN/api/v1/hardware?limit=1&search=${network_hwaddress//[:]/%3A}" \ --header 'accept: application/json' \ --header 'authorization: Bearer '$SNIPEIT_APIKEY'') [[ $(echo $result | /usr/bin/jq '(has("error"))') == true ]] && { printinfo "$(echo $result | /usr/bin/jq .error)"; return 1; } system_check_result=$(echo $result | jq .total) # doesn't work asset_tag=$(echo $result | /usr/bin/jq -r .rows[].asset_tag) if [ -n "$asset_tag" ]; then printinfo "Device already present in Snipe-IT: RE$asset_tag" snipeit_add_asset_to_sysinfofile return 0 else printok "Device not in Snipe-IT" system_pcinfo_value="newdevice" return 0 fi ## end snipe-it check fi } system_register() { set -o pipefail trap ErrorHandling ERR INT if [[ "$system_pcinfo_value" != "newdevice" ]]; then printskip "register Device at Snipe-IT" exit 0 fi # convert json to variables, like key=value eval "$(/usr/bin/jq -r '. | to_entries | .[] | .key + "=" + (.value | @sh)'< $sysinfofolder/$sysinfofile)" printinfo "fetching variables for register $type 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 1) [[ "${type,,}" =~ laptop|notebook ]] && { model=2; display="$Displaysize @ $Resolution"; } || model=1 # cannont locate issue with true and false, simple workaround to fix it # FIXME # 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 <