Fix the install pipeline and an idle-lock startup race

The initial package list was quoted into a single bogus dnf argument
and every dnf error was discarded, so a fresh install silently skipped
most of it. Two package lists were never wired into the pipeline at
all, and change-settings ran before install-packages, so the vicinae
theme step was permanently skipped. Fixed the ordering, the quoting,
and stopped swallowing errors.

Separately, hypridle could start with its WAYLAND_DISPLAY condition
unmet if it raced the env-publish call, silently never starting --
and it's the only listener for the logind Lock signal. Made the start
wait on the environment synchronously. panama-idle also wrote its
generated config to a fixed temp path with no locking, so concurrent
applies could interleave into a corrupt file; switched to mktemp plus
an atomic mv.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
This commit is contained in:
Gabriel Brown
2026-08-18 21:22:52 -04:00
parent c37ca3baee
commit 70d8d32ee2
8 changed files with 91 additions and 20 deletions
+10 -2
View File
@@ -23,8 +23,16 @@ hl.on("hyprland.start", function()
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=Hyprland")
hl.exec_cmd("systemctl --user start hyprland-session.target")
-- Units: polkit prompts, wallpaper, launcher daemon, idle/lock.
hl.exec_cmd("systemctl --user start hyprpolkitagent.service hyprpaper.service vicinae.service hypridle.service")
-- Units: polkit prompts, wallpaper, launcher daemon, idle/lock. All four
-- carry `ConditionEnvironment=WAYLAND_DISPLAY`, and hl.exec_cmd fires
-- commands without waiting for them to finish, so the dbus-update call
-- above racing this one is not safe to assume complete -- a lost race
-- leaves the Condition unmet and the unit silently never starts (exit 0,
-- no error). hypridle is the only listener for the logind Lock signal,
-- so that failure mode is "lock-session goes to nobody". Re-import
-- synchronously in the same shell invocation first so the Condition
-- always sees it, regardless of how the dbus-update call above scheduled.
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP && systemctl --user start hyprpolkitagent.service hyprpaper.service vicinae.service hypridle.service")
-- The shell: bar, dock, overview, quick settings, notifications, capture.
-- No systemd unit ships with quickshell, so it runs as a compositor child.
+15 -2
View File
@@ -25,6 +25,14 @@ generated="$state_dir/hypridle.conf"
dropin_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/hypridle.service.d"
dropin="$dropin_dir/panama.conf"
# Set by generate() to the mktemp path it is currently writing, so concurrent
# invocations (e.g. rapid settings changes each spawning `apply`) never share
# a tmp file and interleave writes into a corrupt hypridle.conf. Cleared once
# the atomic mv below lands, so this is a no-op on a normal exit.
generated_tmp=""
cleanup() { rm -f "$generated_tmp" 2>/dev/null || true; }
trap cleanup EXIT
read_setting() {
local key="$1" fallback="$2"
[[ -r "$settings" ]] || { printf '%s' "$fallback"; return; }
@@ -53,6 +61,10 @@ generate() {
load
mkdir -p "$state_dir"
# Unique per invocation, in the same directory as the destination so the
# final mv is an atomic same-filesystem rename rather than a copy.
generated_tmp="$(mktemp "$generated.XXXXXX")"
{
printf '# Generated by panama-idle from %s\n' "$settings"
printf '# Do not edit: it is rewritten whenever the idle settings change.\n'
@@ -91,9 +103,10 @@ generate() {
printf ' on-timeout = systemctl suspend\n'
printf '}\n'
fi
} >"$generated.tmp"
} >"$generated_tmp"
mv "$generated.tmp" "$generated"
mv "$generated_tmp" "$generated"
generated_tmp=""
}
install_dropin() {
+8 -2
View File
@@ -49,10 +49,16 @@ gsettings set org.gnome.desktop.session idle-delay 0 2>/dev/null || true
# 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.
#
# Explicit order, not glob order: change-settings runs `vicinae theme set`,
# which needs both vicinae itself (installed by install-packages) and the
# theme files it selects among (symlinked into place by link-dotfiles). New
# scripts must be added here explicitly, or they will not run at all.
STAGES=(install-packages link-dotfiles change-settings link-vicinae-scripts)
failed=()
for script in "$PANAMA_PATH"/setup/scripts/*; do
for stage in "${STAGES[@]}"; do
script="$PANAMA_PATH/setup/scripts/$stage"
[[ -x "$script" ]] || continue
stage="$(basename "$script")"
printf '\n=== %s ===\n' "$stage"
if ! "$script"; then
failed+=("$stage")
+1
View File
@@ -32,6 +32,7 @@ mokutil
mozilla-openh264
nautilus-extensions
nautilus-python
nextcloud-client
openssl-devel
opus-devel
python3-dnf-plugin-versionlock
+2
View File
@@ -2,3 +2,5 @@ org.gtk.Gtk3theme.adw-gtk3-dark
io.github.Foldex.AdwSteamGtk
com.bitwarden.desktop
app.bluebubbles.BlueBubbles
org.mozilla.thunderbird_esr
io.missioncenter.MissionCenter
+1
View File
@@ -8,6 +8,7 @@ gpu-screen-recorder
grim
grimblast
gtk-update-icon-cache
helium-browser-bin
hypridle
hyprland
hyprland-guiutils
+48 -14
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# --- Helper functions ---
log() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
exists() { command -v "$1" >/dev/null 2>&1; }
@@ -10,33 +12,37 @@ LOCAL_BIN_PATH="$HOME/.local/bin"
echo -e "\n--- Installing Repositories ---"
log "Installing RPM Fusion Free and Nonfree Repositories"
sudo dnf install -y https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm > /dev/null 2>&1
sudo dnf install -y https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm > /dev/null
log "Enabling Fedora Cisco OpenH264 Repository"
sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1
log "Installing RPM Fusion AppStream Metadata"
sudo dnf update @core -y > /dev/null 2>&1
sudo dnf install -y rpmfusion-\*-appstream-data > /dev/null 2>&1
sudo dnf update @core -y > /dev/null
sudo dnf install -y rpmfusion-\*-appstream-data > /dev/null
log "Installing Terra Repository"
sudo dnf install -y --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release > /dev/null 2>&1
sudo dnf install -y --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release > /dev/null
echo -e "\n--- Installing relevant packages ---"
log "Updating all packages. This may take a while"
sudo dnf update -y --refresh > /dev/null 2>&1
sudo dnf update -y --refresh > /dev/null
log "Updating core, multimedia, and sound-and-video groups"
# A trailing `&& sync` here previously meant a failing groupupdate was exempt
# from set -e (bash does not apply -e to the left side of a && list), so the
# failure went unreported. Sync unconditionally on its own line instead.
sudo dnf4 groupupdate -y 'core' 'multimedia' 'sound-and-video' \
--setop='install_weak_deps=False' \
--exclude='PackageKit-gstreamer-plugin' \
--allowerasing && sync > /dev/null 2>&1
--allowerasing > /dev/null
sync
log "Swapping ffmpeg-free for ffmpeg"
sudo dnf swap -y 'ffmpeg-free' 'ffmpeg' --allowerasing > /dev/null 2>&1
sudo dnf swap -y 'ffmpeg-free' 'ffmpeg' --allowerasing > /dev/null
log "Swapping mesa-va-drivers for mesa-va-drivers-freeworld"
sudo dnf swap -y mesa-va-drivers mesa-va-drivers-freeworld > /dev/null 2>&1
sudo dnf swap -y mesa-va-drivers mesa-va-drivers-freeworld > /dev/null
log "Upgrading Multimedia group with optional packages"
sudo dnf4 group upgrade -y --with-optional Multimedia > /dev/null 2>&1
sudo dnf4 group upgrade -y --with-optional Multimedia > /dev/null
log "Installing GStreamer plugins (bad, good, base)"
sudo dnf install -y gstreamer1-plugins-{bad-\*,good-\*,base} \
--exclude=gstreamer1-plugins-bad-free-devel > /dev/null 2>&1
--exclude=gstreamer1-plugins-bad-free-devel > /dev/null
# --- Install all initial packages ---
PACKAGES_FILE="$PANAMA_PATH/setup/packages/initial-packages"
@@ -45,12 +51,25 @@ if [[ -f "$PACKAGES_FILE" ]]; then
log "Installing Initial Packages"
echo -e "Includes the following packages:"
echo -e "$(<"$PACKAGES_FILE")"
sudo dnf install -y "$INITIAL_PACKAGES" > /dev/null 2>&1
sudo dnf install -y $INITIAL_PACKAGES > /dev/null
log "Initial packages installed!"
else
log "Package list was not in specified path: $PACKAGES_FILE"
fi
# --- Install Desktop Packages ---
DESKTOP_FILE="$PANAMA_PATH/setup/packages/desktop-packages"
if [[ -f "$DESKTOP_FILE" ]]; then
DESKTOP_PACKAGES=$(tr "\n" " " <"$DESKTOP_FILE")
log "Installing Desktop Packages"
echo -e "Includes the following packages:"
echo -e "$(<"$DESKTOP_FILE")"
sudo dnf install -y $DESKTOP_PACKAGES > /dev/null
log "Desktop packages installed!"
else
log "Package list was not in specified path: $DESKTOP_FILE"
fi
# --- Install oh-my-posh if not already installed. ---
mkdir -p "$LOCAL_BIN_PATH"
if [[ -x "$LOCAL_BIN_PATH/oh-my-posh" ]]; then
@@ -67,7 +86,7 @@ if [[ -f "$DEV_FILE" ]]; then
log "Installing Development Packages. Mostly for Neovim."
echo -e "Includes the following packages:"
echo -e "$(<"$DEV_FILE")"
sudo dnf install -y $DEV_PACKAGES > /dev/null 2>&1
sudo dnf install -y $DEV_PACKAGES > /dev/null
log "Development packages installed!"
else
log "Package list was not in specified path: $DEV_FILE"
@@ -78,12 +97,12 @@ fi
HYPR_FILE="$PANAMA_PATH/setup/packages/hyprland-packages"
if [[ -f "$HYPR_FILE" ]]; then
log "Enabling Hyprland COPR"
sudo dnf copr enable -y lionheartp/Hyprland > /dev/null 2>&1
sudo dnf copr enable -y lionheartp/Hyprland > /dev/null
HYPR_PACKAGES=$(tr "\n" " " <"$HYPR_FILE")
log "Installing Hyprland desktop packages"
echo -e "Includes the following packages:"
echo -e "$(<"$HYPR_FILE")"
sudo dnf install -y --setopt=install_weak_deps=False $HYPR_PACKAGES > /dev/null 2>&1
sudo dnf install -y --setopt=install_weak_deps=False $HYPR_PACKAGES > /dev/null
log "Hyprland packages installed!"
else
log "Package list was not in specified path: $HYPR_FILE"
@@ -96,3 +115,18 @@ else
log "Installing Bun via curl..."
curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1
fi
# --- Install Flatpak Packages ---
FLATPAK_FILE="$PANAMA_PATH/setup/packages/flatpak-packages"
if [[ -f "$FLATPAK_FILE" ]]; then
FLATPAK_PACKAGES=$(tr "\n" " " <"$FLATPAK_FILE")
log "Adding Flathub remote"
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo > /dev/null
log "Installing Flatpak Packages"
echo -e "Includes the following packages:"
echo -e "$(<"$FLATPAK_FILE")"
sudo flatpak install -y flathub $FLATPAK_PACKAGES > /dev/null
log "Flatpak packages installed!"
else
log "Package list was not in specified path: $FLATPAK_FILE"
fi
+6
View File
@@ -63,6 +63,12 @@ for dir in "${dirs[@]}"; do
if [ -d "$CONFIG/$dir" ]; then
mv "$CONFIG/$dir" "$PANAMA_OLD/$dir"
log "Moved existing $dir config to $PANAMA_OLD/$dir"
# A regular file at this path (not a directory, not a symlink) is missed by
# the guards above, so `ln -s` below would fail with "File exists" -- back
# it up the same way.
elif [ -f "$CONFIG/$dir" ]; then
mv "$CONFIG/$dir" "$PANAMA_OLD/$dir"
log "Moved existing $dir file to $PANAMA_OLD/$dir"
fi
# Create symlink