Reproducible Ubuntu/Debian workstation setup based on a bare Git repository.
The bare repository is stored at:
~/.dotfiles
The Git work tree is the home directory:
$HOME
This setup intentionally uses plain Git and Bash rather than Stow or chezmoi.
The main helper is:
dotfiles() {
/usr/bin/git --git-dir="$HOME/.dotfiles" --work-tree="$HOME" "$@"
}After the shell configuration has loaded, use dotfiles like a normal Git command:
dotfiles status
dotfiles diff
dotfiles log --oneline
dotfiles push.
├── .bash_aliases
├── .bash_logout
├── .bashrc
├── .bashrc.work
├── .gitconfig
├── .gitignore
├── .gitignore_global
├── .profile
├── .tmux.conf
├── README.md
├── bootstrap/
│ ├── install.sh
│ ├── install-dotfiles.sh
│ ├── install-apt.sh
│ ├── install-snap.sh
│ ├── install-uv.sh
│ ├── install-docker.sh
| ├── install-zotero.sh
| └── install-nzbridge.sh
│ ├── install-vscode.sh
│ └── install-gnome.sh
└── .config/
├── Code/
│ └── User/
│ ├── extensions.txt
│ ├── keybindings.json
│ ├── settings.json
│ └── snippets/
└── dotfiles/
├── git-hooks/
│ └── pre-commit
├── gnome/
│ ├── save.sh
│ ├── apply.sh
│ └── terminal/
│ ├── profiles.dconf
│ ├── save.sh
│ └── apply.sh
├── packages/
│ ├── apt-base.txt
│ ├── apt-work.txt
│ ├── apt-personal.txt
│ ├── snap-base.txt
│ ├── snap-work.txt
│ └── snap-personal.txt
└── shell/
├── dotfiles.sh
├── vscode.sh
└── health.sh
GNOME Terminal configuration belongs under:
~/.config/dotfiles/gnome/terminal/
Install the minimum prerequisites:
sudo apt update
sudo apt install -y git curlClone the repository as a bare Git repository:
git clone --bare git@github.com:b0nl/dotfiles.git "$HOME/.dotfiles"Check out the work tree:
git --git-dir="$HOME/.dotfiles" --work-tree="$HOME" checkoutRun the complete bootstrap:
~/bootstrap/install.shReload Bash and run the health check:
source ~/.bashrc
dotfiles-healthA fresh machine may already contain files such as .bashrc or .profile.
Back conflicting files up under:
~/.dotfiles-backup/
Then retry the checkout.
Do not delete conflicting files until the backup has been checked.
The bootstrap asks once whether the computer is a work laptop.
The selected value is stored in the local configuration of the bare repository:
dotfiles config --local dotfiles.machine workor:
dotfiles config --local dotfiles.machine personalInspect the current value with:
dotfiles config --local --get dotfiles.machineThe value is stored in:
~/.dotfiles/config
It is local to the current computer. It is not tracked or pushed.
The prompt is skipped when dotfiles.machine is already configured.
For a non-interactive installation, set the value explicitly:
DOTFILES_MACHINE=work ~/bootstrap/install.shor:
DOTFILES_MACHINE=personal ~/bootstrap/install.shA work installation:
- installs the base and work package manifests;
- loads
~/.bashrc.work; - installs
kubectl; - installs Docker;
- adds the current user to the
dockergroup; - prompts for Docker registry authentication to
hub.your-work.com.
A personal installation:
- installs the base and personal package manifests;
- does not load a separate personal Bash file;
- skips work-only bootstrap actions.
There is currently no .bashrc.local layer.
The main Bash entrypoint is:
~/.bashrc
It loads the tracked shell helpers under:
~/.config/dotfiles/shell/
Known helpers:
dotfiles.sh
vscode.sh
health.sh
When dotfiles.machine is set to work, Bash also loads:
~/.bashrc.work
Shell files should be sourced defensively:
[ -f "$HOME/.config/dotfiles/shell/dotfiles.sh" ] &&
. "$HOME/.config/dotfiles/shell/dotfiles.sh"The top-level installer is:
~/bootstrap/install.sh
It runs the individual installers in this order:
install-dotfiles.sh
install-apt.sh
install-snap.sh
install-uv.sh
install-docker.sh
install-vscode.sh
install-gnome.sh
The top-level coordinator:
- runs the dotfiles checkout/configuration step first;
- reads the existing
dotfiles.machinevalue; - prompts once when the machine type is not configured;
- stores the answer in the bare repository's local Git config;
- exports
DOTFILES_MACHINEfor the remaining installers; - runs the installers in order.
Run it with:
~/bootstrap/install.shResponsible for:
- cloning or reusing the bare repository;
- checking out the home-directory work tree;
- backing up conflicting files;
- setting
status.showUntrackedFiles no; - setting the tracked Git hook path;
- configuring the VSCode diff tool when
codeis available; - ensuring the expected local Git configuration exists.
Machine selection is not stored in a separate profile file.
Installs curated apt packages from:
~/.config/dotfiles/packages/
Behavior:
- always installs
apt-base.txt; - installs
apt-work.txtwhendotfiles.machine=work; - installs
apt-personal.txtwhendotfiles.machine=personal; - skips missing, blank, and comment-only lines.
Installs curated Snap packages from:
~/.config/dotfiles/packages/
Behavior:
- always installs
snap-base.txt; - installs
snap-work.txtwhendotfiles.machine=work; - installs
snap-personal.txtwhendotfiles.machine=personal; - supports package flags in the manifest.
The work manifest includes:
kubectl --classic
Verify the installation with:
kubectl version --clientKubernetes credentials and cluster configuration under ~/.kube/ must remain untracked.
Installs uv when it is missing.
Updating is intentionally manual:
uv self updateInstalls Docker Engine from Docker's official apt repository.
It also:
- ensures the
dockergroup exists; - adds the current user to the group;
- enables and starts Docker when systemd is available;
- prompts work machines to log in to:
hub.your-work.com
The registry login remains interactive:
docker login hub.your-work.comPasswords and access tokens must never be stored in the bootstrap scripts or committed to the dotfiles repository.
Docker writes local authentication state under:
~/.docker/
That directory must remain untracked.
Docker group membership normally becomes active after logging out and back in.
For the current shell only, it can be activated with:
newgrp dockerCheck membership with:
id -nG | tr ' ' '\n' | grep -x dockerMembership in the docker group grants effectively root-level control over the machine.
Installs extensions listed in:
~/.config/Code/User/extensions.txt
Applies tracked GNOME configuration only when:
dconfis available;- a graphical desktop session is present;
- the relevant tracked configuration files exist.
It should not blindly apply desktop settings on headless systems.
Package manifests live under:
~/.config/dotfiles/packages/
Files:
apt-base.txt
apt-work.txt
apt-personal.txt
snap-base.txt
snap-work.txt
snap-personal.txt
*-base.txt contains packages wanted on every machine.
*-work.txt contains work-only packages.
*-personal.txt contains personal-machine packages.
The active machine type is read with:
dotfiles config --local --get dotfiles.machineChange it manually with:
dotfiles config --local dotfiles.machine workor:
dotfiles config --local dotfiles.machine personalReload Bash after changing it:
source ~/.bashrcKeep package manifests curated.
Do not track generated package snapshots such as:
apt-manual-current.txt
apt-after-install-current.txt
snap-current.txt
The tracked pre-commit hook is:
~/.config/dotfiles/git-hooks/pre-commit
It sorts and deduplicates apt and Snap manifests before commits.
Because the repository is bare, the hook must use the dotfiles Git command rather than plain git add.
If a commit stops after:
==> Sorting package manifests
debug it with:
bash -x ~/.config/dotfiles/git-hooks/pre-commitTemporary bypass:
dotfiles commit --no-verify -m "message"Because $HOME is the Git work tree, never run these casually from the home directory:
dotfiles add .
dotfiles add -AThey can stage the entire home directory.
For already tracked files, prefer:
dotfiles add -uFor new files, add explicit paths:
dotfiles add .gitconfig
dotfiles add bootstrap/install-docker.sh
dotfiles add .config/dotfiles/shell/health.shBefore committing:
dotfiles diff --cachedHidden untracked files
Normal status output hides untracked files:
dotfiles config --local status.showUntrackedFiles noThis only changes status display. It does not ignore files.
Audit untracked files with:
dotfiles status --short --untracked-files=all
dotfiles-audit
dotfiles-audit-allDo not track credentials, private keys, machine state, or application caches.
Examples:
.dotfiles/
.dotfiles-backup/
.gitconfig.local
.cache/
.local/
.ssh/
.aws/
.gnupg/
.kube/
.docker/
*.ovpn
.env
.env.*
.lesshst
An OpenVPN file may contain certificates, private keys, credentials, or private endpoints. Inspect it carefully and keep it untracked by default.
Tracked Git files:
~/.gitconfig
~/.gitignore
~/.gitignore_global
Potential local-only file:
~/.gitconfig.local
Recommended core settings:
[core]
editor = code --wait
excludesfile = ~/.gitignore_global
autocrlf = inputMeaning:
editor = code --waitopens VSCode for Git editing and waits for it to close;excludesfile = ~/.gitignore_globalapplies user-wide ignore rules;autocrlf = inputnormalizes CRLF to LF when committing.
.gitignore applies to the bare dotfiles repository's home-directory work tree.
.gitignore_global applies to every normal Git repository on the machine.
Tracked VSCode files:
~/.config/Code/User/settings.json
~/.config/Code/User/keybindings.json
~/.config/Code/User/extensions.txt
~/.config/Code/User/snippets/
Do not track all of ~/.config/Code; it contains caches and machine state.
VSCode helper functions live in:
~/.config/dotfiles/shell/vscode.sh
Typical update workflow:
vscode-save-config
dotfiles add \
.config/Code/User/settings.json \
.config/Code/User/keybindings.json \
.config/Code/User/extensions.txt
dotfiles diff --cached
dotfiles commit -m "Update VSCode configuration"
dotfiles pushKeep global Python settings restrained. Let each repository's pyproject.toml define strict linting and type-checking behavior.
A reasonable global baseline is:
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.terminal.activateEnvironment": true,
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true,
"ruff.nativeServer": "on"
}GNOME helpers live under:
~/.config/dotfiles/gnome/
Useful dconf dumps:
dconf dump /org/gnome/desktop/interface/
dconf dump /org/gnome/desktop/wm/keybindings/
dconf dump /org/gnome/settings-daemon/plugins/media-keys/
dconf dump /org/gnome/terminal/legacy/profiles:/GNOME Terminal profiles are stored beneath:
/org/gnome/terminal/legacy/profiles:/
The restore process must remain guarded and should not run on headless machines.
Health helpers live in:
~/.config/dotfiles/shell/health.sh
Run:
dotfiles-healthThe health check verifies:
- the
dotfilesfunction; - dotfiles Git status;
- the local
dotfiles.machinevalue; - core commands such as
git,curl,uv,code, andzotero; - Docker installation;
- Docker group membership;
- whether the Docker group is active in the current shell;
- Docker daemon access;
kubectlon work machines;- the current kubectl context;
- the local Docker login entry for
hub.your-work.com; - tracked VSCode configuration files;
- bootstrap script executability.
Work-only checks run only when:
dotfiles.machine = work
Output colors:
OK green
MISSING red
WARN yellow
sections blue
Run it after bootstrap or after changing shell helpers:
source ~/.bashrc
dotfiles-healthdotfiles
dotfiles-ensure-config
dotfiles-audit
dotfiles-audit-all
dotfiles-pick
vscode-save-extensions
vscode-install-extensions
vscode-edit-settings
vscode-save-config
dotfiles-healthIf available:
dotfiles-check-scriptsRecommended checks:
source ~/.bashrc
bash -n ~/.config/dotfiles/shell/health.sh
shellcheck \
~/bootstrap/*.sh \
~/.config/dotfiles/shell/*.sh
dotfiles-health
dotfiles status -sb
dotfiles log --oneline --graph --decorate -10Check:
dotfiles config --local --get dotfiles.machineConfigure a work machine:
dotfiles config --local dotfiles.machine workConfigure a personal machine:
dotfiles config --local dotfiles.machine personalReload Bash:
source ~/.bashrcCheck the current shell:
id -nG | tr ' ' '\n' | grep -x dockerLog out and back in.
For a temporary shell:
newgrp dockerThen verify:
docker infoRun:
docker login hub.your-work.comThen:
dotfiles-healthDo not commit:
~/.docker/config.json
Rerun the Snap installer:
~/bootstrap/install-snap.shOr install it directly:
sudo snap install kubectl --classicVerify:
kubectl version --clientCheck:
kubectl config current-contextKubernetes normally reads configuration from:
~/.kube/config
Obtain the correct kubeconfig through the approved work process.
Do not commit ~/.kube/.
Run:
vscode-install-extensionsor:
~/bootstrap/install-vscode.shConfirm that:
- the system has a graphical GNOME session;
dconfis installed;- the tracked dump files exist;
- the apply script is executable.
Do not force GNOME restoration on a headless system.
Stage only explicit paths:
dotfiles add \
README.md \
.bashrc \
.gitignore \
bootstrap/install.sh \
bootstrap/install-dotfiles.sh \
bootstrap/install-apt.sh \
bootstrap/install-snap.sh \
bootstrap/install-docker.sh \
.config/dotfiles/packages/snap-work.txt \
.config/dotfiles/shell/health.shReview before committing:
dotfiles diff --cachedCommit and push:
dotfiles commit -m "Add machine-aware work bootstrap"
dotfiles pushNever replace the explicit staging command above with:
dotfiles add .
dotfiles add -APotential future work:
- test the bootstrap in a fresh Ubuntu VM or WSL installation;
- add non-Ubuntu operating-system guards if needed;
- refine GNOME interface and keybinding configuration;
- add per-project
.vscode/settings.jsonortasks.jsonfiles where useful; - add project-specific
CLAUDE.mdor.github/copilot-instructions.md; - track sanitized SSH configuration only when it is demonstrably safe;
- revisit chezmoi only if multiple operating systems or secret templating make the bare Git approach too cumbersome.
Ledger Wallet, formerly Ledger Live, is installed on personal machines through:
~/bootstrap/install-ledger-wallet.sh
The installer:
- downloads the current official Linux AppImage;
- installs it under
~/.local/opt/ledger-wallet/; - creates the
ledger-walletcommand under~/.local/bin/; - creates a desktop application entry;
- installs the Linux USB rules required for Ledger hardware devices;
- skips work machines automatically.
Launch it from the application menu or run:
ledger-wallet
Rerunning the installer replaces the installed AppImage with the current official Linux release.
The downloaded binary's SHA-512 hash is printed during installation. It can be checked against Ledger's official download-signatures page before first launch.
Ledger application data and account metadata must remain untracked. Never store a PIN, recovery phrase, private key, account export, or screenshot of a recovery phrase in the dotfiles repository.
Xpad provides lightweight desktop sticky notes similar to Windows Sticky Notes.
It is installed through the base apt package manifest:
~/.config/dotfiles/packages/apt-base.txt
Launch it from the application menu or run:
xpadUseful commands include:
xpad --new
xpad --show
xpad --toggle
xpad --quitXpad stores notes and local application state under:
~/.config/xpad/
This directory must remain untracked because it may contain private notes and machine-specific state. Xpad should be treated as a local scratch-note application rather than a synchronized or version-controlled knowledge store.
Zotero is installed through a dedicated bootstrap script:
~/bootstrap/install-zotero.shThe installer configures the Zotero apt repository and installs Zotero through apt so it receives normal system updates.
After installation, open Zotero and sign in under:
Edit → Settings → Sync
Use the same Zotero account as the web library to synchronize collections, references, notes, and attachments.
Zotero’s local library and application state must remain untracked:
~/.zotero/
~/Zotero/
These directories may contain databases, PDFs, attachments, notes, logs, and account state.
Do not store the active Zotero database inside Dropbox, Google Drive, OneDrive, or another general filesystem-sync directory. Use Zotero Sync for library synchronization.
NZBridge provides bidirectional transfer between Zotero and Google NotebookLM.
It consists of:
- a Zotero plugin;
- an unpacked Chrome or Edge extension.
Prepare both components with:
~/bootstrap/install-nzbridge.shThe downloaded files are stored under:
~/.local/share/nzbridge/
In Zotero, open:
Tools → Plugins → gear icon → Install Plugin From File
Select:
~/.local/share/nzbridge/nz-bridge.xpi
Restart Zotero if requested.
Open:
chrome://extensions
Then:
- Enable Developer mode.
- Select Load unpacked.
- Choose:
~/.local/share/nzbridge/browser-extension/
- Pin NZBridge to the browser toolbar.
- Open the extension’s site settings and allow local-network access if Chrome requests it.
Zotero must be running for the browser extension to communicate with the NZBridge plugin.
Run the installer to download and prepare the latest NZBridge release:
~/bootstrap/install-nzbridge.shThe installer updates both the Zotero plugin and the unpacked browser-extension files under:
~/.local/share/nzbridge/
After the update:
- Open
chrome://extensionsoredge://extensions. - Find NZBridge.
- Click Reload.
You normally do not need to select Load unpacked again because the installer keeps the extension at the same path:
~/.local/share/nzbridge/browser-extension/
To update the Zotero plugin, open Zotero and reinstall:
~/.local/share/nzbridge/nz-bridge.xpi
using:
Tools → Plugins → gear icon → Install Plugin From File
Check for an available update without installing it:
~/bootstrap/install-nzbridge.sh --checkForce a fresh reinstall:
~/bootstrap/install-nzbridge.sh --forceZotero remains the canonical research library. NotebookLM is used for analysis, synthesis, and exploratory questioning.
A typical flow is:
Zotero collection
↓
NotebookLM notebook
↓
Saved NotebookLM notes
↓
Imported back into Zotero
For Zotero to NotebookLM:
- Create a Zotero collection for the project.
- Add papers, PDFs, URLs, and metadata.
- Open the corresponding NotebookLM notebook.
- Use NZBridge’s To NotebookLM tab to transfer the sources.
Note that PDFs will not show up on the browser extension unless their metadata (eg. author, etc.) is retrieved and attached to the PDF in Zotero.
For NotebookLM to Zotero:
- Save useful NotebookLM output as a note.
- Open NZBridge’s To Zotero tab.
- Select the originating Zotero collection.
- Import the saved notes.
- Add provenance and review tags where useful.
Example tags:
origin:notebooklm
status:needs-review
project:<project-name>
Imported NotebookLM content should be treated as research notes, not as an authoritative citation source. Verify important claims against the original Zotero items before citing or publishing them.
Do not track:
~/.zotero/
~/Zotero/
~/.local/share/nzbridge/
Also keep Zotero credentials, Google sessions, browser profiles, NotebookLM authentication state, and research-library data outside the dotfiles repository.