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.
La instalación en una línea
Sección titulada «La instalación en una línea»irm https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.ps1 | iexDeduce la arquitectura, coge la release más nueva, comprueba el archivo contra
la suma de verificación que esa release publicó, y deja ghchronicle.exe en
%LOCALAPPDATA%\Programs\ghchronicle. Una suma que no cuadra lo detiene sin
instalar nada, y no hay parámetro para saltarse ese paso. Si cosign 2.4.2 o
posterior ya está en tu PATH, verifica además que el propio fichero de sumas
viene del workflow de release, la misma comprobación que hace install.sh, y
una firma que cosign no confirma lo detiene, con lo que dijo cosign, para que
una máquina que no llega a Sigstore no se tome por un fichero falsificado. Un
cosign más antiguo no sabe leer el bundle de la firma, así que entonces, como
sin cosign, dice que solo se verificó la suma.
Después añade ese directorio a tu PATH, el de usuario, para que
ghchronicle funcione por nombre. Los programas ya abiertos conservan el PATH
con el que arrancaron, así que abre una terminal nueva. Windows lee el PATH de
máquina antes que el de usuario, así que otro ghchronicle instalado para todo
el mundo, o una compilación vieja de go install, puede seguir respondiendo al
nombre cuando esto termina; el script avisa cuando encuentra uno. No escribe nada fuera
de tu perfil ni pide elevación: una instalación para toda la máquina es cosa de
un instalador con su aviso de UAC, no de un script bajado de la red.
Para fijar una versión, elegir el directorio o no tocar el PATH, el script
admite parámetros, y eso pide la forma algo más larga porque iex no tiene
dónde ponerlos:
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.ps1))) -Version 2.6.5 -BinDir C:\tools -NoPathUpdateEl resto de esta página es esa misma instalación hecha a mano, que es lo que seguir cuando quieres saber exactamente qué ha aterrizado dónde, o cuando prefieres no ejecutar un script que no has escrito.
Después deja que escriba la configuración:
ghchronicle -setupPide un token y dónde van los números, comprueba cada respuesta contra aquello
que nombra, y escribe un config.yaml en %APPDATA%\ghchronicle y, si lo
quieres, una tarea programada que registras con schtasks.
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 = "2.6.5"$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 2.4.2 o posterior; uno más antiguo no sabe leer el bundle y falla diga lo que diga el fichero. 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_VERSION_windows_ARCH.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 -versionResponde con una línea: el número de la versión, y después el commit y la fecha de compilación de esa versión.
ghchronicle 2.6.5 (commit <commit>, built <date>)El token, y el resto del entorno
Sección titulada «El token, y el resto del entorno»Un ${VAR} en una credencial, una dirección o una ruta de fichero de la
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 las cinco
que corren cada quince minutos, actions, events, notifs, activity
y ratelimit, corren cuatro veces menos, 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/v2/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í no tiene commit ni fecha de compilación de los que informar: 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. -version nombra en su
lugar lo que la compilación sí registra, la versión del módulo que descargó
la orden go y la versión de Go que lo compiló:
ghchronicle 2.6.5 (module v2.6.5, built with <go version>)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, registro, caché | 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, y lo hace en
state_file, sinks.dedupe_file y log.file desde la 2.6.1. Una versión
anterior no expande ninguna ruta, y lee ${LOCALAPPDATA}/ghchronicle/state.json
como un directorio llamado literalmente ${LOCALAPPDATA}; con una de esas,
escribe la ruta entera. %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.
La ruta de la propia configuración es la excepción de la tabla: se da en la
línea de órdenes, donde el colector no expande nada, así que el -config de
una tarea programada la nombra entera,
C:\Users\tu\AppData\Local\ghchronicle\config.yaml.
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. Deja escribibles el fichero de estado y el
fichero de caché que hay a su lado. El colector reemplaza los dos escribiendo
un fichero nuevo y renombrándolo sobre el viejo, y NTFS se niega a reemplazar
un fichero de solo lectura incluso cuando se le pide reemplazarlo, así que un
fichero de estado de solo lectura deja de guardarse y cada pasada avisa
state not saved. El registro de escrituras y los ficheros rotados del
destino de fichero son los dos a los que quita ese atributo él mismo, y una
pasada no falla por ellos.