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 diff --git a/docs/workstation/RUNBOOK.md b/docs/workstation/RUNBOOK.md index 163e372..b0ba126 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. --- @@ -45,10 +43,41 @@ 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: +- 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 + +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 +``` + +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) 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 "$@" 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..2f15248 --- /dev/null +++ b/profiles/linux-dev/workstation-v0/bin/patch-shell.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Patch shell rc files to: +# - 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 + +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 <<<" + +block() { + # NOTE: Dollar signs are escaped so we do not bake the *current* PATH into the rc file. + 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 + + { + 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 + + 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 "$@" 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 diff --git a/profiles/linux-dev/workstation-v0/install.sh b/profiles/linux-dev/workstation-v0/install.sh index 3561773..0b4cbf8 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,27 @@ install_sourceos_cli(){ fi } +patch_shell_rc_if_enabled(){ + if ! autopatch_enabled; then + return 0 + fi + + local sh_script="$PROFILE_DIR/bin/patch-shell.sh" + if [[ -x "$sh_script" ]]; then + info "Autopatch enabled: patching shell rc files" + "$sh_script" apply || warn "shell rc patch failed (non-fatal)" + else + 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 +} + apply_gnome_baseline(){ local script="$PROFILE_DIR/gnome/apply.sh" if [[ -x "$script" ]]; then @@ -111,6 +139,7 @@ main(){ install_user install_shell_spine install_sourceos_cli + patch_shell_rc_if_enabled apply_gnome_baseline apply_gnome_extensions apply_launcher_install 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