231 lines
7.7 KiB
Bash
Executable File
231 lines
7.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Function to check if a package is installed
|
|
is_installed() {
|
|
pacman -Qi "$1" &> /dev/null
|
|
}
|
|
|
|
# Function to check if a package is installed
|
|
is_group_installed() {
|
|
pacman -Qg "$1" &> /dev/null
|
|
}
|
|
|
|
# Packages and Flatpaks you don't want, one name per line (# starts a comment).
|
|
# install_packages and install-flatpaks.sh leave them out, so update doesn't
|
|
# put back what you've removed.
|
|
SKIP_PACKAGES="$HOME/.config/coredesktop/skip-packages"
|
|
|
|
# Print each argument that isn't in SKIP_PACKAGES, one per line
|
|
without_skipped() {
|
|
local -A skip=()
|
|
local name
|
|
if [ -f "$SKIP_PACKAGES" ]; then
|
|
while read -r name _; do
|
|
[[ -n "$name" && "$name" != \#* ]] && skip["$name"]=1
|
|
done < "$SKIP_PACKAGES"
|
|
fi
|
|
for name in "$@"; do
|
|
[ -n "${skip[$name]}" ] || echo "$name"
|
|
done
|
|
}
|
|
|
|
# Function to install packages if not already installed
|
|
install_packages() {
|
|
local packages=()
|
|
local to_install=()
|
|
|
|
local as_deps=()
|
|
|
|
mapfile -t packages < <(without_skipped "$@")
|
|
# pacman -Qdq with no names would list every dependency on the system
|
|
[ ${#packages[@]} -ne 0 ] || return 0
|
|
|
|
for pkg in "${packages[@]}"; do
|
|
if ! is_installed "$pkg" && ! is_group_installed "$pkg"; then
|
|
to_install+=("$pkg")
|
|
fi
|
|
done
|
|
|
|
# A package first installed as another one's dependency becomes an orphan
|
|
# when that one is removed, and updates.d/95-cleanup.sh removes orphans
|
|
mapfile -t as_deps < <(pacman -Qdq "${packages[@]}" 2> /dev/null)
|
|
if [ ${#as_deps[@]} -ne 0 ]; then
|
|
sudo pacman -D --asexplicit "${as_deps[@]}" > /dev/null
|
|
fi
|
|
|
|
if [ ${#to_install[@]} -ne 0 ]; then
|
|
echo "Installing: ${to_install[*]}"
|
|
yay -S --noconfirm --answerdiff=None --answeredit=None --answerclean=None "${to_install[@]}"
|
|
fi
|
|
}
|
|
|
|
FEDERATED_REPO_URL="https://gitea.federated.computer/api/packages/Federated_Computer/arch"
|
|
FEDERATED_REPO_KEY="6AB7C080D87C74BEE3925A026329376994F18B4E"
|
|
|
|
# Add Federated Computer's pacman repo, which Gitea serves and signs, and trust
|
|
# its key. Does nothing if both are already set up.
|
|
ensure_federated_repo() {
|
|
if ! sudo pacman-key --list-keys "$FEDERATED_REPO_KEY" &> /dev/null; then
|
|
curl -fsSL "$FEDERATED_REPO_URL/repository.key" | sudo pacman-key --add - || return 1
|
|
sudo pacman-key --lsign-key "$FEDERATED_REPO_KEY" > /dev/null || return 1
|
|
fi
|
|
|
|
if ! grep -q '^\[federated\]' /etc/pacman.conf; then
|
|
printf '\n[federated]\nSigLevel = Required DatabaseRequired\nServer = %s/$repo/$arch\n' \
|
|
"$FEDERATED_REPO_URL" | sudo tee -a /etc/pacman.conf > /dev/null || return 1
|
|
fi
|
|
}
|
|
|
|
# Function to install pipx applications if not already installed
|
|
install_pipx_apps() {
|
|
local installed app status=0
|
|
installed="$(pipx list --short 2> /dev/null | cut -d' ' -f1)"
|
|
|
|
for app in "$@"; do
|
|
if ! grep -qx "$app" <<< "$installed"; then
|
|
echo "Installing $app with pipx..."
|
|
pipx install "$app" || status=1
|
|
fi
|
|
done
|
|
return $status
|
|
}
|
|
|
|
# Reinstall gext if missing or broken. Its pipx venv goes stale when Arch
|
|
# upgrades Python, and migrations run before updates.d/60-pipx.sh rebuilds it.
|
|
ensure_gext() {
|
|
~/.local/bin/gext --version &> /dev/null && return 0
|
|
echo "gext missing or broken, reinstalling..."
|
|
pipx uninstall gnome-extensions-cli &> /dev/null || true
|
|
pipx install --python "$(which python3)" gnome-extensions-cli --system-site-packages
|
|
}
|
|
|
|
# Function to remove pacman/AUR packages if installed
|
|
remove_packages() {
|
|
local packages=("$@")
|
|
local to_remove=()
|
|
|
|
# Match the exact name: pacman -Q also finds a package that provides it, such
|
|
# as herdr-bin for herdr, which pacman -R won't remove by that name
|
|
for pkg in "${packages[@]}"; do
|
|
if [ "$(pacman -Qq "$pkg" 2> /dev/null)" = "$pkg" ]; then
|
|
to_remove+=("$pkg")
|
|
fi
|
|
done
|
|
|
|
if [ ${#to_remove[@]} -ne 0 ]; then
|
|
echo "Removing: ${to_remove[*]}"
|
|
sudo pacman -Rns --noconfirm "${to_remove[@]}"
|
|
fi
|
|
}
|
|
|
|
# Function to remove flatpak applications if installed
|
|
remove_flatpaks() {
|
|
local flatpaks=("$@")
|
|
local to_remove=()
|
|
|
|
for pak in "${flatpaks[@]}"; do
|
|
if flatpak info --system "$pak" &> /dev/null; then
|
|
to_remove+=("$pak")
|
|
fi
|
|
done
|
|
|
|
if [ ${#to_remove[@]} -ne 0 ]; then
|
|
echo "Removing flatpaks: ${to_remove[*]}"
|
|
sudo flatpak uninstall --system --noninteractive "${to_remove[@]}"
|
|
fi
|
|
}
|
|
|
|
# Function to enable systemd services if not already enabled
|
|
# With --now, also start the services enabled here, after making systemd read
|
|
# the unit files that packages just installed
|
|
enable_services() {
|
|
local now=()
|
|
if [ "$1" = "--now" ]; then
|
|
now=(--now)
|
|
shift
|
|
sudo systemctl daemon-reload
|
|
fi
|
|
for service in "$@"; do
|
|
# audit records the services you turn off; leave those off
|
|
if grep -qsx "system $service" "$HOME/.config/coredesktop/services-off"; then
|
|
echo "$service is turned off (see audit)."
|
|
elif ! systemctl is-enabled "$service" &> /dev/null; then
|
|
echo "Enabling $service..."
|
|
sudo systemctl enable "${now[@]}" "$service"
|
|
else
|
|
echo "$service is already enabled."
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Run each script in migrations/ that hasn't run on this machine yet, oldest
|
|
# first. A migration is recorded as done only when it succeeds, and the first
|
|
# failure stops the run so a later migration never runs ahead of an earlier one.
|
|
run_migrations() {
|
|
local dir="${COREDESKTOP:-$HOME/.local/share/coredesktop}/migrations"
|
|
local state="$HOME/.local/state/coredesktop/migrations"
|
|
local file name
|
|
|
|
mkdir -p "$state"
|
|
for file in "$dir"/*.sh; do
|
|
[ -f "$file" ] || continue
|
|
name="$(basename "$file")"
|
|
[ -f "$state/$name" ] && continue
|
|
|
|
echo "Running migration $name..."
|
|
if ! bash "$file"; then
|
|
echo "Migration $name failed; it will run again on the next update."
|
|
return 1
|
|
fi
|
|
touch "$state/$name"
|
|
done
|
|
}
|
|
|
|
# Output of the last update's pacman -Su, written by updates.d/10-pacman.sh
|
|
PACMAN_UPDATE_LOG="$HOME/.local/state/coredesktop/pacman-update.log"
|
|
|
|
# True if the last update rebuilt the initramfs and mkinitcpio reported a
|
|
# failure. Rebooting then can leave the machine unbootable.
|
|
initramfs_build_failed() {
|
|
[ -f "$PACMAN_UPDATE_LOG" ] || return 1
|
|
grep -q "Updating linux initcpios" "$PACMAN_UPDATE_LOG" || return 1
|
|
grep -q "==> ERROR" "$PACMAN_UPDATE_LOG" && return 0
|
|
|
|
# Every image built must report success. Older mkinitcpio says "==> Image
|
|
# generation successful", newer says "==> Initcpio image generation successful".
|
|
local built succeeded
|
|
built="$(grep -c "==> Building image from preset" "$PACMAN_UPDATE_LOG")"
|
|
succeeded="$(grep -cE "==> (Initcpio i|I)mage generation successful" "$PACMAN_UPDATE_LOG")"
|
|
(( succeeded == 0 || succeeded < built ))
|
|
}
|
|
|
|
# pacman downloads from the first mirror in this list (system/mirrors.sh keeps
|
|
# it fresh). Overridable for the tests.
|
|
MIRRORLIST="${MIRRORLIST:-/etc/pacman.d/mirrorlist}"
|
|
# A mirror further behind Arch than this is stale; most sync every few hours
|
|
MIRROR_STALE_HOURS=24
|
|
|
|
# The first mirror in MIRRORLIST, without its /$repo/os/$arch
|
|
first_mirror() {
|
|
sed -nE 's/^[[:space:]]*Server[[:space:]]*=[[:space:]]*//p' "$MIRRORLIST" 2> /dev/null \
|
|
| head -n 1 | sed -E 's#/\$repo.*##'
|
|
}
|
|
|
|
# Print how many hours ago the first mirror last synced with Arch, from the
|
|
# timestamp mirrors publish in lastsync. Fails if it couldn't tell.
|
|
mirror_age_hours() {
|
|
local mirror lastsync
|
|
mirror="$(first_mirror)"
|
|
[ -n "$mirror" ] || return 1
|
|
lastsync="$(curl -sf --max-time 10 "$mirror/lastsync" 2> /dev/null)" || return 1
|
|
[[ "$lastsync" =~ ^[0-9]+$ ]] || return 1
|
|
echo $(( ($(date +%s) - lastsync) / 3600 ))
|
|
}
|
|
|
|
# Install a script atomically (copy to a temp file, then rename over the target)
|
|
# so a copy of it that is running right now keeps reading the old file
|
|
install_script() {
|
|
local tmp
|
|
tmp="$(mktemp "$2.XXXXXX")" && cp "$1" "$tmp" && chmod 755 "$tmp" && mv -f "$tmp" "$2"
|
|
}
|