{ "meta": { "title": "Contribution à ProxMenux | ProxMenux Documentation", "description": "Comment contribuer à ProxMenux : modèle de branches (main / develop / feature/*), processus de pull request, en-tête de script avec attribution et lien de soutien facultatif, interface en deux phases, fonctions de message et passage d’une branche de fonctionnalité à une version stable.", "ogTitle": "Contribuer à ProxMenux", "ogDescription": "Modèle de branches, processus, attribution dans l’en-tête et règle d’interface en deux phases à respecter pour contribuer." }, "header": { "title": "Contribuer à ProxMenux", "description": "ProxMenux accueille les contributions de la communauté : nouveaux scripts, corrections, amélioration des dialogues, traductions et intégrations. Cette page présente la structure du dépôt, le modèle de branches, le processus de pull request, l’en-tête avec attribution et lien de soutien facultatif, ainsi que les conventions à respecter.", "section": "À propos" }, "twoPagesCallout": { "title": "Deux pages connexes, un projet", "body": "Ne confondez pas cette page avec Contributeurs, qui met à l’honneur les testeurs, relecteurs et développeurs déjà impliqués. Cette page s’adresse à vous, futur contributeur, et explique comment préparer une pull request en vue de son intégration." }, "branching": { "heading": "Modèle de branchement", "intro": "ProxMenux utilise un modèle de branchement à trois niveaux :", "headerBranch": "Branche", "headerPurpose": "Objet", "rows": [ { "branch": "main", "purposeRich": "Code stable, prêt pour la production. Seules les modifications prêtes à être publiées sont intégrées ici. C’est la version obtenue lors d’une installation normale de ProxMenux." }, { "branch": "develop", "purposeRich": "Branche de développement actif : tous les nouveaux changements y sont intégrés en premier. Elle correspond au canal « bêta ». Lorsqu’une version stable est prête, elle est publiée dans main à partir de cette branche." }, { "branch": "feature/*", "purposeRich": "Branches temporaires pour une fonctionnalité, une correction ou une amélioration. Créées à partir de develop, puis fusionnées dans develop par pull request après relecture." } ], "calloutTitle": "Ce que cela signifie pour les utilisateurs", "calloutBody": "Les utilisateurs qui suivent main reçoivent des versions testées. Ceux qui souhaitent les nouveautés plus tôt et acceptent quelques imperfections peuvent suivre develop. Les nouvelles fonctions passent toujours d’abord par develop ; rien n’arrive dans main sans ce cycle." }, "workflow": { "heading": "Processus de pull request", "intro": "De l'idée à la version fusionnée en cinq étapes:", "step1Lead": "Créer une branche de fonction à partir de develop:", "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", "step1Trail": "Utilisez un nom descriptif de branche : feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", "step2Lead": "Travailler sur vos changements et pousser la branche:", "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", "step3": "Ouvrez une pull request vers develop, pas vers main. Vérifiez sur GitHub que la branche de base est develop. Décrivez le rôle du script, ses modifications et les versions de Proxmox VE utilisées pour les tests.", "step4": "Après relecture, les modifications sont fusionnées dans develop. Un responsable vérifie le code, les conventions d’en-tête et la règle d’interface en deux phases. S’il demande des corrections, envoyez de nouveaux commits dans la même branche : la PR se met à jour automatiquement.", "step5": "Les versions stables passent de develop à main. Lors d’une publication, main est avancée jusqu’à develop par fast-forward, puis étiquetée. Votre contribution entre dans la prochaine version stable." }, "scriptHeader": { "heading": "En-tête de script — métadonnées et description", "intro": "Chaque script de ProxMenux s'ouvre avec deux blocs de commentaires adjacents qui forment ensemble l'en-tête. Ils sont tous les deux requis — ensemble, ils font savoir à tout lecteur qui a écrit le script et ce qu'il fait, le tout sans ouvrir le code lui-même.", "bullets": [ "Bloc supérieur : métadonnées. Auteur, liens GitHub/de soutien facultatifs, responsable, copyright, licence, version et date de mise à jour. C’est aussi la reconnaissance des contributeurs : pour un nouveau script, ajoutez votre nom et, si vous le souhaitez, votre page GitHub et un profil de soutien tel que Ko-fi, GitHub Sponsors ou Buy Me a Coffee.", "Bloc inférieur : description. Un court paragraphe en anglais simple explique le rôle du script. Il se lit avant le code et doit se suffire à lui-même : objectif, actions principales, ressources concernées et prérequis." ], "licenseCalloutTitle": "La ligne de licence est fixe — GPL-3.0", "licenseCalloutBody": "ProxMenux est publié sous la licence publique générale GNU v3.0. Chaque script du projet est livré sous cette même licence; la ligne License dans l'en-tête est toujours la référence GPL-3.0 montrée dans l'exemple ci-dessous — ce n'est pas un choix par script. En contribuant un script, vous acceptez de le publier sous GPL-3.0, ce qui signifie que n'importe qui peut le lire, le modifier et le redistribuer (y compris les modifications) tant qu'ils le conservent sous la même licence. Le texte entier vit à MacRimi/ProxMenux/LICENSE.", "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", "optionalNote": "Les lignes GitHub et Sponsor sont optionnelles — laissez-les dehors si vous ne voulez pas les publier. Tout le reste est nécessaire.", "whyCalloutTitle": "Pourquoi cela est important", "whyCalloutBody": "Contribuer à l’open source est un travail bénévole. Le nom et le lien de soutien éventuel dans le code rendent l’auteur visible à chaque lecture, au lieu de le cacher derrière le projet. La description a un autre rôle : c’est la première chose qu’un futur responsable ou utilisateur curieux lit. Sa clarté détermine souvent si le script inspire confiance ou est évité." }, "structure": { "heading": "Structure du projet", "intro": "Où chaque type de script vit dans le dépôt :", "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", "outro": "Chaque script charge utils.sh au démarrage avec source pour obtenir les fonctions de message, l’indicateur d’attente, les couleurs et le système de traduction. Les bibliothèques communes dans scripts/global/ sont chargées explicitement par les scripts qui les utilisent." }, "twoPhase": { "heading": "La règle de conception de l’interface en deux phases", "intro": "C’est la convention la plus importante de ProxMenux et celle qui entraîne le plus souvent des demandes de correction dans les PR. Chaque script est divisé en exactement deux phases :", "headerPhase": "Phase", "headerPurpose": "Objet", "headerScreen": "État de l'écran", "rows": [ { "phaseRich": "Phase 1 — Sélection", "purposeRich": "Recueillir chaque décision de l'utilisateur. Effectuer tout travail préparatoire nécessaire (sondes, scans, vérifications).", "screenRich": "dialog superpositions. Si une sonde entre deux menus prend plus d'une seconde ou deux, montrez un spinner msg_info afin que l'utilisateur sache que le script n'a pas gelé, puis appelez stop_spinner juste avant la boîte de dialogue suivante." }, { "phaseRich": "Phase 2 — Exécution", "purposeRich": "Exécutez toutes les opérations. Affichez l'historique complet des progrès accumulés à l'écran.", "screenRich": "Messages visibles msg_info / msg_ok; jamais dialog." } ], "principle": "Le principe: collecte tout d'abord, puis exécute tout. L'utilisateur voit un menu de dialogue propre (phase 1), puis une vue d'exécution propre (phase 2). Pas de mélange — pas de dialogue apparaissant au milieu de l'exécution, pas de bruit de progression pendant la sélection.", "phase1Heading": "Phase 1 — modèle typique", "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", "phase1RulesIntro": "Règles pour la phase 1:", "phase1Rules": [ "Si un spinner msg_info est en cours d'exécution et que vous devez ouvrir un menu dialog ou whiptail, appelez d'abord stop_spinner — le spinner ne peut pas coexister avec la superposition dessinée par l'un ou l'autre des outils. Si aucun spinner n'est actif, vous n'avez pas besoin de l'appeler.", "Utilisez show_proxmenux_logo + msg_title + msg_info pour donner un contexte visuel à une opération longue en phase 1, par exemple une vérification de plus de cinq secondes. La fonction efface déjà l’écran : n’appelez pas clear avant.", "N'appelez pas show_proxmenux_logo entre les menus de dialogue où il n'y a rien à afficher — le nettoyage de l'écran pour un terminal vide n'est que du bruit visuel.", "Conservez toutes les décisions et les résultats de la sonde dans des variables ou des tableaux parallèles. La récapitulation visible se produit au début de la phase 2, pas dans la phase 1." ], "phase2Heading": "Phase 2 — modèle typique", "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", "phase2Rules": "Règles pour la phase 2: toujours commencer avec show_proxmenux_logo + msg_title; récapituler immédiatement les résultats de la phase 1 sous la forme de lignes msg_ok; n'appelez plus jamais show_proxmenux_logo (il effacerait les progrès accumulés); n'appelez jamais dialog dans la phase 2 — si une décision d'exécution est vraiment inévitable, utilisez whiptail (voir la section suivante)." }, "dialogVsWhiptail": { "headingRich": "Quand utiliser dialog vs whiptail", "intro": "Les deux outils dessinent des interfaces utilisateur en mode texte, mais ils se comportent très différemment à l'écran — et ProxMenux les utilise en deux phases distinctes pour une raison.", "headerTool": "Outil", "headerWhen": "Quand l'utiliser", "headerEffect": "Effet sur l'écran", "rows": [ { "toolRich": "dialog", "whenRich": "Toujours dans la phase 1. Chaque sélection interactive — choisir un VM, un disque, un système de fichiers, confirmer une action — utilise dialog. C'est l'outil d'interface utilisateur par défaut de ProxMenux.", "effectRich": "Prend le contrôle du terminal : efface l’écran, dessine son interface et rétablit l’état précédent à la fermeture. Cela convient à la phase 1, où aucun résultat utile n’est encore affiché." }, { "toolRich": "whiptail", "whenRich": "Seulement en phase 2, en cas de nécessité. Le cas typique est la proposition de redémarrage en fin d’exécution. N’utilisez pas whiptail pour demander « continuer ? » en cours d’exécution : cette décision devait être prise en phase 1.", "effectRich": "Interface plus légère qui n’efface pas le contexte du terminal. L’historique msg_ok/msg_info de la phase 2 reste visible derrière la boîte de dialogue. Elle convient donc lorsque la progression est déjà affichée." } ], "calloutTitle": "Pourquoi la scission est importante", "calloutBody": "Si vous appelez dialog au milieu de la phase 2, chaque ligne msg_ok que l'utilisateur regarde disparaît. Vous effacez la piste de vérification. whiptail évite cela. Donc la règle n'est pas arbitraire — il s'agit de préserver la vision de l'utilisateur de ce que le script a fait jusqu'ici.", "rebootHeading": "Proposition de redémarrage : le cas type de whiptail en phase 2", "rebootIntro": "Si un script peut nécessiter un redémarrage après l’activation d’IOMMU ou la modification du noyau, la question de fin de phase 2 utilise whiptail. Prévoyez toujours une branche « Non » avertissant de ne pas utiliser la ressource concernée avant le redémarrage :", "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" }, "messageFunctions": { "heading": "Référence des fonctions du message", "intro": "Toutes sont définies dans utils.sh. Utilisez-les par défaut pour les sorties visibles afin de garder une présentation cohérente entre scripts. Si une fonction manque, par exemple un niveau de sévérité ou un outil de mise en page, proposez-la dans votre pull request. Elle sera examinée et ajoutée à utils.sh si elle est utile à plusieurs scripts.", "headerFunction": "Fonction", "headerWhen": "Quand utiliser", "headerSpinner": "Indicateur de progression", "rows": [ { "function": "msg_info \"text\"", "whenRich": "Opération en cours.", "spinner": "Démarre" }, { "function": "stop_spinner", "whenRich": "Fin des travaux préparatoires silencieux dans la phase 1 — tue spinner, efface la ligne.", "spinner": "Arrête" }, { "function": "msg_ok \"text\"", "whenRich": "Opération réussie. À utiliser aussi pour une fonction activée, même si un redémarrage reste nécessaire.", "spinner": "Arrête" }, { "function": "msg_warn \"text\"", "whenRich": "Avertissement réel ou état dégradé.", "spinner": "Arrête" }, { "function": "msg_error \"text\"", "whenRich": "Erreur fatale.", "spinner": "Arrête" }, { "function": "msg_info2 \"text\"", "whenRich": "Conseil sans blocage, dans une ligne d’information cyan.", "spinner": "Arrête" }, { "function": "msg_success \"text\"", "whenRich": "Message final « Appuyez sur Entrée pour revenir » en fin de phase 2.", "spinner": "Arrête" }, { "function": "msg_title \"text\"", "whenRich": "Titre gras avec espacement intégré. Utilisé au début de la phase 2.", "spinner": "—" }, { "function": "show_proxmenux_logo", "whenRich": "Efface l’écran et affiche le logo. À appeler une seule fois au début de la phase 2.", "spinner": "—" } ] }, "dialogConventions": { "heading": "conventions de dialogue", "bullets": [ "Passez toujours --backtitle \"$BACKTITLE\" à chaque appel dialog et whiptail. $BACKTITLE est toujours \"ProxMenux\" — réglé une fois à l'en-tête du script et jamais dépassé. L'utilisateur doit toujours voir le nom du projet comme le contexte de cadrage, jamais le propre titre du script.", "Enveloppez toujours les titres et les messages avec $(translate \"...\").", "Redirige toujours la sortie dialog avec 2>&1 >/dev/tty — le stdout capturé devient la sélection de l'utilisateur, tandis que la boîte de dialogue elle-même s'appuie sur le terminal.", "Utilisez les variables standard de dimension UI ($UI_MENU_H, $UI_MSG_W, etc.) pour un calibrage cohérent entre les scripts.", "Vérifiez toujours les choix vides ou annulés et gérez-les proprement." ], "exampleIntro": "Exemple complet — construire un menu de sélection VM et gérer l'annulation:", "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" }, "translation": { "heading": "Politique de traduction", "intro": "Toutes les chaînes visibles par l'utilisateur doivent être enveloppées avec la fonction translate. ProxMenux les traduit automatiquement dans toutes les langues prises en charge — vous écrivez l'anglais, l'utilisateur lit leur langue maternelle.", "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", "bullets": [ "Écrivez les chaînes en anglais : la traduction est gérée automatiquement lors du build.", "Rédigez des chaînes concises. Évitez si possible les variables dans de longues phrases.", "Ne traduisez pas les noms de variables, chemins ou identifiants techniques." ] }, "variableStyle": { "heading": "Conventions de variables et de style", "bullets": [ "Utilisez UPPER_CASE pour les variables de niveau script.", "Utiliser lower_case pour les variables de fonction locales (déclarer avec local).", "Entourez les expansions de variables de guillemets : \"$VAR\", pas $VAR.", "Utiliser [[ ]] pour les conditions, et non [ ] (sauf lorsque POSIX est nécessaire).", "show_proxmenux_logo est le moyen prévu pour effacer l’écran : il le nettoie et affiche le logo du projet pour conserver un contexte visuel. Appelez-le une fois au début de la phase 2, et éventuellement avant une longue attente en phase 1." ], "standardNamesIntro": "Noms de variables standard dans l'ensemble du projet :", "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", "redirectHeading": "Réorienter la sortie de l'outil pendant la phase 2", "redirectIntro": "La phase 2 affiche un journal de lignes msg_info → msg_ok. Si un outil appelé, comme apt, mkfs, qm, pct ou dd, écrit sur stdout/stderr, sa sortie fait défiler les messages et brouille la présentation. L’utilisateur ne distingue plus la progression du script des détails produits par l’outil.", "withoutRedirectIntro": "Sans redirection — ce que l'utilisateur voit si vous ne manipulez pas le bruit:", "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", "withRedirectIntro": "Avec redirection — à quoi ressemble la même opération lorsque le bruit est envoyé ailleurs:", "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", "twoPatternsIntro": "Deux modèles à choisir :", "discardLead": "Éliminez la sortie lorsque vous n'en avez pas besoin — plus rapide et plus simple:", "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", "logLead": "Envoyez la sortie à un fichier journal lorsque vous voudrez peut-être l'inspecter plus tard (débogage d'une installation échouée, vérification de ce que dpkg a fait réellement). C'est le modèle préféré pour toute opération apt:", "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", "referenceOutro": "Le script scripts/global/update-pve9_2.sh est une implémentation de référence — chaque appel apt-get envoie une sortie vers un fichier journal de sorte que l'utilisateur ne voit que le flux propre msg_info → msg_ok, tandis que le journal sur le disque vous permet de reconstruire exactement ce qu'apt a fait si quelque chose tourne mal." }, "dosAndDonts": { "heading": "Faites et ne faites pas", "doHeading": "✅ À faire", "doBullets": [ "stop_spinner avant un dialog dans la phase 1 seulement quand un spinner msg_info est en cours d'exécution.", "La phase 2 commence par show_proxmenux_logo + msg_title + msg_ok pour récapituler les résultats de la phase 1.", "Utilisez msg_ok pour des fonctionnalités activées avec succès, même si un redémarrage est nécessaire.", "Utilisez le whiptail (et non le dialog) pour toute invitation après exécution qui doit apparaître dans la phase 2.", "Prévoyez toujours une branche « Non » dans les dialogues de redémarrage pour avertir de ne pas démarrer la ressource concernée avant le redémarrage.", "Protégez la logique VM uniquement en vérifiant [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]] — les contrôleurs et NVMe PCIe ne peuvent pas être ajoutés aux conteneurs LXC.", "Utiliser ensure_repositories de utils-install-functions.sh au lieu de apt-get update inconditionnel.", "Utiliser des tableaux parallèles dans la phase 1 lorsque chaque élément nécessite plusieurs dialogues." ], "dontHeading": "❌ À éviter", "dontBullets": [ "Appelez dialog alors qu'un spinner est actif.", "Passez la récapitulation de la phase 1 au début de la phase 2.", "Appelez show_proxmenux_logo une deuxième fois — il efface tout ce que la phase 2 a imprimé.", "Utiliser dialog dans la phase 2 (utiliser whiptail pour le cas inévitable rare).", "Utiliser clear seul.", "Enveloppez msg_title dans des lignes vierges echo \"\" — il inclut déjà l'espacement.", "Utilisez msg_warn pour signaler une fonctionnalité activée avec succès — c'est un msg_ok.", "Exécuter sans condition apt-get update — utiliser ensure_repositories." ] }, "submitting": { "heading": "Soumettre votre contribution", "steps": [ "Créez un fork de MacRimi/ProxMenux sur GitHub, puis clonez votre fork.", "Créer une branche feature/* à partir de develop (voir Modèle de branchage ci-dessus).", "Suivez ce guide pour tout script nouveau ou modifié. Écrivez l'en-tête avec votre nom; ajoutez la ligne de sponsor optionnelle si vous voulez.", "Testez le script sur une véritable instance Proxmox VE : phase 1 avec toutes les branches de dialogue, et phase 2 avec chaque opération et son retour arrière propre en cas d’erreur.", "Ouvrez une pull request vers develop avec une description claire : rôle du script, changements et version Proxmox VE utilisée pour les tests.", "Assurez-vous que votre contribution respecte le Code de conduite." ], "securityOutro": "Pour les questions sensibles à la sécurité, suivez le flux de divulgation dans SECURITY.md plutôt que d'ouvrir une question publique." }, "whereNext": { "heading": "Où aller ensuite", "items": [ { "kind": "external", "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", "label": "CONTRIBUTING.md (guide complet)", "tail": "— la source de vérité pour chaque convention, y compris les modèles matériels avancés." }, { "kind": "internal", "href": "/docs/about/contributors", "label": "Contributeurs", "tail": " — les personnes dont le travail a façonné les versions de ProxMenux. Il s’agit d’une autre page." }, { "kind": "internal", "href": "/docs/about/code-of-conduct", "label": "Code de conduite", "tail": "— les normes que chaque contributeur accepte de respecter." }, { "kind": "external", "url": "https://github.com/MacRimi/ProxMenux/discussions", "label": "GitHub Discussions", "tail": "— demander avant de construire si vous n'êtes pas sûr si une idée correspond, ou de trouver des collaborateurs sur une fonctionnalité plus grande." } ] } }