Add shellcheck lint and bats tests
make lint runs shellcheck on every tracked bash script, make test runs the bats suite in tests/, and make check runs both. The tests cover the update orchestrator (hook isolation, failure reporting, pull and free-space handling) and utils.sh (migrations, package install and removal, services, initramfs build check), using stub commands so they never touch the real system. Fixes from the first lint run: 20-aur.sh stops if a cd fails rather than building or deleting in the wrong directory, and 80-gnome-extensions.sh refuses to rm -rf with an empty path. The rest are false positives, silenced in .shellcheckrc or per line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# Hooks, configure items and config files are sourced from paths only known at
|
||||||
|
# run time
|
||||||
|
disable=SC1090
|
||||||
|
|
||||||
|
# mkdir -p -m only sets the mode of the last directory; every use here makes
|
||||||
|
# one directory in a parent that already exists
|
||||||
|
disable=SC2174
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Every tracked file (not symlink) that starts with a bash or sh shebang (\043 is "#", which
|
||||||
|
# make would otherwise read as the start of a comment)
|
||||||
|
SCRIPTS := $(shell git ls-files -s | awk '$$1 != 120000 { print $$4 }' | xargs awk 'FNR == 1 && /^#!.*[\/ ](ba)?sh/ { print FILENAME }')
|
||||||
|
|
||||||
|
.PHONY: check lint test
|
||||||
|
|
||||||
|
check: lint test
|
||||||
|
|
||||||
|
# Errors and warnings only, for now; drop --severity to see style notes too
|
||||||
|
lint:
|
||||||
|
shellcheck --severity=warning $(SCRIPTS)
|
||||||
|
|
||||||
|
test:
|
||||||
|
bats tests
|
||||||
@@ -15,10 +15,10 @@ fi
|
|||||||
# The native installer places the binary in ~/.local/bin
|
# The native installer places the binary in ~/.local/bin
|
||||||
LINE='export PATH="$HOME/.local/bin:$PATH"'
|
LINE='export PATH="$HOME/.local/bin:$PATH"'
|
||||||
if grep -qF "$LINE" ~/.bashrc; then
|
if grep -qF "$LINE" ~/.bashrc; then
|
||||||
echo "~/.local/bin already on PATH in .bashrc"
|
echo "\~/.local/bin already on PATH in .bashrc"
|
||||||
else
|
else
|
||||||
echo "$LINE" >> ~/.bashrc
|
echo "$LINE" >> ~/.bashrc
|
||||||
echo "~/.local/bin added to PATH in .bashrc"
|
echo "\~/.local/bin added to PATH in .bashrc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Desktop notifications via notify-send (libnotify)
|
# Desktop notifications via notify-send (libnotify)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ load_items() {
|
|||||||
show_items() {
|
show_items() {
|
||||||
local i mark
|
local i mark
|
||||||
for i in "${!ITEMS[@]}"; do
|
for i in "${!ITEMS[@]}"; do
|
||||||
if [ "${STATES[$i]}" = done ]; then
|
if [ "${STATES[$i]}" = "done" ]; then
|
||||||
mark="\e[32mdone\e[0m "
|
mark="\e[32mdone\e[0m "
|
||||||
else
|
else
|
||||||
mark="\e[33mto do\e[0m"
|
mark="\e[33mto do\e[0m"
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# configure reads TITLE and DESCRIPTION
|
||||||
|
# shellcheck disable=SC2034
|
||||||
TITLE="Federated Computer Core"
|
TITLE="Federated Computer Core"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
DESCRIPTION="Superpanel, Nextcloud, email, files and Element"
|
DESCRIPTION="Superpanel, Nextcloud, email, files and Element"
|
||||||
|
|
||||||
# Each Core is in cores/<domain>.conf, with what you entered last time, so
|
# Each Core is in cores/<domain>.conf, with what you entered last time, so
|
||||||
@@ -309,7 +312,7 @@ step() {
|
|||||||
# can't be reached.
|
# can't be reached.
|
||||||
ask_password() {
|
ask_password() {
|
||||||
local tries
|
local tries
|
||||||
for tries in 1 2 3; do
|
for _ in 1 2 3; do
|
||||||
read -rsp "Your Core password: " PASSWORD
|
read -rsp "Your Core password: " PASSWORD
|
||||||
echo
|
echo
|
||||||
if [ -n "$1" ]; then nextcloud_app_password "$1"; else nextcloud_sign_in; fi
|
if [ -n "$1" ]; then nextcloud_app_password "$1"; else nextcloud_sign_in; fi
|
||||||
@@ -381,7 +384,7 @@ change_password() {
|
|||||||
local tries reached=1
|
local tries reached=1
|
||||||
load_core "$1"
|
load_core "$1"
|
||||||
STATUS=0
|
STATUS=0
|
||||||
for tries in 1 2 3; do
|
for _ in 1 2 3; do
|
||||||
read -rsp "Your new Core password: " PASSWORD
|
read -rsp "Your new Core password: " PASSWORD
|
||||||
echo
|
echo
|
||||||
nextcloud_sign_in
|
nextcloud_sign_in
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# configure reads TITLE and DESCRIPTION
|
||||||
|
# shellcheck disable=SC2034
|
||||||
TITLE="Tailscale"
|
TITLE="Tailscale"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
DESCRIPTION="Join your Core's Tailscale network"
|
DESCRIPTION="Join your Core's Tailscale network"
|
||||||
|
|
||||||
CORE_CONF="$HOME/.config/coredesktop/core.conf"
|
CORE_CONF="$HOME/.config/coredesktop/core.conf"
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# configure reads TITLE and DESCRIPTION
|
||||||
|
# shellcheck disable=SC2034
|
||||||
TITLE="Backup"
|
TITLE="Backup"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
DESCRIPTION="Back up your home folder daily to your Nextcloud files"
|
DESCRIPTION="Back up your home folder daily to your Nextcloud files"
|
||||||
|
|
||||||
CORE_CONF="$HOME/.config/coredesktop/core.conf"
|
CORE_CONF="$HOME/.config/coredesktop/core.conf"
|
||||||
@@ -33,7 +36,7 @@ nextcloud_login() {
|
|||||||
local tries
|
local tries
|
||||||
[ -n "$(backup_secret nextcloud "$WEBDAV")" ] \
|
[ -n "$(backup_secret nextcloud "$WEBDAV")" ] \
|
||||||
&& rclone lsf --max-depth 1 ":webdav:" > /dev/null 2>&1 && return 0
|
&& rclone lsf --max-depth 1 ":webdav:" > /dev/null 2>&1 && return 0
|
||||||
for tries in 1 2 3; do
|
for _ in 1 2 3; do
|
||||||
read -rsp "Your Core password: " PASSWORD
|
read -rsp "Your Core password: " PASSWORD
|
||||||
echo
|
echo
|
||||||
nextcloud_app_password "CoreDesktop backup on $HOSTNAME"
|
nextcloud_app_password "CoreDesktop backup on $HOSTNAME"
|
||||||
@@ -161,7 +164,7 @@ run_configure() {
|
|||||||
echo "There's already a backup in $BACKUP_DIR in your Nextcloud files."
|
echo "There's already a backup in $BACKUP_DIR in your Nextcloud files."
|
||||||
read -rsp "Its passphrase: " pass
|
read -rsp "Its passphrase: " pass
|
||||||
echo
|
echo
|
||||||
RESTIC_PASSWORD_COMMAND= RESTIC_PASSWORD="$pass" restic cat config > /dev/null \
|
RESTIC_PASSWORD_COMMAND='' RESTIC_PASSWORD="$pass" restic cat config > /dev/null \
|
||||||
|| { echo "restic couldn't open it with that passphrase."; return 1; }
|
|| { echo "restic couldn't open it with that passphrase."; return 1; }
|
||||||
save_secret passphrase "$WEBDAV/$BACKUP_DIR" "$pass" || return 1
|
save_secret passphrase "$WEBDAV/$BACKUP_DIR" "$pass" || return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# configure reads TITLE and DESCRIPTION
|
||||||
|
# shellcheck disable=SC2034
|
||||||
TITLE="Claude Code"
|
TITLE="Claude Code"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
DESCRIPTION="Sign in to Anthropic"
|
DESCRIPTION="Sign in to Anthropic"
|
||||||
|
|
||||||
is_installed() {
|
is_installed() {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# configure reads TITLE and DESCRIPTION
|
||||||
|
# shellcheck disable=SC2034
|
||||||
TITLE="opencode"
|
TITLE="opencode"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
DESCRIPTION="Sign in to AI providers"
|
DESCRIPTION="Sign in to AI providers"
|
||||||
|
|
||||||
is_installed() {
|
is_installed() {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# configure reads TITLE and DESCRIPTION
|
||||||
|
# shellcheck disable=SC2034
|
||||||
TITLE="Hermes Agent"
|
TITLE="Hermes Agent"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
DESCRIPTION="Choose a model provider and tools"
|
DESCRIPTION="Choose a model provider and tools"
|
||||||
|
|
||||||
is_installed() {
|
is_installed() {
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ source "$COREDESKTOP/system/backup-env.sh" || exit 1
|
|||||||
[ -n "$RESTIC_PASSWORD_COMMAND" ] || fail "The backup's passphrase isn't in your keyring. Run: configure backup"
|
[ -n "$RESTIC_PASSWORD_COMMAND" ] || fail "The backup's passphrase isn't in your keyring. Run: configure backup"
|
||||||
|
|
||||||
# Run at login, perhaps before the network is up; give it a few minutes
|
# Run at login, perhaps before the network is up; give it a few minutes
|
||||||
for try in {1..10}; do
|
for _ in {1..10}; do
|
||||||
curl -sf --max-time 10 -o /dev/null "${BACKUP_WEBDAV%%/remote.php/*}/status.php" && break
|
curl -sf --max-time 10 -o /dev/null "${BACKUP_WEBDAV%%/remote.php/*}/status.php" && break
|
||||||
sleep 30
|
sleep 30
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Shared setup for the bats tests. Each test gets its own HOME and a bin dir at
|
||||||
|
# the front of PATH where it can put fake versions of pacman, yay, sudo, etc.
|
||||||
|
|
||||||
|
REPO="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
|
||||||
|
|
||||||
|
common_setup() {
|
||||||
|
export HOME="$BATS_TEST_TMPDIR/home"
|
||||||
|
export STUB_BIN="$BATS_TEST_TMPDIR/bin"
|
||||||
|
export STUB_LOG="$BATS_TEST_TMPDIR/stub.log"
|
||||||
|
mkdir -p "$HOME" "$STUB_BIN"
|
||||||
|
: > "$STUB_LOG"
|
||||||
|
export PATH="$STUB_BIN:$PATH"
|
||||||
|
unset COREDESKTOP
|
||||||
|
}
|
||||||
|
|
||||||
|
# A test file that needs more setup defines its own setup that calls this first
|
||||||
|
setup() {
|
||||||
|
common_setup
|
||||||
|
}
|
||||||
|
|
||||||
|
# stub NAME [BODY]: put a fake NAME on PATH that appends "NAME args..." to
|
||||||
|
# $STUB_LOG and then runs BODY, a bash snippet that sees the args as "$@"
|
||||||
|
stub() {
|
||||||
|
local name="$1" body="${2:-}"
|
||||||
|
cat > "$STUB_BIN/$name" << EOF
|
||||||
|
#!/bin/bash
|
||||||
|
echo "$name \$*" >> "\$STUB_LOG"
|
||||||
|
$body
|
||||||
|
EOF
|
||||||
|
chmod +x "$STUB_BIN/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# called NAME ARGS...: true if the stub log has exactly the line "NAME ARGS..."
|
||||||
|
called() {
|
||||||
|
grep -qxF "$*" "$STUB_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# not_called NAME: true if the stub NAME never ran
|
||||||
|
not_called() {
|
||||||
|
! grep -q "^$1 " "$STUB_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# uncalled NAME ARGS...: the opposite of called. (A bare "! called ..." would
|
||||||
|
# not fail the test: bats, like set -e, ignores the status of a ! command.)
|
||||||
|
uncalled() {
|
||||||
|
! called "$@"
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load test_helper
|
||||||
|
|
||||||
|
# Each test runs the real update script from a fake CoreDesktop checkout in the
|
||||||
|
# test's HOME, with fake hooks in updates.d/ that log to $RAN when they run.
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
# A checkout with nothing but the update script and the files it installs
|
||||||
|
common_setup
|
||||||
|
CD="$HOME/.local/share/coredesktop"
|
||||||
|
mkdir -p "$CD/updates.d" "$HOME/.local/bin"
|
||||||
|
cp "$REPO/update" "$CD/update"
|
||||||
|
for script in refresh sibling version configure audit; do
|
||||||
|
echo '#!/bin/bash' > "$CD/$script"
|
||||||
|
done
|
||||||
|
|
||||||
|
export RAN="$BATS_TEST_TMPDIR/ran"
|
||||||
|
unset UPDATE_REEXECED UPDATE_PULL_FAILED UPDATE_FORCE
|
||||||
|
|
||||||
|
stub clear
|
||||||
|
stub git 'exit "${GIT_STATUS:-0}"'
|
||||||
|
stub sudo 'exit "${SUDO_STATUS:-0}"'
|
||||||
|
stub df 'printf "Avail\n%s\n" "${DF_AVAIL:-104857600}"'
|
||||||
|
# Let go of bats' output pipes so the sudo keepalive's leftover sleep
|
||||||
|
# doesn't keep the test waiting
|
||||||
|
stub sleep 'exec > /dev/null 2>&1 3>&-; exec /usr/bin/sleep "$@"'
|
||||||
|
}
|
||||||
|
|
||||||
|
# hook NAME BODY: add updates.d/NAME.sh with BODY as its run_update
|
||||||
|
hook() {
|
||||||
|
printf '#!/bin/bash\nrun_update() {\n%s\n}\n' "$2" > "$CD/updates.d/$1.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
ran() {
|
||||||
|
[ "$(cat "$RAN" 2> /dev/null)" = "$(printf '%s\n' "$@")" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "update pulls, installs its scripts, and runs every hook in order" {
|
||||||
|
hook 20-b 'echo b >> "$RAN"'
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Done."* ]]
|
||||||
|
called git -C "$CD" pull
|
||||||
|
ran a b
|
||||||
|
for script in update refresh sibling version configure audit; do
|
||||||
|
[ -x "$HOME/.local/bin/$script" ]
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "update pulls only once, before re-running its new copy" {
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$(grep -c '^git ' "$STUB_LOG")" -eq 1 ]
|
||||||
|
ran a
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a failing hook doesn't stop the hooks after it" {
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
hook 20-b 'echo b >> "$RAN"; return 1'
|
||||||
|
hook 30-c 'echo c >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"Done, with errors in: 20-b.sh"* ]]
|
||||||
|
ran a b c
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a hook that exits doesn't end the update" {
|
||||||
|
hook 10-a 'echo a >> "$RAN"; exit 0'
|
||||||
|
hook 20-b 'echo b >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ran a b
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a hook using set -e fails at its first failing command" {
|
||||||
|
hook 10-a 'set -e; echo a >> "$RAN"; false; echo after >> "$RAN"'
|
||||||
|
hook 20-b 'echo b >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"errors in: 10-a.sh"* ]]
|
||||||
|
ran a b
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a hook's is_installed and functions don't leak into the next hook" {
|
||||||
|
printf '%s\n' '#!/bin/bash' 'is_installed() { false; }' \
|
||||||
|
'run_update() { echo a >> "$RAN"; }' > "$CD/updates.d/10-a.sh"
|
||||||
|
# 10-a's is_installed must not stop 20-b, and its run_update must not run
|
||||||
|
# again for 30-c, which has none
|
||||||
|
printf '%s\n' '#!/bin/bash' 'run_update() { echo b >> "$RAN"; }' \
|
||||||
|
> "$CD/updates.d/20-b.sh"
|
||||||
|
printf '%s\n' '#!/bin/bash' 'true' > "$CD/updates.d/30-c.sh"
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ran b
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "update runs the hooks on disk when git pull fails, and reports it" {
|
||||||
|
export GIT_STATUS=1
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"could not pull"* ]]
|
||||||
|
[[ "$output" == *"errors in: git pull"* ]]
|
||||||
|
ran a
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "update stops before any hook when / is low on space" {
|
||||||
|
export DF_AVAIL=1048576
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"needs at least 10 GiB"* ]]
|
||||||
|
[ ! -e "$RAN" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "UPDATE_FORCE skips the free space check" {
|
||||||
|
export DF_AVAIL=1048576 UPDATE_FORCE=1
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ran a
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "update stops when sudo fails" {
|
||||||
|
export SUDO_STATUS=1
|
||||||
|
hook 10-a 'echo a >> "$RAN"'
|
||||||
|
|
||||||
|
run "$CD/update"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[ ! -e "$RAN" ]
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load test_helper
|
||||||
|
|
||||||
|
# --- run_migrations ---------------------------------------------------------
|
||||||
|
|
||||||
|
# migration NAME [EXIT]: add migrations/NAME.sh that logs its name and exits EXIT
|
||||||
|
migration() {
|
||||||
|
mkdir -p "$COREDESKTOP/migrations"
|
||||||
|
printf '#!/bin/bash\necho %s >> "%s"\nexit %s\n' "$1" "$RAN" "${2:-0}" \
|
||||||
|
> "$COREDESKTOP/migrations/$1.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_migrations() {
|
||||||
|
export COREDESKTOP="$BATS_TEST_TMPDIR/coredesktop"
|
||||||
|
RAN="$BATS_TEST_TMPDIR/ran"
|
||||||
|
STATE="$HOME/.local/state/coredesktop/migrations"
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "run_migrations runs each migration once, oldest first" {
|
||||||
|
setup_migrations
|
||||||
|
migration 1792592710
|
||||||
|
migration 1792592700
|
||||||
|
migration 1792592720
|
||||||
|
|
||||||
|
run run_migrations
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$(cat "$RAN")" = "$(printf '1792592700\n1792592710\n1792592720')" ]
|
||||||
|
[ -f "$STATE/1792592700.sh" ]
|
||||||
|
[ -f "$STATE/1792592720.sh" ]
|
||||||
|
|
||||||
|
run run_migrations
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$(wc -l < "$RAN")" -eq 3 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "run_migrations stops at a failure and retries it next time" {
|
||||||
|
setup_migrations
|
||||||
|
migration 1792592700
|
||||||
|
migration 1792592710 1
|
||||||
|
migration 1792592720
|
||||||
|
|
||||||
|
run run_migrations
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[ "$(cat "$RAN")" = "$(printf '1792592700\n1792592710')" ]
|
||||||
|
[ -f "$STATE/1792592700.sh" ]
|
||||||
|
[ ! -f "$STATE/1792592710.sh" ]
|
||||||
|
[ ! -f "$STATE/1792592720.sh" ]
|
||||||
|
|
||||||
|
migration 1792592710
|
||||||
|
run run_migrations
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$(tail -n 2 "$RAN")" = "$(printf '1792592710\n1792592720')" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "run_migrations does nothing without a migrations dir" {
|
||||||
|
setup_migrations
|
||||||
|
run run_migrations
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ ! -e "$RAN" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "migrations are named by timestamp so they sort in the order written" {
|
||||||
|
for file in "$REPO"/migrations/*; do
|
||||||
|
[[ "$(basename "$file")" =~ ^[0-9]{10}\.sh$ ]] || {
|
||||||
|
echo "bad migration name: $file"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- install_packages / remove_packages -------------------------------------
|
||||||
|
|
||||||
|
# A fake pacman that knows about the packages in $INSTALLED, of which those in
|
||||||
|
# $AS_DEPS were installed as dependencies
|
||||||
|
stub_pacman() {
|
||||||
|
stub pacman '
|
||||||
|
case "$1" in
|
||||||
|
-Qi) [[ " $INSTALLED " == *" $2 "* ]] ;;
|
||||||
|
-Qg) exit 1 ;;
|
||||||
|
-Qdq) shift; for p; do [[ " $AS_DEPS " == *" $p "* ]] && echo "$p"; done; exit 0 ;;
|
||||||
|
esac'
|
||||||
|
stub sudo
|
||||||
|
stub yay
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "install_packages installs only the missing packages" {
|
||||||
|
export INSTALLED="git vim" AS_DEPS=""
|
||||||
|
stub_pacman
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run install_packages git tmux vim neovim
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
called yay -S --noconfirm --answerdiff=None --answeredit=None --answerclean=None tmux neovim
|
||||||
|
not_called sudo
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "install_packages does nothing when everything is installed" {
|
||||||
|
export INSTALLED="git vim" AS_DEPS=""
|
||||||
|
stub_pacman
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run install_packages git vim
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
not_called yay
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "install_packages marks packages installed as dependencies explicit" {
|
||||||
|
export INSTALLED="git python" AS_DEPS="python"
|
||||||
|
stub_pacman
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run install_packages git python
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
called sudo pacman -D --asexplicit python
|
||||||
|
not_called yay
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "remove_packages removes only exact matches, not providers" {
|
||||||
|
# herdr-bin provides herdr, so pacman -Qq herdr prints herdr-bin
|
||||||
|
stub pacman '
|
||||||
|
[ "$1" = -Qq ] || exit 1
|
||||||
|
case "$2" in
|
||||||
|
herdr) echo herdr-bin ;;
|
||||||
|
vim) echo vim ;;
|
||||||
|
*) exit 1 ;;
|
||||||
|
esac'
|
||||||
|
stub sudo
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run remove_packages herdr vim missing
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
called sudo pacman -Rns --noconfirm vim
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "remove_packages does nothing when nothing is installed" {
|
||||||
|
stub pacman 'exit 1'
|
||||||
|
stub sudo
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run remove_packages vim
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
not_called sudo
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- enable_services --------------------------------------------------------
|
||||||
|
|
||||||
|
@test "enable_services enables disabled services and skips the rest" {
|
||||||
|
stub systemctl '[ "$1" = is-enabled ] && [ "$2" = sshd ]'
|
||||||
|
stub sudo
|
||||||
|
mkdir -p "$HOME/.config/coredesktop"
|
||||||
|
echo "system cups" > "$HOME/.config/coredesktop/services-off"
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run enable_services --now sshd cups bluetooth
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
called sudo systemctl daemon-reload
|
||||||
|
called sudo systemctl enable --now bluetooth
|
||||||
|
uncalled sudo systemctl enable --now sshd
|
||||||
|
uncalled sudo systemctl enable --now cups
|
||||||
|
[[ "$output" == *"cups is turned off"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "enable_services without --now only enables" {
|
||||||
|
stub systemctl 'exit 1'
|
||||||
|
stub sudo
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
|
||||||
|
run enable_services bluetooth
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
called sudo systemctl enable bluetooth
|
||||||
|
uncalled sudo systemctl daemon-reload
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- initramfs_build_failed -------------------------------------------------
|
||||||
|
|
||||||
|
pacman_log() {
|
||||||
|
mkdir -p "$HOME/.local/state/coredesktop"
|
||||||
|
printf '%s\n' "$@" > "$HOME/.local/state/coredesktop/pacman-update.log"
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "initramfs_build_failed is false without a log" {
|
||||||
|
source "$REPO/utils.sh"
|
||||||
|
run initramfs_build_failed
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "initramfs_build_failed is false when no initramfs was built" {
|
||||||
|
pacman_log "upgrading vim..."
|
||||||
|
run initramfs_build_failed
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "initramfs_build_failed is false when every image built" {
|
||||||
|
pacman_log "(3/4) Updating linux initcpios..." \
|
||||||
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'" \
|
||||||
|
"==> Image generation successful" \
|
||||||
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'" \
|
||||||
|
"==> Initcpio image generation successful"
|
||||||
|
run initramfs_build_failed
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "initramfs_build_failed is true when mkinitcpio reports an error" {
|
||||||
|
pacman_log "(3/4) Updating linux initcpios..." \
|
||||||
|
"==> ERROR: module not found" \
|
||||||
|
"==> Image generation successful"
|
||||||
|
run initramfs_build_failed
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "initramfs_build_failed is true when an image didn't build" {
|
||||||
|
pacman_log "(3/4) Updating linux initcpios..." \
|
||||||
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'" \
|
||||||
|
"==> Image generation successful" \
|
||||||
|
"==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'"
|
||||||
|
run initramfs_build_failed
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
+3
-3
@@ -10,14 +10,14 @@ YAY_FLAGS="--noconfirm --answerdiff=None --answeredit=None --answerclean=None"
|
|||||||
if ! yay --version &>/dev/null; then
|
if ! yay --version &>/dev/null; then
|
||||||
echo "yay is broken (likely a libalpm bump). Rebuilding yay..."
|
echo "yay is broken (likely a libalpm bump). Rebuilding yay..."
|
||||||
sudo pacman -R --noconfirm yay
|
sudo pacman -R --noconfirm yay
|
||||||
cd /tmp
|
cd /tmp || exit 1
|
||||||
rm -rf yay
|
rm -rf yay
|
||||||
git clone https://aur.archlinux.org/yay.git
|
git clone https://aur.archlinux.org/yay.git
|
||||||
cd yay
|
cd yay || exit 1
|
||||||
makepkg -si --noconfirm
|
makepkg -si --noconfirm
|
||||||
cd ..
|
cd ..
|
||||||
rm -rf yay
|
rm -rf yay
|
||||||
cd
|
cd || exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
yay -Sua $YAY_FLAGS
|
yay -Sua $YAY_FLAGS
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ reinstall_incompatible_extensions() {
|
|||||||
rm -rf "$backup"
|
rm -rf "$backup"
|
||||||
else
|
else
|
||||||
echo "Reinstall of $uuid failed; restoring the previous version."
|
echo "Reinstall of $uuid failed; restoring the previous version."
|
||||||
rm -rf "$ext_root/$uuid"
|
rm -rf "${ext_root:?}/${uuid:?}"
|
||||||
mv "$backup/$uuid" "$ext_root/$uuid"
|
mv "$backup/$uuid" "$ext_root/$uuid"
|
||||||
rmdir "$backup"
|
rmdir "$backup"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user