From 68b594402f2b1ff8d18d1ba0611b2535d16dc1df Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:16:43 -0400 Subject: [PATCH 01/11] workstation-v0: add shell rc autopatch helper (bash/zsh) --- .../workstation-v0/bin/patch-shell.sh | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 profiles/linux-dev/workstation-v0/bin/patch-shell.sh diff --git a/profiles/linux-dev/workstation-v0/bin/patch-shell.sh b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh new file mode 100644 index 0000000..3a3bc6a --- /dev/null +++ b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Patch shell rc files to: +# - ensure ~/.local/bin is on PATH +# - source the SourceOS shell spine (`$XDG_CONFIG_HOME/sourceos/shell/common.sh`) +# +# Idempotent: uses a marker block. + +MODE="${1:-apply}" # apply|dry-run + +info(){ printf "INFO: %s\n" "$*" >&2; } +warn(){ printf "WARN: %s\n" "$*" >&2; } +err(){ printf "ERROR: %s\n" "$*" >&2; } + +marker_start="# >>> sourceos workstation-v0 >>>" +marker_end="# <<< sourceos workstation-v0 <<<" + +spine_path='${XDG_CONFIG_HOME:-$HOME/.config}/sourceos/shell/common.sh' + +block() { + cat </dev/null +} + +apply_to_file() { + local f=$1 + + if [ ! -e "$f" ]; then + warn "rc file not found (skipping): $f" + return 0 + fi + + if has_block "$f"; then + info "already patched: $f" + return 0 + fi + + if [ "$MODE" = "dry-run" ]; then + info "would patch: $f" + return 0 + fi + + # Append block at end + printf "\n%s\n" "$(block)" >> "$f" + info "patched: $f" +} + +main(){ + case "$MODE" in + apply|dry-run) ;; + *) + err "unknown mode: $MODE (use apply|dry-run)" + exit 2 + ;; + esac + + while IFS= read -r rc; do + apply_to_file "$rc" + done < <(rc_candidates) + + info "done" + if [ "$MODE" = "dry-run" ]; then + info "re-run with: $0 apply" + fi +} + +main "$@" From 5c44cce2719310ca71c43bd7049f0a0008a6e865 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:18:24 -0400 Subject: [PATCH 02/11] workstation-v0: add sourceos fix shell + palette entries (autopatch) --- .../linux-dev/workstation-v0/bin/sourceos | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/profiles/linux-dev/workstation-v0/bin/sourceos b/profiles/linux-dev/workstation-v0/bin/sourceos index 48dcfdd..d397a9d 100644 --- a/profiles/linux-dev/workstation-v0/bin/sourceos +++ b/profiles/linux-dev/workstation-v0/bin/sourceos @@ -44,6 +44,7 @@ sourceos (workstation helper) Usage: sourceos palette + sourceos fix shell [apply|dry-run] sourceos doctor [--open] sourceos status [--json|--open|--write ] sourceos profile apply @@ -88,6 +89,13 @@ profile_path(){ echo "$PROFILE_DIR" } +run_fix_shell(){ + local mode=${1:-apply} + local s="$PROFILE_DIR/bin/patch-shell.sh" + [[ -x "$s" ]] || { err "patch-shell helper missing: $s"; exit 2; } + exec "$s" "$mode" +} + gnome_detect(){ [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] && return 0 [[ "${DESKTOP_SESSION:-}" == *gnome* ]] && return 0 @@ -253,6 +261,8 @@ status_write(){ palette_menu(){ # Print menu entries as: labelcommand cat <<'EOF' +Palette: Fix shell rc (dry-run) sourceos fix shell dry-run +Palette: Fix shell rc (apply) sourceos fix shell apply Status (open report) sourceos status --open Doctor (open report) sourceos doctor --open Apply profile sourceos profile apply @@ -306,6 +316,21 @@ main(){ run_palette ;; + fix) + shift || true + case "${1:-}" in + shell) + shift || true + run_fix_shell "${1:-apply}" + ;; + *) + err "unknown fix target: ${1:-}" + usage + exit 2 + ;; + esac + ;; + doctor) shift || true case "${1:-}" in From ce2e23c31353e53848e41ef3f707a1a7ebb048fd Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:19:55 -0400 Subject: [PATCH 03/11] workstation-v0: optional shell rc autopatch (SOURCEOS_AUTOPATCH_SHELL=1) --- profiles/linux-dev/workstation-v0/install.sh | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/profiles/linux-dev/workstation-v0/install.sh b/profiles/linux-dev/workstation-v0/install.sh index 3561773..60ef920 100644 --- a/profiles/linux-dev/workstation-v0/install.sh +++ b/profiles/linux-dev/workstation-v0/install.sh @@ -10,6 +10,13 @@ warn(){ printf "WARN: %s\n" "$*" >&2; } have(){ command -v "$1" >/dev/null 2>&1; } +autopatch_enabled(){ + case "${SOURCEOS_AUTOPATCH_SHELL:-0}" in + 1|true|TRUE|yes|YES) return 0 ;; + *) return 1 ;; + esac +} + install_system(){ if have rpm-ostree; then info "rpm-ostree detected: installing minimal SYSTEM layer (may require reboot)" @@ -27,7 +34,7 @@ install_system(){ } install_brew(){ - err "brew not found. Install brew first, then re-run." + err "brew not found. Install brew first, then re-run." exit 2 } @@ -65,6 +72,20 @@ install_sourceos_cli(){ fi } +patch_shell_rc_if_enabled(){ + if ! autopatch_enabled; then + return 0 + fi + + local script="$PROFILE_DIR/bin/patch-shell.sh" + if [[ -x "$script" ]]; then + info "Autopatch enabled: patching shell rc files" + "$script" apply || warn "shell rc patch failed (non-fatal)" + else + warn "autopatch enabled but patch helper missing: $script" + fi +} + apply_gnome_baseline(){ local script="$PROFILE_DIR/gnome/apply.sh" if [[ -x "$script" ]]; then @@ -111,6 +132,7 @@ main(){ install_user install_shell_spine install_sourceos_cli + patch_shell_rc_if_enabled apply_gnome_baseline apply_gnome_extensions apply_launcher_install From 5258c7010a9a114314933ebb9352f65501a92a3e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:20:55 -0400 Subject: [PATCH 04/11] docs(workstation): document shell autopatch option and palette fix-shell actions --- docs/workstation/RUNBOOK.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/workstation/RUNBOOK.md b/docs/workstation/RUNBOOK.md index 163e372..bb3ddd4 100644 --- a/docs/workstation/RUNBOOK.md +++ b/docs/workstation/RUNBOOK.md @@ -25,9 +25,7 @@ Quick checks: uname -a && echo "XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP:-}" && command -v rpm-ostree || true && command -v dnf || true ``` -If Homebrew/Linuxbrew is not installed, install it first (inspect scripts before running): -- https://brew.sh -- https://docs.brew.sh/Homebrew-on-Linux +If Homebrew/Linuxbrew is not installed, install it first. --- @@ -49,6 +47,25 @@ Notes: - open-source launcher (fuzzel preferred) + SourceOS palette hotkey - `sourceos` helper wrapper into `~/.local/bin` +### Optional: autopatch shell rc + +If you want the installer to also patch your shell rc files (`~/.bashrc`, `~/.zshrc`) to: +- ensure `$HOME/.local/bin` is on PATH +- source the SourceOS shell spine + +Run: + +```bash +SOURCEOS_AUTOPATCH_SHELL=1 ./profiles/linux-dev/workstation-v0/install.sh +``` + +Or via the palette: + +```bash +sourceos fix shell dry-run +sourceos fix shell apply +``` + --- ## 2) Launcher palette (open-source) From d81354c2e6bbaceb7a0d438c9fee159e7a65a62b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:26:15 -0400 Subject: [PATCH 05/11] workstation-v0: fix patch-shell to avoid baking PATH at patch time --- .../workstation-v0/bin/patch-shell.sh | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/profiles/linux-dev/workstation-v0/bin/patch-shell.sh b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh index 3a3bc6a..acdbcd3 100644 --- a/profiles/linux-dev/workstation-v0/bin/patch-shell.sh +++ b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh @@ -16,24 +16,22 @@ err(){ printf "ERROR: %s\n" "$*" >&2; } marker_start="# >>> sourceos workstation-v0 >>>" marker_end="# <<< sourceos workstation-v0 <<<" -spine_path='${XDG_CONFIG_HOME:-$HOME/.config}/sourceos/shell/common.sh' - block() { + # NOTE: Dollar signs are escaped so we do not bake the *current* PATH into the rc file. cat <> "$f" + { + printf '\n' + block + printf '\n' + } >> "$f" + info "patched: $f" } From 681185c2ed1528cd27df3a9076263010e2e3a95c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:32:28 -0400 Subject: [PATCH 06/11] workstation-v0: add SOURCEOS_RC_FILES hook to patch-shell --- .../workstation-v0/bin/patch-shell.sh | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/profiles/linux-dev/workstation-v0/bin/patch-shell.sh b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh index acdbcd3..2f15248 100644 --- a/profiles/linux-dev/workstation-v0/bin/patch-shell.sh +++ b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh @@ -2,10 +2,11 @@ set -euo pipefail # Patch shell rc files to: -# - ensure ~/.local/bin is on PATH +# - ensure $HOME/.local/bin is on PATH # - source the SourceOS shell spine (`$XDG_CONFIG_HOME/sourceos/shell/common.sh`) # # Idempotent: uses a marker block. +# CI/test hook: SOURCEOS_RC_FILES may be set to a colon-separated list of rc files. MODE="${1:-apply}" # apply|dry-run @@ -31,8 +32,17 @@ EOF } rc_candidates() { - echo "$HOME/.bashrc" - echo "$HOME/.zshrc" + if [[ -n "${SOURCEOS_RC_FILES:-}" ]]; then + local IFS=':' + # shellcheck disable=SC2206 + read -r -a arr <<<"${SOURCEOS_RC_FILES}" + for f in "${arr[@]}"; do + [[ -n "$f" ]] && printf '%s\n' "$f" + done + return 0 + fi + + printf '%s\n' "$HOME/.bashrc" "$HOME/.zshrc" } has_block() { @@ -43,7 +53,7 @@ has_block() { apply_to_file() { local f=$1 - if [ ! -e "$f" ]; then + if [[ ! -e "$f" ]]; then warn "rc file not found (skipping): $f" return 0 fi @@ -53,7 +63,7 @@ apply_to_file() { return 0 fi - if [ "$MODE" = "dry-run" ]; then + if [[ "$MODE" == "dry-run" ]]; then info "would patch: $f" return 0 fi @@ -81,7 +91,7 @@ main(){ done < <(rc_candidates) info "done" - if [ "$MODE" = "dry-run" ]; then + if [[ "$MODE" == "dry-run" ]]; then info "re-run with: $0 apply" fi } From dfbc1fd4ab65cb2dbfb71d82755fdf0c0c36c9b3 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:13:56 -0400 Subject: [PATCH 07/11] workstation-v0: add fish shell spine (common.fish) --- .../workstation-v0/shell/common.fish | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 profiles/linux-dev/workstation-v0/shell/common.fish diff --git a/profiles/linux-dev/workstation-v0/shell/common.fish b/profiles/linux-dev/workstation-v0/shell/common.fish new file mode 100644 index 0000000..8d3e999 --- /dev/null +++ b/profiles/linux-dev/workstation-v0/shell/common.fish @@ -0,0 +1,44 @@ +# SourceOS Workstation v0 shell spine (fish) +# Safe to source multiple times. + +if set -q SOURCEOS_SHELL_SPINE_LOADED + exit 0 +end +set -gx SOURCEOS_SHELL_SPINE_LOADED 1 + +# Ensure ~/.local/bin is on PATH (fish-native) +if type -q fish_add_path + fish_add_path -m $HOME/.local/bin +else + set -gx PATH $HOME/.local/bin $PATH +end + +# direnv +if type -q direnv + direnv hook fish | source +end + +# atuin +if type -q atuin + atuin init fish --disable-up-arrow | source +end + +# zoxide +if type -q zoxide + zoxide init fish | source + alias cd z +end + +# ergonomic aliases +if type -q eza + alias ls 'eza --group-directories-first --icons' + alias ll 'eza -lah --group-directories-first --icons' +end + +if type -q bat + alias cat bat +end + +if type -q yazi + alias y yazi +end From dfcac8f48c42079bd24e374d818b61426e1f84e6 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:15:21 -0400 Subject: [PATCH 08/11] workstation-v0: add fish config autopatch helper --- .../workstation-v0/bin/patch-fish.sh | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 profiles/linux-dev/workstation-v0/bin/patch-fish.sh diff --git a/profiles/linux-dev/workstation-v0/bin/patch-fish.sh b/profiles/linux-dev/workstation-v0/bin/patch-fish.sh new file mode 100644 index 0000000..4835a2d --- /dev/null +++ b/profiles/linux-dev/workstation-v0/bin/patch-fish.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Patch fish config to source SourceOS fish spine. +# Idempotent marker block. + +MODE="${1:-apply}" # apply|dry-run + +info(){ printf "INFO: %s\n" "$*" >&2; } +warn(){ printf "WARN: %s\n" "$*" >&2; } +err(){ printf "ERROR: %s\n" "$*" >&2; } + +marker_start="# >>> sourceos workstation-v0 (fish) >>>" +marker_end="# <<< sourceos workstation-v0 (fish) <<<" + +fish_cfg(){ + echo "${XDG_CONFIG_HOME:-$HOME/.config}/fish/config.fish" +} + +has_block(){ + local f=$1 + grep -Fqx "$marker_start" "$f" 2>/dev/null +} + +block(){ + cat <<'EOF' +# >>> sourceos workstation-v0 (fish) >>> +# Added by SourceOS Workstation v0 +set spine_path "${XDG_CONFIG_HOME:-$HOME/.config}/sourceos/shell/common.fish" +if test -f "$spine_path" + source "$spine_path" +end +# <<< sourceos workstation-v0 (fish) <<< +EOF +} + +apply(){ + local f + f="$(fish_cfg)" + + if [[ ! -e "$f" ]]; then + warn "fish config not found (skipping): $f" + return 0 + fi + + if has_block "$f"; then + info "already patched: $f" + return 0 + fi + + if [[ "$MODE" == "dry-run" ]]; then + info "would patch: $f" + return 0 + fi + + { + printf '\n' + block + printf '\n' + } >> "$f" + + info "patched: $f" +} + +main(){ + case "$MODE" in + apply|dry-run) ;; + *) + err "unknown mode: $MODE (use apply|dry-run)" + exit 2 + ;; + esac + + apply + info "done" +} + +main "$@" From f5978c6c1c303c975b816a181c65f55c98f8894d Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:19:25 -0400 Subject: [PATCH 09/11] workstation-v0: autopatch also handles fish config when present --- profiles/linux-dev/workstation-v0/install.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/profiles/linux-dev/workstation-v0/install.sh b/profiles/linux-dev/workstation-v0/install.sh index 60ef920..0b4cbf8 100644 --- a/profiles/linux-dev/workstation-v0/install.sh +++ b/profiles/linux-dev/workstation-v0/install.sh @@ -77,12 +77,19 @@ patch_shell_rc_if_enabled(){ return 0 fi - local script="$PROFILE_DIR/bin/patch-shell.sh" - if [[ -x "$script" ]]; then + local sh_script="$PROFILE_DIR/bin/patch-shell.sh" + if [[ -x "$sh_script" ]]; then info "Autopatch enabled: patching shell rc files" - "$script" apply || warn "shell rc patch failed (non-fatal)" + "$sh_script" apply || warn "shell rc patch failed (non-fatal)" else - warn "autopatch enabled but patch helper missing: $script" + warn "autopatch enabled but patch helper missing: $sh_script" + fi + + # Optional: patch fish config if present. + local fish_script="$PROFILE_DIR/bin/patch-fish.sh" + if [[ -x "$fish_script" ]]; then + info "Autopatch enabled: patching fish config (if present)" + "$fish_script" apply || warn "fish config patch failed (non-fatal)" fi } From 9275e1ac4a61b094dcc5266951198abf35aa1cba Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:20:43 -0400 Subject: [PATCH 10/11] docs(workstation): document fish spine + fish autopatch helper --- docs/workstation/RUNBOOK.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/workstation/RUNBOOK.md b/docs/workstation/RUNBOOK.md index bb3ddd4..b0ba126 100644 --- a/docs/workstation/RUNBOOK.md +++ b/docs/workstation/RUNBOOK.md @@ -43,13 +43,18 @@ Notes: - SYSTEM baseline (git/ssh/podman/toolbox/wl-clipboard/jq/xclip) - USER toolset via `brew` (manifest-driven) - shell spine config to `$XDG_CONFIG_HOME/sourceos/shell/common.sh` + - fish spine config to `$XDG_CONFIG_HOME/sourceos/shell/common.fish` - GNOME baseline + extensions - open-source launcher (fuzzel preferred) + SourceOS palette hotkey - `sourceos` helper wrapper into `~/.local/bin` ### Optional: autopatch shell rc -If you want the installer to also patch your shell rc files (`~/.bashrc`, `~/.zshrc`) to: +If you want the installer to also patch your shell rc files: +- bash/zsh: `~/.bashrc`, `~/.zshrc` +- fish: `$XDG_CONFIG_HOME/fish/config.fish` (if present) + +It will: - ensure `$HOME/.local/bin` is on PATH - source the SourceOS shell spine @@ -66,6 +71,13 @@ sourceos fix shell dry-run sourceos fix shell apply ``` +For fish: + +```bash +./profiles/linux-dev/workstation-v0/bin/patch-fish.sh dry-run +./profiles/linux-dev/workstation-v0/bin/patch-fish.sh apply +``` + --- ## 2) Launcher palette (open-source) From 346ff3f728a87ad60a4270299ec16282fdea98c8 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:25:39 -0400 Subject: [PATCH 11/11] ci: add patch-shell idempotency smoke test for workstation-v0 --- .github/workflows/workstation-scripts.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/workstation-scripts.yml b/.github/workflows/workstation-scripts.yml index 943018d..2f3969d 100644 --- a/.github/workflows/workstation-scripts.yml +++ b/.github/workflows/workstation-scripts.yml @@ -57,6 +57,22 @@ jobs: bash -n "$f" done <<<"$files" + - name: Smoke: patch-shell idempotent + run: | + set -euo pipefail + p='profiles/linux-dev/workstation-v0/bin/patch-shell.sh' + bash -n "$p" + + tmp=$(mktemp) + printf '# rc\n' > "$tmp" + + SOURCEOS_RC_FILES="$tmp" bash "$p" apply + SOURCEOS_RC_FILES="$tmp" bash "$p" apply + + test "$(grep -cF '# >>> sourceos workstation-v0 >>>' "$tmp")" -eq 1 + test "$(grep -cF '# <<< sourceos workstation-v0 <<<' "$tmp")" -eq 1 + grep -F 'export PATH="$HOME/.local/bin:$PATH"' "$tmp" >/dev/null + - name: Smoke: sourceos help exits cleanly run: | set -euo pipefail