~/oleksandr-ponomarov

Portfolio · Notes · Dotfiles

Search everything

Search case studies, engineering notes, and Dotfiles documentation.

    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:

    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:

    Terminal window
    sudo softwareupdate -i -a
    xcode-select --install

    Ubuntu

    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.

    Clone the repository so you can inspect exactly what the setup will run. Pay particular attention to config.yaml and the main.yaml playbook:

    Terminal window
    repository_url=https://github.com/shmileee/dotfiles.git
    checkout="$HOME/ghq/personalgit/shmileee/dotfiles"
    mkdir -p "$(dirname "$checkout")"
    git clone "$repository_url" "$checkout"
    cd "$checkout"
    less bootstrap/ansible/config.yaml
    less bootstrap/ansible/main.yaml

    When you are comfortable with the configuration, run the complete setup:

    Terminal window
    ./bootstrap/setup.sh

    The 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

    Terminal window
    curl -fsSL https://dotfiles.oponomarov.com/d | sh

    https://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:

    Terminal window
    curl -fsSL https://raw.githubusercontent.com/shmileee/dotfiles/master/bootstrap/setup.sh > setup.sh
    less setup.sh
    chmod +x setup.sh
    ./setup.sh

    What setup does

    1. 01
      Validate the platform

      Reject root, an invalid home, occupied fresh-install targets, and unsupported platforms before privileged work.

    2. 02
      Build the controller

      Stage the configured repository branch, validate pinned uv, and synchronize the locked Python and Ansible project.

    3. 03
      Provision with Ansible

      Install Ubuntu prerequisites and Git, create the persistent checkout, then install Homebrew and the configured workstation state.

    4. 04
      Hand off to mise

      Prepare the persistent checkout for future updates with mise run reconcile.

    Recover or reconcile

    CommandPurpose
    ./bootstrap/setup.shBootstrap a workstation or recover a valid incomplete persistent clone.
    mise run reconcileRun normal ongoing reconciliation from a completed checkout.
    mise run reconcile:checkPreview supported changes after bootstrap.
    mise run voice:setupDownload 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:

    AreaSource of truthTypical changes
    Packages and applicationsbootstrap/ansible/config.yamlHomebrew packages, casks, Dock items, keyboard shortcuts
    System behaviorbootstrap/ansible/roles/Installation logic and macOS defaults
    Home-directory filesconfig/fish, Git, tmux, Neovim, Alacritty, OpenCode
    Tool versionsconfig/private_dot_config/mise/config.tomlLanguage 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:

    Ansible roles

    RoleResponsibility
    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:

    Terminal window
    mise tasks
    CommandPurpose
    mise run reconcileInstall the required Ansible collections and reconcile the machine.
    mise run reconcile:checkPreview the reconciliation using Ansible check mode.
    mise run ansible:validate-runtimeReport and validate the checkout, uv, Python, Ansible, locked dependencies, and collections.
    mise run ansible:syntax-checkCheck that the provisioning playbook parses against the locked runtime.
    mise run lintRun all prek hooks against the repository.
    mise run lint:commitRun the hooks against files changed by the last commit.
    mise run lint:dir bootstrapRun the hooks against a single directory.
    mise run testRun every test suite that needs no container or provisioned machine.
    mise run prek:installInstall or refresh the prek Git shims for this checkout.
    mise run prek:hooksList 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:updateUpdate pinned revisions of remote hook repositories.
    mise run prek:gcReport the prek cache size and drop unused entries.
    mise run statusShow differences between the chezmoi source and files in the home directory.
    mise run importImport all modified, non-template managed files into config/.
    mise run import ~/.config/nvimImport 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:

    Terminal window
    mise run status
    mise run import ~/.config/nvim
    git diff -- config

    Omit 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:

    Terminal window
    git pull --ff-only
    git diff HEAD@{1} -- bootstrap/ansible config
    mise run reconcile

    The 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:

    Terminal window
    docker run --rm -it shmileee/dotfiles

    Test the current checkout in Docker:

    Terminal window
    mise run test:docker

    mise 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:

    Terminal window
    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:

    Terminal window
    mise install
    mise run ansible:validate-runtime
    mise run ansible:syntax-check
    mise run test
    mise run test:docker
    mise run prek:install
    mise run lint

    If the prerequisites are installed, preview the playbook too:

    Terminal window
    mise run reconcile:check

    Why 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.