↓ Skip to main content
  1. Documentation/

Commands

Every Homie command except homie init and homie selfupdate expects to be run from the root of a user environment repo, or with HOMIE_REPO set to its path.


homie init
#

Interactively scaffolds a brand new user environment repo. Run this once on the first machine. Subsequent machines clone the result via bootstrap.sh.

homie init ~/dotfiles

You can also pass flags non-interactively (useful in CI):

homie init \
  --name "Scout Homes" \
  --email scout@homie.sh \
  --github-user scouthomes \
  --github-repo dotfiles \
  --profile personal \
  --shell zsh \
  ~/dotfiles

homie init --update — refresh generated files
#

Init never overwrites; --update is the supported way back into an existing repo. It re-renders the files Homie keeps current across releases — today just bootstrap.sh, which tracks how the current homie expects to be launched — and leaves your seeds (homie.toml, home/, scripts/) untouched.

cd ~/dotfiles
homie init --update

It derives its answers rather than asking: identity from homie.toml, GitHub user and repo from the origin remote (--github-user / --github-repo override, for a repo with no remote), and the clone destination from where the repo actually sits. Keep your repo at ~/Documents/dotfiles instead of ~/dotfiles and bootstrap.sh picks that up — $HOME-relative, so it stays portable across machines:

REPO_DIR="${HOMIE_REPO:-$HOME/Documents/dotfiles}"

Move the repo, re-run --update, commit. Run it from anywhere inside the repo, or pass the path.

Only a location under $HOME is derived, since that’s all another machine is guaranteed to share — a repo outside $HOME (a build directory, a CI checkout) keeps the $HOME/<repo> default. Use --repo-dir to set it explicitly:

homie init --update --repo-dir /opt/dotfiles

Generated files carry a homie:generated stamp with the writing version and a digest, so update can tell an untouched file from an edited one:

StateMeaning
currentalready what this homie writes — no-op
updateduntouched since an older homie wrote it; refreshed
createdmissing; written fresh
skippededited locally, or unstamped — diff shown, nothing written

--force overwrites a skipped file. Deleting the homie:generated line opts a file out for good. Nothing is committed for you — review with git diff.


homie apply
#

Full reconciliation pass — detect → load config → run pre-scripts → install native packages → install declared backends (alphabetical order: brew, flatpak, snap, …) → clone/update [externals] git repos → materialize home/ (symlinks + rendered templates) → run scripts → summary. Backend phases skip with a warning when the tool isn’t on PATH or the backend name isn’t recognized. Externals run before the home phase so templates and symlinks can point into a checkout that’s guaranteed to exist; entries with a ref are held at it, unpinned entries fast-forward to the remote default branch (see Config).

homie apply

apply is idempotent. Running it twice in a row should produce a clean summary with nothing changed.

Flags:

  • --skip-packages — skip the native and backend package phases.
  • --skip-externals — skip the externals (git clone/update) phase.
  • --skip-scripts — skip pre-scripts and post-scripts.
  • --no-tty — force plain output (no Bubble Tea spinner). Auto-detected when stdout isn’t a terminal; this flag overrides the detection.
  • --verbose — raise log level to Debug.

homie home
#

Just the home phase of apply. Walks home/ and any active sibling home.tag-<X>[.tag-<Y>...]/ tree, then for each file:

  • Plain files are symlinked into $HOME at the matching path. If a real file already lives at the destination, it’s backed up to <path>.homie-backup-<timestamp> before linking — Homie never silently overwrites your data.
  • Files ending in .tmpl are rendered through Go text/template + Sprig with the active data set and written into $HOME with the suffix stripped. Source mode is preserved (so home/bin/foo.sh.tmpl renders executable).

When two trees claim the same target, the more-specific tree (more required tags in its directory name) wins; same-specificity collisions error out so you can disambiguate. See Dotfiles for the full model.

Flags:

  • --dry-run — write nothing. Prints the plan (every link and render target with its winning source), then the full rendered content of every active template. Use it to preview how templates resolve on this host before applying; exits non-zero if any template fails to render.

homie render
#

Renders a single .tmpl file to stdout — no writes, no UI chrome, safe to pipe. The data is exactly what a real homie home would use on this host: active tags, [vars], user identity, distro, and the hasTag helper, so the preview is faithful.

homie render home/.gitconfig.tmpl

The path is tried as given first, then relative to the repo root, so the command works from anywhere. A parse or execution error exits non-zero — handy as a template check in CI, and it gives automated agents a feedback loop for template authoring without touching your real $HOME. To preview every active template at once, use homie home --dry-run.


homie context
#

Prints the exact data passed to every template render on this host, as JSON. The keys match the template fields one-to-one — a key named "Email" means a template may reference {{ .Email }} — so one read tells you (or an agent) every field a template can use, with the values it would resolve to right now.

$ homie context
{
  "Name": "Scout Homes",
  "Email": "scout@homie.sh",
  "Profile": "personal",
  "DefaultShell": "zsh",
  "Distro": "fedora",
  "Arch": "amd64",
  "IsContainer": false,
  "IsRoot": false,
  "Tags": ["amd64", "coach", "fedora", "personal"],
  "Vars": { "EDITOR": "nvim" }
}

Output is always JSON, nothing else on stdout — safe to pipe into jq. The hasTag helper and the Sprig function library are callable from templates but aren’t data fields, so they don’t appear here. Pairs naturally with the preview commands: introspect the context, then check a template with homie render or homie home --dry-run. See Dotfiles for the full data reference.


homie install
#

Just the package phases. Resolves [packages].all + [packages].<distro> plus matching [packages."tag:X"] against the detected package manager and installs missing entries, then runs the non-native backend phases ([packages.brew], [packages.flatpak], [packages.snap]) in the same order. On unsupported distros, prints a friendly “not yet supported” notice and skips the native phase. When a backend’s tool isn’t on PATH, prints a warning and skips that backend.


homie run
#

Just the script phase. Runs scripts/*.sh in lexical order, each as a separate bash subprocess with HOMIE_TAGS, HOMIE_REPO, HOMIE_HOME, and every [vars] entry exported to its environment.

Scripts are user code — Homie doesn’t enforce idempotency. Convention is that each script is individually idempotent (e.g. command -v X && exit 0 at the top).

Run from a terminal, scripts inherit your stdin/stdout/stderr, so an in-band prompt (a sudo password, gh auth login, a package-manager confirmation) reaches you directly. When stdin isn’t a terminal (CI, piped, redirected), output is captured per-script and stdin is /dev/null, so a script that would block on a prompt fails fast instead of hanging.

Sibling directories named scripts.tag-<X>[.tag-<Y>...]/ run only when all of their tags are active (AND), mirroring the home.tag-X/ trees. Plain scripts/ always runs. Scripts are ordered by filename across every active tree — the numeric prefix is the single global order, so a scripts.tag-fedora/05-repos.sh runs at position 05 next to a scripts/04-base.sh. The tag trees decide which scripts participate, not a separate ordered stream. The same filename appearing in two active trees is a hard error — unlike the home/ tree, scripts have no “more-specific source wins” override rule, because an imperative script has no single source of truth to override. See Dotfiles for the parallel file-tree model.

Flags:

  • --phase=post (default) — every script whose name does NOT begin with pre-. The “scripts” step of homie apply.
  • --phase=pre — only pre-*.sh scripts. The “pre-scripts” step of homie apply; useful for setting up third-party package sources before homie install.
  • --phase=all — pre-scripts then post-scripts, matching the order homie apply uses.

homie status
#

Read-only view of what apply would do. No changes, no installs, no file writes. Useful for previewing, for CI checks, or for confidence before a real run.

Flags:

  • --json — emit the same information as one JSON document: detected environment, repo path, identity, profile, active tags, the native and per-backend package lists, config warnings, and the error/warning counts from a doctor pass. When no environment repo is found, "repo" is null and the command still exits zero; a HOMIE_REPO that points at a directory without a homie.toml is an error instead.

homie doctor
#

Walks the repo and reports:

  • Broken symlinks (target doesn’t exist).
  • Symlinks in $HOME that point outside the repo.
  • Missing packages.
  • Unrendered templates.
  • Scripts that aren’t executable, and filename collisions between active script trees.
  • Tag-gated home.tag-X/ and scripts.tag-X/ trees that aren’t active on this host, and which multi-tag [packages."tag:X.tag:Y"] blocks are active (informational).
  • Unknown distro detection.

Exit code is 0 if everything is clean, 1 otherwise — useful as a post-deploy health check in CI.

Flags:

  • --json — emit the findings as structured records instead of the styled report:

    {
      "findings": [
        { "severity": "warn", "area": "packages", "message": "1 not installed: ripgrep" }
      ],
      "errors": 0,
      "warnings": 1
    }

    Severity is error, warn, or info; area is the check group (env, config, home, link, render, packages, scripts). The exit-code rule is unchanged — parse the document, then check the exit code. A non-zero exit caused by error findings still emits one valid JSON document on stdout; a failure before the checks run (no repo, unreadable config) prints to stderr with no JSON.


homie bootstrap
#

Installs the minimum tools needed for apply to proceed: git and ca-certificates. Called by bootstrap.sh on a fresh machine before the user repo is cloned. Most users never invoke this directly.


homie selfupdate
#

Updates the homie binary itself to the latest release. Resolves the newest tag, downloads the binary for your OS and architecture, verifies it against the release’s SHA256SUMS, and atomically replaces the running binary — the same checks the install script performs. homie self-update is an alias.

homie selfupdate          # update in place
homie selfupdate --check  # just report whether a newer release exists

Updating writes to the directory homie lives in: a binary in /usr/local/bin usually needs sudo homie selfupdate, while the default user install in ~/.local/bin needs no root. A build from source or a Homebrew-managed binary refuses to self-update — rebuild it, or let the tool that installed it do the upgrade.

Flags:

  • --check — report whether a newer release exists without installing it.

Help topics
#

Beyond per-command --help, the binary ships one reference topic:

  • homie help templating — the template data fields, the hasTag helper, Sprig availability, and the missing-key rules. The offline version of the Dotfiles reference; homie help template and homie help templates resolve to the same page.

Global flags
#

These work on every subcommand:

  • --repo <path> — point at a user repo other than the cwd (or use HOMIE_REPO=<path>).
  • --no-tty — force plain output.
  • --verbose — Debug-level logging.
  • --version — print version and exit.