#!/usr/bin/env bash # Generates and applies the SGU welcome wallpaper inside a Linux desktop session. # It is intentionally best-effort: unavailable AD metadata or desktop APIs must # never delay or prevent the user's session from opening. set -uo pipefail CONFIG_PATH=${SGU_WELCOME_CONFIG:-/etc/sgu/welcome-wallpaper.conf} INSTALL_ROOT=${SGU_WELCOME_ROOT:-/usr/local/lib/sgu-welcome-wallpaper} BASE_IMAGE=${SGU_WELCOME_BASE_IMAGE:-${INSTALL_ROOT}/darkblue.jpg} if [[ -r $CONFIG_PATH ]]; then # The root-owned file contains only deployment metadata, never credentials. # shellcheck source=/dev/null source "$CONFIG_PATH" fi DOMAIN_CONTROLLER=${DOMAIN_CONTROLLER:-} DOMAIN_NAME=${DOMAIN_NAME:-} BASE_DN=${BASE_DN:-} state_root=${XDG_STATE_HOME:-${HOME}/.local/state} wallpaper_root=${XDG_CACHE_HOME:-${HOME}/.cache}/sgu/wallpapers log_path="${state_root}/sgu/welcome-wallpaper.log" log_message() { mkdir -p "$(dirname "$log_path")" 2>/dev/null || true printf '%s %s\n' "$(date --iso-8601=seconds 2>/dev/null || date)" "$*" >>"$log_path" 2>/dev/null || true } fail_softly() { log_message "ERROR $*" exit 0 } [[ -r $BASE_IMAGE ]] || fail_softly "Missing base image: $BASE_IMAGE" if command -v magick >/dev/null 2>&1; then image_command=(magick) elif command -v convert >/dev/null 2>&1; then image_command=(convert) else fail_softly 'ImageMagick is unavailable.' fi raw_user=${USER:-$(id -un 2>/dev/null || printf user)} account_name=${raw_user%@*} account_name=${account_name##*\\} display_name=$(getent passwd "$raw_user" 2>/dev/null | awk -F: 'NR == 1 { split($5,a,","); print a[1] }') [[ -n $display_name ]] || display_name=$account_name computer_name=$(hostname -s 2>/dev/null || true) computer_name=${computer_name^^} location='' distinguished_name='' organizational_unit='' gender='' read_ldif_value() { local attribute=$1 local content=$2 local line value line=$(printf '%s\n' "$content" | awk -v name="$attribute" ' BEGIN { IGNORECASE=1 } index(tolower($0), tolower(name) ":") == 1 { print; exit } ') [[ -n $line ]] || return 0 if [[ $line == "${attribute}:: "* || ${line,,} == "${attribute,,}:: "* ]]; then value=${line#*:: } printf '%s' "$value" | base64 --decode 2>/dev/null || true else printf '%s' "${line#*: }" fi } # SSSD normally obtains a Kerberos ticket during PAM authentication. Use that # ticket for a read-only AD query; never embed a bind password in this helper. if [[ -n $DOMAIN_CONTROLLER && -n $BASE_DN ]] && command -v ldapsearch >/dev/null 2>&1 && command -v klist >/dev/null 2>&1 && klist -s; then ldap_server=$DOMAIN_CONTROLLER if [[ -n $DOMAIN_NAME ]] && command -v resolvectl >/dev/null 2>&1; then discovered_server=$(resolvectl query --type=SRV \ "_ldap._tcp.dc._msdcs.${DOMAIN_NAME}" 2>/dev/null | awk '/ IN SRV / { for (i=1; i<=NF; i++) if ($i == "SRV") { print $(i+4); exit } }' | sed 's/\.$//' || true) [[ -n $discovered_server ]] && ldap_server=$discovered_server elif [[ -n $DOMAIN_NAME ]] && command -v dig >/dev/null 2>&1; then discovered_server=$(dig +short SRV "_ldap._tcp.dc._msdcs.${DOMAIN_NAME}" 2>/dev/null | awk 'NR == 1 { print $4 }' | sed 's/\.$//' || true) [[ -n $discovered_server ]] && ldap_server=$discovered_server fi ldap_result=$(ldapsearch -LLL -N -o ldif-wrap=no -Y GSSAPI \ -H "ldap://${ldap_server}" -b "$BASE_DN" \ "(&(objectCategory=computer)(sAMAccountName=${computer_name}\\24))" \ location distinguishedName 2>/dev/null || true) location=$(read_ldif_value location "$ldap_result") distinguished_name=$(read_ldif_value distinguishedName "$ldap_result") if [[ $distinguished_name =~ ,OU=([^,]+) ]]; then organizational_unit=${BASH_REMATCH[1]} organizational_unit=${organizational_unit//\\,/,} organizational_unit=${organizational_unit//\\=/=} organizational_unit=${organizational_unit//\\+/+} fi # SSSD's GECOS field is not guaranteed to expose AD displayName. Query it # through the same authenticated LDAP session and retain the account-name # fallback when the institutional identifier contains unexpected symbols. if [[ $account_name =~ ^[A-Za-z0-9._-]+$ ]]; then user_result=$(ldapsearch -LLL -N -o ldif-wrap=no -Y GSSAPI \ -H "ldap://${ldap_server}" -b "$BASE_DN" \ "(&(objectCategory=person)(objectClass=user)(sAMAccountName=${account_name}))" \ displayName info 2>/dev/null || true) directory_display_name=$(read_ldif_value displayName "$user_result") [[ -n $directory_display_name ]] && display_name=$directory_display_name directory_info=$(read_ldif_value info "$user_result") gender=$(printf '%s\n' "$directory_info" | awk -F: ' tolower($1) ~ /^[[:space:]]*sgu-gender[[:space:]]*$/ { value=tolower($2); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) if (value == "male") print "Male" else if (value == "female") print "Female" exit } ') fi else log_message 'WARN AD metadata query skipped because Kerberos or LDAP session data was unavailable.' fi article_for() { local value=${1,,} case "$value" in sala*|aula*|facultad*|unidad*|biblioteca*|oficina*|coordinación*) printf la ;; laboratorio*|centro*|edificio*|campus*|taller*|auditorio*) printf el ;; *) printf '' ;; esac } with_article() { local value=$1 local article article=$(article_for "$value") if [[ -n $article ]]; then printf '%s %s' "$article" "$value" else printf '%s' "$value" fi } case "$gender" in Male) welcome_text='Bienvenido,' located_text='Estás ubicado en' engineering_lab_text='Bienvenido al Laboratorio de Cómputo de Ingeniería.' ;; Female) welcome_text='Bienvenida,' located_text='Estás ubicada en' engineering_lab_text='Bienvenida al Laboratorio de Cómputo de Ingeniería.' ;; *) welcome_text='Te damos la bienvenida,' located_text='Ubicación:' engineering_lab_text='Acceso al Laboratorio de Cómputo de Ingeniería.' ;; esac if [[ -n $location && -n $organizational_unit ]]; then room_phrase=$(with_article "$location") ou_article=$(article_for "$organizational_unit") if [[ $ou_article == el ]]; then ou_phrase="del ${organizational_unit}" elif [[ -n $ou_article ]]; then ou_phrase="de ${ou_article} ${organizational_unit}" else ou_phrase="de ${organizational_unit}" fi location_text="${located_text} ${room_phrase} ${ou_phrase}." elif [[ -n $location ]]; then location_text="${located_text} $(with_article "$location")." elif [[ -n $organizational_unit ]]; then location_text="${located_text} $(with_article "$organizational_unit")." else location_text=$engineering_lab_text fi width=1600 height=1000 if command -v xrandr >/dev/null 2>&1; then geometry=$(xrandr --current 2>/dev/null | awk '/\*/ { print $1; exit }') if [[ $geometry =~ ^([0-9]+)x([0-9]+)$ ]]; then width=${BASH_REMATCH[1]} height=${BASH_REMATCH[2]} fi fi mkdir -p "$wallpaper_root" "$(dirname "$log_path")" || fail_softly "Cannot create welcome wallpaper state directories." safe_computer=${computer_name//[^A-Za-z0-9_.-]/_} output_path="${wallpaper_root}/welcome-${safe_computer}.jpg" scale=$(( height * 100 / 1000 )) (( scale > 45 )) || scale=45 welcome_size=$(( 34 * scale / 100 )) name_size=$(( 70 * scale / 100 )) location_size=$(( 27 * scale / 100 )) panel_width=$(( width * 76 / 100 )) panel_height=$(( 310 * scale / 100 )) panel_x1=$(( (width - panel_width) / 2 )) panel_y1=$(( height / 2 - panel_height / 2 )) panel_x2=$(( panel_x1 + panel_width )) panel_y2=$(( panel_y1 + panel_height )) sans_font='DejaVu-Sans' serif_font='DejaVu-Serif' if [[ -r ${INSTALL_ROOT}/fonts/IndivisaTextSans-Bold.otf ]]; then sans_font="${INSTALL_ROOT}/fonts/IndivisaTextSans-Bold.otf" fi if [[ -r ${INSTALL_ROOT}/fonts/IndivisaTextSerif-BoldItalic.otf ]]; then serif_font="${INSTALL_ROOT}/fonts/IndivisaTextSerif-BoldItalic.otf" fi if [[ $sans_font == DejaVu-Sans ]] && command -v fc-list >/dev/null 2>&1; then if fc-list : family | grep -Fqi 'Indivisa Text Sans'; then sans_font='Indivisa Text Sans' elif fc-list : family | grep -Fqi 'Indivisa Text'; then sans_font='Indivisa Text' fi fi if [[ $serif_font == DejaVu-Serif ]] && command -v fc-list >/dev/null 2>&1; then if fc-list : family | grep -Fqi 'Indivisa Text Serif'; then serif_font='Indivisa Text Serif' elif fc-list : family | grep -Fqi 'Indivisa Serif'; then serif_font='Indivisa Serif' fi fi if ! "${image_command[@]}" "$BASE_IMAGE" \ -resize "${width}x${height}^" -gravity center -extent "${width}x${height}" \ -fill 'rgba(0,13,58,0.30)' -draw "rectangle ${panel_x1},${panel_y1} ${panel_x2},${panel_y2}" \ -gravity center \ -font "$sans_font" -weight 700 -style Normal -pointsize "$welcome_size" \ -fill '#D3E2FF' -stroke 'rgba(0,0,0,0.48)' -strokewidth 1 \ -annotate "+0-$(( 92 * scale / 100 ))" "$welcome_text" \ -font "$serif_font" -weight 700 -style Italic -pointsize "$name_size" \ -fill white -annotate "+0-$(( 22 * scale / 100 ))" "$display_name" \ -font "$sans_font" -weight 400 -style Normal -pointsize "$location_size" \ -fill '#D3E2FF' -annotate "+0+$(( 88 * scale / 100 ))" "$location_text" \ -quality 94 "$output_path" 2>>"$log_path"; then fail_softly 'ImageMagick could not render the welcome wallpaper.' fi applied=false if command -v gsettings >/dev/null 2>&1; then if gsettings list-schemas 2>/dev/null | grep -Fxq 'org.cinnamon.desktop.background'; then gsettings set org.cinnamon.desktop.background picture-uri "file://${output_path}" >/dev/null 2>&1 || true gsettings set org.cinnamon.desktop.background picture-options zoom >/dev/null 2>&1 || true applied=true fi if gsettings list-schemas 2>/dev/null | grep -Fxq 'org.gnome.desktop.background'; then gsettings set org.gnome.desktop.background picture-uri "file://${output_path}" >/dev/null 2>&1 || true gsettings set org.gnome.desktop.background picture-uri-dark "file://${output_path}" >/dev/null 2>&1 || true gsettings set org.gnome.desktop.background picture-options zoom >/dev/null 2>&1 || true applied=true fi fi if [[ $applied == false ]] && command -v xfconf-query >/dev/null 2>&1; then while IFS= read -r property; do xfconf-query -c xfce4-desktop -p "$property" -s "$output_path" >/dev/null 2>&1 || true applied=true done < <(xfconf-query -c xfce4-desktop -l 2>/dev/null | grep '/last-image$' || true) fi if [[ $applied == true ]]; then log_message "OK computer=${computer_name}; gender=${gender:-Neutral}; location=$([[ -n $location ]] && printf true || printf false); ou=$([[ -n $organizational_unit ]] && printf true || printf false); output=${output_path}" else log_message 'WARN Wallpaper rendered, but no supported desktop background API was found.' fi exit 0