Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,13 @@ jobs:
- name: Empaqueta la .app y el .dmg
run: just bundle

- name: Sube el .dmg
- name: Sube el .dmg y la actualizacion
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bundle-macos
path: rfirma-app/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
path: |
rfirma-app/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
rfirma-app/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.app.tar.gz
if-no-files-found: error
retention-days: 1

Expand Down Expand Up @@ -317,7 +319,7 @@ jobs:
name: bundle-windows
path: ${{ runner.temp }}/paquetes

- name: Descarga el .dmg de macOS
- name: Descarga el .dmg y la actualizacion de macOS
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: bundle-macos
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
packaging/verify-packages.sh paquetes --against "${{ runner.temp }}/SHA256SUMS.construccion"

# Despues de comparar con la construccion y antes del `SHA256SUMS`, que cubre el `.sig` (ADR-0015).
- name: Firma de actualizacion de cada -setup.exe
- name: Firma de actualizacion de cada -setup.exe y .app.tar.gz
if: ${{ env.IS_PRERELEASE != 'true' }}
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
Expand Down
3 changes: 2 additions & 1 deletion docs/adr/0015-canal-de-distribucion-propio.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ nunca.
| `/apt/` | con `dists/stable/main/binary-amd64/` |
| `/rpm/` | con `repodata/` |
| `/windows/` | el `-setup.exe` y su `.sig` de cada versión, y `latest.json` |
| `/macos/` | el `.app.tar.gz` y su `.sig` de cada versión que lo trae, y `latest.json` |

Estas rutas van dentro del `.flatpakref` y del `.flatpakrepo`, de las órdenes de alta publicadas y del *endpoint*
del *updater* embebido en cada instalación de Windows, así que se fijan aquí.
del *updater* embebido en cada instalación de Windows y de macOS, así que se fijan aquí.

**apt con una sola suite**, no repositorio plano: el plano es más barato y **no admite
`Suites:`/`Components:` en un fichero `.sources` deb822**, que es el formato obligado para
Expand Down
5 changes: 5 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,11 @@ bundle: check-native build-ts
exit 1
fi
echo "dmg: $dmg ($(du -h "$dmg" | cut -f1))"
# La actualización que baja el updater: la .app sola, sin firmar aquí (ADR-0015).
rm -f "$salida"/dmg/*.app.tar.gz
archivo="$salida/dmg/$(basename "$dmg" .dmg).app.tar.gz"
COPYFILE_DISABLE=1 tar -czf "$archivo" -C "$salida/macos" rfirma.app
echo "actualizacion: $archivo ($(du -h "$archivo" | cut -f1))"

# Regenera cargo-sources.json y el sello de Cargo.lock.
[group('release')]
Expand Down
6 changes: 6 additions & 0 deletions packaging/macos/tauri.macos.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@
"signingIdentity": "-",
"hardenedRuntime": false
}
},
"plugins": {
"updater": {
"endpoints": ["https://rfirma.sgomez.me/macos/latest.json"],
"pubkey":"dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ0RjFGMDhEMEQyMUY4RjkKUldUNStDRU5qZkR4Ukx5VlNSZWR2aGl2VVJlSzhUSEMvZGVrVDBIc09Va01lYzN3Um1sVGdHYnMK"
}
}
}
4 changes: 2 additions & 2 deletions packaging/repo/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
# `.flatpakref` y de las órdenes de alta publicadas—, así que se enumeran
# en vez de servir el montaje entero: lo que no esté en esta lista no se
# sirve aunque alguien lo deje ahí.
@repositorio path /flatpak/* /apt/* /rpm/* /rfirma.asc /rfirma.flatpakref /rfirma.flatpakrepo /windows/*
@repositorio path /flatpak/* /apt/* /rpm/* /rfirma.asc /rfirma.flatpakref /rfirma.flatpakrepo /windows/* /macos/*
handle @repositorio {
root * /srv/rfirma-repo/actual
header /rfirma.flatpakrepo Content-Type application/vnd.flatpak.repo
Expand All @@ -79,7 +79,7 @@
# quien las pide es apt, dnf o flatpak, no una persona.
handle_errors 404 {
import cabeceras
@web not path /flatpak/* /apt/* /rpm/* /rfirma.asc /rfirma.flatpakref /rfirma.flatpakrepo /windows/*
@web not path /flatpak/* /apt/* /rpm/* /rfirma.asc /rfirma.flatpakref /rfirma.flatpakrepo /windows/* /macos/*
handle @web {
root * /srv
rewrite * /404.html
Expand Down
21 changes: 12 additions & 9 deletions packaging/repo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,17 +265,19 @@ Si el `grep` no encuentra nada, el fichero lleva la maestra: no se sube. Al term

## La clave de actualizaciones de Windows

Un par minisign propio, distinto de la GPG (ADR-0015): firma el `-setup.exe` de cada versión
estable para el *updater* de Tauri. La pública se versiona en un solo sitio,
`plugins.updater.pubkey` de `packaging/windows/tauri.windows.json`, la configuración que se
pasa con `--config` al bundler: es en la que confía la versión que se instala.
Un par minisign propio, distinto de la GPG (ADR-0015): firma el `-setup.exe` y el
`.app.tar.gz` de macOS de cada versión estable para el *updater* de Tauri. La pública se
versiona en `plugins.updater.pubkey` de `packaging/windows/tauri.windows.json` y de
`packaging/macos/tauri.macos.json`, las configuraciones que se pasan con `--config` al
bundler: es en la que confía la versión que se instala. Las dos llevan la misma, y una
prueba de `desktop/adapters/releases` lo comprueba.

La privada y su contraseña son los secretos `TAURI_SIGNING_PRIVATE_KEY` y
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD` del entorno `release`, y no aparecen en ningún otro
workflow: `.github/check-workflows.sh` lo comprueba.

`release.yml` firma con `packaging/windows/sign-updater.sh`, que deja el `.sig` al lado del
instalador y falla si falta la clave o si la firma no verifica con la pública embebida en la
`release.yml` firma con `packaging/windows/sign-updater.sh`, que deja el `.sig` al lado de
cada paquete y falla si falta la clave o si la firma no verifica con la pública embebida en la
**última etiqueta estable**, que es la que aceptan las instalaciones que ya existen; si todavía
no hay ninguna estable con *updater*, con la embebida actual. Todo lo de abajo se hace en un
equipo propio, nunca en el CI, desde la raíz del repositorio y con las dependencias de
Expand All @@ -296,9 +298,10 @@ ficheros, la privada `rfirma-actualizaciones.key` y la pública
- **Versionar la pública**, tal cual, por PR a `main`:

```bash
CONF=packaging/windows/tauri.windows.json
jq --arg k "$(cat "$DIR/rfirma-actualizaciones.key.pub")" '.plugins.updater.pubkey = $k' \
"$CONF" > "$DIR/conf.json" && mv "$DIR/conf.json" "$CONF"
for CONF in packaging/windows/tauri.windows.json packaging/macos/tauri.macos.json; do
jq --arg k "$(cat "$DIR/rfirma-actualizaciones.key.pub")" '.plugins.updater.pubkey = $k' \
"$CONF" > "$DIR/conf.json" && mv "$DIR/conf.json" "$CONF"
done
```

- **Custodiar la privada**: copia `rfirma-actualizaciones.key` fuera de línea, con copia de
Expand Down
53 changes: 51 additions & 2 deletions packaging/repo/build-tree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
# /apt/ con dists/stable/main/binary-amd64/
# /rpm/ con repodata/
# /windows/ el `-setup.exe` y el `.sig` de cada version, y `latest.json`
# /macos/ el `.app.tar.gz` y el `.sig` de cada version que lo trae, y
# `latest.json`
#
# APT CON SUITE `stable` Y NO REPOSITORIO PLANO (ID-175): el plano es mas
# barato y NO admite `Suites:`/`Components:` en un `.sources` deb822, que es el
Expand Down Expand Up @@ -99,7 +101,7 @@ exe_de_la_version() {
return 1
fi
mapfile -t exes < <("$manifiesto" files "$dir" nsis)
mapfile -t sigs < <("$manifiesto" files "$dir" minisign)
mapfile -t sigs < <("$manifiesto" files "$dir" minisign | grep -v '\.app\.tar\.gz\.sig$' || true)
if [ "${#exes[@]}" -ne 1 ] || [ "${#sigs[@]}" -ne 1 ] || [ "${sigs[0]}" != "${exes[0]}.sig" ]; then
echo "a la version $(basename "$dir") le falta el .exe de Windows o su .sig" >&2
return 1
Expand All @@ -111,6 +113,25 @@ exe_de_la_version() {
echo "${exes[0]}"
}

# La actualizacion de macOS no la traen las versiones de antes de macOS: sin ella, nada.
app_de_la_version() {
local dir="$1" apps sigs
mapfile -t apps < <("$manifiesto" files "$dir" updater)
if [ "${#apps[@]}" -eq 0 ]; then
return 0
fi
mapfile -t sigs < <("$manifiesto" files "$dir" minisign | grep '\.app\.tar\.gz\.sig$' || true)
if [ "${#apps[@]}" -ne 1 ] || [ "${#sigs[@]}" -ne 1 ] || [ "${sigs[0]}" != "${apps[0]}.sig" ]; then
echo "a la version $(basename "$dir") le falta la actualizacion de macOS o su .sig" >&2
return 1
fi
if [ ! -f "$dir/${apps[0]}" ] || [ ! -f "$dir/${sigs[0]}" ]; then
echo "el manifiesto de $(basename "$dir") nombra ${apps[0]} y ${sigs[0]} y no estan en la Release" >&2
return 1
fi
echo "${apps[0]}"
}

# Las versiones de la serie, en orden de version y no de listado: el orden en
# que se importan los bundles es la historia del repositorio ostree, asi que
# tiene que ser el mismo en cada reconstruccion.
Expand Down Expand Up @@ -168,6 +189,10 @@ exe_de=()
for version in "${versiones[@]}"; do
exe_de+=("$(exe_de_la_version "$serie/$version")") || exit 1
done
app_de=()
for version in "${versiones[@]}"; do
app_de+=("$(app_de_la_version "$serie/$version")") || exit 1
done

# Las herramientas, TODAS de golpe y por su nombre de paquete: que la
# publicacion se caiga a la mitad porque falta `createrepo_c` deja el arbol a
Expand Down Expand Up @@ -235,7 +260,7 @@ ceba_el_agente() {
}

rm -rf "$arbol"
mkdir -p "$arbol/flatpak" "$arbol/apt" "$arbol/rpm" "$arbol/windows"
mkdir -p "$arbol/flatpak" "$arbol/apt" "$arbol/rpm" "$arbol/windows" "$arbol/macos"

# La clave publica no la genera nada: es la misma que firma las Releases, y
# aqui se copia tal cual para que apt y dnf la encuentren en la ruta que dicen
Expand Down Expand Up @@ -439,4 +464,28 @@ jq -n \
platforms: {"windows-x86_64": {signature: ($firma | rtrimstr("\n")), url: $url}}}' \
> "$arbol/windows/latest.json"

# ----------------------------------------------------------------- 5. macos --
# Como `windows/`, con las versiones estables que traen la actualizacion de macOS.
echo " macos: colocando las actualizaciones y latest.json"
ultima_app=""
for i in "${!versiones[@]}"; do
[[ "${versiones[$i]}" == *-* ]] && continue
[ -n "${app_de[$i]}" ] || continue
cp "$serie/${versiones[$i]}/${app_de[$i]}" "$serie/${versiones[$i]}/${app_de[$i]}.sig" "$arbol/macos/"
ultima_app="$i"
done
if [ -z "$ultima_app" ]; then
echo " macos: ninguna version estable trae la actualizacion de macOS, no hay latest.json"
else
app_ultima="${app_de[$ultima_app]}"
jq -n \
--arg version "${versiones[$ultima_app]#v}" \
--arg fecha "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg url "https://rfirma.sgomez.me/macos/$(jq -rn --arg f "$app_ultima" '$f | @uri')" \
--rawfile firma "$arbol/macos/$app_ultima.sig" \
'{version: $version, notes: "", pub_date: $fecha,
platforms: {"darwin-aarch64": {signature: ($firma | rtrimstr("\n")), url: $url}}}' \
> "$arbol/macos/latest.json"
fi

echo "OK arbol construido en $arbol con ${#versiones[@]} version(es): ${versiones[*]}"
96 changes: 96 additions & 0 deletions packaging/repo/build-tree.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ instalador_de_pega() {
"$raiz/scripts/packages-manifest.sh" write "$dir"
}

actualizacion_de_macos_de_pega() {
local dir="$1" version="$2"
echo "app $version" > "$dir/rFirma_${version}_aarch64.app.tar.gz"
printf 'firma mac de %s\n' "$version" > "$dir/rFirma_${version}_aarch64.app.tar.gz.sig"
"$raiz/scripts/packages-manifest.sh" write "$dir"
}

sin_actualizacion_de_macos() {
local dir="$1"
rm -f "$dir"/*.app.tar.gz "$dir"/*.app.tar.gz.sig
jq 'map(select(.platform != "macos"))' "$dir/paquetes.json" > "$dir/paquetes.json.nuevo"
mv "$dir/paquetes.json.nuevo" "$dir/paquetes.json"
}

# an_incomplete_series_stops_the_publication
incompleta="$tmp/incompleta"
paquetes_de_pega "$incompleta/v0.4.0" "0.4.0"
Expand Down Expand Up @@ -151,6 +165,20 @@ else
ok "una clave publica que no existe detiene la publicacion"
fi

# a_macos_update_without_its_signature_stops_the_publication
mac_sin_sig="$tmp/mac-sin-sig"
paquetes_de_pega "$mac_sin_sig/v0.4.0" "0.4.0"
actualizacion_de_macos_de_pega "$mac_sin_sig/v0.4.0" "0.4.0"
rm "$mac_sin_sig/v0.4.0"/*.app.tar.gz.sig
"$raiz/scripts/packages-manifest.sh" write "$mac_sin_sig/v0.4.0"
if salida="$("$construye" "$mac_sin_sig" "$tmp/arbol-malo" "$tmp/rfirma.asc" "$SIN_FIRMA" 2>&1)"; then
fail "una actualizacion de macOS sin su .sig no detiene la publicacion"
elif grep -q "actualizacion de macOS" <<< "$salida"; then
ok "una actualizacion de macOS sin su .sig detiene la publicacion"
else
fail "una actualizacion de macOS sin su .sig se para por otro motivo: $salida"
fi

# ---------------------------------------------------------------------------
# Las ordenes de alta de la landing: no necesitan construir nada y son la
# unica atadura entre lo que se sirve y lo que la gente copia.
Expand Down Expand Up @@ -251,12 +279,16 @@ for version in "${versiones[@]}"; do
deb_de_prueba "$version" "$dir/rfirma_${version}_amd64.deb"
rpm_de_prueba "$version" "$dir/rfirma-$version.x86_64.rpm"
instalador_de_pega "$dir" "$version"
# La primera version es de antes de macOS: no trae su actualizacion.
[ "$version" = 0.4.0 ] || actualizacion_de_macos_de_pega "$dir" "$version"
done

# Assets subidos a mano a la Release DESPUES del manifiesto: no estan en el.
echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_x64-setup.exe"
echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_x64-setup.exe.sig"
echo "de mas" > "$serie/v0.4.10/subido-a-mano.exe"
echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_aarch64.app.tar.gz"
echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_aarch64.app.tar.gz.sig"

# ---------------------------------------------------------------------------
# El arbol, dos veces y en dos directorios distintos
Expand Down Expand Up @@ -471,13 +503,77 @@ else
fail "una candidata aparece en windows/ o en latest.json"
fi

# ---------------------------------------------------------------------------
# /macos/: la actualizacion y latest.json
# ---------------------------------------------------------------------------
# macos_has_the_update_and_signature_of_every_version_that_brings_it
faltan_de_macos=0
for version in 0.4.2 0.4.10; do
for fichero in "rFirma_${version}_aarch64.app.tar.gz" "rFirma_${version}_aarch64.app.tar.gz.sig"; do
[ -f "$tmp/arbol/macos/$fichero" ] || { faltan_de_macos=1; echo " falta $fichero" >&2; }
done
done
if [ "$faltan_de_macos" -eq 0 ] && [ -z "$(find "$tmp/arbol/macos" -name '*0.4.0*' -print -quit)" ] \
&& [ ! -e "$tmp/arbol/macos/rFirma_9.9.9_aarch64.app.tar.gz" ]; then
ok "macos/ trae la actualizacion y su .sig de cada version que la trae, y nada fuera del manifiesto"
else
fail "macos/ no trae lo que dicen los manifiestos de la serie"
fi

# macos_latest_json_announces_the_darwin_aarch64_entry_of_the_highest_version
latest_mac="$tmp/arbol/macos/latest.json"
app_alta="rFirma_0.4.10_aarch64.app.tar.gz"
if [ -f "$latest_mac" ] \
&& [ "$(jq -r .version "$latest_mac")" = "0.4.10" ] \
&& [ "$(jq -r '.platforms | keys | join(",")' "$latest_mac")" = "darwin-aarch64" ] \
&& [ "$(jq -r '.platforms["darwin-aarch64"].url' "$latest_mac")" = "https://rfirma.sgomez.me/macos/$app_alta" ] \
&& [ "$(jq -r '.platforms["darwin-aarch64"].signature' "$latest_mac")" = "$(cat "$tmp/arbol/macos/$app_alta.sig")" ] \
&& jq -e '.pub_date != null and .notes != null' "$latest_mac" > /dev/null; then
ok "el latest.json de macos anuncia la entrada darwin-aarch64 de la version mas alta"
else
fail "el latest.json de macos no cumple el contrato del updater: $(cat "$latest_mac" 2>&1)"
fi

# a_candidate_never_appears_in_macos
actualizacion_de_macos_de_pega "$con_candidata/v0.4.11-rc.1" "0.4.11-rc.1"
"$construye" "$con_candidata" "$tmp/arbol-rc-mac" "$tmp/rfirma.asc" "$SIN_FIRMA" > /dev/null 2>&1 \
|| fail "la construccion con una candidata de macOS en la serie falla"
if [ "$(jq -r .version "$tmp/arbol-rc-mac/macos/latest.json")" = "0.4.10" ] \
&& [ -z "$(find "$tmp/arbol-rc-mac/macos" -name '*rc*' -print -quit)" ]; then
ok "una candidata no aparece en macos/ ni en su latest.json"
else
fail "una candidata aparece en macos/ o en su latest.json"
fi

# a_series_without_any_macos_update_still_builds_without_announcing_one
sin_mac="$tmp/sin-mac"
cp -r "$serie" "$sin_mac"
for version in "${versiones[@]}"; do
sin_actualizacion_de_macos "$sin_mac/v$version"
done
if "$construye" "$sin_mac" "$tmp/arbol-sin-mac" "$tmp/rfirma.asc" "$SIN_FIRMA" > /dev/null 2>&1 \
&& [ ! -e "$tmp/arbol-sin-mac/macos/latest.json" ] \
&& [ -f "$tmp/arbol-sin-mac/windows/latest.json" ]; then
ok "una serie sin actualizacion de macOS se construye y no anuncia ninguna"
else
fail "una serie sin actualizacion de macOS no se construye o anuncia una"
fi

# the_caddyfile_serves_windows_from_the_current_link
if grep -E '^\s*@repositorio path .*/windows/\*' "$raiz/packaging/repo/Caddyfile" > /dev/null; then
ok "el Caddyfile sirve /windows/* desde actual"
else
fail "el Caddyfile no sirve /windows/*"
fi

# the_caddyfile_serves_macos_from_the_current_link
if grep -E '^\s*@repositorio path .*/macos/\*' "$raiz/packaging/repo/Caddyfile" > /dev/null \
&& grep -E '^\s*@web not path .*/macos/\*' "$raiz/packaging/repo/Caddyfile" > /dev/null; then
ok "el Caddyfile sirve /macos/* desde actual"
else
fail "el Caddyfile no sirve /macos/*"
fi

# ---------------------------------------------------------------------------
# Lo demas
# ---------------------------------------------------------------------------
Expand Down
Loading
Loading