The first thing that must be done in order to set up a new machine is to
configure SSH and create a key. This allows for cloning of the git
repo that contains my configuration files, which is hosted on GitHub.
First, create the SSH directory:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
Optionally, I can also set up an authorized_keys
file, which is useful
if the new machine is intended to be used as a server in any capacity.
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Next, I need to actually create a new SSH key, which can be done with:
ssh-keygen -t rsa -b 4096 -C "COMMENT GOES HERE"
Then, I can set up a basic SSH configuration file to ensure that the key
that I just generated is used when trying to connect to GitHub as well
as my private git
server, which contains some of the submodules in
this repository. (Note that this basic SSH configuration file will be
overwritten later in this new machine configuration process.)
This allows repositories to be cloned in either of the following manners:
git clone ssh://github/adamliter/conf.git
git clone github:adamliter/conf.git
cat > ~/.ssh/config <<EOF
Host github
Hostname github.com
IdentityFile ~/.ssh/id_rsa
User git
Host git-adamliter
Hostname git.adamliter.org
IdentityFile ~/.ssh/id_rsa
User git
EOF
chmod 600 ~/.ssh/config
Finally, the public key needs to be added to GitHub and to
/home/git/.ssh/authorized_keys
on the machine git.adamliter.org
. (It
will also eventually need to be added to GitLab and Bitbucket.)
To bootstrap the setup of a new machine, use the bootstrap.sh
script,
described below. It can be downloaded from GitHub with either curl
or
wget
:
With curl
:
curl -fsSL https://raw.githubusercontent.com/adamliter/conf/master/bootstrap.sh | bash -v
With wget
:
wget -O- https://raw.githubusercontent.com/adamliter/conf/master/bootstrap.sh | bash -v
This section defines a bootstrap script for installing Homebrew, Emacs, Doom Emacs, and a variety of other things.
That script is written to this repository when this file is tangled. The
output of the tangling is not ignored by version control even though it
is a derived file because tracking it allows a curl
-able version of
the file to exist on GitHub for bootstrapping the setup of a new
machine.
The first thing the script does is set some flags for exiting and debugging:
# -*- coding: utf-8; mode: sh; -*-
# Exit when a command fails
set -o errexit
# Exit when a command in a series of pipes fails
set -o pipefail
# Exit when there is an undeclared variable
set -o nounset
# Trace what gets executed (for debugging)
#set -o xtrace
Let’s run the rest of the script from the home directory:
cd "${HOME}"
Next, let’s have the script detect the operating system, including whether it’s a Mac with an Intel chip or an Apple silicon chip.
if [[ $OSTYPE == darwin* ]]; then
BASH_OS_TYPE='macOS'
elif [[ $OSTYPE == linux-gnu ]]; then
BASH_OS_TYPE='Linux'
echo "This bootstrapping script does not currently support Linux ..."
exit 1
elif [[ '$OS' == Windows* ]]; then
BASH_OS_TYPE='Windows'
echo "This bootstrapping script does not currently support Windows ..."
exit 1
else
BASH_OS_TYPE='Unknown'
echo "Unknown operating system ..."
exit 1
fi
if [[ $BASH_OS_TYPE == 'macOS' ]]; then
if [[ $(uname -m) == arm* ]]; then
MAC_OS_TYPE='apple-silicon'
elif [[ $(uname -m) == x86_64 ]]; then
MAC_OS_TYPE='intel'
else
MAC_OS_TYPE='Unknown'
fi
fi
Let’s install Homebrew as a package manager.
if [[ $BASH_OS_TYPE == macOS ]]; then
if type brew >/dev/null 2>&1; then
echo "Homebrew is already installed ..."
else
echo "Installing Homebrew ..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo "Homebrew was successfully installed ..."
if [[ $MAC_OS_TYPE == 'apple-silicon' ]]; then
echo "Temporarily adding Homebrew to PATH for Apple Silicon Mac ..."
eval $(/opt/homebrew/bin/brew shellenv)
fi
echo "Tapping ralwaycat/emacsport ..."
brew tap railwaycat/emacsmacport
fi
fi
Next, the script clones the repository that contains my configuration
files into ${HOME}/conf
. If that directory already exists, the git
clone
command will fail, so I remove it first just to be cautious, but
this should be unnecessary if this is a new machine.
If this is not a new machine, the command rm -rf "${HOME}/conf"
will
remove the file ~/.ssh/config
, since that file is a symlink that
points inside of the git
repository. This will cause the
bootstrap.sh
script to fail since the submodules in the repository
make use of the SSH hosts defined in the SSH config file. Thus, in order
to ensure that this bootstrap.sh
script can also be run on a machine
that is already setup (to, for example, reset how everything is
configured or apply new changes), we want to ensure that those SSH hosts
still exist when this script tries to clone the repo and its submodules.
if [ -f "${HOME}/.ssh/config" ]; then
cp -H "${HOME}/.ssh/config" "${HOME}/.ssh/config.temp"
rm "${HOME}/.ssh/config"
mv "${HOME}/.ssh/config.temp" "${HOME}/.ssh/config"
fi
if [ -d "${HOME}/conf" ]; then
echo "~/conf already exists; backing it up to ~/conf.bkp ..."
cp -r "${HOME}/conf" "${HOME}/conf.bkp"
fi
rm -rf "${HOME}/conf"
git clone github:adamliter/conf.git "${HOME}/conf"
After cloning, it updates all submodules. Submodules, as always, are a
bit tricky. git submodule update --init --recursive
will recursively
initialize and update all submodules in the git
repo. Moreover, with
the --remote
flag, any submodules that are specified to track a branch
on the remote will be fast-forwarded to the most recent commit on that
branch on the remote, regardless of the status of the submodule in the
parent repo. Furthermore, since submodules are always checked out in a
detached HEAD state, the script also checks out either the branch that
the submodule is set up to track on the remote or the main
branch for
all submodules.
cd "${HOME}/conf"
git submodule update --init --remote --recursive
git submodule foreach --recursive \
'git checkout \
$(git config -f $toplevel/.gitmodules submodule.$name.branch || \
echo main)'
cd "${HOME}"
if [[ $BASH_OS_TYPE == macOS ]]; then
if xcode-select -p >/dev/null 2>&1; then
echo "XCode command line tools are already installed ..."
else
echo "Installing XCode command line tools ..."
xcode-select --install
fi
fi
if [[ $BASH_OS_TYPE == macOS ]]; then
if ([[ $MAC_OS_TYPE == 'apple-silicon' ]] && [ -f /opt/homebrew/bin/emacs ]) \
|| ([[ $MAC_OS_TYPE == 'intel' ]] && [ -f /usr/local/bin/emacs ]); then
echo "Emacs was already installed with Homebrew ..."
else
echo "Installing Emacs ..."
brew install emacs-mac --with-emacs-big-sur-icon --with-imagemagick \
--with-natural-title-bar --with-native-compilation \
--with-mac-metal --with-unlimited-select --with-tree-sitter
fi
fi
First, let’s symlink .doom.d
into place:
rm -rf "${HOME}/.doom.d"
ln -sn "${HOME}/conf/doom.d" "${HOME}/.doom.d"
Next, let’s install the dependencies for Doom Emacs:
if ~/.emacs.d/bin/doom >/dev/null 2>&1; then
echo "Doom Emacs is arleady installed ..."
else
echo "Installing dependencies for Doom Emacs ..."
if [[ $BASH_OS_TYPE == macOS ]]; then
brew install git ripgrep
fi
echo "Installing optional dependencies for Doom Emacs ..."
if [[ $BASH_OS_TYPE == macOS ]]; then
brew install coreutils fd
fi
echo "Cloning the doomemacs repository ..."
git clone https://github.com/doomemacs/doomemacs ~/.emacs.d
echo "Installing Doom Emacs ..."
~/.emacs.d/bin/doom install
fi
Next, the script evaluates all source code blocks in the file
${HOME}/conf/README.org
where :eval yes
as well as tangling the file
in order to put all other configuration information in the proper
locations.
emacs --batch \
--eval="(progn
(require 'org)
(setq org-confirm-babel-evaluate nil)
(find-file \"~/conf/README.org\")
(org-babel-tangle)
(org-babel-execute-buffer)
(kill-buffer))"
Finally, the script sources a separate symlink.sh
which symlinks most
things into place (the exceptions are that bootstrap.sh
does the
symlinking for the submodules of this repository). The symlinking is
separated out into its own script so that I can just run the
symlink.sh
script on a Linux machine where I don’t want to install
anything, but I do want, for example, my Bash configuration set up in
the same way that it is set up on my personal machine. The use case for
this is on the Machine Learning Nodes.
. "${HOME}/conf/symlink.sh"
This section of this org
document both installs the relevant command
line utility (if it is not already installed) and creates the
appropriate configuration files for that utility, if necessary. Even
though all of the configuration files are derived files, they are all
kept under version control and symlinked into the appropriate
locations. Doing so allows me to more readily notice any external
programs or utilities that change my configuration files, as it will
lead to a dirty working tree in git
.
The version of bash that ships on macOS is pretty outdated, so let’s
install a more recent version via Homebrew. In order to use this newer
version of bash as the login shell, it needs to be added to the file
/etc/shells
. Then, the shell can be changed to the new bash version
for the current user. In order for this to take effect, I log back in.
if [[ $BASH_OS_TYPE == macOS ]]; then
brew install bash
if ([[ $MAC_OS_TYPE == 'apple-silicon' ]] && ! grep -q "/opt/homebrew/bin/bash" /etc/shells) \
|| ([[ $MAC_OS_TYPE == 'intel' ]] && ! grep -q "/usr/local/bin/bash" /etc/shells); then
echo "Adding Homebrew's bash to possible login shells ..."
if [[ $MAC_OS_TYPE == 'apple-silicon' ]]; then
sudo bash -c "echo /opt/homebrew/bin/bash >> /etc/shells"
fi
if [[ $MAC_OS_TYPE == 'intel' ]]; then
sudo bash -c "echo /usr/local/bin/bash >> /etc/shells"
fi
else
echo "Homebrew's bash is already a possible login shell ..."
fi
if ([[ $MAC_OS_TYPE == 'apple-silicon' ]] && [ $SHELL == "/opt/homebrew/bin/bash" ]) \
|| ([[ $MAC_OS_TYPE == 'intel' ]] && [ $SHELL == "/usr/local/bin/bash" ]); then
echo "Shell is already set to Homebrew's bash ..."
else
echo "Setting shell to Homebrew's bash ..."
if [[ $MAC_OS_TYPE == 'apple-silicon' ]]; then
chsh -s /opt/homebrew/bin/bash
fi
if [[ $MAC_OS_TYPE == 'intel' ]]; then
chsh -s /usr/local/bin/bash
fi
fi
fi
References:
Login shells (e.g. a shell that you start from a non-graphical desktop environment, like when logging into a machine via SSH) read one of three files (assuming your shell is bash):
~/.bash_profile
~/.bash_login
~/.profile
Whichever file is found first is the one that gets read, and the shell
stops looking for the others. Furthermore, login shells do not read
~/.bashrc
, but the best practice is to have an interactive login
shell read ~/.bashrc
. Ensuring that this happens is done by adding the
following to the ~/.bash_profile
file:
# -*- mode: sh; fill-column: 72; coding: utf-8 -*-
if [ -f "${HOME}/.bashrc" ] && [[ $- == *i* ]]; then
source "${HOME}/.bashrc"
fi
It’s worth noting that on macOS, unlike on Linux, all shells started
from Terminal.app (or iTerm.app) in a graphical environment are started
as login shells and thus read ~/.bash_profile
(instead of ~/.bashrc
,
which is what is read when starting Terminal in a graphical desktop
environment on Linux, since it is a non-login shell). Thus, if you want
anything in your ~/.bashrc
to be read when using macOS, you certainly
need to make sure that ~/.bash_profile
sources ~/.bashrc
.
At any rate, this can now be symlinked into the appropriate location, after it is tangled:
ln -sf "${HOME}/conf/bash/bash_profile" "${HOME}/.bash_profile"
In the ~/.bashrc
file, I want to ensure that the system-wide bashrc
file is read, if it exists. On macOS, this usually exists as
/etc/bashrc
.
if [ -f /etc/bashrc ]; then
source /etc/bashrc
fi
And, on Ubuntu, this usually exists as /etc/bash.bashrc
.
if [ -f /etc/bash.bashrc ]; then
source /etc/bash.bashrc
fi
While it is not considered best practice to source ~/.profile
from
inside of ~/.bashrc
(in particular, see Gilles’s answer to Difference
between .bashrc and .bash_profile), I’m going to go ahead and do this
anyway because it makes life easier, and I have yet to encounter any
problems because of it. The recommended best practice is to source
~/.basrhc
and ~/.profile
from ~/.bash_profile
, in that
order. However, for the reasons mentioned above when discussing macOS
shells started in the graphical desktop environment, most shells started
in a graphical desktop environment will only read ~/.bashrc
because
they are non-login shells. However, this means that environment
variables that are set in ~/.profile
will not be available in these
shells. So I’ll flout the best practice for now, until I run into
problems because of it.
if [ -f "${HOME}/.profile" ]; then
source "${HOME}/.profile"
fi
Sometimes you just need to shrug:
alias eh="echo ¯\\\_\(ツ\)_/¯ | pbcopy"
alias ehh="eh"
Some aliases for changing directories:
alias .1='cd ..'
alias ..='cd ..'
alias .2='cd ../..'
alias ....='cd ../..'
alias .3='cd ../../..'
alias .4='cd ../../../..'
alias .5='cd ../../../../..'
List all the things:
alias l='ls -aF'
alias ll='ls -alF'
# list only hidden directories and files
alias l.='ls -dF .*'
alias ll.'=ls -ldF .*'
To ensure the availability of 256 colors in tmux
(see this answer to
lose vim colorscheme in tmux mode).
alias tmux='tmux -2'
Some macOS-specific aliases:
if [[ $OSTYPE == darwin* ]]; then
alias showFiles='defaults write com.apple.finder AppleShowAllFiles \
YES; killall Finder'
alias hideFiles='defaults write com.apple.finder AppleShowAllFiles NO; \
killall Finder'
fi
An SSH alias for ssh-add
:
alias sshid='ssh-add ~/.ssh/id_rsa'
Some aliases for pass
:
alias ppass='PASSWORD_STORE_DIR=~/.password-store/personal/ pass'
alias pp='ppass'
# lingbib password store
alias lb-pass='PASSWORD_STORE_DIR=~/.password-store/shared-projects\
/lingbib/ pass'
# common logins
alias amazon='pp -c misc/amazon'
alias amex='pp -c finances/amex'
alias bb='pp -c misc/bitbucket'
alias chess='pp -c misc/chess'
alias dl='pp -c travel/delta'
alias fmail='pp -c email/fastmail/password'
alias kb='pp -c keybase/passphrase'
alias msufcu='pp -c finances/msufcu'
Some aliases for git
and hub
:
if type hub >/dev/null 2>&1; then
alias git='hub'
fi
alias g='git status -sb'
alias gp='git pull'
alias gpr='git pull --rebase'
alias gpp='git pull --rebase && git push'
alias ga='git add'
alias gc='git commit'
alias gcn='git commit --no-edit'
alias gce='git commit -e'
alias gces='git commit -eS'
alias gca='git commit --amend'
alias gcah='git commit --amend -C HEAD'
alias gcv='git commit --no-verify'
alias gdv='git diff'
alias gdc='git diff --cached'
alias gl='git log --oneline --decorate --graph'
alias gla='git log --oneline --decorate --graph --all'
alias gt='git tag'
alias grc='git rebase --continue'
alias gsl='git stash list'
alias gss='git stash save'
And an alias for kubectl
:
if command -v kubectl 1>/dev/null 2>&1; then
alias k='kubectl'
fi
Auto completion for things installed with Homebrew:
if [ -d /usr/local/etc/bash_completion.d ]; then
for f in /usr/local/etc/bash_completion.d/*; do
. "${f}"
done
fi
if [ -d /opt/homebrew/etc/bash_completion.d ]; then
for f in /opt/homebrew/etc/bash_completion.d/*; do
. "${f}"
done
fi
Tab auto completion for pass
:
if [ -f /usr/local/etc/bash_completion.d/pass ]; then
source /usr/local/etc/bash_completion.d/pass
fi
# personal completion
_ppass(){
PASSWORD_STORE_DIR=~/.password-store/personal/ _pass
}
complete -o filenames -o nospace -F _ppass ppass
_pp(){
_ppass
}
complete -o filenames -o nospace -F _pp pp
# lingbib completion
_lb-pass(){
PASSWORD_STORE_DIR=~/.password-store/shared-projects/lingbib/ _pass
}
complete -o filenames -o nospace -F _lb-pass lb-pass
Auto completion for kubectl
and alias.
if command -v pyenv 1>/dev/null 2>&1; then
# Only needed if autcompletion isn't already in Hombrew bash completion dir
#source <(kubectl completion bash)
complete -o default -F __start_kubectl k
fi
First, let’s set up some more useful ways to refer to colors:
RED="\[\e[31m\]"
LIGHT_RED="\[\e[91m\]"
GREEN="\[\e[32m\]"
LIGHT_GREEN="\[\e[92m\]"
YELLOW="\[\e[33m\]"
LIGHT_YELLOW="\[\e[93m\]"
BLUE="\[\e[34m\]"
LIGHT_BLUE="\[\e[94m\]"
MAGENTA="\[\e[35m\]"
LIGHT_MAGENTA="\[\e[95m\]"
CYAN="\[\e[36m\]"
LIGHT_CYAN="\[\e[96m\]"
LIGHT_GREY="\[\e[37m\]"
LIGHT_GRAY="\[\e[37m\]"
WHITE="\[\e[97m\]"
COLOR_RESET="\[\e[0m\]"
Next, let’s define a separator to separate information in the prompt:
MY_PS1_SEP=" ${WHITE}█${COLOR_RESET} "
Next, let’s write a function to determine if the current directory is a
git
repo:
function is_git_repository {
git branch > /dev/null 2>&1
}
If it is, we’ll want to determine some information about it:
function set_git_branch {
# Capture the output of the "git status" command.
git_status="$(git status 2> /dev/null)"
# Set color based on clean/staged/dirty
clean_pattern="working (tree|directory) clean"
if [[ ${git_status} =~ ${clean_pattern} ]]; then
state="${LIGHT_GREEN}"
elif [[ ${git_status} =~ "Changes to be committed" ]]; then
state="${LIGHT_YELLOW}"
else
state="${RED}"
fi
# Set arrow icon based on status against remote.
remote_pattern="(# )?Your branch is (ahead of|behind)"
if [[ ${git_status} =~ ${remote_pattern} ]]; then
if [[ ${BASH_REMATCH[2]} == "ahead of" ]]; then
remote="↑"
else
remote="↓"
fi
else
remote=""
fi
diverge_pattern="(# )?Your branch and (.*) have diverged"
if [[ ${git_status} =~ ${diverge_pattern} ]]; then
remote="↕"
fi
# Get the name of the branch.
branch_pattern="^(# )?On branch ([^${IFS}]*)"
detached_head_pattern="HEAD detached from"
if [[ ${git_status} =~ ${branch_pattern} ]]; then
branch=${BASH_REMATCH[2]}
elif [[ ${git_status} =~ ${detached_head_pattern} ]]; then
branch="HEAD"
fi
# Set the final branch string.
BRANCH="${MY_PS1_SEP}${state}(${branch})${remote}${COLOR_RESET}"
}
Additionally, if this is also a Python virtual environment, we’ll want to add some information about that to the prompt:
function set_virtualenv () {
if test -z "${VIRTUAL_ENV}" && test -z "${CONDA_DEFAULT_ENV}"; then
MY_VENV=""
else
if test -z "${VIRTUAL_ENV}"; then
MY_VENV="${LIGHT_GREY}[${CONDA_DEFAULT_ENV}]${COLOR_REST}\
${MY_PS1_SEP}"
else
MY_VENV="${LIGHT_GREY}[${VIRTUAL_ENV##*/}]${COLOR_RESET}\
${MY_PS1_SEP}"
fi
fi
}
We can also change the color of the prompt symbol, based on the exit code of the last command. Here’s a function to get and set that information:
function set_prompt_symbol () {
if test $1 -eq 0 ; then
PROMPT_SYMBOL="\$"
else
PROMPT_SYMBOL="${RED}\$${COLOR_RESET}"
fi
}
I’d also like to shorten the path to the current working directory in the prompt if there isn’t enough room to display it in the shell. In order to do this, I need to first compute the prompt minus the working directory in order to determine the number of remaining columns that I have left to work with:
strip_color () {
COLOR_REGEX='s/\\\[\\e\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]\\\]//g'
if [[ $OSTYPE == darwin* ]]; then
sed -Ee $COLOR_REGEX <<< """$1"""
else
sed -re $COLOR_REGEX <<< """$1"""
fi
}
get_ps1_less_pwd () {
PS1_LESS_PWD=$(printf "%s%s%s%s" \
"$(strip_color "${MY_VENV}")" \
"$(whoami)@$(hostname -s)" \
"$(strip_color "${MY_PS1_SEP}")" \
"$(strip_color "${BRANCH}")")
}
Now I can finally declare the function to actually compute and calculate the bash prompt:
function set_bash_prompt () {
# Set the PROMPT_SYMBOL variable. We do this first so we don't lose the
# return value of the last command.
set_prompt_symbol $?
# Get virtualenv info
set_virtualenv
# Set the BRANCH variable.
if is_git_repository ; then
set_git_branch
else
BRANCH=''
fi
# get PS1_LESS_PWD to calculate length remaining
get_ps1_less_pwd
# get truncated PWD
# loosely based on http://stackoverflow.com/a/26555347/2571049
MY_PWD=$(pwd | awk -F/ -v "u=$PS1_LESS_PWD" -v "n=$(tput cols)" \
-v "h=^$HOME" \
'{sub(h,"~"); u=length(u); n=n-u-1; b=$1} \
length($0)<=n || NF==2 {print; next;} \
NF>2 {b=b"/.../"; e=$NF; n-=length(b $NF); \
for (i=NF-1; i>2 && n>length(e $i)+1; i--) e=$i"/"e;} {print b e;}')
# Set the bash prompt variable.
PS1="
${MY_VENV}\
${WHITE}\u${COLOR_RESET}\
${LIGHT_GREEN}@${COLOR_RESET}\
${LIGHT_MAGENTA}\h${COLOR_RESET}\
${MY_PS1_SEP}\
${LIGHT_CYAN}${MY_PWD}${COLOR_RESET}\
${BRANCH}
${PROMPT_SYMBOL} "
}
And, finally, we can actually call the function to set the prompt:
PROMPT_COMMAND=set_bash_prompt
See How can I configure Mac Terminal to have color ls output?
export CLICOLOR=1
export LSCOLORS=gxBxhxDxfxhxhxhxhxcxcx
eval "$(direnv hook bash)"
Note that eval "$(pyenv virtualenv-init -)"
needs to come after
setting PROMPT_COMMAND
in order to work correctly.
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi
if command -v pyenv-virtualenv-init 1>/dev/null 2>&1; then
eval "$(pyenv virtualenv-init -)"
fi
gpip2(){
PIP_REQUIRE_VIRTUALENV="" pip2 "$@"
}
gpip3(){
PIP_REQUIRE_VIRTUALENV="" pip3 "$@"
}
gpip(){
PIP_REQUIRE_VIRTUALENV="" pip "$@"
}
Finally, ~/.bashrc
can be symlinked into the appropriate location,
after it is tangled:
ln -sf "${HOME}/conf/bash/bashrc" "${HOME}/.bashrc"
if command -v rbenv 1>/dev/null 2>&1; then
eval "$(rbenv init -)"
fi
Finally, ~/.bashrc
can be symlinked into the appropriate location,
after it is tangled:
ln -sf "${HOME}/conf/bash/bashrc" "${HOME}/.bashrc"
~/.profile
is where stuff that is not bash-specifc goes, such as
environment variables.
# -*- mode: sh; fill-column: 72; coding: utf-8 -*-
if [[ $OSTYPE == darwin* ]] && [[ $(uname -m) == arm64 ]]; then
eval $(/opt/homebrew/bin/brew shellenv)
fi
if [[ ":${PATH}:" != *":${HOME}/bin:"* ]] && [ -d "${HOME}/bin" ]; then
export PATH="${HOME}/bin:${PATH}"
fi
if [[ ":${PATH}:" != *":${HOME}/.emacs.d/bin:"* ]] && [ -d "${HOME}/.emacs.d/bin" ]; then
export PATH="${HOME}/.emacs.d/bin:${PATH}"
fi
# ----------------------------------------------------------------------
# EDITOR
# ----------------------------------------------------------------------
if [ -f "${HOME}/bin/ec" ]; then
export EDITOR=ec
else
if [ -f /usr/local/bin/emacs ]; then
export EDITOR=/usr/local/bin/emacs
elif [ -f /opt/homebrew/bin/emacs ]; then
export EDITOR=/opt/homebrew/bin/emacs
else
export EDITOR=emacs
fi
fi
export ALTERNATE_EDITOR=""
# ----------------------------------------------------------------------
# Node stuff
# ----------------------------------------------------------------------
if command -v npm 1>/dev/null 2>&1; then
export NODE_PATH=$(npm root -g)
fi
# ----------------------------------------------------------------------
# Python stuff
# ----------------------------------------------------------------------
if [ -d "$HOME/.pyenv" ]; then
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init --path)"
export PYENV_VIRTUALENV_VERBOSE_ACTIVATE=1
fi
fi
export VIRTUAL_ENV_DISABLE_PROMPT=1
# ----------------------------------------------------------------------
# Rust stuff
# ----------------------------------------------------------------------
if [ -f "${HOME}/.cargo/env" ]; then
source "${HOME}/.cargo/env"
fi
Finally, ~/.profile
can be symlinked into the appropriate location,
after it is tangled:
ln -sf "${HOME}/conf/bash/profile" "${HOME}/.profile"
CMake is needed in order to compile vterm
for use inside Emacs.
brew install cmake
coreutils
is already installed in the bootstrapping process as an
optional dependency for Doom Emacs.
brew install direnv
brew install dvc
brew install editorconfig
fd
is already installed in the bootstrapping process as an optional
dependency for Doom Emacs.
git
is already installed in the bootstrapping process as a dependency
for Doom Emacs.
# -*- mode: gitconfig; coding: utf-8 -*-
[init]
defaultBranch = main
git
needs to know who I am.
[user]
name = Adam Liter
email = [email protected]
signkey = 0x98723A2089026CD6
Set up my editor for git
and a global ignore file.
[core]
editor = "TERM=xterm-emacs emacsclient -t -a=''"
excludesfile = ~/.gitignore_global
attributesfile = ~/.gitattributes_global
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
light = true
features = line-numbers
side-by-side = false
[delta "magit-delta"]
line-numbers = false
[merge]
conflictstyle = diff3
This sets up some defaults for displaying color with git
.
[color]
diff = auto
status = auto
branch = auto
interactive = auto
ui = true
pager = true
This sets up an alias for automatically pushing submodules.
[alias]
pushall = push --recurse-submodule=on-demand
The following sets up a diff
driver called pandoc
for diffing Word
documents that git
is tracking. For reference, see e.g.,
Using Microsoft Word with git.
[diff]
colorMoved = default
[diff "pandoc"]
textconv=pandoc --to=markdown
prompt = false
The following sets up a filter
driver called lfs
for using Git LFS.
[filter "lfs"]
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
process = git-lfs filter-process
required = true
The following sets the default behavior of git push
such that it only
pushes the current branch to the remote repo. For further discussion,
see either of the following questions on Stack Overflow:
- “simple” vs “current” push.default in git for decentralized workflow
- Warning: push.default is unset; its implicit value is changing in Git 2.0
[push]
default = simple
git pull
should rebase by default. In versions of git
older than
1.8.5, this can be achieved by setting the configuration option
pull.rebase
to true
. In git
1.8.5 and more recent versions, it
became possible to set the configuration option pull.rebase
to
preserve
(see here). This is better because it runs git pull
--preserve-merges
instead of git pull --rebase
. The use case for this
is that if you are working locally and do a non-fast-forward merge of
your feature branch into the master branch but then need to pull in
changes from the upstream master branch before being able to push, the
git pull --rebase
would flatten the non-fast-forward merge that you
just did, whereas git pull --preserve-merges
will preserve that merge
commit.
However, in git
2.22.0, the option of setting =pull.rebase= to
preserve
was deprecated in favor of setting pull.rebase
to
merges
in order to preserve merge commits. This runs git rebase
--rebase-merges
behind the scenes.
[pull]
rebase = merges
For gpg
stuff with git
, we want to use gpg2
, which is now just
gpg
, at least if installed with Homebrew.
[gpg]
program = gpg
Configuration for =forge=
This sets up my user names on the git forges, GitHub and GitLab, for use with =forge=.
[gitlab]
user = adamliter
[github]
user = adamliter
Finally, the gitconfig
file needs to be symlinked into the proper
location.
ln -sf "${HOME}/conf/git/gitconfig" "${HOME}/.gitconfig"
A good reference for all sorts of patterns that git
ought to ignore is
=github/gitingore=.
The following things are things that I have git
ignore by default
globally.
Some stuff to ignore on macOS, taken from =github/gitignore=. However, I haven’t added the Icon ignore pattern because it needs to end with a carriage return, and I have Emacs set up to trim trailing white space. I’d rather not disable that for this document just to be able to add this ignore pattern, since it is a pretty uncommon pattern and largely only occurs in the top-level directories of the folders associated with services like Dropbox or Google Drive (I don’t generally change/set a directory’s icon).
# -*- mode: gitignore; coding: utf-8 -*-
*.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
The following is stuff to ignore on Linux, taken from =github/gitignore=.
# temporary files which can be created if a
# process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file
# is removed but is still being accessed
.nfs*
The following is stuff to ignore in Windows, taken from =github/gitingore=.
# Windows image file caches
Thumbs.db
ehthumbs.db
# Dump file
*.stackdump
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
The following is Emacs stuff to ignore, taken from =github/gitignore=.
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
**/eshell/history
**/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
**/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
The following is stuff to ignore for (La)TeX, taken from =github/gitignore=.
## Core latex/pdflatex auxiliary files:
*.aux
*.lof
*.log
*.lot
*.fls
*.out
*.toc
*.fmt
*.fot
*.cb
*.cb2
## Intermediate documents:
*.dvi
*-converted-to.*
# these rules might exclude image files for figures etc.
# *.ps
# *.eps
# *.pdf
## Generated if empty string is given at "Please type another file name for output:"
.pdf
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bcf
*.blg
*-blx.aux
*-blx.bib
*.brf
*.run.xml
## Build tool auxiliary files:
*.fdb_latexmk
*.synctex
*.synctex(busy)
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync
## Auxiliary and intermediate files from other packages:
# algorithms
*.alg
*.loa
# achemso
acs-*.bib
# amsthm
*.thm
# beamer
*.nav
*.snm
*.vrb
# changes
*.soc
# cprotect
*.cpt
# elsarticle
*.spl
# endnotes
*.ent
# fixme
*.lox
# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm
#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
*.[1-9]
*.[1-9][0-9]
*.[1-9][0-9][0-9]
*.[1-9]R
*.[1-9][0-9]R
*.[1-9][0-9][0-9]R
*.eledsec[1-9]
*.eledsec[1-9]R
*.eledsec[1-9][0-9]
*.eledsec[1-9][0-9]R
*.eledsec[1-9][0-9][0-9]
*.eledsec[1-9][0-9][0-9]R
# glossaries
*.acn
*.acr
*.glg
*.glo
*.gls
*.glsdefs
# gnuplottex
*-gnuplottex-*
# gregoriotex
*.gaux
*.gtex
# hyperref
*.brf
# knitr
*-concordance.tex
# TODO Comment the next line if you want to keep your tikz graphics files
*.tikz
*-tikzDictionary
# listings
*.lol
# makeidx
*.idx
*.ilg
*.ind
*.ist
# minitoc
*.maf
*.mlf
*.mlt
*.mtc[0-9]*
*.slf[0-9]*
*.slt[0-9]*
*.stc[0-9]*
# minted
_minted*
*.pyg
# morewrites
*.mw
# nomencl
*.nlo
# pax
*.pax
# pdfpcnotes
*.pdfpc
# sagetex
*.sagetex.sage
*.sagetex.py
*.sagetex.scmd
# scrwfile
*.wrt
# sympy
*.sout
*.sympy
sympy-plots-for-*.tex/
# pdfcomment
*.upa
*.upb
# pythontex
*.pytxcode
pythontex-files-*/
# thmtools
*.loe
# TikZ & PGF
*.dpth
*.md5
*.auxlock
# todonotes
*.tdo
# easy-todo
*.lod
# xindy
*.xdy
# xypic precompiled matrices
*.xyc
# endfloat
*.ttt
*.fff
# Latexian
TSWLatexianTemp*
## Editors:
# WinEdt
*.bak
*.sav
# Texpad
.texpadtmp
# Kile
*.backup
# KBibTeX
*~[0-9]*
# auto folder when using emacs and auctex
/auto/*
The following is stuff to ignore for Python, taken from =github/gitignore=.
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# virtualenv
.env
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
The following is stuff to ignore for R, taken from =github/gitignore=.
# History files
.Rhistory
.Rapp.history
# Session Data files
.RData
# Example code in package build process
*-Ex.R
# Output files from R CMD build
/*.tar.gz
# Output files from R CMD check
/*.Rcheck/
# RStudio files
.Rproj.user/
# produced vignettes
vignettes/*.html
vignettes/*.pdf
# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
.httr-oauth
# knitr and R markdown default cache directories
/*_cache/
/cache/
# Temporary files created by R markdown
*.utf8.md
*.knit.md
Finally, the gitignore_global
file needs to be symlinked into the
proper location.
ln -sf "${HOME}/conf/git/gitignore_global" "${HOME}/.gitignore_global"
This sets attribtues globally for Word documents so that I can diff Word
documents using the pandoc
driver. See the =diff= section above.
# -*- mode: gitattributes; coding: utf-8 -*-
*.docx diff=pandoc
*.doc diff=pandoc
Finally, the gitattributes_global
file needs to be symlinked into the
proper location.
ln -sf "${HOME}/conf/git/gitattributes_global" "${HOME}/.gitattributes_global"
brew install git-delta
The main behavior is configured in ~/.gitconfig
above. See the manual
for configuration options and information.
brew install git-lfs
brew install glow
brew install gnupg
Some useful resources:
brew install helm
brew install htop
Nothing for now.
# -*- mode: conf-unix; coding: utf-8; -*-
Symlink it into place:
mkdir -p ~/.config/htop
ln -sf "${HOME}/conf/htop/htoprc" "${HOME}/.config/htop/htoprc"
brew install hub
brew install jq
brew install kubernetes-cli
brew install lab
# -*- mode: conf-toml; coding: utf-8; -*-
[core]
host = "https://gitlab.com"
load_token = # TODO: set up load_token with pass
user = "adam.liter"
Symlink it into place:
mkdir -p ~/.config/lab
ln -sf "${HOME}/conf/lab/lab.toml" "${HOME}/.config/lab/lab.toml"
brew install minikube
brew install pandoc
There is no configuration setup for pandoc
.
brew install pass
No current configuration for pass.
brew install pinentry-mac
Let’s set it up so that the password is not saved in the macOS Keychain (and so that this option isn’t even displayed).
defaults write org.gpgtools.common UseKeychain NO
defaults write org.gpgtools.common DisableKeychain -bool yes
To use pinentry-mac
, the following line needs to be set in the file
~/.gnupg/gpg-agent.conf
:
pinentry-program $HOMEBREW_PREFIX/bin/pinentry-mac
This is done above in the section for configuring gpg
.
brew install pre-commit
This allows for accessing the clipboard from inside of tmux
sessions
on macOS. See the GitHub repository for more info.
brew install reattach-to-user-namespace
ripgrep
is installed in the bootstrapping process as a dependency for
Doom Emacs.
brew install ruff
ssh
is already installed on both macOS and Linux, by default.
My SSH configuration is kept in a submodule hosted by my private git
server so as to keep the details of my SSH configuration private. The
following code, which is executed when this file is tangled, will create
my SSH config and then symlink it into the appropriate location.
(org-babel-tangle-file "~/conf/ssh/README.org")
ln -sf "${HOME}/conf/ssh/config" "${HOME}/.ssh/config"
Install tmux
:
brew install tmux
To ensure that colors work properly, I’ve followed the suggestion in this answer on Stack Overflow.
# -*- coding: utf-8; mode: conf-unix; fill-column: 72 -*-
set-option -g default-terminal "xterm-256color"
The following ensures that the macOS pasteboard is available in tmux
sessions. See the Github repository for more information.
set-option -g default-command "reattach-to-user-namespace -l $SHELL"
The following allows the tmux
configuration file to be reloaded.
bind-key r source-file ~/.tmux.conf
The following are some key bindings for opening new SSH sessions in either a new windows, a vertically split window, or a horizontally split window.
bind-key S command-prompt -p "host" "new-window -n %1 'ssh %1'"
bind-key C-S command-prompt -p "host" "split-window -v 'ssh %1'"
bind-key M-S command-prompt -p "host" "split-window -h 'ssh %1'"
The following are some key bindings for opening Emacs in either a new window, a vertically split window, or a horizontally split window.
bind-key y new-window -n "emacs" "TERM=xterm-emacs emacsclient -nw"
bind-key C-y split-window -v "TERM=xterm-emacs emacsclient -nw"
bind-key M-y split-window -h "TERM=xterm-emacs emacsclient -nw"
The following defines some more intuitive key bindings for splitting a window and undefines the default key bindings for this.
bind-key | split-window -h
bind-key - split-window -v
unbind '"'
unbind %
The following allows for switching between tmux
panes using just
M-<arrow>, without the tmux
prefix key.
bind-key -n M-Left select-pane -L
bind-key -n M-Right select-pane -R
bind-key -n M-Up select-pane -U
bind-key -n M-Down select-pane -D
And, finally, the following symlinks the configuration file into the appropriate location.
ln -sf "${HOME}/conf/tmux/tmux.conf" "${HOME}/.tmux.conf"
brew install trivy
wget
is not installed by default on macOS:
brew install wget
brew install yq
brew install node
Nothing for now.
# -*- mode: conf-unix; coding: utf-8; -*-
Symlink it into place:
ln -sf "${HOME}/conf/npm/npmrc" "${HOME}/.npmrc"
brew install nodenv
I use pyright as an LSP server for Python development in Emacs.
echo "Installing node package pyright globally ..."
npm install -g pyright
I also use @commitlint/cli
and @commitlint/config-conventional
for
commit linting across a variety of projects.
echo "Installing @commitlint/{cli,config-conventional} globally ..."
npm install -g @commitlint/{cli,config-conventional}
My default directory for org
files is a submodule of this repository
and thus needs to be symlinked into the location of org-directory
,
whose value I’ve set to ~/org
in .doom.d/config.el
.
rm -rf "${HOME}/org"
ln -sn "${HOME}/conf/org" "${HOME}/org"
This answer to Installing anaconda with pyenv, unable to configure virtual environment is helpful for installing conda alongside pyenv.
# -*- mode: yaml; coding: utf-8; -*-
auto_activate_base: false
channels:
- defaults
Symlink the configuration file into the appropriate location:
ln -sf "${HOME}/conf/conda/condarc" "${HOME}/.condarc"
brew install pdm
brew install pyenv pyenv-virtualenv
pyenv install 3:latest
Nothing at the moment.
brew install rustup
# Make sure rust-analyzer is installed for LSP
rustup component add rust-analyzer
Nothing at the moment.
if [ -d "${HOME}/bin" ]; then
echo "~/bin already exists; backing it up to ~/bin.bkp ..."
cp -r "${HOME}/bin" "${HOME}/bin.bkp"
fi
rm -rf ${HOME}/bin
ln -sn "${HOME}/conf/bin" "${HOME}/bin"
TERM=xterm-emacs emacsclient -t -a="" "$@"