diff --git a/bin/sync-upstream b/bin/sync-upstream index c307cf8..93e6f0d 100755 --- a/bin/sync-upstream +++ b/bin/sync-upstream @@ -104,17 +104,23 @@ set_pkgbuild_array() { assert_single_assignment "$pkgbuild" "^${name}=(" "$name" || return 1 - local block="$TEMP_DIR/array-block" - if [[ ${#values[@]} -eq 1 ]]; then - printf "%s=('%s')\n" "$name" "${values[0]}" > "$block" - else - printf '%s=(\n' "$name" > "$block" - printf " '%s'\n" "${values[@]}" >> "$block" - printf ')\n' >> "$block" + if [[ ${#values[@]} -eq 0 ]]; then + print_error "No values to write for ${name}" + return 1 fi - # Rewrite beside the PKGBUILD so the move is an atomic same-filesystem rename. - local rewritten="$pkgbuild.sync-upstream" + local block="$TEMP_DIR/array-block" + if [[ ${#values[@]} -eq 1 ]]; then + printf "%s=('%s')\n" "$name" "${values[0]}" > "$block" || return 1 + else + { + printf '%s=(\n' "$name" + printf " '%s'\n" "${values[@]}" + printf ')\n' + } > "$block" || return 1 + fi + + local rewritten="$TEMP_DIR/pkgbuild-rewritten" if ! awk -v prefix="${name}=(" -v block="$block" ' !replaced && index($0, prefix) == 1 { while ((getline line < block) > 0) print line @@ -134,7 +140,6 @@ set_pkgbuild_array() { return 1 fi - chmod --reference="$pkgbuild" "$rewritten" mv "$rewritten" "$pkgbuild" } @@ -154,18 +159,71 @@ validate_release() { # pkgver is written into the PKGBUILD, which makepkg sources as shell, so it # is held to pacman's own character set rather than merely being non-empty. + # The anchors are \A and \z, not ^ and $: jq's $ also matches before a + # trailing newline, which would let "1.0\n" through and break the rewrite. jq -e ' - (.pkgver | type == "string" and test("^[A-Za-z0-9._+]+$")) + (.pkgver | type == "string" and test("\\A[A-Za-z0-9._+]+\\z")) and (.sha256sums | type == "object" and length > 0) and (.sha256sums | to_entries | all( - .key | test("^[a-z0-9_]+$") + .key | test("\\A[a-z0-9_]+\\z") )) and (.sha256sums | to_entries | all( - .value | type == "array" and length > 0 and all(test("^[0-9a-f]{64}$")) + .value | type == "array" and length > 0 and all(test("\\A[0-9a-f]{64}\\z")) )) ' <<<"$release" >/dev/null } +# Confirm the rewritten PKGBUILD parses and actually holds what we meant to put +# in it. Editing shell with awk and sed can go wrong in ways no amount of +# pattern-matching anticipates -- an array element carrying a ")" in a comment, +# say -- so the result is checked rather than trusted. +verify_pkgbuild() { + local pkgbuild="$1" + local release="$2" + local pkgver="$3" + shift 3 + local arrays=("$@") + + if ! bash -n "$pkgbuild" 2>/dev/null; then + print_error "Rewritten PKGBUILD is not valid shell" + return 1 + fi + + local dump + if ! dump=$(CARCH=x86_64 bash -c ' + source "$1" >/dev/null 2>&1 || exit 1 + printf "pkgver\t%s\n" "$pkgver" + printf "pkgrel\t%s\n" "$pkgrel" + for name in "${@:2}"; do + declare -n array="$name" + printf "%s\t%s\n" "$name" "${array[*]}" + done + ' _ "$pkgbuild" "${arrays[@]}" 2>/dev/null); then + print_error "Rewritten PKGBUILD could not be read back" + return 1 + fi + + local expected + expected=$( + printf 'pkgver\t%s\n' "$pkgver" + printf 'pkgrel\t1\n' + local array arch + for array in "${arrays[@]}"; do + arch="${array#sha256sums}" + arch="${arch#_}" + [[ -n "$arch" ]] || arch="any" + printf '%s\t%s\n' "$array" \ + "$(jq -r --arg arch "$arch" '.sha256sums[$arch] | join(" ")' <<<"$release")" + done + ) + + if [[ "$dump" != "$expected" ]]; then + print_error "Rewritten PKGBUILD does not hold the reported release" + diff <(echo "$expected") <(echo "$dump") | sed 's/^/ /' >&2 || true + return 1 + fi +} + apply_release() { local package_dir="$1" local release="$2" @@ -173,7 +231,7 @@ apply_release() { local pkgbuild="$package_dir/PKGBUILD" local arch array values - local targets=() + local arrays=() while IFS= read -r arch; do if [[ "$arch" == "any" ]]; then @@ -181,29 +239,47 @@ apply_release() { else array="sha256sums_$arch" fi - targets+=("$arch:$array") + arrays+=("$array") done < <(jq -r '.sha256sums | keys[]' <<<"$release") - # Everything the update will touch is checked before anything is written. A - # hook naming an array the PKGBUILD does not have must fail with the file - # untouched rather than half rewritten. - assert_single_assignment "$pkgbuild" '^pkgver=' pkgver || return 1 - assert_single_assignment "$pkgbuild" '^pkgrel=' pkgrel || return 1 - local target - for target in "${targets[@]}"; do - assert_single_assignment "$pkgbuild" "^${target#*:}=(" "${target#*:}" || return 1 - done + # validate_release guarantees at least one entry, so an empty list here means + # jq died inside the process substitution rather than that there is nothing + # to do. + if [[ ${#arrays[@]} -eq 0 ]]; then + print_error "Could not read the checksum architectures from the reported release" + return 1 + fi - for target in "${targets[@]}"; do - arch="${target%%:*}" - array="${target#*:}" + # Every edit lands on a scratch copy that replaces the PKGBUILD in one rename + # at the end, so a failure part way through leaves the original untouched + # rather than half updated. + local scratch="$pkgbuild.sync-upstream" + cp "$pkgbuild" "$scratch" || return 1 - mapfile -t values < <(jq -r --arg arch "$arch" '.sha256sums[$arch][]' <<<"$release") - set_pkgbuild_array "$pkgbuild" "$array" "${values[@]}" || return 1 - done + if ! ( + assert_single_assignment "$scratch" '^pkgver=' pkgver || exit 1 + assert_single_assignment "$scratch" '^pkgrel=' pkgrel || exit 1 - set_pkgbuild_scalar "$pkgbuild" pkgver "$pkgver" || return 1 - set_pkgbuild_scalar "$pkgbuild" pkgrel 1 || return 1 + for array in "${arrays[@]}"; do + arch="${array#sha256sums}" + arch="${arch#_}" + [[ -n "$arch" ]] || arch="any" + + mapfile -t values < <(jq -r --arg arch "$arch" '.sha256sums[$arch][]' <<<"$release") + set_pkgbuild_array "$scratch" "$array" "${values[@]}" || exit 1 + done + + set_pkgbuild_scalar "$scratch" pkgver "$pkgver" || exit 1 + set_pkgbuild_scalar "$scratch" pkgrel 1 || exit 1 + + verify_pkgbuild "$scratch" "$release" "$pkgver" "${arrays[@]}" || exit 1 + ); then + rm -f "$scratch" + return 1 + fi + + chmod --reference="$pkgbuild" "$scratch" + mv "$scratch" "$pkgbuild" } sync_package() { diff --git a/pkgbuilds/openai-codex-desktop/.omarchy/upstream.sh b/pkgbuilds/openai-codex-desktop/.omarchy/upstream.sh index ee749e3..a090a35 100755 --- a/pkgbuilds/openai-codex-desktop/.omarchy/upstream.sh +++ b/pkgbuilds/openai-codex-desktop/.omarchy/upstream.sh @@ -9,17 +9,28 @@ set -euo pipefail BASE_URL="https://persistent.oaistatic.com/codex-app-prod/linux/deb" declare -A DEB_ARCHES=([x86_64]=amd64 [aarch64]=arm64) -# Print " " for the newest stanza in a Packages index. +# Print " " for the newest stanza in a Packages index. Newest +# is vercmp's opinion, which is the one bin/sync-upstream and pacman both use; +# sort -V disagrees with it over versions like 1.0a. newest_release() { local index="$1" + local version sha256 best_version="" best_sha256="" - awk ' + while read -r version sha256; do + if [[ -z "$best_version" ]] || [[ "$(vercmp "$version" "$best_version")" -gt 0 ]]; then + best_version="$version" + best_sha256="$sha256" + fi + done < <(awk ' { sub(/\r$/, "") } /^Version:/ { version = $2 } /^SHA256:/ { sha256 = $2 } /^$/ { if (version && sha256) print version, sha256; version = sha256 = "" } END { if (version && sha256) print version, sha256 } - ' <<<"$index" | sort -V | tail -n 1 + ' <<<"$index") + + [[ -n "$best_version" ]] || return 1 + echo "$best_version $best_sha256" } versions=() diff --git a/pkgbuilds/openai-codex-desktop/PKGBUILD b/pkgbuilds/openai-codex-desktop/PKGBUILD index 8daed49..a92ee95 100644 --- a/pkgbuilds/openai-codex-desktop/PKGBUILD +++ b/pkgbuilds/openai-codex-desktop/PKGBUILD @@ -70,7 +70,7 @@ _pool="https://persistent.oaistatic.com/codex-app-prod/linux/deb/pool/main/c/cha source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") -sha256sums=('fc70527cd961f3a660e2a9d0a2d62b1e3f7a61d8482ee1935a6b25307da278eb') +sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c') sha256sums_x86_64=('708a15a1bb76e2bb7f0e376e5145391fa277ad3a64057c1d32537bdc2a1b4e6e') sha256sums_aarch64=('6ebea681b1e494d218a199f638b4bc886e94e1458dd61079b1e390a6fb98fdd2') diff --git a/pkgbuilds/openai-codex-desktop/chatgpt-launcher.sh b/pkgbuilds/openai-codex-desktop/chatgpt-launcher.sh index fa8a099..2c4f4f2 100755 --- a/pkgbuilds/openai-codex-desktop/chatgpt-launcher.sh +++ b/pkgbuilds/openai-codex-desktop/chatgpt-launcher.sh @@ -2,9 +2,11 @@ set -euo pipefail user_flags=() -flags_file="${XDG_CONFIG_HOME:-${HOME:-}/.config}/codex-flags.conf" +config_home="${XDG_CONFIG_HOME:-}" +[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config" +flags_file="${config_home:+$config_home/codex-flags.conf}" -if [[ -r "$flags_file" ]]; then +if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then while IFS= read -r line || [[ -n "$line" ]]; do line="${line%%#*}" [[ -n "${line//[[:space:]]/}" ]] || continue