14 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
lachtan
e351f2e924 Extract shared prompt values and refactor bash/git prompts
Move color constants and PS1 escape sequences from bash-prompt.sh into
a new shared rc/03-values.sh, and extract the user@host:dir fragment as
LWS_PS1_USER_HOST to eliminate duplication between bash-prompt.sh and
git-prompt.sh. Wire up git-prompt to use bash-git-prompt with the shared
prompt variables.
2026-03-12 12:36:13 +01:00
lachtan obecny
4997598194 Fix: dircolors loading 2026-02-20 06:38:10 +01:00
lachtan obecný
01cefdc6fd InitCLAUDE.ms 2026-02-20 06:38:10 +01:00
18 changed files with 230 additions and 26 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 end_of_line = lf
indent_size = 4 indent_size = 4
indent_style = space indent_style = space
max_line_length = 120
[*.md] [*.md]
trim_trailing_whitespace = false trim_trailing_whitespace = false
@@ -14,7 +15,6 @@ indent_size = 2
[*.{java,kt,kts}] [*.{java,kt,kts}]
continuation_indent_size = 4 continuation_indent_size = 4
max_line_length = 120
wildcard_import_limit = 99 wildcard_import_limit = 99
[*.xml] [*.xml]

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@
.vscode/ .vscode/
.idea/ .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

64
CLAUDE.md Normal file
View File

@@ -0,0 +1,64 @@
# 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.
## Directory Layout
- `rc/` — Shell init files, loaded in `0X-name.sh` order. Per-tool env setup goes here.
- `install/` — Idempotent one-off installer scripts (run manually).
- `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).
- `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 **executable** bash scripts should use:
```bash
#!/usr/bin/env bash
set -E -o errexit -o nounset -o pipefail
```
**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
fi
```
Installers (`install/*.sh`) MUST be idempotent — check before installing.
## 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 For debug log enabled
`LWS_DEBUG=1` `LWS_DEBUG=1`
## Directory notes
`opt/` (git-ignored) is used for locally installed tools (e.g. bash-git-prompt, enhancd).
## Quick commands ## Quick commands
```bash ```bash

7
bashrc
View File

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

View File

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

12
rc/03-values.sh Normal file
View File

@@ -0,0 +1,12 @@
# Terminal colors
LWS_RED=$(tput setaf 1)
LWS_GREEN=$(tput setaf 2)
LWS_CYAN=$(tput setaf 6)
LWS_COLOR_RESET=$(tput sgr0)
# Bash prompt escape sequences
LWS_PS1_USER='\u'
LWS_PS1_HOST='\h'
LWS_PS1_DIR='\w'
LWS_PS1_TYPE='\$'
LWS_PS1_USER_HOST="${LWS_GREEN}${LWS_PS1_USER}@${LWS_PS1_HOST}${LWS_COLOR_RESET}:${LWS_CYAN}${LWS_PS1_DIR}${LWS_COLOR_RESET}"

View File

@@ -2,19 +2,9 @@
# https://www.cyberciti.biz/faq/bash-shell-change-the-color-of-my-shell-prompt-under-linux-or-unix/ # https://www.cyberciti.biz/faq/bash-shell-change-the-color-of-my-shell-prompt-under-linux-or-unix/
# https://linux.101hacks.com/ps1-examples/prompt-color-using-tput/ # https://linux.101hacks.com/ps1-examples/prompt-color-using-tput/
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
CYAN=$(tput setaf 6)
COLOR_RESET=$(tput sgr0)
function get_ps1_prompt() function get_ps1_prompt()
{ {
local username='\u' echo "${debian_chroot:+($debian_chroot)}${LWS_PS1_USER_HOST}${LWS_PS1_TYPE} "
local hostname='\h'
local workdir='\w'
local user_type='\$'
echo "${debian_chroot:+($debian_chroot)}${GREEN}${username}@${hostname}${COLOR_RESET}:${CYAN}${workdir}${COLOR_RESET}${user_type} "
} }
function set_ps1_prompt() function set_ps1_prompt()

View File

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

View File

@@ -5,7 +5,7 @@
#COLORS_FILE="$LWS/conf/dircolors/ansi-light.dircolors" #COLORS_FILE="$LWS/conf/dircolors/ansi-light.dircolors"
COLORS_FILE="$LWS/conf/dircolors/dracula.dircolors" COLORS_FILE="$LWS/conf/dircolors/dracula.dircolors"
if [[ -f "$COLORS_FILE" ]]; then if can_run dircolors && [[ -f "$COLORS_FILE" ]]; then
xlog "Loading colors $COLORS_FILE" xlog "Loading colors $COLORS_FILE"
eval "$(dircolors "$COLORS_FILE")" eval "$(dircolors "$COLORS_FILE")"
else else

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 if [[ -f $_INIT_FILE ]]; then
source $_INIT_FILE source "$_INIT_FILE"
unalias cd unalias cd
alias ecd="__enhancd::cd" alias ecd="__enhancd::cd"
fi fi

View File

@@ -9,5 +9,8 @@ GIT_PROMPT_INIT="$GIT_PROMPT_PATH/gitprompt.sh"
if [ -f "$GIT_PROMPT_INIT" ]; then if [ -f "$GIT_PROMPT_INIT" ]; then
export GIT_PROMPT_PATH export GIT_PROMPT_PATH
GIT_PROMPT_ONLY_IN_REPO=1 GIT_PROMPT_ONLY_IN_REPO=1
GIT_PROMPT_USER_HOST="${LWS_PS1_USER_HOST}"
GIT_PROMPT_START_USER="_LAST_COMMAND_INDICATOR_ ${GIT_PROMPT_USER_HOST}"
GIT_PROMPT_START_ROOT="${GIT_PROMPT_START_USER}"
source "$GIT_PROMPT_INIT" source "$GIT_PROMPT_INIT"
fi fi

View File

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

View File

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