11 Commits

Author SHA1 Message Date
lachtan
0041ce95dc Commit messages must be in English 2026-03-15 20:41:57 +01:00
lachtan
dd7a9249a2 Update project documentation 2026-03-15 20:36:02 +01:00
lachtan
7ad3fa729c Add forgotten .claude/settings.local.json to .gitignore 2026-03-15 20:35:51 +01:00
lachtan
b9fa65b141 Add whole-string quoting rule to bash style guide 2026-03-15 16:34:14 +01:00
lachtan
0d5eb492aa Quote variable expansions in rc scripts 2026-03-15 16:32:14 +01:00
lachtan
79ba0ae913 Add shellcheck hook and configuration 2026-03-15 16:28:34 +01:00
lachtan
aa28c9619f Add commit message style guideline to CLAUDE.md 2026-03-15 16:27:31 +01:00
lachtan
54ad290a82 Add resource cleanup and structured data rules to bash style guide 2026-03-15 15:43:08 +01:00
lachtan
f8a61e9290 Improve CLAUDE.md docs and add bash style rules
Expand CLAUDE.md with detailed helper function reference,
environment variables section, and commit conventions.
Add .claude/rules/bash-style.md for bash coding standards.
2026-03-15 14:36:53 +01:00
lachtan
ef57d56b2c Move max_line_length to global editorconfig section 2026-03-15 14:35:58 +01:00
lachtan
7a344b82dd Suppress output in non-interactive shells
Add is_interactive_shell() helper and gate all user-visible
echo/debug output behind it so that non-interactive invocations
(scripts, cron, ssh commands) stay silent. Move functions.sh
source earlier in bashrc so helpers are available sooner.
2026-03-15 14:25:12 +01:00
14 changed files with 186 additions and 20 deletions

22
.claude/hooks/shellcheck.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -E -o errexit -o nounset -o pipefail
# Exit silently if required tools are missing
command -v shellcheck > /dev/null 2>&1 || exit 0
command -v file > /dev/null 2>&1 || exit 0
command -v jq > /dev/null 2>&1 || exit 0
input=$(cat)
file_path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[[ -z "$file_path" || ! -f "$file_path" ]] && exit 0
file_type=$(file --brief "$file_path")
case "$file_type" in
*"Bourne-Again shell"*|*"bash"*|*"/bin/bash"*) ;;
*"shell script"*|*"/bin/sh"*) ;;
*) exit 0 ;;
esac
shellcheck --format=gcc "$file_path" >&2

View File

@@ -0,0 +1,74 @@
---
paths:
- "**/*.sh"
- "bashrc"
- "bin/*"
---
# Bash Readability Style
The primary goal is code that is **easy to understand at a glance**. Write bash that reads naturally, follows established conventions, and doesn't require the reader to puzzle over clever tricks. Favor clarity and well-known idioms over brevity.
Complements CLAUDE.md. Does not repeat rules already defined there.
## Formatting
- 2-space indentation, no tabs
- `then`/`do` on same line: `if cond; then` / `for x in list; do`
- Blank lines between logical sections; no blanks between tightly coupled statements
- Long pipe chains: one command per line with leading `|`
## Naming
- `UPPER_CASE` — exported env vars and constants; use `readonly` for true constants
- `snake_case` — local variables and function names
- `verb_noun` pattern for functions: `set_ps1_prompt`, `docker_aws_login`
- Meaningful names, no single letters except loop counters (`i`, `f`)
## Functions
```bash
function do_thing() {
local name="$1"
local count="${2:-0}"
...
}
```
- Always `function name() {` declaration style
- Always `local` for function variables, assigned on declaration line
- Name parameters immediately: `local filename="$1"` — no raw `$1` in logic
- Default values via `${VAR:-default}`
- Guard clauses with early `return` over deep nesting
- Keep functions focused — single responsibility
## Syntax Preferences
- `[[ ]]` over `[ ]` for conditionals
- `(( ))` for arithmetic comparisons
- `$(command)` for substitution, never backticks
- Double quotes around expansions: `"$var"`, `"${array[@]}"`
- Quote the whole string, not just the variable: `"$HOME/.local/bin"` not `"$HOME"/.local/bin`
- Single quotes for literals that must not expand
- `printf` over `echo` when output contains escapes or format strings
## Resource Cleanup
- Consider `trap cleanup EXIT` with `mktemp` when scripts create temporary files/dirs or acquire resources; skip when it would add unnecessary complexity (short scripts, no temp state)
## Structured Data
- Prefer `jq` for JSON and `yq` for YAML over ad-hoc `grep`/`awk`/string splitting
- Treat parser errors as fatal; check exit status before using results
## Commands
- Long options for clarity: `--recursive` not `-r` (common test flags `-f`, `-d`, `-e` are fine)
- Redirect stderr: `> /dev/null 2>&1`
- `# shellcheck shell=bash` at top of sourced (non-executable) files
## Comments
- Minimal — explain *why*, not *what*
- URL reference when pattern comes from external source
- No banner/divider comments

15
.claude/settings.json Normal file
View File

@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/shellcheck.sh"
}
]
}
]
}
}

View File

@@ -7,6 +7,7 @@ insert_final_newline = true
end_of_line = lf
indent_size = 4
indent_style = space
max_line_length = 120
[*.md]
trim_trailing_whitespace = false
@@ -14,7 +15,6 @@ indent_size = 2
[*.{java,kt,kts}]
continuation_indent_size = 4
max_line_length = 120
wildcard_import_limit = 99
[*.xml]

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@
.vscode/
.idea/
.claude/settings.local.json

12
.shellcheckrc Normal file
View File

@@ -0,0 +1,12 @@
# Default shell for sourced files without shebang (SC2148)
shell=bash
# Sourced files define variables used by other files after source
disable=SC2034
# Non-constant and external source paths are intentional
disable=SC1090
disable=SC1091
# Functions invoked indirectly via trap/callback
disable=SC2317

View File

@@ -1,6 +1,7 @@
# CLAUDE.md
# Linux Workspace
Dotfiles & workstation automation for Linux, macOS, WSL. `bashrc` loads `rc/*.sh` (ordered by prefix) and adds `bin/` to PATH. `$LWS` points to repo root.
Dotfiles & workstation automation for Linux, macOS, WSL. `bashrc` loads `rc/*.sh` (ordered by prefix)
and adds `bin/` to PATH. `$LWS` points to repo root.
## Directory Layout
@@ -9,19 +10,40 @@ Dotfiles & workstation automation for Linux, macOS, WSL. `bashrc` loads `rc/*.sh
- `bin/` — Scripts auto-added to PATH. New CLI tools go here.
- `scripts/` — Setup scripts NOT on PATH (git-config, docker fixes, gnome config).
- `conf/` — Tool configs (vim, WireGuard, WSL).
- `functions.sh` — Core lib: `append_path`, `prepend_path`, `set_uniq_path`, `source_directory_sh`, `is_fast_init`/`is_slow_init`, WSL detection, debug logging.
- `doc/` — Reference docs, cheatsheets, bookmarks.
- `functions.sh` — Core lib (see below).
## Key Helpers (`functions.sh`)
- `can_run <cmd>` — check if command is available
- `is_slow_init` / `is_fast_init` — check `LWS_FAST` mode
- `is_interactive_shell` — true when `[[ $- == *i* ]]`
- `is_wsl` — detect WSL environment
- `true_false <val>` — normalize `1/true/yes` to boolean
- `prepend_path` / `append_path` / `prepend_path_try` / `append_path_try` — safe PATH manipulation
- `source_try <file>` — source file if it exists
- `source_directory_sh <dir>` — source all `*.sh` in a directory
- `xlog <msg>` — debug log (only in interactive + `LWS_DEBUG`)
## Conventions
All new bash scripts MUST use:
All new **executable** bash scripts should use:
```bash
#!/usr/bin/env bash
set -E -o errexit -o nounset -o pipefail
```
Before writing shell code, check `functions.sh` for existing helper functions (e.g. `can_run`, `append_path_try`, `source_try`) and prefer them over raw commands.
**Sourced files** (rc files, `functions.sh`) must NOT use `set -o errexit` — they run inside
the user's shell. Add `# shellcheck shell=bash` at the top instead of a shebang.
Before writing shell code, check `functions.sh` for existing helper functions and prefer them over raw commands.
Detailed formatting, naming, and syntax rules are in `.claude/rules/bash-style.md`.
RC files MUST guard slow tools and check availability:
```bash
if is_slow_init && can_run pyenv; then
# initialize pyenv
@@ -30,4 +52,13 @@ fi
Installers (`install/*.sh`) MUST be idempotent — check before installing.
Env vars: `LWS_FAST=1` skips slow tools, `LWS_DEBUG=1` enables debug logging.
## Environment Variables
- `LWS_FAST=1` — skips slow tools during shell init
- `LWS_DEBUG=1` — enables debug logging via `xlog`
## Commits
- Do not add `Co-Authored-By` lines to commit messages.
- Commit messages must be in English.
- Prefer one-line commit messages. Add a longer description only when necessary for better understanding.

View File

@@ -24,6 +24,10 @@ For faster load can be set
For debug log enabled
`LWS_DEBUG=1`
## Directory notes
`opt/` (git-ignored) is used for locally installed tools (e.g. bash-git-prompt, enhancd).
## Quick commands
```bash

7
bashrc
View File

@@ -7,13 +7,16 @@ export LWS=$WORKSPACE
LWS_DEBUG=${LWS_DEBUG:-0}
LWS_FAST=${LWS_FAST:-0}
echo "Linux Workspace initialization"
source "$LWS/functions.sh"
if [[ -z $LC_ALL ]]; then
export LC_ALL=en_US.UTF-8
fi
source "$LWS/functions.sh"
if is_interactive_shell; then
echo "Linux Workspace initialization"
fi
source_directory_sh "$LWS/rc"
source_directory_sh "$HOME/.bashrc.d"

View File

@@ -5,6 +5,10 @@ if [ -z "$time_ms" ]; then
readonly time_ms='date +%s%3N'
fi
function is_interactive_shell() {
[[ $- == *i* ]]
}
function is_fast_init() {
(( $LWS_FAST ))
}
@@ -14,7 +18,7 @@ function is_slow_init() {
}
function xlog() {
if true_false "$LWS_DEBUG"; then
if is_interactive_shell && true_false "$LWS_DEBUG"; then
echo "$@"
fi
}
@@ -100,11 +104,11 @@ function source_directory() {
xlog "LOAD DIR $mask"
for file in $mask; do
if [[ -e "$file" ]]; then
if (( $LWS_DEBUG )); then
if is_interactive_shell && (( $LWS_DEBUG )); then
start=$($time_ms)
source "$file"
stop=$($time_ms)
echo "LOAD FILE $file $((stop - start)) ms"
xlog "LOAD FILE $file $((stop - start)) ms"
else
source "$file"
fi

View File

@@ -1 +1 @@
append_path_try $HOME/.local/bin
append_path_try "$HOME/.local/bin"

View File

@@ -1,7 +1,7 @@
_INIT_FILE=$WORKSPACE/opt/enhancd/init.sh
_INIT_FILE="$WORKSPACE/opt/enhancd/init.sh"
if [[ -f $_INIT_FILE ]]; then
source $_INIT_FILE
source "$_INIT_FILE"
unalias cd
alias ecd="__enhancd::cd"
fi

View File

@@ -1,12 +1,12 @@
CRYPTED_DIR=$HOME/Sync/safe
DECRYPTED_DIR=$HOME/mnt/safe
CRYPTED_DIR="$HOME/Sync/safe"
DECRYPTED_DIR="$HOME/mnt/safe"
function safe-mount() {
gocryptfs $CRYPTED_DIR $DECRYPTED_DIR
gocryptfs "$CRYPTED_DIR" "$DECRYPTED_DIR"
}
function safe-unmount() {
fusermount -u $DECRYPTED_DIR
fusermount -u "$DECRYPTED_DIR"
}

View File

@@ -1 +1 @@
$WORKSPACE/scripts/changes.sh
"$WORKSPACE/scripts/changes.sh"