261 lines
11 KiB
YAML
261 lines
11 KiB
YAML
name: release
|
|
|
|
# Construit l'image du plan de contrôle et publie le bundle d'agent.
|
|
#
|
|
# Prérequis côté Gitea :
|
|
# - un secret REGISTERYKEY (jeton d'accès avec les portées repository + package)
|
|
# - les paquets activés sur l'instance
|
|
# - un runner ubuntu-latest ayant accès au socket Docker
|
|
# - un runner windows-latest (test de fumée de l'agent)
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
tags: ['v*']
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
IMAGE_NAME: stream-control-server
|
|
PACKAGE_NAME: stream-control-agent
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.meta.outputs.version }}
|
|
registry: ${{ steps.meta.outputs.registry }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '24'
|
|
|
|
# --- Identité de la version -----------------------------------------
|
|
- name: Calculer version et registre
|
|
id: meta
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# Une étiquette v1.2.3 donne 1.2.3 ; sinon 0.1.0-<sha court>.
|
|
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
|
VERSION="${GITHUB_REF#refs/tags/}"
|
|
VERSION="${VERSION#v}"
|
|
else
|
|
VERSION="0.1.0-${GITHUB_SHA:0:7}"
|
|
fi
|
|
|
|
# L'hôte du registre est celui de l'instance Gitea. La variable
|
|
# REGISTRY_HOST permet de le forcer (instance derrière un autre nom).
|
|
REGISTRY="${{ vars.REGISTRY_HOST }}"
|
|
if [ -z "$REGISTRY" ]; then
|
|
REGISTRY="${GITHUB_SERVER_URL#http://}"
|
|
REGISTRY="${REGISTRY#https://}"
|
|
fi
|
|
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT"
|
|
echo "Version $VERSION → registre $REGISTRY"
|
|
|
|
# --- Qualité ---------------------------------------------------------
|
|
- name: Installer les dépendances
|
|
run: npm ci
|
|
|
|
- name: Contrôle de types
|
|
run: npm run typecheck
|
|
|
|
- name: Compiler
|
|
run: npm run build
|
|
|
|
- name: Produire le bundle d'agent
|
|
run: npm run bundle -w @stream-control/agent
|
|
|
|
# Un bundle qui ne démarre pas ne doit jamais être publié. Le serveur est
|
|
# volontairement injoignable ici : on vérifie que le diagnostic s'exécute.
|
|
- name: Vérifier le bundle (Linux)
|
|
env:
|
|
AGENT_TOKEN: ci-smoke-test
|
|
SERVER_URL: ws://127.0.0.1:1/ws/agent
|
|
run: |
|
|
set -euo pipefail
|
|
OUT="$(node packages/agent/dist-bundle/agent.cjs --check 2>&1 || true)"
|
|
echo "$OUT"
|
|
echo "$OUT" | grep -q 'obs-websocket' || { echo 'Diagnostic incomplet'; exit 1; }
|
|
|
|
# --- Image du plan de contrôle ---------------------------------------
|
|
- name: Authentification au registre
|
|
run: |
|
|
echo "${{ secrets.REGISTERYKEY }}" \
|
|
| docker login "${{ steps.meta.outputs.registry }}" \
|
|
--username "${{ github.repository_owner }}" --password-stdin
|
|
|
|
- name: Construire et pousser l'image
|
|
run: |
|
|
set -euo pipefail
|
|
BASE="${{ steps.meta.outputs.registry }}/${{ github.repository_owner }}/${IMAGE_NAME}"
|
|
VERSION="${{ steps.meta.outputs.version }}"
|
|
|
|
docker build \
|
|
--tag "$BASE:$VERSION" \
|
|
--tag "$BASE:sha-${GITHUB_SHA:0:7}" \
|
|
--label "org.opencontainers.image.revision=$GITHUB_SHA" \
|
|
--label "org.opencontainers.image.version=$VERSION" \
|
|
--label "org.opencontainers.image.source=$GITHUB_SERVER_URL/${{ github.repository }}" \
|
|
.
|
|
|
|
docker push "$BASE:$VERSION"
|
|
docker push "$BASE:sha-${GITHUB_SHA:0:7}"
|
|
|
|
# :latest ne suit que main et les étiquettes, jamais une branche de travail.
|
|
if [[ "$GITHUB_REF" == refs/heads/main || "$GITHUB_REF" == refs/tags/* ]]; then
|
|
docker tag "$BASE:$VERSION" "$BASE:latest"
|
|
docker push "$BASE:latest"
|
|
fi
|
|
|
|
# --- Bundle d'agent et scripts d'installation ------------------------
|
|
- name: Publier les artefacts d'agent
|
|
run: |
|
|
set -euo pipefail
|
|
API="${GITHUB_SERVER_URL}/api/packages/${{ github.repository_owner }}/generic/${PACKAGE_NAME}"
|
|
AUTH="${{ github.repository_owner }}:${{ secrets.REGISTERYKEY }}"
|
|
VERSION="${{ steps.meta.outputs.version }}"
|
|
|
|
publish() {
|
|
local version="$1" file="$2" path="$3"
|
|
curl -sS -f -X PUT --user "$AUTH" --upload-file "$path" \
|
|
"$API/$version/$file"
|
|
echo " publié : $version/$file"
|
|
}
|
|
|
|
# Version immuable, tracée.
|
|
publish "$VERSION" agent.cjs packages/agent/dist-bundle/agent.cjs
|
|
publish "$VERSION" install-agent.sh deploy/install-agent.sh
|
|
publish "$VERSION" install-agent.ps1 deploy/install-agent.ps1
|
|
|
|
# Alias « latest », que visent les commandes d'installation. Le registre
|
|
# générique refuse d'écraser un fichier : on supprime la version d'abord.
|
|
if [[ "$GITHUB_REF" == refs/heads/main || "$GITHUB_REF" == refs/tags/* ]]; then
|
|
curl -sS -X DELETE --user "$AUTH" "$API/latest" >/dev/null || true
|
|
publish latest agent.cjs packages/agent/dist-bundle/agent.cjs
|
|
publish latest install-agent.sh deploy/install-agent.sh
|
|
publish latest install-agent.ps1 deploy/install-agent.ps1
|
|
fi
|
|
|
|
- name: Récapitulatif
|
|
run: |
|
|
BASE="${{ steps.meta.outputs.registry }}/${{ github.repository_owner }}/${IMAGE_NAME}"
|
|
{
|
|
echo "### Publié"
|
|
echo ""
|
|
echo "| Artefact | Référence |"
|
|
echo "| --- | --- |"
|
|
echo "| Image | \`$BASE:${{ steps.meta.outputs.version }}\` |"
|
|
echo "| Agent | \`${PACKAGE_NAME}/${{ steps.meta.outputs.version }}/agent.cjs\` |"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# Le bundle doit démarrer sur Windows : chemins, PowerShell, résolution de
|
|
# modules. Un test sur Linux seul ne couvre pas ces différences.
|
|
verify-windows:
|
|
runs-on: windows-latest
|
|
needs: build
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Le runner fournit Node 20, alors que le projet exige 22+ et que les
|
|
# scripts d'installation déposent 22+ sur les VM. Sans cette étape, le
|
|
# test de fumée validerait le bundle contre une version que la production
|
|
# n'utilisera jamais.
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
# Deux pièges de Windows PowerShell 5.1 sont à l'œuvre dans les étapes
|
|
# `shell: powershell` ci-dessous, et valent d'être connus avant de les
|
|
# modifier :
|
|
#
|
|
# 1. Les guillemets doubles imbriqués dans un argument passé à un
|
|
# exécutable natif sont perdus. `node -p 'x.split(".")[0]'` arrive
|
|
# donc comme `x.split(.)[0]` et lève une SyntaxError. On lit ici
|
|
# `node --version` et on découpe côté PowerShell : rien ne traverse
|
|
# cette frontière.
|
|
# 2. Le runner écrit ce bloc dans un .ps1 sans BOM, que PowerShell 5.1
|
|
# relit dans la page de codes ANSI. Tout caractère non-ASCII y devient
|
|
# illisible (« trouvé » s'affichait « trouvǸ »). Les chaînes affichées
|
|
# restent donc en ASCII — les commentaires, eux, ne sont jamais rendus.
|
|
- name: Confirmer la version de Node
|
|
shell: powershell
|
|
run: |
|
|
$raw = (& node --version)
|
|
if ($raw -match '^v(\d+)\.') { $major = [int]$Matches[1] } else { $major = 0 }
|
|
Write-Host "Node $raw"
|
|
if ($major -lt 22) { throw "Node 22+ requis, version lue : $raw" }
|
|
|
|
# Le script d'installation Windows n'est exécutable que sur Windows :
|
|
# ce contrôle de syntaxe est la seule barrière avant qu'il n'atterrisse
|
|
# sur une VM de production.
|
|
# shell: powershell (Windows PowerShell 5.1) et non pwsh : PowerShell 7
|
|
# n'est pas garanti sur un runner Windows, alors que 5.1 est toujours là.
|
|
# C'est aussi la version que les VM d'enregistrement exécuteront.
|
|
- name: Valider la syntaxe du script d'installation
|
|
shell: powershell
|
|
run: |
|
|
$path = (Resolve-Path 'deploy/install-agent.ps1').Path
|
|
|
|
# Le script doit rester en ASCII pur. Il est publié sans BOM et exécuté
|
|
# par PowerShell 5.1, qui relit alors le fichier dans la page de codes
|
|
# ANSI. Au-delà de l'affichage abîmé, certains caractères cassent le
|
|
# parseur : le tiret cadratin (E2 80 94) se relit en CP1252 comme « — »,
|
|
# dont le dernier caractère est U+201D — un délimiteur de chaîne valide
|
|
# en PowerShell. La chaîne se ferme en plein milieu et l'erreur signalée
|
|
# est une accolade manquante, des lignes plus loin.
|
|
$bad = [System.IO.File]::ReadAllBytes($path) | Where-Object { $_ -gt 127 }
|
|
if ($bad) {
|
|
throw "install-agent.ps1 doit rester en ASCII : $($bad.Count) octet(s) hors plage"
|
|
}
|
|
|
|
$errors = $null
|
|
[System.Management.Automation.Language.Parser]::ParseFile(
|
|
$path, [ref]$null, [ref]$errors) | Out-Null
|
|
if ($errors) {
|
|
$errors | ForEach-Object { Write-Host "$($_.Extent.StartLineNumber): $($_.Message)" }
|
|
throw "$($errors.Count) erreur(s) de syntaxe dans install-agent.ps1"
|
|
}
|
|
Write-Host 'install-agent.ps1 : syntaxe valide.'
|
|
|
|
- name: Récupérer le bundle publié
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
# PowerShell 5.1 ne négocie pas TLS 1.2 par défaut sur toutes les éditions.
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$url = "$env:GITHUB_SERVER_URL/api/packages/${{ github.repository_owner }}/generic/$env:PACKAGE_NAME/${{ needs.build.outputs.version }}/agent.cjs"
|
|
$pair = "${{ github.repository_owner }}:${{ secrets.REGISTERYKEY }}"
|
|
$auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
|
|
Invoke-WebRequest -Uri $url -Headers @{ Authorization = "Basic $auth" } `
|
|
-OutFile agent.cjs -UseBasicParsing
|
|
Write-Host "Bundle : $((Get-Item agent.cjs).Length) octets"
|
|
|
|
- name: Exécuter le diagnostic
|
|
shell: powershell
|
|
env:
|
|
AGENT_TOKEN: ci-smoke-test
|
|
SERVER_URL: ws://127.0.0.1:1/ws/agent
|
|
run: |
|
|
$out = (& node agent.cjs --check 2>&1 | Out-String)
|
|
$code = $LASTEXITCODE
|
|
Write-Host $out
|
|
Write-Host "code de sortie du diagnostic : $code"
|
|
|
|
# On valide que le diagnostic s'est déroulé, pas son verdict.
|
|
foreach ($marker in @('obs-websocket', 'powershell')) {
|
|
if ($out -notmatch [regex]::Escape($marker)) {
|
|
throw "Diagnostic incomplet : marqueur $marker absent de la sortie"
|
|
}
|
|
}
|
|
Write-Host 'Bundle fonctionnel sous Windows.'
|
|
|
|
# --check sort en 1 : le serveur est injoignable par construction ici.
|
|
# Sans ce exit explicite, le wrapper PowerShell propage $LASTEXITCODE
|
|
# et fait échouer l'étape malgré des assertions vertes.
|
|
exit 0
|