summarylogtreecommitdiffstats
path: root/main-update
blob: 899525ded55f6e6ec44942375b02e20e2dc19bba (plain)
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#!/bin/bash
# shellcheck disable=SC2015
set -e

init_logging() {
  if [ ! -d /var/log/garuda/ ]; then
    # shellcheck disable=SC2174
    mkdir -p -m 755 /var/log/garuda/
  fi
  echo -e "\n>-<->-< garuda-update at $(date +"%Y-%m-%d %R %Z(%:::z)")\n" >>/var/log/garuda/garuda-update
  exec &> >(stdbuf -i0 -o0 -e0 tee >(sed '/\x1b\[[0-9][EF]/d;/\r[^\n]$/d;s,\x1B\[[0-9;]*[a-zA-Z],,g' >>/var/log/garuda/garuda-update))
}

launch_health() {
  if [ -x /usr/bin/garuda-health ]; then
    # Exits with error code 1 if at least one LOW or above severity issue is found
    # We do not want this
    /usr/bin/garuda-health --forcetty || true
  fi
}

parse_pacman_log() {
  sed -i -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" "$AUTOPACMAN_LOG"
  local reason
  reason="$(tac "$AUTOPACMAN_LOG" | grep -oP -m 1 '(?<=error: failed to commit transaction \().*(?=\))')"
  case "$reason" in
  # Failed because of a corrupt package? Let's retry!
  "invalid or corrupted package"*)
    RETRY="Some corrupt downloads were detected. Retrying..."
    # Let's retry it with packages directly from the source. Maybe the mirror is misconfigured?
    CUSTOM_PACMAN_CONFIG="$(mktemp)"
    # shellcheck disable=2016
    sed 's|Include = /etc/pacman.d/chaotic-mirrorlist|Server = https://secret-mirror.chaotic.cx/$repo/$arch|g' /etc/pacman.conf >"$CUSTOM_PACMAN_CONFIG"
    pacman_args+=("--config" "$CUSTOM_PACMAN_CONFIG")
    ;;
  "download library error")
    RETRY="Some downloads failed. Retrying..."
    # Let's retry it with a single download connection, maybe we are overloading the machines network connection?
    CUSTOM_PACMAN_CONFIG="$(mktemp)"
    sed '/^ParallelDownloads.*/d' /etc/pacman.conf >"$CUSTOM_PACMAN_CONFIG"
    pacman_args+=("--config" "$CUSTOM_PACMAN_CONFIG")
    ;;
  "conflicting files")
    tac "$AUTOPACMAN_LOG" | gawk 'BEGIN { exitcode=1 }
            /error: failed to commit transaction \(conflicting files\)/ { exit exitcode }
            /\S+: (.*) exists in filesystem/ { if ($0 ~ /\S+: \/usr\/lib\/python[^\/]+\/site-packages\/[^/]+\/__pycache__\/.+\.pyc exists in filesystem/) { exitcode=0 } else { exit 1 } }
            ENDFILE {exit 1}' && pacman_args+=("--overwrite" "/usr/lib/python*/site-packages/*/__pycache__/*.pyc") && RETRY="Overwriting python cache file conflicts..." || true
    ;;
  esac
}

update_mirrorlist() {
  if [[ -v SKIP_MIRRORLIST ]]; then
    return
  fi

  local MIRRORLIST_TEMP MINLINES=0
  MIRRORLIST_TEMP="$(mktemp)"
  if command -v rate-mirrors >/dev/null; then
    MINLINES=10
    echo -e "\n\033[1;33m-->\033[1;34m Refreshing mirrorlists using rate-mirrors, please be patient..🍵\033[0m"
    # Refresh mirrorlist and make sure it actually contains content. There is a bug in rate-mirrors that creates empty files sometimes.
    rate-mirrors --allow-root --save="$MIRRORLIST_TEMP" arch --max-delay=21600 >/dev/null || { rm "$MIRRORLIST_TEMP"; }
    $INT
  elif command -v reflector >/dev/null; then
    MINLINES=5
    echo -e "\n\033[1;33m-->\033[1;34m Refreshing mirrorlists using reflector, please be patient..🍵\033[0m"
    reflector --latest 10 --age 2 --fastest 10 --protocol https --sort rate --save "$MIRRORLIST_TEMP" || { rm "$MIRRORLIST_TEMP"; }
    $INT
  else
    return
  fi

  if [ ! -f "$MIRRORLIST_TEMP" ]; then
    echo -e "\033[1;31mFailed to update mirrorlist\n\033[0m"
    return
  fi

  if COUNT="$(grep -Ec "^Server *=" "$MIRRORLIST_TEMP")" && [ "$COUNT" -ge "$MINLINES" ]; then
    install -m644 "$MIRRORLIST_TEMP" /etc/pacman.d/mirrorlist
    DATABASE_UPDATED="force"
  else
    echo -e "\033[1;31mNew mirrorlist does not contain enough mirrors. Skipping mirrorlist update.\033[0m"
  fi

  # Newline to separate the context
  echo

  rm "$MIRRORLIST_TEMP"
}

do_update() {
  if [ -x /usr/bin/expect ]; then
    local AUTOPACMAN_LOG EXIT=0 RETRY=false CUSTOM_PACMAN_CONFIG="" SUCCESS=false
    if [ ! -v AUTOPACMAN_CONFLICTSFILE ]; then
      local AUTOPACMAN_CONFLICTSFILE
      AUTOPACMAN_CONFLICTSFILE="$(mktemp -u)"
    fi
    AUTOPACMAN_LOG="$(mktemp)"

    # Try to run the update
    LANG=C LANGUAGE=C LC_ALL=C AUTOPACMAN_LOG="$AUTOPACMAN_LOG" AUTOPACMAN_NO_SPACE_CHECK="$NO_SPACE_CHECK" AUTOPACMAN_PACMAN_NOCONFIRM="$PACMAN_NOCONFIRM" AUTOPACMAN_CONFLICTSFILE="$AUTOPACMAN_CONFLICTSFILE" /usr/lib/garuda/garuda-update/auto-pacman "$PACMAN" "${pacman_args[@]}" || { EXIT=$?; }

    # We have no reason not to get rid of the custom pacman config right away
    if [ -n "$CUSTOM_PACMAN_CONFIG" ]; then rm "$CUSTOM_PACMAN_CONFIG"; fi

    if [ "$EXIT" == "134" ] || [ "$EXIT" == "0" ]; then
      SUCCESS=true
    fi

    # If it failed, we take a look at the logfile to find out why
    if [ "$SUCCESS" != "true" ] && [ -z "$ALREADY_RETRIED" ]; then
      parse_pacman_log
    fi
    rm "$AUTOPACMAN_LOG"
    # We retry, as long as we haven't retried before
    if [ "$RETRY" != "false" ]; then
      echo -e "\n\033[1;33m-->\033[1;34m $RETRY \n\033[0m"
      ALREADY_RETRIED=true do_update
      return
    fi
    if [ -v AUTOPACMAN_CONFLICTSFILE ]; then
      rm -f "$AUTOPACMAN_CONFLICTSFILE"
      unset AUTOPACMAN_CONFLICTSFILE
    fi
    # Still no luck after a retry? Exit out, we can't deal with this.
    if [ "$SUCCESS" == "false" ]; then
      false
    fi
  else
    $PACMAN "${pacman_args[@]}"
  fi
}

show_changelog() {
  # Show update notices if the notices file exists
  if [ -e "/var/lib/garuda/tmp/update_notices" ]; then
    echo -e "\033[1;32mUpdate notices:\n\033[1;34m$(gawk -F '\t' '{print $2}' /var/lib/garuda/tmp/update_notices)\n\033[0m"
    rm /var/lib/garuda/tmp/update_notices
    return 0
  fi
  return 1
}

if [ -f /etc/garuda/garuda-update/config ]; then
  # shellcheck disable=SC1091
  source /etc/garuda/garuda-update/config
fi

# Parse CLI options
PARAMETERS=("$@")
PARSED_OPTIONS=$(getopt --options="a" --longoptions="aur,skip-mirrorlist,no-space-check,noconfirm" --name "$0" -- "${PARAMETERS[@]}")
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
  echo -e "\033[1;31m\nFailed to parse CLI options\n\033[0m"
fi
eval set -- "$PARSED_OPTIONS"
while true; do
  case "$1" in
  -a | --aur)
    UPDATE_AUR=1
    shift
    ;;
  --skip-mirrorlist)
    SKIP_MIRRORLIST=1
    shift
    ;;
  --no-space-check)
    NO_SPACE_CHECK=1
    shift
    ;;
  --noconfirm)
    PACMAN_NOCONFIRM=1
    shift
    ;;
  --)
    shift
    # If argc == 1 and the argument is empty as well as $GARUDA_UPDATE_SELFUPDATE == 1, assume no extra arguments
    # This is code to handle a bug in ancient versions of garuda-update
    if [ "$GARUDA_UPDATE_SELFUPDATE" == 1 ] && [ "$#" -eq 1 ] && [ -z "$1" ]; then
      break
    fi
    PACMAN_EXTRA_OPTS+=("${@}")
    break
    ;;
  *)
    echo "Programming error"
    exit 3
    ;;
  esac
done

# We do not use is-snapshot-boot here on purpose because old systems that just now pull in a new garuda-update do not have it yet.
if grep -qE 'subvol=@/.snapshots/[0-9]+/snapshot' /proc/cmdline && [[ ! -v GARUDA_SNAPSHOT_PACMAN ]]; then
  echo -e "\033[1;31mError: You are currently booted into a snapshot. Please restore the snapshot via btrfs-assistant or snapper-tools before updating your system.\n\033[1;34mNote: You can ignore this error by setting GARUDA_SNAPSHOT_PACMAN: GARUDA_SNAPSHOT_PACMAN=1 garuda-update\n\033[1;31mAny modifications made to this snapshot will be lost next reboot. ❌\033[0m"
  exit 1
fi

# We should start writing our log file here now, we're starting to modify the system
init_logging

# Add garuda repo if it doesn't exist
/usr/lib/garuda/garuda-update/update-helper-scripts migrate-garuda-repo && DATABASE_UPDATED=false || true

# Update mirrorlist and recheck if we need to update garuda-update
update_mirrorlist
self_update "${PARAMETERS[@]}"

# Run pre-update routines and hotfixes
/usr/lib/garuda/garuda-update/update-helper-scripts pre-update-routines || { if [ "$?" -eq 2 ]; then self_update "${PARAMETERS[@]}"; fi; }

pacman_args=("-Su")
if [ "$DATABASE_UPDATED" == false ]; then pacman_args+=("-y"); elif [ "$DATABASE_UPDATED" == "force" ]; then pacman_args+=("-yy"); fi
while IFS= read -r line; do
  pacman_args+=("$line")
done < <(/usr/lib/garuda/garuda-update/update-helper-scripts package-replaces)

if [ -v PACMAN_EXTRA_OPTS ]; then
  pacman_args+=("${PACMAN_EXTRA_OPTS[@]}")
fi

do_update

if [[ -v UPDATE_AUR ]] && [[ -z "$GARUDA_UPDATE_RANI" ]]; then
  # Check for AUR helper
  if [ -x /usr/bin/paru ] && [[ -n "$SUDO_UID" ]]; then
    echo -e "\n\033[1;33m-->\033[1;34m Updating AUR packages with paru..\033[0m"
    sudo -u "#$SUDO_UID" paru -Sua || { echo -e "\033[1;31m\nParu exited with error code $?\n\033[0m"; }
  elif [ -x /usr/bin/yay ] && [[ -n "$SUDO_UID" ]]; then
    echo -e "\n\033[1;33m-->\033[1;34m Updating AUR packages with yay..\033[0m"
    sudo -u "#$SUDO_UID" yay -Sua || { echo -e "\033[1;31m\nYay exited with error code $?\n\033[0m"; }
  else
    echo -e "\n\033[1;33m--> UPDATE_AUR specified but no supported AUR helper found ❌\033[0m"
  fi
  $INT
fi

if [[ -v UPDATE_AUR ]] && [[ -v GARUDA_UPDATE_RANI ]]; then
  # In this case we must not attempt running the update. There is no way to confirm/review updates as of today.
  echo -e "\n\033[1;33m-->\033[1;34m Skipping AUR update. Running those via Rani is explicitly not supported. Instead, use the terminal and review updates manually.\033[0m"
fi

# Update plocate index in the background
if [ -x /usr/bin/locate ]; then
  systemctl start plocate-updatedb.service --no-block
fi

# Update fish completions
if [ -f /usr/lib/systemd/user/garuda-fish-completions-update.service ] && [ -n "$SUDO_USER" ]; then
  systemctl --user -M "$SUDO_USER@.host" start garuda-fish-completions-update.service --no-block 2>&1 | awk '/Transport endpoint is not connected$/ { exit 0; } 1'
fi

# Rebuild bat cache
if [ -f /usr/lib/systemd/user/garuda-bat-cache-rebuild.service ] && [ -n "$SUDO_USER" ]; then
  systemctl --user -M "$SUDO_USER@.host" start garuda-bat-cache-rebuild.service --no-block 2>&1 | awk '/Transport endpoint is not connected$/ { exit 0; } 1'
fi

# Update micro plugins
if [ -x /usr/bin/micro ] && [ -n "$SUDO_USER" ]; then
  systemd-run --user -M "$SUDO_USER@.host" --no-block -G -q micro -plugin update
fi

echo -e "\n\033[1;32mSystem updated! 🐧\n\033[0m"
show_changelog && HAD_CHANGELOG=1 || true

launch_health

if [ -v HAD_CHANGELOG ]; then
  echo -e "\033[1;32m\nDo not forget to read the update notices above the garuda-health output!\033[0m"
fi