feat(desktop): manifeste de mise a jour latest.json + builds locaux signes (#77)
CI / lint (push) Successful in 1m7s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m12s
CI / build (push) Successful in 42s
CI / e2e (push) Successful in 10m43s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 1m7s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m12s
CI / build (push) Successful in 42s
CI / e2e (push) Successful in 10m43s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- scripts/updater_manifest.py : construit latest.json (version, pub_date, platforms Windows/Linux avec signature .sig et URLs des assets). - publish_release.py : genere latest.json, upload des .sig + latest.json, rappel de commit. - tauri.conf.json : endpoint updater -> raw/branch/main/desktop/latest.json. - build-windows.bat / build-linux.sh : detection automatique de obsigate-updater.key (signature) ou repli non signe. - .gitignore : ignore desktop/key/ ; tests/test_updater_manifest.py (8 tests).
This commit is contained in:
@@ -26,6 +26,9 @@ import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
||||
from updater_manifest import build_updater_manifest
|
||||
|
||||
DEFAULT_GITEA_URL = "https://git.dracodev.net"
|
||||
DEFAULT_REPO = "Projets/ObsiGate"
|
||||
|
||||
@@ -66,9 +69,13 @@ def find_desktop_binaries(base_dir: pathlib.Path) -> list[pathlib.Path]:
|
||||
"""Recherche les binaires Desktop générés par Tauri dans le répertoire desktop/."""
|
||||
patterns = [
|
||||
"target/**/release/bundle/nsis/*.exe",
|
||||
"target/**/release/bundle/nsis/*.exe.sig",
|
||||
"target/**/release/bundle/msi/*.msi",
|
||||
"target/**/release/bundle/msi/*.msi.sig",
|
||||
"target/**/release/bundle/appimage/*.AppImage",
|
||||
"target/**/release/bundle/appimage/*.AppImage.sig",
|
||||
"target/**/release/bundle/deb/*.deb",
|
||||
"target/**/release/bundle/deb/*.deb.sig",
|
||||
"release_assets/*",
|
||||
]
|
||||
|
||||
@@ -173,6 +180,10 @@ def upload_asset_to_gitea(
|
||||
content_type = "application/vnd.debian.binary-package"
|
||||
elif filename.endswith(".txt"):
|
||||
content_type = "text/plain"
|
||||
elif filename.endswith(".json"):
|
||||
content_type = "application/json"
|
||||
elif filename.endswith(".sig"):
|
||||
content_type = "text/plain"
|
||||
|
||||
body = []
|
||||
body.append(f"--{boundary}".encode("utf-8"))
|
||||
@@ -258,6 +269,22 @@ def main():
|
||||
all_files_to_upload.append(checksums_path)
|
||||
print(" ✅ checksums.txt généré avec succès")
|
||||
|
||||
# 2b. Génération du manifeste de mise à jour Tauri (latest.json)
|
||||
latest_path = root_dir / "desktop" / "latest.json"
|
||||
print(f"\n🔄 Génération du manifeste de mise à jour ({latest_path.name})...")
|
||||
manifest = build_updater_manifest(
|
||||
root_dir / "desktop", tag, gitea_url, repo, notes=args.notes or ""
|
||||
)
|
||||
if manifest:
|
||||
latest_path.write_text(
|
||||
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
all_files_to_upload.append(latest_path)
|
||||
print(f" ✅ latest.json généré ({', '.join(manifest['platforms'])})")
|
||||
else:
|
||||
print(" ⚠️ Aucun artefact signé (.sig) — latest.json non généré.")
|
||||
|
||||
if args.dry_run:
|
||||
print("\n🧪 Mode Dry-Run terminé. Aucune action distante effectuée.")
|
||||
return 0
|
||||
@@ -338,6 +365,11 @@ def main():
|
||||
print("\n==========================================================")
|
||||
print(f" 🎉 Publication terminée avec succès sur Gitea !")
|
||||
print(f" 🔗 {gitea_url}/{repo}/releases/tag/{tag}")
|
||||
if latest_path.exists():
|
||||
print("\n⚠️ Dernière étape : publier le manifeste de mise à jour")
|
||||
print(f" git add desktop/latest.json && git commit -m \"chore: latest.json {tag}\"")
|
||||
print(" git push origin main")
|
||||
print(" (l'updater lit desktop/latest.json sur la branche main)")
|
||||
print("==========================================================")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ObsiGate — Génération du manifeste de mise à jour Tauri (`latest.json`).
|
||||
|
||||
L'updater Tauri attend un document JSON de la forme :
|
||||
|
||||
{
|
||||
"version": "2.3.0",
|
||||
"notes": "…",
|
||||
"pub_date": "2026-09-12T10:00:00Z",
|
||||
"platforms": {
|
||||
"windows-x86_64": {"signature": "<contenu .exe.sig>", "url": "<URL .exe>"},
|
||||
"linux-x86_64": {"signature": "<contenu .AppImage.sig>", "url": "<URL .AppImage>"}
|
||||
}
|
||||
}
|
||||
|
||||
Ce module construit ce manifeste à partir des artefacts signés produits par
|
||||
`cargo tauri build` (avec `bundle.createUpdaterArtifacts: true`) et de leurs
|
||||
fichiers `.sig` (signature minisign).
|
||||
|
||||
Usage en ligne de commande :
|
||||
|
||||
python scripts/updater_manifest.py --tag v2.3.0
|
||||
python scripts/updater_manifest.py --tag v2.3.0 --dry-run
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
DEFAULT_GITEA_URL = "https://git.dracodev.net"
|
||||
DEFAULT_REPO = "Projets/ObsiGate"
|
||||
|
||||
# Plateforme Tauri -> liste de motifs de bundles candidats (par ordre de priorité).
|
||||
# Le premier bundle disposant d'un fichier `<bundle>.sig` est retenu.
|
||||
PLATFORM_BUNDLES: dict[str, list[str]] = {
|
||||
"windows-x86_64": [
|
||||
"target/**/release/bundle/nsis/*-setup.exe",
|
||||
"target/**/release/bundle/nsis/*.exe",
|
||||
"target/**/release/bundle/msi/*.msi",
|
||||
],
|
||||
"linux-x86_64": [
|
||||
"target/**/release/bundle/appimage/*.AppImage",
|
||||
"target/**/release/bundle/deb/*.deb",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def read_version(tauri_conf: pathlib.Path) -> str:
|
||||
"""Lit la version depuis `desktop/tauri.conf.json`."""
|
||||
with open(tauri_conf, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return str(data.get("version", "0.0.0"))
|
||||
|
||||
|
||||
def _find_bundle_with_signature(desktop_dir: pathlib.Path, patterns: list[str]):
|
||||
"""Retourne le premier bundle (le plus récent) disposant d'un `.sig`."""
|
||||
for pattern in patterns:
|
||||
matches = [p for p in desktop_dir.glob(pattern) if p.is_file()]
|
||||
if not matches:
|
||||
continue
|
||||
bundle = max(matches, key=lambda p: p.stat().st_mtime)
|
||||
sig = bundle.with_name(bundle.name + ".sig")
|
||||
if sig.is_file():
|
||||
return bundle, sig
|
||||
return None, None
|
||||
|
||||
|
||||
def build_updater_manifest(
|
||||
desktop_dir,
|
||||
tag: str,
|
||||
gitea_url: str = DEFAULT_GITEA_URL,
|
||||
repo: str = DEFAULT_REPO,
|
||||
notes: str = "",
|
||||
pub_date: str | None = None,
|
||||
) -> dict | None:
|
||||
"""Construit le manifeste `latest.json` à partir des artefacts signés.
|
||||
|
||||
Retourne `None` si aucun artefact signé n'est trouvé.
|
||||
"""
|
||||
desktop_dir = pathlib.Path(desktop_dir)
|
||||
tauri_conf = desktop_dir / "tauri.conf.json"
|
||||
version = read_version(tauri_conf) if tauri_conf.exists() else "0.0.0"
|
||||
|
||||
base_url = f"{gitea_url.rstrip('/')}/{repo.strip('/')}/releases/download/{tag}"
|
||||
platforms: dict[str, dict[str, str]] = {}
|
||||
|
||||
for platform, patterns in PLATFORM_BUNDLES.items():
|
||||
bundle, sig = _find_bundle_with_signature(desktop_dir, patterns)
|
||||
if bundle is None or sig is None:
|
||||
continue
|
||||
platforms[platform] = {
|
||||
"signature": sig.read_text(encoding="utf-8").strip(),
|
||||
"url": f"{base_url}/{bundle.name}",
|
||||
}
|
||||
|
||||
if not platforms:
|
||||
return None
|
||||
|
||||
if pub_date is None:
|
||||
pub_date = datetime.datetime.now(datetime.timezone.utc).strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
|
||||
return {
|
||||
"version": version,
|
||||
"notes": notes,
|
||||
"pub_date": pub_date,
|
||||
"platforms": platforms,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Générer le manifeste de mise à jour Tauri (latest.json)."
|
||||
)
|
||||
parser.add_argument("--tag", "-t", help="Tag de version (ex: v2.3.0)")
|
||||
parser.add_argument("--gitea-url", default=DEFAULT_GITEA_URL)
|
||||
parser.add_argument("--repo", default=DEFAULT_REPO)
|
||||
parser.add_argument("--notes", default="", help="Notes de version")
|
||||
parser.add_argument(
|
||||
"--desktop-dir",
|
||||
default=None,
|
||||
help="Dossier desktop/ (défaut: <racine>/desktop)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default=None,
|
||||
help="Fichier de sortie (défaut: <desktop>/latest.json)",
|
||||
)
|
||||
parser.add_argument("--dry-run", action="store_true", help="Afficher sans écrire")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
root_dir = pathlib.Path(__file__).resolve().parent.parent
|
||||
desktop_dir = pathlib.Path(args.desktop_dir) if args.desktop_dir else root_dir / "desktop"
|
||||
output = pathlib.Path(args.output) if args.output else desktop_dir / "latest.json"
|
||||
|
||||
tag = args.tag
|
||||
if not tag:
|
||||
version = read_version(desktop_dir / "tauri.conf.json")
|
||||
tag = f"v{version}" if not version.startswith("v") else version
|
||||
|
||||
manifest = build_updater_manifest(
|
||||
desktop_dir, tag, args.gitea_url, args.repo, notes=args.notes
|
||||
)
|
||||
|
||||
if manifest is None:
|
||||
print("⚠️ Aucun artefact signé (.sig) trouvé — manifeste non généré.")
|
||||
print("💡 Build d'abord avec la clé updater définie :")
|
||||
print(" - Windows : .\\desktop\\build-windows.bat")
|
||||
print(" - Linux : ./desktop/build-linux.sh")
|
||||
return 1
|
||||
|
||||
content = json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
|
||||
|
||||
if args.dry_run:
|
||||
print(content)
|
||||
print(f"🧪 Dry-run : rien écrit ({output})")
|
||||
return 0
|
||||
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(content, encoding="utf-8")
|
||||
print(f"✅ Manifeste écrit : {output}")
|
||||
print(f" Version : {manifest['version']}")
|
||||
print(f" Plateformes: {', '.join(manifest['platforms'])}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user