Computing and the Command Line

Setting Up a New Machine

The payoff: a new Linux machine gets your whole setup in a few commands, git, an SSH key, clone, install.sh, name and email. Keeping machines in step with commit, push, and pull. Settings for one machine only, in ~/.bash_local, ~/.gitconfig, a hostname test, or a git includeIf for work repositories. What never goes in a dotfiles repository. Other ways people manage dotfiles: a bare repository in $HOME, GNU Stow, and tools like chezmoi.

  • 7 min
  • 7 steps
  • 3 questions
  • Lesson 43 of 80

In this lesson

  1. On a new machine
  2. Keeping machines in step
  3. Settings for one machine only
  4. What never goes in
  5. Other ways to do it
  6. Your turn
  7. So

On a new machine

This is what the project was for. On a fresh Ubuntu install (or a new WSL distribution, or a server you log in to), your whole setup is a handful of commands.

1. Install git, which a minimal system may not have:

me@linuxbox:~$ sudo apt install git

2. Give this machine an SSH key and add it to GitHub, as in module 4. One key per machine means you can revoke one without touching the others 1:

me@linuxbox:~$ ssh-keygen -t ed25519 -C "you@example.com"
me@linuxbox:~$ cat ~/.ssh/id_ed25519.pub

3. Clone, install, and set your name and email:

me@linuxbox:~$ git clone git@github.com:you/dotfiles.git ~/dotfiles
Cloning into '/home/me/dotfiles'...
me@linuxbox:~$ ~/dotfiles/install.sh
linked   ~/.bash_aliases
linked   ~/.inputrc
linked   ~/.nanorc
linked   ~/.config/git/ignore
included gitconfig in ~/.gitconfig
me@linuxbox:~$ git config --global user.name "Your Name"
me@linuxbox:~$ git config --global user.email "you@example.com"
me@linuxbox:~$ cat ~/.gitconfig
[include]
    path = /home/me/dotfiles/gitconfig
[user]
    name = Your Name
    email = you@example.com

On a new machine there are no old files in the way, so everything is just linked. The new ~/.gitconfig holds only the include and this machine’s name and email.

4. Load it. Open a new terminal, or reread the startup file in this one (Shell course, module 5):

me@linuxbox:~$ source ~/.bashrc
me@linuxbox:~$ type mkcd
mkcd is a function
mkcd () 
{ 
    mkdir -p -- "$1" && cd -- "$1"
}
me@linuxbox:~$ alias gl
alias gl='git log --oneline -10'

Your prompt, aliases, history settings, Readline keys, nano settings, and git settings are all in place. Put these steps in the repository’s README.md, so future you doesn’t have to remember them.

Top centre, GitHub, holding the private repository you/dotfiles. Left, this machine: 1, edit ~/.bash_aliases; 2, cd ~/dotfiles; 3, git diff; 4, git commit -am; 5, git push; then pull on your other machines. An arrow labelled git push goes up to GitHub. Right, a new machine, with an arrow from GitHub labelled git clone / git pull: 1, sudo apt install git; 2, ssh-keygen and add the key to GitHub; 3, git clone to ~/dotfiles; 4, ~/dotfiles/install.sh; 5, git config --global user.name and email; 6, open a new terminal. Later, cd ~/dotfiles and git pull. In the middle: the same commits on every machine; machine-only settings go in ~/.bash_local, read at the end of bash_aliases, and ~/.gitconfig, for name, email, and anything local. Under each machine, not in git: ~/.bash_local, ~/.gitconfig, private keys, tokens, and passwords.
Clone and install once per machine; after that, commit, push, and pull. Credit: StudyCorner diagram · CC BY 4.0 · Source

Quick check

On a brand-new machine, after cloning ~/dotfiles, what’s next?

Keeping machines in step

Change a setting on any machine, commit, and push. On the others, pull:

me@linuxbox:~/dotfiles$ git push
To github.com:you/dotfiles.git
   f83aa0a..d608458  main -> main

and on the second machine:

me@linuxbox:~/dotfiles$ git pull
From github.com:you/dotfiles
   f83aa0a..d608458  main       -> origin/main
Updating f83aa0a..d608458
Fast-forward
 bash_aliases | 1 +
 1 file changed, 1 insertion(+)

Since the home-folder files are links, the pull updates them too; open a new terminal to pick up shell changes. If you’ve added a new file to LINKS, run ./install.sh again after pulling, which is exactly why it’s safe to rerun.

If you forget to pull before changing something, it’s the same as any repository: the push is rejected, and git pull --rebase then git push sorts it out (module 4). Small commits make that painless.

Settings for one machine only

Some settings shouldn’t be shared: a work email, a folder that only exists on one computer, a server’s prompt color. Missing Semester suggests a few tricks 2:

A local file that’s sourced if it exists. That’s the ~/.bash_local block at the end of bash_aliases. On a machine that needs something extra, create ~/.bash_local there; it’s not in the repository, so it stays on that machine:

# ~/.bash_local on the backup server only
PS1='\[\e[1;31m\]\u@\h\[\e[0m\]:\w\$ '    # red prompt: careful, this is the server
export BACKUP_DEST=/mnt/backup

Because bash_aliases reads it last, anything in it overrides the shared settings.

Git’s includes. ~/.gitconfig is already per-machine. For a different email only in work repositories, git’s conditional include applies a file only when the repository is in a given folder 3:

# in ~/.gitconfig on the work laptop
[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work

with ~/.gitconfig-work holding [user] and email = you@work.example. A pattern ending in / matches that folder and everything inside it 3.

A test in the shared file, when the difference is small and you’d rather keep it in git 2 4:

if [[ "$(hostname)" == "garden-server" ]]; then
    alias logs='journalctl -f'
fi

Quick check

Your work laptop needs a different git email for work repositories. Where does that go?

What never goes in

A dotfiles repository is easy to treat as “my settings folder” and push without a second look. Keep these out, even of a private repository:

  • Private keys: ~/.ssh/id_ed25519 and friends. Each machine makes its own (module 4). ~/.ssh/config, which lists hosts and usernames, is safe if you’re comfortable with what’s in it.
  • Tokens and passwords: API tokens, personal access tokens, .netrc, anything with “secret” in it. If a program’s config file mixes settings and a token, put the token in ~/.bash_local or a separate file that’s not in the repository.
  • History and caches: ~/.bash_history holds every command you’ve typed, passwords included if you ever typed one.

Before each push, git diff --staged (module 2) is a quick look at exactly what’s going out. And if a secret does get committed, deleting it in the next commit isn’t enough: it’s in the history (module 6), so change the secret (module 2).

Quick check

Which of these belongs in your dotfiles repository?

Other ways to do it

The folder-plus-links approach is the one Missing Semester recommends, but it isn’t the only one. Three you’ll see:

  • A bare repository in your home folder. Popularized on Hacker News and described in the Arch Wiki: git init --bare ~/.dotfiles, an alias like alias dotfiles='/usr/bin/git --git-dir="$HOME/.dotfiles/" --work-tree="$HOME"', and dotfiles config status.showUntrackedFiles no. The files stay at their real paths with no links, and you use dotfiles add, dotfiles commit, and so on 4. The cost is that git’s view of your home folder is easy to confuse.
  • GNU Stow, a “symlink farm manager”: arrange the repository in folders that mirror your home folder (bash/.bash_aliases, nano/.nanorc), then stow bash makes the links 5. It replaces install.sh with a convention.
  • Dotfile managers like chezmoi, which add templates for per-machine differences. The Arch Wiki keeps a comparison table 4.

Your script is small, tested, and does exactly what you understand, and you can switch later; your files and their history come with you.

Your turn

Project, part 2

  1. Write the new-machine steps into your README.md and commit.
  2. Simulate a new machine without one: export TESTHOME="$(mktemp -d)", then git clone ~/dotfiles "$TESTHOME/dotfiles" and HOME="$TESTHOME" "$TESTHOME/dotfiles/install.sh". Copy in Ubuntu’s default .bashrc, which new accounts get from /etc/skel: cp /etc/skel/.bashrc "$TESTHOME/". Then HOME="$TESTHOME" bash -i starts a shell using that home; check your prompt and aliases there, and exit.
  3. Create a ~/.bash_local with one setting, open a new terminal, and confirm it’s applied but not in git status.
  4. Add an includeIf for a ~/work/ folder with a different email. Make a repository inside ~/work/ and check git config user.email there and elsewhere.
  5. Run git log -p in ~/dotfiles and read your settings’ history.
  6. When you install Linux, do it for real: a few commands to a familiar shell.
Hints
  1. The clone and install should give five linked/included lines. In the bash -i shell, your prompt should appear; exit returns to your normal one. rm -r "$TESTHOME" afterward.
  2. git status in ~/dotfiles stays clean: ~/.bash_local isn’t in the repository.
  3. Inside ~/work/<repo>, git config user.email prints the work address; elsewhere, your usual one. git config --show-origin user.email shows which file it came from.

So

On a new machine: install git, make an SSH key, clone ~/dotfiles, run install.sh, set your name and email, and open a new terminal. After that, commit and push changes from wherever you make them, and pull everywhere else. Machine-only settings go in ~/.bash_local, ~/.gitconfig (with includeIf for folders), or a hostname test; keys, tokens, and history never go in the repository. That finishes the Git course: you can now version, branch, share, repair, and understand your work, and carry your whole command-line setup to any Linux machine.

Lesson complete

Nice work.

1day streak
0/1today's goal
–correct

Up next · 6 min

Distributions and Releases

Next lesson
Sources for this lesson
  1. 1
    Generating a new SSH key and adding it to the ssh-agent. GitHub Docs. verifiedGenerate a key with ssh-keygen -t ed25519 -C "your_email@example.com" (RSA 4096 for legacy systems), accept the default file, and set a secure passphrase; with a passphrase, add the key to ssh-agent (eval "$(ssh-agent -s)", ssh-add) so the agent remembers it.
  2. 2
    Anish Athalye, Jon Gjengset, Jose Javier Gonzalez Ortiz. Command-line Environment (The Missing Semester of Your CS Education, 2020). MIT CSAIL. verifiedDotfiles section, Portability: if-statements on uname, $SHELL, or hostname for machine-specific settings; git's [include] path = ~/.gitconfig_local; sourcing a shared ~/.aliases file if it exists.
  3. 3
    git-config documentation. Git project (git-scm.com). verifiedReference for reading and writing git settings at the --system, --global, --local, and --worktree levels, with --list and --show-origin, and for every configuration variable, including user.name, user.email, init.defaultBranch, core.editor, core.autocrlf, and alias.*.
  4. 4
    Dotfiles. ArchWiki. verifiedTracking dotfiles directly with git: the bare repository and alias method (git init --bare ~/.dotfiles; alias with --git-dir and --work-tree=$HOME; status.showUntrackedFiles no), host-specific configuration by branches or hostname conditionals, and a comparison table of dotfile tools including GNU Stow and chezmoi.
  5. 5
    GNU Stow manual. Free Software Foundation. verifiedStow is a symlink farm manager: each package lives in its own directory tree and Stow makes symbolic links so it appears installed in a common tree; also used for managing configuration files in a home directory alongside version control.