Compare commits
3 Commits
637798b2b9
...
e5ce3b2ec9
| Author | SHA1 | Date | |
|---|---|---|---|
| e5ce3b2ec9 | |||
| fb29b59625 | |||
| 1c3cb60153 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,15 +1,9 @@
|
||||
build/
|
||||
dist/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.spec.bak
|
||||
*.log
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
*.db
|
||||
*.sqlite
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# 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)
|
||||
5585
build/patch_manager_v2/Analysis-00.toc
Normal file
5585
build/patch_manager_v2/Analysis-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
3062
build/patch_manager_v2/EXE-00.toc
Normal file
3062
build/patch_manager_v2/EXE-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
3040
build/patch_manager_v2/PKG-00.toc
Normal file
3040
build/patch_manager_v2/PKG-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
BIN
build/patch_manager_v2/PYZ-00.pyz
Normal file
BIN
build/patch_manager_v2/PYZ-00.pyz
Normal file
Binary file not shown.
2093
build/patch_manager_v2/PYZ-00.toc
Normal file
2093
build/patch_manager_v2/PYZ-00.toc
Normal file
File diff suppressed because it is too large
Load Diff
BIN
build/patch_manager_v2/SANEF_Patch_Manager.pkg
Normal file
BIN
build/patch_manager_v2/SANEF_Patch_Manager.pkg
Normal file
Binary file not shown.
BIN
build/patch_manager_v2/base_library.zip
Normal file
BIN
build/patch_manager_v2/base_library.zip
Normal file
Binary file not shown.
66
build/patch_manager_v2/warn-patch_manager_v2.txt
Normal file
66
build/patch_manager_v2/warn-patch_manager_v2.txt
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
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)
|
||||
24881
build/patch_manager_v2/xref-patch_manager_v2.html
Normal file
24881
build/patch_manager_v2/xref-patch_manager_v2.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dist/SANEF_Patch_Manager.7z
vendored
Normal file
BIN
dist/SANEF_Patch_Manager.7z
vendored
Normal file
Binary file not shown.
BIN
dist/SANEF_Patch_Manager.exe
vendored
Normal file
BIN
dist/SANEF_Patch_Manager.exe
vendored
Normal file
Binary file not shown.
@ -670,15 +670,72 @@ def build_fqdn(server, env):
|
||||
return [server, f"{server}.sanef.groupe"]
|
||||
|
||||
def load_key(keyfile):
|
||||
if not keyfile or not os.path.exists(keyfile.strip('"').strip("'")):
|
||||
"""Charge une clé SSH privée. Retourne l'objet PKey ou None.
|
||||
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
|
||||
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]:
|
||||
try:
|
||||
return cls.from_private_key_file(keyfile)
|
||||
except Exception:
|
||||
except Exception as ex:
|
||||
last_ex = ex
|
||||
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
|
||||
load_key.last_error = None
|
||||
|
||||
def ssh_connect(server, env, settings, pkey, pkey2, cyb_password=None):
|
||||
"""Connexion SSH avec fallback FQDN et clé. Retourne (client, hostname_effectif)"""
|
||||
@ -2294,8 +2351,28 @@ class PatchManagerV2:
|
||||
self._reload_keys()
|
||||
|
||||
def _reload_keys(self):
|
||||
self.pkey = load_key(self.settings.get("keyfile",""))
|
||||
self.pkey2 = load_key(self.settings.get("keyfile2",""))
|
||||
errors = []
|
||||
kf1 = self.settings.get("keyfile", "")
|
||||
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
|
||||
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
paramiko>=3.0
|
||||
openpyxl>=3.1
|
||||
requests>=2.31
|
||||
pyVmomi>=8.0
|
||||
ttkbootstrap>=1.10
|
||||
pyinstaller>=6.0
|
||||
Loading…
Reference in New Issue
Block a user