feat: a new home manager

This commit updates the scope of this project to manage the files and
directories within my home directory. The Makefile and helper bash
scripts are now replaced with mage targets so that the home directory is
now managed with Mage. The state of the home directory is managed using
a JSON configuration for each machine host. The manager is a set of mage
targets to manage various aspects of the home directory. At the moment
the manager can:

- ensure specified directories are present within the home directory.
- ensure application configuration files are up-to-date and have the
  correct symlinks within the user's home configuration directory.
- manages the user's bash profile (a.k.a bashrc) file.

Other notable changes:

- The X11 xinitrc is removed because it is not currently used and won't
  be used for the forseeable future as we slowly move to Wayland.
- All bashrc configurations are now defined in one file and is now fully
  managed by the manager.
- The dunst configuration is currently removed but will make a comeback.
- The ansible configuration is removed as it is no longer used.
- The logrotate configuration is updated and now generated from a
  template.
- Added configuration for the foot terminal.
- Added configuration for the River window manager.
This commit is contained in:
Dan Anglin 2024-09-12 16:35:06 +01:00
parent b0db6ff602
commit 2402833b1a
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
72 changed files with 1374 additions and 1200 deletions

5
.gitignore vendored
View file

@ -1,3 +1,2 @@
config/git/config/* managed/*
!config/git/config/.gitkeep !managed/.gitkeep
!config/git/config/README.md

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
X11_DOTFILES_SOURCE_DIR="${GIT_ROOT_DIR}/config/X11"
X11_DOTFILES_DESTINATION_DIR="${XDG_CONFIG_HOME}/X11"
if ! [ -d ${X11_DOTFILES_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${X11_DOTFILES_DESTINATION_DIR}"
mkdir ${X11_DOTFILES_DESTINATION_DIR}
fi
rsync -avh ${X11_DOTFILES_SOURCE_DIR}/xinitrc ${X11_DOTFILES_DESTINATION_DIR}/xinitrc && chmod a-rwx,u+rw $_

View file

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
AMFORA_CONFIG_SOURCE_DIR="${GIT_ROOT_DIR}/config/amfora"
AMFORA_CONFIG_DESTINATION_DIR="${XDG_CONFIG_HOME}/amfora"
if ! [ -d ${AMFORA_CONFIG_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${AMFORA_CONFIG_DESTINATION_DIR}"
mkdir ${AMFORA_CONFIG_DESTINATION_DIR}
fi
rsync -avh ${AMFORA_CONFIG_SOURCE_DIR}/ ${AMFORA_CONFIG_DESTINATION_DIR}
chmod a-rwx,u+rw ${AMFORA_CONFIG_DESTINATION_DIR}/*

View file

@ -1,21 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
ANSIBLE_DOTFILES_DIR="${GIT_ROOT_DIR}/config/ansible"
ANSIBLE_CONFIG_DIR=${XDG_CONFIG_HOME}/ansible
ANSIBLE_DATA_DIR=${XDG_DATA_HOME}/ansible
ANSIBLE_CACHE_DIR=${XDG_CACHE_HOME}/ansible
for d in ${ANSIBLE_CONFIG_DIR} ${ANSIBLE_DATA_DIR} ${ANSIBLE_CACHE_DIR}; do
if ! [ -d ${d} ]; then
echo "INFO: Creating directory ${d}"
mkdir ${d}
fi
done
for f in $( find ${ANSIBLE_DOTFILES_DIR} -type f -exec basename {} \; ); do
echo "INFO: Installing ${f}"
rsync -avh ${ANSIBLE_DOTFILES_DIR}/${f} ${ANSIBLE_CONFIG_DIR}/${f} && chmod a-rwx,u+rw $_
done

View file

@ -1,31 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
BASH_DOTFILES_DIR="${GIT_ROOT_DIR}/config/bash"
BASHRC_DIRNAME="bashrc.d"
BASHRC_CONFIG_DIR=${XDG_CONFIG_HOME}/bash/${BASHRC_DIRNAME}
BASH_DATA_DIR=${XDG_DATA_HOME}/bash
source ${GIT_ROOT_DIR}/helpers/lib/install-config
for d in ${BASHRC_CONFIG_DIR} ${BASH_DATA_DIR}; do
if ! [ -d ${d} ]; then
mkdir -p ${d}
fi
done
for f in $(find ${BASH_DOTFILES_DIR}/${BASHRC_DIRNAME} -mindepth 1 -maxdepth 1 -type f -exec basename {} \;); do
configFilename=${f}
if [ -f ${BASHRC_CONFIG_DIR}/${f}.disabled ]; then
configFilename=${f}.disabled
fi
install_config ${BASH_DOTFILES_DIR}/${BASHRC_DIRNAME}/${f} ${BASHRC_CONFIG_DIR}/${configFilename}
done
for f in "bashrc" "bash_profile"; do
install_config ${BASH_DOTFILES_DIR}/${f} ${HOME}/.${f}
done

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
DUNST_DOTFILES_SOURCE_DIR="${GIT_ROOT_DIR}/config/dunst"
DUNST_DOTFILES_DESTINATION_DIR="${XDG_CONFIG_HOME}/dunst"
if ! [ -d ${DUNST_DOTFILES_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${DUNST_DOTFILES_DESTINATION_DIR}"
mkdir ${DUNST_DOTFILES_DESTINATION_DIR}
fi
rsync -avh ${DUNST_DOTFILES_SOURCE_DIR}/dunstrc ${DUNST_DOTFILES_DESTINATION_DIR}/dunstrc && chmod a-rwx,u+rw $_

View file

@ -1,63 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
GIT_DOTFILES_DIR="${GIT_ROOT_DIR}/config/git"
PARAMETER_FILE="${GIT_DOTFILES_DIR}/config/gitconfig-parameters"
GITCONFIG_FILE="${GIT_DOTFILES_DIR}/gitconfig"
GITIGNORE_FILE="${GIT_DOTFILES_DIR}/gitignore"
GITMESSAGE_FILE="${GIT_DOTFILES_DIR}/gitmessage"
GIT_OUTPUT_ROOT_DIR="${XDG_CONFIG_HOME}/git"
GITCONFIG_OUTPUT="${GIT_OUTPUT_ROOT_DIR}/config"
GITIGNORE_OUTPUT="${GIT_OUTPUT_ROOT_DIR}/ignore"
GITMESSAGE_OUTPUT="${GIT_OUTPUT_ROOT_DIR}/message"
validate_params() {
if [ -z ${GIT_USER_NAME+x} ]; then
echo "ERROR: 'GIT_USER_NAME' is not set or is empty in ${PARAMETER_FILE}"
return 1
fi
if [ -z ${GIT_USER_EMAIL+x} ]; then
echo "ERROR: 'GIT_USER_EMAIL' is not set or is empty in ${PARAMETER_FILE}"
return 1
fi
if [ -z ${GIT_USER_SIGNINGKEY+x} ]; then
echo "ERROR: 'GIT_USER_SIGNINGKEY' is not set or is empty in ${PARAMETER_FILE}"
return 1
fi
return 0
}
echo "INFO: Attempting to source parameters from ${PARAMETER_FILE}."
test -f ${PARAMETER_FILE} && source ${PARAMETER_FILE}
echo "INFO: Validating parameters."
validate_params || exit 1
if ! [ -d ${GIT_OUTPUT_ROOT_DIR} ]; then
echo "INFO: Creating directory ${GIT_OUTPUT_ROOT_DIR}"
mkdir ${GIT_OUTPUT_ROOT_DIR}
fi
echo "INFO: Generating the gitconfig file and installing it to ${GITCONFIG_OUTPUT}."
gitmessage_output=${GITMESSAGE_OUTPUT} \
gitignore_output=${GITIGNORE_OUTPUT} \
git_user_name=${GIT_USER_NAME} \
git_user_email=${GIT_USER_EMAIL} \
git_user_signingkey=${GIT_USER_SIGNINGKEY} \
envsubst < ${GITCONFIG_FILE} > ${GITCONFIG_OUTPUT}
chmod a-rwx,u+rw ${GITCONFIG_OUTPUT}
echo "INFO: Installing ${GITIGNORE_OUTPUT}."
rsync -avh ${GITIGNORE_FILE} ${GITIGNORE_OUTPUT} && chmod a-rwx,u+rw $_
echo "INFO: Installing ${GITMESSAGE_OUTPUT}."
rsync -avh ${GITMESSAGE_FILE} ${GITMESSAGE_OUTPUT} && chmod a-rwx,u+rw $_

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
LF_DOTFILES_SOURCE_DIR="${GIT_ROOT_DIR}/config/lf"
LF_DOTFILES_DESTINATION_DIR="${XDG_CONFIG_HOME}/lf"
if ! [ -d ${LF_DOTFILES_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${LF_DOTFILES_DESTINATION_DIR}"
mkdir ${LF_DOTFILES_DESTINATION_DIR}
fi
rsync -avh ${LF_DOTFILES_SOURCE_DIR}/lfrc ${LF_DOTFILES_DESTINATION_DIR}/lfrc && chmod a-rwx,u+rw $_

View file

@ -1,8 +0,0 @@
# vi: set ft=bash :
install_config() {
[ -f ${2} ] && rm -rf ${2}
echo "INFO: Installing ${1} to ${2}"
cp ${1} ${2}
chmod -R a-rwx,u+rw ${2}
}

View file

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
LF_DOTFILES_SOURCE_DIR="${GIT_ROOT_DIR}/config/logrotate"
LF_DOTFILES_DESTINATION_DIR="${XDG_CONFIG_HOME}/logrotate"
if ! [ -d ${LF_DOTFILES_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${LF_DOTFILES_DESTINATION_DIR}"
mkdir ${LF_DOTFILES_DESTINATION_DIR}
fi
envsubst < ${LF_DOTFILES_SOURCE_DIR}/logrotate.conf > ${LF_DOTFILES_DESTINATION_DIR}/logrotate.conf
chmod a-rwx,u+rw ${LF_DOTFILES_DESTINATION_DIR}/logrotate.conf

View file

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
SCRIPTS_SOURCE_DIR="${GIT_ROOT_DIR}/scripts"
SCRIPTS_DESTINATION_DIR="${HOME}/.local/scripts"
if ! [ -d ${SCRIPTS_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${SCRIPTS_DESTINATION_DIR}"
mkdir ${SCRIPTS_DESTINATION_DIR}
fi
rsync -avh ${SCRIPTS_SOURCE_DIR}/ ${SCRIPTS_DESTINATION_DIR}
chmod -R 0700 ${SCRIPTS_DESTINATION_DIR}

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
TMUX_DOTFILES_SOURCE_DIR="${GIT_ROOT_DIR}/config/tmux"
TMUX_DOTFILES_DESTINATION_DIR="${XDG_CONFIG_HOME}/tmux"
if ! [ -d ${TMUX_DOTFILES_DESTINATION_DIR} ]; then
echo "INFO: Creating directory ${TMUX_DOTFILES_DESTINATION_DIR}"
mkdir ${TMUX_DOTFILES_DESTINATION_DIR}
fi
rsync -avh ${TMUX_DOTFILES_SOURCE_DIR}/tmux.conf ${TMUX_DOTFILES_DESTINATION_DIR}/tmux.conf && chmod a-rwx,u+rw $_

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
VIM_CONFIG_SOURCE_DIR="${GIT_ROOT_DIR}/config/vim"
VIM_CONFIG_DESTINATION_DIR="${XDG_CONFIG_HOME}/vim"
MINPAC_SOURCE="https://github.com/k-takata/minpac.git"
MINPAC_DESTINATION="${XDG_DATA_HOME}/vim/pack/bundle/opt/minpac"
GOTAGS_SOURCE="github.com/jstemmer/gotags"
# Creating essential vim directories
mkdir -p ${VIM_CONFIG_DESTINATION_DIR}
mkdir -p ${XDG_CACHE_HOME}/vim/{backup,swap,undo,view} && chmod 0700 ${XDG_CACHE_HOME}/vim/{backup,swap,undo,view}
mkdir -p ${XDG_DATA_HOME}/vim/spell && chmod 0700 ${XDG_DATA_HOME}/vim/spell
# Syncing the vim configuration files
echo "INFO: syncing ${VIM_CONFIG_SOURCE_DIR} to ${VIM_CONFIG_DESTINATION_DIR}"
rsync -avh --exclude=*.gitkeep ${VIM_CONFIG_SOURCE_DIR}/ ${VIM_CONFIG_DESTINATION_DIR}
chmod a-rwx,u+rwx ${VIM_CONFIG_DESTINATION_DIR}
find ${VIM_CONFIG_DESTINATION_DIR} -type d -not -wholename */pack/bundle/opt/* -not -wholename */pack/bundle/start/* \
| xargs chmod a-rwx,u+rwx
# Ensuring minpac and gotags are installed
if ! [ -d ${MINPAC_DESTINATION}/.git ]; then
echo "INFO: Cloning ${MINPAC_SOURCE} to ${MINPAC_DESTINATION}"
git clone --depth=1 ${MINPAC_SOURCE} ${MINPAC_DESTINATION}
fi
if [ -x "$( command -v go )" ]; then
if ! [ -x "$( command -v gotags )" ]; then
echo "INFO: Installing gotags"
go get -u ${GOTAGS_SOURCE}
fi
fi

View file

@ -1,23 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [ -z ${XDG_CONFIG_HOME+x} ]; then
echo "ERROR: 'XDG_CONFIG_HOME' is not set."
exit 1
fi
if [ -z ${XDG_DATA_HOME+x} ]; then
echo "ERROR: 'XDG_DATA_HOME' is not set."
exit 1
fi
if [ -z ${XDG_CACHE_HOME+x} ]; then
echo "ERROR: 'XDG_CACHE_HOME' is not set."
exit 1
fi
if [ -z ${XDG_STATE_HOME+x} ]; then
echo "ERROR: 'XDG_STATE_HOME' is not set."
exit 1
fi

View file

@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT_DIR="$( cd "$( dirname $0 )/../.." && pwd )"
XDG_DOTFILES_DIR="${GIT_ROOT_DIR}/config/xdg"
for f in $( find ${XDG_DOTFILES_DIR} -type f -exec basename {} \; ); do
echo "INFO: Installing ${f}"
rsync -avh ${XDG_DOTFILES_DIR}/${f} ${XDG_CONFIG_HOME}/${f} && chmod a-rwx,u+rw $_
done

View file

@ -1,46 +0,0 @@
check_xdg_settings:
@./.helpers/xdg/check-xdg-settings
.PHONY: scripts
scripts:
@./.helpers/scripts/install-scripts
.PHONY: ansible_configs
ansible_configs: check_xdg_settings
@./.helpers/ansible/generate-ansible-config
.PHONY: bash_configs
bash_configs:
@./.helpers/bash/generate-bash-config
.PHONY: git_configs
git_configs: check_xdg_settings
@./.helpers/git/generate-git-config
.PHONY: tmux_configs
tmux_configs: check_xdg_settings
@./.helpers/tmux/generate-tmux-config
.PHONY: xdg_configs
xdg_configs: check_xdg_settings
@./.helpers/xdg/generate-user-dirs-config
.PHONY: x11_configs
x11_configs: check_xdg_settings
@./.helpers/X11/generate-x11-config
.PHONY: lf_configs
lf_configs: check_xdg_settings
@./.helpers/lf/generate-lf-config
.PHONY: amfora_configs
amfora_configs: check_xdg_settings
@./.helpers/amfora/generate-amfora-config
.PHONY: dunst_configs
dunst_configs: check_xdg_settings
@./.helpers/dunst/generate-dunst-config
.PHONY: logrotate_configs
logrotate_configs: check_xdg_settings
@./.helpers/logrotate/generate-logrotate-config

58
bash/profile.gotmpl Normal file
View file

@ -0,0 +1,58 @@
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
# Environment section
## Session Paths - These will be added to PATH
{{- range $sessionPath := .BashProfile.SessionPaths -}}
{{- $fullPath := printf "%s/%s" (env "HOME") $sessionPath.Path -}}
{{ print "" }}
{{ print "" }}
### Add {{ $sessionPath.Description }} to PATH
if ! [[ "${PATH}" =~ {{ $fullPath }} ]]; then
export PATH={{ $fullPath}}:${PATH}
fi
{{- end -}}
{{ print "" }}
{{ print "" }}
## XDG Directories
{{ print "" }}
{{- range $key, $value := .BashProfile.XdgDirectories -}}
{{ print "" }}
export {{ $key }}="{{ $value }}"
{{- end -}}
{{ print "" }}
{{ print "" }}
## Extra Environment variables
{{ print "" }}
{{- range $key, $value := .BashProfile.EnvironmentVariables -}}
{{ print "" }}
export {{ $key }}="{{ $value }}"
{{- end -}}
{{ print "" }}
{{ print "" }}
# Aliases
{{ print "" }}
{{- range $key, $value := .BashProfile.Aliases -}}
{{ print "" }}
alias {{ $key }}="{{ $value }}"
{{- end -}}
{{ print "" }}
{{ print "" }}
# Bash Prompt
HOSTNAME_BG="$(tput setab 26)"
GREY_BG="$(tput setab 240)"
RESET="$(tput sgr0)"
FILEPATH_TC="$(tput setaf 81)"
WHITE_TC="$(tput setaf 255)"
PS1='${HOSTNAME_BG}${WHITE_TC}\033[1m \H ${GREY_BG} \u • ${FILEPATH_TC}\w ${RESET}\n\$ '
PS2=" -> "
# Commands
{{ print "" }}
{{- range $command := .BashProfile.Commands -}}
{{ print "" }}
## {{ $command.Description }}
{{ $command.Command }}
{{- end -}}

View file

@ -1,20 +0,0 @@
#!/usr/bin/env bash
[[ -f "${HOME}/.bashrc" ]] && source "${HOME}/.bashrc"
# Start dunst
if [ -x "$( command -v dunst )" ]; then
dunst &
fi
# Load the wallpaper
xwallpaper --stretch ~/.local/config/wallpaper/wallpaper.png &
# Rotate the home logs with logrotate
logrotate \
--state=${XDG_STATE_HOME}/logrotate/status \
--log=${LOG_HOME}/logrotate.log \
${XDG_CONFIG_HOME}/logrotate/logrotate.conf \
&
exec startdwm

View file

@ -1,10 +0,0 @@
[defaults]
nocows = 1
cow_selection = default
interpreter_python = /usr/bin/python3
[persistent_connection]
connect_timeout = 30
command_timeout = 60

View file

@ -1,5 +0,0 @@
---
all:
hosts:
localhost:
ansible_connection: local

View file

@ -1,5 +0,0 @@
[[ -f ~/.bashrc ]] && source ~/.bashrc
if [[ ! $DISPLAY && $XDG_VTNR && $XDG_VTNR -le 3 ]]; then
exec startx "$XINITRC"
fi

View file

@ -1,27 +0,0 @@
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
# == Add personal bin directory to PATH
if ! [[ "${PATH}" =~ "${HOME}/.local/bin" ]]; then
export PATH=${HOME}/.local/bin:${PATH}
fi
# == Add custom scripts directory to PATH
if ! [[ "${PATH}" =~ "${HOME}/.local/scripts" ]]; then
export PATH=${HOME}/.local/scripts:${PATH}
fi
export XDG_CONFIG_HOME=${HOME}/.local/config
export XDG_DATA_HOME=${HOME}/.local/share
export XDG_CACHE_HOME=${HOME}/.local/cache
export XDG_STATE_HOME=${HOME}/.local/state
export LOG_HOME="${XDG_STATE_HOME}/logs"
# == load all enabled scripts in ~/.bashrc.d
# == any scripts with the .disabled extension won't be loaded
for f in $(find ${XDG_CONFIG_HOME}/bash/bashrc.d -mindepth 1 -maxdepth 1 -type f -not -iname *.disabled | sort); do source $f; done
# == Run tmux in the default session whenever a terminal session starts
if command -v tmux &> /dev/null && [ -n "$PS1" ] && [[ ! "$TERM" =~ screen ]] && [[ ! "$TERM" =~ tmux ]] && [ -z "$TMUX" ]; then
exec tmux new-session -A -s main
fi

View file

@ -1,4 +0,0 @@
## Description: Bash configuration for X11.
## vim: ft=sh :
export XINITRC=${XDG_CONFIG_HOME}/X11/xinitrc

View file

@ -1,31 +0,0 @@
## Description: All aliases are defined here.
## vim: ft=sh :
alias ls='ls --color=auto'
alias ll='ls -laF'
alias la='ls -A'
alias l='ls -CF'
alias rm='rm -i'
alias mv='mv -i'
alias cp='cp -i'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
alias systemctl='sudo systemctl'
alias journalctl='sudo journalctl'
# aliases for pacman
if [[ -x $( command -v pacman ) ]]; then
alias pacman='sudo pacman'
alias pacupdate='sudo pacman -Syu --noconfirm'
fi
alias pwgen="pwgen -s -c -n"
alias dc="docker-compose"
alias vim="nvim"
alias view="nvim -R"
alias vimdiff="nvim -d"

View file

@ -1,7 +0,0 @@
## Description: Bash configuration for Ansible.
## vim: ft=sh :
export ANSIBLE_CONFIG=${XDG_CONFIG_HOME}/ansible/ansible.cfg
export ANSIBLE_INVENTORY=${XDG_CONFIG_HOME}/ansible/hosts.yml
export ANSIBLE_LOCAL_TEMP=${XDG_CACHE_HOME}/ansible
export ANSIBLE_COLLECTIONS_PATH=${XDG_DATA_HOME}/ansible/collections

View file

@ -1,3 +0,0 @@
## vim: ft=sh :
export DOCKER_CONFIG=${XDG_CONFIG_HOME}/docker

View file

@ -1,8 +0,0 @@
## File: ~/.bashrc.d/exercism
## Description: Bash completion for exercism
## vim: ft=sh :
# == Bash completion for exercism
if [ -f ${HOME}/.config/exercism/exercism_completion.bash ]; then
source ~/.config/exercism/exercism_completion.bash
fi

View file

@ -1,29 +0,0 @@
## Description: All aliases are defined here.
## vim: ft=sh :
# mkcd creates a new directory (including the parent directories if they don't exist)
# and makes it the current directory.
mkcd() {
mkdir -p $1
cd $1
}
# go_up() navigates up a specified number of parent directories.
# Inspired from DT's up() function:
# https://gitlab.com/dwt1/dotfiles/-/blob/80632c5cad56ac96955e0ca1d582a4b59741bace/.bashrc#L109
go_up() {
local d=""
local steps="$1"
if [ -z "$steps" ] || [ "$steps" -lt 1 ]; then
steps=1
fi
for ((i=1; i<=steps; i++)); do
d="../$d"
done
if ! cd "$d"; then
echo "Unable to go up $steps directories."
fi
}

View file

@ -1,10 +0,0 @@
## File: ~/.bashrc.d/git
## Description: Bash configuration for Git
## vim: ft=sh :
if [ -f /usr/share/bash-completion/completions/git ]; then
source /usr/share/bash-completion/completions/git
fi
alias g="git"
complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g

View file

@ -1,4 +0,0 @@
## vim: ft=sh :
export GPG_TTY=$(tty)
export GNUPGHOME=${XDG_DATA_HOME}/gnupg

View file

@ -1,30 +0,0 @@
## Description: Bash configuration for the Go setup
## vim: ft=sh :
if [ -f "${HOME}/.local/software/go/bin/go" ]; then
export GOPATH=${XDG_DATA_HOME}/go
export GOROOT=${HOME}/.local/software/go
export GOBIN=${HOME}/.local/goblin
if ! [[ "${PATH}" =~ "${GOBIN}" ]]; then
export PATH=${GOBIN}:${PATH}
fi
if ! [ -d "${GOPATH}" ]; then
mkdir ${GOPATH}
fi
if ! [ -d "${GOBIN}" ]; then
mkdir ${GOBIN}
fi
export GOOS=linux
export GOARCH=amd64
export CGO_ENABLED=0
fi
# Magefile stuff
if [ -x "$( command -v mage )" ]; then
export MAGEFILE_CACHE=${XDG_CACHE_HOME}/magefile
export MAGEFILE_ENABLE_COLOR=true
fi

View file

@ -1,21 +0,0 @@
## File: ~/.bashrc.d/history
## Description: Configuration for Bash history
## vim: ft=sh :
# The name of the history file.
HISTFILE=${XDG_STATE_HOME}/bash/history
# Don't put duplicate lines or lines starting with space in the history.
HISTCONTROL=ignoreboth
# The maximum number of lines in the history file.
HISTFILESIZE=10000
# The number of commands to remember in the command history.
HISTSIZE=1000
# Display the timestamp of each command in history using the below format.
HISTTIMEFORMAT="%d/%m/%y %T: "
# Append to the history file, don't overwrite it.
shopt -s histappend

View file

@ -1,9 +0,0 @@
## Description: Bash settings for kubectl
## vim: ft=sh :
export KUBECONFIG=${XDG_CONFIG_HOME}/kube/config
if [ -x "$( command -v kubectl )" ]; then
source <(kubectl completion bash)
alias k='kubectl'
complete -F __start_kubectl k
fi

View file

@ -1,3 +0,0 @@
## vim: ft=sh :
export LESSHISTFILE=${XDG_STATE_HOME}/less/history

View file

@ -1,18 +0,0 @@
#!/bin/bash
lf () {
tmp="$(mktemp)"
logpath="${LOG_HOME}/lf.log"
command lf --last-dir-path="$tmp" --log="$logpath" "$@"
if [ -f "$tmp" ]; then
dir="$(cat "$tmp")"
rm -f "$tmp"
if [ -d "$dir" ]; then
if [ "$dir" != "$(pwd)" ]; then
cd "$dir"
fi
fi
fi
}

View file

@ -1,12 +0,0 @@
if [[ -d "$HOME/.local/software" ]]; then
for dir in ${HOME}/.local/software/*; do
dir=${dir:A}
if [[ -d "$dir/bin" ]]; then
if ! [[ "${PATH}" =~ "$dir/bin" ]]; then
export PATH="$dir/bin:$PATH"
fi
fi
done
fi
unset dir

View file

@ -1,3 +0,0 @@
## vim: ft=sh :
export MINIKUBE_HOME=${XDG_DATA_HOME}/minikube

View file

@ -1,21 +0,0 @@
## Description: Bash configuration where we can't categorize anywhere else.
## vim: ft=sh :
export BROWSER=firefox
export EDITOR=nvim
export TERMINAL=st
export LANG=en_GB.UTF-8
export MANPAGER="nvim +Man!"
# == vi mode in Bash
set -o vi
# == make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# == ensure that programmable completion features are enabled.
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi

View file

@ -1,9 +0,0 @@
## Description: Bash configuration for pass
## vim: ft=sh :
export PASSWORD_STORE_DIR=${XDG_DATA_HOME}/pass
if ! [ -d "${PASSWORD_STORE_DIR}" ]; then
mkdir ${PASSWORD_STORE_DIR}
chmod 0700 ${PASSWORD_STORE_DIR}
fi

View file

@ -1,32 +0,0 @@
## File: ~/.bashrc.d/prompt
## Description: Configuration for the Bash prompt
## vim: ft=sh :
get_date() {
date '+%d.%m.%Y'
}
if [ -f /usr/share/git/completion/git-prompt.sh ]; then
source /usr/share/git/completion/git-prompt.sh
elif [ -f /usr/share/git/git-prompt.sh ]; then
source /usr/share/git/git-prompt.sh
elif [ -f /usr/lib/git-core/git-sh-prompt ]; then
source /usr/lib/git-core/git-sh-prompt
elif [ -f /etc/bash_completion.d/git-prompt ]; then
source /etc/bash_completion.d/git-prompt
elif [ -f ${HOME}/.git-prompt ]; then
source ${HOME}/.git-prompt
fi
GIT_PS1_SHOWDIRTYSTATE="true"
GIT_PS1_SHOWUPSTREAM="auto"
GIT_PS1_STATESEPARATOR=": "
DATE_BG="$(tput setab 68)"
GREY_BG="$(tput setab 240)"
RESET="$(tput sgr0)"
FILEPATH_TC="$(tput setaf 81)"
WHITE_TC="$(tput setaf 255)"
GREEN_TC="$(tput setaf 2)"
PS1='${DATE_BG}${WHITE_TC}$(get_date) ${GREY_BG} \u • ${FILEPATH_TC}\033[1m\w ${RESET} ${GREEN_TC}\033[3m$(__git_ps1 "git:(%s)")${RESET}\n\$ '
PS2=" -> "

View file

@ -1,6 +0,0 @@
## File: ~/.bashrc.d/pulumi
## Description: Bash configuration for the pulumi setup
## vim: ft=sh :
export PULUMI_SKIP_UPDATE_CHECK="true"
export PULUMI_HOME="${XDG_DATA_HOME}/pulumi"

View file

@ -1,4 +0,0 @@
## vim: ft=sh :
export TERMINFO=${XDG_DATA_HOME}/terminfo
export TERMINFO_DIRS=${TERMINFO}:/usr/share/terminfo

View file

@ -1,3 +0,0 @@
## vim: ft=sh :
export VAGRANT_HOME=${XDG_DATA_HOME}/vagrant

View file

@ -1,8 +0,0 @@
## File: ~/.bashrc.d/vault
## Description: Bash configuration for vault setup.
## vim: ft=sh :
# bash autocompletion for vault
if [ -f "${HOME}/.local/bin/vault" ]; then
complete -C /home/dananglin/.local/bin/vault vault
fi

View file

@ -1,438 +0,0 @@
# See dunst(5) for all configuration options
[global]
### Display ###
# Which monitor should the notifications be displayed on.
monitor = 0
# Display notification on focused monitor. Possible modes are:
# mouse: follow mouse pointer
# keyboard: follow window with keyboard focus
# none: don't follow anything
#
# "keyboard" needs a window manager that exports the
# _NET_ACTIVE_WINDOW property.
# This should be the case for almost all modern window managers.
#
# If this option is set to mouse or keyboard, the monitor option
# will be ignored.
follow = keyboard
### Geometry ###
# dynamic width from 0 to 300
# width = (0, 300)
# constant width of 300
width = 300
# The maximum height of a single notification, excluding the frame.
height = 300
# Position the notification in the top right corner
origin = top-right
# Offset from the origin
offset = 20x40
# Scale factor. It is auto-detected if value is 0.
scale = 0
# Maximum number of notification (0 means no limit)
notification_limit = 3
### Progress bar ###
# Turn on the progess bar. It appears when a progress hint is passed with
# for example dunstify -h int:value:12
progress_bar = true
# Set the progress bar height. This includes the frame, so make sure
# it's at least twice as big as the frame width.
progress_bar_height = 10
# Set the frame width of the progress bar
progress_bar_frame_width = 1
# Set the minimum width for the progress bar
progress_bar_min_width = 150
# Set the maximum width for the progress bar
progress_bar_max_width = 300
# Show how many messages are currently hidden (because of
# notification_limit).
indicate_hidden = yes
# The transparency of the window. Range: [0; 100].
# This option will only work if a compositing window manager is
# present (e.g. xcompmgr, compiz, etc.). (X11 only)
transparency = 0
# Draw a line of "separator_height" pixel height between two
# notifications.
# Set to 0 to disable.
separator_height = 2
# Padding between text and separator.
padding = 8
# Horizontal padding.
horizontal_padding = 8
# Padding between text and icon.
text_icon_padding = 0
# Defines width in pixels of frame around the notification window.
# Set to 0 to disable.
frame_width = 2
# Defines color of the frame around the notification window.
frame_color = "#bb9af7"
# Define a color for the separator.
# possible values are:
# * auto: dunst tries to find a color fitting to the background;
# * foreground: use the same color as the foreground;
# * frame: use the same color as the frame;
# * anything else will be interpreted as a X color.
separator_color = frame
# Sort messages by urgency.
sort = yes
# Don't remove messages, if the user is idle (no mouse or keyboard input)
# for longer than idle_threshold seconds.
# Set to 0 to disable.
# A client can set the 'transient' hint to bypass this. See the rules
# section for how to disable this if necessary
idle_threshold = 120
### Text ###
font = Source Code Pro 12
# The spacing between lines. If the height is smaller than the
# font height, it will get raised to the font height.
line_height = 0
# Possible values are:
# full: Allow a small subset of html markup in notifications:
# <b>bold</b>
# <i>italic</i>
# <s>strikethrough</s>
# <u>underline</u>
#
# For a complete reference see
# <https://docs.gtk.org/Pango/pango_markup.html>.
#
# strip: This setting is provided for compatibility with some broken
# clients that send markup even though it's not enabled on the
# server. Dunst will try to strip the markup but the parsing is
# simplistic so using this option outside of matching rules for
# specific applications *IS GREATLY DISCOURAGED*.
#
# no: Disable markup parsing, incoming notifications will be treated as
# plain text. Dunst will not advertise that it has the body-markup
# capability if this is set as a global setting.
#
# It's important to note that markup inside the format option will be parsed
# regardless of what this is set to.
markup = full
# The format of the message. Possible variables are:
# %a appname
# %s summary
# %b body
# %i iconname (including its path)
# %I iconname (without its path)
# %p progress value if set ([ 0%] to [100%]) or nothing
# %n progress value if set without any extra characters
# %% Literal %
# Markup is allowed
format = "<b>%s</b>\n%b"
# Alignment of message text.
# Possible values are "left", "center" and "right".
alignment = left
# Vertical alignment of message text and icon.
# Possible values are "top", "center" and "bottom".
vertical_alignment = center
# Show age of message if message is older than show_age_threshold
# seconds.
# Set to -1 to disable.
show_age_threshold = 60
# Specify where to make an ellipsis in long lines.
# Possible values are "start", "middle" and "end".
ellipsize = middle
# Ignore newlines '\n' in notifications.
ignore_newline = no
# Stack together notifications with the same content
stack_duplicates = true
# Hide the count of stacked notifications with the same content
hide_duplicate_count = false
# Display indicators for URLs (U) and actions (A).
show_indicators = yes
### Icons ###
# Align icons left/right/top/off
icon_position = left
# Scale small icons up to this size, set to 0 to disable. Helpful
# for e.g. small files or high-dpi screens. In case of conflict,
# max_icon_size takes precedence over this.
min_icon_size = 0
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 32
# Paths to default icons.
icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
### History ###
# Should a notification popped up from history be sticky or timeout
# as if it would normally do.
sticky_history = yes
# Maximum amount of notifications kept in history
history_length = 20
### Misc/Advanced ###
# dmenu path.
dmenu = /usr/bin/dmenu -p dunst:
# Browser for opening urls in context menu.
browser = /usr/bin/xdg-open
# Always run rule-defined scripts, even if the notification is suppressed
always_run_script = true
# Define the title of the windows spawned by dunst
title = Dunst
# Define the class of the windows spawned by dunst
class = Dunst
# Define the corner radius of the notification window
# in pixel size. If the radius is 0, you have no rounded
# corners.
# The radius will be automatically lowered if it exceeds half of the
# notification height to avoid clipping text and/or icons.
corner_radius = 2
# Ignore the dbus closeNotification message.
# Useful to enforce the timeout set by dunst configuration. Without this
# parameter, an application may close the notification sent before the
# user defined timeout.
ignore_dbusclose = false
### Wayland ###
# These settings are Wayland-specific. They have no effect when using X11
# Uncomment this if you want to let notications appear under fullscreen
# applications (default: overlay)
# layer = top
# Set this to true to use X11 output on Wayland.
force_xwayland = false
### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support.
# This setting is provided for compatibility with older nVidia drivers that
# do not support RandR and using it on systems that support RandR is highly
# discouraged.
#
# By enabling this setting dunst will not be able to detect when a monitor
# is connected or disconnected which might break follow mode if the screen
# layout changes.
force_xinerama = false
### mouse
# Defines list of actions for each mouse event
# Possible values are:
# * none: Don't do anything.
# * do_action: Invoke the action determined by the action_name rule. If there is no
# such action, open the context menu.
# * open_url: If the notification has exactly one url, open it. If there are multiple
# ones, open the context menu.
# * close_current: Close current notification.
# * close_all: Close all notifications.
# * context: Open context menu for the notification.
# * context_all: Open context menu for all notifications.
# These values can be strung together for each mouse event, and
# will be executed in sequence.
mouse_left_click = close_current
mouse_middle_click = do_action, close_current
mouse_right_click = close_all
# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
# Calculate the dpi to use on a per-monitor basis.
# If this setting is enabled the Xft.dpi value will be ignored and instead
# dunst will attempt to calculate an appropriate dpi value for each monitor
# using the resolution and physical size. This might be useful in setups
# where there are multiple screens with very different dpi values.
per_monitor_dpi = false
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
# Otherwise the "#" and following would be interpreted as a comment.
background = "#222222"
foreground = "#888888"
timeout = 10
# Icon for notifications with low urgency, uncomment to enable
#default_icon = /path/to/icon
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 10
# Icon for notifications with normal urgency, uncomment to enable
#default_icon = /path/to/icon
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
frame_color = "#ff0000"
timeout = 0
# Icon for notifications with critical urgency, uncomment to enable
#default_icon = /path/to/icon
# Every section that isn't one of the above is interpreted as a rules to
# override settings for certain messages.
#
# Messages can be matched by
# appname (discouraged, see desktop_entry)
# body
# category
# desktop_entry
# icon
# match_transient
# msg_urgency
# stack_tag
# summary
#
# and you can override the
# background
# foreground
# format
# frame_color
# fullscreen
# new_icon
# set_stack_tag
# set_transient
# set_category
# timeout
# urgency
# icon_position
# skip_display
# history_ignore
# action_name
# word_wrap
# ellipsize
# alignment
# hide_text
#
# Shell-like globbing will get expanded.
#
# Instead of the appname filter, it's recommended to use the desktop_entry filter.
# GLib based applications export their desktop-entry name. In comparison to the appname,
# the desktop-entry won't get localized.
#
# SCRIPTING
# You can specify a script that gets run when the rule matches by
# setting the "script" option.
# The script will be called as follows:
# script appname summary body icon urgency
# where urgency can be "LOW", "NORMAL" or "CRITICAL".
#
# NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules.
# Disable the transient hint so that idle_threshold cannot be bypassed from the
# client
#[transient_disable]
# match_transient = yes
# set_transient = no
#
# Make the handling of transient notifications more strict by making them not
# be placed in history.
#[transient_history_ignore]
# match_transient = yes
# history_ignore = yes
# fullscreen values
# show: show the notifications, regardless if there is a fullscreen window opened
# delay: displays the new notification, if there is no fullscreen window active
# If the notification is already drawn, it won't get undrawn.
# pushback: same as delay, but when switching into fullscreen, the notification will get
# withdrawn from screen again and will get delayed like a new notification
#[fullscreen_delay_everything]
# fullscreen = delay
#[fullscreen_show_critical]
# msg_urgency = critical
# fullscreen = show
#[espeak]
# summary = "*"
# script = dunst_espeak.sh
#[script-test]
# summary = "*script*"
# script = dunst_test.sh
#[ignore]
# # This notification will not be displayed
# summary = "foobar"
# skip_display = true
#[history-ignore]
# # This notification will not be saved in history
# summary = "foobar"
# history_ignore = yes
#[skip-display]
# # This notification will not be displayed, but will be included in the history
# summary = "foobar"
# skip_display = yes
#[signed_on]
# appname = Pidgin
# summary = "*signed on*"
# urgency = low
#
#[signed_off]
# appname = Pidgin
# summary = *signed off*
# urgency = low
#
#[says]
# appname = Pidgin
# summary = *says*
# urgency = critical
#
#[twitter]
# appname = Pidgin
# summary = *twitter.com*
# urgency = normal
#
#[stack-volumes]
# appname = "some_volume_notifiers"
# set_stack_tag = "volume"
#
# vim: ft=cfg

View file

@ -1,7 +0,0 @@
${LOG_HOME}/*.log ${LOG_HOME}/nvim/*.log {
rotate 3
size 10M
nocompress
notifempty
copytruncate
}

View file

@ -269,60 +269,53 @@ entries_per_page = 20
[theme] [theme]
# Atom One Dark theme ported to Amfora # Tokyo Night
# by Serge Tymoshenko <serge@tymo.name>
# Link: https://github.com/makeworld-the-better-one/amfora/blob/master/contrib/themes/one_dark.toml
bg = "#282c34" bg = "#1a1b26"
fg = "#abb2bf" fg = "#a9b1d6"
tab_num = "#abb2bf" tab_num = "#565f89"
tab_divider = "#abb2bf" tab_divider = "#3b4261"
bottombar_bg = "#abb2bf" bottombar_label = "#7aa2f7"
bottombar_text = "#282c34" bottombar_text = "#7aa2f7"
bottombar_label = "#282c34" bottombar_bg = "#1f2335"
scrollbar = "#565f89"
hdg_1 = "#e06c75" hdg_1 = "#f7768e"
hdg_2 = "#c678dd" hdg_2 = "#7dcfff"
hdg_3 = "#c678dd" hdg_3 = "#bb9af7"
amfora_link = "#61afef" amfora_link = "#73daca"
foreign_link = "#56b6c2" foreign_link = "#b4f9f8"
link_number = "#abb2bf" link_number = "#ff9e64"
regular_text = "#abb2bf" regular_text = "#a9b1d6"
quote_text = "#98c379" quote_text = "#e0af68"
preformatted_text = "#e5c07b" preformatted_text = "#2ac3de"
list_text = "#abb2bf" list_text = "#a9b1d6"
btn_bg = "#282c34" btn_bg = "#414868"
btn_text = "#abb2bf" btn_text = "#7aa2f7"
dl_choice_modal_bg = "#98c379" dl_choice_modal_bg = "#414868"
dl_choice_modal_text = "#282c34" dl_choice_modal_text = "#c0caf5"
dl_modal_bg = "#414868"
dl_modal_text = "#c0caf5"
info_modal_bg = "#414868"
info_modal_text = "#c0caf5"
error_modal_bg = "#414868"
error_modal_text = "#f7768e"
yesno_modal_bg = "#414868"
yesno_modal_text = "#e0af68"
tofu_modal_bg = "#414868"
tofu_modal_text = "#2ac3de"
subscription_modal_bg = "#414868"
subscription_modal_text = "#bb9af7"
dl_modal_bg = "#98c379" input_modal_bg = "#414868"
dl_modal_text = "#282c34" input_modal_text = "#c0caf5"
input_modal_field_bg = "#33467c"
input_modal_field_text = "#a9b1d6"
info_modal_bg = "#98c379" bkmk_modal_bg = "#414868"
info_modal_text = "#282c34" bkmk_modal_text = "#c0caf5"
bkmk_modal_label = "#c0caf5"
error_modal_bg = "#e06c75" bkmk_modal_field_bg = "#33467c"
error_modal_text = "#282c34" bkmk_modal_field_text = "#a9b1d6"
yesno_modal_bg = "#e5c07b"
yesno_modal_text = "#282c34"
tofu_modal_bg = "#e5c07b"
tofu_modal_text = "#282c34"
input_modal_bg = "#98c379"
input_modal_text = "#282c34"
input_modal_field_bg = "#282c34"
input_modal_field_text = "#abb2bf"
bkmk_modal_bg = "#98c379"
bkmk_modal_text = "#282c34"
bkmk_modal_label = "#282c34"
bkmk_modal_field_bg = "#282c34"
bkmk_modal_field_text = "#abb2bf"
subscription_modal_bg = "#c678dd"
subscription_modal_text = "#282c34"

239
files/foot/foot.ini Normal file
View file

@ -0,0 +1,239 @@
# -*- conf -*-
# shell=$SHELL (if set, otherwise user's default shell from /etc/passwd)
term=foot
login-shell=no
app-id=foot
title="Dan's Foot Terminal"
locked-title=yes
# Font configuration
font=SauceCodePro Nerd Font Mono:size=10.50
font-bold=SauceCodePro Nerd Font Mono:size=10.50:weight=bold
font-italic=SauceCodePro Nerd Font Mono:size=10.50:slant=italic
font-bold-italic=SauceCodePro Nerd Font Mono:size=10.50:weight=bold:slant=italic
# font-size-adjustment=0.5
# line-height=<font metrics>
# letter-spacing=0
# horizontal-letter-offset=0
# vertical-letter-offset=0
# underline-offset=<font metrics>
# underline-thickness=<font underline thickness>
# strikeout-thickness=<font strikeout thickness>
# box-drawings-uses-font-glyphs=no
# dpi-aware=no
# initial-window-size-pixels=700x500 # Or,
# initial-window-size-chars=<COLSxROWS>
# initial-window-mode=windowed
# pad=0x0 # optionally append 'center'
# resize-by-cells=yes
# resize-keep-grid=yes
# resize-delay-ms=100
bold-text-in-bright=yes
# word-delimiters=,│`|:"'()[]{}<>
# selection-target=primary
# workers=<number of logical CPUs>
# utmp-helper=/usr/lib/utempter/utempter # When utmp backend is libutempter (Linux)
# utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ulog (FreeBSD)
[environment]
# name=value
[bell]
# urgent=no
# notify=no
# visual=no
# command=
# command-focused=no
#[desktop-notifications]
# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}
# command-action-argument=--action ${action-name}=${action-label}
# close=""
# inhibit-when-focused=yes
[scrollback]
lines=10000
multiplier=1.0
# indicator-position=relative
# indicator-format=""
[url]
launch=xdg-open ${url}
# label-letters=sadfjklewcmpgh
# osc8-underline=url-mode
protocols=http, https, file
# uri-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+="'()[]
[cursor]
style=beam
beam-thickness=1.5
unfocused-style=hollow
blink=no
# blink-rate=500
# color=<inverse foreground/background>
# underline-thickness=<font underline thickness>
[mouse]
# hide-when-typing=no
# alternate-scroll-mode=yes
[touch]
# long-press-delay=400
[colors]
alpha=1.0
background=1a1b26
foreground=c0caf5
flash=7f7f00
flash-alpha=0.5
## Normal/regular colors (color palette 0-7)
regular0=15161e # black
regular1=f7768e # red
regular2=9ece6a # green
regular3=e0af68 # yellow
regular4=7aa2f7 # blue
regular5=bb9af7 # magenta
regular6=7dcfff # cyan
regular7=a9b1d6 # white
## Bright colors (color palette 8-15)
bright0=414868 # bright black
bright1=f7768e # bright red
bright2=9ece6a # bright green
bright3=e0af68 # bright yellow
bright4=7aa2f7 # bright blue
bright5=bb9af7 # bright magenta
bright6=7dcfff # bright cyan
bright7=c0caf5 # bright white
## dimmed colors (see foot.ini(5) man page)
# dim0=<not set>
# ...
# dim7=<not-set>
## The remaining 256-color palette
# 16 = <256-color palette #16>
# ...
# 255 = <256-color palette #255>
## Misc colors
# selection-foreground=<inverse foreground/background>
# selection-background=<inverse foreground/background>
# jump-labels=<regular0> <regular3> # black-on-yellow
# scrollback-indicator=<regular0> <bright4> # black-on-bright-blue
# search-box-no-match=<regular0> <regular1> # black-on-red
# search-box-match=<regular0> <regular3> # black-on-yellow
# urls=<regular3>
[csd]
# preferred=server
# size=26
# font=<primary font>
# color=<foreground color>
# hide-when-maximized=no
# double-click-to-maximize=yes
# border-width=0
# border-color=<csd.color>
# button-width=26
# button-color=<background color>
# button-minimize-color=<regular4>
# button-maximize-color=<regular2>
# button-close-color=<regular1>
[key-bindings]
# scrollback-up-page=Shift+Page_Up
# scrollback-up-half-page=none
# scrollback-up-line=none
# scrollback-down-page=Shift+Page_Down
# scrollback-down-half-page=none
# scrollback-down-line=none
# scrollback-home=none
# scrollback-end=none
# clipboard-copy=Control+Shift+c XF86Copy
# clipboard-paste=Control+Shift+v XF86Paste
# primary-paste=Shift+Insert
# search-start=Control+Shift+r
# font-increase=Control+plus Control+equal Control+KP_Add
# font-decrease=Control+minus Control+KP_Subtract
# font-reset=Control+0 Control+KP_0
# spawn-terminal=Control+Shift+n
# minimize=none
# maximize=none
# fullscreen=none
# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
# pipe-selected=[xargs -r firefox] none
# pipe-command-output=[wl-copy] none # Copy last command's output to the clipboard
# show-urls-launch=Control+Shift+o
# show-urls-copy=none
# show-urls-persistent=none
# prompt-prev=Control+Shift+z
# prompt-next=Control+Shift+x
# unicode-input=Control+Shift+u
# noop=none
[search-bindings]
# cancel=Control+g Control+c Escape
# commit=Return
# find-prev=Control+r
# find-next=Control+s
# cursor-left=Left Control+b
# cursor-left-word=Control+Left Mod1+b
# cursor-right=Right Control+f
# cursor-right-word=Control+Right Mod1+f
# cursor-home=Home Control+a
# cursor-end=End Control+e
# delete-prev=BackSpace
# delete-prev-word=Mod1+BackSpace Control+BackSpace
# delete-next=Delete
# delete-next-word=Mod1+d Control+Delete
# extend-char=Shift+Right
# extend-to-word-boundary=Control+w Control+Shift+Right
# extend-to-next-whitespace=Control+Shift+w
# extend-line-down=Shift+Down
# extend-backward-char=Shift+Left
# extend-backward-to-word-boundary=Control+Shift+Left
# extend-backward-to-next-whitespace=none
# extend-line-up=Shift+Up
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
# primary-paste=Shift+Insert
# unicode-input=none
# quit=none
# scrollback-up-page=Shift+Page_Up
# scrollback-up-half-page=none
# scrollback-up-line=none
# scrollback-down-page=Shift+Page_Down
# scrollback-down-half-page=none
# scrollback-down-line=none
# scrollback-home=none
# scrollback-end=none
[url-bindings]
# cancel=Control+g Control+c Control+d Escape
# toggle-url-visible=t
[text-bindings]
# \x03=Mod4+c # Map Super+c -> Ctrl+c
[mouse-bindings]
# scrollback-up-mouse=BTN_WHEEL_BACK
# scrollback-down-mouse=BTN_WHEEL_FORWARD
# font-increase=Control+BTN_WHEEL_BACK
# font-decrease=Control+BTN_WHEEL_FORWARD
# selection-override-modifiers=Shift
# primary-paste=BTN_MIDDLE
# select-begin=BTN_LEFT
# select-begin-block=Control+BTN_LEFT
# select-extend=BTN_RIGHT
# select-extend-character-wise=Control+BTN_RIGHT
# select-word=BTN_LEFT-2
# select-word-whitespace=Control+BTN_LEFT-2
# select-quote = BTN_LEFT-3
# select-row=BTN_LEFT-4
# vim: ft=dosini

View file

@ -1,6 +1,6 @@
/tags /.notes/*
/notes/* /.hack/*
/tmp/* /.tmp/*
library/__pycache__/ library/__pycache__/
library/*.pyc library/*.pyc

77
files/river/init Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Set the background and border colours
riverctl background-color 0x15161e
riverctl border-color-focused 0xbb9af7 # Magenta
riverctl border-color-unfocused 0x444444 # Grey
# Super+Shift+T to spawn an instance of foot
riverctl map normal Super+Shift T spawn foot
# Super+Shift+B to spawn an instance of firefox
riverctl map normal Super+Shift B spawn firefox
# Super+Shift+Q to exit river
riverctl map normal Super+Shift Q exit
# Super+C to close the focused view
riverctl map normal Super C close
# Super+H to shift the focus to the previous view in the layout stack
# Super+L to shift the focus to the next view in the layout stack
riverctl map normal Super H focus-view previous
riverctl map normal Super L focus-view next
# Super+Shift+H to swap the focused view with the previous view in the layout stack
# Super+Shift+L to swap the focused view with the next view in the layout stack
riverctl map normal Super+Shift H swap previous
riverctl map normal Super+Shift L swap next
# Super+Period to focus the next output
# Super+Comma to focus the previous output
riverctl map normal Super Period focus-output next
riverctl map normal Super Comma focus-output previous
# Super+Shift+Period to send the focused view to the next output
# Super+Shift+Comma to send the focused view to the previous output
riverctl map normal Super+Shift Period send-to-output next
riverctl map normal Super+Shift Comma send-to-output previous
# Super+Return to bump the focused view to the top of the layout stack
riverctl map normal Super Return zoom
# Super+J to increase the main ratio of rivertile(1)
# Super+K to decrease the main ratio of rivertile(1)
riverctl map normal Super J send-layout-cmd rivertile "main-ratio +0.05"
riverctl map normal Super K send-layout-cmd rivertile "main-ratio -0.05"
# Super+Shift+J to increment the main count of rivertile(1)
# Super+Shift+K to decrement the main count of rivertile(1)
riverctl map normal Super+Shift J send-layout-cmd rivertile "main-count +1"
riverctl map normal Super+Shift K send-layout-cmd rivertile "main-count -1"
# Super+Alt+{H,J,K,L} to move views
riverctl map normal Super+Alt H move left 100
riverctl map normal Super+Alt J move down 100
riverctl map normal Super+Alt K move up 100
riverctl map normal Super+Alt L move right 100
# Super+Alt+Control+{H,J,K,L} to snap views to screen edges
riverctl map normal Super+Alt+Control H snap left
riverctl map normal Super+Alt+Control J snap down
riverctl map normal Super+Alt+Control K snap up
riverctl map normal Super+Alt+Control L snap right
# Super+Alt+Shift+{H,J,K,L} to resize views
riverctl map normal Super+Alt+Shift H resize horizontal -100
riverctl map normal Super+Alt+Shift J resize vertical 100
riverctl map normal Super+Alt+Shift K resize vertical -100
riverctl map normal Super+Alt+Shift L resize horizontal 100
# Configure the keyboard layout
riverctl keyboard-layout gb
# Set the default layout generator to be rivertile and start it.
# River will send the process group of the init executable SIGTERM on exit.
riverctl default-layout rivertile
rivertile -view-padding 6 -outer-padding 6 &

View file

@ -1,6 +1,8 @@
#set-option -g default-terminal "tmux-256color"
set-option -g default-terminal "$TERM" set-option -g default-terminal "$TERM"
set-option -ag terminal-overrides ",$TERM:Tc" set-option -ag terminal-overrides ",$TERM:Tc"
set-option -g default-shell /usr/bin/bash # set-option -g default-shell /usr/bin/env bash
set-option -g default-command "/usr/bin/env bash"
set-option -g mouse off set-option -g mouse off
set-window-option -g status-keys vi set-window-option -g status-keys vi

11
files/zk/config.toml Normal file
View file

@ -0,0 +1,11 @@
[note]
default-title = "Note"
extension = "md"
filename = "{{date now '%Y%m%d%H%M%S'}}-{{slug title}}"
language = "en"
[extra]
author = "dananglin"
[tool]
editor = "nvim"

197
hosts/falcon.json Normal file
View file

@ -0,0 +1,197 @@
{
"bashProfile": {
"manage": true,
"filename": ".bashrc",
"sessionPaths": [
{
"path": ".local/software/element-desktop/bin",
"description": "Element"
},
{
"path": ".local/software/firefox/bin",
"description": "Firefox"
},
{
"path": ".local/software/go/bin",
"description": "Go"
},
{
"path": ".local/software/lua/bin",
"description": "Lua"
},
{
"path": ".local/software/lua-language-server/bin",
"description": "the Lua Language Server"
},
{
"path": ".local/software/luarocks/bin",
"description": "Luarocks"
},
{
"path": ".local/software/neovim/bin",
"description": "Neovim"
},
{
"path": ".local/software/node/bin",
"description": "Node"
},
{
"path": ".local/software/pulumi/bin",
"description": "Pulumi"
},
{
"path": ".local/software/radicle/bin",
"description": "Radicle"
},
{
"path": ".local/software/ruby/bin",
"description": "Ruby"
},
{
"path": ".local/bin",
"description": "the local bin directory"
},
{
"path": "Applications",
"description": "the AppImage directory"
},
{
"path": ".local/goblin",
"description": "the local gobin directory"
},
{
"path": ".local/scripts",
"description": "the directory of custom scripts"
}
],
"xdgDirectories": {
"XDG_CACHE_HOME": "${HOME}/.local/cache",
"XDG_CONFIG_HOME": "${HOME}/.local/config",
"XDG_DATA_HOME": "${HOME}/.local/share",
"XDG_STATE_HOME": "${HOME}/.local/state"
},
"environmentVariables": {
"LOG_HOME": "${XDG_STATE_HOME}/logs",
"DOCKER_CONFIG": "${XDG_CONFIG_HOME}/docker",
"GPG_TTY": "$(tty)",
"GNUPGHOME": "${XDG_DATA_HOME}/gnupg",
"GOPATH": "${XDG_DATA_HOME}/go",
"GOROOT": "${HOME}/.local/software/go",
"GOBIN": "${HOME}/.local/goblin",
"GOOS": "linux",
"GOARCH": "amd64",
"CGO_ENABLED": "0",
"MAGEFILE_CACHE": "${XDG_CACHE_HOME}/magefile",
"MAGEFILE_ENABLE_COLOR": "true",
"HISTFILE": "${XDG_STATE_HOME}/bash/history",
"HISTCONTROL": "ignoreboth",
"HISTFILESIZE": "10000",
"HISTSIZE": "1000",
"HISTTIMEFORMAT": "%d/%m/%y %T: ",
"KUBECONFIG": "${XDG_CONFIG_HOME}/kube/config",
"LESSHISTFILE": "${XDG_STATE_HOME}/less/history",
"MINIKUBE_HOME": "${XDG_DATA_HOME}/minikube",
"BROWSER": "firefox",
"EDITOR": "nvim",
"TERMINAL": "st",
"LANG": "en_GB.UTF-8",
"MANPAGER": "nvim +Man!",
"PULUMI_SKIP_UPDATE_CHECK": "true",
"PULUMI_HOME": "${XDG_DATA_HOME}/pulumi",
"RAD_HOME": "${XDG_DATA_HOME}/radicle",
"TERMINFO": "${XDG_DATA_HOME}/terminfo",
"TERMINFO_DIRS": "${TERMINFO}:/usr/share/terminfo",
"VAGRANT_HOME": "${XDG_DATA_HOME}/vagrant"
},
"aliases": {
"ls": "ls --color=auto",
"ll": "ls -laF",
"la": "ls -A",
"l": "ls -CF",
"rm": "rm -i",
"mv": "mv -i",
"cp": "cp -i",
"grep": "grep --color=auto",
"fgrep": "fgrep --color=auto",
"egrep": "egrep --color=auto",
"systemctl": "sudo systemctl",
"journalctl": "sudo journalctl",
"pwgen": "pwgen -s -c -n",
"dc": "docker-compose",
"vim": "nvim",
"view": "nvim -R",
"vimdiff": "nvim -d",
"freeflow": "enbas --config-dir ${XDG_CONFIG_HOME}/enbas/free-flow",
"g": "git",
"k": "kubectl",
"pass": "PASSWORD_STORE_DIR=${XDG_DATA_HOME}/pass pass"
},
"commands": [
{
"command": "shopt -s histappend",
"description": "Append to the history file, don't overwrite it."
},
{
"command": "set -o vi",
"description": "Activate vi mode."
},
{
"command": ". /usr/share/bash-completion/bash_completion",
"description": "Ensure that programmable completion features are enabled."
},
{
"command": "source /usr/share/bash-completion/completions/git",
"description": "Enable bash completion for git"
},
{
"command": "complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g",
"description": "Enable bash completion for the g alias"
},
{
"command": "source <(gopass completion bash)",
"description": "Enable bash completion for gopass"
},
{
"command": "eval \"$(SHELL=/bin/sh lesspipe)\"",
"description": "Make less more friendly for non-text input files"
}
]
},
"directories": {
"useDefaultDirectories": true,
"includeXDGDirectories": true,
"additionalDirectories": [
".local/goblin",
".local/share/go",
".local/software",
"Certificates",
"Docker",
"Installations",
"ISOs",
"Laboratory",
"Laboratory/Lab",
"Laboratory/Training",
"Notes",
"Projects"
]
},
"git": {
"gpgSign": true,
"user": {
"email": "daangling@gmail.com",
"name": "Dan Anglin",
"signingKey": "0C1D44CFBEE68638"
}
},
"managedConfigurations": [
"alacritty",
"amfora",
"git",
"lf",
"logrotate",
"tmux",
"user-dirs.dirs",
"user-dirs.locale",
"zk"
]
}

64
hosts/sparrow.json Normal file
View file

@ -0,0 +1,64 @@
{
"bashProfile": {
"manage": true,
"filename": ".bash_profile",
"aliases": {},
"commands": [
{
"command": "shopt -s histappend",
"description": "Append to the history file, don't overwrite it."
},
{
"command": "set -o vi",
"description": "Activate vi mode."
}
],
"environmentVariables": {},
"sessionPaths": [
{
"path": ".local/goblin",
"description": "the directory of go binaries"
},
{
"path": "Applications",
"description": "the AppImage directory"
}
],
"xdgDirectories": {}
},
"directories": {
"useDefaultDirectories": true,
"includeXDGDirectories": true,
"additionalDirectories": [
".local/bin",
".local/data/gnupg",
".local/data/go",
".local/goblin",
".local/opt",
".local/state/bash",
".local/state/less",
"Docker",
"Laboratory",
"Notes",
"Projects"
]
},
"git": {
"gpgSign": false,
"user": {
"email": "daangling@gmail.com",
"name": "Dan Anglin",
"signingKey": ""
}
},
"managedConfigurations": [
"foot",
"git",
"lf",
"logrotate",
"river",
"tmux",
"user-dirs.dirs",
"user-dirs.locale"
]
}

54
magefiles/bash_profile.go Normal file
View file

@ -0,0 +1,54 @@
//go:build mage
package main
import (
"fmt"
"os"
"path/filepath"
"text/template"
)
// BashProfile manages the user's Bash Profile using their configuration and the Bash Profile template.
func BashProfile() error {
const (
bashProfileTemplateFile string = "bash/profile.gotmpl"
managedBashProfile string = "managed/bash_profile"
defaultFilename string = ".bash_profile"
)
homeDirectory, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("unable to get the user's home configuration directory: %w", err)
}
config, err := newConfig()
if err != nil {
return fmt.Errorf("unable to load the configuration: %w", err)
}
if !config.BashProfile.Manage {
return nil
}
funcMap := template.FuncMap{
"env": env,
}
if err := renderTemplate(config, bashProfileTemplateFile, managedBashProfile, funcMap); err != nil {
return fmt.Errorf("unable to generate the Bash Profile: %w", err)
}
filename := config.BashProfile.Filename
if filename == "" {
filename = defaultFilename
}
symlinkPath := filepath.Join(homeDirectory, filename)
if err := ensureSymlink(managedBashProfile, symlinkPath); err != nil {
return err
}
return nil
}

132
magefiles/common.go Normal file
View file

@ -0,0 +1,132 @@
//go:build mage
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
)
const (
dirModePerm fs.FileMode = 0o700
configDir string = "hosts"
rootManagedDir string = "managed"
rootFilesDir string = "files"
rootTemplateDir string = "templates"
)
func ensureDirectory(path string) error {
info, err := os.Stat(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(path, dirModePerm); err != nil {
return fmt.Errorf("unable to create the directory: %w", err)
}
return nil
}
return fmt.Errorf(
"received an unexpected error after attempting to get the directory information: %w",
err,
)
}
if !info.IsDir() {
return errors.New("the path exists but it is not a directory")
}
if info.Mode().Perm() != dirModePerm {
if err := os.Chmod(path, dirModePerm); err != nil {
return fmt.Errorf("unable to update the directory's mode to %d: %w", dirModePerm, err)
}
}
return nil
}
func ensureSymlink(source, dest string) error {
absolutePathErrorMessageFormat := "unable to get the absolute path to %s: %w"
absoluteSourcePath, err := filepath.Abs(source)
if err != nil {
return fmt.Errorf(absolutePathErrorMessageFormat, source, err)
}
absoluteDestPath, err := filepath.Abs(dest)
if err != nil {
return fmt.Errorf(absolutePathErrorMessageFormat, dest, err)
}
destInfo, err := os.Lstat(absoluteDestPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Printf("Linking %s to %s\n", absoluteDestPath, absoluteSourcePath)
if err := os.Symlink(absoluteSourcePath, absoluteDestPath); err != nil {
return fmt.Errorf(
"unable to symlink %s to %s: %w",
absoluteDestPath,
absoluteSourcePath,
err,
)
}
return nil
}
return fmt.Errorf("unable to get the file info for %s: %w", absoluteDestPath, err)
}
if destInfo.Mode().Type() != fs.ModeSymlink {
return fmt.Errorf("the path %s exists but it is not a symlink", absoluteDestPath)
}
destLinksTo, err := filepath.EvalSymlinks(absoluteDestPath)
if err != nil {
return fmt.Errorf("unable to evaluate the symlink %s: %w", absoluteDestPath, err)
}
if destLinksTo == absoluteSourcePath {
return nil
}
fmt.Printf(
"%s should link back to %s but instead links back to %s\n",
absoluteDestPath,
absoluteSourcePath,
destLinksTo,
)
fmt.Println("Recreating:", absoluteDestPath)
if err := os.Remove(absoluteDestPath); err != nil {
return fmt.Errorf("unable to remove %s: %w", absoluteDestPath, err)
}
if err := os.Symlink(absoluteSourcePath, absoluteDestPath); err != nil {
return fmt.Errorf(
"unable to symlink %s to %s: %w",
absoluteDestPath,
absoluteSourcePath,
err,
)
}
return nil
}
func managedConfigSet(applicationConfigurationList []string) map[string]struct{} {
set := make(map[string]struct{})
for _, app := range slices.All(applicationConfigurationList) {
set[app] = struct{}{}
}
return set
}

112
magefiles/config.go Normal file
View file

@ -0,0 +1,112 @@
//go:build mage
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type config struct {
ManagedConfigurations []string `json:"managedConfigurations"`
BashProfile configBashProfile `json:"bashProfile"`
Directories configDirectories `json:"directories"`
Git configGit `json:"git"`
}
type configDirectories struct {
UseDefaultDirectories bool `json:"useDefaultDirectories"`
IncludeXDGDirectories bool `json:"includeXDGDirectories"`
AdditionalDirectories []string `json:"additionalDirectories"`
}
type configGit struct {
GpgSign bool `json:"gpgSign"`
User configGitUser `json:"user"`
}
type configGitUser struct {
Email string `json:"email"`
Name string `json:"name"`
SigningKey string `json:"signingKey"`
}
type configBashProfile struct {
Manage bool `json:"manage"`
Filename string `json:"filename"`
SessionPaths []configBashProfileSessionPath `json:"sessionPaths"`
XdgDirectories map[string]string `json:"xdgDirectories"`
EnvironmentVariables map[string]string `json:"environmentVariables"`
Aliases map[string]string `json:"aliases"`
Commands []configBashProfileCommand `json:"commands"`
}
type configBashProfileSessionPath struct {
Path string `json:"path"`
Description string `json:"description"`
}
type configBashProfileCommand struct {
Command string `json:"command"`
Description string `json:"description"`
}
func newConfig() (config, error) {
cfg := defaultConfig()
path, err := configFilePath()
if err != nil {
return config{}, fmt.Errorf("unable to calculate the config file path: %w", err)
}
file, err := os.Open(path)
if err != nil {
return config{}, fmt.Errorf("unable to open the file: %w", err)
}
defer file.Close()
if err = json.NewDecoder(file).Decode(&cfg); err != nil {
return config{}, fmt.Errorf("unable to decode the JSON file: %w", err)
}
return cfg, nil
}
func configFilePath() (string, error) {
hostname, err := os.Hostname()
if err != nil {
return "", fmt.Errorf("unable to get the machine's hostname: %w", err)
}
hostnameParts := strings.SplitN(hostname, "-", 3)
if len(hostnameParts) != 3 {
return "", fmt.Errorf("unexpected hostname format")
}
identifier := hostnameParts[1]
return filepath.Join(configDir, identifier+".json"), nil
}
func defaultConfig() config {
return config{
Directories: configDirectories{
UseDefaultDirectories: true,
IncludeXDGDirectories: true,
AdditionalDirectories: []string{},
},
Git: configGit{
GpgSign: false,
User: configGitUser{
Email: "",
Name: "",
SigningKey: "",
},
},
ManagedConfigurations: []string{},
}
}

96
magefiles/directories.go Normal file
View file

@ -0,0 +1,96 @@
//go:build mage
package main
import (
"fmt"
"os"
"path/filepath"
"slices"
)
// Directories ensure that the specified home directories are present.
func Directories() error {
config, err := newConfig()
if err != nil {
return fmt.Errorf("unable to load the configuration: %w", err)
}
userHome, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("unable to get the user's home directory: %w", err)
}
directories := make([]string, 0)
if config.Directories.UseDefaultDirectories{
defaultHomeDirs := homeDirectories(userHome, defaultDirectories())
directories = append(directories, defaultHomeDirs...)
}
if config.Directories.IncludeXDGDirectories{
directories = append(directories, xdgDirectories()...)
}
if len(config.Directories.AdditionalDirectories) != 0 {
additionalHomeDirs := homeDirectories(userHome, config.Directories.AdditionalDirectories)
directories = append(directories, additionalHomeDirs...)
}
for _, dir := range slices.All(directories) {
if err := ensureDirectory(dir); err != nil {
return fmt.Errorf("unable to ensure that %s is present: %w", dir, err)
}
fmt.Printf("Successfully ensured %s is present.\n", dir)
}
return nil
}
func defaultDirectories() []string {
return []string{
".local",
".local/bin",
"Applications",
"Desktop",
"Documents",
"Downloads",
"Games",
"Git",
"Music",
"Pictures",
"Templates",
"Videos",
}
}
func homeDirectories(userHome string, directories []string) []string{
absolutePaths := make([]string, len(directories))
for ind := range slices.All(directories) {
absolutePaths[ind] = filepath.Join(userHome, directories[ind])
}
return absolutePaths
}
func xdgDirectories() []string {
xdgEnvVars := []string{
"XDG_DATA_HOME",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
"XDG_STATE_HOME",
}
directories := make([]string, 0)
for _, envVar := range slices.All(xdgEnvVars) {
directory := os.Getenv(envVar)
if directory != "" {
directories = append(directories, directory)
}
}
return directories
}

84
magefiles/files.go Normal file
View file

@ -0,0 +1,84 @@
//go:build mage
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"github.com/magefile/mage/sh"
)
// Files ensure that the configuration files in the managed directory is up to date and
// ensures that they are symlinked correctly to the files in the user's home configuration
// directory.
func Files() error {
homeConfigDirectory, err := os.UserConfigDir()
if err != nil {
return fmt.Errorf("unable to get the user's home configuration directory: %w", err)
}
config, err := newConfig()
if err != nil {
return fmt.Errorf("unable to load the configuration: %w", err)
}
managedConfig := managedConfigSet(config.ManagedConfigurations)
if err = filepath.WalkDir(rootFilesDir, manageFilesFunc(homeConfigDirectory, managedConfig)); err != nil {
return fmt.Errorf("received an error while processing the files: %w", err)
}
return nil
}
func manageFilesFunc(homeConfigDirectory string, managedConfig map[string]struct{}) fs.WalkDirFunc {
return func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == rootFilesDir {
return nil
}
relativePath := strings.TrimPrefix(path, rootFilesDir+"/")
appConfigName := strings.SplitN(relativePath, "/", 2)[0]
if _, exists := managedConfig[appConfigName]; !exists {
return nil
}
managedPath := filepath.Join(rootManagedDir, relativePath)
configPath := filepath.Join(homeConfigDirectory, relativePath)
if d.IsDir() {
dirs := []string{managedPath, configPath}
for _, dir := range slices.All(dirs) {
if err := ensureDirectory(dir); err != nil {
return fmt.Errorf("unable to ensure the existence of the directory %q: %w", dir, err)
}
}
return nil
}
fmt.Println("Processing file:", relativePath)
if err := sh.Copy(managedPath, path); err != nil {
return fmt.Errorf("unable to copy %s to %s: %w", path, managedPath, err)
}
if err := ensureSymlink(managedPath, configPath); err != nil {
return err
}
return nil
}
}

5
magefiles/go.mod Normal file
View file

@ -0,0 +1,5 @@
module codeflow.dananglin.me.uk/linux-home/manager/magefiles
go 1.23.1
require github.com/magefile/mage v1.15.0

2
magefiles/go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=

13
magefiles/main.go Normal file
View file

@ -0,0 +1,13 @@
//go:build ignore
package main
import (
"os"
"github.com/magefile/mage/mage"
)
func main() {
os.Exit(mage.Main())
}

14
magefiles/manage.go Normal file
View file

@ -0,0 +1,14 @@
//go:build mage
package main
import "github.com/magefile/mage/mg"
var Default = Manage
// Manage runs all the management tasks.
func Manage() error {
mg.Deps(Directories, Files, Templates)
return nil
}

130
magefiles/templates.go Normal file
View file

@ -0,0 +1,130 @@
//go:build mage
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"text/template"
)
const templateExtension string = ".gotmpl"
// Templates generates the configuration files in the managed directory from the templates and
// ensures that they the generated files are symlinked correctly to the files in the user's home
// configuration directory.
func Templates() error {
homeConfigDirectory, err := os.UserConfigDir()
if err != nil {
return fmt.Errorf("unable to get the user's home configuration directory: %w", err)
}
config, err := newConfig()
if err != nil {
return fmt.Errorf("unable to load the configuration: %w", err)
}
managedConfig := managedConfigSet(config.ManagedConfigurations)
if err = filepath.WalkDir(rootTemplateDir, manageTemplatesFunc(homeConfigDirectory, config, managedConfig)); err != nil {
return fmt.Errorf("received an error while processing the templates: %w", err)
}
return nil
}
func manageTemplatesFunc(homeConfigDirectory string, config config, managedConfig map[string]struct{}) fs.WalkDirFunc {
funcMap := template.FuncMap{
"env": env,
}
return func(templatePath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if templatePath == rootTemplateDir {
return nil
}
relativePath := strings.TrimPrefix(templatePath, rootTemplateDir+"/")
appConfigName := strings.SplitN(relativePath, "/", 2)[0]
if _, exists := managedConfig[appConfigName]; !exists {
return nil
}
if d.IsDir() {
managedDir := filepath.Join(rootManagedDir, relativePath)
configDir := filepath.Join(homeConfigDirectory, relativePath)
dirs := []string{managedDir, configDir}
for _, dir := range slices.All(dirs) {
if err := ensureDirectory(dir); err != nil {
return fmt.Errorf("unable to ensure the existence of the directory %q: %w", dir, err)
}
}
return nil
}
if !strings.HasSuffix(templatePath, templateExtension) {
return fmt.Errorf(
"the template %s does not have the %q file extension",
templatePath,
templateExtension,
)
}
managedPath := filepath.Join(rootManagedDir, strings.TrimSuffix(relativePath, templateExtension))
configPath := filepath.Join(homeConfigDirectory, strings.TrimSuffix(relativePath, templateExtension))
fmt.Println("Processing template:", relativePath)
if err := renderTemplate(config, templatePath, managedPath, funcMap); err != nil {
return fmt.Errorf(
"unable to generate %s from template %s: %w",
managedPath,
templatePath,
err,
)
}
if err := ensureSymlink(managedPath, configPath); err != nil {
return err
}
return nil
}
}
func renderTemplate(config config, templatePath, managedPath string, funcMap template.FuncMap) error {
name := filepath.Base(templatePath)
tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(templatePath)
if err != nil {
return fmt.Errorf("unable to create a new template value from %s: %w", templatePath, err)
}
output, err := os.Create(managedPath)
if err != nil {
return fmt.Errorf("unable to create %s: %w", managedPath, err)
}
defer output.Close()
if err := tmpl.Execute(output, config); err != nil {
return fmt.Errorf("unable to render the template to %s: %w", managedPath, err)
}
return nil
}
func env(value string) string {
return os.Getenv(value)
}

0
managed/.gitkeep Normal file
View file

View file

@ -1,36 +1,41 @@
[alias] [alias]
br = branch br = branch
cm = commit cm = commit
co = checkout
df = diff df = diff
gr = log --all --graph --decorate --format=format:'%C(bold "#62ccff")%h%C(reset) %C(bold "#fa9c43")(%ai)%C(reset) %C(auto)%d%C(reset)%n%C(italic "#929292")%an:%C(reset) %C("#ffffff")%s%C(reset)%n' gr = log --all --graph --decorate --format=format:'%C(bold "#62ccff")%h%C(reset) %C(bold "#fa9c43")(%ai)%C(reset) %C(auto)%d%C(reset)%n%C(italic "#929292")%an:%C(reset) %C("#ffffff")%s%C(reset)%n'
# mr allows you to checkout a merge request locally.
# $1 is the name of the remote
# $2 is the ID of the merge request
# e.g. git mr origin 3
# e.g. git mr upstream 5
mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
pl = pull pl = pull
ps = push ps = push
sr = reset --soft sr = reset --soft
st = status st = status
up = !sh -c 'git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@') && git fetch --all && git pull origin $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')' up = !sh -c 'git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@') && git fetch --all && git pull origin $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')'
sw = switch
rb = rebase
fh = !sh -c 'git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main'
[commit] [commit]
gpgsign = true gpgsign = {{ .Git.GpgSign }}
template = $gitmessage_output template = {{ env "XDG_CONFIG_HOME" | printf "%s/git/message" }}
[core] [core]
excludesFile = $gitignore_output excludesFile = {{ env "XDG_CONFIG_HOME" | printf "%s/git/ignore" }}
[diff] [diff]
tool = vimdiff tool = vimdiff
[fetch] [fetch]
prune = true prune = true
[gpg] [gpg]
program = gpg2 program = gpg
[pull] [pull]
rebase = false rebase = false
[user] [user]
email = $git_user_email email = {{ .Git.User.Email }}
name = $git_user_name name = {{ .Git.User.Name }}
signingkey = $git_user_signingkey {{- if .Git.GpgSign -}}
{{ print "" }}
signingkey = {{ .Git.User.SigningKey }}
{{- end -}}
{{ print "" }}
[init] [init]
defaultBranch = main defaultBranch = main
[filter "lfs"]
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
process = git-lfs filter-process

View file

@ -0,0 +1,15 @@
{{ env "LOG_HOME" | printf "%s/*.log" }} {
rotate 3
size 3M
nocompress
notifempty
copytruncate
}
{{ env "XDG_STATE_HOME" | printf "%s/nvim/log" }} {{ env "XDG_STATE_HOME" | printf "%s/nvim/*.log" }} {
rotate 3
size 10M
nocompress
notifempty
copytruncate
}