#!/bin/sh
#
# filature-report - report a bug to the SliTaz developers
#
# Filature - SliTaz developer platform
# Copyright (C) 2026 SliTaz GNU/Linux - BSD License, see LICENSE
#
# The engine collects anonymous system information, builds the report
# and POSTs it with BusyBox wget. Front ends (this script's interactive
# mode, the GTK3 dialog) only call it. The format is the one of TazHelper,
# and so are the system lines and the privacy filter: no MAC address,
# IP address, user name, host name or serial number leaves the machine,
# and nothing is sent before the user saw the whole report.
#
# Usage:
#   filature-report                   ask, show the report, send on yes
#   filature-report system            print the system lines
#   filature-report tech              print the technical lines
#   filature-report make [-t] TITLE [CONTACT] < description
#                                     print the report, -t with the
#                                     technical lines
#   filature-report send FILE         send a report file, "-" for stdin
#
# Config: /etc/slitaz/filature-report.conf (URL, TIMEOUT), or the
# FILATURE_REPORT_URL environment variable.
#

VERSION=1
CONF=/etc/slitaz/filature-report.conf
URL="https://filature.slitaz.org/report"
TIMEOUT=20
MAX_VALUE=500

[ -f "$CONF" ] && . "$CONF"
[ -n "$FILATURE_REPORT_URL" ] && URL="$FILATURE_REPORT_URL"

# One value, made anonymous: from TazHelper's clean_value
clean_value() {
    user="$(id -un 2>/dev/null)"
    host="$(hostname 2>/dev/null)"
    sedx=""
    case "$user" in
        root|tux|"") ;;
        *[!a-z0-9_-]*) ;;
        *) sedx="s/(^|[^[:alnum:]])$user([^[:alnum:]]|$)/\1<user>\2/g" ;;
    esac
    case "$host" in
        slitaz|localhost|"") ;;
        *[!A-Za-z0-9.-]*) ;;
        *) host="$(echo "$host" | sed 's/\./\\./g')"
           sedx="$sedx;s/(^|[^[:alnum:]])$host([^[:alnum:]]|$)/\1<host>\2/g" ;;
    esac
    tr '\n\t\r' '   ' | tr -d '\000-\037\177' | sed -E \
        -e 's/([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}/<mac>/g' \
        -e 's/[0-9]{1,3}(\.[0-9]{1,3}){3}/<ip>/g' \
        -e 's|/home/[A-Za-z0-9._-]+|/home/<user>|g' \
        -e "${sedx:-s/^//}" \
        -e 's/^ +//;s/ +$//' | cut -c 1-$MAX_VALUE
}

# Display controllers: "vendor:device driver", from sysfs (no lspci)
gpu_info() {
    for dev in /sys/bus/pci/devices/*; do
        case "$(cat $dev/class 2>/dev/null)" in 0x03*) ;; *) continue ;; esac
        drv="none"
        [ -e "$dev/driver" ] && drv="$(basename "$(readlink "$dev/driver")")"
        printf '%s:%s %s, ' "$(sed 's/^0x//' $dev/vendor)" \
            "$(sed 's/^0x//' $dev/device)" "$drv"
    done | sed 's/, $//'
}

# The system lines, the same keys as TazHelper
system_lines() {
    flavor="unknown"
    [ -s /etc/slitaz/flavor ] && flavor="$(head -n 1 /etc/slitaz/flavor)"
    boot="bios"
    [ -d /sys/firmware/efi ] && boot="efi"
    live="no"
    case "$(awk '$2 == "/" { print $3 }' /proc/mounts | tail -n 1)" in
        rootfs|tmpfs) live="yes" ;;
    esac
    cpu="$(sed -n 's/^model name[[:space:]]*: //p' /proc/cpuinfo | head -n 1)"
    cores="$(grep -c '^processor' /proc/cpuinfo)"
    ram="$(awk '/^MemTotal:/ { print int($2 / 1024) }' /proc/meminfo)"
    {
        echo "date=$(date -u +%Y-%m-%d)"
        echo "release=$(cat /etc/slitaz-release 2>/dev/null || echo unknown)"
        echo "flavor=$flavor"
        echo "arch=$(uname -m)"
        echo "kernel=$(uname -r)"
        echo "boot=$boot"
        echo "live=$live"
        echo "cpu=$cpu"
        echo "cpu_cores=$cores"
        echo "ram_mb=$ram"
        echo "gpu=$(gpu_info)"
        echo "locale=${LANG:-C}"
    } | while IFS= read -r line; do
        echo "${line%%=*}=$(printf '%s' "${line#*=}" | clean_value)"
    done
}

# PCI devices of a class prefix: "vendor:device driver", one per line
pci_drivers() {
    for dev in /sys/bus/pci/devices/*; do
        case "$(cat $dev/class 2>/dev/null)" in $1*) ;; *) continue ;; esac
        drv="none"
        [ -e "$dev/driver" ] && drv="$(basename "$(readlink "$dev/driver")")"
        echo "$(sed 's/^0x//' $dev/vendor):$(sed 's/^0x//' $dev/device) $drv"
    done
}

# USB devices, hubs left out: "vendor:product driver,driver"
usb_drivers() {
    for dev in /sys/bus/usb/devices/*; do
        [ -f "$dev/idVendor" ] || continue
        [ "$(cat $dev/bDeviceClass)" = "09" ] && continue
        drv="$(for i in $dev/*:*; do
            [ -e "$i/driver" ] && basename "$(readlink "$i/driver")"
        done | sort -u | tr '\n' ',' | sed 's/,$//')"
        echo "$(cat $dev/idVendor):$(cat $dev/idProduct) ${drv:-none}"
    done
}

# KEY: stdin lines as "KEY=value" lines, made anonymous. Disk UUIDs go,
# and so do whole lines naming a serial number or a wifi network.
tech_key() {
    grep -viE 'serial|ssid' | sed -E \
        -e 's/^\[ *[0-9.]+\] //' \
        -e 's/[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}/<uuid>/g' |
    while IFS= read -r line; do
        line="$(printf '%s' "$line" | clean_value)"
        [ -n "$line" ] && echo "$1=$line"
    done
}

# The technical lines: what helpers ask first, a few KiB at most
tech_lines() {
    pci_drivers 0x02 | tech_key network
    pci_drivers 0x04 | tech_key sound
    usb_drivers | tech_key usb
    lsmod 2>/dev/null | awk 'NR > 1 { printf "%s ", $1 } END { print "" }' |
        fold -s -w 400 | tech_key modules
    tech_key cmdline < /proc/cmdline
    dmesg 2>/dev/null | grep -iE 'error|fail|warn|missing|not found|timed? ?out' | tail -n 10 |
        tech_key dmesg
    xlog=/var/log/Xorg.0.log
    [ -f "$HOME/.local/share/xorg/Xorg.0.log" ] &&
        xlog="$HOME/.local/share/xorg/Xorg.0.log"
    grep -E '^\[[ 0-9.]+\] \((EE|WW)\)' "$xlog" 2>/dev/null | tail -n 10 | tech_key xorg
    sed "s/$(printf '\033')\[[0-9;]*[a-zA-Z]//g" /var/log/boot.log 2>/dev/null |
        grep -iE 'error|fail' | tail -n 10 | tech_key boot
}

# make [-t] TITLE [CONTACT] < description: the report on stdout, -t adds
# the technical lines. The contact is what the user chose to give and is
# kept as typed; devs only see it.
make_report() {
    tech=""
    [ "$1" = "-t" ] && { tech=yes; shift; }
    title="$(printf '%s' "$1" | clean_value)"
    [ -n "$title" ] || { echo "filature-report: a title is needed" >&2; return 1; }
    desc=""
    # The GTK front end hands a text without a final newline: keep its
    # last line
    while IFS= read -r line || [ -n "$line" ]; do
        desc="${desc}description=$(printf '%s' "$line" | clean_value)
"
    done
    [ -n "$desc" ] || { echo "filature-report: a description is needed" >&2; return 1; }
    echo "filature_report=$VERSION"
    echo "title=$title"
    printf '%s' "$desc"
    system_lines
    [ -n "$tech" ] && tech_lines
    [ -n "$2" ] && echo "contact=$(printf '%s' "$2" | tr -d '\000-\037\177' | cut -c 1-200)"
    return 0
}

# send FILE: POST it, print the server answer, 0 if stored ("OK ...")
send_report() {
    file="$1"
    if [ "$file" = "-" ]; then
        file="$(mktemp "${TMPDIR:-/tmp}/filature-report.XXXXXX")" || return 1
        cat > "$file"
        trap 'rm -f "$file"' EXIT
    fi
    [ -s "$file" ] || { echo "filature-report: no report in $1" >&2; return 1; }
    answer="$(wget -q -T "$TIMEOUT" -O - --header 'Content-Type: text/plain' \
        --post-file "$file" "$URL" 2> /dev/null)"
    if [ -z "$answer" ]; then
        echo "filature-report: no answer from $URL" >&2
        return 1
    fi
    echo "$answer" | head -n 3
    case "$answer" in
        OK*) return 0 ;;
        *) return 1 ;;
    esac
}

# Interactive mode, plain prompts: works on a console and in a terminal
interactive() {
    echo "Report a bug to the SliTaz developers."
    echo "The report is anonymous unless you leave a contact: a mail address is"
    echo "seen by the developers only, a nick is credited on the public bug."
    echo "You will see all of it before anything is sent."
    echo
    printf 'Title (one line): '
    IFS= read -r title || return 1
    echo "Description, end with a line holding a single dot:"
    desc=""
    while IFS= read -r line; do
        [ "$line" = "." ] && break
        desc="$desc$line
"
    done
    printf 'Contact for questions (mail: devs only, nick: public credit, empty: anonymous): '
    IFS= read -r contact
    printf 'Add technical details (drivers, kernel, X and boot errors)? [Y/n] '
    IFS= read -r tech
    case "$tech" in n|N|no) tech="" ;; *) tech="-t" ;; esac
    report="$(mktemp "${TMPDIR:-/tmp}/filature-report.XXXXXX")" || return 1
    if ! printf '%s' "$desc" | make_report $tech "$title" "$contact" > "$report"; then
        rm -f "$report"
        return 1
    fi
    echo
    echo "---- The report, exactly as it will be sent ----"
    cat "$report"
    echo "------------------------------------------------"
    printf 'Send it to %s? [y/N] ' "$URL"
    IFS= read -r yes
    case "$yes" in
        y|Y|yes) send_report "$report"; status=$? ;;
        *) echo "Not sent. The report is kept in $report"; return 1 ;;
    esac
    if [ "$status" = 0 ]; then
        rm -f "$report"
    else
        echo "Not sent. The report is kept in $report"
    fi
    return $status
}

case "$1" in
    "") interactive ;;
    system) system_lines ;;
    tech) tech_lines ;;
    make) shift; make_report "$@" ;;
    send) send_report "${2:--}" ;;
    *) sed -n '/^# Usage:/,/^# Config/p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 1 ;;
esac
