# macOS

El camino completo en macOS: el archivo darwin, la cuarentena, un agente o demonio de launchd y compilarlo tú mismo.

Source: https://docs.jmrp.io/ghchronicle/es/install/macos/

El mismo binario estático que en todas partes, compilado para `darwin` tanto en
Apple silicon como en Intel. macOS no es una plataforma que solo se compile y
se dé por buena: cada cambio ejecuta la suite unitaria entera y la suite de
extremo a extremo en un runner de macOS, junto a Linux y Windows.

## La instalación en una línea

```sh
curl -fsSL https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.sh | bash
```

Deduce la arquitectura, coge la release más nueva, comprueba el archivo contra
la suma de verificación que esa release publicó, y deja el binario en
`/usr/local/bin` si tiene permiso de escritura. Si no lo tiene, coge el primero
de `~/.local/bin` y `~/bin` que exista y ya esté en tu `PATH`, y recurre a
`~/.local/bin` cuando no lo está ninguno. Una suma que no cuadra lo detiene sin
instalar nada, y no hay opción para saltarse ese paso. Si `cosign` 2.4.2 o
posterior ya está en la máquina, verifica además que el propio fichero de sumas
viene del workflow de release, y una firma que cosign no confirma lo detiene,
con lo que dijo cosign. 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.
Termina ejecutando lo que ha instalado, así que la versión que ves
es la del fichero recién escrito, y avisa cuando otro `ghchronicle` anterior en
tu `PATH` sigue ganando el nombre.

Fijar una versión, o elegir dónde va:

```sh
curl -fsSL https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.sh | VERSION=2.6.5 BIN_DIR=~/bin bash
```

> **Leerlo antes es justo para lo que sirve que sea corto**
>
> Meter cualquier cosa por una tubería a un shell es ejecutar lo que esa URL
> responda hoy. `curl -fsSL <url> -o install.sh`, léelo, y luego
> `bash install.sh`: es el mismo script, y es lo bastante corto para leerlo.

El 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.

## Elegir el archivo

Los archivos de release dicen `darwin`, que es el nombre que la cadena de
herramientas de Go da al sistema; macOS es el nombre que le da Apple. `uname
-m` dice qué arquitectura es.

| Lo que dice `uname -m` | La máquina    | El archivo que toca |
| ---------------------- | ------------- | ------------------- |
| `arm64`                | Apple silicon | `darwin_arm64`      |
| `x86_64`               | Intel         | `darwin_amd64`      |

```sh
VERSION=2.6.5
arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; esac
base=https://github.com/jmrplens/ghchronicle/releases/download/v$VERSION
curl -fsSLO "$base/ghchronicle_${VERSION}_darwin_${arch}.tar.gz"
```

> **Nombra la versión en vez de pedir la más nueva**
>
> El repositorio publica una etiqueta mayor móvil junto a las releases
> numeradas, porque la Action está listada en el Marketplace y ese listado
> necesita una. La release de esa etiqueta **no lleva ningún fichero**, así que
> nunca construyas una URL de descarga a partir de la etiqueta mayor: detrás no
> hay nada que descargar. Coge la versión de la
> [página de releases](https://github.com/jmrplens/ghchronicle/releases) y
> escríbela, como arriba.

## Comprobar lo que has descargado

Junto a los archivos se publican dos ficheros: `checksums.txt`, que lleva el
SHA-256 de cada archivo, y `checksums.txt.sigstore.json`, que es una firma
sobre ese fichero.

1. Coge el fichero de sumas y su firma.

    ```sh
    curl -fsSLO "$base/checksums.txt"
    curl -fsSLO "$base/checksums.txt.sigstore.json"
    ```

2. Comprueba el archivo contra él. macOS trae `shasum` y no el `sha256sum` de
   una máquina Linux, así que se selecciona la línea de tu archivo y se le pasa
   por la tubería, que funciona con cualquier versión de `shasum`.

    ```sh
    grep "darwin_${arch}.tar.gz$" checksums.txt | shasum -a 256 -c -
    ```

    ```text
    ghchronicle_2.6.5_darwin_arm64.tar.gz: OK
    ```

3. Comprueba el propio fichero de sumas, si tienes
   [cosign](https://docs.sigstore.dev/cosign/system_config/installation/)
   2.4.2 o posterior. Uno más antiguo no sabe leer el bundle y falla diga lo
   que diga el fichero.

    ```sh
    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.txt
    ```

    ```text
    Verified OK
    ```

La firma es sin claves: la identidad que se verifica es el workflow que se
ejecutó, anotado en un registro público de transparencia, y por eso las dos
opciones `--certificate` no son opcionales. Sin ellas cosign confirmaría que
alguien firmó el fichero, que no es la pregunta.

Después deja que escriba la configuración:

```sh
ghchronicle -setup
```

Pide un token y dónde van los números, comprueba cada respuesta contra aquello
que nombra, y escribe un `config.yaml` y, si lo quieres, un agente de launchd
en `~/Library/LaunchAgents`. El instalador de arriba lo ofrece como último
paso.

## Ponerlo en el PATH

El archivo lleva tres ficheros y ningún directorio.

- ghchronicle_VERSION_darwin_ARCH.tar.gz
  - ghchronicle el binario
  - LICENSE
  - README.md

- **Para todos**

  ```sh
  tar -xzf "ghchronicle_${VERSION}_darwin_${arch}.tar.gz" ghchronicle
  sudo install -m 755 ghchronicle /usr/local/bin/
  ghchronicle -version
  ```

  `/usr/local/bin` está en el `PATH` por omisión de cualquier instalación de
  macOS, en las dos arquitecturas, porque `/etc/paths` lo lista el primero.

- **Para una cuenta**

  ```sh
  mkdir -p ~/bin
  tar -xzf "ghchronicle_${VERSION}_darwin_${arch}.tar.gz" -C ~/bin ghchronicle
  chmod 755 ~/bin/ghchronicle
  echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zprofile
  ```

  `~/bin` no está en el `PATH` por omisión, de ahí la última línea. `zsh` es
  el intérprete de acceso en todo macOS soportado.

> **Si macOS se niega a ejecutarlo**
>
> El binario no lleva firma de Developer ID ni está notarizado, así que una
> copia que llegue con el atributo de cuarentena se rechaza con «no se puede
> abrir porque no se puede verificar el desarrollador». El atributo no viaja
> dentro del archivo: lo pone el proceso que escribe cada fichero, y lo heredan
> los procesos que ese arranca. Un navegador escribe con la marca el `.tar.gz`
> que descarga, `curl` no, y `tar -xzf` lanzado desde el Terminal escribe un
> binario sin marca en cualquiera de los dos casos. El caso que muerde es abrir
> el archivo en el Finder, porque Utilidad de Archivos pasa la marca a lo que
> extrae. Así que mira antes de borrar nada:
>
> ```sh
> xattr -l "ghchronicle_${VERSION}_darwin_${arch}.tar.gz"  # lo que marcó el navegador
> xattr -l ghchronicle                             # el binario extraído
> xattr -c ghchronicle                             # quitarlo
> ```
>
> `xattr -c` quita todos los atributos extendidos y termina bien cuando no hay
> ninguno. `xattr -d com.apple.quarantine` no: en un binario extraído desde el
> Terminal se para con `No such xattr: com.apple.quarantine`, que parece una
> instrucción rota y es solo que el atributo nunca estuvo ahí.

## Ejecutarlo una vez

El binario necesita un fichero de configuración y un token, y
[el inicio rápido](https://docs.jmrp.io/ghchronicle/es/start/quickstart/) escribe los dos en seis
pasos. Con eso hecho:

```sh
ghchronicle -config config.yaml -list   # qué se recogería
ghchronicle -config config.yaml -once   # una pasada y termina
```

## Dejarlo corriendo con launchd

launchd es lo que macOS tiene en vez de systemd, y la primera decisión que te
pide es agente o demonio.

|                | Un LaunchAgent            | Un LaunchDaemon            |
| -------------- | ------------------------- | -------------------------- |
| Vive en        | `~/Library/LaunchAgents/` | `/Library/LaunchDaemons/`  |
| Corre como     | tú                        | `root`, o el `UserName` que le des |
| Corre cuando   | has iniciado sesión       | la máquina está encendida, desde el arranque |
| Bueno para     | un portátil que usas      | un Mac que se queda encendido |

El agente es por donde empezar. No necesita `sudo`, y un colector que se para
mientras el dueño del portátil está fuera de sesión no pierde nada que la
siguiente pasada no recoja.

```xml title="~/Library/LaunchAgents/io.jmrp.ghchronicle.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>io.jmrp.ghchronicle</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/ghchronicle</string>
    <string>-config</string>
    <string>/Users/tu/Library/Application Support/ghchronicle/config.yaml</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>GITHUB_TOKEN</key>
    <string>github_pat_...</string>
  </dict>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/Users/tu/Library/Logs/ghchronicle.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/tu/Library/Logs/ghchronicle.log</string>
</dict>
</plist>
```

1. Protege el fichero antes de que lleve un token, y luego cárgalo.

    ```sh
    chmod 600 ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
    ```

2. Mira que está arriba, y lee lo que dice.

    ```sh
    launchctl print gui/$(id -u)/io.jmrp.ghchronicle
    tail -f ~/Library/Logs/ghchronicle.log
    ```

3. Después de editar el fichero, descárgalo y vuelve a cargarlo. launchd lee la
   lista de propiedades una sola vez, al arrancarla.

    ```sh
    launchctl bootout gui/$(id -u)/io.jmrp.ghchronicle
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
    ```

> **El token queda en la lista de propiedades**
>
> Un trabajo de launchd no lee el perfil de tu intérprete, así que
> `EnvironmentVariables` es donde tiene que ir el token. Ese fichero es
> entonces el equivalente en macOS del fichero de entorno de systemd, y merece
> el mismo trato: modo `600`, nunca en un repositorio, y acordarse de él cuando
> se rote el token. `launchctl setenv` es la alternativa, y es peor: deja el
> token en todos los procesos de la sesión.

### O una pasada con temporizador

`StartInterval` es el cron de launchd, en segundos, y `-once` es el modo que le
va. Sustituye `KeepAlive` por él y añade `-once` a `ProgramArguments`:

```xml
  <key>StartInterval</key>
  <integer>3600</integer>
```

Deja `state_file` en una ruta que sobreviva, en este modo sobre todo. Sin él
cada ejecución recoge todas las familias, diga lo que diga su cadencia, y
vuelve a recorrer la lista de estrellas, el historial entero de estrellas y las
pull requests en coautoría; el fichero de caché que hay a su lado es lo que
deja a una ejecución preguntar a GitHub solo por lo que cambió.

## Compilarlo desde fuente

Go 1.27.2 o más nuevo es lo que declara el módulo. La compilación fija
`CGO_ENABLED=0`, así que las herramientas de línea de órdenes de Xcode no hacen
falta para ella.

- **go install**

  ```sh
  go install github.com/jmrplens/ghchronicle/v2/cmd/ghchronicle@latest
  ```

  Cae en `$(go env GOPATH)/bin`, que es `~/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ó:

  ```text
  ghchronicle 2.6.5 (module v2.6.5, built with <go version>)
  ```

- **make**

  ```sh
  git clone https://github.com/jmrplens/ghchronicle
  cd ghchronicle
  make build      # a bin/ghchronicle
  make install    # a GOBIN, sellado como una compilación de release
  ```

  El Makefile está escrito para GNU Make 3.81, que es la versión que trae
  macOS, así que el `make` de serie lo ejecuta.

## 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. macOS no tiene la costumbre de
`/etc/ghchronicle`, así que estos son los sitios convencionales, no sitios que
la herramienta conozca:

| Fichero           | Para un agente                                | Para un demonio                |
| ----------------- | --------------------------------------------- | ------------------------------ |
| Configuración     | `~/Library/Application Support/ghchronicle/`  | `/usr/local/etc/ghchronicle/`  |
| Estado, registro, caché | `~/Library/Application Support/ghchronicle/` | `/usr/local/var/ghchronicle/` |
| Log               | `~/Library/Logs/ghchronicle.log`              | `/usr/local/var/log/`          |

`state_file` tiene un valor por omisión propio, `ghchronicle-state.json` en el
directorio de trabajo, con el registro de escrituras al lado como
`ghchronicle-state-written.bin` y la caché como `ghchronicle-state-cache.bin`.
El directorio de trabajo de un trabajo de launchd no es algo en lo que
apoyarse. Ponlo. Desde la 2.6.1 un ajuste de ruta toma una `~` inicial como el
directorio personal, así que la configuración de un agente puede decir
`state_file: ~/Library/Application Support/ghchronicle/state.json`; una versión
anterior lee la `~` como el nombre de un directorio, y quiere
`/Users/tu/...` escrito entero.
