Someone reached out to me to get instructions for how I set up my environment, tooling, projects, etc; and how I use that as a base for migrating and using boot.dev exercises locally.
Unfortunately, I'm currently refactoring my entire gs-dotfiles project, which is going to automate all of this away (regardless of OS) but until then these instructions will have to do.
NOTE: This user is on WSL, which many of the boot.dev courses seem to favor for some reason. It's fine, I have to run WSL along all my other machines and OSes too, but bear in mind these instructions were originally written for WSL. Adjust accordingly.
I notice a lot of you aren't using a proper terminal emulator. This is required. After you've installed it, run and use it for the rest of these instructions.
I assume you've already installed WSL or WSL2 and Ubuntu or some linux distro into it. I'm also going to assume you already installed scoop or chocolatey.
Use scoop or chocolatey to install alacritty* and jetbrainsmono nerd font (you'll need a mono nerd font for proper ligatures and symbols, this is my favorite one):
scoop bucket add extras
scoop bucket add nerd-fonts
scoop install JetBrainsMono-NF
scoop install alacritty
ghostterm isn't on windows, kitty has to run FROM the wsl, and I'm not sure about wezterm but I don't have good dotfiles for it anyway
Create and edit a file at this location (this is the unix WSL location, adjust appropriately if you're in windows)
/mnt/c/Users/<username>/Application\ Data/alacritty/alacritty.toml
Add this into it (catppuccin mocha theme, adjust accordingly):
[terminal.shell]
program = "C:\\Windows\\System32\\wsl.exe"
[font]
normal = { family = "JetBrainsMono Nerd Font Mono", style = "Regular" }
size = 12
[window]
option_as_alt = "Both"
[colors.primary]
background = "#1e1e2e"
foreground = "#cdd6f4"
dim_foreground = "#7f849c"
bright_foreground = "#cdd6f4"
[colors.cursor]
text = "#1e1e2e"
cursor = "#f5e0dc"
[colors.vi_mode_cursor]
text = "#1e1e2e"
cursor = "#b4befe"
[colors.search.matches]
foreground = "#1e1e2e"
background = "#a6adc8"
[colors.search.focused_match]
foreground = "#1e1e2e"
background = "#a6e3a1"
[colors.footer_bar]
foreground = "#1e1e2e"
background = "#a6adc8"
[colors.hints.start]
foreground = "#1e1e2e"
background = "#f9e2af"
[colors.hints.end]
foreground = "#1e1e2e"
background = "#a6adc8"
[colors.selection]
text = "#1e1e2e"
background = "#f5e0dc"
[colors.normal]
black = "#45475a"
red = "#f38ba8"
green = "#a6e3a1"
yellow = "#f9e2af"
blue = "#89b4fa"
magenta = "#f5c2e7"
cyan = "#94e2d5"
white = "#bac2de"
[colors.bright]
black = "#585b70"
red = "#f38ba8"
green = "#a6e3a1"
yellow = "#f9e2af"
blue = "#89b4fa"
magenta = "#f5c2e7"
cyan = "#94e2d5"
white = "#a6adc8"
[[colors.indexed_colors]]
index = 16
color = "#fab387"
[[colors.indexed_colors]]
index = 17
color = "#f5e0dc"If you don't have Homebrew, open a terminal and get it installed:
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"My preferred terminal for linux and MacOS is kitty:
curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin
brew install font-jetbrains-mono-nerd-fontBut my runner up is Ghostty:
brew install --cask ghostty
brew install font-jetbrains-mono-nerd-fontEither install kitty using the install script above for MacOS or install Ghostty using your preferred package manager.
You will also need homebrew and a nerd font for the rest of these instructions:
curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdinbrew install font-jetbrains-mono-nerd-fontI know there are lots of package managers out there, but Homebrew is the simplest one I've found that works across MacOS and Linux that is (usually) relatively up to date. We use this because MacOS doesn't have a proper built in package manager, and apt and yum are trash and always super out of date. pacman is pretty cool but if you're on Arch you probably don't need this guide in the first place.
WSL: You're the only one here who hasn't installed it yet. Open Alacritty and run this:
Install homebrew:
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"EVERYONE: Activate it manually for now, we'll be loading my shell scripts for all this later:
# Linux
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
# MacOS Apple Silicon
eval "$(/opt/homebrew/bin/brew shellenv)"
# MacOS Intel
eval "$(/usr/local/bin/brew shellenv)"We're going to install my terminal tools now.
mkdir -p ~/tmp/gmoney
touch ~/tmp/gmoney/BrewfileHere's a dump of what I have installed on my linux machine right now, throw it in that Brewfile. I included descriptions so you know what you're installing.
I recommend most of these for everyone, but adjust as necessary.
# Commands for manipulating POSIX access control lists
brew "acl"
# Extraction utility for .zip compressed archives
brew "unzip"
# Interpreted, interactive, object-oriented programming language
brew "python@3.14"
# Display directories as trees (with optional color/HTML output)
brew "tree"
# Macro processing language
brew "m4"
# Record and share terminal sessions
brew "asciinema"
# Manipulate filesystem extended attributes
brew "attr"
# Highly capable, feature-rich programming language
brew "perl"
# Automatic configure script builder
brew "autoconf"
# Bourne-Again SHell, a UNIX command interpreter
brew "bash"
# Programmable completion for Bash 4.2+
brew "bash-completion@2"
# Clone of cat(1) with syntax highlighting and Git integration
brew "bat"
# GNU binary tools for native development
brew "binutils"
# GNU compiler collection
brew "gcc"
# Resource monitor. C++ version and continuation of bashtop and bpytop
brew "btop"
# GNU Transport Layer Security (TLS) Library
brew "gnutls"
# Container runtimes on MacOS (and Linux) with minimal setup
brew "colima"
# GNU File, Shell, and Text utilities
brew "coreutils"
# Get a file from an HTTP, HTTPS or FTP server
brew "curl"
# Power of curl, ease of use of httpie
brew "curlie"
# File comparison utilities
brew "diffutils"
# Bash, Zsh and Fish completion for Docker
brew "docker-completion"
# Pack, ship and run any application as a lightweight container
brew "docker"
# Isolated development environments using Docker
brew "docker-compose"
# Classic UNIX line editor
brew "ed"
# GNU Emacs text editor
brew "emacs"
# Modern, maintained replacement for ls
brew "eza"
# Like neofetch, but much faster because written mostly in C
brew "fastfetch"
# Simple, fast and user-friendly alternative to find
brew "fd"
# Play, record, convert, and stream select audio and video codecs
brew "ffmpeg"
# Utility to determine file types
brew "file-formula"
# Collection of GNU find, xargs, and locate
brew "findutils"
# User-friendly command-line shell for UNIX-like operating systems
brew "fish"
# Fast Lexical Analyzer, generates Scanners (tokenizers)
brew "flex"
# Command-line fuzzy finder written in Go
brew "fzf"
# GNU awk utility
brew "gawk"
# Distributed revision control system
brew "git"
# Render markdown on the CLI
brew "glow"
# C code prettifier
brew "gnu-indent"
# GNU implementation of the famous stream editor
brew "gnu-sed"
# GNU version of the tar archiving utility
brew "gnu-tar"
# GNU implementation of which utility
brew "gnu-which"
# GNU Privacy Guard (OpenPGP)
brew "gnupg"
# Apply a diff file to an original
brew "gpatch"
# GNU grep, egrep and fgrep
brew "grep"
# Make JSON greppable
brew "gron"
# Popular GNU data compression program
brew "gzip"
# Easy plain text accounting with command-line, terminal and web UIs
brew "hledger"
# Improved top (interactive process viewer)
brew "htop"
# Git-compatible distributed version control system
brew "jj"
# Lightweight and flexible command-line JSON processor
brew "jq"
# Lazier way to manage everything docker
brew "lazydocker"
# Simple terminal UI for git commands
brew "lazygit"
# TUI for Jujutsu/jj
brew "lazyjj"
# Pager program similar to more
brew "less"
# Clone of ls with colorful output, file type icons, and more
brew "lsd"
# Powerful, lightweight programming language
brew "lua"
# Package manager for the Lua programming language
brew "luarocks"
# Utility for directing compilation
brew "make"
# Generate a markdown TOC (table of contents) with Remarkable
brew "markdown-toc"
# Fast, flexible, config-based cli for linting Markdown/CommonMark files
brew "markdownlint-cli2"
# CLI for Mermaid library
brew "mermaid-cli"
# Polyglot runtime manager (asdf rust clone)
brew "mise"
# Free (GNU) replacement for the Pico text editor
brew "nano"
# Incremental parsing library
brew "tree-sitter"
# Ambitious Vim-fork focused on extensibility and agility
brew "neovim"
# Modern shell for the GitHub era
brew "nushell"
# Utility that provides fast incremental file transfer
brew "rsync"
# Search tool like grep and The Silver Searcher
brew "ripgrep"
# OpenBSD freely-licensed SSH connectivity tools
brew "openssh"
# 7-Zip (high compression file archiver) implementation
brew "p7zip"
# Swiss-army knife of markup format conversion
brew "pandoc"
# Python package management tool
brew "poetry"
# Modern API client that lives in your terminal
brew "posting"
# Code formatter for JavaScript, CSS, JSON, GraphQL, Markdown, YAML
brew "prettier"
# Readline wrapper: adds readline support to tools that lack it
brew "rlwrap"
# Terminal multiplexer with VT100/ANSI terminal emulation
brew "screen"
# Smart session manager for the terminal
brew "sesh"
# SQL linter and auto-formatter for Humans
brew "sqlfluff"
# Cross-shell prompt for astronauts
brew "starship"
# Organize software neatly under a single directory tree (e.g. /usr/local)
brew "stow"
# Opinionated Lua code formatter
brew "stylua"
# Version control system designed to be a better CVS
brew "subversion"
# Easiest, most secure way to use WireGuard and 2FA
brew "tailscale"
# Official tldr client written in Rust
brew "tlrc"
# Parser generator tool
brew "tree-sitter-cli"
# Vi 'workalike' with many additional features
brew "vim"
# Command-line tool for sharing terminal over the web
brew "ttyd"
# Your CLI home video recorder
brew "vhs"
# Executes a program periodically, showing output fullscreen
brew "watch"
# Display word differences between text files
brew "wdiff"
# Internet file retriever
brew "wget"
# Friendly and fast tool for sending HTTP requests
brew "xh"
# Blazing fast terminal file manager written in Rust, based on async I/O
brew "yazi"
# Pluggable terminal workspace, with terminal multiplexer as the base feature
brew "zellij"
# Compression and file packaging/archive utility
brew "zip"
# Shell extension to navigate your filesystem faster
brew "zoxide"
# UNIX shell (command interpreter)
brew "zsh"
# Nerd font
cask "font-jetbrains-mono-nerd-font"
MacOS BONUS: Homebrew on mac can install desktop software too. You should be using homebrew (and mise) for everything you can. Here's a dump of what I have installed on my mb-air. Pick and choose if you want.
Install them:
brew bundle install --file ~/tmp/gmoney/BrewfileMy setup uses lazyvim as its base, so if you want to use my neovim dotfiles, you'll have to backup your configs and install lazyvim. here are the complete commands for doing so:
# required
mv ~/.config/nvim{,.bak}
# optional but recommended
mv ~/.local/share/nvim{,.bak}
mv ~/.local/state/nvim{,.bak}
mv ~/.cache/nvim{,.bak}
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.gitstart up nvim, let it install everything, hit S maybe one more time to make sure it got everything, then exit
Learn to use LazyVim here.
It's a learning curve, I know, but it's super worth it once you get the hang of it. I started in Feb of this year, so only a few months ago, but I am absolutely zooming with it right now. I'll never go back to another editor/IDE.
Unfortunately I haven't had time to update my setup scripts and dotfiles for the public yet, but they're good enough for now as a starting point.
mkdir ~/dotfiles ~/gitclones ~/tmp
cd ~/gitclones
git clone https://github.com/g-lok/gs-dotfiles.git
cd gs-dotfiles/install.d/dotfiles
cp -r bash btop golangci mise neovim shellrc starship yazi zsh zellij-macos-intel ~/dotfiles/IMPORTANT NOTE:
Zellij: My dotfiles.sh script only detects cpu arch, not OS, and homebrew uses different paths for
different oses/arch too. The zellij stow package I specified up there will kinda work for x86 systems.
If you're on arm (Mac Silicon or raspberry pi or something) you'll want to grab the zellij-macos-arm64 package instead.
MacOS or Linux Desktop Users: Also grab the kitty/ghostty stow packages appropriate for your architecture. Note that I don't work on my mbp intel as much so it might be a little out of date, but you can Frankenstein the configs from the arm64 folders if that's the case. I'm reasonably sure I updated them though.
backup your .zshrc, .bashrc and .bash_profile:
cp ~/.zshrc ~/tmp/gmoney/zshrc.bkp
cp ~/.bashrc ~/tmp/gmoney/bashrc.bkp
cp ~/.bash_profile ~/tmp/gmoney/bash_profile.bkpBefore applying the dotfiles, I recommend switching to zsh for your shell if you're using bash. It's basically the same as bash with nicer features. Plus I use oh-my-zsh to manage plugins for autocomplete, suggestions, auto-fzf fuzzy find, vim mode, etc.
For all you fish and nushell users out there, I'm sure you got things under control, but you'll probably have to update your /etc/shells and chsh -s $(which <myshell>) to get the homebrew versions.
First, you'll need to add the homebrew versions of bash and zsh as authorized shells:
sudo cat >> /etc/shells <<EOF
/home/linuxbrew/.linuxbrew/bin/bash
/home/linuxbrew/.linuxbrew/bin/zsh
EOF
(change filepaths to whatever your OS's version of homebrew is using) Then change your default shell:
chsh -s $(which zsh)I have a script to install oh-my-zsh and the plugins. run:
~/gitclones/gs-dotfiles/install.d/zsh.sh
NOTE sudo by default won't have access to your paths and env variables and stuff,
so it won't be able to use all these homebrew tools and aliases and stuff we're going to get to.
It's a risk and I didn't do too much research into the "proper" way to do this,
but I basically ran sudo visudo and changed the top part to this:
#Defaults env_reset
Defaults !secure_path
Defaults env_keep += "XDG_CONFIG_HOME HOME"
Defaults env_keep += "EDITOR VISUAL"
#Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
#Defaults env_keep += "PATH"
#Defaults env_keep += "HOME"
# This fixes CVE-2005-4890 and possibly breaks some versions of kdesu
# (#1011624, https://bugs.kde.org/show_bug.cgi?id=452532)
Defaults use_pty
I also updated my /root/* .zshrc and .bashrc to load my user's copies under /home/<username/ so sudo has all my environment configs and aliases and stuff.
Seems to work ok. Again, not ideal, but it got the job done.
now let's get to the dotfiles. the way this works is I use gnu stow from the ~/dotfiles directory to adopt and symlink the original file locations to this one. that means that all your configs will be in one place that you can use git/jj VCS with. each subdir is a "package" that contains a directory structure that assumes a root at your $HOME directory.
First, add the follow (janky) script to ~/dotfiles/dotfiles.sh and chmod +x ~/dotfiles/dotfiles.sh to make it executable.
#!/usr/bin/env bash
## Use GNU Stow to set dotfile configs
## Set current working directory to dotfiles folder
cd "$(dirname "$0")" || exit 0
pwd
OS=$(uname -s)
if [[ "$(uname -m)" == "arm64" ]]; then
arch="arm64"
else
arch="intel"
fi
## Get the script path while we're at it
export GSDOT_DOTFILES_PATH=$(dirname "$(readlink -f "$0")")
stow_and_copy() {
config=$1
echo "GNU Stow Adopt/import dotfiles: $config"
## GNU Stow to set up non-OS specific configs
## stow adopt it first before anything to keep the user's existing configs safe
stow --target="$HOME" --adopt "${config}" --override='.*'
## stow restow to make it clean
stow --target="$HOME" --restow "${config}"
}
## We doing this precise and intentional baby
GSDOT_UNIVERSAL=(
"bash"
"btop"
"golangci"
"neovim"
"shellrc"
"starship"
"yazi"
"zellij"
"zsh"
)
# gum style \
# --bold "GNU Stow/Adopt/Copy the following configs to ~/dotfiles.\n""\
# If you don't know what this means, just select them all with ctrl+a."
# export GSDOT_CHOICES=$(gum choose "${GSDOT_UNIVERSAL[@]" --no-limit --header="SSelect configs to copy.")
# GSDOT_CHOICES=$(gum choose "${GSDOT_UNIVERSAL[@]}" --no-limit --height 5 --header "Select optional configs.")
## I Shouldnt have to do this, but I cant get gum choose to return a usable array
# mapfile -t GSDOT_CONF_CHOICES_MAP <<<"$GSDOT_CHOICES"
# GSDOT_CONF_CHOICES_MAP=()
# while IFS= read -r line; do
# GSDOT_CONF_CHOICES_MAP+=("$line")
# done <<<"$GSDOT_CHOICES"
## GNU Stow to set up non-OS specific configs
# for config in "${GSDOT_UNIVERSAL[@]}"; do
# ## stow adopt it first before anything to keep the user's existing configs safe
# stow --target="$HOME" --adopt "$config"
# ## stow restow to make it clean
# stow --target="$HOME" --restow "$config"
# done
for choice in "${GSDOT_UNIVERSAL[@]}"; do
case $choice in
"golangci")
stow_and_copy "golangci"
;;
"zellij")
stow_and_copy "zellij-macos-${arch}"
;;
"bash")
stow_and_copy "bash"
;;
"btop")
stow_and_copy "btop"
;;
"neovim")
stow_and_copy "neovim"
;;
"shellrc")
stow_and_copy "shellrc"
;;
"starship")
stow_and_copy "starship"
;;
"yazi")
stow_and_copy "yazi"
;;
"zsh")
stow_and_copy "zsh"
;;
esac
done
MACOS AND LINUX DESKTOP USERS: You'll have to update the script to use the right zellij and kitty/ghostty/whatever dotfiles you grabbed for your system. If you don't know bash pm me and I'll sort you out. Yes I know it's jank, but sometimes you gotta work with what god gave you in the moment. All of this is currently being refactored, it'll be real nice someday. I promise.
Then run it. I had to modify it for this use case and didn't test it, but it should work. lmk if it doesnt.
cd ~/dotfiles
./dotfiles.shThe first time this is run, it adopts EXISTING configs into the relevant dotfiles packages to be safe.
that means it overwrote mine. the original locations are now symlinked to their
relevant ~/dotfiles/** locations now though, so any changes to the dotfiles
location will automatically be represented in their normal config locations.
Let's get mine back in there, but do it smartly:
cd ~/gitclones/gs-dotfiles/install.d/dotfiles
cp -r btop golangci mise neovim shellrc starship yazi zsh zellij-macos-intel ~/dotfiles/ # modify as needed
# WSL Users: there's a bunch of stuff wsl puts into your bash scripts that you want to keep, so we're going to append to the bottom of it instead.
cat bash/.bashrc >> ~/dotfiles/bash/.bashrc
cp bash/.bash_profile ~/dotfiles/bash/
# Non WSL users: just cp the bash dir over
cp -r bash ~/dotfiles/Load everything up:
exec zshNow we make some edits:
-
edit
~/dotfiles/zellij-macos-intel/.config/zellij/config.kdlchange the zsh location on line 280 to/home/linuxbrew/.linuxbrew/bin/zshif you're on linux. Otherwise, use the homebrew location for your OS/arch. MacOS users: double check to make sure your terminal configs are properly symlinked and using the correct shell paths. If you decide to use zellij, by default it will use my 3-pane split layout. you can change this back to default by commenting line 289. however, I already have an alias set that will launch a new zellij tab with the "clean" default layout (zdtab), so you get the best of both worlds out of the box. -
if you don't want vim mode on your zsh shell prompt, which can be weird to get used to (I still haven't figured out how to get alt+. to work), edit
~/dotfiles/zsh/.zshrc, go down to the plugins on line 87 and remove it from the list -
Clear out (dont delete)
~/dotfiles/mise/.config/mise/config.toml. Mise is what we use for programming language tooling, but I have a bunch of languages in here you probably don't want. also, the poetry install can sometimes be weird unless you install it directly from the git. Save the cleared miseconfig.toml, then run the following for python stuff:mise use --global python@latest mise plugins install poetry https://github.com/mise-plugins/mise-poetry.git mise use --global poetry@latest poetry config virtualenvs.in-project true mise use --global uv@latestIf you want others, you can check out
~/gitclones/gs-dotfiles/install.d/mise_all.sh -
If you don't like my ostentacious prompt, you can change
~/dotfiles/starship/.config/starship.toml. Read the docs or find pre-made ones online.
reload everything with exec zsh.
Start VCS on the dotfiles:
cd ~/dotfiles
git initI also use jujutsu on top of git, because it's just plain better:
jjgi # my alias for 'jj git init'I don't know if you already use Neovim and have your perfect personal set up already, but I'm using a heavily customized Lazyvim that I'm just going to assume is being used moving forward. Lazyvim is a "batteries included" neovim setup with Lazy for plugin management and lazy loading, configs, etc, with sane defaults for its built in stuff and extras. I have heavily customized it to be even better, but that's a matter of opinion.
-
launch nvim (my alias:
nv). let lazy install everything. restart. -
hit
xon the main page to go to extras and thenxon anylang.*languages you don't want. -
I recommend keeping the other non-lang* plugins I've added though. If you made any extras changes, restart neovim.
-
Most configs go/are under
~/.config/nvim/lua/plugins. This is how you add plugins not listed under<leader>lor:LazyExtras, or change or extend the configurations of ones already installed through lazyvim. Everything here gets "merged" with the plugin defaults and the lazyvim defaults, overwriting the equivalent fields with whatever you put here, and then additional changes are merged from the config files in~/.config/nvim/lua/config/. I've made QUITE a few changes that I really like, but you should review what I put there and figure out how to get your own personalizations in. -
REMEMBER that these are supposed to be symlinked to
~/dotfiles/neovim/**. Any changes, deletions, or additions should be made there FIRST, thencd ~/dotfiles ./dotfiles.sh
to stow them into the neovim configs.
other things to know:
- both bash and zsh are loading configs from
~/.shellrc/. This is where I set up PATH stuff, aliases, activation for fzf, mise, starship, zoxide, etc etc. Put your custom init shell scripts in here, and check out everything else I put in here and adjust as desired. - we're using modern tooling to replace typical old stuff:
- use ripgrep
rginstead of grep cdnow useszoxide, which builds up a history of visited directories and lets you use shortcuts to them. for example, if you cd'ed into~/Projects/coding/myprojectonce, from now on you can justcd mypfrom anywhere to go straight there. if you have multiplemyprojectdirectories scattered about, you can do things likecd pro co mypto specify one.lsnow useseza, a turbocharged ls with tons of features. I only have a few aliases set up for it but you can make more for its other cool features.- We use
batinstead ofcatnow. If you dont want the interactive window, use my aliasbpp. - We have
tldrfor quicker, more readable docs thanman. jja much better VCS built on top of existing git repos. I made a ton of aliases for this. Has a learning curve, but worth it, I promise. Check youtube for videos, and read the docs. PM me if you want to know my typical workflow.lazygitlazyjjlazydockerand other very nice TUI apps for various things.btopbeautifultopreplacement.fzfbaked into the oh-my-zsh autocomplete and suggestions, but you can pipe any output into it for fuzzy finding.zellij- tab/window/session manager. I like it a bit better than tmux for now. Most key shortcuts should display at the bottom.sesh- for managing tmux and zellij sessionsgh dash- for managing PRs and GH stuffbrew- better than apt and yum, more packages, more up to date. we use this for most base tooling not covered bymise. Default tomiseif it has current support for the tools you want, as it tends to be better maintained (but not always) and it has more flexibility for automatically using different versions depending on the nearest parentmise.tomlconfig. That being said, sometimes you just need to use the native apt/yum/pacman versions, so be prepared for that. You can simply reference them from their location under /bin or /usr/bin or whatever, and if you need them often you can create aliases for them.
- use ripgrep
and much much more. Check out the Brewfile from earlier.
before we get into the python stuff, let's get git and jj set up
-
configure gh/git with your github creds:
gh auth login
-
Make sure you apply it to your default git config when it asks.
-
Configure your global name and email:
git config --global user.name "Your Name" git config --global user.email "your.email@example.com" jj config set --user user.name "Your Name" jj config set --user user.email "your.email@example.com"
-
Install gh dash:
gh extension install dlvhdr/gh-dash
ok we're ready. Here's the basic workflow:
What I use, but uv is good too. I'll give separate instructions for uv
-
Create a new poetry project with best practices src layout:
poetry new my-project
or init inside an existing project:
poetry init
-
cdinto the project if you're not already there. -
install dev group packages:
poetry add --group dev pytest black ruff mypy pytest-cov
-
The next part is weird and might need fiddling. I've set some mise env variables in
~/shellrc/99-init.shto work with the mise poetry plugin to automatically generate and activate project virtualenvs, but we have to hack it into working for each project. -
If you need to use or pin a different python version than the @latest one installed by mise,
mise install python@<version>first. -
Add a
mise.tomlat the project root using the template below. Never uselatestfor the version, always pin a specific version. You can add tasks, env variables, and a bunch of stuff in here, check the mise docs.mise.toml:[tools] poetry = { version = "latest", pyproject = "{{ config_root }}/pyproject.toml" } # This MUST come before the python version below python = "3.14.3"
-
mise will probably complain about trust. trust the config with
mise trust -
Delete the poetry-created venv. Mise won't kick into action unless there's a
poetry.lockand NO venv.rm -rf .venv
-
cdout of the project, clear the mise cache, thencdback in:cd .. mise cache clear cd my-project
Mise should automatically kick in and create the .venv and activate it. If it doesnt, lmk.
It's weird. double check which python to make sure it's using the local .venv path.
poetry createby default uses best practices src layout. This means all your modules, submodules, and code goes insrc/my-project/**. If you plan on executing it, your entry hook should besrc/my-project/__main__.py. This ensures your project/library wont end up creating dependency hell and conflicting packages/libs/versions if it's ever installed with pip. Then you configure a mise.toml task, or if you want to make it an executable command, configure your pyproject.toml with poetry tool scripts andpoetry installfor the local workspace, or if you want it global on your system usepip install -eon your project. check out my asteroids project to see how it's done.- tests go in
tests/. We use pytest here and so does neovim. - your project, its dependencies, build setup, and all the important stuff is in your
pyproject.toml
Pretty similar to Poetry. I don't use it much so I'm not going to give as many details, check the docs/tutorials.
You want to create a similar src structured project that poetry new creates, along with a pyproject.toml, and
add the dev packages mentioned earlier. Here's a mise.toml that should work in a similar way as the poetry one,
but without the need for tricking it into working (if I recall. you still might have to delete the .venv.
You also might have to config uv to use a local venv if it doesn't already). I dunno, read the uv and mise docs,
google around, figure it out.
mise.toml
[tools]
uv = "latest"
python = "3.14.3"
[settings]
python.uv_venv_auto = "create|source"Not every boot.dev exercise has usable tests. If it's running the cmd line tool or hiding
parts of the test suite in the main_test.py tab, you'll have to roll your own. BUT, most exercises
do have usable tests under the main_test.py tab, and many are using parameterized tests like pytest
uses. You'll just have to muck around with it a bit.
I had to keep my repo private because I didn't get permission to share it, but here's some examples from the data structures and algos course to demonstrate a straightforward, mostly copypasta example, a neovim debugger ready sample, and a more complicated example that required actually reviewing the test code to see what it was doing so I could write a similar test suite in pytest format.
Note how parameterized pytests are set up. you have a decorator function over the test function, you tell it how to assign the split data variables, then provide a collection of tests that are divided into input data and expected data. This is how most boot.dev python tests are set up, you just might need to remove some layers of nested tuples/lists or whatever but the gist is the same. This also lets you test the "submit" full tests suite without ruining your streak
In lazyvim, you run tests with <leader>tt for current test file, or <leader>tT to run all test files.
You can also use the pytest cmd line to run tests and set flags for more output or test coverage.
from typing import List
import pytest
from datastr_algos import ch4l01
theprimeagen = ch4l01.Influencer(100, 1)
pokimane = ch4l01.Influencer(800, 2)
spambot = ch4l01.Influencer(0, 200)
lane = ch4l01.Influencer(10, 2)
badcop = ch4l01.Influencer(1, 2)
@pytest.mark.parametrize(
"influencers, expected",
[
([], []),
([lane], [lane]),
([badcop, lane], [badcop, lane]),
([lane, badcop, pokimane], [badcop, lane, pokimane]),
([spambot, theprimeagen], [theprimeagen, spambot]),
(
[pokimane, theprimeagen, spambot, badcop, lane],
[badcop, lane, theprimeagen, pokimane, spambot],
),
],
)
def test_ch4l01_sort(
influencers: List[ch4l01.Influencer], expected: List[ch4l01.Influencer]
):
sorted_vanity = ch4l01.vanity_sort(influencers)
assert sorted_vanity == expectedCompare against the boot.dev exercise main_test.py to see how I did this.
here's another super simple one. note a slight difference here: for sorting algos and recursive algos, I found that I really needed a debugger to step through my code so I could monitor the entire state at every breakpoint. Every variable, every function in the call stack, etc.
To make debugging work in lazyvim, you first set breakpoints in your code at whatever lines make sense to stop and inspect at, then you go into the test file like this and uncomment that bottom line. that line is there so that you can use the dap.core python debugger, which doesn't get activated with neotests, so you have to literally run the file to make the pytests execute with the debugger active.
To toggle breakpoints, use <leader>db. To run the debugger on the test file, <leader>dc (no flags or anything).
You may want to set breakpoints in the test file too to get the final state before it closes out.
from typing import List
import pytest
from datastr_algos.ch4l02 import bubble_sort
@pytest.mark.parametrize(
"nums, expected",
(
([5, 7, 3, 6, 8], [3, 5, 6, 7, 8]),
([2, 1], [1, 2]),
([], []),
([1], [1]),
([1, 5, -3, 2, 4], [-3, 1, 2, 4, 5]),
([9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9]),
([1, 3, 2, 5, 4], [1, 2, 3, 4, 5]),
),
)
def test_ch4lw_bubble_sort(nums: List[int], expected: List[int]):
is_same = bubble_sort(nums) == expected
assert is_same
# pytest.main()here's an example of a test I had to actually sort of hand-roll from the boot.dev version. more complicated, but by now you should be able to read the tests in main_test.py (if available) to determine how they work, and just adapt it into something less janky locally.
import pytest
from datastr_algos.ch5l04 import letter_combinations
@pytest.mark.parametrize(
"digits, expected_length, expected_initial, expected_contains",
(
("", 0, [], ""),
("67", 12, ["mp", "mq", "mr"], "op"),
("43556", 243, ["gdjjm", "gdjjn", "gdjjo"], "hello"),
("2668338", 2187, ["ammtddt", "ammtddu", "ammtddv"], "bootdev"),
("420", 0, [], "ValueError"),
("7878326", 3888, ["ptptdam", "ptptdan", "ptptdao"], "rustfan"),
("4568346", 2187, ["gjmtdgm", "gjmtdgn", "gjmtdgo"], "ilovego"),
),
)
def test_exp_letter_combos(
digits: str,
expected_length: int,
expected_initial: list[str],
expected_contains: str,
):
try:
result = letter_combinations(digits)
actual_length = len(result)
## What was I doing here?
# if expected_length == 0 and expected_contains == "":
# return True
if expected_length == 0 and actual_length == expected_length:
return True
except ValueError:
assert expected_length == 0 and expected_contains == "ValueError"
else:
actual_initial = result[:3]
actual_contains = expected_contains in result
assert (
actual_length == expected_length
and actual_initial == expected_initial
and actual_contains
)
# pytest.main()I hope this gets you on good footing with a cracked environment and workflow that I've spent months learning and developing. That being said, there are some exercises and courses that will NOT let you do this easily. I have opened numerous threads and FRs for boot.dev to localize libraries/apis so we can use our local environments and tooling for everything on the site. Please add your voice to the chorus and maybe they'll open Pandora's box for us.