Skip to main content
  1. Posts/
  2. The Solo Stack/
  3. Dotfiles/

From Clone to Commit: Setting Up Git in chezmoi

Dotfiles - This article is part of a series.
Part 6: This Article

In the previous post, we automated the installation of age and zsh with a run_once script. After running chezmoi init --apply on a fresh machine, the right tools are now in place and our SSH keys are decrypted. But there’s a catch — we still can’t push.

Three Gaps
#

After chezmoi init --apply, the dotfiles repo is cloned via HTTPS. That’s how chezmoi fetches it by default. And while the repo is there and the files are applied, git itself isn’t ready:

  1. No identity. Git doesn’t know who you are. Try to commit and you’ll get the familiar Please tell me who you are error.
  2. No SSH key configured. Even though the SSH key is now decrypted and sitting in ~/.ssh/, git doesn’t know to use it.
  3. Wrong remote URL. The origin remote still points to https://github.com/.... You need it to be git@github.com:... to push over SSH.

All three need to be fixed before we can make our first commit or push from this machine. And since we already have the run_once pattern working, this is a natural fit for another script.

The Script
#

Here’s run_once_configure-git-repo.sh, which lives in the .chezmoiscripts/ directory:

#!/bin/bash
set -euo pipefail

CHEZMOI_SOURCE="${CHEZMOI_SOURCE_DIR%/}"
SSH_KEY="$HOME/.ssh/id_ed25519_knuth-info"

git -C "$CHEZMOI_SOURCE" config user.name "Marcus Knuth"
git -C "$CHEZMOI_SOURCE" config user.email "github@knuth.info"
git -C "$CHEZMOI_SOURCE" config core.sshCommand "ssh -i $SSH_KEY"

# Switch remote from HTTPS to SSH if needed
CURRENT_URL="$(git -C "$CHEZMOI_SOURCE" remote get-url origin)"
if [[ "$CURRENT_URL" == https://github.com/* ]]; then
    # Convert https://github.com/user/repo.git -> git@github.com:user/repo.git
    SSH_URL="${CURRENT_URL/https:\/\/github.com\//git@github.com:}"
    git -C "$CHEZMOI_SOURCE" remote set-url origin "$SSH_URL"
fi

Let’s walk through each part.

Setting Git Identity
#

CHEZMOI_SOURCE="${CHEZMOI_SOURCE_DIR%/}"

git -C "$CHEZMOI_SOURCE" config user.name "Marcus Knuth"
git -C "$CHEZMOI_SOURCE" config user.email "github@knuth.info"

Chezmoi exposes CHEZMOI_SOURCE_DIR as an environment variable during script execution — it points to the local chezmoi source directory (where the repo lives). The %/ trims any trailing slash, just to keep paths clean.

The git -C flag tells git to operate as if it were inside that directory. This means the user.name and user.email are set locally for the chezmoi repo only, not globally. Other git repos on the machine won’t be affected.

Pointing Git at the Right SSH Key
#

SSH_KEY="$HOME/.ssh/id_ed25519_knuth-info"

git -C "$CHEZMOI_SOURCE" config core.sshCommand "ssh -i $SSH_KEY"

By default, SSH tries keys in a standard order (id_rsa, id_ed25519, etc.). But our key has a specific name — id_ed25519_knuth-info — because it’s dedicated to this repo. Setting core.sshCommand tells git to use SSH with the -i flag pointing to that exact key.

This is scoped to the chezmoi repo, just like the identity config. If you have other repos using different keys, they won’t be affected.

Switching From HTTPS to SSH
#

CURRENT_URL="$(git -C "$CHEZMOI_SOURCE" remote get-url origin)"
if [[ "$CURRENT_URL" == https://github.com/* ]]; then
    SSH_URL="${CURRENT_URL/https:\/\/github.com\//git@github.com:}"
    git -C "$CHEZMOI_SOURCE" remote set-url origin "$SSH_URL"
fi

This is the part that makes pushing possible. When chezmoi clones the repo during init, it uses the HTTPS URL. This block checks whether the origin remote is still HTTPS, and if so, converts it to the SSH equivalent.

The conversion uses bash string substitution: https://github.com/user/repo.git becomes git@github.com:user/repo.git. The if guard makes the script safe to re-run — if the URL is already SSH, it does nothing.

Why run_once?
#

Like the package installation script from the previous post, this uses chezmoi’s run_once prefix. Git identity and remote URLs only need to be configured once per machine. Re-running the script is harmless — it would just set the same values again — but there’s no reason to do it on every chezmoi apply.

If you ever need to change something (say, a new email address), just update the script. Chezmoi tracks the script’s content hash, so it’ll re-run automatically on the next apply.

Adding the Script to chezmoi
#

We already have a .chezmoiscripts/ directory from the package installation script, so the new script goes right next to it:

chezmoi cd
touch .chezmoiscripts/run_once_configure-git-repo.sh
chmod +x .chezmoiscripts/run_once_configure-git-repo.sh
# (paste in the script contents)
git add .chezmoiscripts/run_once_configure-git-repo.sh
git commit -m "add run_once script for git repo configuration"
git push

Testing It
#

Same approach as always — spin up a disposable container and run through the full setup:

podman run -it --rm debian:bookworm-slim bash
apt update -y && apt upgrade -y && apt install curl git nano -y
mkdir -p ~/.config/age
chmod 700 ~/.config/age
touch ~/.config/age/dotfiles-repo-key.txt
chmod 600 ~/.config/age/dotfiles-repo-key.txt
nano ~/.config/age/dotfiles-repo-key.txt  # paste in key
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply knuth-info
zsh

After chezmoi finishes, check that everything landed correctly:

chezmoi cd
git config user.name     # → Marcus Knuth
git config user.email    # → github@knuth.info
git remote -v            # → git@github.com:knuth-info/dotfiles.git (SSH)

If the SSH key is in place and your GitHub account has the matching public key, you can test a push directly. Everything should work without any additional configuration.

What We’ve Achieved So Far
#

The manual steps for setting up a new machine haven’t changed from last time — we’re still placing the age key and running chezmoi init --apply. But the result is now more complete. After the same set of commands, you get:

  • zsh installed and set as default shell
  • The correct version of age, installed from source
  • SSH keys decrypted and in place
  • Git identity configured for the dotfiles repo
  • SSH key linked to git
  • Remote URL switched to SSH, ready to push

That last part is the real payoff. On a fresh machine, after running chezmoi init --apply, you can immediately start making changes to your dotfiles and push them back to the repo. No manual git setup required.

What’s Next
#

The setup flow is getting solid, but there’s more ground to cover. Next, we’ll look at making the dotfiles repo easier to work with day-to-day — things like auto-committing changes and keeping machines in sync.

Marcus Knuth
Author
Marcus Knuth
Dotfiles - This article is part of a series.
Part 6: This Article

Related

Automating age Installation with chezmoi

·940 words·5 mins
In the previous article, we encrypted our SSH keys with age and stored them in the chezmoi repo. That’s great — but there’s a gap. When we set up a new machine and run chezmoi init, chezmoi needs age to decrypt those keys. And as we saw earlier in the series, the version of age available via apt on Debian is too old for post-quantum support.

Why apt install age Isn't Enough

·612 words·3 mins
In the previous article, I set up disposable containers to test my chezmoi dotfiles. That works, but there’s still too much manual effort involved — especially the SSH key. Every new container means copying it in by hand. So I decided to encrypt the SSH key with age and store it directly in the dotfiles repo. Chezmoi supports age-based decryption out of the box, which makes this a natural fit. On top of that, I want future tool installations — zsh, tmux, neovim — to be handled automatically too, not typed in manually every time.