Repository navigation
feat: track portable shell and git customizations #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
a9501c0
fix: prevent duplicate wallpaper listeners
SamuelIVX 11d9407
fix: make rofi pickers handle selections correctly
SamuelIVX e09766d
fix: make workstation tuning conservative and verifiable
SamuelIVX 9197750
feat: track portable shell and git customizations
SamuelIVX bcee2be
fix: reserve clipboard deletion keys for picker actions
SamuelIVX 51df1b6
Merge clipboard keybinding correction into conservative tuning
SamuelIVX 638d2dd
Merge clipboard keybinding correction into portable configuration
SamuelIVX d6583d2
fix: report wallpaper workspace query failures
SamuelIVX 6748871
Merge wallpaper error handling correction into fix/rofi-pickers
SamuelIVX 663dd9e
Merge wallpaper error handling correction into fix/conservative-tuning
SamuelIVX d118258
Merge wallpaper error handling correction into feat/portable-shell-git
SamuelIVX e607f7a
docs: establish verified workstation performance baselines (#13)
SamuelIVX 6657a70
fix: make rofi pickers handle selections correctly (#10)
SamuelIVX c3a153e
Merge branch 'fix/wallpaper-listener' into feat/portable-shell-git
SamuelIVX d639ccd
Merge branch 'main' into feat/portable-shell-git
SamuelIVX b5912ae
fix: address workstation setup review findings
SamuelIVX File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Workstation performance baseline | ||
|
|
||
| ## TL;DR | ||
|
|
||
| Preserve the current visuals and target balanced responsiveness. Configuration is | ||
| not a benchmark. The measurements below were captured in a restricted agent | ||
| sandbox, not a complete Hyprland session; no desktop performance gain is claimed. | ||
|
|
||
| ## Recorded evidence | ||
|
|
||
| | Check | Observation | Limit | | ||
| |---|---|---| | ||
| | OS | Fedora 43 KDE edition | Read from host os-release | | ||
| | Memory/storage | About 30 GiB RAM, 8 GiB zram, Btrfs on NVMe | Snapshot, not a pressure test | | ||
| | Shell with current startup | 20 samples; median 39.32 ms; range 38.53–41.25 ms | Non-login interactive Bash, no PTY, output discarded | | ||
| | Shell without Fastfetch | 20 samples; median 33.95 ms; range 32.84–35.54 ms | Temporary rc file; original startup untouched | | ||
| | Fastfetch difference | 5.37 ms between medians | Sandbox observation only; retain current behavior | | ||
| | Codec package | mesa-va-drivers-freeworld installed; mesa-va-drivers absent | Package inventory does not prove application decoding | | ||
| | Power configuration | `/etc/tuned/active_profile`: balanced; EPP: balance_performance | Live TuneD activation could not be queried | | ||
| | Monitor/compositor | Unverified | Hyprland socket access denied | | ||
| | Boot/failed services | Unverified | System and user bus access denied | | ||
| | Desktop idle CPU/power | Unverified | Process visibility excludes the live desktop | | ||
|
|
||
| The historical samples above collected each variant as a separate batch. Cache | ||
| warm-up or changing host load can bias that difference. The repeat procedure | ||
| below interleaves variants and alternates first position within each pair; the | ||
| historical numbers have not been remeasured with this procedure. | ||
|
|
||
| ## Repeat shell startup measurement | ||
|
|
||
| Run from your normal host terminal. This measures startup without terminal | ||
| rendering, so also compare perceived opening time in Kitty. The temporary variant | ||
| intercepts the direct Fastfetch startup invocation; it never edits dotfiles. | ||
|
|
||
| ```python | ||
| import pathlib, statistics, subprocess, tempfile, time | ||
|
|
||
| original = pathlib.Path.home().joinpath('.bashrc').read_text() | ||
| with tempfile.TemporaryDirectory() as directory: | ||
| variants = ('current', 'without_fastfetch') | ||
| samples = {variant: [] for variant in variants} | ||
| configs = {} | ||
| for variant in variants: | ||
| rc = pathlib.Path(directory) / variant | ||
| # Intercept only direct fastfetch calls in this disposable child shell. | ||
| # The tracked fragment and existing rc both use direct calls. | ||
| rc.write_text(('fastfetch() { :; }\n' if variant == 'without_fastfetch' else '') + original) | ||
| configs[variant] = rc | ||
| for round_number in range(20): | ||
| order = variants if round_number % 2 == 0 else variants[::-1] | ||
| for variant in order: | ||
| rc = configs[variant] | ||
| start = time.perf_counter() | ||
| subprocess.run(['bash', '--noprofile', '--rcfile', str(rc), '-ic', 'exit'], | ||
| stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) | ||
| samples[variant].append((time.perf_counter() - start) * 1000) | ||
| for variant, values in samples.items(): | ||
| assert len(values) == 20 | ||
| print(variant, 'median/range ms:', statistics.median(values), min(values), max(values)) | ||
| ``` | ||
|
|
||
| This temporary function does not intercept an explicit `command fastfetch` or | ||
| absolute executable path; inspect customized startup files before using it. | ||
| Retain Fastfetch unless repeated host measurements or your preference justify | ||
| moving it to an on-demand command. | ||
|
|
||
| ## Complete the host baseline | ||
|
|
||
| - Capture `systemd-analyze`, `systemd-analyze critical-chain`, and failed system/user | ||
| units across three comparable cold boots. Report medians and ranges, not a single | ||
| longest service: services can start in parallel. | ||
| - Run `hyprctl monitors`, `hyprctl configerrors`, `tuned-adm active`, and | ||
| `tuned-adm verify`. Confirm actual resolution/refresh and power-profile activation. | ||
| - Count `wallpaper.sh` listener processes after login and after ten `SUPER+A` | ||
| refreshes. There should be one listener. | ||
| - With the same apps open and no builds/downloads, capture three sixty-second | ||
| idle samples using `pidstat -u -r 1 60` if installed. Record compositor, bar, | ||
| wallpaper, CPU activity, and available hardware power readings separately. | ||
| - Inspect `vainfo` and the browser's media diagnostics during the same video test. | ||
| Confirm the codec, rendering/decoding device, CPU use, and dropped frames. | ||
|
|
||
| ## Change policy | ||
|
|
||
| Keep blur, transparency, animations, VRR, Kitty delays, and wallpaper transition | ||
| rate unchanged in this series. Test one proposed tuning change at a time with the | ||
| same workload. Adopt it only if improvement is repeatable beyond baseline variation, | ||
| and record any visual, latency, or idle-power tradeoff plus rollback. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Portable diff presentation; identity, credentials, and signing remain local. | ||
| [core] | ||
| pager = delta | ||
| [interactive] | ||
| diffFilter = delta --color-only | ||
| [delta] | ||
| navigate = true | ||
| light = false | ||
| side-by-side = false | ||
| line-numbers = true | ||
| syntax-theme = "Catppuccin Mocha" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,33 @@ | ||
| #!/usr/bin/env bash | ||
| # Link tracked configs and portable fragments, preserving local settings in backups. | ||
| set -euo pipefail | ||
| for tool in python3 git; do | ||
| command -v "$tool" >/dev/null || { echo "Missing required tool: $tool" >&2; exit 1; } | ||
| done | ||
| DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" | ||
|
|
||
| DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
| CONFIG_SOURCE="$DOTFILES_DIR/.config" | ||
| TARGET_DIR="$HOME/.config" | ||
|
|
||
| echo "[+] Installing dotfiles symlinks into $TARGET_DIR..." | ||
| mkdir -p "$TARGET_DIR" | ||
|
|
||
| for item in "$CONFIG_SOURCE"/*; do | ||
| base="$(basename "$item")" | ||
| target="$TARGET_DIR/$base" | ||
|
|
||
| if [ -L "$target" ] && [ "$(readlink -f "$target")" = "$item" ]; then | ||
| echo " [=] $base is already linked." | ||
| continue | ||
| # Preserve conflicts, including dangling links, before installing one source. | ||
| link_item() { | ||
| local source=$1 target=$2 backup | ||
| mkdir -p "$(dirname "$target")" | ||
| if [[ -L $target && $(readlink -f "$target") == "$source" ]]; then | ||
|
SamuelIVX marked this conversation as resolved.
|
||
| echo "Already linked: $target" | ||
| return | ||
| fi | ||
|
|
||
| if [ -e "$target" ]; then | ||
| backup="${target}.backup-$(date +%s)" | ||
| echo " [!] Backing up existing $base to $backup" | ||
| mv "$target" "$backup" | ||
| if [[ -e $target || -L $target ]]; then | ||
| backup=$(mktemp -d "${target}.backup-XXXXXX") | ||
| mv -- "$target" "$backup/original" | ||
| echo "Backup: $backup/original" | ||
| fi | ||
| ln -s -- "$source" "$target" | ||
| echo "Linked: $target" | ||
| } | ||
|
|
||
| ln -s "$item" "$target" | ||
| echo " [+] Linked $base -> $item" | ||
| # Migrate before linking so unsupported shell ownership fails without changing configs. | ||
| python3 "$DOTFILES_DIR/scripts/migrate-shell.py" | ||
| for item in "$DOTFILES_DIR/.config/"*; do | ||
| link_item "$item" "$HOME/.config/$(basename "$item")" | ||
| done | ||
|
|
||
| echo "[✓] All dotfiles successfully linked!" | ||
| link_item "$DOTFILES_DIR/shell/dotfiles.sh" "$HOME/.bashrc.d/dotfiles.sh" | ||
| link_item "$DOTFILES_DIR/git/developer.gitconfig" "$HOME/.config/git/developer.gitconfig" | ||
| echo 'Configuration installed. Open a new interactive shell to use the fragment.' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| #!/usr/bin/env python3 | ||
| """Migrate only the recognized inline Bash block, preserving other local content.""" | ||
| from pathlib import Path | ||
| import shutil | ||
| import subprocess | ||
| import os | ||
| import tempfile | ||
|
|
||
| LEGACY = '''alias syu="sudo dnf upgrade" | ||
|
|
||
| # Modern developer aliases | ||
| if command -v eza >/dev/null 2>&1; then | ||
| alias ls="eza --icons --group-directories-first" | ||
| alias ll="eza -la --icons --group-directories-first" | ||
| alias tree="eza --tree --icons" | ||
| fi | ||
|
|
||
| if command -v bat >/dev/null 2>&1; then | ||
| alias cat="bat --paging=never" | ||
| fi | ||
|
|
||
| if command -v duf >/dev/null 2>&1; then | ||
| alias df="duf" | ||
| fi | ||
|
|
||
| # Zoxide smart jumping (replaces cd) | ||
| command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init bash)" | ||
|
|
||
| # FZF keybindings & fuzzy completion | ||
| command -v fzf >/dev/null 2>&1 && eval "$(fzf --bash)" | ||
|
|
||
| eval "$(starship init bash)" | ||
| fastfetch | ||
| ''' | ||
| DIRECTORY_LOADER = '''if [ -d ~/.bashrc.d ]; then | ||
| for rc in ~/.bashrc.d/*; do | ||
| if [ -f "$rc" ]; then | ||
| . "$rc" | ||
| fi | ||
| done | ||
| fi''' | ||
| HOOK = '''# Dotfiles portable developer configuration | ||
| if [ -r "$HOME/.bashrc.d/dotfiles.sh" ]; then | ||
| . "$HOME/.bashrc.d/dotfiles.sh" | ||
| fi | ||
| ''' | ||
|
|
||
|
|
||
| def migrate(path: Path) -> None: | ||
| """Back up and migrate an exact known block; never remove edited local code. | ||
|
|
||
| Args: | ||
| path: Local Bash startup file. | ||
| Raises: | ||
| ValueError: The startup file is a symlink requiring manual integration. | ||
| OSError: Backup or update fails. | ||
| """ | ||
| if path.is_symlink(): | ||
| raise ValueError('Symlinked .bashrc: integrate shell/dotfiles.sh manually') | ||
| original = path.read_text() if path.exists() else '' | ||
| updated = original.replace(LEGACY, '') | ||
| # shortcut: loader detection matches raw text; manually source the fragment when a loader is unreachable. | ||
| if DIRECTORY_LOADER not in updated and HOOK not in updated: | ||
| updated = updated.rstrip('\n') + '\n\n' + HOOK | ||
|
SamuelIVX marked this conversation as resolved.
|
||
| if updated == original: | ||
| return | ||
| if path.exists(): | ||
| with tempfile.NamedTemporaryFile(prefix=path.name + '.backup-', dir=path.parent, delete=False) as backup: | ||
| shutil.copy2(path, backup.name) | ||
| print(f'Bash backup: {backup.name}') | ||
| mode = path.stat().st_mode & 0o7777 if path.exists() else 0o600 | ||
| temporary_name = None | ||
| try: | ||
| with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix=path.name + '.tmp-', | ||
| dir=path.parent, delete=False) as temporary: | ||
| temporary_name = temporary.name | ||
| temporary.write(updated) | ||
| temporary.flush() | ||
| os.fchmod(temporary.fileno(), mode) | ||
| os.fsync(temporary.fileno()) | ||
| os.replace(temporary_name, path) | ||
| finally: | ||
| if temporary_name is not None: | ||
| Path(temporary_name).unlink(missing_ok=True) | ||
|
|
||
|
|
||
| def migrate_git(path: Path, include: Path) -> None: | ||
| """Prepend a portable include and remove only matching legacy Git values. | ||
|
|
||
| Local identity and differing overrides stay after the include and take priority. | ||
| Raises ValueError for symlinked files and OSError or CalledProcessError on failure. | ||
| """ | ||
| if path.is_symlink(): | ||
| raise ValueError('Symlinked .gitconfig: integrate git/developer.gitconfig manually') | ||
| original = path.read_text() if path.exists() else '' | ||
| settings = { | ||
| 'core.pager': 'delta', 'interactive.diffFilter': 'delta --color-only', | ||
| 'delta.navigate': 'true', 'delta.light': 'false', 'delta.side-by-side': 'false', | ||
| 'delta.line-numbers': 'true', 'delta.syntax-theme': 'Catppuccin Mocha', | ||
| } | ||
| with tempfile.TemporaryDirectory(dir=path.parent) as directory: | ||
| draft = Path(directory) / 'gitconfig' | ||
| draft.write_text(original) | ||
| for key, value in settings.items(): | ||
| result = subprocess.run(['git', 'config', '--file', str(draft), '--fixed-value', | ||
| '--unset-all', key, value], capture_output=True, text=True, check=False) | ||
| if result.returncode not in (0, 5): | ||
| raise RuntimeError(f'Cannot migrate Git setting {key}: {result.stderr}') | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| # Let Git quote the path instead of implementing its configuration grammar. | ||
| prefix = Path(directory) / 'include' | ||
| subprocess.run(['git', 'config', '--file', str(prefix), '--add', 'include.path', str(include)], check=True) | ||
| existing = subprocess.run(['git', 'config', '--file', str(draft), '--get-all', 'include.path'], | ||
| capture_output=True, text=True, check=False) | ||
| if existing.returncode not in (0, 1): | ||
| raise RuntimeError('Cannot read Git include settings') | ||
| updated = draft.read_text() | ||
| if str(include) not in existing.stdout.splitlines(): | ||
| updated = prefix.read_text() + updated | ||
| if updated == original: | ||
| return | ||
| if path.exists(): | ||
| with tempfile.NamedTemporaryFile(prefix=path.name + '.backup-', dir=path.parent, delete=False) as backup: | ||
| shutil.copy2(path, backup.name) | ||
| print(f'Git backup: {backup.name}') | ||
| draft.chmod(path.stat().st_mode & 0o777) | ||
| else: | ||
| draft.chmod(0o600) | ||
| draft.write_text(updated) | ||
| os.replace(draft, path) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| home = Path.home() | ||
| migrate(home / '.bashrc') | ||
| migrate_git(home / '.gitconfig', home / '.config/git/developer.gitconfig') | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| #!/usr/bin/env bash | ||
| # Portable interactive developer aliases, navigation, completion, and prompt. | ||
| [[ $- == *i* ]] || return 0 | ||
| alias syu='sudo dnf upgrade' | ||
| if command -v eza >/dev/null 2>&1; then | ||
| alias ls='eza --icons --group-directories-first' | ||
| alias ll='eza -la --icons --group-directories-first' | ||
| alias tree='eza --tree --icons' | ||
| fi | ||
| command -v bat >/dev/null 2>&1 && alias cat='bat --paging=never' | ||
| command -v duf >/dev/null 2>&1 && alias df=duf | ||
| if command -v zoxide >/dev/null 2>&1; then eval "$(zoxide init bash)"; fi | ||
| if command -v fzf >/dev/null 2>&1; then eval "$(fzf --bash)"; fi | ||
| if command -v starship >/dev/null 2>&1; then eval "$(starship init bash)"; fi | ||
| if command -v fastfetch >/dev/null 2>&1; then fastfetch; fi |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.