Files
Panama/setup/scripts/link-dotfiles
T
Gabriel Brown 2d69ce7648 Make the lock screen follow the colour scheme
hyprlock.conf shipped with Tokyo Night Moon hardcoded in six places, so
choosing light mode left the lock screen dark. Every other surface had
been taught to follow the scheme this week -- kitty, GTK, the launcher,
btop, tmux, neovim -- and this was the one left, which is unfortunate,
because it is the screen a user sees most often and the worst possible
place to find a theming bug: you discover it while locked out of the
machine and cannot fix it from there.

It is now generated from a template on every scheme change, the same
shape kitty, GTK, tmux and btop already use, and seeded by link-dotfiles
so the first lock of a fresh install is themed rather than falling back
to hyprlock's bare grey default. hyprlock is launched fresh on each lock
(`pidof hyprlock || hyprlock`), so it picks the file up with no restart.

The dark output is byte-identical to the file it replaces, ignoring
comments -- verified by diff -- so nothing changes for anyone already in
dark mode.

One detail worth recording: hyprlock takes rgba(r, g, b, a) in DECIMAL,
not hex, so the template carries "R, G, B" triples where every other
theme file in this repository uses hex. Two values are the exception,
sitting inside Pango markup where hyprlock wants ##rrggbb. Getting
either wrong is not a parse error -- hyprlock ignores the value and uses
its own default, silently.

Which is why this has a contract. It generates both schemes into a
fixture, never the live config, and checks that no placeholder survives
substitution, that every colour is a well-formed decimal triple, that
the Pango values are well-formed hex, that a light lock screen is
actually light, and that the two schemes differ at all. Verified it
catches a hardcoded colour left in the template and a light mode built
from the dark palette, which is the original bug exactly.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 14:36:35 -04:00

256 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# --- Helper functions ---
log() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
# Define paths as they have not been defined by new bashrc yet!
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
PANAMA_BASH="${PANAMA_BASH:-$PANAMA_PATH/config/bash}"
PANAMA_DOT="$PANAMA_PATH/config/dot"
PANAMA_OLD="$PANAMA_PATH/config/old"
CONFIG="$HOME/.config"
# Make backup folder if it doesn't exist
mkdir -p "$PANAMA_OLD"
# --- Bashrc ---
echo -e "\n--- Replacing .bashrc ---"
# Backup existing .bashrc if it's a regular file
if [ -f "$HOME/.bashrc" ] && [ ! -L "$HOME/.bashrc" ]; then
log "Backing up existing .bashrc"
mv "$HOME/.bashrc" "$PANAMA_OLD/.bashrc"
fi
# Remove old symlink if it exists and points somewhere else
if [ -L "$HOME/.bashrc" ]; then
log "Removing old .bashrc symlink"
rm "$HOME/.bashrc"
fi
# Symlink Panama .bashrc file to ~/.bashrc
log "Symlinking Panama .bashrc file to ~/.bashrc"
ln -s "$PANAMA_BASH/.bashrc" "$HOME/.bashrc"
# Each entry is symlinked as ~/.config/<name> -> $PANAMA_DOT/<name>.
# "forge" is the GNOME tiling extension; "hypr"/"quickshell"/"wofi" are the
# Hyprland desktop. They coexist deliberately — the GNOME session keeps working
# while Hyprland is being used, so you can log back into either.
dirs=("espanso" "forge" "ghostty" "gtk-3.0" "gtk-4.0" "hypr" "kitty" "nvim" \
"quickshell" "tmux" "uwsm" "vicinae" "wofi" "xdg-desktop-portal")
# --- Vim vimrc ---
echo -e "\n--- Setting up vim ---"
mkdir -p "$HOME/.vim"
if [ -L "$HOME/.vim/vimrc" ]; then
rm "$HOME/.vim/vimrc"
log "Removed old symlink at ~/.vim/vimrc"
fi
if [ -f "$HOME/.vim/vimrc" ]; then
mv "$HOME/.vim/vimrc" "$PANAMA_OLD/vimrc"
log "Moved existing ~/.vim/vimrc to $PANAMA_OLD/vimrc"
fi
ln -s "$PANAMA_DOT/vim/vimrc" "$HOME/.vim/vimrc"
log "Linked $PANAMA_DOT/vim/vimrc → ~/.vim/vimrc"
for dir in "${dirs[@]}"; do
echo -e "\n--- Setting up $dir ---"
# Remove old symlink if it exists
if [ -L "$CONFIG/$dir" ]; then
rm "$CONFIG/$dir"
log "Removed old symlink at $CONFIG/$dir"
fi
# Backup existing directory if it exists
if [ -d "$CONFIG/$dir" ]; then
mv "$CONFIG/$dir" "$PANAMA_OLD/$dir"
log "Moved existing $dir config to $PANAMA_OLD/$dir"
fi
# Create symlink
ln -s "$PANAMA_DOT/$dir" "$CONFIG/$dir"
log "Linked $PANAMA_DOT/$dir$CONFIG/$dir"
done
# tmux.conf ends with a source-file of current-theme.conf, generated from the
# colour scheme rather than committed. Seed it so a fresh checkout starts themed
# -- the source-file is -q, so a missing file is silent, which would leave tmux
# unstyled with nothing to explain it.
TMUX_THEME="$PANAMA_DOT/tmux/current-theme.conf"
if [ -e "$TMUX_THEME" ]; then
log "Keeping existing tmux theme at $TMUX_THEME"
elif [ -d "$PANAMA_DOT/tmux/themes" ]; then
tmux_scheme="dark"
tmux_prefs="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
if [ -r "$tmux_prefs" ]; then
tmux_stored="$(jq -r '.colorScheme // "dark"' "$tmux_prefs" 2>/dev/null || echo dark)"
[ "$tmux_stored" = "light" ] && tmux_scheme="light"
fi
[ "$tmux_scheme" = "light" ] && tmux_name="tokyonight-day" || tmux_name="tokyonight-moon"
cp "$PANAMA_DOT/tmux/themes/$tmux_name.conf" "$TMUX_THEME"
log "Seeded tmux $tmux_scheme theme ($tmux_name) → $TMUX_THEME"
fi
# hyprlock.conf is generated from a template on every colour scheme change and
# is not committed. Seed it so the FIRST lock of a fresh install is themed --
# without it hyprlock falls back to its own defaults, which is a bare grey
# screen with none of this desktop's identity, and the first time anyone would
# find out is when they walked away from the machine.
HYPRLOCK_TEMPLATE="$PANAMA_DOT/hypr/hyprlock.conf.template"
HYPRLOCK_CONF="$PANAMA_DOT/hypr/hyprlock.conf"
if [ -e "$HYPRLOCK_CONF" ]; then
log "Keeping existing hyprlock config at $HYPRLOCK_CONF"
elif [ -r "$HYPRLOCK_TEMPLATE" ]; then
lock_scheme="dark"
lock_prefs="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
if [ -r "$lock_prefs" ]; then
lock_stored="$(jq -r '.colorScheme // "dark"' "$lock_prefs" 2>/dev/null || echo dark)"
[ "$lock_stored" = "light" ] && lock_scheme="light"
fi
if [ "$lock_scheme" = "light" ]; then
sed -e "s/@FG@/55, 96, 191/g" -e "s/@MUTED@/97, 114, 176/g" \
-e "s/@ACCENT@/46, 125, 233/g" -e "s/@ERROR@/245, 42, 101/g" \
-e "s/@BG@/225, 226, 231/g" -e "s/@FIELD@/208, 213, 227/g" \
-e "s/@MUTED_HEX@/6172b0/g" -e "s/@ERROR_HEX@/f52a65/g" \
"$HYPRLOCK_TEMPLATE" > "$HYPRLOCK_CONF"
else
sed -e "s/@FG@/200, 211, 245/g" -e "s/@MUTED@/130, 139, 184/g" \
-e "s/@ACCENT@/130, 170, 255/g" -e "s/@ERROR@/255, 117, 127/g" \
-e "s/@BG@/34, 36, 54/g" -e "s/@FIELD@/46, 47, 61/g" \
-e "s/@MUTED_HEX@/828bb8/g" -e "s/@ERROR_HEX@/ff757f/g" \
"$HYPRLOCK_TEMPLATE" > "$HYPRLOCK_CONF"
fi
log "Seeded hyprlock $lock_scheme theme → $HYPRLOCK_CONF"
fi
# btop reads themes from its own config directory, but OWNS btop.conf -- it
# rewrites that file on exit -- so only the theme files are exposed, per file,
# and the config itself is left to btop. panama-theme-apps edits the single
# color_theme line in place.
BTOP_THEME_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/btop/themes"
mkdir -p "$BTOP_THEME_DIR"
for btop_theme_src in "$PANAMA_DOT"/btop/themes/*.theme; do
[ -e "$btop_theme_src" ] || continue
btop_theme_dst="$BTOP_THEME_DIR/$(basename "$btop_theme_src")"
if [ -L "$btop_theme_dst" ]; then
rm "$btop_theme_dst"
fi
if [ -e "$btop_theme_dst" ]; then
log "Keeping existing btop theme at $btop_theme_dst"
else
ln -s "$btop_theme_src" "$btop_theme_dst"
log "Linked btop theme → $btop_theme_dst"
fi
done
# GTK3 has no include mechanism, so its settings.ini is generated whole from a
# template rather than layered. Without this, a fresh checkout has a template
# and no settings.ini, and GTK3 applications fall back to their built-in theme.
# panama-theme-apps rewrites both files on every scheme change after this.
for gtk_version in 3.0 4.0; do
gtk_template="$PANAMA_DOT/gtk-$gtk_version/settings.ini.template"
gtk_settings="$PANAMA_DOT/gtk-$gtk_version/settings.ini"
[ -r "$gtk_template" ] || continue
if [ -e "$gtk_settings" ]; then
log "Keeping existing GTK settings at $gtk_settings"
else
gtk_scheme="dark"
gtk_prefs="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
if [ -r "$gtk_prefs" ]; then
gtk_stored="$(jq -r '.colorScheme // "dark"' "$gtk_prefs" 2>/dev/null || echo dark)"
[ "$gtk_stored" = "light" ] && gtk_scheme="light"
fi
if [ "$gtk_scheme" = "light" ]; then
gtk_name="adw-gtk3"; gtk_dark=0
else
gtk_name="adw-gtk3-dark"; gtk_dark=1
fi
sed -e "s/@GTK_THEME@/$gtk_name/" -e "s/@PREFER_DARK@/$gtk_dark/" \
"$gtk_template" > "$gtk_settings"
log "Generated GTK $gtk_version settings ($gtk_name) → $gtk_settings"
fi
done
# kitty.conf ends with `include current-theme.conf`, and that file is generated
# from the desktop colour scheme rather than committed -- it is machine state.
# A fresh checkout therefore has no such file, and kitty starts by complaining
# about a missing include and falling back to its stock colours. Seed it from
# the scheme in settings.json (dark unless the user has chosen otherwise) so a
# first launch is themed; ColorScheme.qml overwrites it on every change after.
KITTY_THEME="$PANAMA_DOT/kitty/current-theme.conf"
if [ -e "$KITTY_THEME" ]; then
log "Keeping existing kitty theme at $KITTY_THEME"
else
scheme="dark"
settings="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
if [ -r "$settings" ]; then
stored="$(jq -r '.colorScheme // "dark"' "$settings" 2>/dev/null || echo dark)"
[ "$stored" = "light" ] && scheme="light"
fi
[ "$scheme" = "light" ] && theme="tokyonight-day" || theme="tokyonight-moon"
cp "$PANAMA_DOT/kitty/themes/$theme.conf" "$KITTY_THEME"
log "Seeded kitty $scheme theme ($theme) → $KITTY_THEME"
fi
# Vicinae 0.26 discovers user themes from its XDG data directory rather than
# from ~/.config/vicinae. Keep the authored theme in Panama with the rest of
# the launcher config and expose only that file at Vicinae's runtime path.
VICINAE_THEME_DIR="$HOME/.local/share/vicinae/themes"
mkdir -p "$VICINAE_THEME_DIR"
# Every authored theme, not just the dark one: vicinae.json selects a theme per
# system appearance, so linking only Moon left the launcher falling back to its
# stock palette whenever Panama was in light mode.
for theme_src in "$PANAMA_DOT"/vicinae/themes/*.toml; do
[ -e "$theme_src" ] || continue
theme_dst="$VICINAE_THEME_DIR/$(basename "$theme_src")"
if [ -L "$theme_dst" ]; then
rm "$theme_dst"
fi
if [ -e "$theme_dst" ]; then
log "Keeping existing Vicinae theme at $theme_dst"
else
ln -s "$theme_src" "$theme_dst"
log "Linked Vicinae theme → $theme_dst"
fi
done
# Panama-native applications live in the user data directory so launchers can
# discover them alongside system desktop entries. Keep each authored file in
# the repository and expose it with a narrow per-file symlink.
# Panama ships its own application icons. Without these the desktop entries fall
# back to a generic symbolic glyph, which is drawn for 16px toolbar use and
# looks wrong beside full-colour application icons in the dock.
PANAMA_ICON_DIR="$PANAMA_PATH/config/local/share/icons/hicolor/scalable/apps"
USER_ICON_DIR="$HOME/.local/share/icons/hicolor/scalable/apps"
mkdir -p "$USER_ICON_DIR"
for icon_file in "$PANAMA_ICON_DIR"/*.svg; do
[[ -e "$icon_file" ]] || continue
icon_name="$(basename "$icon_file")"
icon_target="$USER_ICON_DIR/$icon_name"
if [[ -L "$icon_target" ]]; then
rm "$icon_target"
elif [[ -e "$icon_target" ]]; then
mv "$icon_target" "$icon_target.bak"
fi
ln -s "$icon_file" "$icon_target"
done
gtk-update-icon-cache -f -t "$HOME/.local/share/icons/hicolor" 2>/dev/null || true
PANAMA_APPLICATION_DIR="$PANAMA_PATH/config/local/share/applications"
USER_APPLICATION_DIR="$HOME/.local/share/applications"
mkdir -p "$USER_APPLICATION_DIR"
for desktop_file in "$PANAMA_APPLICATION_DIR"/*.desktop; do
[[ -e "$desktop_file" ]] || continue
desktop_name="$(basename "$desktop_file")"
desktop_target="$USER_APPLICATION_DIR/$desktop_name"
if [[ -L "$desktop_target" ]]; then
rm "$desktop_target"
elif [[ -e "$desktop_target" ]]; then
log "Keeping existing desktop entry at $desktop_target"
continue
fi
ln -s "$desktop_file" "$desktop_target"
log "Linked $desktop_name$desktop_target"
done