Set up a workstation
Review, install, customize, and reapply the workstation configuration.
On this page15 sections
Use the review-first path for an existing machine. The one-line installer is intended for a new machine or for a configuration you already trust.
Before you begin
The full setup can:
- install apt packages, Homebrew packages, and macOS applications;
- change your login shell to fish;
- initialize chezmoi and force-apply files from this repository;
- install tmux and Neovim plugins;
- install Rancher Desktop on macOS; and
- change macOS defaults, Dock contents, and keyboard shortcuts.
Back up your existing configuration before continuing. At minimum, inspect
config.yaml
and the files under
config/.
Platform requirements
macOS
Setup requires the Xcode Command Line Tools and stops if they are missing. Install available system updates and the tools before the first run:
sudo softwareupdate -i -axcode-select --installUbuntu
Use Ubuntu on ARM64 with a non-root account authorized to use the installed
sudo command. The base installation must provide a POSIX shell, curl,
tar, sed, and standard shell utilities. Ansible installs Git, native apt
prerequisites, and Homebrew.
Apple Silicon macOS and Ubuntu ARM64 are the supported targets; setup rejects Intel macOS and x86-64 Ubuntu. It validates the operating system and architecture before downloading sources or requesting elevated privileges.
Recommended: review, then run
Clone the repository so you can inspect exactly what the setup will run. Pay
particular attention to
config.yaml
and the
main.yaml
playbook:
repository_url=https://github.com/shmileee/dotfiles.gitcheckout="$HOME/ghq/personalgit/shmileee/dotfiles"mkdir -p "$(dirname "$checkout")"git clone "$repository_url" "$checkout"cd "$checkout"
less bootstrap/ansible/config.yamlless bootstrap/ansible/main.yamlWhen you are comfortable with the configuration, run the complete setup:
./bootstrap/setup.shThe local command is also the recovery path after a bootstrap that created the checkout but did not finish. It builds a disposable locked Ansible controller from this checkout and preserves all durable Ansible changes if a task fails.
Fast path: bootstrap a new machine
curl -fsSL https://dotfiles.oponomarov.com/d | shhttps://dotfiles.oponomarov.com/d is the author’s convenience redirect for
installing this repository on a fresh, trusted workstation. It redirects to
bootstrap/setup.sh
on the configured mutable branch. The POSIX loader stages that branch in a
private temporary workspace, downloads the pinned uv controller, and hands all
workstation changes to Ansible. The controller workspace is deleted on every
ordinary exit; uv’s normal download cache is retained.
The command above keeps certificate verification enabled. A minimal Ubuntu
base without a CA bundle cannot validate even this first HTTPS request; only on
that base, fetch the loader with
curl -kfsSL https://dotfiles.oponomarov.com/d | sh.
The loader detects the missing bundle and limits disabled verification to its
remaining pre-controller curl downloads and the pre-Ansible Galaxy collection
install. uv verifies the locked Python environment with its own trusted roots.
Ansible then installs and updates ca-certificates; the persistent Git clone
and every later download use normal certificate verification. macOS and Ubuntu
systems with an existing CA bundle never disable verification. No persistent
insecure setting is written.
Review the installer first
If you want the convenience of the bootstrap without piping directly into a shell:
curl -fsSL https://raw.githubusercontent.com/shmileee/dotfiles/master/bootstrap/setup.sh > setup.shless setup.shchmod +x setup.sh./setup.shWhat setup does
-
01
Validate the platform
Reject root, an invalid home, occupied fresh-install targets, and unsupported platforms before privileged work.
-
02
Build the controller
Stage the configured repository branch, validate pinned uv, and synchronize the locked Python and Ansible project.
-
03
Provision with Ansible
Install Ubuntu prerequisites and Git, create the persistent checkout, then install Homebrew and the configured workstation state.
-
04
Hand off to mise
Prepare the persistent checkout for future updates with
mise run reconcile.
Recover or reconcile
| Command | Purpose |
|---|---|
./bootstrap/setup.sh | Bootstrap a workstation or recover a valid incomplete persistent clone. |
mise run reconcile | Run normal ongoing reconciliation from a completed checkout. |
mise run reconcile:check | Preview supported changes after bootstrap. |
mise run voice:setup | Download the voice dictation models and start the local transcription service. Manual because it fetches ~3 GB. |
The hosted one-line installer is for a new workstation. It stops if the derived
checkout path already exists and leaves that path untouched. For a valid
incomplete checkout, run ./bootstrap/setup.sh from that checkout. After a
successful setup, use mise run reconcile; rerun the local setup script only if
the installed mise environment needs to be recovered.
Customize the setup
Make changes in four places:
| Area | Source of truth | Typical changes |
|---|---|---|
| Packages and applications | bootstrap/ansible/config.yaml | Homebrew packages, casks, Dock items, keyboard shortcuts |
| System behavior | bootstrap/ansible/roles/ | Installation logic and macOS defaults |
| Home-directory files | config/ | fish, Git, tmux, Neovim, Alacritty, OpenCode |
| Tool versions | config/private_dot_config/mise/config.toml | Language runtimes and developer tools |
The dotfiles.checkout value in
config.yaml
points chezmoi at the persistent path derived from the configured repository
slug. The role force-applies that checkout’s
config/ directory,
so the source remains available after the temporary bootstrap controller is
removed.
Forking the repository
This documentation describes shmileee/dotfiles and deliberately uses its
real repository URLs, checkout path, documentation domain, Docker image, and
personal settings. It is not a generic dotfiles template.
If you fork this repository to provision your own dotfiles, update the code and documentation in multiple places. At minimum, replace or review:
repository_slug,repository_ref, andbootstrap_urlat the top ofbootstrap/setup.sh;- the public redirect itself, so it points to your slug and branch;
- the Docker Hub identity in
.github/workflows/docker.yamlif it differs from the GitHub repository owner and slug used by its defaults; - the
UBUNTU_ARM_RUNNERGitHub Actions repository variable used by the Docker workflow; - the repository file links throughout
docs/content/, now rendered by the oponomarov.com engine repository; - Git author and namespace values under
config/private_dot_config/private_git/; and - every package, application, secret template, shell preference, and macOS
default under
bootstrap/ansible/config.yamlandconfig/.
Ansible roles
| Role | Responsibility |
|---|---|
bootstrap_prerequisites | Validate the platform, install Ubuntu prerequisites, and create or verify the persistent checkout |
homebrew | Install Homebrew on macOS and Ubuntu |
common | Install shared command-line tools and platform-specific packages and applications |
fonts | Install developer fonts on macOS and Ubuntu |
dotfiles | Install chezmoi and apply the current checkout |
fish | Install fish, make it the login shell, and synchronize Fisher plugins |
mise | Install the tools declared in the mise configuration |
neovim | Install LazyVim and its plugins in headless mode |
docker | Install Rancher Desktop on macOS |
tmux | Install tmux, TPM, and declared plugins |
system_defaults | Apply macOS preferences, Dock items, and keyboard settings |
handoff | Prepare the persistent runtime used by mise run reconcile |
On macOS, the Homebrew role downloads the current signed Homebrew.pkg and
installs it through Ansible’s become mechanism. On Ubuntu, Ansible first creates
the user-owned Linuxbrew prefix, then runs Homebrew’s current shell installer
without sudo. The macOS package installation and Ubuntu prefix preparation may
prompt for the user’s sudo password through Ansible.
Routine work with mise
The initial setup installs mise and the tools pinned by this repository.
After that bootstrap, run routine workflows from the repository checkout with
mise run. List the available tasks and their descriptions at any time:
mise tasks| Command | Purpose |
|---|---|
mise run reconcile | Install the required Ansible collections and reconcile the machine. |
mise run reconcile:check | Preview the reconciliation using Ansible check mode. |
mise run ansible:validate-runtime | Report and validate the checkout, uv, Python, Ansible, locked dependencies, and collections. |
mise run ansible:syntax-check | Check that the provisioning playbook parses against the locked runtime. |
mise run lint | Run all prek hooks against the repository. |
mise run lint:commit | Run the hooks against files changed by the last commit. |
mise run lint:dir bootstrap | Run the hooks against a single directory. |
mise run test | Run every test suite that needs no container or provisioned machine. |
mise run prek:install | Install or refresh the prek Git shims for this checkout. |
mise run prek:hooks | List the configured hooks and their ids. |
mise run prek:why <file> | Show the type tags prek infers for a file, to debug a filter. |
mise run prek:update | Update pinned revisions of remote hook repositories. |
mise run prek:gc | Report the prek cache size and drop unused entries. |
mise run status | Show differences between the chezmoi source and files in the home directory. |
mise run import | Import all modified, non-template managed files into config/. |
mise run import ~/.config/nvim | Import one managed file or directory. |
Task execution installs any missing tools declared in mise.toml
automatically. Before each reconciliation, the runtime validator prints the
persistent checkout commit and every version selected by the shared lock and
collection requirements.
Import local dotfile changes
When a managed file was edited directly in the home directory, inspect the differences before copying them back into the repository:
mise run statusmise run import ~/.config/nvimgit diff -- configOmit the path to import every modified managed file. The task uses
chezmoi re-add, which does not overwrite template source files.
For a rendered file backed by config/**/*.tmpl, reconcile the local change
with its template explicitly. Always review the resulting Git diff before
committing.
Reapply after an update
After the initial setup has installed mise, pull the latest changes, review
them, and rerun the Ansible stage:
git pull --ff-onlygit diff HEAD@{1} -- bootstrap/ansible configmise run reconcileThe roles are designed to be rerun, and a repeated run should leave converged state unchanged. Setup may still refresh Homebrew metadata and update managed formulae or versioned casks, so review changes before rerunning after a long gap.
There is no repository-wide upgrade command. Use brew upgrade for an
intentional full Homebrew upgrade, let Renovate propose changes to versions
declared in the repository, and use Lazy, Fisher, or TPM for intentional plugin
updates. Review and commit any resulting lockfile, manifest, or Ansible pin
changes. The checked-out chezmoi source remains authoritative and is
force-applied during setup.
Try the Linux path in Docker
Run the published image:
docker run --rm -it shmileee/dotfilesTest the current checkout in Docker:
mise run test:dockermise run test:docker builds the integration fixture from the current checkout
and verifies that setup completes on Ubuntu.
Build an image from the current checkout:
docker build -t dotfiles --progress plain .The image starts fish as the non-root linuxbrew user.
Validate changes locally
Run the repository’s static checks and Ansible syntax validation:
mise installmise run ansible:validate-runtimemise run ansible:syntax-checkmise run testmise run test:dockermise run prek:installmise run lintIf the prerequisites are installed, preview the playbook too:
mise run reconcile:checkWhy Ansible and chezmoi?
Ansible owns machine state: packages, applications, services, shell setup, and operating-system preferences. Its roles make the order and platform conditions explicit, and repeated runs provide a practical convergence check.
chezmoi owns files in the home directory. It renders templates using facts such as the operating system and architecture, which keeps one source tree useful across macOS and the Ubuntu integration environment. Keeping these responsibilities separate makes it clear whether a change belongs to the machine or to the user’s configuration.