Install Update File

Da ich immer häufiger Software verwende die in Go geschrieben ist und somit als Single-File veröffentlich wird, habe ich mich entschlossen dafür ein Basis-Skript zu entwickeln, mit dem ich die Installation und weitere Aktualisierungen durchführen kann. Als Basis diente ein Script von einem Kollegen.

Script-Datei

Zuerst hier einmal das Skript, eine detaliertere Beschreibung findet ihr im Anhang:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#!/usr/bin/bash
set -euo pipefail

### Configuration ###
target_architecture="linux-amd64"
prog_installdir="/usr/local/bin"

prog_link_name="<symlink-name>"
prog_service="${prog_link_name}.service"

git_server="code.example.com"
git_repo="<developer-name>/<project-name>"

### Buildes Configurations ###
git_repo_url="https://${git_server}/${git_repo}.git"
git_release_url="https://${git_server}/api/v1/repos/${git_repo}/releases/latest"

######################################################################

# Return 0 if service is running and 1 if it's not.
function check_service_running() {
  echo "Checking service '$1' state..."
  set +e
  prog_service_state=$(systemctl is-active $1)
  running=$?
  set -e
  if [ $running -eq 0 ]; then
    echo "Service '$1' is running."
    return 0
  fi
  echo "Service '$1' is not running (state: ${prog_service_state})."
  return 1
}

######################################################################

### Main script
echo "=== Update Program to latest version ==="
pushd $prog_installdir
current_version=1.0.0
if [ -f $prog_link_name ]; then
  current_version=$(./${prog_link_name} --version | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+" | sed -nr 's/\+/-/p')
fi
# Remember the old binary path to delete it later.
old_binary_path=$(realpath $prog_link_name)
echo "Currently installed version: $current_version (${prog_link_name} -> ${old_binary_path})"

release_tag=$(curl -s $git_release_url | jq .tag_name -r)
version=$release_tag
echo "Latest release tag on server: ${release_tag} => Program version: ${version}"

# Finish the script if there is no new version on the server.
if [[ $current_version = "$version" ]]; then
  echo "Latest version is already installed - Nothing to do :-)"
  service_running=$(check_service_running $prog_service)
  if [[ $service_running ]]; then
    exit 0
  fi
  exit 1
fi

# Build URLs
assets=$(curl -s $git_release_url | jq -c '.assets[] | select(.name | match("linux-amd64$"))')
binary_filename=$(echo $assets | jq .name -r)
download_url=$(echo $assets | jq .browser_download_url -r)

echo "Binary name. ${binary_filename}"
echo "Download URL: ${download_url}"
echo

# Remove binary if it exists, download new binary and set it's executable flag.
echo "Downloading new version..."
rm -f "$binary_filename"
curl -JOL "$download_url"
chmod +x "$binary_filename"

# Update symlink and get the new full path to the binary.
echo "Linking ${prog_link_name} to new binary ${binary_filename}..."
ln -sf "$binary_filename" $prog_link_name
new_binary_path=$(realpath $prog_link_name)

# Restart the service to use the new binary.
echo "Restarting service..."
systemctl restart $prog_service

# Check if it's running successfully.
service_running=$(check_service_running $prog_service)
if [[ $service_running ]]; then
  echo "Success!"
  if [[ $new_binary_path = "$old_binary_path" ]]; then
    echo "Binary path unchanged - don't delete old binary."
  else
    echo "Delete old binary ${old_binary_path}..."
    rm "${old_binary_path}"
  fi
  exit 0
else
  echo ":-("
  exit 1
fi

Parameter

Im ersten Teil werden sind die Konfiguration, die anzupassen sind. Die Architektur und das Installationsverzeichnis sollte meist passen und muss nicht verändert werden.

Der prog_link_name definiert den Namen unter dem das Program im System registriet wird. Hierfür wird ein symbolischer Link auf die Datei mit der Versionsnummer hinterlegt. Hermit können die Dateien einfacher ausgetauscht werden. Beim prog_installdir wird die ablage der Binärdateien definiert, diese sollte nicht verändert werden.

Mit git_serverund git_repo definiert man den Server und die das Projekt, in dem die Software gepflegt und die Release Versionen freigegeben werden.

Ablauf

Das Script selbst läuft wie folgt ab:

  1. Die aktuell installierte Version wird mit der aktuell freigegebenen Version vergleichen
  2. Der aktuelle Binärpfad wird gespeichert für späteres löschen
  3. Sollte keine neuere Version vorhanden sein, ist das Skript fertig
  4. Die neue Version wird ins Installationsverzeichnis heruntergeladen, als ausführbar gekenzeichnet
  5. Der Link wird auf die neue Version umgestellt
  6. Der Dienst wird neugstartet und überprüft ob er läuft
  7. Wenn der Dienst erfolgreich gestartet wurde, wird die Binärdatei der alten Version entfernt