Merge remote-tracking branch 'origin/main' into feat/panama-health
# Conflicts: # config/dot/quickshell/modules/settings/HealthPage.qml # tests/quickshell/health-ui-contract.sh
This commit is contained in:
@@ -8,7 +8,7 @@ SettingsPage {
|
|||||||
objectName: "system-health-page"
|
objectName: "system-health-page"
|
||||||
|
|
||||||
title: "System Health"
|
title: "System Health"
|
||||||
lede: "Panama checks the parts of your desktop it owns and explains what needs attention."
|
lede: "Checks the parts of the desktop this app owns, and explains what needs attention."
|
||||||
|
|
||||||
property var pendingConfirmation: null
|
property var pendingConfirmation: null
|
||||||
property string instructionTarget: ""
|
property string instructionTarget: ""
|
||||||
@@ -32,7 +32,7 @@ SettingsPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
group: "panama-tools",
|
group: "panama-tools",
|
||||||
title: "Panama tools",
|
title: "Desktop tools",
|
||||||
subtitle: "Tracked links, launcher commands, apps, and inhibitors."
|
subtitle: "Tracked links, launcher commands, apps, and inhibitors."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -204,7 +204,7 @@ SettingsPage {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: root.pendingConfirmation
|
text: root.pendingConfirmation
|
||||||
? `${root.pendingConfirmation.action.label}?`
|
? `${root.pendingConfirmation.action.label}?`
|
||||||
: "Restart Panama?"
|
: "Restart the shell?"
|
||||||
color: Theme.fg
|
color: Theme.fg
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSize
|
font.pixelSize: Theme.fontSize
|
||||||
@@ -234,7 +234,7 @@ SettingsPage {
|
|||||||
|
|
||||||
SettingsButton {
|
SettingsButton {
|
||||||
id: repairButton
|
id: repairButton
|
||||||
text: root.pendingConfirmation ? root.pendingConfirmation.action.label : "Restart Panama"
|
text: root.pendingConfirmation ? root.pendingConfirmation.action.label : "Restart the shell"
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
border.width: activeFocus ? 2 : 1
|
border.width: activeFocus ? 2 : 1
|
||||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||||
@@ -297,7 +297,7 @@ SettingsPage {
|
|||||||
SettingsCard {
|
SettingsCard {
|
||||||
visible: root.instructionTarget === "ddc-permissions"
|
visible: root.instructionTarget === "ddc-permissions"
|
||||||
title: "External monitor brightness"
|
title: "External monitor brightness"
|
||||||
subtitle: "Panama can see DDC/CI support, but this session cannot access the monitor bus."
|
subtitle: "The monitor reports DDC/CI support, but this session cannot reach the monitor bus."
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -401,7 +401,7 @@ SettingsPage {
|
|||||||
anchors.right: gnomeSettingsButton.left
|
anchors.right: gnomeSettingsButton.left
|
||||||
anchors.rightMargin: 18
|
anchors.rightMargin: 18
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Use GNOME Settings for the parts of the system Panama does not manage."
|
text: "Use GNOME Settings for the parts of the system this app does not manage."
|
||||||
color: Theme.fgDim
|
color: Theme.fgDim
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ SettingsCard {
|
|||||||
if (Health.diagnosticUnavailable)
|
if (Health.diagnosticUnavailable)
|
||||||
return "Health check unavailable";
|
return "Health check unavailable";
|
||||||
if (Health.checks.length === 0)
|
if (Health.checks.length === 0)
|
||||||
return "Checking Panama desktop";
|
return "Checking the desktop";
|
||||||
if (Health.status === "error")
|
if (Health.status === "error")
|
||||||
return "Action required";
|
return "Action required";
|
||||||
if (Health.status === "warning")
|
if (Health.status === "warning")
|
||||||
@@ -22,9 +22,9 @@ SettingsCard {
|
|||||||
}
|
}
|
||||||
readonly property string heroDetail: {
|
readonly property string heroDetail: {
|
||||||
if (Health.diagnosticUnavailable)
|
if (Health.diagnosticUnavailable)
|
||||||
return Health.lastError || "Panama could not complete the latest health check.";
|
return Health.lastError || "The latest health check could not be completed.";
|
||||||
if (Health.checks.length === 0)
|
if (Health.checks.length === 0)
|
||||||
return "Panama is checking the desktop services, tools, and integrations it owns.";
|
return "Checking the desktop services, tools, and integrations this app owns.";
|
||||||
if (Health.status === "error")
|
if (Health.status === "error")
|
||||||
return root.observationCount === 1
|
return root.observationCount === 1
|
||||||
? "One part of the desktop needs action."
|
? "One part of the desktop needs action."
|
||||||
@@ -33,7 +33,7 @@ SettingsCard {
|
|||||||
return root.observationCount === 1
|
return root.observationCount === 1
|
||||||
? "Your desktop is working. One feature needs a decision."
|
? "Your desktop is working. One feature needs a decision."
|
||||||
: `Your desktop is working. ${root.observationCount} features need a decision.`;
|
: `Your desktop is working. ${root.observationCount} features need a decision.`;
|
||||||
return "Panama-owned desktop services and tools are working normally.";
|
return "Desktop services and tools are working normally.";
|
||||||
}
|
}
|
||||||
readonly property color statusColor: {
|
readonly property color statusColor: {
|
||||||
if (Health.diagnosticUnavailable || Health.status === "error")
|
if (Health.diagnosticUnavailable || Health.status === "error")
|
||||||
|
|||||||
@@ -301,14 +301,14 @@ Rectangle {
|
|||||||
|
|
||||||
function footerText(): string {
|
function footerText(): string {
|
||||||
if (Health.checks.length === 0)
|
if (Health.checks.length === 0)
|
||||||
return Health.diagnosticUnavailable ? "Health check unavailable" : "Checking Panama desktop";
|
return Health.diagnosticUnavailable ? "Health check unavailable" : "Checking the desktop";
|
||||||
if (Health.status === "error")
|
if (Health.status === "error")
|
||||||
return "Panama requires attention";
|
return "Desktop needs attention";
|
||||||
if (Health.status === "warning") {
|
if (Health.status === "warning") {
|
||||||
const count = Health.summary.warnings + Health.summary.errors;
|
const count = Health.summary.warnings + Health.summary.errors;
|
||||||
return count + (count === 1 ? " health observation" : " health observations");
|
return count + (count === 1 ? " health observation" : " health observations");
|
||||||
}
|
}
|
||||||
return "Panama desktop is healthy";
|
return "Desktop is healthy";
|
||||||
}
|
}
|
||||||
|
|
||||||
function footerColor(): color {
|
function footerColor(): color {
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
source ~/.local/share/Panama/bin/ascii
|
|
||||||
# Set host name
|
# Panama's installer. Safe to re-run: every stage is idempotent, and this is
|
||||||
|
# also the upgrade path.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
|
||||||
|
source "$PANAMA_PATH/bin/ascii"
|
||||||
|
|
||||||
|
# ── Hostname, which is optional ──────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Declining this used to `exit`, which aborted the ENTIRE installation. The
|
||||||
|
# prompt defaults to N, so simply pressing Enter -- the obvious thing to do when
|
||||||
|
# you do not want to rename your machine -- installed nothing at all and said
|
||||||
|
# nothing about it.
|
||||||
echo -e "Current hostname is: $(hostname)"
|
echo -e "Current hostname is: $(hostname)"
|
||||||
read -p "Do you want to change the hostname? [y/N]: " confirm_change
|
read -r -p "Do you want to change the hostname? [y/N]: " confirm_change
|
||||||
if [[ "$confirm_change" =~ ^[Yy]$ ]]; then
|
if [[ "$confirm_change" =~ ^[Yy]$ ]]; then
|
||||||
read -p "Hostname: " HOST_NAME
|
read -r -p "Hostname: " HOST_NAME
|
||||||
read -p "Set hostname to '$HOST_NAME'? [y/N]: " confirm_hostname
|
read -r -p "Set hostname to '$HOST_NAME'? [y/N]: " confirm_hostname
|
||||||
if [[ "$confirm_hostname" =~ ^[Yy]$ ]]; then
|
if [[ "$confirm_hostname" =~ ^[Yy]$ ]]; then
|
||||||
sudo hostnamectl set-hostname "$HOST_NAME"
|
sudo hostnamectl set-hostname "$HOST_NAME"
|
||||||
echo "Hostname set to: $(hostname)"
|
echo "Hostname set to: $(hostname)"
|
||||||
@@ -13,18 +26,45 @@ if [[ "$confirm_change" =~ ^[Yy]$ ]]; then
|
|||||||
echo "Hostname not changed."
|
echo "Hostname not changed."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Not changing hostname."
|
echo "Keeping the current hostname."
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure computer doesn't go to sleep.
|
# ── Keep the machine awake for the duration ──────────────────────────────────
|
||||||
gsettings set org.gnome.desktop.screensaver lock-enabled false
|
# Package installation takes long enough to hit an idle lock, and being locked
|
||||||
gsettings set org.gnome.desktop.session idle-delay 0
|
# out mid-transaction is unpleasant. Restored on every exit path, including
|
||||||
|
# failure and Ctrl-C, so an interrupted install does not leave the screen
|
||||||
|
# permanently awake.
|
||||||
|
restore_idle() {
|
||||||
|
gsettings set org.gnome.desktop.screensaver lock-enabled true 2>/dev/null || true
|
||||||
|
gsettings set org.gnome.desktop.session idle-delay 300 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap restore_idle EXIT INT TERM
|
||||||
|
|
||||||
# Run each setup stage in its own process. This keeps strict-shell options and
|
gsettings set org.gnome.desktop.screensaver lock-enabled false 2>/dev/null || true
|
||||||
# helper variables local to the script that owns them.
|
gsettings set org.gnome.desktop.session idle-delay 0 2>/dev/null || true
|
||||||
for script in ~/.local/share/Panama/setup/scripts/*; do "$script"; done
|
|
||||||
|
|
||||||
# Revert to normal idle settings
|
# ── Stages ───────────────────────────────────────────────────────────────────
|
||||||
gsettings set org.gnome.desktop.screensaver lock-enabled true
|
# Each runs in its own process so strict-shell options and helper variables stay
|
||||||
gsettings set org.gnome.desktop.session idle-delay 300
|
# local to the script that owns them. A failing stage is reported and the rest
|
||||||
|
# still run: a missing optional package should not stop the dotfiles being
|
||||||
|
# linked. The summary at the end is what decides whether the install worked,
|
||||||
|
# because a failure scrolled past twenty minutes ago is a failure nobody saw.
|
||||||
|
failed=()
|
||||||
|
for script in "$PANAMA_PATH"/setup/scripts/*; do
|
||||||
|
[[ -x "$script" ]] || continue
|
||||||
|
stage="$(basename "$script")"
|
||||||
|
printf '\n=== %s ===\n' "$stage"
|
||||||
|
if ! "$script"; then
|
||||||
|
failed+=("$stage")
|
||||||
|
printf '!!! %s failed\n' "$stage" >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
if (( ${#failed[@]} == 0 )); then
|
||||||
|
echo "Panama installed. Log out and choose the Hyprland session to start it."
|
||||||
|
else
|
||||||
|
printf 'Panama installed with %d failed stage(s): %s\n' "${#failed[@]}" "${failed[*]}" >&2
|
||||||
|
printf 'Re-running ./install is safe and will retry them.\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,38 +1,45 @@
|
|||||||
hyprland
|
NetworkManager
|
||||||
hyprland-uwsm
|
|
||||||
uwsm
|
|
||||||
quickshell
|
|
||||||
vicinae
|
|
||||||
hyprlock
|
|
||||||
hypridle
|
|
||||||
hyprpaper
|
|
||||||
hyprpicker
|
|
||||||
hyprsunset
|
|
||||||
hyprpolkitagent
|
|
||||||
hyprshutdown
|
|
||||||
hyprpwcenter
|
|
||||||
hyprsysteminfo
|
|
||||||
hyprland-guiutils
|
|
||||||
xdg-desktop-portal-hyprland
|
|
||||||
grim
|
|
||||||
slurp
|
|
||||||
grimblast
|
|
||||||
satty
|
|
||||||
wl-clipboard
|
|
||||||
wf-recorder
|
|
||||||
gpu-screen-recorder
|
|
||||||
brightnessctl
|
|
||||||
playerctl
|
|
||||||
pamixer
|
|
||||||
udiskie
|
|
||||||
wofi
|
|
||||||
adw-gtk3-theme
|
adw-gtk3-theme
|
||||||
adwaita-icon-theme
|
adwaita-icon-theme
|
||||||
adwaita-sans-fonts
|
adwaita-sans-fonts
|
||||||
qt6-qtwayland
|
brightnessctl
|
||||||
nm-connection-editor
|
ddcutil
|
||||||
|
gpu-screen-recorder
|
||||||
|
grim
|
||||||
|
grimblast
|
||||||
|
gtk-update-icon-cache
|
||||||
|
hypridle
|
||||||
|
hyprland
|
||||||
|
hyprland-guiutils
|
||||||
|
hyprland-uwsm
|
||||||
|
hyprlock
|
||||||
|
hyprpaper
|
||||||
|
hyprpicker
|
||||||
|
hyprpolkitagent
|
||||||
|
hyprpwcenter
|
||||||
|
hyprshutdown
|
||||||
|
hyprsunset
|
||||||
|
hyprsysteminfo
|
||||||
kde-connect
|
kde-connect
|
||||||
|
libnotify
|
||||||
|
nm-connection-editor
|
||||||
|
orca
|
||||||
|
pamixer
|
||||||
|
playerctl
|
||||||
|
qrencode
|
||||||
|
qt6-qtwayland
|
||||||
|
quickshell
|
||||||
|
satty
|
||||||
|
slurp
|
||||||
|
system-config-printer
|
||||||
tesseract
|
tesseract
|
||||||
tesseract-langpack-eng
|
tesseract-langpack-eng
|
||||||
|
udiskie
|
||||||
|
uwsm
|
||||||
|
vicinae
|
||||||
|
wf-recorder
|
||||||
|
wireplumber
|
||||||
|
wl-clipboard
|
||||||
|
wofi
|
||||||
|
xdg-desktop-portal-hyprland
|
||||||
zbar
|
zbar
|
||||||
system-config-printer
|
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
awk
|
awk
|
||||||
bat
|
bat
|
||||||
|
btop
|
||||||
cargo
|
cargo
|
||||||
curl
|
curl
|
||||||
eza
|
eza
|
||||||
|
fontconfig
|
||||||
|
fwupd
|
||||||
fzf
|
fzf
|
||||||
git-all
|
|
||||||
gh
|
gh
|
||||||
|
git-all
|
||||||
gum
|
gum
|
||||||
|
jq
|
||||||
|
kitty
|
||||||
ksshaskpass
|
ksshaskpass
|
||||||
|
libselinux-utils
|
||||||
neovim
|
neovim
|
||||||
openssl
|
openssl
|
||||||
|
pciutils
|
||||||
|
python3-dnf
|
||||||
python3-neovim
|
python3-neovim
|
||||||
rustup
|
rustup
|
||||||
|
tmux
|
||||||
unzip
|
unzip
|
||||||
wireguard-tools
|
|
||||||
wget
|
wget
|
||||||
|
wireguard-tools
|
||||||
zoxide
|
zoxide
|
||||||
|
|||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Every external command Panama's own scripts invoke must be installed by
|
||||||
|
# Panama's own package lists.
|
||||||
|
#
|
||||||
|
# This exists because the lists had drifted badly. jq is used by thirty-one call
|
||||||
|
# sites across the helpers and the contracts; kitty has a full shipped config
|
||||||
|
# and a dock pin; tmux and btop have shipped themes that the colour scheme
|
||||||
|
# switches. None of the four were declared. So a fresh machine that followed
|
||||||
|
# this repository's own install instructions would not have them.
|
||||||
|
#
|
||||||
|
# The failure is quiet by design, which is what makes it worth a test: the
|
||||||
|
# helpers are written to report "not installed" rather than crash, so a missing
|
||||||
|
# dependency presents as a feature that silently is not there.
|
||||||
|
#
|
||||||
|
# Commands from coreutils and the shell itself are not checked -- nothing
|
||||||
|
# installs those separately, and listing them would be noise.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'declared dependencies contract: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Shell syntax and builtins. These are not commands anyone installs, and the
|
||||||
|
# first version of this contract reported `then`, `esac` and `done` as missing
|
||||||
|
# packages, which buried the four real findings in a hundred lines of noise.
|
||||||
|
SHELL_WORDS='^(if|then|else|elif|fi|for|while|until|do|done|case|esac|in|function|select|time|coproc|break|continue|return|exit|local|readonly|declare|export|unset|shift|eval|exec|source|trap|set|shopt|alias|unalias|builtin|command|enable|help|let|read|mapfile|printf|echo|test|true|false|wait|jobs|bg|fg|kill|pwd|cd|dirs|pushd|popd|umask|type|hash|getopts|split|sync)$'
|
||||||
|
|
||||||
|
# Provided by any Fedora install: coreutils, util-linux, the shell, and the
|
||||||
|
# systemd/session tooling. Nothing here is a choice Panama makes.
|
||||||
|
BASELINE='^(sh|bash|cat|cut|sed|awk|gawk|grep|egrep|head|tail|sort|uniq|tr|wc|find|xargs|basename|dirname|mkdir|rm|cp|mv|ln|chmod|chown|stat|df|du|date|sleep|env|id|tee|touch|mktemp|readlink|realpath|seq|comm|join|paste|od|file|nl|fold|column|tput|timeout|flock|install|sha256sum|md5sum|base64|nproc|uptime|free|uname|hostname|whoami|ps|pgrep|pkill|kill|killall|lsblk|mount|umount|sudo|su|rpm|dnf|flatpak|git|python3|ss|ip|lsof)$'
|
||||||
|
|
||||||
|
SESSION='^(systemctl|busctl|journalctl|loginctl|hostnamectl|localectl|systemd-inhibit|systemd-run|udevadm|gsettings|dconf|dbus-send|dbus-monitor|hyprctl|qs|quickshell|gnf|panama|wl-copy|wl-paste)$'
|
||||||
|
|
||||||
|
declared="$(cat "$repo_dir"/setup/packages/* 2>/dev/null | sed 's/#.*//' | tr -d ' ' | grep -v '^$' | sort -u)"
|
||||||
|
[[ -n "$declared" ]] || fail 'no package lists found'
|
||||||
|
|
||||||
|
# A package is not always named after its command. Only the genuine mismatches
|
||||||
|
# are mapped, so an unmapped command is a real omission rather than a lookup
|
||||||
|
# failure.
|
||||||
|
package_for() {
|
||||||
|
case "$1" in
|
||||||
|
zbarimg) printf 'zbar' ;;
|
||||||
|
fc-list|fc-match) printf 'fontconfig' ;;
|
||||||
|
lspci) printf 'pciutils' ;;
|
||||||
|
getenforce) printf 'libselinux-utils' ;;
|
||||||
|
nmcli) printf 'NetworkManager' ;;
|
||||||
|
wpctl) printf 'wireplumber' ;;
|
||||||
|
nvim) printf 'neovim' ;;
|
||||||
|
fwupdmgr) printf 'fwupd' ;;
|
||||||
|
dnf4) printf 'python3-dnf' ;;
|
||||||
|
notify-send) printf 'libnotify' ;;
|
||||||
|
wl-copy|wl-paste) printf 'wl-clipboard' ;;
|
||||||
|
rg) printf 'ripgrep' ;;
|
||||||
|
python3) printf 'python3' ;;
|
||||||
|
*) printf '%s' "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
missing=()
|
||||||
|
checked=0
|
||||||
|
|
||||||
|
while read -r script; do
|
||||||
|
[[ -n "$script" ]] || continue
|
||||||
|
head -1 "$script" | grep -qE 'bash|/sh' || continue
|
||||||
|
|
||||||
|
# Commands appearing at the start of a statement or after a pipe. Crude, but
|
||||||
|
# it is looking for undeclared dependencies, not building a call graph.
|
||||||
|
#
|
||||||
|
# No minimum length. An earlier version required three characters, which
|
||||||
|
# quietly excluded the most-used dependency in the repository -- jq, at
|
||||||
|
# thirty-one call sites -- along with rg, ss and ip. A dependency checker
|
||||||
|
# with a blind spot for short names is worse than none, because it reports
|
||||||
|
# PASS.
|
||||||
|
while read -r cmd; do
|
||||||
|
[[ -n "$cmd" ]] || continue
|
||||||
|
[[ "$cmd" =~ $SHELL_WORDS ]] && continue
|
||||||
|
[[ "$cmd" =~ $BASELINE ]] && continue
|
||||||
|
[[ "$cmd" =~ $SESSION ]] && continue
|
||||||
|
|
||||||
|
pkg="$(package_for "$cmd")"
|
||||||
|
grep -qx "$pkg" <<<"$declared" && continue
|
||||||
|
|
||||||
|
# Only report a command that actually exists on this machine. An
|
||||||
|
# invented name in a comment or a heredoc is a false positive; a real
|
||||||
|
# binary that nothing declares is the thing being looked for.
|
||||||
|
command -v "$cmd" >/dev/null 2>&1 || continue
|
||||||
|
|
||||||
|
missing+=("$cmd (from $(basename "$script"), package: $pkg)")
|
||||||
|
done < <({
|
||||||
|
# Statement-initial or after a pipe.
|
||||||
|
grep -oE '(^|[|;&]|\$\()[[:space:]]*[a-z][a-z0-9_-]+' "$script" \
|
||||||
|
| grep -oE '[a-z][a-z0-9_-]+$'
|
||||||
|
|
||||||
|
# Behind a wrapper. ddcutil is always invoked as `timeout 10 ddcutil`,
|
||||||
|
# so it never appears statement-initial and was missed entirely.
|
||||||
|
grep -oE '\b(timeout[[:space:]]+[0-9.]+|sudo|nohup|env)[[:space:]]+[a-z][a-z0-9_-]+' "$script" \
|
||||||
|
| grep -oE '[a-z][a-z0-9_-]+$'
|
||||||
|
|
||||||
|
# `command -v X` is how these helpers probe for a tool before using it,
|
||||||
|
# which makes it the clearest possible statement of a dependency.
|
||||||
|
grep -oE 'command -v[[:space:]]+[a-z][a-z0-9_-]+' "$script" \
|
||||||
|
| grep -oE '[a-z][a-z0-9_-]+$'
|
||||||
|
} | sort -u)
|
||||||
|
|
||||||
|
checked=$((checked + 1))
|
||||||
|
done < <(find "$repo_dir/config/dot/quickshell/scripts" \
|
||||||
|
"$repo_dir/config/local/share/vicinae/scripts" \
|
||||||
|
"$repo_dir/setup/scripts" "$repo_dir/bin" \
|
||||||
|
-type f 2>/dev/null)
|
||||||
|
|
||||||
|
if (( ${#missing[@]} > 0 )); then
|
||||||
|
printf 'declared dependencies contract: commands used but never installed:\n' >&2
|
||||||
|
printf ' %s\n' "${missing[@]}" | sort -u >&2
|
||||||
|
fail 'add each to a list in setup/packages/, or the feature silently will not exist on a fresh machine'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'declared dependencies contract: PASS (%d scripts)\n' "$checked"
|
||||||
@@ -68,6 +68,24 @@ rg -Fq 'SystemSettings.openGnomePanel("color")' "$settings_dir/HealthPage.qml" \
|
|||||||
|| fail 'Fedora ownership boundary lost the Colour profiles handoff'
|
|| fail 'Fedora ownership boundary lost the Colour profiles handoff'
|
||||||
rg -Fq 'SystemSettings.openGnomePanel("wellbeing")' "$settings_dir/HealthPage.qml" \
|
rg -Fq 'SystemSettings.openGnomePanel("wellbeing")' "$settings_dir/HealthPage.qml" \
|
||||||
|| fail 'Fedora ownership boundary lost the Digital wellbeing handoff'
|
|| fail 'Fedora ownership boundary lost the Digital wellbeing handoff'
|
||||||
|
# Exact authored handoffs are asserted above. Also prove every panel named by
|
||||||
|
# this boundary is accepted by SystemSettings, so a typo cannot ship a dead
|
||||||
|
# button even if its copy still looks correct.
|
||||||
|
rg -Fq 'title: "Fedora system settings"' "$settings_dir/HealthPage.qml" \
|
||||||
|
|| fail 'the Fedora ownership boundary card is gone'
|
||||||
|
|
||||||
|
allowed="$(rg -o '"[a-z-]+"' "$repo_dir/config/dot/quickshell/services/SystemSettings.qml" \
|
||||||
|
| sed -n '/"\(applications\|background\|bluetooth\|color\|display\|keyboard\|mouse\|multitasking\|network\|notifications\|online-accounts\|power\|printers\|privacy\|search\|sharing\|sound\|system\|universal-access\|wacom\|wellbeing\|wifi\|wwan\)"/p' \
|
||||||
|
| tr -d '"' | sort -u)"
|
||||||
|
|
||||||
|
while read -r panel; do
|
||||||
|
[[ -n "$panel" ]] || continue
|
||||||
|
grep -qx "$panel" <<<"$allowed" \
|
||||||
|
|| fail "the Fedora card opens \"$panel\", which openGnomePanel does not allow -- that button does nothing"
|
||||||
|
done < <(rg -o 'openGnomePanel\("([a-z-]+)"' -r '$1' "$settings_dir/HealthPage.qml" | sort -u)
|
||||||
|
|
||||||
|
rg -q 'openGnomePanel\(' "$settings_dir/HealthPage.qml" \
|
||||||
|
|| fail 'the Fedora ownership boundary does not open GNOME Settings at all'
|
||||||
rg -Fq 'Health.repair(check.id, false)' "$settings_dir/HealthPage.qml" \
|
rg -Fq 'Health.repair(check.id, false)' "$settings_dir/HealthPage.qml" \
|
||||||
|| fail 'Settings repair does not stay inline/non-external'
|
|| fail 'Settings repair does not stay inline/non-external'
|
||||||
rg -Fq 'ShellState.openSettings(check.action.target)' "$settings_dir/HealthPage.qml" \
|
rg -Fq 'ShellState.openSettings(check.action.target)' "$settings_dir/HealthPage.qml" \
|
||||||
|
|||||||
@@ -25,8 +25,16 @@ trap cleanup EXIT
|
|||||||
if rg -q 'setup/scripts/link-vicinae-scripts' "$dotfile_installer"; then
|
if rg -q 'setup/scripts/link-vicinae-scripts' "$dotfile_installer"; then
|
||||||
fail 'link-dotfiles also invokes the command installer'
|
fail 'link-dotfiles also invokes the command installer'
|
||||||
fi
|
fi
|
||||||
rg -Fq 'do "$script"; done' "$top_level_installer" \
|
# Each setup stage must run in its OWN process, so strict-shell options and
|
||||||
|| fail 'top-level installer sources setup scripts into one shared shell'
|
# helper variables stay local to the script that owns them. What matters is
|
||||||
|
# that the stages are executed rather than sourced -- this previously matched
|
||||||
|
# the literal one-liner `do "$script"; done`, which failed the moment the loop
|
||||||
|
# gained error reporting and spanned more than one line, despite the property
|
||||||
|
# it cares about being unchanged.
|
||||||
|
rg -q '(^|[^a-z-])(\.|source)\s+[^;]*setup/scripts' "$top_level_installer" \
|
||||||
|
&& fail 'top-level installer sources setup scripts into one shared shell'
|
||||||
|
rg -q '"\$script"' "$top_level_installer" \
|
||||||
|
|| fail 'top-level installer does not execute the setup scripts'
|
||||||
|
|
||||||
mkdir -p "$data_dir/scripts/panama" "$fake_bin"
|
mkdir -p "$data_dir/scripts/panama" "$fake_bin"
|
||||||
printf 'user-owned\n' >"$data_dir/scripts/panama/keep.sh"
|
printf 'user-owned\n' >"$data_dir/scripts/panama/keep.sh"
|
||||||
|
|||||||
@@ -137,10 +137,20 @@ last_error="$(qs_for_harness ipc call settings-system-test status | jq -r .lastE
|
|||||||
|
|
||||||
# ── A rejected value must be refused, not silently accepted ──────────────────
|
# ── A rejected value must be refused, not silently accepted ──────────────────
|
||||||
qs_for_harness ipc call settings-system-test apply "$target_auto_hdr" 7 "$target_direct" >/dev/null
|
qs_for_harness ipc call settings-system-test apply "$target_auto_hdr" 7 "$target_direct" >/dev/null
|
||||||
sleep 0.3
|
|
||||||
|
# The refusal is reported asynchronously, so wait for it rather than sleeping a
|
||||||
|
# fixed 0.3s and hoping. That sleep made this fail roughly one run in three,
|
||||||
|
# reporting "a rejected value did not surface an error" when the error simply
|
||||||
|
# had not arrived yet -- which reads as a missing guard rather than a slow one.
|
||||||
|
rejected=""
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
rejected="$(qs_for_harness ipc call settings-system-test status | jq -r .lastError)"
|
||||||
|
[[ -n "$rejected" ]] && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
[[ "$(read_option misc:vrr)" == "$target_vrr" ]] || fail 'an out-of-allow-list VRR value reached the compositor'
|
[[ "$(read_option misc:vrr)" == "$target_vrr" ]] || fail 'an out-of-allow-list VRR value reached the compositor'
|
||||||
[[ -n "$(qs_for_harness ipc call settings-system-test status | jq -r .lastError)" ]] \
|
[[ -n "$rejected" ]] || fail 'a rejected VRR value did not surface an error'
|
||||||
|| fail 'a rejected VRR value did not surface an error'
|
|
||||||
|
|
||||||
# ── Every getoption answer shape must be handled, not just integers ──────────
|
# ── Every getoption answer shape must be handled, not just integers ──────────
|
||||||
# The compositor reports each option in a different JSON field depending on its
|
# The compositor reports each option in a different JSON field depending on its
|
||||||
|
|||||||
Reference in New Issue
Block a user