diff --git a/.gitignore b/.gitignore index 1ef2952..49eeee5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ cache/ -macos/Brewfile.lock.json +macos/*.lock.json diff --git a/Makefile b/Makefile index 76feb3e..219451b 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,14 @@ SHELL := /bin/bash OS := $(shell uname -s) -GIT := git -HATCH := hatch -.PHONY: help -help: - @echo "Usage: make [TARGET] ..." - @echo "" - @echo "Targets:" - @echo " bootstrap Initialize the project by running the bootstrap script." - @echo " deps Install dependencies for the project based on the OS." - @echo " docs Serve the documentation locally." - @echo " fmt Run pre-commit hooks on all files." - @echo " help Show this help message." - @echo " sync Update the project and its submodules." +##@ dotfiles .PHONY: bootstrap -bootstrap: +bootstrap: ## Initialize the project by running the bootstrap script. $(SHELL) ./bootstrap/bootstrap.sh .PHONY: deps -deps: +deps: ## Install dependencies for the project based on the OS. ifeq ($(OS),Linux) $(SHELL) ./bin/aptfile ./linux/Aptfile else ifeq ($(OS),Darwin) @@ -30,13 +18,31 @@ else endif .PHONY: sync -sync: - $(GIT) pull --recurse-submodules --jobs=4 +sync: ## Update the project and its submodules. + git pull --recurse-submodules --jobs=4 + +##@ development .PHONY: fmt -fmt: +fmt: ## Run pre-commit hooks on all files. pre-commit run --all-files .PHONY: docs -docs: - $(HATCH) run docs:serve --livereload +docs: ## Serve the documentation locally. + hatch run docs:serve --livereload + +##@ general + +.PHONY: version +version: ## Show the version of the project. + @echo "dotfiles $$(git describe --tags --abbrev=0)" + +.DEFAULT_GOAL := help +.PHONY: help +help: ## Show this help message and exit. +################################################ +# Auto-Generated Help: +# - "##@" denotes a target category +# - "##" denotes a specific target description +############################################### + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/bin/now b/bin/now new file mode 100755 index 0000000..995f7c7 --- /dev/null +++ b/bin/now @@ -0,0 +1,41 @@ +#!/bin/bash + +# +# See what 'read -t' returns on timeout. +# +# For that - read from stdout. Alternatively, it could read +# from /dev/zero, but it's not available under Cygwin and +# in other non-*nix environments. +# +# Also note, that while bash manual states that the return +# code on a timeout is greater than 128, it doesn't hold +# true with a number of bash implementations, most notably +# including GNU bash that returns 1 instead. +# +read -t 1 <&1 +timeout=$? + +# +# loop forever +# +while true; do + # + # relay stdin to stdout + # + while true; do + IFS= read -r -t 1 line + rc=$? + if [ $rc != 0 ]; then break; fi + echo "$line" + done + # + # exit status is greater than 128 if the timeout is exceeded + # + if [ $rc != $timeout ]; then break; fi + + # + # print the timestamp + # + now=$(date ${1:++"$1"}) + echo -ne "$now\r" >&2 +done diff --git a/bootstrap/bootstrap.sh b/bootstrap/bootstrap.sh index f47f157..b860727 100755 --- a/bootstrap/bootstrap.sh +++ b/bootstrap/bootstrap.sh @@ -185,16 +185,22 @@ function log_event() { LOGGING_TIMESTAMP="${BLUE}$(date +"%F %T,000")${NO_COLOR}" case "${1}" in "info") - echo -e "${LOGGING_TIMESTAMP} ${GREEN}INFO: ${NO_COLOR}${2}" + echo -e "${LOGGING_TIMESTAMP} ${GREEN}[ INFO]: ${NO_COLOR}${2}" ;; "error") - echo -e "${LOGGING_TIMESTAMP} ${RED}ERROR: ${NO_COLOR}${2}" + echo -e "${LOGGING_TIMESTAMP} ${RED}[ ERROR]: ${NO_COLOR}${2}" ;; "warning") - echo -e "${LOGGING_TIMESTAMP} ${ORANGE}WARNING: ${NO_COLOR}${2}" + echo -e "${LOGGING_TIMESTAMP} ${ORANGE}[ WARNING]: ${NO_COLOR}${2}" + ;; + "debug") + echo -e "${LOGGING_TIMESTAMP} ${PURPLE}[ DEBUG]: ${NO_COLOR}${2}" + ;; + "critical") + echo -e "${LOGGING_TIMESTAMP} ${RED}[CRITICAL]: ${NO_COLOR}${2}" ;; *) - echo -e "${LOGGING_TIMESTAMP} ${PURPLE}${1}: ${NO_COLOR}${2}" + echo -e "${LOGGING_TIMESTAMP} ${GREEN}[ INFO]: ${NO_COLOR}${1}" ;; esac } @@ -267,12 +273,7 @@ function symlink_item() { function symlink_shell() { symlink_item "${DOTFILES_DIR}/shell/.shell_aliases" "${HOME}/.shell_aliases" -} - -function symlink_mac() { - if [[ $(uname) == "Darwin" ]]; then - symlink_item "${DOTFILES_DIR}/shell/.mac_aliases" "${HOME}/.mac_aliases" - fi + symlink_item "${DOTFILES_DIR}/shell/.shell_functions" "${HOME}/.shell_functions" } function symlink_zsh() { @@ -306,6 +307,7 @@ function symlink_misc() { function symlink_bin() { symlink_item "${DOTFILES_DIR}/bin/has" "/usr/local/bin/has" + symlink_item "${DOTFILES_DIR}/bin/now" "/usr/local/bin/now" if [[ $(uname) == "Linux" ]]; then symlink_item "${DOTFILES_DIR}/bin/aptfile" "/usr/local/bin/aptfile" fi @@ -318,7 +320,6 @@ function symlink_tools() { function symlink_dotfiles() { symlink_shell - symlink_mac symlink_zsh symlink_bash symlink_git diff --git a/docs/installation.md b/docs/installation.md index a4cb25e..7e71892 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -67,7 +67,7 @@ the steps below: ln -s ~/.dotfiles/shell/.profile ~/.profile # SHELL ln -s ~/.dotfiles/shell/.shell_aliases ~/.shell_aliases - ln -s ~/.dotfiles/shell/.mac_aliases ~/.mac_aliases + ln -s ~/.dotfiles/shell/.shell_functions ~/.shell_functions # PYENV ln -s ~/.dotfiles/bootstrap/pyenv ~/.pyenv # GIT diff --git a/macos/Brewfile b/macos/Brewfile index 740b86a..3139515 100644 --- a/macos/Brewfile +++ b/macos/Brewfile @@ -6,12 +6,17 @@ tap "hashicorp/tap" tap "homebrew/bundle" tap "homebrew/cask-fonts" tap "homebrew/services" +tap "derailed/k9s" ########################################################## ####################### UTILITIES ######################## ########################################################## +brew "asdf" brew "awsume" +brew "aws-sso-util" +brew "ca-certificates" +brew "checkov" brew "curl" brew "direnv" brew "ffmpeg" @@ -26,14 +31,18 @@ brew "helm" brew "htop" brew "hugo" brew "jq" +brew "k9s" brew "make" brew "neovim" brew "node" brew "openjdk" brew "pipx" +brew "pre-commit" brew "ripgrep" +brew "sops" brew "terraform" brew "terragrunt" +brew "tmate" brew "thefuck" brew "tree" brew "wget" @@ -50,8 +59,9 @@ cask "docker" cask "hammerspoon" cask "iterm2" cask "lens" +cask "ngrok" cask "spotify" -cask "syntax-highlight" +cask "syntax-highlight", args: { no_quarantine: true } cask "vlc" ########################################################## diff --git a/macos/XBrewfile b/macos/XBrewfile new file mode 100644 index 0000000..e8db3a4 --- /dev/null +++ b/macos/XBrewfile @@ -0,0 +1,15 @@ +########################################################## +######################### TAPS ########################### +########################################################## + +tap "homebrew/bundle" + +########################################################## +######################## PYTHON ########################## +########################################################## + +brew "python@3.8" +brew "python@3.9" +brew "python@3.10" +brew "python@3.11" +brew "python@3.12" diff --git a/shell/.mac_aliases b/shell/.mac_aliases deleted file mode 100644 index d5d9fad..0000000 --- a/shell/.mac_aliases +++ /dev/null @@ -1,18 +0,0 @@ -# notifications for when script finishes: -function notify() { - NOTIFICATION_MESSAGE=${1:-Alert} - NOTIFICATION_TITLE=${2:-Terminal Notification} - NOTIFICATION_SCRIPT_PREFACE="osascript -e " - NOTIFICATION_SCRIPT_SUFFIX='display notification "'${NOTIFICATION_MESSAGE}'" with title "'${NOTIFICATION_TITLE}'" sound name "Submarine"' - NOTIFICATION_SCRIPT=${NOTIFICATION_SCRIPT_PREFACE}"'${NOTIFICATION_SCRIPT_SUFFIX}'" - eval ${NOTIFICATION_SCRIPT} -} - -# x86_64 Homebrew for Apple Silicon -alias xbrew="arch -x86_64 /usr/local/bin/brew" - -alias xpython3.12="$(xbrew --prefix python@3.12)/bin/python3.12" -alias xpython3.11="$(xbrew --prefix python@3.11)/bin/python3.11" -alias xpython3.10="$(xbrew --prefix python@3.10)/bin/python3.10" -alias xpython3.9="$(xbrew --prefix python@3.9)/bin/python3.9" -alias xpython3.8="$(xbrew --prefix python@3.8)/bin/python3.8" diff --git a/shell/.shell_aliases b/shell/.shell_aliases index dddf422..c523667 100644 --- a/shell/.shell_aliases +++ b/shell/.shell_aliases @@ -7,137 +7,22 @@ alias cls="clear" alias ls="ls -a -G -F --color=auto" alias ll='ls -l -A -F -h --color=auto' -DOTFILES_DIR="${DOTFILES_DIR:-${HOME}/.dotfiles}" - -function dotfiles-git() { - local GIT_COMMAND="git ${@}" - pushd "${DOTFILES_DIR}" &>/dev/null || { - echo "Error: Failed to change to ${DOTFILES_DIR}." - return 1 - } - log_event "info" "${PURPLE}[${DOTFILES_DIR}]${NO_COLOR} ${CYAN}${GIT_COMMAND}${NO_COLOR} 📂" - git "${@}" || { - echo "Error: git command failed." - popd &>/dev/null - return 1 - } - popd &>/dev/null || echo "Warning: Failed to return to previous directory." -} - -function dotfiles-sync() { - log_event "info" "Syncing dotfiles 🔄" - dotfiles-git pull --recurse-submodules --jobs=4 - log_event "info" "Dotfiles synced successfully 🎉" -} - -function dotfiles-bootstrap() { - bash "${DOTFILES_DIR}/bootstrap/bootstrap.sh" -} - -function dotfiles-deps() { - OS_NAME=$(uname -s) - if [[ ${OS_NAME} == "Darwin" ]]; then - if ! command -v brew &>/dev/null; then - echo "Error: Homebrew is not installed." - return 1 - fi - log_event "info" "Installing packages from Brewfile 📦" - brew bundle --file="${DOTFILES_DIR}/macos/Brewfile" - elif [[ ${OS_NAME} == "Linux" ]]; then - if ! command -v apt &>/dev/null; then - echo "Error: apt is not installed." - return 1 - fi - log_event "info" "Installing packages from Aptfile 📦" - bash "${DOTFILES_DIR}/bin/aptfile" "${DOTFILES_DIR}/linux/Aptfile" - else - log_event "error" "Unsupported OS: ${OS_NAME}" - return 1 - fi - log_event "info" "Dependencies installed successfully 🎉" -} - -NO_COLOR='\033[0m' -BLUE='\033[0;34m' -GREEN='\033[0;32m' -RED='\033[0;31m' -ORANGE='\033[0;33m' -PURPLE='\033[0;35m' - -# Logging -function log_event() { - LOGGING_TIMESTAMP="${BLUE}$(date +"%F %T,000")${NO_COLOR}" - case "${1}" in - "info") - echo -e "${LOGGING_TIMESTAMP} ${GREEN}INFO: ${NO_COLOR}${2}" - ;; - "error") - echo -e "${LOGGING_TIMESTAMP} ${RED}ERROR: ${NO_COLOR}${2}" - ;; - "warning") - echo -e "${LOGGING_TIMESTAMP} ${ORANGE}WARNING: ${NO_COLOR}${2}" - ;; - *) - echo -e "${LOGGING_TIMESTAMP} ${PURPLE}${1}: ${NO_COLOR}${2}" - ;; - esac -} - -# shallow clone from GitHub -function install_from_github() { - local repo=$1 - local target=$2 - if [[ ! -d ${target} ]]; then - log_event "info" "Cloning ${PURPLE}${repo}${NO_COLOR} from GitHub: ${GREEN}${target}${NO_COLOR} 🗂️" - git clone -q --depth=1 https://github.com/${repo}.git ${target} && - log_event "info" "Installation of ${PURPLE}${repo}${NO_COLOR} successful 📪" || - log_event "error" "Installation of ${PURPLE}${repo}${NO_COLOR} failed 🚫" - fi -} - -# file search functions -function f() { - find . -iname "*$1*" ${@:2} -} -function r() { - grep "$1" ${@:2} -R . -} - -# Create a folder and move into it in one command -function mkcd() { - mkdir -p "$@" && cd "$_" -} - -# check out a branch -function check() { - git checkout $1 -} - -# remove local versions of deleted remote branches -function git-remove-deleted() { - git fetch -p - for branch in $(git branch -vv | grep ': gone]' | awk '{print $1}'); do - git branch -D $branch - done -} - -function git-tree() { - tree -C -I $((cat .gitignore 2> /dev/null || cat $(git rev-parse --show-toplevel 2> /dev/null)/.gitignore 2> /dev/null || echo "node_modules") | egrep -v "^#.*$|^[[:space:]]*$" | tr "\\n" "|" | rev | cut -c 2- | rev) -} - # verbose `git status` alias alias status="git status --verbose --ahead-behind --branch --find-renames" -# AWS 🤝 Docker -function aws-docker-login() { - AWS_DOCKER_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) - aws ecr get-login-password \ - --region us-east-1 | - docker login \ - --username AWS \ - --password-stdin ${AWS_DOCKER_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com -} +########################################################## +##################### MAC ALIASES ######################## +########################################################## if [[ ${OSTYPE} == "darwin"* ]]; then - [[ ! -f ${DOTFILES_DIR}/shell/mac_aliases.zsh ]] || source ${DOTFILES_DIR}/shell/mac_aliases.zsh + ###################################################### + ################### X86 ALIASES ###################### + ###################################################### + alias xbrew="arch -x86_64 /usr/local/bin/brew" + alias xpython3.12="$(xbrew --prefix python@3.12)/bin/python3.12" + alias xpython3.11="$(xbrew --prefix python@3.11)/bin/python3.11" + alias xpython3.10="$(xbrew --prefix python@3.10)/bin/python3.10" + alias xpython3.9="$(xbrew --prefix python@3.9)/bin/python3.9" + alias xpython3.8="$(xbrew --prefix python@3.8)/bin/python3.8" + ###################################################### fi diff --git a/shell/.shell_functions b/shell/.shell_functions new file mode 100644 index 0000000..2294ea0 --- /dev/null +++ b/shell/.shell_functions @@ -0,0 +1,255 @@ +########################################################## +####################### DOTFILES ######################### +########################################################## + +DOTFILES_DIR="${DOTFILES_DIR:-${HOME}/.dotfiles}" + +############################################# +# dotfiles-git +# ------------------------------------------- +# Run a git command in the dotfiles directory +############################################# +function dotfiles-git() { + local GIT_COMMAND="git ${@}" + pushd "${DOTFILES_DIR}" &>/dev/null || { + echo "Error: Failed to change to ${DOTFILES_DIR}." + return 1 + } + log_event "info" "${PURPLE}[${DOTFILES_DIR}]${NO_COLOR} ${CYAN}${GIT_COMMAND}${NO_COLOR} 📂" + git "${@}" || { + echo "Error: git command failed." + popd &>/dev/null + return 1 + } + popd &>/dev/null || echo "Warning: Failed to return to previous directory." +} + +############################################# +# dotfiles-sync +# ------------------------------------------- +# Sync the dotfiles +############################################# +function dotfiles-sync() { + log_event "info" "Syncing dotfiles 🔄" + dotfiles-git pull --recurse-submodules --jobs=4 + log_event "info" "Dotfiles synced successfully 🎉" +} + +############################################# +# dotfiles-bootstrap +# ------------------------------------------- +# Bootstrap the dotfiles +############################################# +function dotfiles-bootstrap() { + bash "${DOTFILES_DIR}/bootstrap/bootstrap.sh" +} + +############################################# +# dotfiles-deps +# ------------------------------------------- +# Install dependencies from Brewfile or Aptfile +############################################# +function dotfiles-deps() { + OS_NAME=$(uname -s) + if [[ ${OS_NAME} == "Darwin" ]]; then + if ! command -v brew &>/dev/null; then + echo "Error: Homebrew is not installed." + return 1 + fi + log_event "info" "Installing packages from Brewfile 📦" + brew bundle --file="${DOTFILES_DIR}/macos/Brewfile" + elif [[ ${OS_NAME} == "Linux" ]]; then + if ! command -v apt &>/dev/null; then + echo "Error: apt is not installed." + return 1 + fi + log_event "info" "Installing packages from Aptfile 📦" + bash "${DOTFILES_DIR}/bin/aptfile" "${DOTFILES_DIR}/linux/Aptfile" + else + log_event "error" "Unsupported OS: ${OS_NAME}" + return 1 + fi + log_event "info" "Dependencies installed successfully 🎉" +} + +########################################################## +######################### SEARCH ######################### +########################################################## + +############################################# +# f +# ------------------------------------------- +# Find a file by name +############################################# +function f() { + find . -iname "*$1*" ${@:2} +} + +############################################# +# r +# ------------------------------------------- +# Grep recursively +############################################# +function r() { + grep "$1" ${@:2} -R . +} + +########################################################## +###################### DIRECTORIES ####################### +########################################################## + +############################################# +# mkcd +# ------------------------------------------- +# Create a directory and change to it +############################################# +function mkcd() { + mkdir -p "$@" && cd "$_" +} + +############################################# +# mktmpdir +# ------------------------------------------- +# Create a temporary directory +############################################# +function mktmpdir() { + test -z "$TMPDIR" && TMPDIR="$(mktemp -d)" + mkdir -p "${TMPDIR}" + echo "${TMPDIR}" +} + +########################################################## +########################## GIT ########################### +########################################################## + +############################################# +# check +# ------------------------------------------- +# Checkout a branch +############################################# +function check() { + git checkout $1 +} + +############################################# +# git-remove-deleted +# ------------------------------------------- +# Remove branches that have been deleted +############################################# +function git-remove-deleted() { + git fetch -p + for branch in $(git branch -vv | grep ': gone]' | awk '{print $1}'); do + git branch -D $branch + done +} + +############################################# +# git-tree +# ------------------------------------------- +# Display a tree considering .gitignore +############################################# +function git-tree() { + tree -C -I $((cat .gitignore 2> /dev/null || cat $(git rev-parse --show-toplevel 2> /dev/null)/.gitignore 2> /dev/null || echo "node_modules") | egrep -v "^#.*$|^[[:space:]]*$" | tr "\\n" "|" | rev | cut -c 2- | rev) +} + +########################################################## +######################### MISC ########################### +########################################################## + +############################################# +# is_command +# ------------------------------------------- +# Check if a command exists +############################################# +is_command() { + command -v "$1" >/dev/null +} + +############################################# +# date_iso8601 +# ------------------------------------------- +# Generate an ISO8601 formatted date string +############################################# +date_iso8601() { + date -u +%Y-%m-%dT%H:%M:%S+0000 +} + +############################################# +# hash_sha256 +# ------------------------------------------- +# Generate a SHA-256 hash of a file, string, or stdin +# If no input is provided, generate a hash +############################################# +hash_sha256() { + if [ -p /dev/stdin ]; then + cat - | shasum -a 256 | awk '{print $1}' + elif [ -n "$1" ]; then + if [ -f "$1" ]; then + shasum -a 256 "$1" | awk '{print $1}' + else + printf '%s' "$1" | shasum -a 256 | awk '{print $1}' + fi + else + printf '%s' "$(date +%s)$(openssl rand -hex 12)" | shasum -a 256 | awk '{print $1}' + fi +} + + +########################################################## +######################### AWS ############################ +########################################################## + +############################################# +# aws-docker-login +# ------------------------------------------- +# Login to AWS ECR +############################################# +function aws-docker-login() { + AWS_DOCKER_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + aws ecr get-login-password \ + --region us-east-1 | + docker login \ + --username AWS \ + --password-stdin ${AWS_DOCKER_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com +} + +########################################################## +####################### LOGGING ########################## +########################################################## + +NO_COLOR='\033[0m' +BLUE='\033[0;34m' +GREEN='\033[0;32m' +RED='\033[0;31m' +ORANGE='\033[0;33m' +PURPLE='\033[0;35m' +CYAN='\033[0;36m' + +############################################# +# log_event +# ------------------------------------------- +# Log an event +############################################# +function log_event() { + LOGGING_TIMESTAMP="${BLUE}$(date +"%F %T,000")${NO_COLOR}" + case "${1}" in + "info") + echo -e "${LOGGING_TIMESTAMP} ${GREEN}[ INFO]: ${NO_COLOR}${2}" + ;; + "error") + echo -e "${LOGGING_TIMESTAMP} ${RED}[ ERROR]: ${NO_COLOR}${2}" + ;; + "warning") + echo -e "${LOGGING_TIMESTAMP} ${ORANGE}[ WARNING]: ${NO_COLOR}${2}" + ;; + "debug") + echo -e "${LOGGING_TIMESTAMP} ${PURPLE}[ DEBUG]: ${NO_COLOR}${2}" + ;; + "critical") + echo -e "${LOGGING_TIMESTAMP} ${CYAN}[CRITICAL]: ${NO_COLOR}${2}" + ;; + *) + echo -e "${LOGGING_TIMESTAMP} ${GREEN}[ INFO]: ${NO_COLOR}${1}" + ;; + esac +} diff --git a/shell/.zshrc b/shell/.zshrc index c2e2089..1948b8f 100644 --- a/shell/.zshrc +++ b/shell/.zshrc @@ -87,7 +87,7 @@ setopt inc_append_history # Add commands to the history file immediately. [[ ! -f ${HOME}/.zshenv ]] || source ${HOME}/.zshenv # Aliases [[ ! -f ${HOME}/.shell_aliases ]] || source ${HOME}/.shell_aliases -[[ ! -f ${HOME}/.mac_aliases ]] || source ${HOME}/.mac_aliases +[[ ! -f ${HOME}/.shell_functions ]] || source ${HOME}/.shell_functions [[ ! -f ${HOME}/.zsh_aliases ]] || source ${HOME}/.zsh_aliases ##########################################################