Compare commits

..

No commits in common. "e5ce3b2ec90dc012f3e8c825374deb3b4807d777" and "637798b2b923137aa7b1493ba7345bc421441fdc" have entirely different histories.

15 changed files with 10 additions and 38847 deletions

6
.gitignore vendored
View File

@ -1,9 +1,15 @@
build/
dist/
__pycache__/ __pycache__/
*.pyc *.pyc
*.pyo *.pyo
*.pyd *.pyd
*.spec.bak
*.log
.venv/ .venv/
venv/ venv/
.env .env
*.db
*.sqlite
.idea/ .idea/
.vscode/ .vscode/

View File

@ -1,33 +0,0 @@
# SANEF Patch Manager v2
Gestion du patching Linux via Excel + SSH + vSphere (PyQt/Tkinter + PyInstaller).
## Prérequis
- Python 3.11+ sur Windows
- Accès réseau proxy SANEF : `http://proxy.sanef.fr:8080`
## Installation des dépendances
```
py -m pip install -r requirements.txt --proxy http://proxy.sanef.fr:8080
```
## Exécution depuis les sources
```
py patch_manager_v2.py
```
## Build de l'exécutable (PyInstaller)
```
py -m PyInstaller patch_manager_v2.spec --clean --noconfirm
```
L'exe généré : `dist/SANEF_Patch_Manager.exe`
## Archive packagée
`dist/SANEF_Patch_Manager.7z` contient l'exe prêt à distribuer.
## Fichiers
- `patch_manager_v2.py` — code principal
- `patch_manager_v2.spec` — config PyInstaller
- `patch_manager_v2_backup_before_theme.py` — backup avant refonte thème
- `requirements.txt` — dépendances Python
- `build/`, `dist/` — artefacts PyInstaller (inclus dans le repo pour récup rapide)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +0,0 @@
This file lists modules PyInstaller was not able to find. This does not
necessarily mean these modules are required for running your program. Both
Python's standard library and 3rd-party Python packages often conditionally
import optional modules, some of which may be available only on certain
platforms.
Types of import:
* top-level: imported at the top-level - look at these first
* conditional: imported within an if-statement
* delayed: imported within a function
* optional: imported within a try-except-statement
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!
missing module named pyimod02_importers - imported by C:\Users\netadmin\AppData\Local\Programs\Python\Python312\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
missing module named urllib.urlopen - imported by urllib (delayed, optional), lxml.html (delayed, optional)
missing module named urllib.urlencode - imported by urllib (delayed, optional), lxml.html (delayed, optional)
missing module named posix - imported by os (conditional, optional), shutil (conditional), importlib._bootstrap_external (conditional), posixpath (optional)
missing module named resource - imported by posix (top-level)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), getpass (delayed), netrc (delayed, conditional), http.server (delayed, optional)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
missing module named 'numpy.typing' - imported by PIL._typing (conditional, optional)
missing module named _posixsubprocess - imported by subprocess (conditional)
missing module named fcntl - imported by subprocess (optional), pty (delayed, optional), invoke.runners (optional), invoke.terminals (conditional), paramiko.agent (delayed)
missing module named olefile - imported by PIL.FpxImagePlugin (top-level), PIL.MicImagePlugin (top-level)
missing module named defusedxml - imported by openpyxl.xml (delayed, optional), PIL.Image (optional)
missing module named OpenSSL - imported by pyVim.sso (top-level)
missing module named htmlentitydefs - imported by lxml.html.soupparser (optional)
missing module named BeautifulSoup - imported by lxml.html.soupparser (optional)
missing module named bs4 - imported by lxml.html.soupparser (optional)
missing module named urlparse - imported by pyVmomi.five (conditional), lxml.ElementInclude (optional), lxml.html.html5parser (optional)
missing module named urllib2 - imported by lxml.ElementInclude (optional), lxml.html.html5parser (optional)
missing module named 'html5lib.treebuilders' - imported by lxml.html.html5parser (top-level)
missing module named html5lib - imported by lxml.html._html5builder (top-level), lxml.html.html5parser (top-level)
missing module named 'cython.cimports' - imported by lxml.html.diff (optional)
missing module named cython - imported by lxml.html.diff (optional), lxml.html._difflib (optional)
missing module named lxml_html_clean - imported by lxml.html.clean (optional)
excluded module named unittest - imported by doctest (top-level)
missing module named termios - imported by getpass (optional), tty (top-level), invoke.runners (optional), invoke.terminals (conditional)
missing module named vms_lib - imported by platform (delayed, optional)
missing module named 'java.lang' - imported by platform (delayed, optional)
missing module named java - imported by platform (delayed)
missing module named _winreg - imported by platform (delayed, optional)
missing module named readline - imported by cmd (delayed, conditional, optional), code (delayed, conditional, optional), pdb (delayed, optional)
missing module named cssselect - imported by lxml.cssselect (optional)
missing module named httplib - imported by pyVmomi.five (conditional), pyVmomi.SoapAdapter (conditional)
missing module named 'defusedxml.ElementTree' - imported by openpyxl.xml.functions (conditional)
missing module named numpy - imported by openpyxl.compat.numbers (optional)
missing module named _scproxy - imported by urllib.request (conditional)
missing module named pyVmomi.vmodl - imported by pyVmomi (top-level), pyVim.connect (top-level)
missing module named Cookie - imported by pyVmomi.SoapAdapter (conditional)
missing module named StringIO - imported by pyVmomi.SoapAdapter (conditional)
missing module named elementtree - imported by pyVim.connect (optional)
missing module named openpyxl.tests - imported by openpyxl.reader.excel (optional)
missing module named yaml - imported by invoke.vendor.yaml.cyaml (top-level), invoke.util (optional)
missing module named lexicon - imported by invoke.util (optional), invoke.parser.parser (optional), invoke.parser.context (optional)
missing module named fluidity - imported by invoke.parser.parser (optional)
missing module named 'unittest.mock' - imported by invoke.context (top-level)
missing module named sspi - imported by paramiko.ssh_gss (optional)
missing module named sspicon - imported by paramiko.ssh_gss (optional)
missing module named pywintypes - imported by paramiko.ssh_gss (optional)
missing module named gssapi - imported by paramiko.ssh_gss (optional)

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@ -670,72 +670,15 @@ def build_fqdn(server, env):
return [server, f"{server}.sanef.groupe"] return [server, f"{server}.sanef.groupe"]
def load_key(keyfile): def load_key(keyfile):
"""Charge une clé SSH privée. Retourne l'objet PKey ou None. if not keyfile or not os.path.exists(keyfile.strip('"').strip("'")):
Si format PuTTY (.ppk), tente conversion auto via puttygen vers OpenSSH PEM.
Stocke le détail de l'échec dans load_key.last_error pour affichage UI."""
load_key.last_error = None
if not keyfile:
return None return None
keyfile = keyfile.strip('"').strip("'") keyfile = keyfile.strip('"').strip("'")
if not os.path.exists(keyfile):
load_key.last_error = f"Fichier clé introuvable : {keyfile}"
return None
# Détection format PuTTY (.ppk)
try:
with open(keyfile, "r", encoding="utf-8", errors="ignore") as f:
first_line = f.readline().strip()
except Exception as ex:
load_key.last_error = f"Lecture impossible : {ex}"
return None
is_ppk = first_line.startswith("PuTTY-User-Key-File-")
if is_ppk:
# Paramiko ne lit pas .ppk nativement → tenter conversion auto via puttygen
converted = keyfile + ".openssh"
puttygen_paths = [
r"C:\Program Files\PuTTY\puttygen.exe",
r"C:\Program Files (x86)\PuTTY\puttygen.exe",
"puttygen", # si dans PATH
"puttygen.exe",
]
import subprocess
converted_ok = False
for pg in puttygen_paths:
try:
subprocess.run(
[pg, keyfile, "-O", "private-openssh", "-o", converted],
check=True, capture_output=True, timeout=10,
)
converted_ok = True
break
except (FileNotFoundError, subprocess.SubprocessError, OSError):
continue
if not converted_ok:
load_key.last_error = (
f"Format PuTTY (.ppk) détecté pour {os.path.basename(keyfile)}. "
f"Paramiko ne lit pas ce format nativement et puttygen.exe est introuvable. "
f"Convertir manuellement : "
f'puttygen "{keyfile}" -O private-openssh -o "{keyfile}.openssh" '
f"puis pointer la conf vers le fichier converti."
)
return None
keyfile = converted
# Tenter chargement PEM/OpenSSH (multi-format)
last_ex = None
for cls in [paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey]: for cls in [paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey]:
try: try:
return cls.from_private_key_file(keyfile) return cls.from_private_key_file(keyfile)
except Exception as ex: except Exception:
last_ex = ex
continue continue
load_key.last_error = (
f"Aucun loader paramiko n'a pu lire {os.path.basename(keyfile)} "
f"(RSA / Ed25519 / ECDSA tentés). Dernière erreur : {last_ex}"
)
return None return None
load_key.last_error = None
def ssh_connect(server, env, settings, pkey, pkey2, cyb_password=None): def ssh_connect(server, env, settings, pkey, pkey2, cyb_password=None):
"""Connexion SSH avec fallback FQDN et clé. Retourne (client, hostname_effectif)""" """Connexion SSH avec fallback FQDN et clé. Retourne (client, hostname_effectif)"""
@ -2351,28 +2294,8 @@ class PatchManagerV2:
self._reload_keys() self._reload_keys()
def _reload_keys(self): def _reload_keys(self):
errors = [] self.pkey = load_key(self.settings.get("keyfile",""))
kf1 = self.settings.get("keyfile", "") self.pkey2 = load_key(self.settings.get("keyfile2",""))
if kf1:
self.pkey = load_key(kf1)
if self.pkey is None and load_key.last_error:
errors.append(f"Clé 1 ({kf1}) :\n{load_key.last_error}")
else:
self.pkey = None
kf2 = self.settings.get("keyfile2", "")
if kf2:
self.pkey2 = load_key(kf2)
if self.pkey2 is None and load_key.last_error:
errors.append(f"Clé 2 ({kf2}) :\n{load_key.last_error}")
else:
self.pkey2 = None
if errors:
try:
messagebox.showwarning("Clés SSH non chargées", "\n\n".join(errors))
except Exception:
# Pas de UI dispo (lancement CLI ?), juste print
for e in errors:
print(f"[load_key] {e}")
# ========================================================================== # ==========================================================================
# ONGLET 1 — LOGIQUE # ONGLET 1 — LOGIQUE

View File

@ -1,6 +0,0 @@
paramiko>=3.0
openpyxl>=3.1
requests>=2.31
pyVmomi>=8.0
ttkbootstrap>=1.10
pyinstaller>=6.0