Windows
Windows es una plataforma publicada, no algo añadido después: cada cambio
ejecuta la suite unitaria entera y la de extremo a extremo en un runner de
Windows, y el código fuente lleva partes solo de Windows allí donde el sistema
se comporta distinto. Lo que sigue está escrito para PowerShell, con la forma
de cmd al lado siempre que las dos difieran.
Lo que cambia aquí
Sección titulada «Lo que cambia aquí»Lee esto primero. Cuatro de las cinco líneas de abajo son la razón de que una orden copiada de una página de Linux no funcione.
- El programa es
ghchronicle.exe. Escribirghchroniclelo encuentra, porquePATHEXTlista.EXE. - Dependencias en ejecución: ninguna. El binario se compila con
CGO_ENABLED=0, así que no hay runtime de C que instalar. - Pararlo es Ctrl+C en su consola. No hay señal que enviarle, y
taskkill /Flo termina sin cerrar sus destinos. - Rutas en la configuración: una barra invertida dentro de un escalar YAML entre comillas dobles es un escape. Usa barras normales.
- Ejecución desatendida: una tarea programada. El binario no es un servicio del Administrador de control de servicios.
Elegir el archivo
Sección titulada «Elegir el archivo»Los archivos de Windows son zip y no tar.gz, y se llaman
ghchronicle_<versión>_windows_<arq>.zip, donde <arq> es amd64 o arm64.
$env:PROCESSOR_ARCHITECTURE | El archivo que toca |
|---|---|
AMD64 | windows_amd64 |
ARM64 | windows_arm64 |
Esa variable describe el proceso, no la máquina. Un PowerShell de 32 bits
en una máquina de 64 informa de x86 y deja la arquitectura real de la
máquina en $env:PROCESSOR_ARCHITEW6432; un PowerShell x64 emulado en una
máquina ARM64 informa de AMD64 y no pone nada más, que es el caso que te
entrega sin ruido el archivo equivocado. Si alguno de los dos puedes ser tú,
pregúntale a la máquina y no al intérprete:
(Get-CimInstance Win32_Processor).Architecture # 9 es x64, 12 es ARM64$version = "1.0.0"$arch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "amd64" }$base = "https://github.com/jmrplens/ghchronicle/releases/download/v$version"$zip = "ghchronicle_${version}_windows_${arch}.zip"Invoke-WebRequest -UseBasicParsing -Uri "$base/$zip" -OutFile $zip-UseBasicParsing no es adorno. En Windows PowerShell 5.1 Invoke-WebRequest
arma su resultado a través del motor de Internet Explorer salvo que se le diga
que no, y falla del todo en una máquina donde Internet Explorer se haya quitado
o nunca haya pasado su configuración de primer arranque, que es el caso de
Server Core y de casi cualquier imagen endurecida. En PowerShell 7 el
modificador se acepta y no hace nada.
Comprobar lo que has descargado
Sección titulada «Comprobar lo que has descargado»checksums.txt lleva el SHA-256 de cada archivo, y
checksums.txt.sigstore.json es una firma sobre ese fichero.
-
Coge el fichero de sumas y compara la única línea que es tuya.
Ventana de terminal Invoke-WebRequest -UseBasicParsing -Uri "$base/checksums.txt" -OutFile checksums.txt$expected = (Select-String -Path checksums.txt -Pattern ([regex]::Escape($zip) + '$')).Line.Split(" ")[0]$actual = (Get-FileHash -Algorithm SHA256 -Path $zip).Hashif ($actual -eq $expected) { "OK" } else { "MISMATCH" }Get-FileHashdevuelve el resumen en mayúsculas ychecksums.txtlo guarda en minúsculas. Aun así comparan igual porque el-eqde PowerShell entre dos cadenas ignora las mayúsculas, que es el único sitio de esta página donde ese comportamiento por omisión resulta cómodo en vez de una trampa. -
Comprueba el propio fichero de sumas, si tienes cosign. La orden es la misma que imprimen las notas de la release, y la misma que ejecuta quien lea la página de Linux o la de macOS.
Ventana de terminal cosign verify-blob `--certificate-identity-regexp 'https://github.com/jmrplens/ghchronicle/.github/workflows/release.yml@refs/tags/.*' `--certificate-oidc-issuer https://token.actions.githubusercontent.com `--bundle checksums.txt.sigstore.json `checksums.txtEl acento grave es la continuación de línea de PowerShell, donde un guion de intérprete usa la barra invertida.
Ponerlo en un sitio y en el PATH
Sección titulada «Ponerlo en un sitio y en el PATH»El archivo lleva tres ficheros y ningún directorio, así que descomprímelo en un directorio que hayas hecho.
Directorioghchronicle_1.0.0_windows_amd64.zip
- ghchronicle.exe el binario
- LICENSE
- README.md
Un PowerShell elevado, porque tanto el directorio como el Path de máquina
piden permisos de administrador.
$dir = "C:\Program Files\ghchronicle"Expand-Archive -Path $zip -DestinationPath $dir -Force$machine = [Environment]::GetEnvironmentVariable("Path", "Machine")[Environment]::SetEnvironmentVariable("Path", "$machine;$dir", "Machine")Sin elevación, y sin tocar nada fuera de tu perfil.
$dir = "$env:LOCALAPPDATA\Programs\ghchronicle"Expand-Archive -Path $zip -DestinationPath $dir -Force$user = [Environment]::GetEnvironmentVariable("Path", "User")[Environment]::SetEnvironmentVariable("Path", "$user;$dir", "User")Un Path nuevo solo llega a los procesos que arranquen después, así que abre
una terminal nueva antes de la comprobación de abajo. La actual se queda con el
entorno que le dieron.
ghchronicle -versionghchronicle 1.0.0 (commit 4e5dfc2, built 2026-09-14T23:04:02Z)El token, y el resto del entorno
Sección titulada «El token, y el resto del entorno»Cada ${VAR} del fichero de configuración se lee del entorno cuando arranca el
proceso, así que el token nunca tiene que estar en el fichero.
# Solo esta ventana$env:GITHUB_TOKEN = "github_pat_..."
# Persistido para esta cuenta[Environment]::SetEnvironmentVariable("GITHUB_TOKEN", "github_pat_...", "User")rem Solo esta ventanaset GITHUB_TOKEN=github_pat_...
rem Persistido para esta cuenta, y NO visible en esta ventanasetx GITHUB_TOKEN "github_pat_..."Ejecutarlo una vez
Sección titulada «Ejecutarlo una vez»El binario necesita un fichero de configuración, y el inicio rápido escribe uno en seis pasos. Guárdalo en UTF-8, y atención a los dos detalles de Windows de debajo.
ghchronicle -config config.yaml -list # qué se recogeríaghchronicle -config config.yaml -once # una pasada y terminaSi el binario está en el directorio actual y no en el PATH, PowerShell
necesita que se le nombre como ruta: .\ghchronicle.exe. Un nombre a secas es
una orden, y el directorio actual no se busca para órdenes.
Rutas en el fichero de configuración
Sección titulada «Rutas en el fichero de configuración»YAML trata la barra invertida como un escape dentro de un escalar entre comillas dobles y como un carácter corriente en cualquier otro sitio. Así que una ruta de Windows entre comillas dobles no es la ruta que escribiste, y normalmente tampoco es YAML válido:
state_file: "C:\ghchronicle\state.json" # ghchronicle: config.yaml: yaml: line N: found unknown escape characterstate_file: C:\ghchronicle\state.json # correcto, escalar simplestate_file: 'C:\ghchronicle\state.json' # correcto, comillas simplesstate_file: C:/ghchronicle/state.json # correcto, y el que convieneLas barras normales son la respuesta más simple: Windows las acepta en una ruta, y sobreviven a entrecomillarlas de cualquiera de las maneras.
El fichero tiene que ser UTF-8
Sección titulada «El fichero tiene que ser UTF-8»Windows PowerShell 5.1, el que viene de serie, no escribe ninguna de las dos
cosas que quiere un analizador de YAML, y escribe una cosa equivocada distinta
según cómo se lo pidas. > y Out-File producen UTF-16LE, que el analizador
lee como binario. Set-Content produce la página de códigos activa del
sistema, normalmente ANSI, que se analiza bien mientras el fichero sea ASCII
puro y destroza el primer carácter acentuado que aparezca. PowerShell 7 usa
UTF-8 sin marca de orden de bytes por omisión y no tiene ninguno de los dos
problemas; en 5.1, sé explícito:
Set-Content -Path config.yaml -Value $text -Encoding utf8utf8 en 5.1 quiere decir UTF-8 con marca de orden de bytes, cosa que el
valor no sabe expresar y de la que el cmdlet no avisa. El analizador del
colector la salta, así que el fichero funciona; una herramienta que lea los
primeros bytes por su cuenta puede que no.
Dejarlo corriendo
Sección titulada «Dejarlo corriendo»No hay modo servicio. El colector es un programa de consola: no habla con el
Administrador de control de servicios, así que registrarlo con sc.exe create
produce un servicio que Windows arranca y luego abandona, informando de que «no
respondió a la petición de inicio o control de manera oportuna». Existen
envoltorios de servicio de terceros y este proyecto ni distribuye ni prueba
ninguno.
La forma corriente de ejecutar algo desatendido en Windows es una tarea programada, y tiene dos formas.
El equivalente de cron en Windows, y el que conviene: no hay nada que parar, y una ejecución perdida cuesta una pasada.
$action = New-ScheduledTaskAction ` -Execute "C:\Program Files\ghchronicle\ghchronicle.exe" ` -Argument '-config "C:\ProgramData\ghchronicle\config.yaml" -once'$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) ` -RepetitionInterval (New-TimeSpan -Hours 1) ` -RepetitionDuration ([TimeSpan]::MaxValue)Register-ScheduledTask -TaskName ghchronicle -Action $action -Trigger $trigger-RepetitionDuration ([TimeSpan]::MaxValue) es lo que deja escrito «para
siempre». La regla del propio Programador de tareas es que una repetición
sin duración se repite indefinidamente, así que omitirlo no es un error,
pero el cmdlet no tiene valor propio por omisión y la tarea queda
registrada sin ninguna duración.
Esta forma trae dos condiciones, y ninguna es la de cron. Una tarea horaria
da a todas las familias una cadencia horaria como mucho, así que el ritmo
de quince minutos de actions se pierde, que es el mismo cambio que
hace cron en Linux.
Y aquí Register-ScheduledTask no nombra ni -User ni -Principal, así
que la tarea queda registrada con la cuenta que la crea y con el tipo de
inicio de sesión por omisión, y corre solo mientras esa cuenta tenga la
sesión iniciada. Un trabajo de cron no se para al cerrar sesión. Para
que esta se comporte igual, regístrala con una entidad de seguridad con
«ejecutar tanto si el usuario inició sesión como si no» marcado, que es
New-ScheduledTaskPrincipal.
Una tarea disparada al iniciar sesión o al arrancar, con el colector en su modo permanente para que cada familia conserve su cadencia.
$action = New-ScheduledTaskAction ` -Execute "C:\Program Files\ghchronicle\ghchronicle.exe" ` -Argument '-config "C:\ProgramData\ghchronicle\config.yaml"'$trigger = New-ScheduledTaskTrigger -AtLogOn$settings = New-ScheduledTaskSettingsSet ` -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) ` -ExecutionTimeLimit ([TimeSpan]::Zero)Register-ScheduledTask -TaskName ghchronicle -Action $action ` -Trigger $trigger -Settings $settings-ExecutionTimeLimit ([TimeSpan]::Zero) es el que importa: el valor por
omisión detiene la tarea a los tres días, que para un proceso pensado para
no terminar es un reinicio que nadie pidió.
Start-ScheduledTask -TaskName ghchronicleGet-ScheduledTaskInfo -TaskName ghchronicle # última ejecución, último resultadoStop-ScheduledTask termina el proceso en vez de pedirle que acabe: es
taskkill /F con otro nombre, y no cierra ningún destino al salir. En la forma
con temporizador no hay nada que parar, que es buena parte de por qué es la
mejor opción por omisión.
Compilarlo desde fuente
Sección titulada «Compilarlo desde fuente»Go 1.27.1 o más nuevo, que es la versión que declara go.mod. CGO_ENABLED=0
quiere decir sin MSVC, sin MinGW y sin SDK de Windows.
go install github.com/jmrplens/ghchronicle/cmd/ghchronicle@latestCae en $(go env GOPATH)\bin, que es %USERPROFILE%\go\bin salvo que lo
hayas movido, y ese directorio tiene que estar en tu Path. Un binario
hecho así informa de su versión pero no de su commit ni de su fecha de
compilación, porque esas dos las sella la compilación de release y un
módulo descargado por el proxy no lleva copia de trabajo de donde leerlas.
git clone https://github.com/jmrplens/ghchroniclecd ghchroniclego build -o ghchronicle.exe .\cmd\ghchroniclego build y no make: el Makefile es un Makefile de GNU cuyas recetas son
intérprete POSIX, así que quiere Git Bash, MSYS2 o WSL. Esta línea es lo
que hace make build, menos el sellado de la versión.
Dónde van los ficheros
Sección titulada «Dónde van los ficheros»El colector no busca el fichero de configuración en ningún sitio concreto:
-config vale por omisión config.yaml relativo al directorio de trabajo,
y detrás no hay ninguna ruta de búsqueda. El directorio de trabajo de una tarea
programada no es algo en lo que apoyarse, así que da todas las rutas del
fichero en absoluto.
| Fichero | Para una tarea de máquina | Para una cuenta |
|---|---|---|
| Configuración | C:/ProgramData/ghchronicle/ | ${LOCALAPPDATA}/ghchronicle/ |
| Estado y registro | C:/ProgramData/ghchronicle/ | ${LOCALAPPDATA}/ghchronicle/ |
| Log | C:/ProgramData/ghchronicle/ | ${LOCALAPPDATA}/ghchronicle/ |
${LOCALAPPDATA} se escribe así porque ${VAR} es la única forma que el
colector expande, desde el entorno, al arrancar el proceso. %VAR% es una
notación del intérprete y para el fichero no significa nada: un
%LOCALAPPDATA% copiado dentro del YAML te da un directorio llamado
literalmente %LOCALAPPDATA%, al lado de donde la tarea estuviera trabajando.
Son %LOCALAPPDATA% en cmd y $env:LOCALAPPDATA en PowerShell los que crean
el directorio en primer lugar.
Un directorio bajo C:\ProgramData lo puede escribir quien lo creó y lo puede
leer todo el mundo, así que créalo elevado y luego concede escritura a la
cuenta con la que corre la tarea. El fichero de estado y su registro de
escrituras son los dos que el colector reescribe en cada pasada; si uno de
ellos está marcado como de solo lectura el colector quita ese atributo él
mismo, porque NTFS se niega a reemplazar un fichero de solo lectura incluso
cuando se le pide reemplazarlo, y una pasada no debería fallar por una
propiedad de fichero.