#!/bin/bash
# script destiné à être lancé par l'utilisateur se4install afin d générer l'image wine-$1.img, et également si besoin tous les éléments nécessaires pour
# faire un paquet sambaedu-$1

# au logon, les prefix sont montés sur /var/tmp/$USER/wine/wine-appli/prefix.
# toute modification apportée au prefix est enregistrée en overlay.
# le script peut refaire une image à partir du montage en cours modifié, par exemple en cas de mise à jour.

# les lanceurs sont à modifier manuellement :
# wine va en général mettre un lanceur .desktop dans ~/.local/share/applications/, avec une icone en référence.
# afin de permettre une personnalisation maximale, il est conseillé de faire fichier /tmp/appli/appli.desktop qui lance un script wrapper /usr/bin/appli qui accepte éventuelleemnt des arguments
# ce script doit être nommé /tmp/appli/appli

# généralement wine va ajouter les associations de fichiers définies par l'installeur dans .config/mimeapps.list. Il faut extraire celles que l'on souhaite conserver et les
# mettre dans un fichier mime.list

# en résumé :

# /tmp/appli/wine-appli.img -> image du prefix -> /var/sambaedu/unattended/install/wine/wine-appli.img + sha256sum  + lien dans /usr/share/sambaedu/wine/
# /tmp/appli/appli.desktop -> entrée de menu -> .local/share/applications/
# /tmp/appli/appli.png -> icone -> .local/share/icons/XxX/
# /tmp/appli/appli -> wrapper (executable) -> /usr/bin/appli
# /tmp/appli/mime.list -> associations par défaut -> fusionner avec .config/mimeapps.list

# test local

# /usr/share/sambaedu/sbin/make_prefix_image.sh appli local
#

# envoi sur le dépôt (il faut avoir la clé pour ssh root@deb.sambaedu.org)

git="/home/$USER/git/sambaedu-wpkg-xml/sources"

# dépôt des recettes wpkg (stable/testing/XP/manuel), et checkout local de se4
# (pour wpkg-se4.js). Utilisés par install_wpkg et test_wine_apps ci-dessous.
wpkg_xml_repo="/home/$USER/git/sambaedu-wpkg-xml"
git_se4="/home/$USER/git/se4"

# cherche la recette wpkg d'une application dans le dépôt sambaedu-wpkg-xml.
# affiche le chemin trouvé sur stdout, échoue si aucune recette n'existe.
function find_package_xml() {
    local appli="$1" repo
    for repo in stable testing XP manuel; do
        if [ -f "$wpkg_xml_repo/$repo/$appli.xml" ]; then
            echo "$wpkg_xml_repo/$repo/$appli.xml"
            return 0
        fi
    done
    return 1
}

# résout un id de paquet (<package id="...">, PAS un nom de fichier) vers
# le chemin de sa recette xml, en respectant la même priorité que
# find_package_xml (stable > testing > XP > manuel). Contrairement à
# find_package_xml, l'id ne correspond pas toujours au nom de fichier
# (ex : "dotnet3.5" n'est défini que dans dotnet35.xml, "python-idle" dans
# python.xml) : on tente d'abord le nom de fichier par optimisme, puis on
# scanne tout le dépôt si besoin. Utilisée pour résoudre les <depends>.
function find_package_by_id() {
    local pkgid="$1" fast
    if fast="$(find_package_xml "$pkgid")"; then
        if python3 -c '
import sys, xml.etree.ElementTree as ET
try:
    pkg = ET.parse(sys.argv[1]).getroot().find("package")
except ET.ParseError:
    pkg = None
sys.exit(0 if pkg is not None and pkg.get("id") == sys.argv[2] else 1)
' "$fast" "$pkgid"; then
            echo "$fast"
            return 0
        fi
    fi
    python3 -c '
import sys, os, glob
import xml.etree.ElementTree as ET
pkgid, repo = sys.argv[1], sys.argv[2]
for sub in ("stable", "testing", "XP", "manuel"):
    for path in sorted(glob.glob(os.path.join(repo, sub, "*.xml"))):
        try:
            root = ET.parse(path).getroot()
        except ET.ParseError:
            continue
        pkg = root.find("package")
        if pkg is not None and pkg.get("id") == pkgid:
            print(path)
            sys.exit(0)
sys.exit(1)
' "$pkgid" "$wpkg_xml_repo"
}

# calcule la fermeture transitive des dépendances (<depends package-id="X"/>)
# d'une recette wpkg, et produit sur stdout le packages.xml fusionné
# correspondant : wpkg-se4.js résout les dépendances uniquement à
# l'intérieur du packages.xml déjà chargé, jamais à la demande (confirmé en
# lisant getPackageDependencies()/installPackageReferences() dans
# wpkg-se4.js), donc packages.xml doit contenir toute la fermeture, pas
# seulement le paquet ciblé. Le parsing passe exclusivement par
# xml.etree.ElementTree (jamais grep/sed) car des <depends> commentés
# existent réellement dans le dépôt (ex: kompozer.xml) et ne doivent pas
# être suivis. Émet sur stderr un "SRCFILE:<chemin>" par fichier xml source
# contribuant à la fermeture (nécessaire ensuite pour télécharger aussi les
# installeurs des dépendances), et un "MISSING-DEP:<id>" par dépendance
# non commentée mais introuvable (échec, exit 1, aucun xml produit).
function compute_packages_closure() {
    python3 - "$1" "$wpkg_xml_repo" <<'PYEOF'
import sys, os, glob
import xml.etree.ElementTree as ET

xmlsrc, repo = sys.argv[1], sys.argv[2]
subdirs = ("stable", "testing", "XP", "manuel")


def resolve(pkgid):
    for sub in subdirs:
        fast = os.path.join(repo, sub, pkgid + ".xml")
        if os.path.isfile(fast):
            try:
                root = ET.parse(fast).getroot()
            except ET.ParseError:
                root = None
            if root is not None:
                pkg = root.find("package")
                if pkg is not None and pkg.get("id") == pkgid:
                    return fast
    for sub in subdirs:
        for path in sorted(glob.glob(os.path.join(repo, sub, "*.xml"))):
            try:
                root = ET.parse(path).getroot()
            except ET.ParseError:
                continue
            pkg = root.find("package")
            if pkg is not None and pkg.get("id") == pkgid:
                return path
    return None


visited = {}
order = []
srcfiles = []
missing = []
queue = [xmlsrc]
queued_ids = set()

while queue:
    path = queue.pop(0)
    try:
        root = ET.parse(path).getroot()
    except ET.ParseError as e:
        print("MISSING-DEP:" + path + " (xml invalide : " + str(e) + ")", file=sys.stderr)
        continue
    pkg = root.find("package")
    if pkg is None:
        print("MISSING-DEP:" + path + " (pas de <package>)", file=sys.stderr)
        continue
    pkgid = pkg.get("id")
    if pkgid in visited:
        continue
    visited[pkgid] = pkg
    order.append(pkgid)
    srcfiles.append(path)
    for dep in pkg.findall("depends"):
        depid = dep.get("package-id")
        if not depid or depid in visited or depid in queued_ids:
            continue
        target = resolve(depid)
        if target is None:
            missing.append(depid)
            continue
        queued_ids.add(depid)
        queue.append(target)

if missing:
    for m in missing:
        print("MISSING-DEP:" + m, file=sys.stderr)
    sys.exit(1)

print('<?xml version="1.0" encoding="UTF-8"?>')
print("<packages>")
for pkgid in order:
    print(ET.tostring(visited[pkgid], encoding="unicode"))
print("</packages>")

for s in srcfiles:
    print("SRCFILE:" + s, file=sys.stderr)
PYEOF
}

# vrai (exit 0) si l'application n'a pas besoin d'être testée sous wine
# car elle dispose d'un équivalent linux :
#
# 1. balise <linux .../> explicite (type ou method="apt" : les deux
#    s'utilisent dans le dépôt, les 63 occurrences actuelles sont toutes des
#    déclarations apt) : on fait confiance à cette déclaration du xml sans
#    condition, même si apt-cache ne trouve pas (encore) le paquet sur cette
#    machine - ex: bambustudio.xml déclare <linux type="apt"
#    package="bambustudio"/> alors que le paquet n'est pas encore synchronisé
#    dans le cache apt local : ce n'est pas une raison de tester l'appli
#    sous wine, la déclaration du xml fait foi.
# 2. sinon (pas de balise <linux>), convention implicite documentée dans le
#    README de sambaedu-wpkg-xml : l'id du <package> en minuscules est
#    utilisé comme nom de paquet Debian. Ici, contrairement au cas explicite,
#    on vérifie via apt-cache que ce paquet existe réellement : c'est une
#    simple supposition (l'id peut très bien ne correspondre à aucun paquet).
function has_native_linux() {
    local pkgname
    pkgname="$(python3 -c '
import sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[1]).getroot()
linux = root.find(".//linux")
if linux is not None:
    print("EXPLICIT")
else:
    print(root.find("package").get("id").lower())
' "$1")"
    [ "$pkgname" = "EXPLICIT" ] && return 0
    [ -n "$pkgname" ] && apt-cache show "$pkgname" >/dev/null 2>&1
}

# vrai (exit 0) si au moins une dépendance transitive (<depends
# package-id="...">) de l'appli a elle-même un équivalent linux natif
# (has_native_linux). Tester l'appli sous wine impliquerait alors
# d'installer sous wine cette dépendance alors qu'elle est déjà couverte
# nativement - un exercice inutile, et souvent voué à l'échec pour une
# raison sans rapport avec la recette elle-même (ex: freemind dépend de
# jre, qui a un paquet debian natif mais dont l'installeur .exe échoue
# sous wine avec une erreur MSI 1603).
# Réutilise compute_packages_closure (définie plus bas) pour la résolution
# de dépendances : une seule logique de résolution dans tout le script.
function has_native_dependency() {
    local xmlfile="$1" stderr_out line dep
    stderr_out="$(compute_packages_closure "$xmlfile" 2>&1 >/dev/null)"
    while IFS= read -r line; do
        case "$line" in
            SRCFILE:*)
                dep="${line#SRCFILE:}"
                [ "$dep" = "$xmlfile" ] && continue
                has_native_linux "$dep" && return 0
                ;;
        esac
    done <<<"$stderr_out"
    return 1
}

# applique les correctifs nécessaires pour faire tourner wpkg-se4.js sous Wine.
# Ne modifie que la copie utilisée dans le prefix de test : le fichier suivi
# par le dépôt se4 n'est jamais touché.
#
# 1. hosts_file_name/profiles_file_name/packages_file_name sont coincées dans
#    un commentaire dans wpkg-se4.js et ne sont donc jamais déclarées. Ceci
#    est invisible en prod car le client SE4 utilise toujours une base http
#    (packages_xml_out.php...), jamais de fichiers locaux (hosts.xml,
#    profiles.xml, packages.xml).
# 2. IXMLDOMElement.setAttribute() ne convertit pas automatiquement un
#    tableau JS ou null en chaîne sous Wine, contrairement à Windows. Ceci
#    fait échouer createSettings() (attribut "ipaddresses"/"groups"/etc.).
# 3. getRegistrySubkeys() (utilisée par <check type="uninstall">) énumère les
#    sous-clés via WbemScripting.SWbemLocator/StdRegProv, indisponible sous
#    Wine en mode wow64. On la réécrit pour énumérer via "reg.exe query",
#    outil natif de Wine qui lit directement le registre, sans passer par
#    WMI. WshShell.RegRead (utilisé ensuite pour lire les valeurs) fonctionne
#    déjà nativement sous Wine et n'a pas besoin d'être touché.
function patch_wpkg_for_wine() {
    python3 - "$1" <<'PYEOF'
import sys
path = sys.argv[1]
with open(path) as fh:
    content = fh.read()

marker = 'var packages_path = "";'
if marker not in content:
    sys.exit("patch_wpkg_for_wine: marqueur des *_file_name introuvable (wpkg-se4.js a changé ?)")
content = content.replace(
    marker,
    'var packages_file_name = "packages.xml";\n'
    'var profiles_file_name = "profiles.xml";\n'
    'var hosts_file_name = "hosts.xml";\n'
    + marker,
    1,
)

old = ('\t\t\tvar value = hostInformation.Item(attributes[i]);\n'
       '\t\t\tnewSettings.setAttribute(attributes[i], value);')
if old not in content:
    sys.exit("patch_wpkg_for_wine: marqueur setAttribute introuvable (wpkg-se4.js a changé ?)")
new = ('\t\t\tvar value = hostInformation.Item(attributes[i]);\n'
       '\t\t\tif (value instanceof Array) { value = value.join(","); }\n'
       '\t\t\tif (value == null) { value = ""; }\n'
       '\t\t\tnewSettings.setAttribute(attributes[i], value);')
content = content.replace(old, new, 1)

start_marker = 'function getRegistrySubkeys(parentKey, subLevels) {'
end_marker = '/**\n * Returns value of given key in registry.'
start_idx = content.find(start_marker)
end_idx = content.find(end_marker, start_idx)
if start_idx == -1 or end_idx == -1:
    sys.exit("patch_wpkg_for_wine: fonction getRegistrySubkeys introuvable (wpkg-se4.js a changé ?)")

new_func = '''function getRegistrySubkeys(parentKey, subLevels) {
\t// dinfo("Getting registry subkeys from: " + parentKey);

\t// get number of recursion levels
\tif (subLevels == null) {
\t\tsubLevels = 0;
\t}

\tvar returnArray = new Array();

\ttry {
\t\t// Wine (mode wow64) ne fournit pas WbemScripting/StdRegProv : on
\t\t// enumere les sous-cles en lisant directement le registre via
\t\t// reg.exe (outil natif, independant de WMI).
\t\tvar WshShell = new ActiveXObject("WScript.Shell");
\t\tvar exec = WshShell.Exec('reg.exe query "HKLM\\\\' + parentKey + '"');
\t\tvar output = exec.StdOut.ReadAll();
\t\texec.StdErr.ReadAll();

\t\tvar prefix = ("HKEY_LOCAL_MACHINE\\\\" + parentKey + "\\\\").toUpperCase();
\t\tvar lines = output.split("\\n");
\t\tfor (var i = 0; i < lines.length; i++) {
\t\t\tvar line = trim(lines[i]);
\t\t\tif (line.length == 0) {
\t\t\t\tcontinue;
\t\t\t}
\t\t\tif (line.toUpperCase().indexOf(prefix) == 0) {
\t\t\t\tvar subKeyName = line.substring(prefix.length);
\t\t\t\tif (subKeyName.length > 0) {
\t\t\t\t\treturnArray.push(subKeyName);
\t\t\t\t}
\t\t\t}
\t\t}

\t\t// if there is a sub key parse it as well if recursion is requested
\t\tif (returnArray.length > 0 && ((subLevels >= 1) || subLevels < 0)) {
\t\t\tvar directKeys = returnArray.slice();
\t\t\tfor (var i = 0; i < directKeys.length; i++) {
\t\t\t\tvar subKey = parentKey + "\\\\" + directKeys[i];
\t\t\t\tvar subKeys = getRegistrySubkeys(subKey, subLevels - 1);
\t\t\t\tif (subKeys != null) {
\t\t\t\t\tfor (var j = 0; j < subKeys.length; j++) {
\t\t\t\t\t\treturnArray
\t\t\t\t\t\t\t.push(directKeys[i] + "\\\\" + subKeys[j]);
\t\t\t\t\t}
\t\t\t\t}
\t\t\t}
\t\t}

\t} catch (err) {
\t\terror("Error when searching registry sub-keys at 'HKLM\\\\" + parentKey
\t\t\t+ "'\\nCode: " + hex(err.number) + "; Descriptions: "
\t\t\t+ err.description);
\t\treturnArray = null;
\t}

\treturn returnArray;
}

'''

content = content[:start_idx] + new_func + content[end_idx:]

with open(path, "w") as fh:
    fh.write(content)
PYEOF
}

# installe une application via le client wpkg windows (wpkg-se4.js) dans un
# prefix wine dédié, à partir de la recette du dépôt local sambaedu-wpkg-xml.
# En prod le téléchargement des installeurs est fait côté serveur (le client
# tourne toujours en /noDownload car wpkg-se4.js ne comprend pas les
# attributs saveto/sha256sum utilisés par nos xml) : on le reproduit ici,
# en réutilisant le cache /opt/install/packages en lecture seule quand le
# fichier y est déjà présent, et en téléchargeant sinon dans le prefix lui-même.
function install_wpkg() {
    local appli="$1" prefixname="$2"
    local PREFIX="/var/tmp/$USER/wine/wine-$prefixname/prefix"
    if [ ! -d "$PREFIX" ]; then
        echo "Le prefix $prefixname n'existe pas. Lancez d'abord : $0 new $prefixname"
        return 1
    fi
    echo "tentative d'installation via wpkg windows de l'application $appli sur le prefix wine-$prefixname"

    local xmlsrc
    if ! xmlsrc="$(find_package_xml "$appli")"; then
        echo "recette wpkg introuvable pour $appli dans $wpkg_xml_repo"
        return 1
    fi

    # l'id du paquet (attribut id="..." du <package>) ne correspond pas
    # toujours exactement (à la casse près) au nom du fichier xml : c'est lui
    # qu'il faut utiliser pour /install: et profiles.xml, pas $appli.
    local pkgid
    pkgid="$(python3 -c '
import sys, xml.etree.ElementTree as ET
print(ET.parse(sys.argv[1]).getroot().find("package").get("id"))
' "$xmlsrc")"
    if [ -z "$pkgid" ]; then
        echo "impossible de déterminer l'id du paquet dans $xmlsrc"
        return 1
    fi

    local INSTALL_DIR="$PREFIX/drive_c/windows/install"
    local WPKG_DIR="$INSTALL_DIR/wpkg"
    mkdir -p "$WPKG_DIR"

    # packages.xml doit contenir $xmlsrc ET toute sa fermeture de
    # dépendances (<depends package-id="...">) : voir compute_packages_closure.
    local closure_err="/var/tmp/$USER/wine/wine-$prefixname-closure.err"
    if ! compute_packages_closure "$xmlsrc" >"$WPKG_DIR/packages.xml" 2>"$closure_err"; then
        echo "résolution des dépendances impossible pour $appli :"
        cat "$closure_err"
        rm -f "$closure_err"
        return 1
    fi
    local srcfiles=()
    mapfile -t srcfiles < <(grep "^SRCFILE:" "$closure_err" | sed 's/^SRCFILE://')
    rm -f "$closure_err"

    cat >"$WPKG_DIR/hosts.xml" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<wpkg>
    <host name="$(hostname)" profile-id="wine"/>
</wpkg>
EOF
    cat >"$WPKG_DIR/profiles.xml" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<profiles>
    <profile id="wine">
        <package package-id="$pkgid"/>
    </profile>
</profiles>
EOF

    cp "$git_se4/sources/var/sambaedu/unattended/install/wpkg/wpkg-se4.js" "$WPKG_DIR/wpkg-se4.js"
    if ! patch_wpkg_for_wine "$WPKG_DIR/wpkg-se4.js"; then
        return 1
    fi

    # certaines recettes (ex: adnarn, curl, geogebra, vlc...) utilisent des
    # utilitaires livrés à côté de wpkg-se4.js en production
    # (%Z%\wpkg\tools\7za.exe, nircmd.exe...) : sans cette copie elles
    # échouent avec "fichier introuvable".
    if [ -d "$git_se4/sources/var/sambaedu/unattended/install/wpkg/tools" ]; then
        rm -rf "$WPKG_DIR/tools"
        cp -r "$git_se4/sources/var/sambaedu/unattended/install/wpkg/tools" "$WPKG_DIR/tools"
    fi

    # téléchargement des fichiers d'installation référencés par la recette
    # ET par toute sa fermeture de dépendances (srcfiles, calculée plus
    # haut) : une dépendance a aussi ses propres <download> à récupérer.
    # le cache /opt/install/packages n'est réutilisé que si son checksum
    # correspond à celui attendu par la recette (sinon il peut être plus
    # ancien que ce que déclare le xml : on retélécharge dans ce cas).
    # dlerrors/INSTALL_WPKG_DL_ERROR (volontairement PAS locale : elle doit
    # rester lisible par l'appelant, ex. test_wine_apps, après le retour de
    # cette fonction) permet de distinguer explicitement dans le rapport un
    # échec de téléchargement (url injoignable, checksum différent - donc un
    # problème de recette/ressource externe) d'un échec d'installation.
    local ok=1 url saveto sha256 dest cached cached_sha256 got srcfile
    local dlerrors=()
    INSTALL_WPKG_DL_ERROR=""
    for srcfile in "${srcfiles[@]}"; do
        while IFS=$'\t' read -r url saveto sha256; do
            [ -z "$url" ] && continue
            dest="$INSTALL_DIR/$saveto"
            mkdir -p "$(dirname "$dest")"

            if [ ! -f "$dest" ]; then
                cached="/opt/install/$saveto"
                cached_sha256="$(sha256sum "$cached" 2>/dev/null | cut -d' ' -f1)"
                if [ -f "$cached" ] && { [ -z "$sha256" ] || [ "${cached_sha256^^}" == "${sha256^^}" ]; }; then
                    echo "copie depuis le cache $cached"
                    cp "$cached" "$dest"
                else
                    echo "téléchargement de $url"
                    if ! wget -q -O "$dest" "$url"; then
                        echo "échec du téléchargement de $url"
                        rm -f "$dest"
                        ok=0
                        dlerrors+=("url injoignable : $url")
                        continue
                    fi
                fi
            fi

            if [ -n "$sha256" ]; then
                got="$(sha256sum "$dest" | cut -d' ' -f1)"
                if [ "${got^^}" != "${sha256^^}" ]; then
                    echo "sha256 invalide pour $dest (attendu $sha256, obtenu $got)"
                    ok=0
                    dlerrors+=("checksum différent : $saveto (attendu ${sha256^^}, obtenu ${got^^})")
                fi
            fi
        done < <(python3 -c '
import sys, xml.etree.ElementTree as ET
tree = ET.parse(sys.argv[1])
for d in tree.getroot().iter("download"):
    print(d.get("url","") + "\t" + d.get("saveto","") + "\t" + d.get("sha256sum",""))
' "$srcfile")
    done

    if [ "$ok" != "1" ]; then
        INSTALL_WPKG_DL_ERROR="$(IFS='; '; echo "${dlerrors[*]}")"
        echo "téléchargement des fichiers d'installation incomplet pour $appli : $INSTALL_WPKG_DL_ERROR"
        return 3
    fi

    # certains installeurs silencieux (dont celui de 7-Zip) rendent la main
    # avant d'avoir fini d'écrire tous les fichiers sur disque (plus visible
    # sous Wine que sous Windows) : la vérification post-installation de
    # wpkg-se4.js peut donc échouer alors que l'application est bel et bien
    # installée. wpkg est idempotent (la vérification pré-installation d'un
    # nouvel essai constate alors que le paquet est déjà installé) : on
    # retente donc quelques fois avant de déclarer un échec définitif.
    local log="/var/tmp/$USER/wine/wine-$prefixname-wpkg.log"
    local attempt
    local wine_status
    for attempt in 1 2 3; do
        rm -f "$WPKG_DIR/wpkg-settings.xml"
        # certains installeurs affichent leur propre boîte d'erreur, ou wine
        # affiche sa fenêtre de crash (winedbg, désactivée dans create_prefix
        # mais on garde ce filet de sécurité) : sans limite de temps, un test
        # non interactif resterait bloqué indéfiniment sur un clic humain qui
        # ne viendra jamais. timeout garantit qu'on ne reste jamais coincé.
        # SOFTWARE : certaines recettes (7za, Anmanie, openoffice...) l'utilisent
        # comme base pour les fichiers téléchargés, au lieu de %Z%\packages.
        # En prod c'est le lanceur (wpkg-client.vbs) qui la positionne ainsi
        # (oSysEnv("SOFTWARE") = Z & "\packages"), pas wpkg-se4.js lui-même.
        # WINEDLLOVERRIDES=winemenubuilder.exe= : winemenubuilder n'a pas
        # d'option pour désactiver spécifiquement l'export d'icônes vers le
        # VRAI bureau linux (confirmé en lisant son binaire) - il le fait
        # unilatéralement pour tout raccourci créé pendant l'installation, ce
        # qui pollue le bureau réel de l'utilisateur pour chaque appli
        # testée. On le désactive donc entièrement ici (les .lnk bruts
        # restent dans le prefix, utilisés directement par
        # run_prefix_app/smoke_test_app) ; create_debian_source le
        # réactive ponctuellement, pour la seule appli qu'on package
        # réellement, où avoir un menu/icônes est nécessaire.
        WINEPREFIX="$PREFIX" Z='C:\windows\install' SOFTWARE='C:\windows\install\packages' \
            WINEDLLOVERRIDES='winemenubuilder.exe=' \
            timeout --kill-after=10 300 \
            wine "C:\\windows\\syswow64\\cscript.exe" //nologo \
            "C:\\windows\\install\\wpkg\\wpkg-se4.js" /install:"$pkgid" \
            /base:"C:\\windows\\install\\wpkg" /profile:wine /host:"$(hostname)" \
            /settings:"C:\\windows\\install\\wpkg\\wpkg-settings.xml" \
            /noDownload:true /noreboot:true /quitonerror:true \
            >"$log.attempt$attempt" 2>&1
        wine_status=$?
        cp "$log.attempt$attempt" "$log"
        if grep -qE "installed successfully|Already installed \(checks succeeded\)" "$log"; then
            echo "$appli : installation réussie (log : $log, essai $attempt/3)"
            return 0
        fi
        if [ "$wine_status" -eq 124 ] || [ "$wine_status" -eq 137 ]; then
            echo "$appli : installation interrompue après 300s (blocage probable : boîte de dialogue, crash) (essai $attempt/3)"
        fi
        # état propre pour le prochain essai : un process tué par timeout
        # peut laisser une boîte de dialogue ou un mutex qui referait
        # bloquer l'essai suivant sinon.
        WINEPREFIX="$PREFIX" wineserver -k >/dev/null 2>&1
        sleep 3
    done

    # patch_wpkg_for_wine réécrit getRegistrySubkeys() pour passer par
    # reg.exe (natif) au lieu de WMI (WbemScripting.SWbemLocator, indisponible
    # sous Wine en mode wow64 sur cette machine), donc <check type="uninstall">
    # fonctionne normalement. On garde ce cas à part par sécurité (ex : reg.exe
    # manquant dans un prefix corrompu) plutôt que de le compter comme un échec
    # de la recette elle-même.
    if grep -q "Error when searching registry sub-keys" "$log"; then
        echo "$appli : non testable ici (erreur d'accès au registre via reg.exe) (log : $log)"
        return 2
    fi

    echo "$appli : échec de l'installation (log : $log)"
    return 1
}

# lance brièvement l'application installée pour vérifier qu'elle ne plante
# pas immédiatement - un signal complémentaire, PAS une preuve de bon
# fonctionnement (une appli bloquée sur une boîte de dialogue de première
# utilisation est indiscernable ici d'une appli qui marche). N'utilise
# jamais le PID bash du lancement en arrière-plan pour détecter/tuer le
# process réel : sur un lancement via .lnk, ce PID est celui du wrapper
# "start.exe" de wine, jamais l'exécutable réel (vérifié via pstree) - on
# utilise donc "wine tasklist" (le vrai tasklist.exe de wine, scopé au
# WINEPREFIX) pour observer les process réels, via un diff avant/après
# (aucune balise StartupWMClass disponible ici : winemenubuilder, seule
# source de cette information, est désactivé pendant install_wpkg - voir
# find_prefix_shortcuts). wineserver -k pour tout arrêter proprement à la
# fin. Affiche sur stdout un statut parmi : SMOKE-OK, SMOKE-FAIL,
# SMOKE-SKIPPED-no-shortcut, SMOKE-SKIPPED-tasklist-unavailable.
# smoke_test_app appli prefix
function smoke_test_app() {
    local appli="$1" prefixname="$2"
    local PREFIX="/var/tmp/$USER/wine/wine-$prefixname/prefix"
    local grace=10

    local lnk
    lnk="$(find_prefix_shortcuts "$prefixname" | head -n1)"
    if [ -z "$lnk" ]; then
        echo "SMOKE-SKIPPED-no-shortcut"
        return 0
    fi

    # wine tasklist encode sa sortie dans la page de code OEM (pas UTF-8) :
    # grep -a force le mode texte, sinon grep peut détecter à tort un
    # "fichier binaire" sur les octets non-UTF-8 des colonnes accentuées
    # (ex: "Mémoire") et se comporter de façon peu fiable.
    local before status
    if ! before="$(WINEPREFIX="$PREFIX" wine tasklist 2>/dev/null)"; then
        echo "SMOKE-SKIPPED-tasklist-unavailable"
        return 0
    fi

    local log="/var/tmp/$USER/wine/wine-$prefixname-smoke.log"
    WINEPREFIX="$PREFIX" WINEDLLOVERRIDES='winemenubuilder.exe=' wine "$lnk" >"$log" 2>&1 &
    sleep "$grace"

    local after new_procs
    if ! after="$(WINEPREFIX="$PREFIX" wine tasklist 2>/dev/null)"; then
        status="SMOKE-SKIPPED-tasklist-unavailable"
    else
        # une ligne présente après le lancement mais pas avant = un nouveau
        # process réel (l'appli elle-même, pas le wrapper start.exe qui,
        # lui, apparaît puis disparaît généralement avant ce point).
        new_procs="$(comm -13 <(echo "$before" | sort) <(echo "$after" | sort))"
        if [ -n "$new_procs" ]; then
            status="SMOKE-OK"
        else
            status="SMOKE-FAIL"
        fi
    fi

    WINEPREFIX="$PREFIX" wineserver -k >/dev/null 2>&1
    echo "$status"
}

# crée (ou remet à niveau) un prefix wine capable de faire tourner
# wpkg-se4.js : wsh57 (vrai moteur JScript Microsoft) et msxml3 (vrai moteur
# MSXML) sont indispensables, le JScript/MSXML intégré à Wine est trop
# incomplet (COM WshNamedCollection, XSLT, coercion de type). Non-interactif
# (wineboot --init), utilisable en CI.
function create_prefix() {
    local prefixname="$1"
    local wp="/var/tmp/$USER/wine/wine-$prefixname/prefix"
    mkdir -p "$wp"
    WINEDLLOVERRIDES='winemenubuilder.exe=' WINEPREFIX="$wp" wineboot --init >/dev/null 2>&1
    WINEPREFIX="$wp" wineserver -w
    WINEDLLOVERRIDES='winemenubuilder.exe=' WINEPREFIX="$wp" winetricks -q wsh57 msxml3 >/dev/null 2>&1
    # désactive la fenêtre de crash de wine (winedbg) : sans ça, un
    # installeur qui plante sous wine affiche une boîte de dialogue qui
    # attend un clic humain et bloque indéfiniment un test non interactif.
    WINEPREFIX="$wp" wine reg add "HKEY_CURRENT_USER\\Software\\Wine\\WineDbg" /v ShowCrashDialog /t REG_DWORD /d 0 /f >/dev/null 2>&1

    # wineboot --init pointe par défaut Desktop/Documents/Pictures/Music/
    # Videos/Downloads (drive_c/users/$USER/*) vers les VRAIS dossiers de
    # l'utilisateur (Documents et Pictures pointent même directement sur
    # $HOME en entier !). Un installeur qui écrit là (raccourci sur le
    # bureau, fichier dans "Mes documents"...) pollue donc directement les
    # vrais fichiers de l'utilisateur. On remplace tout symlink qui pointe
    # sous $HOME par un vrai dossier local, confiné au prefix jetable.
    local f target
    for f in "$wp/drive_c/users/$USER"/*; do
        [ -L "$f" ] || continue
        target="$(readlink -f "$f")"
        case "$target" in
        "$HOME" | "$HOME"/*)
            rm -f "$f"
            mkdir -p "$f"
            ;;
        esac
    done
}

# boucle d'intégration continue : teste, chacune dans un prefix wine séparé,
# toutes les applications du dépôt sambaedu-wpkg-xml (stable/) pour lesquelles
# aucun paquet debian du même id n'existe (voir has_native_linux).
function test_wine_apps() {
    local report="/var/tmp/$USER/wine/wpkg-ci-report.txt"
    local mode="$1"

    # reprise : par défaut (pas d'argument "reset"), une appli déjà présente
    # dans le rapport (d'une exécution précédente, interrompue ou non)
    # n'est pas retestée. Une interruption (Ctrl+C, timeout bloquant, arrêt
    # manuel) ne fait donc jamais repartir de zéro.
    local -A done_apps=()
    if [ "$mode" = "reset" ]; then
        : >"$report"
    elif [ -f "$report" ]; then
        local line
        while IFS= read -r line; do
            done_apps["${line%%:*}"]=1
        done <"$report"
    else
        : >"$report"
    fi

    local total=0 xmlfile appli status smoke

    for xmlfile in "$wpkg_xml_repo/stable/"*.xml; do
        [ -f "$xmlfile" ] || continue
        appli="$(basename "$xmlfile" .xml)"

        [ -n "${done_apps[$appli]:-}" ] && continue

        if has_native_linux "$xmlfile"; then
            continue
        fi

        # inutile de tester sous wine une appli dont une dépendance a déjà
        # un équivalent natif (voir has_native_dependency) : cela reviendrait
        # à installer cette dépendance sous wine sans raison.
        if has_native_dependency "$xmlfile"; then
            continue
        fi

        total=$((total + 1))
        echo "=== Test de $appli ($total) ==="
        create_prefix "$appli"
        install_wpkg "$appli" "$appli"
        status=$?
        case $status in
            0)
                # signal complémentaire, non bloquant (cf. smoke_test_app) :
                # ne modifie jamais le statut ok/fail/untestable ci-dessus.
                smoke="$(smoke_test_app "$appli" "$appli")"
                echo "$appli: OK ($smoke)" >>"$report"
                ;;
            2)
                echo "$appli: NON TESTABLE (erreur d'accès au registre)" >>"$report"
                ;;
            3)
                # échec de téléchargement (url injoignable, checksum différent) :
                # un problème de recette/ressource externe, pas un défaut de
                # wpkg-se4.js ou de l'installeur lui-même - à distinguer
                # explicitement d'un échec d'installation (voir install_wpkg).
                echo "$appli: ECHEC (téléchargement : $INSTALL_WPKG_DL_ERROR)" >>"$report"
                ;;
            *)
                echo "$appli: ECHEC (installation)" >>"$report"
                ;;
        esac

        # le sweep sert à valider la compatibilité, pas à produire des
        # artefacts à publier (ça, c'est le rôle de new/install_wpkg/prepare/
        # image pour UNE appli choisie séparément) : chaque prefix (plusieurs
        # Go avec winetricks) est donc supprimé aussitôt son résultat
        # enregistré, sinon le disque se remplit en quelques dizaines
        # d'applis. Les logs (petits fichiers texte, utiles pour le
        # diagnostic) sont conservés, eux.
        WINEPREFIX="/var/tmp/$USER/wine/wine-$appli/prefix" wineserver -k >/dev/null 2>&1
        rm -rf "/var/tmp/$USER/wine/wine-$appli"
    done

    # décompte final à partir du rapport complet (cumulatif d'éventuelles
    # reprises précédentes), pas seulement des applis traitées dans cet
    # appel, pour rester cohérent après une reprise.
    local ok fail untestable skipped=0
    ok="$(grep -c ': OK' "$report")"
    untestable="$(grep -c ': NON TESTABLE' "$report")"
    fail="$(grep -c ': ECHEC' "$report")"
    for xmlfile in "$wpkg_xml_repo/stable/"*.xml; do
        [ -f "$xmlfile" ] || continue
        if has_native_linux "$xmlfile" || has_native_dependency "$xmlfile"; then
            skipped=$((skipped + 1))
        fi
    done
    echo "Résumé : $ok/$((ok + fail + untestable)) réussites, $fail échec(s), $untestable non testable(s) (registre), $skipped application(s) native(s) ou à dépendance native ignorée(s)."
    echo "Détail : $report"
}

# cherche le(s) raccourci(s) .lnk créés par l'installeur dans le prefix
# wine-$prefixname (Menu Démarrer, tous utilisateurs et utilisateur
# courant). Utilisé directement (winemenubuilder étant désactivé pendant
# install_wpkg, voir plus haut, aucun .desktop n'est généré à ce stade) par
# run_prefix_app et smoke_test_app. Un chemin unix par ligne sur stdout
# (peut être vide : certaines applis n'ont pas de raccourci, ex. outils en
# ligne de commande ou mises à jour silencieuses).
function find_prefix_shortcuts() {
    local prefixname="$1" PREFIX
    PREFIX="/var/tmp/$USER/wine/wine-$prefixname/prefix"
    find "$PREFIX/drive_c/ProgramData/Microsoft/Windows/Start Menu/Programs" \
        "$PREFIX/drive_c/users/$USER/AppData/Roaming/Microsoft/Windows/Start Menu/Programs" \
        -iname "*.lnk" 2>/dev/null
}

# lance interactivement une application déjà installée dans son prefix, pour
# validation fonctionnelle manuelle par un humain (contrairement à
# install_wpkg/test_wine, ce n'est PAS une action pensée pour le CI :
# premier plan, pas de log, pas de retry - l'utilisateur observe et
# interagit directement avec wine, Ctrl+C pour arrêter).
# run_prefix_app appli prefix [sous-chaine-desktop]
function run_prefix_app() {
    local appli="$1" prefixname="$2" filtre="$3"
    local PREFIX="/var/tmp/$USER/wine/wine-$prefixname/prefix"
    if [ ! -d "$PREFIX" ]; then
        echo "Le prefix $prefixname n'existe pas. Lancez d'abord : $0 new $prefixname puis $0 install_wpkg $appli $prefixname"
        return 1
    fi

    local shortcuts=()
    mapfile -t shortcuts < <(find_prefix_shortcuts "$prefixname")
    if [ -n "$filtre" ]; then
        local filtered=() d
        for d in "${shortcuts[@]}"; do
            [[ "$d" == *"$filtre"* ]] && filtered+=("$d")
        done
        shortcuts=("${filtered[@]}")
    fi

    if [ "${#shortcuts[@]}" -eq 0 ]; then
        echo "aucun raccourci .lnk trouvé pour le prefix wine-$prefixname (l'appli n'a peut-être pas de raccourci Menu Démarrer, ex. outil en ligne de commande)."
        return 1
    fi

    local choix
    if [ "${#shortcuts[@]}" -eq 1 ]; then
        choix="${shortcuts[0]}"
    else
        echo "plusieurs raccourcis trouvés pour wine-$prefixname :"
        local i
        for i in "${!shortcuts[@]}"; do
            echo "  $((i + 1))) $(basename "${shortcuts[$i]}" .lnk)"
        done
        local reponse
        read -r -p "lequel lancer (numéro, ou relancez avec un 3e argument pour filtrer) ? " reponse
        if ! [[ "$reponse" =~ ^[0-9]+$ ]] || [ "$reponse" -lt 1 ] || [ "$reponse" -gt "${#shortcuts[@]}" ]; then
            echo "choix invalide."
            return 1
        fi
        choix="${shortcuts[$((reponse - 1))]}"
    fi

    echo "lancement de $(basename "$choix" .lnk) (Ctrl+C pour arrêter) :"
    WINEPREFIX="$PREFIX" WINEDLLOVERRIDES='winemenubuilder.exe=' wine "$choix"
}

# copie le .desktop et et les icones associées dans l'arborescence git du package
# ajoute les fichier au sambaedu-$1.install
# ajoute la $2 comme catégorie (Education par exemple)
# prépare un fichier usr/bin/$1 qui sert de lanceur pour le raccourci
# Si vous voulez pouvoir passer des arguments dans le .desktop,
# il faudra probablement éditer ce lanceur pour lancer directement l'exe et pas le .lnk qui est généralement
# utilisé dans le lanceur généré par wine

# les icones sont dans ./local/share/icons/hicolor/XxX/apps/
# on recherche leur nom à partir du raccourci créé par wine pour l'application
# on les copie toutes dans le git/sambaedu-wpkg-xml/sources/usr/share/icons
# on crée les dossiers si besoin.

# create_debian_source nom_package nom_image [categorie]

function create_debian_source() {
    # repart de zéro à chaque exécution (idempotent : "prepare" doit pouvoir
    # être rejoué pour mettre à jour un paquet existant sans accumuler de
    # doublons si le jeu de .desktop/icônes a changé entre deux versions).
    : >"$git/debian/sambaedu-$1.install"
    local stub_written=0

    # install_wpkg désactive winemenubuilder pendant l'installation (pour ne
    # pas polluer le vrai bureau linux, voir install_wpkg) : aucun
    # .desktop/icône n'existe donc encore à ce stade pour cette appli. On
    # régénère ici, ponctuellement et volontairement (winemenubuilder
    # activé), les entrées de menu pour les seuls raccourcis de CE prefix -
    # nécessaire pour que la boucle ci-dessous (grep sur
    # ~/.local/share/applications/wine/Programs/) trouve quelque chose.
    local lnk
    while IFS= read -r lnk; do
        WINEPREFIX="/var/tmp/$USER/wine/wine-$2/prefix" wine winemenubuilder.exe -w "$lnk" >/dev/null 2>&1
    done < <(find_prefix_shortcuts "$2")

    while IFS= read -r desktop; do
        dest="usr/share/applications"
        mkdir -p "$(dirname "$git/$dest")"
        fichier="$(basename "${desktop// /_}")"
        cp "$desktop" "$git/$dest/$fichier"

        run="# $(sed -n "s/$USER/\$USER/g;s/Exec=//p" "$desktop")"
        icon="$(sed -n 's/^Icon=//p' "$desktop")"
        icone="${icon// /_}"
        sed -i "s/Exec=.*$/Exec=\/usr\/bin\/$1/;/Path=/d;s/${icon}/${icone}/" "$git/$dest/$fichier"
        if [ -n "$3" ]; then
            echo "Categories=$3;" >>"$git/$dest/$fichier"
        fi

        echo "$dest/$fichier" >>"$git/debian/sambaedu-$1.install"

        # le lanceur n'est écrit qu'une fois par exécution, même si plusieurs
        # .desktop correspondent (ex: suites avec plusieurs raccourcis) :
        # sinon son contenu était dupliqué une fois par raccourci trouvé,
        # y compris au sein d'une seule exécution.
        if [ "$stub_written" = 0 ]; then
            echo "#!/bin/bash
# lanceur pour $1, à adapter
#wine_scripts pre $1 || true
#$run
#wine_scripts post $1 || true
" >"$git/usr/bin/$1"
            chmod 755 "$git/usr/bin/$1"
            echo "usr/bin/$1" >>"$git/debian/sambaedu-$1.install"
            stub_written=1
        fi
        cp "$git/debian/sambaedu-appli.postinst.ex" "$git/debian/sambaedu-$1.postinst"
        sed -i "s/{{appli}}/$1/g" "$git/debian/sambaedu-$1.postinst"
        sed -i "s/{{prefix}}/wine-$2/g" "$git/debian/sambaedu-$1.postinst"

        find "$HOME/.local/share/icons" -name "${icon}.png" | while read -r src; do
            rel="${src#"$HOME/.local/share/icons/"}"
            dest="usr/share/icons/${rel}"
            mkdir -p "$(dirname "$git/$dest")"
            cp "$src" "$git/${dest// /_}"
            # ajout icones dans le package
            echo "${dest// /_}" >>"$git/debian/sambaedu-$1.install"
        done
    done < <(grep -rl --include="*.desktop" "Exec=env \"WINEPREFIX=/var/tmp/$USER/wine/wine-$2/prefix\"" ~/.local/share/applications/wine/Programs/)
    # ajout au debian/control si besoin
    if ! grep -q "sambaedu-$1" "$git/debian/control"; then
        echo "
Package: sambaedu-$1
Architecture: all
Depends: \${misc:Depends}, sambaedu-linux-stations-wine
Description: Package $1 pour l'image wine-$2.
 Déploie sur le serveur se4fs l'image squashfs wine-$2, et ajoute les raccourcis et menus nécessaires." >>"$git/debian/control"
    fi
}

# compile l'image squashfs à partir de l'image montée par l'utilisateur
# make_image prefix
function make_image() {
    PREFIX="/var/tmp/$USER/wine/wine-$1/prefix"
    mkdir -p "$PREFIX"
    # rename user and hostname in prefix
    sed -i "s/$USER/{{USER}}/g" $PREFIX/*.reg
    sed -i "s/$(hostname)/{{HOSTNAME}}/g" $PREFIX/*.reg
    mv "$PREFIX/drive_c/users/$USER" "$PREFIX/drive_c/users/{{USER}}"

    mksquashfs "$PREFIX" "/var/tmp/$USER/wine/wine-$1.img" -all-root -no-progress -no-xattrs -noappend # -quiet
    sha256sum "/var/tmp/$USER/wine/wine-$1.img" | cut -d' ' -f1 >"/var/tmp/$USER/wine/wine-$1.sha256"

    # revert user and hostname in prefix
    sed -i "s/{{USER}}/$USER/g" $PREFIX/*.reg
    sed -i "s/{{HOSTNAME}}/$(hostname)/g" $PREFIX/*.reg
    mv "$PREFIX/drive_c/users/{{USER}}" "$PREFIX/drive_c/users/$USER"
}
# construit le package sambaedu-$1 demandé
function make_package() {
    cd "$git" || return
    rm -f "*.deb"
    DEBFULLNAME="$USER" DEBEMAIL="$USER@sambaedu.org" dch -i "compilation du paquet $1"
    debuild -us -uc -b
    find ../ -maxdepth 1 -name "sambaedu-*.deb" ! -name "sambaedu-$1*.deb" -delete
    cd
}

# installe l'image en local (il faut être root)
# compile le package et l'installe avec dpkg -i
#
function install_local() {
    dpkg -i "$git/../sambaedu-$1*.deb"
}

# pousse l'image sur le repo, pousse les fichiers dans un projet git, et lance la construction distante du paquet
# et sa publication par reprepro
# install_repo prefix nom "message de commit"
install_repo() {
    # on pose l'image et le checksum Azerty-12345437765
    SSH="ssh root@deb.sambaedu.org"
    SERVER="http://deb.sambaedu.org/wpkg/files/wine"

    # test si l'image existe déjà, on compare son checksum avec celui du dépôt
    if [ -f "/var/tmp/$USER/wine/wine-$2.sha256" ]; then
        if ! wget -q -O /tmp/wine-$2.sha256 $SERVER/wine-$2.sha256 || [ "$(cat /tmp/wine-$2.sha256)" != "$(cat /var/tmp/$USER/wine/wine-$2.sha256)" ]; then
            scp /var/tmp/$USER/wine/wine-$2.* root@deb.sambaedu.org:/var/www/wpkg/files/wine/
        fi
    fi
    # git
    cd "$git"
    # on commite les changements
    git commit -a -m "génération du paquet wine $1 $2"
    git pull
    git push
    # reprepro
    $SSH "mkdir -p /root/tmpse4"
    deb="$(ls $git/../sambaedu-$1*.deb 2>/dev/null | tail -1)"
    if [ -n "$deb" ]; then
    scp "$deb" root@deb.sambaedu.org:/root/tmpse4
    paquet=$(basename "$deb")
    $SSH "echo \"traitement de $paquet\"; reprepro -C se4XP -b /var/www/debian includedeb trixie /root/tmpse4/$paquet"
    $SSH "echo \"traitement de $paquet\"; reprepro -C stable -b /var/www/debian includedeb trixie /root/tmpse4/$paquet"
    $SSH "rm -fr /root/tmpse4/$paquet"
    fi
}

pull_repo() {
    mkdir -p $HOME/git
    cd $HOME/git
    if [ ! -d "$HOME/git/sambaedu-wpkg-xml" ]; then
        git clone https://gitlab.sambaedu.org/sambaedu/sambaedu-wpkg-xml.git
    fi
    cd $HOME/git/sambaedu-wpkg-xml
    git pull
    cd
}

action="$1"
package="$2"
if [ -z "$3" ]; then
    prefix="$package"
else
    prefix="$3"
fi
if [ -z "$action" ] || [ "$action" == "--help" ]; then
    echo "$0 new|install_wpkg|run|test_wine|prepare|image|build|install|publish <package> [prefix] [categorie]

 new : crée un nouveau prefix (non-interactif, adapté au CI) ou remet à niveau un existant.
    A faire avant d'installer l'application Windows. le prefix est dans /var/tmp/$USER/wine/wine-$prefix/prefix
    Normalement toute commande wine doit être précédée de WINEPREFIX=/var/tmp/$USER/wine/wine-$prefix/prefix.
    Installe également wsh57 et msxml3 (indispensables pour install_wpkg).
    En cas de modification d'un prefix déjà en place, cette étape n'est pas nécessaire.

 install_wpkg : installe l'application via le client wpkg windows (wpkg-se4.js), à partir de la
    recette du dépôt local sambaedu-wpkg-xml (~/git/sambaedu-wpkg-xml). Le prefix doit déjà exister
    (make-prefix new). Ne dépend pas du domaine SambaEdu ni d'un poste joint au domaine. Installe
    aussi les dépendances éventuelles (<depends package-id=...>) de la recette.

 run : lance l'application déjà installée (install_wpkg) pour une validation fonctionnelle manuelle.
    Ce n'est PAS une action pour le CI : premier plan, interactif, Ctrl+C pour arrêter.
    make-prefix run <package> <prefix> [sous-chaine-desktop pour désambiguïser]

 test_wine : lance install_wpkg (dans un prefix séparé pour chacune) pour toutes les applications de
    sambaedu-wpkg-xml/stable pour lesquelles aucun paquet debian du même id n'existe, ni aucune de leurs
    dépendances (apt-cache show / balise <linux>). Pensé pour le CI.
    Rapport dans /var/tmp/$USER/wine/wpkg-ci-report.txt : une appli déjà présente dans ce rapport n'est PAS
    retestée (reprise automatique après une interruption). Pour repartir de zéro : make-prefix test_wine reset

 prepare : fusionne les éléments du prefix en cours avec le dépôt git sambaedu-wpkg-xml/sources. Tente de récupérer les
    .desktop, icones, et crée un lanceur shell. Ces éléments sont à modifier manuellement avant de construire le paquet.

 image : construit l'image squashfs du prefix (assez long)

 build : construit le paquet debian. Le numéro de version est incrémenté.

 publish : pousse sur le dépôt gitlab et deb le paquet (authentification sur deb.sambaedu.org et gitlab indispensables).
    seuls les paquets indiqués seront publiés. Si certains ont des dépendances il faut publier aussi les dépendances.

 install : installe localement le paquet généré (droits root indispensables)"
fi

case $action in
new)
    create_prefix "$prefix"
    export WINEPREFIX="/var/tmp/$USER/wine/wine-$prefix/prefix"
    echo "le prefix /var/tmp/$USER/wine/wine-$prefix/prefix a été créé est est actif par défaut pour wine pour cette session."
    ;;
install_wpkg)
    install_wpkg $package $prefix
    status=$?
    [ "$status" = 0 ] && echo "smoke-test : $(smoke_test_app "$package" "$prefix")"
    exit $status
    ;;
run)
    run_prefix_app "$package" "$prefix" "$4"
    ;;
test_wine)
    test_wine_apps "$package"
    ;;
prepare)
    pull_repo
    create_debian_source $package $prefix $4
    echo "le dépôt local $package a été mis à jour. modifiez manuellement les choses nécessaires..."
    ;;
image)
    make_image $prefix
    ;;
build)
    make_package $package
    ;;
install)
    install_local $package $prefix
    ;;
publish)
    install_repo $package $prefix
    ;;
esac
