Initial commit: modular bash configuration

Reinitialised repo to purge credential history.
Credential files are now gitignored with .example templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-06 11:57:24 +01:00
commit 4cfec0b336
22 changed files with 364 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Credential files — never commit real secrets
99-claude
99-gemini
99-google
99-huggingface
99-replicate

19
00-credential-guard Executable file
View file

@ -0,0 +1,19 @@
# shellcheck shell=bash
# Helper to warn if a credential file has loose permissions
# require_private <file>
# Emits a warning to stderr if the file is group- or world-readable.
require_private() {
local file="$1"
[[ -f "$file" ]] || return 0
local perms
perms=$(stat -c %a "$file" 2>/dev/null) || {
echo "bash.d: WARNING: cannot check permissions on $file (stat failed)" >&2
return 1
}
# Check that group and other bits are both zero (e.g., 600, 700)
# Uses arithmetic on octal value to handle both 3- and 4-digit modes
if (( (8#$perms) & 8#077 )); then
echo "bash.d: WARNING: $file is group/world-accessible (mode $perms). Run: chmod 600 $file" >&2
fi
}

13
00-path-helper Executable file
View file

@ -0,0 +1,13 @@
# shellcheck shell=bash
# Helper functions to safely modify PATH
# Both check that the directory exists AND prevent duplicates on re-source
# Append directory to PATH (lower priority than existing entries)
path_append() {
[[ -d "$1" ]] && [[ ":$PATH:" != *":$1:"* ]] && PATH="$PATH:$1"
}
# Prepend directory to PATH (higher priority than existing entries)
path_prepend() {
[[ -d "$1" ]] && [[ ":$PATH:" != *":$1:"* ]] && PATH="$1:$PATH"
}

2
10-bun-path Executable file
View file

@ -0,0 +1,2 @@
# shellcheck shell=bash
path_append "$HOME/.bun/bin"

3
10-go-path Executable file
View file

@ -0,0 +1,3 @@
# shellcheck shell=bash
export GOPATH="$HOME/go"
path_append "$GOPATH/bin"

3
10-rust-path Executable file
View file

@ -0,0 +1,3 @@
# shellcheck shell=bash
export CARGO_HOME="$HOME/.cargo"
path_append "$CARGO_HOME/bin"

9
20-ninja Executable file
View file

@ -0,0 +1,9 @@
# shellcheck shell=bash
if command -v ninja &>/dev/null; then
export NINJA_STATUS="[%e sec | %p (%u remaining) | %o / sec] "
export CMAKE_GENERATOR=Ninja
fi
if command -v ccache &>/dev/null; then
export CMAKE_C_COMPILER_LAUNCHER=ccache
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
fi

13
20-oneapi Executable file
View file

@ -0,0 +1,13 @@
# shellcheck shell=bash
# Intel oneAPI toolkit — source the environment setup if installed
# shellcheck disable=SC1090
ONEAPI_SETVARS="/opt/intel/oneapi/setvars.sh"
if [[ -f "$ONEAPI_SETVARS" ]]; then
. "$ONEAPI_SETVARS" --force > /dev/null
if [[ -n "$ONEAPI_ROOT" ]]; then
case ":${LD_LIBRARY_PATH}:" in
*":${ONEAPI_ROOT}/lib:"*) ;;
*) export LD_LIBRARY_PATH="${ONEAPI_ROOT}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" ;;
esac
fi
fi

6
30-starship Executable file
View file

@ -0,0 +1,6 @@
# shellcheck shell=bash
# Initialize Starship prompt
# Requires: starship binary in PATH (installed to ~/.local/bin)
if command -v starship &>/dev/null; then
eval "$(starship init bash)"
fi

3
50-asdf-completion Executable file
View file

@ -0,0 +1,3 @@
# shellcheck shell=bash
# shellcheck disable=SC1090
command -v asdf &>/dev/null && . <(asdf completion bash)

72
50-claude-completion Executable file
View file

@ -0,0 +1,72 @@
# shellcheck shell=bash
# shellcheck disable=SC2207 # Standard pattern for bash completions
# Bash completion for Claude Code CLI
command -v claude &>/dev/null || return
_claude_completions() {
local cur prev opts commands
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# Subcommands
commands="doctor install mcp plugin setup-token update"
# All options
opts="-c -d -h -p -r -v
--add-dir --agent --agents --allow-dangerously-skip-permissions
--allowed-tools --append-system-prompt --betas --chrome --continue
--dangerously-skip-permissions --debug --debug-file --disable-slash-commands
--disallowed-tools --fallback-model --file --fork-session --help --ide
--include-partial-messages --input-format --json-schema --max-budget-usd
--mcp-config --model --no-chrome --no-session-persistence --output-format
--permission-mode --plugin-dir --print --replay-user-messages --resume
--session-id --setting-sources --settings --strict-mcp-config
--system-prompt --tools --verbose --version"
# Handle option arguments
case "${prev}" in
--model|--fallback-model)
COMPREPLY=( $(compgen -W "sonnet opus haiku" -- "${cur}") )
return 0
;;
--permission-mode)
COMPREPLY=( $(compgen -W "acceptEdits bypassPermissions default delegate dontAsk plan" -- "${cur}") )
return 0
;;
--input-format)
COMPREPLY=( $(compgen -W "text stream-json" -- "${cur}") )
return 0
;;
--output-format)
COMPREPLY=( $(compgen -W "text json stream-json" -- "${cur}") )
return 0
;;
--add-dir|--plugin-dir|--debug-file)
COMPREPLY=( $(compgen -d -- "${cur}") )
return 0
;;
--settings|--mcp-config)
COMPREPLY=( $(compgen -f -- "${cur}") )
return 0
;;
install)
COMPREPLY=( $(compgen -W "stable latest" -- "${cur}") )
return 0
;;
esac
# Complete subcommands or options
if [[ ${cur} == -* ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
elif [[ ${COMP_CWORD} -eq 1 ]]; then
COMPREPLY=( $(compgen -W "${commands} ${opts}" -- "${cur}") )
else
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
fi
return 0
}
complete -F _claude_completions claude

3
50-fj-completion Executable file
View file

@ -0,0 +1,3 @@
# shellcheck shell=bash
# shellcheck disable=SC1090
command -v fj &>/dev/null && . <(fj completion bash)

3
50-tailscale-completion Executable file
View file

@ -0,0 +1,3 @@
# shellcheck shell=bash
# shellcheck disable=SC1090
command -v tailscale &>/dev/null && . <(tailscale completion bash)

3
50-uv-completion Executable file
View file

@ -0,0 +1,3 @@
# shellcheck shell=bash
# Enable bash completion for uv (Python package manager)
command -v uv &>/dev/null && eval "$(uv generate-shell-completion bash)"

9
99-android Executable file
View file

@ -0,0 +1,9 @@
# shellcheck shell=bash
if [[ -d "$HOME/android-sdk" ]]; then
export ANDROID_HOME="$HOME/android-sdk"
path_append "$ANDROID_HOME/cmdline-tools/latest/bin"
path_append "$ANDROID_HOME/platform-tools"
fi
if [[ -d /usr/lib/jvm/java-17-openjdk-amd64 ]]; then
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
fi

4
99-claude.example Normal file
View file

@ -0,0 +1,4 @@
# shellcheck shell=bash
# Copy to 99-claude and fill in your token, then: chmod 700 99-claude
require_private "${BASH_SOURCE[0]}"
export FORGEJO_ISSUE_TOKEN_FOR_CLAUDE=your-token-here

4
99-gemini.example Normal file
View file

@ -0,0 +1,4 @@
# shellcheck shell=bash
# Copy to 99-gemini and fill in your key, then: chmod 700 99-gemini
require_private "${BASH_SOURCE[0]}"
export GEMINI_API_KEY=your-key-here

4
99-google.example Normal file
View file

@ -0,0 +1,4 @@
# shellcheck shell=bash
# Copy to 99-google and fill in your key, then: chmod 700 99-google
require_private "${BASH_SOURCE[0]}"
export GOOGLE_API_KEY=your-key-here

4
99-huggingface.example Normal file
View file

@ -0,0 +1,4 @@
# shellcheck shell=bash
# Copy to 99-huggingface and fill in your token, then: chmod 700 99-huggingface
require_private "${BASH_SOURCE[0]}"
export HF_TOKEN=your-token-here

4
99-replicate.example Normal file
View file

@ -0,0 +1,4 @@
# shellcheck shell=bash
# Copy to 99-replicate and fill in your token, then: chmod 700 99-replicate
require_private "${BASH_SOURCE[0]}"
export REPLICATE_API_TOKEN=your-token-here

63
CLAUDE.md Normal file
View file

@ -0,0 +1,63 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
This is a modular bash configuration directory (`~/.bash.d/`). Files here are sourced by `~/.bashrc` to set up the shell environment. Each file handles a specific concern (PATH additions, environment variables, shell completions). See `README.md` for a full description of the directory and its contents.
## File Naming Convention
Files use numeric prefixes to control load order:
- **00-** : Helper functions (loads first — used by other scripts)
- **10-** : PATH configuration (foundational)
- **20-** : Build tool settings (depends on paths)
- **30-** : Shell/prompt setup
- **50-** : Shell completions (named `50-<tool>-completion`)
- **99-** : Application config, credentials (loads last)
## Available Helpers (defined in `00-*`)
Use these in all scripts — do not manipulate `PATH` or check permissions manually:
- `path_append <dir>` — add directory to end of `$PATH` (checks existence, prevents duplicates)
- `path_prepend <dir>` — add directory to start of `$PATH`
- `require_private <file>` — warn if file is group/world-accessible; use in credential files
## Writing Scripts
- Every file must start with `# shellcheck shell=bash`
- Guard external tools with `command -v <tool> &>/dev/null` before using them
- Guard directory-dependent exports with `[[ -d <path> ]]` before exporting
- Do not suppress stderr from external scripts — only redirect stdout when needed
- Avoid `eval` when `. <(cmd)` (process substitution) works
- Prevent `LD_LIBRARY_PATH` duplicates with a `case` guard (see `20-oneapi` for example)
## Permissions
- Directory `~/.bash.d/` itself: mode `700`
- Regular scripts: mode `755` (executable is **required** for sourcing)
- Credential files (`99-*`): mode `700` and must call `require_private "${BASH_SOURCE[0]}"` as the first functional line
## Validation
Validate all shell scripts with shellcheck before committing:
```bash
shellcheck <filename>
```
## Security
Credential files are **excluded from git** via `.gitignore`. Each has a tracked `.example` template with placeholder values.
To set up a credential:
1. Copy the template: `cp 99-foo.example 99-foo`
2. Fill in your real secret
3. Restrict permissions: `chmod 700 99-foo`
When adding new credential files:
- Create a `.example` template (mode `644`) tracked in git
- Add the real filename to `.gitignore`
- Use mode `700` on the real file: `chmod 700 <file>`
- Call `require_private "${BASH_SOURCE[0]}"` as the first functional line
- **Never commit real secrets**
- Be aware that `export`ed tokens are visible to all child processes

114
README.md Normal file
View file

@ -0,0 +1,114 @@
# ~/.bash.d — Modular Bash Configuration
This directory contains modular shell configuration files that are automatically sourced by `~/.bashrc` on every new shell session. Instead of maintaining one monolithic `.bashrc`, each concern lives in its own file — making the setup easier to understand, maintain, and extend.
## How It Works
The `~/.bashrc` iterates over all files in `~/.bash.d/` and sources each one that has the **executable bit** set. Non-executable files are silently skipped, which provides a simple way to temporarily disable a configuration (just `chmod -x` the file).
Files are sourced in **lexicographic order**, so numeric prefixes control the load sequence. This matters because later files may depend on functions or variables defined by earlier ones.
## File Naming Convention
| Prefix | Purpose | Example |
|--------|----------------------------------|---------------------------|
| `00-` | Helper functions (loaded first) | `00-path-helper` |
| `10-` | PATH configuration | `10-go-path`, `10-rust-path` |
| `20-` | Build tool settings | `20-ninja` |
| `30-` | Shell/prompt setup | `30-starship` |
| `50-` | Shell completions | `50-claude-completion` |
| `99-` | Credentials and app config (last)| `99-gemini`, `99-huggingface` |
Lower numbers load first, so foundational pieces like helper functions (`00-`) and PATH entries (`10-`) are available before anything that depends on them.
## Current Files
### Helpers (`00-`)
- **`00-path-helper`** — Defines `path_append` and `path_prepend` functions that safely add directories to `$PATH` (checking the directory exists and avoiding duplicates).
- **`00-credential-guard`** — Defines `require_private`, which warns at shell startup if a credential file has overly permissive permissions (anything beyond owner-only access).
### PATH (`10-`)
- **`10-bun-path`** — Adds Bun (JavaScript runtime) binaries to PATH.
- **`10-go-path`** — Sets `$GOPATH` and adds Go binaries to PATH.
- **`10-rust-path`** — Sets `$CARGO_HOME` and adds Cargo binaries to PATH.
### Build Tools (`20-`)
- **`20-ninja`** — Configures Ninja as the default CMake generator and enables ccache for C/C++ builds (only when installed).
- **`20-oneapi`** — Sources the Intel oneAPI environment and adds its libraries to `LD_LIBRARY_PATH`.
### Prompt (`30-`)
- **`30-starship`** — Initialises the [Starship](https://starship.rs) cross-shell prompt, if installed.
### Completions (`50-`)
- **`50-asdf-completion`** — Tab completion for the asdf version manager.
- **`50-claude-completion`** — Tab completion for [Claude Code](https://claude.com/claude-code) CLI.
- **`50-fj-completion`** — Tab completion for the Forgejo CLI (`fj`).
- **`50-tailscale-completion`** — Tab completion for Tailscale.
- **`50-uv-completion`** — Tab completion for [uv](https://github.com/astral-sh/uv) (Python package manager).
### Credentials & App Config (`99-`)
- **`99-android`** — Sets `$ANDROID_HOME`, `$JAVA_HOME`, and adds Android SDK tools to PATH (only when installed).
- **`99-claude.example`** — Template for Forgejo issue token for Claude Code integrations.
- **`99-gemini.example`** — Template for Gemini API key.
- **`99-google.example`** — Template for Google API key.
- **`99-huggingface.example`** — Template for HuggingFace token (`$HF_TOKEN`).
- **`99-replicate.example`** — Template for Replicate API token.
## Adding a New File
1. Create the file with the appropriate numeric prefix:
```bash
vim ~/.bash.d/50-mytool-completion
```
2. Start the file with the shellcheck directive:
```bash
# shellcheck shell=bash
```
3. Make it executable (required for it to be sourced):
```bash
chmod +x ~/.bash.d/50-mytool-completion
```
4. For credential files, create a `.example` template and the real file:
```bash
# Create the template (tracked in git)
cat > ~/.bash.d/99-mytool.example << 'EOF'
# shellcheck shell=bash
# Copy to 99-mytool and fill in your token, then: chmod 700 99-mytool
require_private "${BASH_SOURCE[0]}"
export MY_SECRET_TOKEN=your-token-here
EOF
# Create the real file from the template
cp ~/.bash.d/99-mytool.example ~/.bash.d/99-mytool
# Edit 99-mytool and fill in your real secret
chmod 700 ~/.bash.d/99-mytool
```
Then add `99-mytool` to `.gitignore`.
5. Validate with shellcheck:
```bash
shellcheck ~/.bash.d/50-mytool-completion
```
## Security
Credential files (`99-*`) are **excluded from git** via `.gitignore` and are never committed. Each credential has a tracked `.example` template with placeholder values. To set up credentials:
1. Copy the template: `cp 99-foo.example 99-foo`
2. Fill in your real secret
3. Restrict permissions: `chmod 700 99-foo`
Additional protections:
- **File permissions** — Credential files use mode `700` (owner-only).
- **Runtime checks** — The `require_private` helper warns on shell startup if a credential file has been accidentally loosened.
- **Never commit real secrets** — Only `.example` templates are tracked in git.