Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Shell Lint

on:
push:
paths:
- '**.sh'
- '.github/workflows/shellcheck.yml'
pull_request:
paths:
- '**.sh'
- '.github/workflows/shellcheck.yml'

jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install ShellCheck
run: sudo apt-get update -y && sudo apt-get install -y shellcheck

- name: Run ShellCheck
run: |
set -euo pipefail
echo "Scanning shell scripts..."
# Find all tracked *.sh files plus executable scripts with bash shebang
SCRIPTS=$(git ls-files '*.sh')
# Optionally include non-.sh executables with bash shebang
while IFS= read -r f; do
if head -1 "$f" | grep -qE '^#!.*bash'; then
SCRIPTS+=$'\n'$f
fi
done < <(git ls-files | grep -v '\.sh$')

# De-duplicate list
echo "$SCRIPTS" | sort -u > /tmp/scripts.list
echo "Files to check:"; cat /tmp/scripts.list
# Run shellcheck (excluding vendor or 3rd-party dirs if any in future)
xargs -a /tmp/scripts.list -r shellcheck --severity=style

- name: Upload SARIF (optional code scanning)
if: false
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Dotfiles

[![Shell Lint](https://github.com/JoannaaKL/dotfiles/actions/workflows/shellcheck.yml/badge.svg)](https://github.com/JoannaaKL/dotfiles/actions/workflows/shellcheck.yml)

Opinionated workstation + Codespaces bootstrap.

## Features

- Idempotent `install.sh` with:
- Environment detection (local vs Codespaces)
- Neovim install + minimal config bootstrap
- Spaceship ZSH theme setup
- Meslo Nerd Font install (skips if present)
- Safe dotfile symlinking (backs up existing files)
- Optional GitHub Copilot CLI extension
- Delta (syntax highlighted pager) install
- Managed Codespaces SSH config export (`export_codespace_cfg`)
- Codespace cleanup utility (`clean-codespaces.sh --dry-run`)
- Shell linting via GitHub Actions + local `make lint`.

## Quick Start

Clone then run:

```bash
./install.sh
```

Start a new shell (or `source ~/.zshrc`) to pick up changes.

## Linting Shell Scripts

Run locally:

```bash
make lint
```

The CI workflow runs automatically on pushes and pull requests touching any `*.sh` files.

## Codespaces SSH Config Export

```bash
./export_codespace_cfg
```

This inserts/updates a managed block in `~/.ssh/config` between markers:

```text
# >>> codespaces (managed) >>>
# <<< codespaces (managed) <<<
```

## Cleaning Shutdown Codespaces

Preview:

```bash
./clean-codespaces.sh --dry-run
```

Execute deletions:

```bash
./clean-codespaces.sh
```

## Future Ideas

- Brewfile / apt manifest abstraction
- Tmux plugin manager integration
- Advanced Neovim config module
39 changes: 31 additions & 8 deletions clean-codespaces.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

clean(){
items=$(gh codespace list --json name,state | jq -c -r '.[] | select(.state | contains("Shutdown")) | .name')
for i in $items; do
echo "Deleting codespace $i"
result=$(gh codespace delete -c $i -f)
done
}
clean
command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 1; }
command -v gh >/dev/null 2>&1 || { echo "GitHub CLI (gh) is required" >&2; exit 1; }

dry_run=false
if [[ "${1:-}" == "--dry-run" ]]; then
dry_run=true
fi

names=$(gh codespace list --json name,state | jq -r '.[] | select(.state=="Shutdown") | .name')
if [[ -z "$names" ]]; then
echo "No shutdown codespaces to delete"
exit 0
fi

echo "Found shutdown codespaces:" >&2
# shellcheck disable=SC2001
echo "${names}" | sed 's/^/ - /'

for name in $names; do
if $dry_run; then
echo "[dry-run] Would delete: $name"
else
echo "Deleting codespace: $name"
if ! gh codespace delete -c "$name" -f; then
echo "Failed to delete $name" >&2
fi
fi
done
41 changes: 33 additions & 8 deletions export_codespace_cfg
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
echo "Remove old codespace_config"
rm -rf codespace_config
echo "Remove old entries from .ssh/config"
less ~/.ssh/config_backup > ~/.ssh/config
echo "Save ssh config for available codespaces"
gh codespace ssh --config | sed -e 's/ -- -i \/Users\/joannaakl\/.ssh\/codespaces.auto//g; s/IdentityFile \/Users\/joannaakl\/.ssh\/codespaces.auto//g' > codespace_config
echo "Save cleaned codespaces ssh config in .ssh/config"
cat codespace_config >> ~/.ssh/config
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

command -v gh >/dev/null 2>&1 || { echo "gh CLI required" >&2; exit 1; }

SSH_DIR="$HOME/.ssh"
CONFIG_FILE="$SSH_DIR/config"
BACKUP="$CONFIG_FILE.$(date +%Y%m%d%H%M%S).bak"
MANAGED_START="# >>> codespaces (managed) >>>"
MANAGED_END="# <<< codespaces (managed) <<<"

mkdir -p "$SSH_DIR"
[[ -f "$CONFIG_FILE" ]] && cp "$CONFIG_FILE" "$BACKUP"

echo "Generating Codespaces SSH config block..." >&2
TMP=$(mktemp)
gh codespace ssh --config | sed -E 's@ -- -i [^ ]*codespaces\.auto@@g; s@IdentityFile [^ ]*codespaces\.auto@@g' > "$TMP"

# Remove previous managed block if present
if grep -q "$MANAGED_START" "$CONFIG_FILE" 2>/dev/null; then
awk -v start="$MANAGED_START" -v end="$MANAGED_END" 'BEGIN{skip=0} {if($0==start){skip=1} else if($0==end){skip=0; next} if(!skip) print}' "$CONFIG_FILE" > "$CONFIG_FILE.tmp"
mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
fi

{
echo "$MANAGED_START"
cat "$TMP"
echo "$MANAGED_END"
} >> "$CONFIG_FILE"

rm -f "$TMP"
echo "Updated $CONFIG_FILE (backup: $BACKUP)" >&2
83 changes: 47 additions & 36 deletions helpers.sh
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# Helper functions for dotfiles installation

# Constants
ALREADY_INSTALLED_MSG="is already installed"

codespaces_setup(){
echo "***Codespaces setup***"
merge_zsh_config
install "tmux" "apt install tmux"
install "bat" "apt install bat"
install "rg" "apt-get install ripgrep"
codespaces_install_shellcheck
echo "***Codespaces setup done***"
}
log_helpers(){ printf "[helpers] %s\n" "$*"; }

local_setup(){
echo "***Local setup***"
install "tmux" "brew install tmux"
install "bat" "brew install bat"
install "rg" "brew install ripgrep"
local_install_shellcheck
echo "***Local setup done***"
# Generic install wrapper: install_if_missing <binary> <command...>
install_if_missing(){
local bin="$1"; shift
if command -v "$bin" >/dev/null 2>&1; then
log_helpers "$bin $ALREADY_INSTALLED_MSG"
return 0
fi
log_helpers "Installing $bin"
"$@"
}

codespaces_install_shellcheck() {
scversion="latest"
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv
cp "shellcheck-${scversion}/shellcheck" /usr/bin/
codespaces_install_shellcheck(){
local scversion="v0.10.0" # pinned for reproducibility
if command -v shellcheck >/dev/null 2>&1; then
log_helpers "shellcheck $ALREADY_INSTALLED_MSG"; return 0; fi
local url="https://github.com/koalaman/shellcheck/releases/download/${scversion}/shellcheck-${scversion}.linux.x86_64.tar.xz"
local tmpdir
tmpdir="$(mktemp -d)"
curl -fsSL "$url" | tar -xJ -C "$tmpdir"
sudo install -m 0755 "$tmpdir/shellcheck-${scversion}/shellcheck" /usr/local/bin/shellcheck
shellcheck --version
}

local_install_shellcheck() {
brew install shellcheck
}

install(){
pkg_name=$1
cmd_if_not_installed=$2
if command -v "$pkg_name" &> /dev/null
then
echo "$pkg_name $ALREADY_INSTALLED_MSG"
local_install_shellcheck(){
if command -v shellcheck >/dev/null 2>&1; then
log_helpers "shellcheck $ALREADY_INSTALLED_MSG"; return 0; fi
if command -v brew >/dev/null 2>&1; then
brew install shellcheck
else
echo "$pkg_name is not installed, installing..."
eval "$cmd_if_not_installed"
log_helpers "brew not found; skipping shellcheck"
fi
}

codespaces_setup(){
log_helpers "*** Codespaces setup ***"
install_if_missing tmux bash -c 'sudo apt-get update -y && sudo apt-get install -y tmux'
install_if_missing bat sudo apt-get install -y bat
install_if_missing rg sudo apt-get install -y ripgrep
codespaces_install_shellcheck
log_helpers "*** Codespaces setup done ***"
}

local_setup(){
log_helpers "*** Local setup ***"
install_if_missing tmux brew install tmux
install_if_missing bat brew install bat
install_if_missing rg brew install ripgrep
local_install_shellcheck
log_helpers "*** Local setup done ***"
}

setup(){
if [ -n "$CODESPACES" ]; then
if [[ -n "${CODESPACES:-}" ]]; then
codespaces_setup
else
local_setup
Expand Down
Loading
Loading