chore: initial commit local CI/CD system (e2e verified, production-ready)
Sistema CI locale basato su VMware Workstation + Gitea Actions + act_runner. Testato e2e con nsis-plugin-nsinnounp (MSBuild + Python, 4 configurazioni parallele). - scripts/: Invoke-CIJob, Invoke-RemoteBuild, New/Remove-BuildVM, Wait-VMReady, Get-BuildArtifacts - runner/: act_runner config (windows-build label, capacity 4) - gitea/workflows/: build-nsis.yml (template per progetti MSBuild/Python) - template/: script di provisioning template VM (VS BuildTools 2026, .NET SDK 10, Python 3.13) - docs/: ARCHITECTURE, CI-FLOW, OPTIMIZATION, BEST-PRACTICES, Setup-GiteaSSH Verificato: e2e-009 SUCCESS in 02:09, cleanup automatico VM confermato.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
#Requires -RunAsAdministrator
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Downloads, registers, and installs act_runner as a Windows service.
|
||||
|
||||
.DESCRIPTION
|
||||
1. Downloads the act_runner binary from the specified URL (or Gitea releases).
|
||||
2. Registers the runner with the Gitea instance using a registration token.
|
||||
3. Installs it as a Windows service that auto-starts with the system.
|
||||
|
||||
Prerequisites:
|
||||
- Run as Administrator
|
||||
- NSSM (Non-Sucking Service Manager) must be installed and in PATH,
|
||||
OR use the built-in sc.exe method (creates a wrapper via PowerShell).
|
||||
- The Gitea instance must be reachable from this host.
|
||||
|
||||
.PARAMETER GiteaURL
|
||||
Base URL of your Gitea instance. Example: http://192.168.1.50:3000
|
||||
|
||||
.PARAMETER RegistrationToken
|
||||
Runner registration token from Gitea:
|
||||
Admin Panel → Settings → Actions → Runners → "Create Runner Token"
|
||||
|
||||
.PARAMETER InstallDir
|
||||
Directory where act_runner will be installed.
|
||||
Default: F:\CI\act_runner
|
||||
|
||||
.PARAMETER RunnerName
|
||||
Display name for this runner in the Gitea UI.
|
||||
Default: local-windows-runner
|
||||
|
||||
.PARAMETER ActRunnerVersion
|
||||
Version tag to download. Default: latest (resolves via GitHub API).
|
||||
Example: v0.2.11
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Windows service name. Default: act_runner
|
||||
|
||||
.EXAMPLE
|
||||
.\Install-Runner.ps1 `
|
||||
-GiteaURL "http://192.168.1.50:3000" `
|
||||
-RegistrationToken "abc123xyz"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $GiteaURL,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $RegistrationToken,
|
||||
|
||||
[string] $InstallDir = 'F:\CI\act_runner',
|
||||
|
||||
[string] $RunnerName = 'local-windows-runner',
|
||||
|
||||
[string] $ActRunnerVersion = 'latest',
|
||||
|
||||
[string] $ServiceName = 'act_runner'
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ── Resolve download URL ──────────────────────────────────────────────────────
|
||||
if ($ActRunnerVersion -eq 'latest') {
|
||||
Write-Host "[Install-Runner] Resolving latest act_runner version from Gitea releases..."
|
||||
try {
|
||||
$releaseInfo = Invoke-RestMethod -Uri 'https://dl.gitea.com/act_runner/version.json' -UseBasicParsing
|
||||
$ActRunnerVersion = $releaseInfo.latest
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not resolve latest version automatically. Set -ActRunnerVersion explicitly."
|
||||
Write-Warning "Find releases at: https://gitea.com/gitea/act_runner/releases"
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
$downloadUrl = "https://dl.gitea.com/act_runner/${ActRunnerVersion}/act_runner-${ActRunnerVersion}-windows-amd64.exe"
|
||||
$binaryPath = Join-Path $InstallDir 'act_runner.exe'
|
||||
$configPath = Join-Path $InstallDir 'config.yaml'
|
||||
|
||||
Write-Host "[Install-Runner] Version : $ActRunnerVersion"
|
||||
Write-Host "[Install-Runner] Install dir: $InstallDir"
|
||||
Write-Host "[Install-Runner] Service : $ServiceName"
|
||||
|
||||
# ── Create install directory ──────────────────────────────────────────────────
|
||||
if (-not (Test-Path $InstallDir)) {
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
Write-Host "[Install-Runner] Created directory: $InstallDir"
|
||||
}
|
||||
|
||||
# ── Download act_runner binary ────────────────────────────────────────────────
|
||||
if (Test-Path $binaryPath) {
|
||||
Write-Host "[Install-Runner] act_runner.exe already exists — skipping download."
|
||||
Write-Host " Delete $binaryPath to force re-download."
|
||||
}
|
||||
else {
|
||||
Write-Host "[Install-Runner] Downloading from: $downloadUrl"
|
||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $binaryPath -UseBasicParsing
|
||||
Write-Host "[Install-Runner] Downloaded: $binaryPath"
|
||||
}
|
||||
|
||||
# ── Copy config.yaml if not already present ───────────────────────────────────
|
||||
$repoConfigPath = Join-Path $PSScriptRoot 'config.yaml'
|
||||
if (-not (Test-Path $configPath)) {
|
||||
if (Test-Path $repoConfigPath) {
|
||||
Copy-Item $repoConfigPath $configPath
|
||||
Write-Host "[Install-Runner] Copied config.yaml from repo to $configPath"
|
||||
Write-Host " Review and update paths in $configPath before proceeding."
|
||||
}
|
||||
else {
|
||||
Write-Warning "[Install-Runner] config.yaml not found at $repoConfigPath"
|
||||
Write-Warning " Generating default config and registering without custom config."
|
||||
# Generate default config
|
||||
Push-Location $InstallDir
|
||||
& $binaryPath generate-config | Out-File $configPath -Encoding UTF8
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "[Install-Runner] Using existing config.yaml: $configPath"
|
||||
}
|
||||
|
||||
# ── Register runner with Gitea ────────────────────────────────────────────────
|
||||
$stateFile = Join-Path $InstallDir '.runner'
|
||||
if (Test-Path $stateFile) {
|
||||
Write-Host "[Install-Runner] Runner already registered (.runner state file exists)."
|
||||
Write-Host " Delete $stateFile to re-register."
|
||||
}
|
||||
else {
|
||||
Write-Host "[Install-Runner] Registering runner with Gitea at $GiteaURL..."
|
||||
Push-Location $InstallDir
|
||||
try {
|
||||
& $binaryPath register `
|
||||
--no-interactive `
|
||||
--instance $GiteaURL `
|
||||
--token $RegistrationToken `
|
||||
--name $RunnerName `
|
||||
--labels 'windows-build:host,dotnet:host,msbuild:host' `
|
||||
--config $configPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "act_runner register exited with code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Write-Host "[Install-Runner] Runner registered successfully."
|
||||
}
|
||||
|
||||
# ── Install as Windows service ────────────────────────────────────────────────
|
||||
$existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($existingService) {
|
||||
Write-Host "[Install-Runner] Service '$ServiceName' already exists."
|
||||
Write-Host " Stopping and updating..."
|
||||
Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Build service command (act_runner daemon with explicit config)
|
||||
$serviceCmd = "`"$binaryPath`" daemon --config `"$configPath`""
|
||||
|
||||
if ($existingService) {
|
||||
# Update existing service binary path
|
||||
sc.exe config $ServiceName binpath= $serviceCmd | Out-Null
|
||||
}
|
||||
else {
|
||||
# Create new service
|
||||
New-Service `
|
||||
-Name $ServiceName `
|
||||
-BinaryPathName $serviceCmd `
|
||||
-DisplayName 'Gitea act_runner (CI/CD)' `
|
||||
-Description 'Gitea Actions runner for ephemeral VMware build VMs' `
|
||||
-StartupType Automatic | Out-Null
|
||||
Write-Host "[Install-Runner] Service created: $ServiceName"
|
||||
}
|
||||
|
||||
# Configure service recovery: restart on failure
|
||||
sc.exe failure $ServiceName reset= 86400 actions= restart/5000/restart/10000/restart/30000 | Out-Null
|
||||
|
||||
# Start the service
|
||||
Start-Service -Name $ServiceName
|
||||
Write-Host "[Install-Runner] Service '$ServiceName' started."
|
||||
|
||||
# ── Verify ────────────────────────────────────────────────────────────────────
|
||||
Start-Sleep -Seconds 3
|
||||
$svc = Get-Service -Name $ServiceName
|
||||
if ($svc.Status -eq 'Running') {
|
||||
Write-Host "[Install-Runner] act_runner is running as a Windows service."
|
||||
Write-Host "[Install-Runner] Check Gitea Admin → Settings → Actions → Runners to confirm it appears Online."
|
||||
}
|
||||
else {
|
||||
Write-Warning "[Install-Runner] Service status: $($svc.Status). Check Windows Event Log for errors."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Installation complete."
|
||||
Write-Host " Binary : $binaryPath"
|
||||
Write-Host " Config : $configPath"
|
||||
Write-Host " Service : $ServiceName (startup: Automatic)"
|
||||
@@ -0,0 +1,82 @@
|
||||
# act_runner configuration for Local CI/CD System
|
||||
# Documentation: https://gitea.com/gitea/act_runner
|
||||
|
||||
log:
|
||||
# Log level: trace, debug, info, warn, error, fatal
|
||||
level: info
|
||||
# Log format: text or json
|
||||
format: text
|
||||
|
||||
runner:
|
||||
# Path to the runner state file (stores runner registration info)
|
||||
file: .runner
|
||||
|
||||
# Maximum number of concurrent jobs this runner will accept.
|
||||
# Each job spawns one ephemeral VM. Budget: 64GB RAM / ~8GB per VM = 8 max.
|
||||
# Set conservatively to 4 until you benchmark actual memory usage.
|
||||
capacity: 4
|
||||
|
||||
# Environment variables injected into every job's environment.
|
||||
envs:
|
||||
# Path to the template VM - update after provisioning the template
|
||||
GITEA_CI_TEMPLATE_PATH: "F:\\CI\\Templates\\WinBuild\\CI-WinBuild.vmx"
|
||||
# Base directory where ephemeral clone VMs are created
|
||||
GITEA_CI_CLONE_BASE_DIR: "F:\\CI\\BuildVMs"
|
||||
# Base directory for collected build artifacts
|
||||
GITEA_CI_ARTIFACT_DIR: "F:\\CI\\Artifacts"
|
||||
# Windows Credential Manager target name for guest VM credentials
|
||||
GITEA_CI_GUEST_CRED_TARGET: "BuildVMGuest"
|
||||
|
||||
# Optional: load additional env vars from a file (one KEY=VALUE per line)
|
||||
# env_file: "F:\\CI\\runner.env"
|
||||
|
||||
# Maximum duration for a single job before it is killed.
|
||||
# Set this higher than your longest expected build.
|
||||
timeout: 2h
|
||||
|
||||
# Labels this runner advertises to Gitea.
|
||||
# Workflows using "runs-on: windows-build" will be routed to this runner.
|
||||
labels:
|
||||
- "windows-build:host"
|
||||
- "dotnet:host"
|
||||
- "msbuild:host"
|
||||
|
||||
# Uncomment to ignore specific errors
|
||||
# insecure: false
|
||||
|
||||
cache:
|
||||
# Enable the built-in cache server (used by actions/cache steps)
|
||||
enabled: true
|
||||
# Cache storage directory on the host
|
||||
dir: "F:\\CI\\Cache"
|
||||
# External cache server - leave empty to use built-in
|
||||
host: ""
|
||||
port: 0
|
||||
|
||||
container:
|
||||
# IMPORTANT: must be false on this runner.
|
||||
# All builds run in VMware VMs, not Docker containers.
|
||||
enabled: false
|
||||
|
||||
# These settings are ignored when container.enabled is false,
|
||||
# but kept for documentation purposes.
|
||||
# network: ""
|
||||
# privileged: false
|
||||
# options: ""
|
||||
# workdir_parent: ""
|
||||
|
||||
host:
|
||||
# Working directory on the host for act_runner's own workspace files.
|
||||
# Each job gets a subdirectory here for checkout and step scripts.
|
||||
workdir_parent: "F:\\CI\\RunnerWork"
|
||||
|
||||
artifact:
|
||||
# Gitea artifact proxy settings
|
||||
# Artifacts uploaded by steps (actions/upload-artifact) are stored via Gitea.
|
||||
# The runner itself handles local artifact paths via Invoke-CIJob.ps1.
|
||||
enabled: true
|
||||
dir: "F:\\CI\\Artifacts"
|
||||
# Retention in days for artifacts stored by act_runner itself
|
||||
# (distinct from the Gitea server's own artifact retention setting)
|
||||
retention:
|
||||
days: 7
|
||||
Reference in New Issue
Block a user