Les binaires ne peuvent pas venir du dépôt Gitea : il est privé, et un jeton
embarqué dans une application distribuée est un jeton donné à tous ses
utilisateurs. Ils viennent donc du serveur WGSecure, où le contrôle d'accès
existe déjà — l'API n'est joignable que par le tunnel, et on y parvient parce
que sa clé WireGuard est déclarée. Aucun secret à embarquer.
Le serveur expose GET /api/version, qui rend la version publiée et l'empreinte
de chaque artefact, et /api/version/download/{clé} qui les sert. Le manifeste
est déposé par `make publish-updates`, avec les condensats calculés une fois à
la publication : les recalculer à chaque appel bloquerait l'API sur 125 Mo.
`ANNOUNCE=` permet d'annoncer les artefacts sous un autre numéro, pour exercer
la chaîne sans compiler une seconde version.
Rien ne s'installe sans un clic. La vérification est automatique — au montage
du tunnel puis toutes les demi-heures, car ne la faire qu'au montage laissait
un serveur momentanément injoignable annuler toute proposition pour la session
— mais le téléchargement attend le bouton. Cette application monte un VPN, et
une version défaillante qui se propagerait seule couperait l'accès d'un parc
entier sans que personne ne l'ait demandé.
L'artefact est vérifié contre son empreinte SHA-256 avant d'être mis en place,
et le fichier temporaire est détruit à la moindre anomalie. Le canal est déjà
authentifié par WireGuard ; le condensat couvre ce qu'il ne couvre pas — un
téléchargement tronqué, un disque plein, un artefact mal publié.
Sous Linux, le binaire est remplacé par renommage, sur le même système de
fichiers que sa destination. Un déplacement depuis /tmp se rabattait sur une
copie, donc sur une écriture dans l'exécutable en cours, que le noyau refuse
(ETXTBSY). Un renommage ne touche qu'une entrée de répertoire : l'ancien inode
reste vivant pour le processus, qui continue jusqu'à sa fermeture — et
l'application propose désormais de redémarrer plutôt que de le laisser deviner.
Sous Windows, l'installation passe par le service, qui tourne en LocalSystem et
écrit dans Program Files sans invite d'élévation. La commande `apply_update` ne
prend aucun paramètre : le service lit le manifeste, télécharge et vérifie
lui-même. Lui passer un fichier déjà téléchargé aurait donné à tout compte du
poste — le pipe est ouvert aux utilisateurs interactifs — le moyen de faire
exécuter ce qu'il veut avec les privilèges du système.
L'installeur orchestre le remplacement, ce qu'il ne faisait pas : les deux
exécutables à remplacer tournent au moment de la mise à jour, et Windows
verrouille l'image d'un processus vivant. Il ferme donc l'application par le
gestionnaire de redémarrage, arrête les tunnels et le service avant la copie —
en attendant la libération réelle du fichier, `sc stop` rendant la main avant
la fin de l'arrêt — et garde `restartreplace` en secours. Le service, lui,
lance l'installeur détaché et ne l'attend pas : il lui demande de se remplacer
lui-même.
Durcissement du service au passage. Le premier appelant devient propriétaire et
son SID est retenu ; ensuite seuls ce compte et les administrateurs sont
servis. Le pipe étant ouvert à tout utilisateur interactif, n'importe quel
compte du poste pouvait jusqu'ici couper le tunnel d'un autre ou poser une
règle NRPT valable pour toute la machine. Le contenu d'un `.conf` est également
vérifié — seul son chemin l'était — et les directives exécutables y sont
refusées.
Corrige enfin un défaut de compilation : Linux et Windows partageaient le
répertoire de travail de PyInstaller, que `--clean` vide au démarrage. Lancées
à la suite, les deux cibles effaçaient mutuellement leurs fichiers
intermédiaires, produisant un .exe gonflé de 44 Mo et un binaire Linux tronqué
dont l'archive ne se décompressait plus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
464 lines
24 KiB
Makefile
464 lines
24 KiB
Makefile
## ──────────────────────────────────────────────
|
|
## WGSecure (WGS) — Makefile
|
|
## Compilation Linux & Windows via PyInstaller
|
|
## ──────────────────────────────────────────────
|
|
|
|
APP := wgsecure
|
|
VERSION := 0.10.0
|
|
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
|
|
# `--clean` vide le répertoire de travail au démarrage. Linux et Windows
|
|
# portant le même `--name`, ils partageaient `build/wgsecure/` : lancés à la
|
|
# suite, chacun effaçait les fichiers intermédiaires de l'autre en plein
|
|
# travail. Résultat observé le 8 septembre 2026 — un .exe Windows gonflé de
|
|
# 44 Mo et un binaire Linux tronqué dont l'archive ne se décompressait plus
|
|
# (« decompression resulted in return code -3 »). D'où un workpath par cible.
|
|
PI_OPTS := \
|
|
--noconfirm \
|
|
--clean \
|
|
--name $(APP) \
|
|
--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 helper windows-release check-windows-release installer-deps release publish publish-updates 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) \
|
|
--workpath build/linux \
|
|
--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) \
|
|
--workpath build/windows \
|
|
--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 ""
|
|
|
|
# ── Service Windows privilégié (wgsecure-helper.exe) ─────────────────────────
|
|
# Exécutable séparé de wgsecure.exe : installe/désinstalle le tunnel et pose/
|
|
# retire les règles NRPT (split-DNS) en LocalSystem, pour éviter une invite
|
|
# d'authentification à chaque connexion/déconnexion (cf. app/service/). Utilise
|
|
# pywin32, installé uniquement dans ce venv Wine — jamais dans .venv (Linux) ni
|
|
# dans les dépendances de wgsecure.exe. Cible explicite : ni `windows` ni
|
|
# `release` ne la déclenchent implicitement, ce composant restant optionnel.
|
|
# --hidden-import win32timezone : dépendance dynamique de pywin32 que
|
|
# l'analyse statique de PyInstaller ne détecte pas seule (service qui
|
|
# démarre mais plante à l'exécution sans ce forçage — cas connu).
|
|
helper:
|
|
@echo ""
|
|
@echo " 🛡️ Compilation du service (Wine + PyInstaller)…"
|
|
@echo ""
|
|
@which wine > /dev/null 2>&1 \
|
|
|| (echo " ❌ Wine non installé → sudo apt install wine"; exit 1)
|
|
@test -f "$(WINE_PYTHON)" \
|
|
|| (echo " ❌ Python Windows introuvable dans Wine → make setup-wine"; exit 1)
|
|
@wine $(WINE_PYTHON) -m pip install -r requirements-helper.txt --quiet
|
|
@wine $(WINE_PYTHON) -m PyInstaller \
|
|
--noconfirm \
|
|
--clean \
|
|
--workpath build/helper \
|
|
--name wgsecure-helper \
|
|
--onefile \
|
|
--hidden-import win32timezone \
|
|
app/service/helper_service.py
|
|
@echo ""
|
|
@echo " ✅ Service → $(DIST)/wgsecure-helper.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) ─────────────────────────────────────
|
|
# Ne fait pas `clean` : celui-ci vide tout $(DIST), y compris les artefacts
|
|
# Windows produits juste avant (compilés sous Wine, plusieurs minutes) que
|
|
# `publish` doit justement joindre à la release. Seul l'artefact Linux est
|
|
# retiré, ce qui suffit à garantir sa fraîcheur.
|
|
release: linux
|
|
@rm -f $(DIST)/$(APP)-*-linux-x86_64
|
|
@mv $(DIST)/$(APP) $(DIST)/$(APP)-$(VERSION)-linux-x86_64
|
|
@echo " 📦 Release → $(DIST)/$(APP)-$(VERSION)-linux-x86_64"
|
|
|
|
# ── Chaîne Windows complète ───────────────────────────────────────────────────
|
|
# Compile l'exe, le service, construit l'installeur, puis renomme le tout avec
|
|
# la version — dans cet ordre : wgsecure.iss source `dist\wgsecure.exe` et
|
|
# `dist\wgsecure-helper.exe` sous leurs noms bruts, renommer avant de compiler
|
|
# l'installeur le ferait échouer (ou, pire, embarquer les binaires de la
|
|
# version précédente).
|
|
#
|
|
# La version vit à deux endroits — ce fichier et wgsecure.iss — et rien ne les
|
|
# synchronise : la garde ci-dessous refuse de produire un installeur dont le
|
|
# numéro contredirait celui de la release.
|
|
# Sortie d'ISCC journalisée plutôt qu'affichée : Wine y mêle ses propres
|
|
# avertissements (wow64), sans intérêt quand tout se passe bien. En cas
|
|
# d'échec le journal est recraché, sinon la raison serait perdue.
|
|
ISCC_LOG := /tmp/wgsecure-iscc.log
|
|
ISCC := $(HOME)/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe
|
|
ISS_VERSION = $(shell sed -n 's/^#define MyAppVersion "\(.*\)"/\1/p' wgsecure.iss)
|
|
|
|
# Vérifications d'abord, compilations ensuite : placées dans la recette de
|
|
# windows-release, elles n'auraient averti qu'après plusieurs minutes de
|
|
# PyInstaller, pour un défaut connu dès la première seconde.
|
|
check-windows-release:
|
|
@test "$(ISS_VERSION)" = "$(VERSION)" \
|
|
|| (echo " ❌ Version incohérente : Makefile $(VERSION), wgsecure.iss $(ISS_VERSION)"; \
|
|
echo " → Alignez #define MyAppVersion dans wgsecure.iss"; exit 1)
|
|
@test -f "$(ISCC)" \
|
|
|| (echo " ❌ Inno Setup introuvable ($(ISCC))"; \
|
|
echo " → Installez Inno Setup 6 dans Wine."; exit 1)
|
|
|
|
windows-release: check-windows-release installer-deps windows helper
|
|
@echo ""
|
|
@echo " 📦 Construction de l'installeur Windows…"
|
|
@wine "$(ISCC)" wgsecure.iss > $(ISCC_LOG) 2>&1 \
|
|
|| (echo " ❌ Échec de la compilation Inno Setup :"; \
|
|
tail -20 $(ISCC_LOG); exit 1)
|
|
@mv $(DIST)/$(APP).exe $(DIST)/$(APP)-$(VERSION)-windows-x86_64.exe
|
|
@mv $(DIST)/$(APP)-helper.exe $(DIST)/$(APP)-helper-$(VERSION).exe
|
|
@echo ""
|
|
@echo " ✅ Windows → $(DIST)/$(APP)-$(VERSION)-windows-x86_64.exe"
|
|
@echo " ✅ Service → $(DIST)/$(APP)-helper-$(VERSION).exe"
|
|
@echo " ✅ Setup → $(DIST)/$(APP)-$(VERSION)-setup.exe"
|
|
@echo ""
|
|
|
|
# ── 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
|
|
|
|
# ── Publication des artefacts vers le serveur de mise à jour ─────────────────
|
|
# Le client va chercher ses mises à jour sur le serveur WGSecure, pas sur
|
|
# Gitea : le dépôt est privé, et un jeton embarqué dans une application
|
|
# distribuée est un jeton donné à tous ses utilisateurs. Cette cible dépose
|
|
# les binaires et le manifeste que la route /api/version publie.
|
|
#
|
|
# Le condensat est calculé ici, une fois, et non par le serveur à chaque
|
|
# appel : un SHA-256 sur 125 Mo à chaque interrogation bloquerait l'API.
|
|
UPDATE_HOST := root@192.168.1.206
|
|
UPDATE_DIR := /opt/WGSecure-Serveur/artifacts
|
|
|
|
# ANNOUNCE permet d'annoncer les artefacts sous un autre numéro que celui
|
|
# qu'ils portent : `make publish-updates ANNOUNCE=9.9.9` fait croire au parc
|
|
# qu'une version existe, sans avoir à en compiler une seconde. Sert à exercer
|
|
# la chaîne de mise à jour — détection, téléchargement, empreinte, bascule —
|
|
# avec les binaires déjà construits.
|
|
ANNOUNCE ?= $(VERSION)
|
|
|
|
publish-updates:
|
|
@test -f "$(DIST)/$(APP)-$(VERSION)-linux-x86_64" \
|
|
|| (echo " ❌ Artefact Linux absent — lancez d'abord : make release"; exit 1)
|
|
@echo ""
|
|
@echo " 📤 Publication des artefacts $(VERSION) vers $(UPDATE_HOST)…"
|
|
@FILES="$(DIST)/$(APP)-$(VERSION)-linux-x86_64"; \
|
|
[ -f "$(DIST)/$(APP)-$(VERSION)-setup.exe" ] \
|
|
&& FILES="$$FILES $(DIST)/$(APP)-$(VERSION)-setup.exe" \
|
|
|| echo " ⚠️ Installeur Windows absent : seuls les postes Linux seront servis"; \
|
|
scp -q $$FILES $(UPDATE_HOST):$(UPDATE_DIR)/
|
|
@$(PYTHON) -c "import json, hashlib, os, sys; \
|
|
v='$(VERSION)'; a='$(ANNOUNCE)'; d='$(DIST)'; app='$(APP)'; \
|
|
spec=[('linux-x86_64', f'{app}-{v}-linux-x86_64'), ('windows-setup', f'{app}-{v}-setup.exe')]; \
|
|
arts=[{'key': k, 'filename': n, 'size': os.path.getsize(f'{d}/{n}'), \
|
|
'sha256': hashlib.sha256(open(f'{d}/{n}','rb').read()).hexdigest()} \
|
|
for k, n in spec if os.path.isfile(f'{d}/{n}')]; \
|
|
notes=open('CHANGELOG.md', encoding='utf-8').read().split('## [')[1]; \
|
|
json.dump({'version': a, 'notes': '## [' + notes.split('\n---')[0].strip(), \
|
|
'artifacts': arts}, open('/tmp/wgs-manifest.json','w', encoding='utf-8'), \
|
|
ensure_ascii=False, indent=2); \
|
|
print(f' 🔑 {len(arts)} artefact(s) de {v}, annonces comme {a}')"
|
|
@scp -q /tmp/wgs-manifest.json $(UPDATE_HOST):$(UPDATE_DIR)/manifest.json
|
|
@ssh $(UPDATE_HOST) "chown -R wgsecure:wgsecure $(UPDATE_DIR)"
|
|
@rm -f /tmp/wgs-manifest.json
|
|
@echo " ✅ Mise à jour $(ANNOUNCE) proposée aux clients"
|
|
@echo ""
|
|
|
|
# ── 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 helper Service Windows (Wine) ║"
|
|
@echo " ║ make installer-deps Prérequis wgsecure.iss ║"
|
|
@echo " ║ make release Linux + nommage release ║"
|
|
@echo " ║ make windows-release Windows + exe + setup ║"
|
|
@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 ""
|