diff --git a/app/routers/planning_import.py b/app/routers/planning_import.py
index 0b58cf3..7c438a2 100644
--- a/app/routers/planning_import.py
+++ b/app/routers/planning_import.py
@@ -619,10 +619,13 @@ async def iexec_page(request: Request, db=Depends(get_db),
r.intervenant,
r.is_eligible, r.server_id,
r.pct_required, r.pct_confirmed_at,
- s.hostname, vs.effective_excludes
+ s.hostname, vs.effective_excludes,
+ s.skip_first_reboot, s.patching_notes,
+ sc.name AS cluster_name, sc.reboot_delay_min_minutes
FROM patch_planning_import_rows r
LEFT JOIN servers s ON s.id = r.server_id
LEFT JOIN v_servers vs ON vs.id = r.server_id
+ LEFT JOIN server_clusters sc ON sc.id = s.cluster_id
WHERE r.id IN ({placeholders}) AND r.is_eligible = true
""")).fetchall()
diff --git a/app/templates/partials/server_detail.html b/app/templates/partials/server_detail.html
index 8364a41..be66fb1 100644
--- a/app/templates/partials/server_detail.html
+++ b/app/templates/partials/server_detail.html
@@ -73,12 +73,20 @@
Frequence{{ s.patch_frequency }}
Podman{{ 'Oui' if s.is_podman else 'Non' }}
Prevenance PCT{{ 'Oui' if s.pct_required else 'Non' }}
+ Skip 1er reboot{{ 'Oui' if s.skip_first_reboot else 'Non' }}
Jour préféré{{ s.pref_patch_jour or 'indifférent' }}
Heure préférée{{ s.pref_patch_heure or 'indifférent' }}
Satellite{% if s.satellite_url %}{% if 'sat1' in s.satellite_url %}SAT1 (DMZ){% elif 'sat2' in s.satellite_url %}SAT2 (LAN){% else %}{{ s.satellite_url }}{% endif %}{% else %}N/A{% endif %}
+ {% if s.patching_notes %}
+
+
⚠ Particularités de patching
+
{{ s.patching_notes }}
+
+ {% endif %}
+
Responsables
diff --git a/app/templates/patching_iexec.html b/app/templates/patching_iexec.html
index c05726d..a49dd45 100644
--- a/app/templates/patching_iexec.html
+++ b/app/templates/patching_iexec.html
@@ -28,6 +28,15 @@
border-color:#f59e0b; animation: pulseIt 1.2s infinite; }
@keyframes pulseIt { 0%,100% { opacity:1 } 50% { opacity:.55 } }
+{# Banner cumulé des particularités (servers avec patching_notes) #}
+{% set has_notes = rows|selectattr('patching_notes')|list %}
+{% if has_notes %}
+
+
⚠ Particularités de patching pour {{ has_notes|length }} serveur(s)
+
Clique sur le badge ⚠ note à côté du nom du serveur pour voir la procédure spécifique.
+
+{% endif %}
+
1 Vérif
→
@@ -86,12 +95,24 @@
{% for r in rows %}
+ data-pct-confirmed="{{ '1' if r.pct_confirmed_at else '0' }}"
+ data-skip-first-reboot="{{ '1' if r.skip_first_reboot else '0' }}"
+ data-cluster-name="{{ r.cluster_name or '' }}"
+ data-reboot-delay-min="{{ r.reboot_delay_min_minutes or 0 }}">
| {{ r.asset_name }}{% if r.pct_required %}
{% if r.pct_confirmed_at %}✅ PCT ok{% else %}⚠ Prév PCT à faire{% endif %}
+ {% endif %}{% if r.skip_first_reboot %}
+ ⏭ skip 1st reboot
+ {% endif %}{% if r.patching_notes %}
+
+ {% endif %}{% if r.cluster_name and r.reboot_delay_min_minutes and r.reboot_delay_min_minutes > 0 %}
+ ⏱ {{ r.reboot_delay_min_minutes }}min
{% endif %} |
{{ r.hostname or '–' }} |
{{ r.environnement or '' }} |
@@ -833,4 +854,29 @@ function toggleDetails(){
});
})();
+
+{# Modal de patching_notes (affiché au clic sur badge ⚠ note) #}
+
+
+
+
⚠ Particularités de patching —
+
+
+
+
+
+
+
{% endblock %}
diff --git a/migrate_patching_notes_20260507.sql b/migrate_patching_notes_20260507.sql
new file mode 100644
index 0000000..80422ef
--- /dev/null
+++ b/migrate_patching_notes_20260507.sql
@@ -0,0 +1,317 @@
+-- Migration : particularités patching extraites du wiki SANEF "Procédures cas spécifiques"
+-- - servers.skip_first_reboot : boolean (default false) — saute le 1er reboot post-patch
+-- - servers.patching_notes : text — mémo opérateur (markdown), affiché dans iexec
+-- - server_clusters.reboot_delay_min_minutes : int default 0 — délai minimum entre reboots de membres
+-- Idempotent.
+-- Source : DokuWiki SANEF "Procédures de mises à jour Linux cas spécifiques"
+
+-- ─── 1) Nouvelles colonnes ────────────────────────────────────
+
+ALTER TABLE public.servers
+ ADD COLUMN IF NOT EXISTS skip_first_reboot boolean NOT NULL DEFAULT false,
+ ADD COLUMN IF NOT EXISTS patching_notes text;
+
+ALTER TABLE public.server_clusters
+ ADD COLUMN IF NOT EXISTS reboot_delay_min_minutes integer NOT NULL DEFAULT 0;
+
+-- ─── 2) skip_first_reboot — TPV1 (info JFCRABS) ──────────────
+
+UPDATE public.servers SET skip_first_reboot = true
+ WHERE LOWER(hostname::text) IN ('vptraatpf1', 'vptraatpf2');
+
+-- ─── 3) Backfill patching_notes par groupe applicatif ────────
+
+-- ASM stockage : exclude kernel* déjà fait, on ajoute un mémo sur la procédure grubby
+UPDATE public.servers SET patching_notes =
+$mn$**ASM Oracle stockage** — kernel* exclu (kmod-redhat-oracleasm doit suivre la version d'ASM).
+
+Post-MAJ : vérifier le kernel par défaut avec `sudo grubby --info=ALL`.
+Si le nouveau kernel a été installé, remettre l'ancien comme kernel de boot par défaut :
+`sudo grubby --set-default /boot/vmlinuz-`
+
+Si apparition de `kmod-redhat-oracleasm-kernel_` : exclure le kernel à la prochaine MAJ.$mn$
+WHERE LOWER(hostname::text) IN (
+ 'lamarrac1','lamarrac2','lamarrac3','lamarrac4',
+ 'srdsibora1','srdsibora2','lptrabgas1',
+ 'lragtbpla1','lpaiigrid1',
+ 'lamaprac1','lamaprac2','lamaprac3','lamaprac4',
+ 'lampadp1','lampadp2','lampadv1','lampasu1','lampasu2',
+ 'lampcrm1','lampdec1','lampfin1','lampfin3','lampoct1','lamppea1',
+ 'lamppla1','lamppla2','lamrsip1','lamrsip2','lamtinf1','lamtpfe1',
+ 'lpagtbpla1','lpemvbaemv1','lpemvbpemv1','lpemvbpemv2',
+ 'lpsimbpfe1','lraiibora1','lremvbremv1','lremvbremv2',
+ 'rmila150','rmilgmo3','rmilsas1',
+ 'rsmiged1','rsmigmo1','rsmipat1','rsmisar2',
+ 'srtrabgas1','vpemvgrid1','vpnapamed1'
+);
+
+-- TPV1 — pas de 1er reboot
+UPDATE public.servers SET patching_notes =
+$mn$**TPV1 — pas de premier reboot pour les pf** (info JFCRABS).
+
+`skip_first_reboot=true` est positionné, le workflow iexec saute automatiquement le 1er reboot.$mn$
+WHERE LOWER(hostname::text) IN ('vptraatpf1', 'vptraatpf2');
+
+-- HAproxy FL
+UPDATE public.servers SET patching_notes =
+$mn$**HAproxy Flux Libre** — exclusions du domaine Flux Libre s'appliquent
+(`*podman* run* *container* *sdcss-kmod*` uniquement).$mn$
+WHERE LOWER(hostname::text) IN ('vrpeahbst1','vppeahbst1','vipeahbst1');
+
+-- Covoiturage
+UPDATE public.servers SET patching_notes =
+$mn$**Covoiturage** — services métier ne se relancent pas automatiquement.
+
+Post-reboot :
+```bash
+systemctl restart covoit*
+systemctl start covoit-load # si non démarré
+```$mn$
+WHERE LOWER(hostname::text) = 'vppeaaphov1';
+
+-- SI Patrimoine 1.0 - sipat (vrpatbsip1)
+UPDATE public.servers SET patching_notes =
+$mn$**SI Patrimoine** — patcher CE SERVEUR AVANT vrpataels1 (sipat doit être arrêté).
+
+Pre-patch :
+```bash
+sudo su - sipat -c "/applis/sipat/stop.bash"
+# Commenter dans crontab user sipat :
+# * * * * * /applis/sipat/start.bash sipat &>/dev/null
+```
+
+Post-patch : redémarrer, vérifier que sipat n'est PAS lancé, puis patcher vrpataels1.
+
+Après vrpataels1 OK : décommenter crontab + redémarrer.$mn$
+WHERE LOWER(hostname::text) = 'vrpatbsip1';
+
+UPDATE public.servers SET patching_notes =
+$mn$**SI Patrimoine — Elasticsearch** — patcher APRÈS vrpatbsip1 (sipat arrêté).
+
+Post-patch : vérifier que Elasticsearch est toujours fonctionnel.
+
+Vérifier présence des certificats CA recette dans `/usr/lib/jvm/java/lib/security/cacerts` :
+```bash
+keytool -list -keystore /usr/lib/jvm/java/lib/security/cacerts | grep -i recette
+```
+Doit afficher `acdistributionrecette` et `acracinerecette`. Sinon importer via `/etc/pki/ca-trust/source/anchors/recette-ac.pem` puis `sudo update-ca-trust`.$mn$
+WHERE LOWER(hostname::text) = 'vrpataels1';
+
+UPDATE public.servers SET patching_notes =
+$mn$**SI Patrimoine — Kibana** — *kibana* déjà dans la base d'exclusions globale, rien à faire.$mn$
+WHERE LOWER(hostname::text) = 'vppatakib1';
+
+-- Talend
+UPDATE public.servers SET patching_notes =
+$mn$**Talend** — relancer Talend depuis l'utilisateur Talend en passant par le compte root.$mn$
+WHERE LOWER(hostname::text) = 'vdechatal1';
+
+-- Scoop (Debian — hors yum)
+UPDATE public.servers SET patching_notes =
+$mn$**Scoop Debian** — HORS workflow yum standard. Utilise `apt-mark hold` :
+
+```bash
+sudo apt-mark hold *mongodb* *sql* *postgres* *mariadb* *oracle* *centreon* *pgdg* *php* *java* *containers* *docker* *qwserver* *ansible* *node* *tomcat* *jupyter*
+sudo apt-get update && sudo apt-get upgrade
+sudo apt-mark unhold *mongodb* *sql* *postgres* *mariadb* *oracle* *centreon* *pgdg* *php* *java* *containers* *docker* *qwserver* *ansible* *node* *tomcat* *jupyter*
+```
+
+vrscobpf1 : post-reboot, java/scoop-ptf nécessite connexion compte `scoop` :
+```bash
+sudo su -l scoop
+sudo systemctl status scoop-ptf
+```$mn$
+WHERE LOWER(hostname::text) IN ('vrscobpf1','vrscouevs1');
+
+-- Scoop CentOS (vpscoav2x1)
+UPDATE public.servers SET patching_notes =
+$mn$**Scoop CentOS** — arrêter les containers Docker AVANT le patch :
+
+```bash
+sudo docker container ls # lister
+sudo docker container stop ... # arrêter
+# patch yum normal (avec exclude *containers*)
+sudo docker container start ... # redémarrer
+```
+
+Pour vrscoav2x1 : laisser IP forwarding activé (`net.ipv4.ip_forward = 1`) sinon métier KO.$mn$
+WHERE LOWER(hostname::text) = 'vpscoav2x1';
+
+-- DATI - composants pm2/tomcat
+UPDATE public.servers SET patching_notes =
+$mn$**DATI — composant FrontalIP (node/pm2)** — métier non auto post-reboot :
+```bash
+sudo su - node -c "pm2 start /applis/frontal-ip/frontal.js -i 2"
+```$mn$
+WHERE LOWER(hostname::text) IN ('vrexpadat3','vpexpadat3');
+
+UPDATE public.servers SET patching_notes =
+$mn$**DATI — Tomcat** — métier non auto post-reboot :
+```bash
+sudo su - tomcat -c "/applis/tomcat/bin/catalina.sh start"
+```$mn$
+WHERE LOWER(hostname::text) IN ('vpexpadat1','vpexpadat2');
+
+-- COMMVAULT
+UPDATE public.servers SET patching_notes =
+$mn$**COMMVAULT** — pré-patch obligatoire :
+
+1. Mettre le serveur en mode maintenance dans la console Commvault.
+2. Suspendre les jobs qui utilisent le media agent à patcher.
+
+Vérifier post-patch que les jobs reprennent correctement avant de désactiver le mode maintenance.$mn$
+WHERE LOWER(hostname::text) IN (
+ 'spbckamag1','spbckamag2','spbckamag3','spbckamag4',
+ 'spbckamag5','spbckamag6','spbckamag7','spbckamag8',
+ 'spbckmmag1','spbckmmag2'
+);
+
+-- Masterparc
+UPDATE public.servers SET patching_notes =
+$mn$**Masterparc — kmeihm via pm2** — relance manuelle si non auto :
+```bash
+sudo su - root -c "/usr/local/bin/pm2 list"
+sudo su - root -c "/usr/local/bin/pm2 start 'cd /applis/kme/kmeihm ; node ./app.js' --name kmeihm"
+sudo su - root -c "/usr/local/bin/pm2 show kmeihm"
+```$mn$
+WHERE LOWER(hostname::text) = 'vrtrabkme1';
+
+-- SPLUNK
+UPDATE public.servers SET patching_notes =
+$mn$**Splunk Enterprise** — procédure RPM spéciale, NE PAS patcher avec workflow yum standard.
+
+Cf wiki SANEF section "SPLUNK" :
+1. `sudo /applis/splunk/bin/splunk stop`
+2. `sudo cp -r /applis/splunk/ /applis/splunk-avant-maj` (rollback)
+3. `sudo rpm -U --prefix=/applis /root/splunk-X.Y.Z.x86_64.rpm`
+4. `sudo /applis/splunk/bin/splunk start` + accepter licence + migration config$mn$
+WHERE LOWER(hostname::text) IN ('spsecalog1','spemvalog1');
+
+-- Site institutionnel HAproxy + backends
+UPDATE public.servers SET patching_notes =
+$mn$**Site institutionnel — HAproxy + backends www.sanef.com**
+
+Pré-patch obligatoire : rotation des backends dans `/etc/haproxy/haproxy.cfg` sur vpintaprx2.
+
+Avant de patcher vpintaweb2 : commenter `app1 192.168.20.36:80` (vpintaweb2)
+Avant de patcher vpintaweb3 : commenter `app2 192.168.20.37:80` (vpintaweb3)
+
+Test config + reload après chaque modif :
+```bash
+sudo haproxy -f /etc/haproxy/haproxy.cfg -c
+sudo systemctl restart haproxy.service
+```
+
+Post-patch : faire valider le métier par RA Arnaud Meillon / Bertrand Letendard avant de réactiver les 2 backends.
+
+Ne pas MAJ les paquets `*node*` (déjà dans la base d'exclusions).$mn$
+WHERE LOWER(hostname::text) IN ('vpintaprx2','vpintaweb2','vpintaweb3');
+
+-- Centreon polleurs
+UPDATE public.servers SET patching_notes =
+$mn$**Centreon — patcher 1 serveur à la fois et valider avant le suivant.**
+
+Post-reboot : vérifier services :
+```bash
+sudo systemctl status centengine
+sudo systemctl status centreontrapd # seulement s'il existe
+```
+Démarrer manuellement si non actifs.$mn$
+WHERE LOWER(hostname::text) IN (
+ 'vpaiiapol1','vpaiiapol2','vpaiiapol3','vpaiiapol4','vpaiiapol5',
+ 'vpaiiapol6','vpaiiapol7','vpaiiapol9','vpaiiapol10'
+);
+
+-- Sextan
+UPDATE public.servers SET patching_notes =
+$mn$**Sextan** — vérifier IP forwarding (`sysctl net.ipv4.ip_forward` doit être 1).
+
+Si à 0 : éditer `/etc/sysctl.conf`, mettre `net.ipv4.ip_forward=1` puis `sudo sysctl -p /etc/sysctl.conf`.
+
+⚠ ATTENTION : espacer les reboots de minimum 10 min entre chaque serveur Sextan (sinon perte de données). Le cluster Sextan a `reboot_delay_min_minutes=10`.$mn$
+WHERE LOWER(hostname::text) IN (
+ 'vdameasxt1','vdameasxt2','vdameasxt3','vdameasxt4',
+ 'vrameahtp1','vrameahtp2',
+ 'vrameasxt1','vrameasxt2','vrameasxt3','vrameasxt4'
+);
+
+-- OCTAN - vrameaoct1
+UPDATE public.servers SET patching_notes =
+$mn$**OCTAN — applis ne se relancent pas auto post-reboot** :
+```bash
+sudo su - tomcat -c 'pm2 start /applis/decisionnelpeage/backend/index.js --name backend'
+sudo /bin/bash /applis/base-recette/octan-edition-4.8/bin/catalina.sh start &
+sudo /bin/bash /applis/base-recette/octan-services/bin/control.sh start &
+sudo /bin/bash /applis/base-recette/octan-services-a150/bin/run.sh start &
+sudo /bin/bash /applis/base-qualif/octan-edition-4.8/bin/catalina.sh start &
+sudo /bin/bash /applis/base-qualif/octan-services/bin/control.sh start &
+```$mn$
+WHERE LOWER(hostname::text) = 'vrameaoct1';
+
+-- PAIPOR
+UPDATE public.servers SET patching_notes =
+$mn$**PAIPOR** — pré-patch : mettre site en maintenance.
+
+Sur vpcliabosp1, éditer `/applis/sanef/forsAccess.override.json` :
+```json
+"sanef": { "maintenanceEspaceClient": true, ... }
+```
+Post-patch : remettre `false`.
+
+Ne pas MAJ `python2-certbot` ni `*certbot*` (déjà dans base exclusions).$mn$
+WHERE LOWER(hostname::text) = 'vpsimapai1';
+
+-- Gaspar
+UPDATE public.servers SET patching_notes =
+$mn$**Gaspar** — applis Java non auto post-reboot. Relance manuelle Tomcat (cf wiki).$mn$
+WHERE LOWER(hostname::text) = 'vrtraagas1';
+
+-- Postgres BDD
+UPDATE public.servers SET patching_notes =
+$mn$**Postgres** — service ne se lance pas automatiquement post-reboot :
+```bash
+sudo systemctl start postgres*
+```
+Pour vraiibpgs3 (réplication de vraiibpgs2) : si problème, demander à Nadine BENARD de redémarrer le métier.$mn$
+WHERE LOWER(hostname::text) IN ('vraiibpgs1','vraiibpgs2','vraiibpgs3');
+
+-- Oracle OEM
+UPDATE public.servers SET patching_notes =
+$mn$**Oracle OEM** — relancer la couche OMS manuellement post-reboot, en utilisateur `oracle` :
+```bash
+/applis/oracle/oem/middleware134/bin/emctl start oms
+```$mn$
+WHERE LOWER(hostname::text) = 'lpaiigrid1';
+
+-- SMTP Relay
+UPDATE public.servers SET patching_notes =
+$mn$**SMTP Relay** — post-reboot, vérifier services :
+```bash
+systemctl status postfix
+systemctl status keepalived
+```
+
+Lancer le check_smtp et confirmer la réception du mail à Joel CAVE :
+```bash
+cd /usr/local/bin && ./check_smtp.sh
+```$mn$
+WHERE LOWER(hostname::text) IN ('vpdsismtp1','vpdsismtp2');
+
+-- ─── 4) reboot_delay_min_minutes sur cluster Sextan ──────────
+
+-- Création du cluster Sextan s'il n'existe pas (idempotent via ON CONFLICT)
+INSERT INTO public.server_clusters (name, description, reboot_strategy, reboot_delay_min_minutes)
+VALUES ('Sextan', 'Cluster Sextan — espacer les reboots de 10 min minimum (perte données sinon)',
+ 'sequential', 10)
+ON CONFLICT (name) DO UPDATE
+ SET reboot_delay_min_minutes = 10,
+ description = EXCLUDED.description;
+
+-- Rattache les serveurs Sextan au cluster
+UPDATE public.servers s
+ SET cluster_id = (SELECT id FROM server_clusters WHERE name='Sextan')
+ WHERE LOWER(s.hostname::text) IN (
+ 'vdameasxt1','vdameasxt2','vdameasxt3','vdameasxt4',
+ 'vrameahtp1','vrameahtp2',
+ 'vrameasxt1','vrameasxt2','vrameasxt3','vrameasxt4'
+ );