Corrige la SDDL invalide (erreur 1804) produite par le fix précédent quand le service porte une clause S: (SACL/étiquette d'intégrité), et les accents corrompus dans les messages d'erreur Windows (mauvais codepage/encodage lors de la capture de sortie). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
332 lines
17 KiB
Makefile
332 lines
17 KiB
Makefile
## ──────────────────────────────────────────────
|
|
## WGSecure (WGS) — Makefile
|
|
## Compilation Linux & Windows via PyInstaller
|
|
## ──────────────────────────────────────────────
|
|
|
|
APP := wgsecure
|
|
VERSION := 0.7.9
|
|
VENV := .venv
|
|
PYTHON := $(VENV)/bin/python3
|
|
PIP := $(VENV)/bin/pip
|
|
SRC := main.py
|
|
DIST := dist
|
|
BUILD := build
|
|
ICON := /tmp/wgsecure_icon_build.png
|
|
|
|
# Options PyInstaller communes aux deux plateformes
|
|
PI_OPTS := \
|
|
--noconfirm \
|
|
--clean \
|
|
--name $(APP) \
|
|
--hidden-import pyotp \
|
|
--hidden-import qrcode \
|
|
--hidden-import qrcode.image.pil \
|
|
--hidden-import PIL \
|
|
--hidden-import PIL.Image \
|
|
--hidden-import cryptography \
|
|
--hidden-import cryptography.hazmat.primitives.asymmetric.x25519 \
|
|
--collect-submodules PyQt6
|
|
|
|
.PHONY: all linux windows installer-deps release publish version changelog install install-gnome uninstall-gnome run run-admin icon reset-password setup-sudoers check-privileges clean clean-all help venv
|
|
|
|
# ── Cible par défaut ────────────────────────────────────────────────────────
|
|
all: linux
|
|
|
|
# ── Environnement virtuel ────────────────────────────────────────────────────
|
|
# Le venv est reconstruit si la version de Python du système a changé : un venv
|
|
# créé pour python3.13 pointe vers .venv/lib/python3.13/site-packages, que
|
|
# python3.14 n'ouvre pas. L'interpréteur démarrait alors sans aucune de ses
|
|
# dépendances (« No module named PyQt6 ») sans que make ne le remarque.
|
|
venv:
|
|
@if [ -x $(PYTHON) ] && \
|
|
[ "$$($(PYTHON) -c 'import sys;print("%d.%d"%sys.version_info[:2])' 2>/dev/null)" \
|
|
= "$$(python3 -c 'import sys;print("%d.%d"%sys.version_info[:2])')" ] && \
|
|
$(PYTHON) -c 'import PyQt6' >/dev/null 2>&1; then \
|
|
echo " ✅ Environnement virtuel valide ($(VENV))"; \
|
|
else \
|
|
echo " ♻️ (Re)création de l'environnement virtuel $(VENV)…"; \
|
|
rm -rf $(VENV); \
|
|
python3 -m venv $(VENV); \
|
|
$(PIP) install --upgrade pip --quiet; \
|
|
$(PIP) install -r requirements.txt --quiet; \
|
|
fi
|
|
|
|
# ── Génération de l'icône PNG (utilisée par PyInstaller) ────────────────────
|
|
icon:
|
|
@$(PYTHON) -c "\
|
|
import sys; sys.path.insert(0,'.');\
|
|
from PyQt6.QtWidgets import QApplication; app=QApplication(sys.argv);\
|
|
from app.ui.icons import icon_app;\
|
|
pix=icon_app().pixmap(256,256); pix.save('$(ICON)','PNG');\
|
|
print(' Icône générée →', '$(ICON)')" 2>/dev/null
|
|
@test -f $(ICON) || (echo " ❌ Génération icône échouée"; exit 1)
|
|
|
|
# ── Binaire Linux ────────────────────────────────────────────────────────────
|
|
linux: install icon
|
|
@echo ""
|
|
@echo " 🐧 Compilation Linux (PyInstaller onefile)…"
|
|
@echo ""
|
|
$(PYTHON) -m PyInstaller $(PI_OPTS) \
|
|
--onefile \
|
|
--icon=$(ICON) \
|
|
--add-data "CHANGELOG.md:." \
|
|
$(SRC)
|
|
@echo ""
|
|
@echo " ✅ Binaire Linux → $(DIST)/$(APP)"
|
|
@echo ""
|
|
|
|
# ── Binaire Windows (via Wine + Python Windows) ──────────────────────────────
|
|
# Le hook PyQt6 de pyinstaller-hooks-contrib introspecte QLibraryInfo pour
|
|
# localiser Qt6/plugins/platforms au moment du build : sous Wine cette
|
|
# introspection échoue silencieusement, donc qwindows.dll n'est jamais copié
|
|
# et l'exe plante au lancement avec « no Qt platform plugin could be
|
|
# initialized ». On force sa copie via --add-data.
|
|
WINE_QT_PLUGINS := $(HOME)/.wine/drive_c/users/$(USER)/AppData/Local/Programs/Python/Python311/Lib/site-packages/PyQt6/Qt6/plugins
|
|
|
|
windows: icon
|
|
@echo ""
|
|
@echo " 🪟 Compilation Windows (Wine + PyInstaller)…"
|
|
@echo ""
|
|
@which wine > /dev/null 2>&1 \
|
|
|| (echo " ❌ Wine non installé → sudo apt install wine"; exit 1)
|
|
@test -f "$(WINE_QT_PLUGINS)/platforms/qwindows.dll" \
|
|
|| (echo " ❌ qwindows.dll introuvable dans $(WINE_QT_PLUGINS)/platforms"; \
|
|
echo " → Installez PyQt6 dans Wine avec : make setup-wine"; exit 1)
|
|
@wine $(WINE_PYTHON) -m PyInstaller $(PI_OPTS) \
|
|
--onefile \
|
|
--windowed \
|
|
--icon=$(ICON) \
|
|
--add-data "$(WINE_QT_PLUGINS)/platforms;PyQt6/Qt6/plugins/platforms" \
|
|
--add-data "$(WINE_QT_PLUGINS)/styles;PyQt6/Qt6/plugins/styles" \
|
|
--add-data "CHANGELOG.md;." \
|
|
$(SRC) \
|
|
|| (echo ""; \
|
|
echo " ❌ Échec : Python Windows introuvable dans Wine."; \
|
|
echo " → Installez-le avec : make setup-wine"; \
|
|
echo ""; exit 1)
|
|
@echo ""
|
|
@echo " ✅ Binaire Windows → $(DIST)/$(APP).exe"
|
|
@echo ""
|
|
|
|
# ── Prérequis de l'installeur Windows (wgsecure.iss) ─────────────────────────
|
|
# WireGuard n'est pas empaqueté avec WGSecure (c'est wg.exe et le pilote
|
|
# tunnel qui manquent, pas une simple DLL) : wgsecure.iss l'installe donc en
|
|
# silencieux à partir de ce binaire, téléchargé ici une bonne fois pour
|
|
# toutes plutôt que d'imposer une étape manuelle avant chaque compilation.
|
|
WIREGUARD_URL := https://download.wireguard.com/windows-client/wireguard-installer.exe
|
|
VCREDIST_URL := https://aka.ms/vs/17/release/vc_redist.x64.exe
|
|
|
|
installer/wireguard-installer.exe:
|
|
@echo " ⬇️ Téléchargement de l'installeur WireGuard…"
|
|
@curl -L --fail -o $@ $(WIREGUARD_URL)
|
|
|
|
installer/VC_redist.x64.exe:
|
|
@echo " ⬇️ Téléchargement du Visual C++ Redistributable…"
|
|
@curl -L --fail -o $@ $(VCREDIST_URL)
|
|
|
|
installer-deps: installer/wireguard-installer.exe installer/VC_redist.x64.exe
|
|
@echo " ✅ Prérequis de l'installeur prêts dans installer/"
|
|
|
|
# ── Installation de Python dans Wine ─────────────────────────────────────────
|
|
WINE_PY_VER := 3.11.9
|
|
WINE_PY_URL := https://www.python.org/ftp/python/$(WINE_PY_VER)/python-$(WINE_PY_VER)-amd64.exe
|
|
WINE_PY_EXE := /tmp/python-$(WINE_PY_VER)-amd64.exe
|
|
WINE_PYTHON := $(HOME)/.wine/drive_c/users/$(USER)/AppData/Local/Programs/Python/Python311/python.exe
|
|
|
|
setup-wine:
|
|
@echo " 📦 Installation Python $(WINE_PY_VER) Windows dans Wine…"
|
|
@which wine > /dev/null 2>&1 \
|
|
|| (echo " ❌ wine non installé → sudo apt install wine"; exit 1)
|
|
@test -f $(WINE_PY_EXE) \
|
|
|| (echo " ⬇️ Téléchargement Python Windows…" \
|
|
&& curl -L -o $(WINE_PY_EXE) $(WINE_PY_URL))
|
|
@echo " 🔧 Installation silencieuse…"
|
|
wine $(WINE_PY_EXE) /quiet InstallAllUsers=0 PrependPath=1
|
|
@echo " 📦 Installation des dépendances…"
|
|
wine $(WINE_PYTHON) -m pip install --upgrade pip
|
|
wine $(WINE_PYTHON) -m pip install -r requirements.txt
|
|
wine $(WINE_PYTHON) -m pip install pyinstaller
|
|
@echo " ✅ Python Windows prêt dans Wine"
|
|
|
|
# ── Release (Linux renommé avec version) ─────────────────────────────────────
|
|
release: clean linux
|
|
@mv $(DIST)/$(APP) $(DIST)/$(APP)-$(VERSION)-linux-x86_64
|
|
@echo " 📦 Release → $(DIST)/$(APP)-$(VERSION)-linux-x86_64"
|
|
|
|
# ── Publication de la release sur Gitea ───────────────────────────────────────
|
|
# Rebuild l'artefact Linux (fraîcheur garantie avec VERSION), joint aussi les
|
|
# artefacts Windows présents dans dist/ s'ils ont déjà été produits pour cette
|
|
# version (binaire + installeur), puis tague et publie sur Gitea. Réutilise
|
|
# gitea_release.sh (BorisSkill) plutôt que de réimplémenter la création de
|
|
# tag/release/upload d'assets déjà gérée par ce script (jeton, détection du
|
|
# remote, notes issues du CHANGELOG).
|
|
GITEA_RELEASE_SH := $(HOME)/.claude/skills/BorisSkill/scripts/gitea_release.sh
|
|
|
|
publish: release
|
|
@test -f "$(GITEA_RELEASE_SH)" \
|
|
|| (echo " ❌ gitea_release.sh introuvable ($(GITEA_RELEASE_SH))"; \
|
|
echo " → Nécessite BorisSkill (Claude Code)."; exit 1)
|
|
@ASSETS="--asset $(DIST)/$(APP)-$(VERSION)-linux-x86_64"; \
|
|
for f in "$(DIST)/$(APP)-$(VERSION)-windows-x86_64.exe" "$(DIST)/$(APP)-$(VERSION)-setup.exe"; do \
|
|
[ -f "$$f" ] && ASSETS="$$ASSETS --asset $$f"; \
|
|
done; \
|
|
bash "$(GITEA_RELEASE_SH)" --version $(VERSION) $$ASSETS
|
|
|
|
# ── Version courante ──────────────────────────────────────────────────────────
|
|
version:
|
|
@echo "$(VERSION)"
|
|
|
|
# ── Dernière entrée du changelog ─────────────────────────────────────────────
|
|
changelog:
|
|
@awk '/^## \[/{if(n++)exit} n' CHANGELOG.md
|
|
|
|
# ── Installation GNOME (utilisateur local, sans sudo) ────────────────────────
|
|
INSTALL_BIN := $(HOME)/.local/bin
|
|
INSTALL_ICON := $(HOME)/.local/share/icons/hicolor/256x256/apps
|
|
INSTALL_APPS := $(HOME)/.local/share/applications
|
|
|
|
install-gnome: linux
|
|
@echo ""
|
|
@echo " 🐧 Installation de WGSecure pour GNOME…"
|
|
@mkdir -p $(INSTALL_BIN) $(INSTALL_ICON) $(INSTALL_APPS)
|
|
@cp $(DIST)/$(APP) $(INSTALL_BIN)/$(APP)
|
|
@chmod +x $(INSTALL_BIN)/$(APP)
|
|
@echo " ✅ Binaire → $(INSTALL_BIN)/$(APP)"
|
|
@cp $(ICON) $(INSTALL_ICON)/$(APP).png
|
|
@echo " ✅ Icône → $(INSTALL_ICON)/$(APP).png"
|
|
@printf '[Desktop Entry]\nType=Application\nName=WGSecure\nComment=WireGuard GUI avec MFA TOTP\nExec=%s\nIcon=%s\nCategories=Network;Security;\nStartupWMClass=wgsecure\nTerminal=false\n' \
|
|
"$(INSTALL_BIN)/$(APP)" "$(APP)" \
|
|
> $(INSTALL_APPS)/$(APP).desktop
|
|
@echo " ✅ Lanceur → $(INSTALL_APPS)/$(APP).desktop"
|
|
@gtk-update-icon-cache -f -t $(HOME)/.local/share/icons/hicolor 2>/dev/null || true
|
|
@update-desktop-database $(INSTALL_APPS) 2>/dev/null || true
|
|
@echo ""
|
|
@echo " 🎉 Installation terminée. WGSecure est disponible dans le menu GNOME."
|
|
@echo ""
|
|
|
|
uninstall-gnome:
|
|
@echo " 🗑️ Désinstallation de WGSecure…"
|
|
@rm -f $(INSTALL_BIN)/$(APP)
|
|
@rm -f $(INSTALL_ICON)/$(APP).png
|
|
@rm -f $(INSTALL_APPS)/$(APP).desktop
|
|
@gtk-update-icon-cache -f -t $(HOME)/.local/share/icons/hicolor 2>/dev/null || true
|
|
@update-desktop-database $(INSTALL_APPS) 2>/dev/null || true
|
|
@echo " ✅ WGSecure désinstallé"
|
|
|
|
# ── Dépendances Python ────────────────────────────────────────────────────────
|
|
install: venv
|
|
$(PIP) install -r requirements.txt
|
|
$(PIP) install pyinstaller
|
|
@echo " ✅ Dépendances installées dans $(VENV)"
|
|
|
|
# ── Diagnostic des droits ────────────────────────────────────────────────────
|
|
check-privileges:
|
|
@echo ""
|
|
@echo " 🔐 Droits requis par WGSecure :"
|
|
@$(PYTHON) -c "\
|
|
import sys; sys.path.insert(0, '.'); \
|
|
from app.utils.platform_utils import privilege_report; \
|
|
rows = privilege_report(); \
|
|
print(' (exécution en root : aucun droit à configurer)') if not rows else None; \
|
|
[print(' ' + ('✅' if ok else '⚠️ ') + ' ' + name.ljust(12) + ('sans mot de passe' if ok else 'dialogue polkit à chaque appel').ljust(32) + '— ' + role) for name, ok, role in rows]; \
|
|
print() if not rows else print(chr(10) + ' ⚠️ → lancez make setup-sudoers pour supprimer les dialogues.' if any(not ok for _, ok, _ in rows) else chr(10) + ' ✅ Tout est configuré.')"
|
|
@echo ""
|
|
|
|
# ── Droits sudo wg-quick sans dialogue de mot de passe ───────────────────────
|
|
# `pkexec` (utilisé jusqu'ici) exige un agent d'authentification polkit
|
|
# graphique : absent d'un terminal simple (SSH, tmux, session sans agent), il
|
|
# reste bloqué en attente d'une réponse qui n'arrive jamais. `sudo` n'a pas ce
|
|
# problème : lancé depuis un terminal, il y invite directement le mot de
|
|
# passe, avec ou sans polkit. La cible est idempotente : elle recalcule les
|
|
# règles à chaque lancement et n'invite pour un mot de passe que si le
|
|
# contenu déjà installé diffère (donc jamais lors d'une relance sans
|
|
# changement).
|
|
setup-sudoers:
|
|
@echo ""
|
|
@echo " 🔧 Configuration sudoers (wg-quick + nettoyage réseau, sans mot de passe)…"
|
|
@$(PYTHON) -c "\
|
|
import os, subprocess, tempfile, shutil; \
|
|
user = os.environ.get('SUDO_USER') or os.environ.get('USER') or os.path.basename(os.path.expanduser('~')); \
|
|
w = lambda n: shutil.which(n) or ''; \
|
|
cmds = [c for c in ( \
|
|
w('wg-quick'), \
|
|
w('wg'), \
|
|
w('ip') + ' link delete dev *' if w('ip') else '', \
|
|
w('resolvconf') + ' -d *' if w('resolvconf') else '', \
|
|
w('resolvectl') + ' revert *' if w('resolvectl') else '', \
|
|
w('resolvectl') + ' dns *' if w('resolvectl') else '', \
|
|
w('resolvectl') + ' domain *' if w('resolvectl') else '', \
|
|
w('install') + ' -D -m 600 -o root -g root * /etc/wireguard/*' if w('install') else '', \
|
|
) if c]; \
|
|
rules = ''.join(user + ' ALL=(ALL) NOPASSWD: ' + c + chr(10) for c in cmds); \
|
|
dest = '/etc/sudoers.d/wgsecure'; \
|
|
current = subprocess.run(['sudo', '-n', 'cat', dest], capture_output=True, text=True); \
|
|
already_ok = current.returncode == 0 and current.stdout == rules; \
|
|
print(' ✅ Déjà configuré pour ' + user + ' — rien à faire.') if already_ok else None; \
|
|
import sys; sys.exit(0) if already_ok else None; \
|
|
fd, tmp = tempfile.mkstemp(prefix='wgsecure_sudoers_'); \
|
|
os.write(fd, rules.encode()); os.close(fd); os.chmod(tmp, 0o600); \
|
|
chk = subprocess.run(['visudo', '-cqf', tmp], capture_output=True); \
|
|
ok = chk.returncode == 0; \
|
|
r = subprocess.run(['sudo', 'install', '-m', '440', '-o', 'root', '-g', 'root', tmp, dest]) if ok else None; \
|
|
os.unlink(tmp); \
|
|
print(' ❌ Règles sudoers invalides, rien installé : ' + chk.stderr.decode().strip()) if not ok else \
|
|
print(' ✅ ' + dest + ' configuré pour ' + user + ' — plus de dialogue admin.' if r.returncode == 0 else ' ❌ Échec.')"
|
|
@echo ""
|
|
|
|
# ── Réinitialisation du mot de passe administrateur ──────────────────────────
|
|
reset-password:
|
|
@echo ""
|
|
@echo " ⚠️ Réinitialisation du mot de passe administrateur WGSecure…"
|
|
@$(PYTHON) -c "\
|
|
import sys; sys.path.insert(0,'.');\
|
|
from app.core.config import Config;\
|
|
cfg = Config();\
|
|
cfg.set_admin_password('');\
|
|
print(' ✅ Mot de passe supprimé — accès admin sans restriction au prochain démarrage.')"
|
|
@echo ""
|
|
|
|
# ── Lancer l'application ─────────────────────────────────────────────────────
|
|
run: venv
|
|
DISPLAY=:0 $(PYTHON) $(SRC)
|
|
|
|
run-admin: venv
|
|
DISPLAY=:0 $(PYTHON) $(SRC) --admin
|
|
|
|
# ── Nettoyage ────────────────────────────────────────────────────────────────
|
|
clean:
|
|
@echo " 🧹 Nettoyage…"
|
|
@rm -rf $(BUILD) $(DIST) *.spec
|
|
@find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null; true
|
|
@find . -name "*.pyc" -delete 2>/dev/null; true
|
|
@echo " ✅ Artefacts supprimés"
|
|
|
|
clean-all: clean
|
|
@rm -rf $(VENV)
|
|
@echo " ✅ Venv supprimé"
|
|
|
|
# ── Aide ─────────────────────────────────────────────────────────────────────
|
|
help:
|
|
@echo ""
|
|
@echo " ╔══════════════════════════════════════════════╗"
|
|
@echo " ║ 🛡️ WGSecure v$(VERSION) — Makefile ║"
|
|
@echo " ╠══════════════════════════════════════════════╣"
|
|
@echo " ║ make install Installe les dépendances ║"
|
|
@echo " ║ make linux Binaire Linux (onefile) ║"
|
|
@echo " ║ make windows Binaire Windows (Wine) ║"
|
|
@echo " ║ make installer-deps Prérequis wgsecure.iss ║"
|
|
@echo " ║ make release Linux + nommage release ║"
|
|
@echo " ║ make publish Tague + publie sur Gitea ║"
|
|
@echo " ║ make version Affiche la version ║"
|
|
@echo " ║ make changelog Dernière entrée changelog║"
|
|
@echo " ║ make setup-wine Python Windows dans Wine ║"
|
|
@echo " ║ make install-gnome Installe dans GNOME ║"
|
|
@echo " ║ make uninstall-gnome Désinstalle de GNOME ║"
|
|
@echo " ║ make setup-sudoers wg-quick sans sudo ║"
|
|
@echo " ║ make reset-password Supprime le MDP admin ║"
|
|
@echo " ║ make run Lance l'application ║"
|
|
@echo " ║ make run-admin Lance en mode admin ║"
|
|
@echo " ║ make clean Supprime les artefacts ║"
|
|
@echo " ╚══════════════════════════════════════════════╝"
|
|
@echo ""
|