Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ Personal dotfiles: a shell/terminal/dev foundation plus a bspwm-based X11
desktop, installed via Dotbot from the repo root. The desktop half is
optional: `--no-desktop` on `./install` and `./provision.sh` skips it.

Do not confuse `claude/CLAUDE.md` (the file this repo symlinks to
`~/.claude/CLAUDE.md` on install — guidance for using Claude Code itself)
Do not confuse `agents-config/user/AGENTS.md` (the file this repo symlinks to
`~/.claude/CLAUDE.md` on install — user-level agent guidance)
with this file (guidance for editing this repo).

## Layout

- `bash/`, `git/`, `tmux/`, `kitty/`, `starship/`, `claude/` — the shell/
terminal/dev foundation. Each is independent of the others, so they get
their own top-level directory rather than nesting under one umbrella.
- `bash/`, `git/`, `tmux/`, `kitty/`, `starship/` — the shell/terminal/dev
foundation. Each is independent of the others, so they get their own
top-level directory rather than nesting under one umbrella.
- `agents-config/` — harness-agnostic agent instructions and content.
- `claude/` — Claude-specific integration such as the user status-line script.
- `desktop-environment/` — the bspwm + sxhkd + polybar X11 stack, plus
picom and dunst. These stay grouped under one directory because they're
developed and tested together (sxhkd's hotkeys and polybar's toggle
Expand Down Expand Up @@ -97,10 +99,11 @@ Use scoped commits (`<scope>: <description>`, e.g. `kitty: ...`, `bspwm:

## Machine-local overlay seams

`bash/bashrc`, `bash/bash_aliases`, `git/gitconfig`, and `claude/CLAUDE.md`
each reference an untracked `~/*.local` sidecar behind an existence guard,
so a private overlay repo can inject machine-specific config without
modifying these files. Never add a `*.local` file to this repo; it stays
public and self-contained. The same split applies to config that isn't a
sidecar: work-machine bspwm profiles live in the overlay, not in
`bash/bashrc`, `bash/bash_aliases`, and `git/gitconfig` each reference an
untracked `~/*.local` sidecar behind an existence guard, so a private overlay
repo can inject machine-specific config without modifying these files. Never
add a `*.local` file to this repo; it stays public and self-contained. The same
split applies to config that isn't a sidecar: private work-machine bspwm
profiles live in the overlay, while intentionally public hardware-specific
profiles may live in
`desktop-environment/bspwm/profiles/`.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ each gets its own top-level directory:
nord-tmux, tmux-sensible)
- `kitty/` — config, themes, `launch.sh`, `zenmode.py`
- `starship/` — `starship.toml` (prompt theming)
- `claude/` — files this repo deploys to `~/.claude/` (`CLAUDE.md`,
`status-line.sh`)
- `agents-config/` — harness-agnostic agent instructions and content
deployed through Claude-compatible paths
- `claude/` — Claude-specific integration, including the status-line script

Desktop:

Expand Down Expand Up @@ -93,7 +94,6 @@ forking this repo:
env vars)
- `bash/bash_aliases` sources `~/.bash_aliases.local` if it exists (e.g.
work-specific shell functions)
- `claude/CLAUDE.md` imports `~/.claude/CLAUDE.local.md` if it exists
- `git/gitconfig` includes `~/.gitconfig.local` if it exists (identity,
credential helpers)

Expand Down
2 changes: 0 additions & 2 deletions claude/CLAUDE.md → agents-config/user/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,3 @@ When writing text that will be reviewed by other humans (MR/PR descriptions, cod
- `claude -p` refuses to run inside a Claude Code session (detects the `CLAUDECODE` env var)
- Workaround: `env -u CLAUDECODE claude -p "..." --model sonnet`
- Safe for `-p` (print/non-interactive mode) since it's stateless

@~/.claude/CLAUDE.local.md
145 changes: 145 additions & 0 deletions bin/desktop-session-log
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env bash
# Capture safe, local diagnostics for graphical-session startup.
#
# GNOME runs this from XDG autostart. bspwmrc calls it directly because bspwm
# has no equivalent autostart phase. Logs stay under XDG_STATE_HOME and are
# never part of the repository.
set -u

umask 077

STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}"
LOG_DIR="$STATE_DIR/desktop-session"
mkdir -p "$LOG_DIR" || exit 1

BOOT_ID=unknown
if [[ -r /proc/sys/kernel/random/boot_id ]]; then
read -r BOOT_ID < /proc/sys/kernel/random/boot_id || BOOT_ID=unknown
fi
BOOT_ID="${BOOT_ID//-/}"
BOOT_LOG="$LOG_DIR/boot-${BOOT_ID}.log"
TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"

section() {
printf '\n===== %s =====\n' "$1"
}

capture_context() {
local session="$1"

section "session metadata"
printf 'timestamp_utc=%s\n' "$TIMESTAMP"
printf 'session=%s\n' "$session"
printf 'uid=%s user=%s\n' "$(id -u)" "$(id -un)"
printf 'pid=%s ppid=%s\n' "$$" "$PPID"
printf 'boot_id=%s\n' "$BOOT_ID"
printf 'kernel=%s\n' "$(uname -srmo)"
printf 'DISPLAY=%q\n' "${DISPLAY-}"
printf 'XAUTHORITY=%q\n' "${XAUTHORITY-}"
printf 'XDG_SESSION_TYPE=%q\n' "${XDG_SESSION_TYPE-}"
printf 'XDG_SESSION_DESKTOP=%q\n' "${XDG_SESSION_DESKTOP-}"
printf 'XDG_CURRENT_DESKTOP=%q\n' "${XDG_CURRENT_DESKTOP-}"
printf 'DESKTOP_SESSION=%q\n' "${DESKTOP_SESSION-}"
printf 'XDG_SESSION_ID=%q\n' "${XDG_SESSION_ID-}"

section "active window manager"
if command -v xprop >/dev/null 2>&1; then
xprop -root _NET_SUPPORTING_WM_CHECK _NET_WM_NAME 2>&1 || true
else
printf 'xprop is unavailable\n'
fi
if command -v bspc >/dev/null 2>&1; then
bspc wm -d 2>&1 || true
bspc query -M --names 2>&1 || true
fi

section "monitor topology"
if command -v xrandr >/dev/null 2>&1; then
xrandr --query 2>&1 || true
else
printf 'xrandr is unavailable\n'
fi

section "relevant processes"
ps -eo pid=,ppid=,stat=,comm=,args= 2>&1 |
awk '$4 ~ /^(Xorg|bspwm|sxhkd|gnome-session|gnome-shell|mutter|gdm|polybar|picom)$/'

section "failed user units"
if command -v systemctl >/dev/null 2>&1; then
systemctl --user --no-pager --failed 2>&1 || true
else
printf 'systemctl is unavailable\n'
fi

section "login session status"
if command -v loginctl >/dev/null 2>&1 && [[ -n "${XDG_SESSION_ID-}" ]]; then
loginctl session-status "$XDG_SESSION_ID" --no-pager 2>&1 || true
else
printf 'loginctl session id is unavailable\n'
fi

section "user journal: graphical-session events"
if command -v journalctl >/dev/null 2>&1; then
journalctl --user -b -n 300 --no-pager -o short-precise 2>&1 |
rg -i 'gnome-session|gnome-shell|mutter|bspwm|sxhkd|xorg|gdm|nvidia|polybar|picom|monitor|failed|error|fatal|permission' ||
true
else
printf 'journalctl is unavailable\n'
fi

section "system journal: display-manager events"
if command -v journalctl >/dev/null 2>&1; then
journalctl -b -n 300 --no-pager -o short-precise 2>&1 |
rg -i 'gdm|gdm-x-session|Xorg|gnome-shell|mutter|bspwm|sxhkd|nvidia|failed|error|fatal|permission' ||
true
else
printf 'journalctl is unavailable\n'
fi
}

startup_log() {
local session="$1"
exec >>"$BOOT_LOG" 2>&1
ln -sfn -- "$(basename "$BOOT_LOG")" "$LOG_DIR/latest-startup.log"
printf '=== desktop session startup: %s ===\n' "$session"
capture_context "$session"
}

exit_log() {
local session="$1"
local status="$2"
exec >>"$BOOT_LOG" 2>&1
printf '\n=== desktop session exit: %s status=%s ===\n' "$session" "$status"
printf 'timestamp_utc=%s\n' "$(date -u +%Y%m%dT%H%M%SZ)"
if command -v bspc >/dev/null 2>&1; then
bspc wm -d 2>&1 || true
fi
}

diagnostic_log() {
local session="${2:-manual}"
local log_file="$LOG_DIR/diagnostic-${TIMESTAMP}-$$.log"
printf 'Writing desktop-session diagnostics to %s\n' "$log_file"
exec >"$log_file" 2>&1
ln -sfn -- "$(basename "$log_file")" "$LOG_DIR/latest-diagnostic.log"
printf '=== manual desktop-session diagnostic: %s ===\n' "$session"
capture_context "$session"
printf '\nDiagnostic complete.\n'
}

case "${1-}" in
--startup)
startup_log "${2:-unknown}"
;;
--exit)
exit_log "${2:-unknown}" "${3:-unknown}"
;;
--diagnose)
diagnostic_log "$@"
;;
*)
printf 'Usage: %s --startup SESSION | --exit SESSION STATUS | --diagnose [SESSION]\n' \
"$0" >&2
exit 2
;;
esac
File renamed without changes.
22 changes: 16 additions & 6 deletions desktop-environment/bspwm/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ Ordered startup — each step depends on the previous:
4. **Launch background apps** — picom, nitrogen, redshift, nm-applet, blueman, protonvpn (all pgrep-guarded)
5. **Start sxhkd LAST** — `pkill` + 200ms wait + launch. Must be last so X key grabs succeed after monitor setup

Logs: `$XDG_STATE_HOME/bspwm/bspwm.log`, `$XDG_STATE_HOME/sxhkd/sxhkd.log`
Logs: `$XDG_STATE_HOME/bspwm/bspwm.log`, `$XDG_STATE_HOME/sxhkd/sxhkd.log`,
and `$XDG_STATE_HOME/desktop-session/boot-<boot-id>.log`. The latter captures
filtered GNOME/Mutter, bspwm, Xorg, GDM, NVIDIA, monitor, and failed-unit
events for both GNOME autostart and bspwm startup.

### Monitor Manager (Python)

Expand Down Expand Up @@ -68,7 +71,12 @@ ui: # polybar bars per monitor (orientation, font_size, modules)

Profiles use logical aliases (laptop, main, vertical) resolved to actual outputs at runtime.

**Current profiles:** personal-solo (eDP-1), personal-home. Work-machine profiles are not tracked here; they live in the private overlay repo alongside the rest of the machine-local config.
**Current profiles:** personal-solo (eDP-1), personal-home,
work-laptop-home, work-laptop-woodinville (laptop fallback enabled), and
work-laptop-woodinville-clamshell (external monitors only). Profile matching
uses the ACPI lid state when available and skips an enabled-laptop profile
while the lid is closed. It also falls back to requiring an active Xrandr mode
when the lid state is unavailable.

### Shell Scripts (scripts/)

Expand Down Expand Up @@ -97,10 +105,12 @@ icon at startup, which is why apply-auto.sh restarts it afterwards (nm-applet
re-registers on its own).

`scripts/network-env.sh`, sourced by bspwmrc and apply-auto.sh, exports
`NETWORK_INTERFACE` (whichever interface holds the default route) and
`NETWORK_LABEL` for the network module — the literal token `%essid%` on Wi-Fi
so polybar keeps it live, or a wired link's NetworkManager connection name,
since polybar renders `%essid%` as junk on a wired interface.
`NETWORK_INTERFACE` (whichever interface holds the default route), and on a
wired link `NETWORK_LABEL` (its NetworkManager connection name). Both are
read by the network module's `../polybar/shades/scripts/network-label.sh`,
a custom/script module rather than polybar's `internal/network`. Wi-Fi leaves
`NETWORK_LABEL` unset: the script resolves the live SSID and signal strength
itself, so roaming needs no bar restart.

### sxhkd (../sxhkd/sxhkdrc)

Expand Down
31 changes: 27 additions & 4 deletions desktop-environment/bspwm/bspwmrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,25 @@ BSPWM_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}"
LOG_FILE="$STATE_DIR/bspwm/bspwm.log"
SXHKD_LOG="$STATE_DIR/sxhkd/sxhkd.log"
SESSION_LOGGER="$HOME/.local/bin/desktop-session-log"
mkdir -p "$STATE_DIR/bspwm" "$STATE_DIR/sxhkd"

log() {
echo "[$(date +%s.%N)] $1" >>"$LOG_FILE"
}

session_log() {
if [ -x "$SESSION_LOGGER" ]; then
"$SESSION_LOGGER" "$@" || true
fi
}

on_exit() {
local rc=$?
log "bspwmrc config finished with status $rc"
}
trap on_exit EXIT

# Rotate logs larger than 256KB, keeping one .1 backup
rotate_if_large() {
local file="$1"
Expand All @@ -31,6 +44,7 @@ rotate_if_large "$SXHKD_LOG"

# Append so a failing run's diagnostics survive a subsequent restart
echo "=== bspwmrc started at $(date) ===" >>"$LOG_FILE"
session_log --startup bspwm

# Make the X session env available to systemd user units (swapescape.service
# runs setxkbmap and needs DISPLAY/XAUTHORITY)
Expand All @@ -47,7 +61,7 @@ fi
# shellcheck source=scripts/network-env.sh
. "$BSPWM_DIR/scripts/network-env.sh"
network_env
log "NETWORK_INTERFACE=$NETWORK_INTERFACE NETWORK_LABEL=$NETWORK_LABEL"
log "NETWORK_INTERFACE=$NETWORK_INTERFACE NETWORK_LABEL=${NETWORK_LABEL-<unset: wireless>}"

# 1. Wait for X/display driver to be ready
log "Waiting for X..."
Expand All @@ -67,6 +81,10 @@ if [ "$x_ready" = false ]; then
notify-send -u critical "Monitor Manager" "X not ready - emergency fallback"
fi

# Do not leave unmapped monitor windows behind after a display is disabled.
# Stale bspwm monitor windows can sit above Polybar and swallow its clicks.
bspc config remove_disabled_monitors true

# 2a. Disable phantom outputs (disconnected but still active in xrandr).
# Happens after unplugging externals — xrandr keeps stale modes/positions,
# bspwm/apps get mapped into dead space. Profile apply only --offs outputs
Expand Down Expand Up @@ -140,11 +158,16 @@ sleep 0.2
echo "=== sxhkd watchdog started at $(date) ===" >>"$SXHKD_LOG"
(
exec -a sxhkd-watchdog bash -c '
while true; do
while bspc wm -d >/dev/null 2>&1; do
sxhkd 2>>"'"$SXHKD_LOG"'"
echo "=== sxhkd exited at $(date), restarting in 1s ===" >>"'"$SXHKD_LOG"'"
sleep 1
if bspc wm -d >/dev/null 2>&1; then
echo "=== sxhkd exited at $(date), restarting in 1s ===" >>"'"$SXHKD_LOG"'"
sleep 1
else
break
fi
done
echo "=== sxhkd watchdog exiting at $(date): bspwm is unavailable ===" >>"'"$SXHKD_LOG"'"
'
) &
disown
Expand Down
Loading
Loading