This post documents my minimal desktop setup on Ubuntu, built around the i3 window manager. It’s designed to be fast, distraction-free, and fully keyboard-driven — a system that adapts to different kinds of work and gives me full control over my workspace environment.
I use Dropbox to sync files across machines and symlink all key config files — Emacs, Tmux, i3, and more — from a central dotfiles repository. This setup makes it easy to manage multiple machines with different configurations, like separate work and personal environments, while maintaining a shared foundation.
This post also serves as a reference for myself when setting things up on new machines.
Base System Setup
I primarily use apt-get
for installing packages, but occasionally fall back to snap
when it’s the simpler or more reliable choice. The goal is practicality — whatever gets things working cleanly with minimal fuss.
sudo apt-get update
sudo apt-get install build-essential
sudo apt-get install git curl make cmake autoconf vim tldr htop xsel arandr tree openssh-server net-tools
Disabling the GRUB splash makes boot cleaner and faster. I also unbind Control + semicolon
from IBus to free it up for other use.
# disable splash screen
sudo apt-get purge plymouth-theme-ubuntu-text
# edit /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT=""
sudo update-grub2
# remove C-; keybinding from ibus
ibus-setup # delete keybinding
To make additional drives available on boot, I manually find their UUIDs and add them to /etc/fstab
.
# find UUIDs and Filesystem Types
lsblk -o NAME,FSTYPE,UUID
# create mountpoints with correct user permissions
sudo mkdir /mnt/drive1
sudo chown -R $USER:$USER /mnt/drive1
# edit /etc/fstab
UUID=your-uuid /mnt/drive1 ext4 defaults 0 0
I like using Source Code Pro as my terminal and editor font.
wget https://github.com/adobe-fonts/source-code-pro/archive/2.030R-ro/1.050R-it.zip
unzip 1.050R-it.zip
mkdir ~/.local/share/fonts
cp source-code-pro-*-it/OTF/*.otf ~/.local/share/fonts
fc-cache -f -v
Shell and Dev Environment
ZSH
Install and configure ZSH with oh-my-zsh
. This sets up a more readable, functional shell with fuzzy search and syntax highlighting.
sudo apt-get install zsh
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# zsh syntax highlighting
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
sudo apt-get install fzf
plugins=(git fzf colored-man-pages colorize zsh-syntax-highlighting)
Tmux and Tmuxinator
Tmux setup with TPM for managing plugins, and tmuxinator for complex session layouts.
sudo apt-get install tmux tmuxinator
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
Minimal .tmux.conf
:
# ~/.tmux.conf
set -g mouse on
set -g history-limit 10000
set-option -g allow-rename off
bind | split-window -h
bind - split-window -v
unbind '"'
unbind %
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
run '~/.tmux/plugins/tpm/tpm'
Sample tmuxinator config for a default session.
# ~/.tmuxinator/default.yml
name: default
root: ~/
windows:
- system:
layout: tiles
panes:
- htop
- cd ~/grind && jupyter lab
- nvidia-smi -l 2
- cd
- editor:
layout: tiles
panes:
- cd
SSH Key Setup for Multiple Accounts
To keep work, personal, and other Git accounts separate, I generate multiple SSH keys and configure them in ~/.ssh/config
. This lets me switch between GitHub, Bitbucket, and other services without conflicts.
# create multiple SSH keys, one for each account
ssh-keygen -t ed25519 -C "comment"
# create other keys
# start ssh-agent
eval "$(ssh-agent -s)"
# ssh-add multiple keys
ssh-add ~/.ssh/id_ed25519_1
# add other keys
Edit ~/.ssh/config
to specify which key to use for each host.
# ~/.ssh/config
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_1
Host bitbucket.org
HostName bitbucket.org
User git
IdentityFile ~/.ssh/id_ed25519_2
# add other hosts as needed
Managing Python with pyenv
To manage multiple Python versions independently of the system Python, I use pyenv
. This makes it easy to install, switch and isolate Python runtimes.
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
# add pyenv to PATH
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init - zsh)"' >> ~/.zshrc
# install python dependencies
sudo apt-get update
sudo apt-get install make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
pyenv install x.yy.z
pyenv global x.yy.z
Emacs
Set up Emacs as the default editor – a full operating system in the guise of a text editor – and configure related environment variables. For a deep dive into my full Emacs configuration, see: Emacs Configuration.
sudo apt-get install ripgrep git-delta
sudo snap install node
sudo snap install emacs --classic
# ~/.zshrc
export EDITOR="emacsclient -c -a emacs"
export VISUAL="$EDITOR"
alias sudo-emacsclient="SUDO_EDITOR=\"emacsclient\" sudo -e"
# variables used by Emacs
export DROPBOX_HOME="$HOME/Dropbox"
export ORG_HOME="$DROPBOX_HOME/org_files"
Window Manager: i3 Setup
i3 is the core of my environment — a tiling window manager that enables a fully keyboard-driven, minimal workflow. I extend it with Rofi for application launching, and Greenclip for clipboard management.
# install i3
sudo apt-get install i3 xautolock
# install rofi
sudo apt-get install rofi rofi-dev
# to change rofi theme, run
rofi -show run
# choose rofi-theme-selector
# install greenclip
wget https://github.com/erebe/greenclip/releases/download/v4.2/greenclip
chmod +x greenclip
sudo mv greenclip /usr/local/bin
# rofi-calc
# follow instructions from https://github.com/svenstaro/rofi-calc
i3 Config
Basic i3 configuration to set up the environment, mostly using the default settings.
# ~/.config/i3/config
# set modifier key
set $mod Mod4
# Font for window titles. Will also be used by the bar unless a different font
# is used in the bar {} block below.
font pango:SourceCodePro-Semibold 8
# Use Mouse+$mod to drag floating windows to their wanted position
floating_modifier $mod
# terminal
bindsym $mod+Return exec i3-sensible-terminal
# bindsym $mod+Return exec /usr/bin/urxvt
# reload the configuration file
bindsym $mod+Shift+c reload
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindsym $mod+Shift+r restart
# exit i3 (logs you out of your X session)
bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
# lock and autolock
bindsym Control+$mod+l exec i3lock -c 000000
exec_always --no-startup-id xautolock -time 10 -locker "i3lock -c 000000"
Remap Keys: Caps Lock to Control for better keyboard shortcuts.
# remap Capslock to control, double shift for Capslock
exec_always --no-startup-id setxkbmap -option 'ctrl:nocaps,shift:both_capslock_cancel'
Setup Rofi for application launching, window switching, and other tasks.
# rofi
bindsym $mod+d exec --no-startup-id "rofi -combi-modi window,drun -show combi -modi combi -show-icons"
# rofi-calc
bindsym $mod+c exec --no-startup-id "rofi -show calc -modi calc -no-show-match -no-sort"
# ssh
bindsym $mod+z exec --no-startup-id "rofi -show ssh"
# clipboard with greenclip
exec --no-startup-id greenclip daemon
bindsym $mod+x exec --no-startup-id rofi -modi "clipboard:greenclip print" -show clipboard -run-command '{cmd}'
Display Layout with xrandr: I use arandr
to manually configure multi-monitor layouts for work, personal, and other environments. I save them as shell scripts and use xrandr
to apply them when switching between setups. I use keybings in i3 to quickly switch between them.
# ~/.screenlayout/setup1.sh
# sample xrandr command
xrandr --output HDMI-0 --primary --mode 2560x1440 --pos 1920x0 --rotate normal --output DP-0 --off --output DP-1 --mode 1920x1200 --pos 0x0 --rotate normal --output HDMI-1 --off --output DP-2 --off --output DP-3 --mode 1920x1200 --pos 4480x0 --rotate normal --output DP-4 --off --output DP-5 --off --output HDMI-1-0 --off --output DP-1-0 --off --output DP-1-1 --off --output DP-1-2 --off --output DP-1-3 --off --output USB-C-1-0 --off
in i3:
# ~/.config/i3/config
bindsym $mod+Shift+[ exec ~/.screenlayout/setup1.sh
Start Applications: I map specific applications to dedicated workspaces
exec --no-startup-id i3-msg 'workspace 1; exec /snap/bin/emacs'
exec --no-startup-id i3-msg 'workspace 2; exec /snap/bin/firefox'
exec --no-startup-id i3-msg 'workspace 3; exec i3-sensible-terminal'
exec --no-startup-id i3-msg 'workspace 5; exec /usr/bin/nautilus'
exec --no-startup-id i3-msg 'workspace 9; exec /usr/local/bin/zotero'
Assign workspaces to displays: I map specific applications to dedicated workspaces and assign those workspaces to monitors based on what I’m currently working on. For example, when programming, I prefer Emacs on the primary display and Firefox on a secondary one. For writing, I might reverse that, or shift focus to a different set of apps — the layout adapts to the task. To achieve that, I write scripts using i3-msg
and bind them to keys in i3.
# work_setup__prog.sh
i3-msg "workspace 1, move workspace to output DP-3"
i3-msg "workspace 2, move workspace to output HDMI-0"
i3-msg "workspace 3, move workspace to output DP-1"
i3-msg "workspace 5, move workspace to output DP-1"
and in i3:
# ~/.config/i3/config
bindsym $mod+Shift+o exec /path/to/work_setup__prog.sh
bindsym $mod+Shift+p exec /path/to/work_setup__writing.sh
Start applets, other services:
# nm applet
exec --no-startup-id nm-applet
# bluetooth
exec --no-startup-id blueman-applet
# dropbox
exec --no-startup-id dropbox start
i3bar: I use i3bar with i3status for a clean, minimal status bar
# Base16 Black Metal theme colors
set $base00 #000000
set $base01 #121212
set $base02 #222222
set $base03 #333333
set $base04 #999999
set $base05 #c1c1c1
set $base06 #999999
set $base07 #c1c1c1
set $base08 #5f8787
set $base09 #aaaaaa
set $base0A #a06666
set $base0B #dd9999
set $base0C #aaaaaa
set $base0D #888888
set $base0E #999999
set $base0F #444444
bar {
status_command i3status
font pango:Ubuntu Mono 9
position top
# Disable scrolling in the bar
bindsym button4 nop
bindsym button5 nop
tray_output primary
separator_symbol "|"
colors {
background $base00
separator $base01
statusline $base04
# State Border BG Text
focused_workspace $base05 $base0D $base00
active_workspace $base03 $base01 $base05
inactive_workspace $base03 $base01 $base05
urgent_workspace $base08 $base08 $base00
binding_mode $base00 $base0A $base00
}
}
Media keys:
# Pulse Audio controls
bindsym XF86AudioRaiseVolume exec pactl set-sink-volume @DEFAULT_SINK@ +5% #increase sound volume
bindsym XF86AudioLowerVolume exec pactl set-sink-volume @DEFAULT_SINK@ -5% #decrease sound volume
bindsym XF86AudioMute exec pactl set-sink-mute @DEFAULT_SINK@ toggle # mute sound
# Media player controls
bindsym XF86AudioPlay exec playerctl play
bindsym XF86AudioPause exec playerctl pause
bindsym XF86AudioNext exec playerctl next
bindsym XF86AudioPrev exec playerctl previous
Changing focus, moving workspaces:
# change focus
bindsym $mod+j focus left
bindsym $mod+k focus down
bindsym $mod+l focus up
bindsym $mod+semicolon focus right
# alternatively, you can use the cursor keys:
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
# move focused window
bindsym $mod+Shift+j move left
bindsym $mod+Shift+k move down
bindsym $mod+Shift+l move up
bindsym $mod+Shift+semicolon move right
# alternatively, you can use the cursor keys:
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
# split in horizontal orientation
bindsym $mod+h split h
# split in vertical orientation
bindsym $mod+v split v
# enter fullscreen mode for the focused container
bindsym $mod+f fullscreen toggle
# change container layout (stacked, tabbed, toggle split)
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+e layout toggle split
# toggle tiling / floating
bindsym $mod+Shift+space floating toggle
# change focus between tiling / floating windows
bindsym $mod+space focus mode_toggle
# focus the parent container
bindsym $mod+a focus parent
# focus the child container
#bindsym $mod+d focus child
# switch to workspace
bindsym $mod+1 workspace 1
bindsym $mod+2 workspace 2
bindsym $mod+3 workspace 3
bindsym $mod+4 workspace 4
# ...
# move focused container to workspace
bindsym $mod+Shift+1 move container to workspace 1
bindsym $mod+Shift+2 move container to workspace 2
bindsym $mod+Shift+3 move container to workspace 3
bindsym $mod+Shift+4 move container to workspace 4
# ...
Resizing windows:
# resize window (you can also use the mouse for that)
mode "resize" {
# These bindings trigger as soon as you enter the resize mode
bindsym j resize shrink width 10 px or 10 ppt
bindsym k resize grow height 10 px or 10 ppt
bindsym l resize shrink height 10 px or 10 ppt
bindsym semicolon resize grow width 10 px or 10 ppt
# same bindings, but for the arrow keys
bindsym Left resize shrink width 10 px or 10 ppt
bindsym Down resize grow height 10 px or 10 ppt
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
# back to normal: Enter or Escape
bindsym Return mode "default"
bindsym Escape mode "default"
}
bindsym $mod+r mode "resize"