Ceci est une ancienne révision du document !
Table des matières
Serveur winget privé (rewinged)
Source de paquets winget auto-hébergée pour le parc géré Make IT Simple. Permet d'aligner les versions logicielles sur l'ensemble des parcs clients sans donner à l'agent de déploiement le droit de remplacer son propre binaire.
1. Architecture
Poste client
|
| HTTPS 443
v
+------------------------------+
| VM Caddy (frontal existant) |
| repo.makeitsimple.be |
| TLS Let's Encrypt |
+---------------+--------------+
|
| HTTP 8080 (interne)
v
Forgejo +--------------------------------+
mis/winget-manifests | VM Debian winget-repo |
manifests YAML | |
+ MSI en Git LFS | rewinged :8080 |
| | -manifestPath manifests |
| git pull | -autoInternalizePath |
| + lfs pull | installers |
| timer 1 min | |
+------------->| |
+--------------------------------+
| Composant | Machine | Rôle |
|---|---|---|
| Forgejo | existante | Source de vérité : manifests YAML et installeurs MSI. |
| Caddy frontal | frontal existant | Terminaison TLS. Aucun fichier local. |
| rewinged | VM dédiée | API REST Microsoft.Rest et service des installeurs. |
| Wazuh | existant | FIM sur manifests, installeurs et binaires. |
Le dépôt Forgejo peut rester privé. Les postes ne dialoguent qu'avec
repo.makeitsimple.be. Aucun accès anonyme à Forgejo n'est requis, puisque
la synchronisation se fait par clé de déploiement SSH.
Chaîne de confiance. L'intégrité des paquets ne repose pas sur le TLS mais sur le SHA256 déclaré dans chaque manifest. Un installeur substitué échoue à la vérification et winget refuse l'installation.
2. Prérequis
- VM Debian 12 minimale, 1 vCPU / 1 Go RAM / disque dimensionné au volume d'installeurs
- Enregistrement DNS public
repo.makeitsimple.bevers le frontal Caddy - Dépôt Forgejo
mis/winget-manifestsavec Git LFS activé - Clé de déploiement Forgejo en lecture seule
- Paquets
gitetgit-lfssur la VM
3. Installation de rewinged
3.1 Utilisateur et arborescence
useradd --system --home /srv/winget --shell /usr/sbin/nologin rewinged mkdir -p /srv/winget chown -R rewinged:rewinged /srv/winget
Les répertoires manifests et installers seront créés par le clone Git
(section 6).
Le compte rewinged doit pouvoir écrire dans installers : c'est là que
l'auto-internalisation dépose les installeurs tiers rapatriés.
3.2 Binaire
Télécharger la release depuis GitHub.
Épingler une version précise. Le projet est en pré-1.0 : l'auteur indique que configuration, sortie et comportement peuvent changer à tout moment.
VERSION="0.15.0" cd /tmp # adapter le nom d'archive à celui de la release tar xzf rewinged_${VERSION}_linux_amd64.tar.gz install -m 0755 rewinged /usr/local/bin/rewinged /usr/local/bin/rewinged --help
3.3 Options utiles
| Flag | Rôle |
|---|---|
-manifestPath | Répertoire des manifests (défaut ./packages) |
-autoInternalize | Rapatrie et sert localement les installeurs distants |
-autoInternalizePath | Répertoire des installeurs servis (défaut ./installers) |
-trustedProxies | IP du frontal, pour journaliser l'IP réelle du client |
-listen | Adresse et port d'écoute |
-sourceAuthType | none ou microsoftEntraId |
-https | TLS assuré par rewinged, inutile derrière un frontal |
-logLevel | disable, error, warn, info, debug, trace |
Deux usages de -autoInternalizePath. rewinged y dépose les installeurs
qu'il rapatrie automatiquement, et sert directement tout fichier qui s'y
trouve sous /installers/. Les MSI synchronisés depuis Forgejo y sont donc
servis sans serveur de fichiers supplémentaire.
Vérifié :
curl -I http://10.0.0.20:8080/installers/mis-agent-1.2.0.msi # HTTP/1.1 200 OK # Content-Type: application/x-msi # Accept-Ranges: bytes
3.4 Service systemd
- /etc/systemd/system/rewinged.service
[Unit] Description=rewinged winget package source After=network.target [Service] User=rewinged Group=rewinged ExecStart=/usr/local/bin/rewinged \ -listen 10.0.0.20:8080 \ -manifestPath /srv/winget/manifests \ -autoInternalize \ -autoInternalizePath /srv/winget/installers \ -trustedProxies 10.0.0.10 \ -logLevel info Restart=on-failure RestartSec=5 # Durcissement ProtectSystem=strict ProtectHome=true PrivateTmp=true PrivateDevices=true NoNewPrivileges=true RestrictSUIDSGID=true LockPersonality=true MemoryDenyWriteExecute=true ReadOnlyPaths=/srv/winget/manifests ReadWritePaths=/srv/winget/installers [Install] WantedBy=multi-user.target
systemctl daemon-reload systemctl enable --now rewinged systemctl status rewinged
4. Pare-feu de la VM
Seul le frontal doit joindre rewinged.
CADDY_IP="10.0.0.10" ufw default deny incoming ufw allow from ${CADDY_IP} to any port 8080 proto tcp ufw allow from 100.64.0.0/10 to any port 22 proto tcp ufw enable
5. Configuration du frontal Caddy
À ajouter au Caddyfile de la machine frontale.
- /etc/caddy/Caddyfile
repo.makeitsimple.be { tls vincent@makeitsimple.be reverse_proxy 10.0.0.20:8080 header { Strict-Transport-Security "max-age=31536000" X-Content-Type-Options "nosniff" -Server } log { output file /var/log/caddy/repo.log format json } }
Winget exige HTTPS avec une chaîne validée par le magasin de confiance Windows. Let's Encrypt sur un nom public évite d'avoir à déployer une CA interne sur chaque poste.
Un seul reverse_proxy : rewinged sert à la fois l'API sous /api et les
installeurs sous /installers.
6. Dépôt Forgejo
Un seul dépôt contient manifests et installeurs. Les MSI passent par Git LFS pour ne pas alourdir l'historique.
6.1 Structure
winget-manifests/
├── .gitattributes
├── manifests/
│ └── m/MakeItSimple/
│ ├── Agent/1.2.0/MakeItSimple.Agent.yaml
│ └── RustDesk/1.4.3/MakeItSimple.RustDesk.yaml
└── installers/
├── mis-agent-1.2.0.msi
└── rustdesk-1.4.3.msi
6.2 Activation de Git LFS
Côté poste de travail, une seule fois :
git lfs install git lfs track "*.msi" git lfs track "*.exe" git add .gitattributes git commit -m "Suivi LFS des installeurs" git push
Vérifier que LFS est activé sur l'instance Forgejo et que le quota de stockage est suffisant. LFS ne fait pas de delta : chaque version d'un MSI est stockée intégralement.
6.3 Clone initial sur la VM
apt install git-lfs sudo -u rewinged git lfs install --skip-repo sudo -u rewinged git clone \ git@forgejo.makeitsimple.be:mis/winget-manifests.git \ /srv/winget sudo -u rewinged git -C /srv/winget lfs pull
La clé de déploiement doit être en lecture seule : le serveur de paquets ne doit jamais pouvoir écrire dans le dépôt.
7. Synchronisation automatique
Pas de webhook : un timer évite d'exposer un endpoint entrant sur la machine qui décide de ce que le parc installe. Le flux part toujours du serveur vers Forgejo.
7.1 Rechargement des manifests
Vérifier d'abord si rewinged relit ses manifests à chaud. Déposer un manifest
dans /srv/winget/manifests sans redémarrer le service, puis :
curl -s http://10.0.0.20:8080/api/packageManifests/MakeItSimple.Agent
- Réponse avec le manifest : rechargement natif, utiliser le script
simple (7.2)
- 404 : un redémarrage est nécessaire après chaque publication,
utiliser le script avec détection de changement (7.3)
7.2 Script de synchronisation
- /usr/local/bin/winget-sync.sh
#!/bin/bash set -e cd /srv/winget BEFORE=$(runuser -u rewinged -- git rev-parse HEAD) runuser -u rewinged -- git pull --ff-only runuser -u rewinged -- git lfs pull AFTER=$(runuser -u rewinged -- git rev-parse HEAD) # Redemarrage uniquement si le depot a bouge. # A supprimer si rewinged recharge les manifests a chaud (7.1). if [ "$BEFORE" != "$AFTER" ]; then logger -t winget-sync "Publication detectee: $BEFORE -> $AFTER" systemctl try-reload-or-restart rewinged fi exit 0
chmod 0755 /usr/local/bin/winget-sync.sh
Le –ff-only est délibéré : si l'historique local diverge, le pull échoue
au lieu de fusionner silencieusement. Une divergence signifie une écriture
locale, ce qui ne doit jamais arriver.
7.3 Unités systemd
- /etc/systemd/system/winget-sync.service
[Unit] Description=Sync winget manifests and installers from Forgejo After=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/winget-sync.sh
- /etc/systemd/system/winget-sync.timer
[Unit] Description=Sync winget repository every minute [Timer] OnBootSec=1min OnUnitActiveSec=1min [Install] WantedBy=timers.target
systemctl daemon-reload systemctl enable --now winget-sync.timer systemctl list-timers winget-sync.timer journalctl -u winget-sync.service -f
<WRAP center round important>
L'unité tourne en root, sans directive User=, afin de pouvoir piloter
systemd. Les opérations Git sont abaissées vers le compte rewinged par
runuser, ce qui préserve la propriété des fichiers et le durcissement de
l'unité principale.
L'alternative — unité sous User=rewinged avec une règle polkit autorisant
le redémarrage — est plus complexe pour un bénéfice équivalent.
</WRAP>
Ne pas redémarrer inconditionnellement. Un git pull sans changement ne
justifie pas de couper le service. À raison de 1440 exécutions par jour, un
redémarrage systématique pollue les journaux et provoque des micro-coupures
pendant les téléchargements. La comparaison de HEAD ramène cela à quelques
redémarrages par semaine, et donne un signal net : un redémarrage équivaut à
une publication.
8. Format d'un manifest
- MakeItSimple.Agent.yaml
PackageIdentifier: MakeItSimple.Agent PackageVersion: 1.2.0 PackageLocale: en-US Publisher: Make IT Simple SRL PackageName: MIS Agent License: Proprietary ShortDescription: Agent d'inventaire et de mise a jour Installers: - Architecture: x64 InstallerType: msi InstallerUrl: https://repo.makeitsimple.be/installers/mis-agent-1.2.0.msi InstallerSha256: A1B2C3D4E5F6... Scope: machine InstallerSwitches: Silent: /quiet /norestart AppsAndFeaturesEntries: - ProductCode: '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' UpgradeCode: '{YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY}' ManifestType: singleton ManifestVersion: 1.6.0
Calcul du hash :
sha256sum mis-agent-1.2.0.msi | tr 'a-f' 'A-F'
Types d'installeurs supportés : msi, exe, inno, nullsoft,
wix, burn, msix, appx, zip, portable.
Ne pas utiliser portable pour un service Windows — il ne fait qu'un alias
sur le PATH.
9. Extraction des codes MSI
Le ProductCode change à chaque version d'un MSI bien construit ;
l'UpgradeCode reste stable et identifie le produit dans la durée.
Le bloc AppsAndFeaturesEntries en dépend, notamment pour l'adoption des
logiciels déjà installés depuis d'anciens paquets custom.
9.1 Depuis un fichier MSI
À exécuter sur la machine de build (Windows). Lecture seule, sans installation ni Orca.
- Get-MsiCodes.ps1
function Get-MsiCodes { param([string]$Path) $installer = New-Object -ComObject WindowsInstaller.Installer $db = $installer.GetType().InvokeMember('OpenDatabase','InvokeMethod',$null,$installer,@($Path,0)) $props = @{} foreach ($p in 'ProductCode','UpgradeCode','ProductVersion','ProductName') { $view = $db.GetType().InvokeMember('OpenView','InvokeMethod',$null,$db, @("SELECT Value FROM Property WHERE Property='$p'")) $view.GetType().InvokeMember('Execute','InvokeMethod',$null,$view,$null) $rec = $view.GetType().InvokeMember('Fetch','InvokeMethod',$null,$view,$null) if ($rec) { $props[$p] = $rec.GetType().InvokeMember('StringData','GetProperty',$null,$rec,1) } } [PSCustomObject]$props } Get-MsiCodes 'C:\build\mis-agent-1.2.0.msi'
Sortie type :
ProductName : MIS Agent
ProductVersion : 1.2.0
ProductCode : {A1B2C3D4-1111-2222-3333-444455556666}
UpgradeCode : {99887766-AAAA-BBBB-CCCC-DDDDEEEEFFFF}
Le COM WindowsInstaller.Installer n'existe que sous Windows : ce script ne
tourne pas sur la VM Debian.
9.2 Depuis un poste où le logiciel est installé
Pour recenser les GUID en circulation dans le parc :
Get-ItemProperty ` HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*, ` HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object DisplayName -like '*RustDesk*' | Select-Object DisplayName, DisplayVersion, PSChildName
PSChildName porte le ProductCode lorsque la clé est un GUID, ce qui est le
cas des MSI. L'UpgradeCode n'y figure pas.
<WRAP center round important>
Éviter Get-CimInstance Win32_Product en production : la classe déclenche
une réévaluation de tous les MSI installés, ce qui est lent et peut générer
des événements de réparation. Acceptable ponctuellement sur un poste témoin.
</WRAP>
9.3 Automatisation
L'agent d'inventaire devrait remonter le ProductCode de chaque logiciel
installé. Cela donne côté serveur la liste exacte des GUID du parc, donc les
AppsAndFeaturesEntries à renseigner, sans interroger les postes un par un.
10. Workflow de publication
- Construire l'installeur
- Relever les codes avec
Get-MsiCodes(9.1) - Calculer le SHA256
- Placer le MSI dans
installers/et le manifest dansmanifests/ - Commit sur une branche, ouvrir une PR, revue, merge sur
main - Le timer applique dans la minute
- Valider depuis un poste témoin avant diffusion
winget search -s mis-repo MakeItSimple winget install --id MakeItSimple.Agent -s mis-repo --silent
11. Configuration des postes
11.1 Enregistrement de la source
winget source add -n mis-repo ` -a https://repo.makeitsimple.be/api ` -t "Microsoft.Rest"
À exécuter en scope machine, sinon la source n'existe que pour l'utilisateur courant et reste invisible à l'agent tournant en service.
11.2 Winget en contexte SYSTEM
winget n'est pas dans le PATH pour le compte SYSTEM. Résoudre le chemin
versionné :
$winget = (Get-ChildItem ` "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe\winget.exe" | Select-Object -Last 1).FullName
Ajouter –disable-interactivity à tous les appels non interactifs, sinon
un prompt bloque le processus indéfiniment.
11.3 Adoption du parc existant
Un paquet installé autrement que depuis la source privée n'est pas reconnu
par winget upgrade tant qu'il n'a pas été installé une fois depuis cette
source. Deux contournements :
- renseigner les
AppsAndFeaturesEntriesdes anciens builds - passer une réinstallation depuis
mis-repo
À valider sur un poste témoin avant de dimensionner la campagne.
11.4 Compatibilité
| Système | App Installer |
|---|---|
| Windows 10 1809+ / 11 | Livré |
| Windows Server 2025 | Livré |
| Windows Server 2019 / 2022 | Absent — installation manuelle, non supportée |
| Éditions N et LTSC sans Store | Installation manuelle |
Inventorier la part de Server 2019/2022 du parc : elle décide si winget est le canal unique ou s'il faut un second canal.
12. Surveillance Wazuh
12.1 FIM
- ossec.conf
<syscheck> <directories check_all="yes" realtime="yes" report_changes="yes"> /srv/winget/manifests </directories> <directories check_all="yes" realtime="yes"> /srv/winget/installers </directories> <directories check_all="yes" realtime="yes"> /usr/local/bin </directories> </syscheck>
12.2 Réduction du bruit
La FIM se déclenche à chaque synchronisation légitime. Pour distinguer le légitime de l'anormal :
- signer les commits en GPG et vérifier la signature dans
winget-sync
avant application — un fichier modifié sans commit signé devient alors une
alerte non ambiguë * à défaut, corréler avec les logs de ''winget-sync.service''
12.3 Alertes complémentaires
- arrêt du service
rewinged - échecs répétés de
winget-sync.timer— un dépôt figé est un mode de
panne silencieux
- volumétrie anormale de téléchargements sur
/installers/ - saturation disque de la VM (croissance LFS)
13. Points ouverts
| Sujet | Action |
|---|---|
| Rechargement à chaud | Trancher le test 7.1 et simplifier le script si le rechargement est natif |
| Auto-internalisation | Valider le rapatriement des installeurs tiers depuis les URL éditeur |
| Bug de matching | Tester sur un poste témoin avec un logiciel préinstallé |
| Server 2019/2022 | Inventorier la proportion dans le parc |
| Upgrade RustDesk | Vérifier que la mise à jour préserve RustDesk2.toml et le mot de passe permanent |
| Rétention LFS | Définir une politique de purge des anciennes versions d'installeurs |
| Quota Forgejo | Dimensionner le stockage LFS et surveiller sa croissance |
