# macOS

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

Source: https://jmrplens.github.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.

## 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=1.0.0
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 móvil `v1` junto a las releases
> numeradas, porque la Action está listada en el Marketplace y ese listado
> necesita una. La release `v1` **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_1.0.0_darwin_arm64.tar.gz: OK
    ```

3. Comprueba el propio fichero de sumas, si tienes
   [cosign](https://docs.sigstore.dev/cosign/system_config/installation/).

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

## Ponerlo en el PATH

El archivo lleva tres ficheros y ningún directorio.

- ghchronicle_1.0.0_darwin_arm64.tar.gz
  - ghchronicle el binario
  - LICENSE
  - README.md

- **Para todos**

  ```sh
  tar -xzf ghchronicle_1.0.0_darwin_arm64.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_1.0.0_darwin_arm64.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_1.0.0_darwin_arm64.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](/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: es lo que
evita que el recorrido completo de estrellas y el relleno año por año del
calendario de contribuciones vuelvan a ocurrir en cada ejecución.

## Compilarlo desde fuente

Go 1.27.1 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/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í
  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.

- **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 y registro | `~/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`. El directorio de trabajo de un trabajo de
launchd no es algo en lo que apoyarse. Ponlo.
