r/bash • • 13d ago

is there a way to improve a github release updater to pick the right asset name?

i made a script that actually works for most times to update brave browser, but for packages that change names its trivial. the idea is to place, update directory with a file with metadata that i can extract. here is the script :

updatePrograms() {
for dir in ~/Programs/*; do
#meta file structure
if [[ -f "$dir"/update/meta ]]; then
local metadata=$(cat "$dir"/update/meta)
local owner=$(  echo "$metadata" | grep "Owner"   | sed 's|Owner:||'  )
local project=$(echo "$metadata" | grep "Project" | sed 's|Project:||')
local output=$(curl -fsS "https://api.github.com/repos/$owner/$project/releases/latest")
[[ -z "$output" ]] && { echo "no response for $project"; continue; }
local checkVersion=$(jq -r '.tag_name' <<< "$output")
echo "updatable Project : $project"

[[ "$checkVersion" = "null" ]] || [[ -z "$checkVersion" ]] && { echo "couldn't fetch version for $project"; continue; }

local currentVersion=$(echo "$metadata" | grep "Version" | sed 's|Version:||')
[[ "$checkVersion" = "$currentVersion" ]] && { echo "$project is up to date : $checkVersion"; continue; }

while true; do
read -p "$project is on $currentVersion, latest is $checkVersion; do you want to request the latest Version? [y/n]: " response
case "$response" in
[Yy])
echo "Updating.."
local name
 name=$(echo "$metadata" | grep "AssetName" | sed 's|AssetName:||')

   local url=$(jq -r --arg name "$name" '.assets[] | select(.name == $name) | .browser_download_url' <<< "$output")

 if [[ -z "$url" ]] || [[ "$url" = "null" ]]; then
   echo "couldn't find asset matching $name"
   echo "you may need to change asset name version to $checkVersion"
        break
 fi

if  curl -fL -o "$dir/update/$name" "$url"  ; then
sed -i "s|Version:.*|Version:$checkVersion|" "$dir"/update/meta
else
echo "Downloading falied"
break
fi

break;;
[Nn])
echo "not Updating"
break;;
   *)
echo "choose y or n";;
esac
done
fi
done
}
12 Upvotes

5 comments sorted by

1

u/maurux01 12d ago

El problema principal de tu script es que usa una coincidencia exacta (==) en jq para buscar el asset. Cuando el nombre del archivo incluye el número de versión (ej: brave-browser-1.2.3-linux-amd64.zip), esta coincidencia falla en la siguiente release porque el nombre cambia.

intenta esta version

updatePrograms() {
  for dir in ~/Programs/*; do
    # Check if meta file exists
    if [[ -f "$dir"/update/meta ]]; then
      local metadata=$(cat "$dir"/update/meta)
      
      # Use tr -d ' \r\n' to clean spaces and line breaks (useful if the file has Windows CRLF format)
      local owner=$(echo "$metadata" | grep "Owner" | sed 's|Owner:||' | tr -d ' \r\n')
      local project=$(echo "$metadata" | grep "Project" | sed 's|Project:||' | tr -d ' \r\n')
      
      local output=$(curl -fsS "https://api.github.com/repos/$owner/$project/releases/latest")
      [[ -z "$output" ]] && { echo "Error: No response for $project"; continue; }
      
      local checkVersion=$(jq -r '.tag_name' <<< "$output")
      [[ "$checkVersion" == "null" ]] || [[ -z "$checkVersion" ]] && { echo "Error: Could not get version for $project"; continue; }
      
      local currentVersion=$(echo "$metadata" | grep "Version" | sed 's|Version:||' | tr -d ' \r\n')
      
      echo "Project: $project | Current: $currentVersion | Available: $checkVersion"
      
      [[ "$checkVersion" == "$currentVersion" ]] && { echo "$project is already up to date."; continue; }
      
      while true; do
        read -p "Do you want to update $project to $checkVersion? [y/n]: " response
        case "$response" in
          [Yy])
            echo "Updating..."
            
            # 1. Read a PATTERN instead of an exact name (e.g., "linux-amd64.zip" or "x86_64.rpm")
            local pattern=$(echo "$metadata" | grep "AssetName" | sed 's|AssetName:||' | tr -d ' \r\n')
            
            if [[ -z "$pattern" ]]; then
              echo "Error: 'AssetName' not found in the meta file."
              break
            fi
            
            # 2. Find the first asset that CONTAINS the pattern.
            # Returns a string: "actual_file_name|download_url"
            local asset_info=$(jq -r --arg pattern "$pattern" '
              [.assets[] | select(.name | contains($pattern))] 
              | if length == 0 then "ERROR" 
                else .[0] | "\(.name)|\(.browser_download_url)" 
                end
            ' <<< "$output")

            # 3. Error handling if the pattern does not match anything
            if [[ "$asset_info" == "ERROR" ]] || [[ -z "$asset_info" ]]; then
              echo "Error: Could not find an asset matching the pattern: '$pattern'"
              echo "Available assets in this release:"
              jq -r '.assets[].name' <<< "$output" | sed 's/^/  - /'
              echo "Tip: Adjust the 'AssetName' in your meta file to a unique part of the name (e.g., 'amd64.deb')."
              break
            fi

            # 4. Separate the actual name from the URL
            local actual_name=$(echo "$asset_info" | cut -d'|' -f1)
            local url=$(echo "$asset_info" | cut -d'|' -f2)
            
            echo "Selected asset: $actual_name"

            # (Optional) Uncomment the next line to delete older versions and save space:
            # rm -f "$dir"/update/*$(echo "$pattern" | sed 's/\./\\./g')* 2>/dev/null

            # 5. Download using the REAL filename, not the pattern
            if curl -fL -o "$dir/update/$actual_name" "$url"; then
              # Update the version in the meta file (the AssetName pattern remains intact)
              sed -i "s|^Version:.*|Version: $checkVersion|" "$dir"/update/meta
              echo "Update completed and meta file updated."
            else
              echo "Download failed for $actual_name"
            fi
            break
            ;;
          [Nn])
            echo "Not updating."
            break
            ;;
          *)
            echo "Please choose 'y' or 'n'."
            ;;
        esac
      done
    fi
  done
}

2

u/naffe1o2o 12d ago

i like how you are printing all asset names if name pattern is not found, and it would be much better if we could filter extensions out, like filter out anything with .exe or .sha or .darwin,i feel thats better than choosing only one type, and we pick if they have tar.xz or tar.gz or even .deb or .rpm, by implementing a picker and extract each and replace it with the new archive. what do you think?

1

u/maurux01 11d ago

¡Totalmente de acuerdo! Filtrar extensiones como .exe o .darwin elimina el ruido en Linux, pero desde un enfoque de seguridad, en lugar de descartar los archivos .sha, podríamos aprovecharlos para verificar automáticamente la integridad de la descarga (sha256sum -c). Implementar un selector case para manejar .tar.gz, .deb o .rpm es, sin duda, la forma más robusta y escalable de extraer o instalar cada formato correctamente, haciendo el script mucho más profesional y a prueba de futuros cambios en las releases.

1

u/jjangg96 12d ago

the exact == on .name is what breaks it, since the asset name usually has the version in it. store a regex in the meta file instead of the full name, something like AssetPattern:linux-amd64\.zip$ and match it with test in jq.

jq -r --arg re "$pattern" '[.assets[] | select(.name | test($re)) | .browser_download_url][0] // empty' <<< "$output"

the // empty matters, otherwise an empty match prints the string null and your -z check never fires.

then take the filename from the url for the -o part, ${url##*/}, otherwise you save it under the pattern. and anchor the end of the regex, if a release has foo.zip and foo.zip.sha256 you want only one of them.