home · contact · privacy
Ensure .sh suffix to (most) shell-code files. master
authorPlom Heller <plom@plomlompom.com>
Thu, 24 Sep 2026 23:33:04 +0000 (01:33 +0200)
committerPlom Heller <plom@plomlompom.com>
Thu, 24 Sep 2026 23:33:04 +0000 (01:33 +0200)
15 files changed:
CLAUDE.md
home/any/.profile.d/aliases [deleted file]
home/any/.profile.d/aliases.sh [new file with mode: 0644]
home/root/.profile.d/hibernation [deleted file]
home/root/.profile.d/hibernation.sh [new file with mode: 0644]
home/user/.config/sway/config
home/user/.profile.d/hibernation [deleted file]
home/user/.profile.d/hibernation.sh [new file with mode: 0644]
home/user/.profile.d/kb [deleted file]
home/user/.profile.d/kb.sh [new file with mode: 0644]
home/user/.profile.d/prompt [deleted file]
home/user/.profile.d/prompt.sh [new file with mode: 0644]
home/user/.profile.d/ssh-agent [deleted file]
home/user/.profile.d/ssh-agent.sh [new file with mode: 0644]
scripts/lib/link_home.sh

index c53937f57caeffebba078ccf155fe865365757ee..5c6d9a6a40d7d58e4ff89d3947c15a362a38a772 100644 (file)
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -262,11 +262,14 @@ caller) rather than functions reaching for same-named globals.
     changing the skeleton does *not* refresh existing links; remove a stale
     link by hand first. The parent `mkdir -p` still runs regardless, so a
     *new* skeleton file in an already-populated directory still works.
-  - Appends `for FILE in ${HOME}/.profile.d/*; do . "${FILE}"; done` to
-    `${HOME}/.profile`, guarded by `grep -qxF`. A loop, because a bare
-    `. ${HOME}/.profile.*` would source only the first match (further words
-    become positional parameters); this way a new fragment in `home/any/` or
-    `home/<target>/` never needs a `link_home` edit.
+  - Appends to `${HOME}/.profile`, guarded by `grep -qxF`, a
+    `for FILE in ${HOME}/.profile.d/*.sh` loop dot-sourcing each match that
+    passes `[ -f ]`. A loop, because a bare `. ${HOME}/.profile.d/*.sh`
+    would source only the first match (further words become positional
+    parameters); this way a new fragment in `home/any/` or `home/<target>/`
+    never needs a `link_home` edit. Only `*.sh` is sourced, so non-code
+    files can sit next to the fragments. The `[ -f ]` skips an unmatched
+    literal glob and dangling links.
   - Both halves are safe to call repeatedly; `${HOME}` at run time decides
     whose home is touched.
 - `start_root <t490s|server>` — shared first-login helper; see
@@ -677,11 +680,11 @@ Both scripts share `PATH_GIT_BASE` and (with `install_server.sh`)
 ## `home/` skeletons
 
 All symlinked by `link_home` (see "Shared library"). Profile fragments live
-in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
+in `.profile.d/` as `*.sh`, sourced by the loop line in `~/.profile`.
 
 ### `home/any/` (every account)
 
-- `.profile.d/aliases` — the `ls --color=auto` alias, plus `LS_COLORS` filled
+- `.profile.d/aliases.sh` — the `ls --color=auto` alias, plus `LS_COLORS` filled
   via `dircolors -b` (forcing `COLORTERM=truecolor` if unset) when
   `LS_COLORS` is empty: a shell reached via `su -l` (root's, from the user)
   loses foot's `COLORTERM`, and `dircolors` has no database entry for foot's
@@ -689,7 +692,7 @@ in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
 
 ### `home/root/` and `home/user/`
 
-- `.profile.d/hibernation` (both, differing) — defines a `hibernate_safe`
+- `.profile.d/hibernation.sh` (both, differing) — defines a `hibernate_safe`
   alias (`command systemctl hibernate || systemctl poweroff`) and shadows a
   command to steer plain `systemctl hibernate` towards it: `systemctl` itself
   for root, `sudo` for the user (who only reaches `systemctl` through it).
@@ -699,10 +702,10 @@ in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
 
 ### `home/user/` only
 
-- `.profile.d/ssh-agent` — starts `ssh-agent` and `ssh-add -q` once per
+- `.profile.d/ssh-agent.sh` — starts `ssh-agent` and `ssh-add -q` once per
   login, guarded by `[ -z "${SSH_AGENT_PID}" ]` (so e.g. a second `foot`
   window doesn't start another). Root needs no agent.
-- `.profile.d/prompt` — colored (bold, `COLOR_NUMBER`, default green)
+- `.profile.d/prompt.sh` — colored (bold, `COLOR_NUMBER`, default green)
   `[<date>/<time>/<zone> user@host:path]$` `PS1`, with `PS2`/`PS3`/`PS4`
   matching. Colors from `tput`; `\D{...}`/`\u`/`\h`/`\w` instead of forked
   `date`/`pwd`. In bash the colors are wrapped in `\[ \]` for readline's
@@ -711,13 +714,13 @@ in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
   unset after the call. Takes effect because the loop line sits at the very
   end of `~/.profile`, after Debian's stock one has sourced `~/.bashrc`
   (which sets its own `PS1`), so this fragment gets the last word.
-- `.profile.d/kb` — `kb` alias: `swaymsg 'input * xkb_switch_layout next'`.
+- `.profile.d/kb.sh` — `kb` alias: `swaymsg 'input * xkb_switch_layout next'`.
 - `.gitconfig` — minimal `[user]` section.
 - `.config/foot/foot.ini` — `font=monospace:size=14.5` and
   `login-shell=yes`. The latter is needed because Sway spawns foot with no
   arguments, foot spawns `${SHELL}` as a plain interactive shell, and a
   non-login shell never reads `~/.profile` — which is where the
-  `.profile.d/*` loop lives. No tracked `~/.bashrc` exists to also keep in
+  `.profile.d/*.sh` loop lives. No tracked `~/.bashrc` exists to also keep in
   sync.
 - `.config/sway/config` — Sway config. Notably `input * xkb_layout "us,de"`
   (toggled via `kb`; must stay in sync with `KB_LAYOUTS` in `status.py`) and
@@ -794,6 +797,12 @@ in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
   is for scripts, `_lib.sh` and `lib/`. A non-script config file a script
   reads *in place* from a fixed repo-relative location (like
   `to_install/<target>`) gets its own top-level directory.
+- Shell code files that are run or sourced directly from their path (or a
+  symlink to it) end in `.sh`. Exception: commands in `home/user/.local/bin/`,
+  kept suffix-less for terse names in `PATH`. Templates don't count: they
+  aren't run from their own path, and what gets run is their rendered output
+  under whatever name it needs (e.g. `update-efi-hook`, installed as
+  `post-update.d/update-efi`, since `run-parts` skips names with a dot).
 - Prefer `templates/` over a new inline heredoc for any file written with
   variable substitution; a heredoc long or numerous enough to clutter its
   script is the signal. One- or two-line writes with little substitution
diff --git a/home/any/.profile.d/aliases b/home/any/.profile.d/aliases
deleted file mode 100644 (file)
index 77d8158..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# dircolors only knows a terminal via its $TERM or a non-empty $COLORTERM,
-# and its database has no entry for foot's TERM=foot. So a shell that has
-# lost foot's COLORTERM=truecolor (e.g. root's, via "su -l", which keeps only
-# TERM) gets no LS_COLORS and hence no ls colors. Fill it in with a forced
-# COLORTERM in that case; an existing LS_COLORS is left alone.
-[ -n "${LS_COLORS}" ] || eval "$(COLORTERM=${COLORTERM:-truecolor} dircolors -b)"
-alias ls="ls --color=auto"
diff --git a/home/any/.profile.d/aliases.sh b/home/any/.profile.d/aliases.sh
new file mode 100644 (file)
index 0000000..77d8158
--- /dev/null
@@ -0,0 +1,7 @@
+# dircolors only knows a terminal via its $TERM or a non-empty $COLORTERM,
+# and its database has no entry for foot's TERM=foot. So a shell that has
+# lost foot's COLORTERM=truecolor (e.g. root's, via "su -l", which keeps only
+# TERM) gets no LS_COLORS and hence no ls colors. Fill it in with a forced
+# COLORTERM in that case; an existing LS_COLORS is left alone.
+[ -n "${LS_COLORS}" ] || eval "$(COLORTERM=${COLORTERM:-truecolor} dircolors -b)"
+alias ls="ls --color=auto"
diff --git a/home/root/.profile.d/hibernation b/home/root/.profile.d/hibernation
deleted file mode 100644 (file)
index 8f1a3c5..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# /etc/systemd/system/sytemd-hibernate.service.d/60-poweroff-on-failure.conf
-# is bypassed by pure "systemctl hibernate", so only allow wrap with poweroff
-alias hibernate_safe="command systemctl hibernate || systemctl poweroff"
-systemctl () {
-    local MSG="Use 'hibernate_safe' instead."
-    [ "$@" = "hibernate" ] && echo "${MSG}" && return 1
-    command systemctl "$@"
-}
diff --git a/home/root/.profile.d/hibernation.sh b/home/root/.profile.d/hibernation.sh
new file mode 100644 (file)
index 0000000..8f1a3c5
--- /dev/null
@@ -0,0 +1,8 @@
+# /etc/systemd/system/sytemd-hibernate.service.d/60-poweroff-on-failure.conf
+# is bypassed by pure "systemctl hibernate", so only allow wrap with poweroff
+alias hibernate_safe="command systemctl hibernate || systemctl poweroff"
+systemctl () {
+    local MSG="Use 'hibernate_safe' instead."
+    [ "$@" = "hibernate" ] && echo "${MSG}" && return 1
+    command systemctl "$@"
+}
index 2f297b3d34ee58d592ecfa3876357a8671004183..edb6193be8a1c1a462cd81d2a459597619b8c607 100644 (file)
@@ -11,7 +11,7 @@ workspace_layout tabbed
 # simple green background
 output * background #559911 solid_color
 
-# keyboard layout – to be switched with ~/.profile.d/kb
+# keyboard layout – to be switched with ~/.profile.d/kb.sh
 input * xkb_layout "us,de"
 
 # deactivate t490s touchpad
diff --git a/home/user/.profile.d/hibernation b/home/user/.profile.d/hibernation
deleted file mode 100644 (file)
index da7ef8f..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-alias hibernate_safe="sudo /bin/sh -lc hibernate_safe"
-sudo() {
-    local MSG="Use 'hibernate_safe' instead."
-    [ "$1 $2" = "systemctl hibernate" ] && echo "${MSG}" && return 1
-    command sudo "$@"
-}
diff --git a/home/user/.profile.d/hibernation.sh b/home/user/.profile.d/hibernation.sh
new file mode 100644 (file)
index 0000000..da7ef8f
--- /dev/null
@@ -0,0 +1,6 @@
+alias hibernate_safe="sudo /bin/sh -lc hibernate_safe"
+sudo() {
+    local MSG="Use 'hibernate_safe' instead."
+    [ "$1 $2" = "systemctl hibernate" ] && echo "${MSG}" && return 1
+    command sudo "$@"
+}
diff --git a/home/user/.profile.d/kb b/home/user/.profile.d/kb
deleted file mode 100644 (file)
index af82ed0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-alias kb="swaymsg 'input * xkb_switch_layout next'"
diff --git a/home/user/.profile.d/kb.sh b/home/user/.profile.d/kb.sh
new file mode 100644 (file)
index 0000000..af82ed0
--- /dev/null
@@ -0,0 +1 @@
+alias kb="swaymsg 'input * xkb_switch_layout next'"
diff --git a/home/user/.profile.d/prompt b/home/user/.profile.d/prompt
deleted file mode 100644 (file)
index c543779..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-_dot_profile_dot_prompt() {
-    local COLOR_NUMBER=2
-    local COLOR_ON
-    local COLOR_OFF
-    COLOR_ON="$(tput setaf "${COLOR_NUMBER}" 2>/dev/null)$(tput bold 2>/dev/null)"
-    COLOR_OFF="$(tput sgr0 2>/dev/null)"
-    # Bash confuses the line length when not told not to count escape
-    # sequences. PS3 is printed verbatim by "select", so it must get the
-    # unwrapped codes.
-    PS3="${COLOR_ON}select: ${COLOR_OFF}"
-    if [ -n "${BASH:-}" ]; then
-        COLOR_ON="\[${COLOR_ON}\]"
-        COLOR_OFF="\[${COLOR_OFF}\]"
-    fi
-    PS1="${COLOR_ON}[\D{%Y-%m-%d/%H:%M:%S/%Z} \u@\h:\w]\$ ${COLOR_OFF}"
-    PS2="${COLOR_ON}> ${COLOR_OFF}"
-    PS4="${COLOR_ON}+ ${COLOR_OFF}"
-}
-_dot_profile_dot_prompt
-unset -f _dot_profile_dot_prompt
diff --git a/home/user/.profile.d/prompt.sh b/home/user/.profile.d/prompt.sh
new file mode 100644 (file)
index 0000000..c543779
--- /dev/null
@@ -0,0 +1,20 @@
+_dot_profile_dot_prompt() {
+    local COLOR_NUMBER=2
+    local COLOR_ON
+    local COLOR_OFF
+    COLOR_ON="$(tput setaf "${COLOR_NUMBER}" 2>/dev/null)$(tput bold 2>/dev/null)"
+    COLOR_OFF="$(tput sgr0 2>/dev/null)"
+    # Bash confuses the line length when not told not to count escape
+    # sequences. PS3 is printed verbatim by "select", so it must get the
+    # unwrapped codes.
+    PS3="${COLOR_ON}select: ${COLOR_OFF}"
+    if [ -n "${BASH:-}" ]; then
+        COLOR_ON="\[${COLOR_ON}\]"
+        COLOR_OFF="\[${COLOR_OFF}\]"
+    fi
+    PS1="${COLOR_ON}[\D{%Y-%m-%d/%H:%M:%S/%Z} \u@\h:\w]\$ ${COLOR_OFF}"
+    PS2="${COLOR_ON}> ${COLOR_OFF}"
+    PS4="${COLOR_ON}+ ${COLOR_OFF}"
+}
+_dot_profile_dot_prompt
+unset -f _dot_profile_dot_prompt
diff --git a/home/user/.profile.d/ssh-agent b/home/user/.profile.d/ssh-agent
deleted file mode 100644 (file)
index 6fec6f9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-[ -z "${SSH_AGENT_PID}" ] && eval $(ssh-agent) > /dev/null && ssh-add -q
diff --git a/home/user/.profile.d/ssh-agent.sh b/home/user/.profile.d/ssh-agent.sh
new file mode 100644 (file)
index 0000000..6fec6f9
--- /dev/null
@@ -0,0 +1 @@
+[ -z "${SSH_AGENT_PID}" ] && eval $(ssh-agent) > /dev/null && ssh-add -q
index 146e8fa08e9020d4b39bcc4fe7b0dd7a12e576e8..1758f9887bc3f6e4a67b97542291e4feb7e0ef5d 100644 (file)
@@ -3,7 +3,9 @@ include PATH_REPO
 include msg
 
 link_home() {
-    local LOOP_PROFILES='for FILE in ${HOME}/.profile.d/*; do . "${FILE}"; done'
+    local GLOB_PROFILES='${HOME}/.profile.d/*.sh'
+    local DOT_IF_FILE='[ -f "${FILE}" ] && . "${FILE}"'
+    local LOOP_PROFILES="for FILE in ${GLOB_PROFILES}; do ${DOT_IF_FILE}; done"
     local PATH_PROFILE="${HOME}/${FNAME_PROFILE}"
     for DIRNAME in any "$1"; do
         local PATH_SKEL="${PATH_REPO}/home/${DIRNAME}"