diff --git a/.gitea/workflows/validate.yml b/.gitea/workflows/validate.yml new file mode 100644 index 0000000..d075318 --- /dev/null +++ b/.gitea/workflows/validate.yml @@ -0,0 +1,34 @@ +name: Validate Actions + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Valida YAML + run: | + for f in */action.yml; do + echo "Validazione: $f" + python3 -c "import yaml; yaml.safe_load(open('$f')); print('OK')" + done + + - name: Verifica struttura action + run: | + required_fields=("name" "runs") + for f in */action.yml; do + for field in "${required_fields[@]}"; do + if ! grep -q "^$field:" "$f"; then + echo "ERRORE: $f manca del campo '$field'" + exit 1 + fi + done + done + echo "Struttura azioni valida" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/README.md b/README.md index e69de29..c1a7714 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,65 @@ +# Gitea Actions + +Repository contenente action [Gitea Actions](https://docs.gitea.com/usage/actions/overview) centralizzate +utilizzabili da altri repository del workspace. + +## Action disponibili + +### `version-from-tag` + +Estrae la versione da un tag (formato `v1.2.3.4[-suffix]`) e produce le variabili +`appver`, `fullver`, `suffix` e `version`. + +```yaml +- name: Calcola versione + uses: https:////Actions/version-from-tag@ + with: + ref-name: ${{ github.ref_name }} +``` + +### `publish-dotnet` + +Compila (restore + publish) un progetto .NET e sincronizza l'output su un path +locale via `rsync`. + +```yaml +- name: Publish + uses: https:////Actions/publish-dotnet@ + with: + project: src/MyApp/MyApp.csproj + output-path: /var/publish/myapp + version: ${{ steps.versione.outputs.appver }} + # opzionali: + configuration: Release + subpath: "wwwroot" + exclude-dirs: store + exclude-files: appsettings.json +``` + +### `deploy-iis` + +Esegue il deploy su IIS: ferma sito/application pool, copia i file via `robocopy`, +riavvia i servizi. + +```yaml +- name: Deploy IIS + uses: https:////Actions/deploy-iis@ + with: + source-path: /var/publish/myapp + destination-path: C:\inetpub\wwwroot\myapp + site-name: MySite + app-pool-name: MyAppPool + exclude-dirs: store + exclude-files: appsettings.json +``` + +## Versionamento delle action + +Per puntare a una versione stabile, crea un tag su questo repository +(es. `v1.0.0`) e usalo nel riferimento: + +```yaml +uses: https:////Actions/publish-dotnet@v1.0.0 +``` + +Oppure punta a un branch (`@main`) per avere sempre l'ultima versione. diff --git a/deploy-iis/action.yml b/deploy-iis/action.yml new file mode 100644 index 0000000..72899c6 --- /dev/null +++ b/deploy-iis/action.yml @@ -0,0 +1,128 @@ +name: Deploy IIS +description: Ferma sito e application pool IIS, sincronizza i file pubblicati e riavvia i servizi. + +inputs: + source-path: + description: Cartella sorgente da distribuire su IIS. + required: true + destination-path: + description: Cartella di destinazione sul server IIS. + required: true + site-name: + description: Nome del sito IIS da fermare e riavviare. + required: true + app-pool-name: + description: Nome dell'application pool IIS da fermare e riavviare. + required: true + exclude-dirs: + description: Elenco di directory da escludere dal mirroring (separate da virgola, punto e virgola o newline). + required: false + default: store + exclude-files: + description: Elenco di file da escludere dal mirroring (separati da virgola, punto e virgola o newline). + required: false + default: appsettings.json + +runs: + using: composite + steps: + - name: Deploy su IIS + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + $appcmd = Join-Path $env:SystemRoot 'System32\inetsrv\appcmd.exe' + $src = '${{ inputs.source-path }}' + $dst = '${{ inputs.destination-path }}' + $site = '${{ inputs.site-name }}' + $appPool = '${{ inputs.app-pool-name }}' + + function Split-InputList { + param([string]$Value) + + if ([string]::IsNullOrWhiteSpace($Value)) { + return @() + } + + return @( + $Value -split '[,;\r\n]+' + | ForEach-Object { $_.Trim() } + | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + ) + } + + if (-not (Test-Path -LiteralPath $appcmd)) { + throw "appcmd.exe non trovato: $appcmd" + } + + foreach ($entry in @{ + 'source-path' = $src + 'destination-path' = $dst + 'site-name' = $site + 'app-pool-name' = $appPool + }.GetEnumerator()) { + if ([string]::IsNullOrWhiteSpace($entry.Value)) { + throw "Input '$($entry.Key)' mancante." + } + } + + if (-not (Test-Path -LiteralPath $src)) { + throw "Cartella sorgente non trovata: $src" + } + + $excludeDirs = Split-InputList '${{ inputs.exclude-dirs }}' + $excludeFiles = Split-InputList '${{ inputs.exclude-files }}' + + $robocopyArgs = @( + $src + $dst + '/MIR' + '/R:2' + '/W:1' + '/NFL' + '/NDL' + '/NP' + ) + + if ($excludeDirs.Count -gt 0) { + $robocopyArgs += '/XD' + $robocopyArgs += $excludeDirs + } + + if ($excludeFiles.Count -gt 0) { + $robocopyArgs += '/XF' + $robocopyArgs += $excludeFiles + } + + $siteStopped = $false + $poolStopped = $false + + try { + & $appcmd stop site "/site.name:$site" + if ($LASTEXITCODE -ne 0) { throw "stop site fallito ($LASTEXITCODE)" } + $siteStopped = $true + + & $appcmd stop apppool "/apppool.name:$appPool" + if ($LASTEXITCODE -ne 0) { throw "stop apppool fallito ($LASTEXITCODE)" } + $poolStopped = $true + + New-Item -ItemType Directory -Force -Path $dst | Out-Null + + & robocopy @robocopyArgs + $robocopyExitCode = $LASTEXITCODE + if ($robocopyExitCode -ge 8) { + throw "robocopy fallito ($robocopyExitCode)" + } + } + finally { + if ($poolStopped) { + & $appcmd start apppool "/apppool.name:$appPool" + if ($LASTEXITCODE -ne 0) { throw "start apppool fallito ($LASTEXITCODE)" } + } + + if ($siteStopped) { + & $appcmd start site "/site.name:$site" + if ($LASTEXITCODE -ne 0) { throw "start site fallito ($LASTEXITCODE)" } + } + } + diff --git a/example-workflow.yml b/example-workflow.yml new file mode 100644 index 0000000..5679335 --- /dev/null +++ b/example-workflow.yml @@ -0,0 +1,32 @@ +name: Build and Deploy + +on: + push: + tags: [v*] + +jobs: + deploy: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Estrai versione dal tag + id: ver + uses: https:////Actions/version-from-tag@v1.2.3 + with: + ref-name: ${{ github.ref_name }} + + - name: Publish .NET + uses: https:////Actions/publish-dotnet@v1.2.3 + with: + project: src/MyApp/MyApp.csproj + output-path: publish + version: ${{ steps.ver.outputs.appver }} + + - name: Deploy IIS + uses: https:////Actions/deploy-iis@v1.2.3 + with: + source-path: publish + destination-path: C:\inetpub\wwwroot\myapp + site-name: MySite + app-pool-name: MyAppPool diff --git a/publish-dotnet/action.yml b/publish-dotnet/action.yml new file mode 100644 index 0000000..8dd826d --- /dev/null +++ b/publish-dotnet/action.yml @@ -0,0 +1,79 @@ +name: Publish .NET +description: > + Esegue restore, publish e rsync per un progetto .NET. + +inputs: + project: + description: Path al file .csproj del progetto. + required: true + output-path: + description: Directory di destinazione per i file pubblicati. + required: true + version: + description: Versione applicativa da applicare (es. 1.0.0.0). + required: true + subpath: + description: > + Sottopath relativo alla cartella di publish da copiare + (es. "wwwroot" per Blazor WASM). Vuoto per copiare tutto. + required: false + default: "" + configuration: + description: Configurazione di build (es. Release, Debug). + required: false + default: Release + exclude-dirs: + description: Directory da escludere dal rsync (separate da virgola, punto e virgola o newline). + required: false + default: store + exclude-files: + description: File da escludere dal rsync (separati da virgola, punto e virgola o newline). + required: false + default: appsettings.json + +runs: + using: composite + steps: + - name: Restore + shell: bash + run: dotnet restore "${{ inputs.project }}" + + - name: Publish e rsync + shell: bash + run: | + set -euo pipefail + + split_input_list() { + local value="$1" + if [ -z "$value" ]; then + echo "" + return + fi + echo "$value" | tr ',;\r\n' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' + } + + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' EXIT + + dotnet publish "${{ inputs.project }}" \ + -c "${{ inputs.configuration }}" \ + -p:Version="${{ inputs.version }}" \ + -o "$tmpdir" + + src="$tmpdir" + if [ -n "${{ inputs.subpath }}" ]; then + src="$tmpdir/${{ inputs.subpath }}" + fi + + mkdir -p "${{ inputs.output-path }}" + + exclude_args=() + while IFS= read -r dir; do + [ -n "$dir" ] && exclude_args+=(--exclude="$dir") + done < <(split_input_list "${{ inputs.exclude-dirs }}") + + while IFS= read -r file; do + [ -n "$file" ] && exclude_args+=(--exclude="$file") + done < <(split_input_list "${{ inputs.exclude-files }}") + + rsync -a --delete "${exclude_args[@]}" "$src/" "${{ inputs.output-path }}/" diff --git a/version-from-tag/action.yml b/version-from-tag/action.yml new file mode 100644 index 0000000..51245c4 --- /dev/null +++ b/version-from-tag/action.yml @@ -0,0 +1,55 @@ +name: Versione da tag +description: Valida il tag in compilazione e popola le variabili di versionamento. + +inputs: + ref-name: + description: Nome del tag o ref da elaborare. + required: true + +outputs: + appver: + description: Versione applicativa in formato n.n.n.n. + value: ${{ steps.versione.outputs.appver }} + fullver: + description: Versione completa, comprensiva di eventuale suffisso. + value: ${{ steps.versione.outputs.fullver }} + suffix: + description: Suffisso estratto dal tag, comprensivo del trattino iniziale quando presente. + value: ${{ steps.versione.outputs.suffix }} + version: + description: Versione normalizzata senza punti, mantenendo l'eventuale suffisso con trattino. + value: ${{ steps.versione.outputs.version }} + +runs: + using: composite + steps: + - name: Calcola versione da tag + id: versione + shell: bash + run: | + set -euo pipefail + + ref_name='${{ inputs.ref-name }}' + + if [[ -z "$ref_name" ]]; then + echo "Input 'ref-name' mancante." + exit 1 + fi + + tag="${ref_name#v}" + + if [[ "$tag" =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(-.+)?$ ]]; then + appver="${BASH_REMATCH[1]}" + suffix="${BASH_REMATCH[2]:-}" + else + echo "Formato tag non valido: '$ref_name'. Atteso: v1.0.0.0 oppure v1.0.0.0-suffisso" + exit 1 + fi + + fullver="$appver$suffix" + version="${appver//./}$suffix" + + echo "appver=$appver" >> "$GITHUB_OUTPUT" + echo "fullver=$fullver" >> "$GITHUB_OUTPUT" + echo "suffix=$suffix" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT"