TL;DR
In this post, I’ll walk through the internal logic of how my WSL bootstrap works and let’s focus on
run.sh,utils.sh, andpackages.conf. You’ll learn how I made the setup modular, safe to re-run, and easy to extend.
The Big Picture
In Part 1, I talked about why I built this and now let’s explore how it works.
At the heart of it is a simple philosophy:
Don’t repeat yourself.
Don’t install what’s already there.
Keep everything scriptable and modular.
The setup revolves around three key components:
-
run.sh– the orchestrator -
utils.sh– helper functions -
packages.conf– what to install
Let’s break each down.
1. run.sh – The Orchestrator
This script is the entry point of the whole bootstrap.
Responsibilities:
- Define the list of setup scripts to run
- Execute them one by one
- Log their progress
- Fail fast if something breaks
Structure:
readonly SCRIPTS=(
"node-setup.sh"
"rust-setup.sh"
"docker-setup.sh"
"lazygit-setup.sh"
"python-setup.sh"
"zoxide-setup.sh"
"tmux-config-setup.sh"
"tpm-setup.sh"
"nvim-setup.sh"
)
Each script is passed into the run_script() function from utils.sh, which:
- Checks if the file exists
- Makes it executable
- Runs it and logs its completion
2. utils.sh – The Bash Toolkit
This file contains reusable helpers that keep the logic clean and DRY.
Key Functions:
is_installed_apt / is_installed_snap
Check if a package is already installed before trying to install it.
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -q "install ok installed"
install_apt_packages / install_snap_packages
Take a list of packages, filter out the ones already installed, and install only what’s needed.
Includes logic to:
- Run
apt-get updateonly if required - Handle Snap not being installed
- Try both normal and
-classicSnap installs
die
A simple exit-on-error function to fail cleanly and loudly.
die() {
echo "ERROR: $*" >&2
exit 1
}
In the future, I plan to add features like color-coded logging, verbosity levels, timestamps, and WSL detection. But for now, the current setup does exactly what I need: simple, readable, and effective.
3. packages.conf – Centralized Package List
This is where all core APT and Snap packages are defined and grouped by category for clarity.
Example:
SYSTEM_UTILS_APT=(
wget curl fzf build-essential fd-find ripgrep
)
DEV_TOOLS_SNAP=(
nvim
)
Each package list is passed into the installer functions in utils.sh.
This structure makes it easy to:
- Add/remove tools cleanly
- See what’s included at a glance
- Extend with new categories later
Why This Structure Works
- Modularity: each setup script does one job
- Reusability: helpers prevent duplication
- Clarity: it’s easy to understand what gets installed and how
- Safety: checks prevent reinstalling or breaking existing tools
Want to Try It?
⚠️Security Reminder
Never run scripts from the internet — including mine — without reading and understanding them first.
Even if you trust the source, it's good practice to inspect any Bash script before executing it.
I care about security too and my WSL bootstrap project is open source, transparent, and written to be as readable and modular as possible. Fork it, inspect it, tweak it.
git clone https://github.com/LazyDoomSlayer/os-bootstraps
cd os-bootstraps/ubuntu
chmod +x run.sh
./run.sh
Install time: ~5 minutes on a VM with 8GB RAM, 8-core CPU, and 50Mbps connection.
First-time runs may take longer if your system hasn’t been updated with apt-get update && upgrade.
Everything else happens automatically. Grab a coffee. ☕
What’s Next: The CLI Stack
In Part 3, I’ll show you the terminal tools I bootstrap like tmux, zoxide, lazygit, and neovim and how they fit into my daily workflow.
Want to go full terminal-first? This next part is for you.
Top comments (0)