My portable dotfiles
My portable dot-packages
.
Zero-conf, zero-dep with a simple bash script that focuses on cognitive simplicity
and natural workflows while allowing maximum flexibility for each pkg to be modular
and set its own rules of how to link, install and cleanup.
I’ve used various dot patterns out there in the last 2 decade from stow (longest)
to ansible (next) to fancy managers like chez_moi, dotter or nix all the way with
home-manager (love nix for flakes and pkg management). However, I’ve never enjoyed
the complexity as it grows just to keep my config and DRY on my essentials.
This is an attempt to simplify and make life sane.
Note: I tend to work as close to defaults as possible and minimal configuration
in most cases, except for some essential foundations or esoteric things that I use
often (e.g., my tmux prefix is Alt+E
: so it’s one-hand accessible, doesn’t conflict
and doesn’t stress my finger muscles for doing this all day).
Read the pkg.mod.sh
file in each pkg for a quick idea on conventions.
./pkg.sh sync bash
to link them to your home dir
bash/
├── .bashrc
├── .profile
└── .bashrc.d/
├── aliases.sh
└── functions.sh
pkg.mod.sh
inside the dir for configvars
function and set LINKS
array var to take control of want to link.
vars() {
LINKS=(.config/nvim)
}
./pkg.sh sync nvim
to link them to your home dir.
nvim/
├── .config/nvim/<files>
└── pkg.mod.sh
pkg.mod.sh
inside the dir for configvars
function and set LINKS
array var to set tmux conf only.post_link
to setup tpm plugin manager and install plugins.pre_unlink
to cleanup tpm dir.clean_conf
vars() {
TMUX_PLUGIN_DIR="$HOME/.tmux/plugins"
TMUX_TPM_DIR="$TMUX_PLUGIN_DIR/tpm"
LINKS=(
.tmux.conf
)
}
post_link() {
rm -rf "$TMUX_TPM_DIR"
git clone https://github.com/tmux-plugins/tpm "$TMUX_TPM_DIR"
$TMUX_TPM_DIR/bin/install_plugins
}
pre_unlink() {
rm -rf "$TMUX_TPM_DIR"
}
clean_conf() {
rm -rf "$TMUX_PLUGIN_DIR"
}
./pkg.sh sync tmux
to see it in action - your tmux conf linked
tmux/
├── .tmux.conf
└── pkg.mod.sh
install
, uninstall
commands.
vars() {
TMUX_PLUGIN_DIR="$HOME/.tmux/plugins"
TMUX_TPM_DIR="$TMUX_PLUGIN_DIR/tpm"
LINKS=(
.tmux.conf
)
}
post_link() {
rm -rf "$TMUX_TPM_DIR"
git clone https://github.com/tmux-plugins/tpm "$TMUX_TPM_DIR"
$TMUX_TPM_DIR/bin/install_plugins
}
pre_unlink() {
rm -rf "$TMUX_TPM_DIR"
}
clean_conf() {
rm -rf "$TMUX_PLUGIN_DIR"
}
check_install() {
command -v tmux > /dev/null
}
install() {
sudo apt install tmux
}
uninstall() {
sudo apt purge tmux --autoremove
}
./pkg.sh sync tmux
to link and setup tpm. tmux will be installed if needed,install
, uninstall
and leave LINKS
to default of ()
.
```bash
vars() {
BASH_COMPLETIONS_DIR=$HOME/.bashrc.d/completions
}
check_install() {
command -v deno > /dev/null
}
install() {
curl -fsSL https://deno.land/x/install/install.sh | sh
after_install
}
after_install() {
deno completions bash > "$BASH_COMPLETIONS_DIR/deno"
}
uninstall() {
rm -f "$BASH_COMPLETIONS_DIR/deno"
rm -rf "$HOME/.deno"
}
./pkg.sh sync deno
and you’re all set.Here’s another for Rust toolchain with rustup:
vars() {
BASH_COMPLETIONS_DIR=$HOME/.bashrc.d/completions
COMPLETION_BINS=(
rustup
cargo
)
}
check_install() {
command -v rustup > /dev/null
}
install() {
# for all options:
# curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --help
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path -y
after_install
}
after_install() {
for bin in "${COMPLETION_BINS[@]}"; do
rustup completions bash $bin > "$BASH_COMPLETIONS_DIR/$bin"
done
rustup component add rust-analyzer
rustup target add wasm32-unknown-unknown
rustup toolchain add nightly
}
uninstall() {
# remove completions
for bin in "${COMPLETION_BINS[@]}"; do
rm -f "$BASH_COMPLETIONS_DIR/$bin"
done
rustup self uninstall -y
rm -rf "$HOME/.rustup"
}
purge() {
rm -rf "$HOME/.cargo"
}
./pkg.sh sync rust
and you’re all set.The entire working is probably best explained by reading the tiny ./pkg.sh
file.
If you don’t have time, these lines are the key logic - that’s it.
local x
for x in "${pkgs[@]}"; do
echo "=== $x ==="
pushd "./$x" >/dev/null
(
# Note that we run all of these in a subshell giving it's mod file the
# ability to override anything from this file and start fresh again.
source "./$pkg_mod_file" 2>/dev/null || true
run_cmd "$cmd"
)
popd >/dev/null
done
pkg.mod.sh
file inside the pkg dirPKG_MOD_SH
)./pkg.sh <command> <package1> [<package2> ...]
to execute commands forpkg.sh
file does the following:pkg.mod.sh
file if availablevars
so you can override them all.sync
, link
, install
, clean
etc.)pkg.sh
vars
:LINKS=()
(nothing will get linked)link|unlink
pre_link | pre_unlink
if existsLINKS
var, link them inside TARGET
dirpost_link | post_unlink
if existsLINKS
in vars
to autolink and use pre_
and post_
link
and unlink
completely and choose your own mechanisminstall|uninstall
:linuxbrew
or if you’re like me andnix
than your operating system.APT_PKGS
DNF_PKGS
or BREW_PKGS
. But should be trivial to extend default install
link
hooks use a default impl and offerafter
and before
hooks for flexibility.xset
dir for example)
usage: <command> <package1> [<package2> ...]
env:
TARGET: (target to install to, defaults to HOME env)
PKG_MOD_SH: (the mod sh file to use for each module)
FORCE_RELINK: (force relink without requiring unlink or fail for safety)
commands:
sync: brings everything up to date: check_install [install], [re]link
install: install the package
check_install: check if package requires installation
uninstall: uninstall the package
link: link the package, can fail if links already exist
relink: force re-link the package
unlink: unlink the package
clean_conf: clean the package configuration
clean: unlink, clean_conf and cleans other files
purge: uninstall, clean and purge other files
Example: ./pkg.sh sync bash tmux rust deno
will make sure all these pkgs
are installed, configured and ready to go!