Configuration for the machines I use: laptop (i3), remote servers, a home server and
a phone (Termux). One repo, linked into $HOME with dotbot.
- Shell: zsh with zinit turbo mode
(plugins load after the first prompt, so startup is ~50 ms),
starship prompt, zoxide (
j), fzf + fzf-tab - Terminal: tmux (plugins via tpm), ghostty / alacritty
- Editor: neovim with LazyVim
- Files / search: yazi, ranger,
fd,ripgrep,bat,eza - Git: forgit, tig,
gh with custom aliases (
bin/git-alias,bin/gh-aliases) - Desktop: i3, i3blocks, rofi
- Misc: cheat, lnav, taskwarrior, beets, cmus
- Packages: zerobrew (fast, sudo-free Homebrew-compatible client), listed in
Brewfile
Needs git and curl. If a private repo or submodule is involved, set up your SSH key first:
eval "$(ssh-agent -s)" && ssh-add # ~/.ssh/id_rsacurl -fsSL https://raw.githubusercontent.com/lbesnard/dotfiles/main/initsys.sh | bashThis installs zerobrew, makes a shallow clone of this repo into ~/github_repo/dotfiles,
installs the Brewfile packages and links everything into $HOME. It is safe to re-run (it
updates instead of cloning again).
Add -s -- --ansible to also run the user-level roles of
Fleet-Control
(miniforge, neovim, cheat):
curl -fsSL https://raw.githubusercontent.com/lbesnard/dotfiles/main/initsys.sh | bash -s -- --ansibleSystem packages, i3, fonts etc. are provisioned by Fleet-Control, which also installs these dotfiles:
ansible-pull -U https://github.com/lbesnard/Fleet-Control.git \
-d ~/ansible_pull_tmp -i ansible/hosts ansible/local.yml -Kgit clone --depth 1 https://github.com/lbesnard/dotfiles.git ~/github_repo/dotfiles
cd ~/github_repo/dotfiles
./install # fetches submodules (shallow) and links the files listed in install.conf.yamlRe-run ./install after adding a link to install.conf.yaml.
First sync the Termux packages (a partial upgrade leaves curl/git broken with cannot locate symbol "SSL_set_quic_tls_early_data_enabled"):
pkg update -y && pkg upgrade -yThen:
curl -fsSL https://raw.githubusercontent.com/lbesnard/dotfiles/main/termux-install.sh | shtermux-install.sh runs pkg upgrade, installs the core tools (zsh, tmux, perl, starship, zoxide, fzf, openssh, neovim)
plus Termux equivalents of the packages in Fleet-Control's homebrew_packages.yml (bat, eza, fd, ripgrep, yazi,
lazygit, gh, ...) with pkg; packages missing from the Termux repo are skipped and listed. It then shallow-clones this repo into ~/dotfiles, runs ./install (dotbot, like every other machine) and sets zsh as login shell. Re-running updates.
- It uses
pkgrather than zerobrew: zerobrew's binaries are built for glibc, Termux uses bionic. cannot locate symbol "SSL_set_quic_tls_early_data_enabled"fromgit/curl: your Termux packages are partially upgraded (new libcurl, old openssl). Runpkg update -y && pkg upgrade -y(trytermux-change-repoif a mirror is stale), then re-run the installer. The script does this upgrade itself.zsh/zshrc.termuxis a self-contained, minimal zsh config. tmux autostarts in interactive shells (attaches to sessionmain); setexport TERMUX_NO_TMUX=1in~/.zshenvto disable.- tmux uses the same Oh my tmux!
tmux.confas the other machines, withtmux/tmux.conf.termuxas its~/.tmux.conf.local. That file sourcestmux.conf.ssh(the server look, prefixC-b) and adds phone tweaks (no pane-border row, no plugin cloning, Android clipboard). Oh my tmux needsperl, which the installer adds. - Optional:
pkg i termux-api(+ the Termux:API app) so copy-modeyreaches the Android clipboard;pkg i termux-servicesthensv-enable ssh-agent/sv-enable sshdto run them as services.
This repo is public, but credentials live in it encrypted with age.
Everything listed in secrets.list (aws, ssh config, tokens, taskrc, ...) exists as:
- a plaintext file in the usual place: gitignored, so
git statusnever shows it; - a
<file>.agenext to it: encrypted to the public key inrecipients.txt, committed.
The private key is ~/.config/age/key.txt. It is not in the repo; keep a copy in KeePass.
Without it you get the public dotfiles only and everything else still works.
./install does it all: it offers to install zerobrew + age (and pre-commit via pip),
asks you to paste the key (hidden; the AGE-SECRET-KEY-1... line is enough, it is checked against
recipients.txt), decrypts, installs the git hooks and links the secret files
(install.private.conf.yaml). Press Enter at the key prompt to skip.
git status will not show an edited secret (it is gitignored). Check with:
cd ~/github_repo/dotfiles
./secrets status # "plaintext differs from .age" = you have an unencrypted change
./secrets encrypt # writes the .age files
git add -A && git commit -m "..." && git pushRun ./secrets encrypt yourself when the secret is the only thing you changed: the commit hook only
runs once git sees something to commit, so a secret-only change would otherwise say "nothing to commit".
When you commit other files too, the hook re-encrypts for you (with the pre-commit framework the first
attempt stops once after staging the .age files; commit again). It also refuses to commit a plaintext
secret or an *.age file that is not really age.
A post-merge / post-rewrite hook runs ./secrets decrypt after git pull, so the plaintext follows.
It never overwrites a local edit that was not encrypted yet: you get a CONFLICT line and your file is kept
(./secrets decrypt --force to take the repo's version). It says what it did (secrets: refreshing decrypted secrets after pull..., then decrypt: N updated, M unchanged); without a key it prints a one-line note, without age it warns.
git pull itself never fails because of it.
- Add its path to
secrets.listand to.gitignore(encryptrefuses a path that is not ignored). ./secrets encrypt, then commit the new.agefile.- If it needs a symlink, add it to
install.private.conf.yaml(notinstall.conf.yaml).
secrets is also on your PATH (bin/secrets), so these work from any directory, with zsh tab completion.
| Command | |
|---|---|
./secrets status |
key, hook, and the state of every secret |
./secrets encrypt [--stage|--add] |
re-encrypt changed plaintext (only if it really changed; age output is random) |
./secrets decrypt [--force] [--quiet|--hook] |
write plaintext from the .age files |
./secrets install-hook |
git hooks: pre-commit framework if it runs, else plain git hooks |
./secrets install-completion |
link the zsh completion (zsh/completions/_secrets) into your $fpath; ./install does it too |
./secrets bootstrap |
the interactive install/paste-the-key step of ./install |
- The ciphertext is public and permanent. If the key leaks, anything ever pushed can be read, and changing
recipients.txtdoes not help for old commits: rotate the secrets themselves (new tokens, keys, passwords). - Never commit a secret in plaintext by accident: if you did, rewriting history is not enough on its own, see how fleet-compose rotates TLS/ACME keys.
- The key is a random file, not a passphrase, so brute-forcing the public ciphertext is not realistic.
- Nextcloud and Joplin keep their state in real
~/.configfolders; only their config files are linked from here.
| File | Role |
|---|---|
zsh/zshrc |
Detects the machine profile once (termux, server, homeserver, laptop; override with ZSH_PROFILE_NAME) and sources the right files |
zsh/zshrc.common |
Shared config: tmux autostart (first, so the outer shell does no wasted work), zerobrew PATH, prompt, turbo-loaded plugins, fzf, completion styling |
zsh/zshrc.laptop, .server, .homeserver |
Per-machine additions |
zsh/zshrc.termux |
Standalone Termux config |
zsh/functions/, zsh/completions/ |
Autoloaded functions and completions |
alias, alias_forgit |
Aliases and helper functions (loaded last so they override plugin aliases) |
config/starship.toml |
Prompt config |
Profile startup with ZSH_PROFILE=1 zsh -i -c exit (prints a zprof report), or
for i in {1..5}; do time zsh -i -c exit; done.
zb install <formula>
zb bundle dump -f Brewfile --force # keep Brewfile in syncinstall, install.conf.yaml dotbot entrypoint and link map
initsys.sh, Brewfile bootstrap for fresh machines
termux-install.sh bootstrap for Termux (Android)
zsh/ tmux/ nvim/ config/ tool configs (config/* is linked into ~/.config)
bin/ scripts, put on $PATH as ~/bin
secrets, secrets.list age-encrypted secrets: tool, list of files (+ *.age, recipients.txt)
install.private.conf.yaml links for decrypted secrets (only used when the age key is present)
