Outils pour utilisateurs

Outils du site


linux:rewinged

Ceci est une ancienne révision du document !


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.be vers le frontal Caddy
  • Dépôt Forgejo mis/winget-manifests avec Git LFS activé
  • Clé de déploiement Forgejo en lecture seule
  • Paquets git et git-lfs sur 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

  1. Construire l'installeur
  2. Relever les codes avec Get-MsiCodes (9.1)
  3. Calculer le SHA256
  4. Placer le MSI dans installers/ et le manifest dans manifests/
  5. Commit sur une branche, ouvrir une PR, revue, merge sur main
  6. Le timer applique dans la minute
  7. 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 AppsAndFeaturesEntries des 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

14. Références

linux/rewinged.1789198329.txt.gz · Dernière modification : de root