cu-watcher: monitoraggio automatico degli aggiornamenti SQL Server
Tenere traccia di tutti gli aggiornamenti cumulativi (CU) e General Distribution Release (GDR) di SQL Server è un compito noioso ma critico per chi gestisce ambienti di produzione. Microsoft pubblica le informazioni sulle build su Microsoft Learn, ma non esiste un feed strutturato che avvisi quando esce una nuova patch. cu-watcher risolve il problema: è un tool scritto in Go multipiattaforma, pensato per essere lanciato da cron/Windows Task Scheduler, che scarica le pagine delle build, le analizza, le persiste su SQL Server e invia un’email quando viene rilevata una nuova release.
Cosa fa cu-watcher
Il ciclo di vita di ogni esecuzione si articola in quattro fasi:
- Scarica le pagine “build-versions” di Microsoft Learn per ogni versione SQL Server configurata.
- Analizza le tabelle HTML per estrarre CU, GDR e articoli KB collegati; se disponibile, scarica anche il CSV dei file inclusi in ogni pacchetto KB.
- Persiste tutto su SQL Server, evitando duplicati a tre livelli distinti: snapshot grezzi delle pagine, righe di build, file list KB.
- Notifica via email se vengono rilevate nuove release rispetto all’esecuzione precedente.
L’applicazione funziona in modo molto semplice: non ha demoni, non apre porte, non richiede un runtime aggiuntivo. E’ un singolo eseguibile che si auto-configura leggendo un file YAML e un file .env.
Prerequisiti
- Go 1.22+ (necessario solo per compilare; il binario distribuito non richiede Go a runtime)
- SQL Server 2017+ (o Azure SQL) raggiungibile dalla macchina che esegue lo scraper
- Un account SMTP (opzionale, solo per le notifiche email)
Installazione e compilazione
Su Mac/Linux
Clonare il repository e compilare per la propria piattaforma:
git clone https://github.com/ddominici/cu-watcher.git
cd cu-watcher
# Compilazione per la piattaforma corrente
make build
# Il binario viene creato in dist/cu-watcher
# Oppure cross-compile per tutte le piattaforme
make all
# Produce:
# dist/cu-watcher-darwin-amd64
# dist/cu-watcher-darwin-arm64
# dist/cu-watcher-linux-amd64
# dist/cu-watcher-linux-arm64
# dist/cu-watcher-windows-amd64.exe
Su Windows
Scaricare lo zip dal sito https://github.com/ddominici/cu-watcher e scompattarlo nella cartella desiderata.
# Compilazione per Windows
set GOOS=windows
set GOARCH=amd64
go build -ldflags "-s -w -X main.version=1.0" -o cu-watcher.exe .\cmd
Configurazione
cu-watcher usa due file distinti per tenere separata la configurazione non sensibile dai segreti:
config.yaml— parametri di comportamento.env— credenziali e connection string
Struttura di config.yaml
db:
connectionString: "${DB_CONNECTION_STRING}"
scraper:
userAgent: "CUWatcher/1.0 (+contact: info@example.it)"
timeoutSeconds: 60
maxConcurrency: 4
delayBetweenRequestsMs: 250
followKbLinks: true
maxKbToFetch: 500
since: "2017-01-01"
sources:
- key: "sql2025"
majorVersion: 2025
url: "https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2025/build-versions"
- key: "sql2022"
majorVersion: 2022
url: "https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/build-versions"
- key: "sql2019"
majorVersion: 2019
url: "https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/build-versions"
- key: "sql2017"
majorVersion: 2017
url: "https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2017/build-versions"
logging:
level: "info"
file: "logs/cu-watcher.log"
maxSizeMB: 50
maxBackups: 10
maxAgeDays: 14
email:
enabled: false
from: "${EMAIL_FROM}"
to:
- "${EMAIL_TO}"
smtpHost: "${EMAIL_SMTP_HOST}"
smtpPort: 465
username: "${EMAIL_USERNAME}"
password: "${EMAIL_PASSWORD}"
useTLS: true
I placeholder ${VARIABILE} vengono espansi automaticamente prima del parsing YAML: il caricatore legge prima il file .env (se esiste), poi sostituisce le variabili all’interno del file YAML. Le variabili già presenti nell’ambiente di sistema hanno sempre priorità sul file .env.
File .env
# Copia da .env.example e compila con i valori reali
DB_CONNECTION_STRING=sqlserver://sa:StrongPassword123@sqlserver.internal:1433?database=cu-watcher&encrypt=true&trustservercertificate=true
EMAIL_FROM=cu-watcher@example.com
EMAIL_TO=dba-team@example.com
EMAIL_SMTP_HOST=smtp.example.com
EMAIL_USERNAME=alerts@example.com
EMAIL_PASSWORD=s3cr3t
Per aggiungere una nuova versione SQL Server al monitoraggio basta aggiungere una voce alla sezione sources di config.yaml — nessuna modifica al codice.
ATTENZIONE !!! cu-watcher è stato testato per le versioni di SQL Server a partire da 2017 e fino alla 2025.
Su Windows, le variabili vanno impostate dalle proprietà di sistema (Windows + R, scrivere sysdm.cpl, premere INVIO, quindi cliccare sulla tab Advanced e sul pulsante Environment Variables), mantenendo gli stessi nomi.

La distribuzione di cu-watcher è veramente banale: un singolo file eseguibile e il file .yaml di configurazione. Non serve altro. La cartella logs viene creata automaticamente. Se dovesse dare errore durante la sua creazione, crearla manualmente ed assegnare alla cartella i permessi adeguati (succede, ad esempio, se si installa cu-watcher nella cartella C:\Program Files, che ha permessi ristretti).

Inizializzazione del database
Prima del primo utilizzo occorre creare il database in SQL Server e le tabelle statiche.
Il flag --init-db esegue il codice per farlo ed è a prova di errore, nel senso che ogni CREATE TABLE è protetta da IF OBJECT_ID(...) IS NULL in modo da non generare errori:
./dist/cu-watcher --init-db
Le tabelle create sono:
| Tabella | Scopo |
|---|---|
dbo.RawPages |
Snapshot grezzi delle pagine HTTP (HTML/CSV) con SHA256 |
dbo.KbArticles |
Contenuto degli articoli KB (titolo, testo, sezioni) |
dbo.KbPackageFiles |
File list CSV di ogni pacchetto KB |
Le tabelle delle build vengono create automaticamente on-demand dalla funzione EnsureTopicTable. Il nome segue lo schema dbo.Sql{MajorVersion}_{slug}:
| Slug | Condizione |
|---|---|
CU_Builds |
Argomento contiene “cumulative update” o “aggiornamento cumulativo” |
GDR_Builds |
Argomento contiene “gdr” |
AzureConnectPack_Builds |
Argomento contiene “azure connect” |
Other_Builds |
Tutto il resto |
Esempi di tabelle generate: dbo.Sql2022_CU_Builds, dbo.Sql2019_GDR_Builds, dbo.Sql2017_CU_Builds.

Utilizzo pratico
Esecuzione standard (tutti i source)
./dist/cu-watcher
Scarica tutte le sorgenti configurate, persiste le righe nuove e, se attivata, invia l’email di notifica.
Solo alcune versioni SQL Server
./dist/cu-watcher --only sql2022,sql2019
Elabora solo le sorgenti con key sql2022 e sql2019. Utile per test o aggiornamenti mirati.
Filtrare per data (–since)
# Ignora release precedenti al 1° gennaio 2024
./dist/cu-watcher --since 2024-01-01
Il filtro opera sui dati estratti prima dell’insert: le righe con ReleaseDate antecedente alla data specificata vengono scartate. Utile per la prima esecuzione su un database già popolato parzialmente.
Disabilitare il follow dei link KB
./dist/cu-watcher --follow-kb=false
Per impostazione predefinita, cu-watcher segue tutti i link KB trovati nelle pagine di build e scarica gli articoli corrispondenti. Con questo flag il processo si limita alle sole pagine di build-versions, riducendo il numero di richieste HTTP.
Limitare le KB scaricate
./dist/cu-watcher --max-kb 50
Scarica al massimo 50 articoli KB per esecuzione. Comodo per le prime esecuzioni su database vuoti dove la coda KB potrebbe avere centinaia di voci.
Override della connection string
./dist/cu-watcher --connection "sqlserver://sa:pass@localhost:1433?database=test"
Sovrascrive db.connectionString da config.yaml senza modificare il file. Utile per ambienti multipli (staging/production) con la stessa configurazione di base.
Override del log level e del log file
./dist/cu-watcher --log-level debug --log-file /var/log/cu-watcher/run.log
Il logger usa zap con rotazione automatica (lumberjack): maxSizeMB, maxBackups, maxAgeDays controllano la politica di retention.
Notifica email “latest builds” on-demand
./dist/cu-watcher --notify-latest
Interroga il database e invia un’email con la build più recente (per data di rilascio) per ogni tabella CU_Builds e GDR_Builds presente. Utile per un report settimanale della situazione corrente, indipendentemente da nuove release.
Config alternativa
./dist/cu-watcher --config /etc/cu-watcher/config.yaml
Permette di avere configurazioni diverse per ambienti diversi, o di installare il binario in /usr/local/bin mantenendo la configurazione altrove.
Schedulazione con cron
Il caso d’uso principale è l’esecuzione periodica via cron. Esempio su Linux (crontab con crontab -e):
# Esegui cu-watcher ogni giorno alle 06:00
0 6 * * * /usr/local/bin/cu-watcher --config /etc/cu-watcher/config.yaml >> /var/log/cu-watcher/cron.log 2>&1
Su macOS con launchd, creare /Library/LaunchDaemons/it.sqlserverinfo.cu-watcher.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>it.sqlserverinfo.cu-watcher</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/cu-watcher</string>
<string>--config</string>
<string>/etc/cu-watcher/config.yaml</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/var/log/cu-watcher/stdout.log</string>
<key>StandardErrorPath</key>
<string>/var/log/cu-watcher/stderr.log</string>
</dict>
</plist>
Per ambienti containerizzati, il binario si presta a essere eseguito come Job Kubernetes o ECS Task:
# Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: cu-watcher
spec:
schedule: "0 6 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cu-watcher
image: ghcr.io/your-org/cu-watcher:latest
args: ["--config", "/config/config.yaml"]
volumeMounts:
- name: config
mountPath: /config
env:
- name: DB_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: cu-watcher-secrets
key: db-connection-string
volumes:
- name: config
configMap:
name: cu-watcher-config
restartPolicy: OnFailure
Notifiche email
Le notifiche sono disabilitate per default (enabled: false). Per abilitarle:
email:
enabled: true
smtpPort: 587 # STARTTLS
useTLS: false # false = STARTTLS (587), true = TLS implicito (465)
cu-watcher supporta entrambi i protocolli di cifratura SMTP:
- Porta 587 + STARTTLS: connessione in chiaro, poi upgrade via
EHLO/STARTTLS. Usato da Gmail, Outlook, la maggior parte dei provider moderni. - Porta 465 + TLS implicito: connessione TLS fin dall’inizio (
tls.Dial). Usato da alcuni provider legacy come Aruba.
La notifica “new releases” viene inviata solo se l’esecuzione ha trovato almeno una riga con KbNumber non ancora presente nel database. Righe senza KbNumber non vengono conteggiate per evitare falsi positivi (identità ambigua).
Esempio di email ricevuta:
Subject: CU Watcher: 2 new SQL Server update(s) detected
The following new SQL Server fixes/CUs have been detected:
• [SQL 2022] SQL Server 2022 CU17
Build: 16.0.4185.3
KB: KB5048038
Released: 2025-11-13
URL: https://support.microsoft.com/help/5048038
• [SQL 2019] SQL Server 2019 CU31
Build: 15.0.4430.1
KB: KB5046862
Released: 2025-11-13
URL: https://support.microsoft.com/help/5046862
Architettura interna
HTTP client con retry
Il client HTTP (internal/httpx) applica automaticamente fino a 5 tentativi con backoff esponenziale quadratico (attempt² × 300ms) per errori di rete e risposte 429/5xx. Un delay configurabile (delayBetweenRequestsMs) viene applicato prima di ogni richiesta per non sovraccaricare i server Microsoft.
Ogni risposta viene salvata nella tabella dbo.RawPages con il suo hash SHA256. La deduplicazione avviene tramite un vincolo UNIQUE su (SourceKey, SHA256): se la pagina non è cambiata dall’ultima esecuzione, il contenuto non viene riscritto.
Parser HTML multi-lingua
Il parser di build-versions (internal/parse/build_versions.go) gestisce sia le intestazioni in italiano sia quelle in inglese delle tabelle Microsoft Learn. Questo perché le pagine possono essere servite in lingue diverse a seconda della geo-localizzazione del client o del profilo utente:
updateName := pick(m,
"Nome dell'aggiornamento cumulativo", "Cumulative update name",
"Nome GDR", "GDR name",
)
Per default vengono rilevati e memorizzati i dati nella versione inglese.
Parser CSV file list KB
I CSV pubblicati su download.microsoft.com hanno caratteristiche specifiche che il parser gestisce esplicitamente:
- BOM UTF-8 in testa, rimosso con
strings.TrimPrefix - Struttura multi-sezione: titolo componente → riga header → righe file
- Formato data
22-Jan-26parsato con layout"02-Jan-06"(anno a 2 cifre, Go interpreta 00-99 come 2000-2099) - Valori
n/aper versione o piattaforma non applicabile
Batch insert e limite parametri SQL Server
SQL Server ammette al massimo 2100 parametri per query. Gli insert batch calcolano dinamicamente maxRows = 2000 / colsPerRow e suddividono automaticamente i dati in chunk sicuri:
const colsPerRow = 8
const maxParams = 2000
maxRows := maxParams / colsPerRow // = 250 righe per batch
Tutti i nomi di tabella costruiti dinamicamente passano per SanitizeIdentifier() prima di essere interpolati nelle query, prevenendo SQL injection.
Deduplicazione a tre livelli
| Entità | Meccanismo |
|---|---|
RawPages |
IF NOT EXISTS su (SourceKey, SHA256) — stessa pagina non riscritta |
BuildRow |
Pre-query per KB già esistenti — invia email solo per righe veramente nuove |
KbArticles |
MERGE su KbNumber — aggiorna dati se l’articolo cambia |
KbPackageFiles |
HasKbFiles check — il CSV non viene riscaricato se già presente |
Interrogare i dati raccolti
Una volta avviato il sistema, i dati sono immediatamente interrogabili con SQL standard. Alcuni esempi utili:
-- Ultime 10 CU per SQL Server 2022
SELECT TOP 10
UpdateName,
SqlBuild,
KbNumber,
ReleaseDate
FROM dbo.Sql2022_CU_Builds
ORDER BY ReleaseDate DESC;
-- Confronto build tra versioni
SELECT
'2022' AS Version, UpdateName, SqlBuild, ReleaseDate FROM dbo.Sql2022_CU_Builds
WHERE ReleaseDate >= '2024-01-01'
UNION ALL
SELECT
'2019', UpdateName, SqlBuild, ReleaseDate FROM dbo.Sql2019_CU_Builds
WHERE ReleaseDate >= '2024-01-01'
ORDER BY ReleaseDate DESC;
-- Articoli KB con testo completo
SELECT
KbNumber,
Title,
ReleaseDate,
ProductVersion,
LEFT(ContentText, 500) AS Excerpt
FROM dbo.KbArticles
WHERE KbNumber = 'KB5048038';
-- File inclusi in un pacchetto KB
SELECT
Component,
FileName,
FileVersion,
FileSizeBytes,
FileDate,
Platform
FROM dbo.KbPackageFiles
WHERE KbNumber = 'KB5048038'
ORDER BY Component, FileName;
-- Snapshots grezzi delle pagine (per debug)
SELECT TOP 5
SourceKey,
RetrievedAtUtc,
StatusCode,
Sha256,
LEN(Html) AS HtmlBytes
FROM dbo.RawPages
ORDER BY RetrievedAtUtc DESC;
Workflow consigliato per il primo avvio
# 1. Copia e compila la configurazione
cp .env.example .env
# Edita .env con i valori reali
# 2. Crea le tabelle statiche
./dist/cu-watcher --init-db
# 3. Prima esecuzione limitata: solo SQL 2022, ultimi 2 anni, senza KB
./dist/cu-watcher \
--only sql2022 \
--since 2023-01-01 \
--follow-kb=false \
--log-level debug
# 4. Aggiungi le altre versioni SQL Server
./dist/cu-watcher \
--only sql2019,sql2017 \
--since 2023-01-01 \
--follow-kb=false
# 5. Carica tutti gli articoli KB (può richiedere diversi minuti)
./dist/cu-watcher --max-kb 500
# 6. Verifica il contenuto del DB
# (esegui le query di esempio sopra)
# 7. Attiva le notifiche email in config.yaml e pianifica il cron
Estensibilità
Aggiungere una nuova versione SQL Server
Basta aggiungere una voce in config.yaml:
sources:
- key: "sql2016"
majorVersion: 2016
url: "https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2016/build-versions"
Nessuna modifica al codice necessaria. Le tabelle dbo.Sql2016_CU_Builds e dbo.Sql2016_GDR_Builds vengono create automaticamente al primo run.
Aggiungere una nuova variabile sensibile
- Aggiungere la variabile in
.env.examplecon un valore placeholder - Usare
${NOME_VARIABILE}nel punto corrispondente diconfig.yaml - Leggere il valore nella struct
Configininternal/config/config.go
Struttura dei package Go
Il progetto rispetta il principio di nessuna dipendenza circolare: logging e httpx non importano config. I valori vengono trasferiti da config ai package di destinazione tramite struct proprie, esplicitamente popolate in cmd/main.go:
// main.go costruisce esplicitamente le struct dei sotto-package
log := logging.New(logging.Config{
Level: cfg.Logging.Level,
File: cfg.Logging.File,
MaxSizeMB: cfg.Logging.MaxSizeMB,
// ...
})
client := httpx.New(httpx.ScraperCfg{
UserAgent: cfg.Scraper.UserAgent,
TimeoutSeconds: cfg.Scraper.TimeoutSeconds,
DelayBetweenRequestsMs: cfg.Scraper.DelayBetweenRequestsMs,
}, log)
Questo pattern evita un problema sottile di Go: struct con field tag yaml diversi sono tipi distinti, e un cast diretto fallirebbe a runtime.
Considerazioni operative
Throttling: il parametro delayBetweenRequestsMs (default: 250ms) introduce un delay tra ogni richiesta HTTP. Con 4 versioni SQL Server e 500 articoli KB la prima esecuzione completa può richiedere circa 2-3 minuti. Le esecuzioni successive sono molto più veloci grazie alla deduplicazione.
Dimensione del log file: con il logging a livello info, ogni esecuzione produce circa 10-50 KB di log a seconda del numero di pagine elaborate. La rotazione automatica con maxSizeMB: 50 e maxBackups: 10 garantisce al massimo 500 MB di log totali.
Crescita del database: la tabella dbo.RawPages è quella che cresce più velocemente, conservando l’HTML grezzo di ogni pagina distinta (per SHA256). Si può ridurre la crescita aumentando delayBetweenRequestsMs (minor frequenza di fetch) o impostando una policy di pulizia periodica sui record più vecchi.
Idempotenza: cu-watcher è progettato per essere sicuro da eseguire più volte. La deduplicazione a tre livelli garantisce che esecuzioni ripetute non producano righe duplicate, notifiche spurie o errori di chiave duplicata.
Conclusioni
cu-watcher risolve in modo pragmatico il problema del monitoraggio delle patch SQL Server: nessun servizio in background, nessuna dipendenza esterna a runtime, configurazione minima. Un singolo binario schedulato che mantiene un archivio strutturato e interrogabile di tutte le build pubblicate da Microsoft, con alerting automatico via email quando escono nuove release.