Declarative machine setup — replicate a workstation after a wipe, keep dotfiles and symlinks in sync, or hand a colleague a config that installs deps and clones the right repos.
Real-world example: .dotfiles machine_setup.yaml.
- TUI Dashboard: Real-time progress with task list, per-task logs, elapsed times, and a parallel log stream when multiple tasks run. Powered by ratatui. Use
--no-tui(or a non-TTY) for CI. - Async Engine: Task execution powered by tokio for concurrent I/O (file ops, git, shell commands with streaming output).
- Task History: Tracks install/update/uninstall timestamps in
~/.machine_setup/history.json. Already-installed tasks are skipped unless--forceis used. - PowerShell Support: Use
powershellas a shell option alongsidebashandzsh. - Remote Configs: Point directly at a URL instead of a local file — great for bootstrapping a clean machine without cloning first.
See CHANGELOG.md for release-by-release notes.
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/timopruesse/machine_setup/main/install/install.sh | shWindows (PowerShell):
irm https://raw.githubusercontent.com/timopruesse/machine_setup/main/install/install.ps1 | iexVia Homebrew (macOS / Linux):
brew install timopruesse/repo/machine_setupVia Cargo:
cargo install machine_setupManual download: Grab a binary from the release page.
| command | description | example |
|---|---|---|
| install | install the defined tasks | machine_setup install |
| update | update the defined tasks | machine_setup update |
| uninstall | uninstall the defined tasks | machine_setup uninstall |
| list | list all of the defined tasks | machine_setup list |
| validate | validate the config without executing | machine_setup validate |
| completions | generate shell completions | machine_setup completions zsh |
By default, machine_setup looks for ./machine_setup. If that path has no extension, it tries .yaml, then .yml, then .json. Supported formats are YAML and JSON.
| flag | value | example |
|---|---|---|
| -c --config |
path or URL to the config file | machine_setup install -c ./config/my_setup.yaml |
| -t --task |
only run the specified task | machine_setup install -t my_task2 |
| -s --select |
select a task to run | machine_setup install -s |
| --with-deps | also run transitive depends_on tasks |
machine_setup update -t leaf --with-deps |
| -f --force |
force execution (bypass history checks) | machine_setup install --force |
| --no-tui | disable TUI; also auto-disabled on non-TTY / CI | machine_setup install --no-tui |
| -h --help |
display help information | machine_setup --help |
| -v --version |
display version information | machine_setup --version |
| -d --debug |
print additional debug information | machine_setup install --debug |
| -l --level |
set a log level (info, warn, error, debug, trace) | machine_setup install --level=info |
You can pass a URL instead of a local path — the config is fetched and executed directly. GitHub blob URLs are automatically converted to raw URLs.
machine_setup install -c https://github.com/timopruesse/.dotfiles/blob/main/machine_setup.yamlThis is especially useful for setting up a fresh machine without cloning your dotfiles first:
# Install machine_setup
curl -fsSL https://raw.githubusercontent.com/timopruesse/machine_setup/main/install/install.sh | sh
# Run your dotfiles setup directly from GitHub
machine_setup install -c https://github.com/timopruesse/.dotfiles/blob/main/machine_setup.yamlWhen running in an interactive terminal, a TUI dashboard is shown with:
- Task list with status indicators (pending, running, completed, failed, skipped)
- Per-task scrollable log output (and a tagged parallel stream when multiple tasks run)
- Progress bar with completion stats and elapsed times
- Keyboard navigation:
j/korUp/Down— navigate tasks/— filter tasks by name (Enterto apply,Escto cancel or clear)PgUp/PgDn— scroll logs;Home/End— jump (Endre-enables follow)qorCtrl+C— cancel and quit;Esc— clear filter, or quit if none
The TUI is automatically disabled in non-interactive environments (piped output, CI). You can also explicitly disable it with --no-tui.
Tasks can be defined under the tasks root key.
Every task can contain an arbitrary number of commands.
| key | description | values | default |
|---|---|---|---|
| tasks | root key for all of the tasks | ||
| default_shell | shell that is used when not specified by the command | bash, zsh, powershell |
bash |
| temp_dir | define where temporary files are stored | ~/.machine_setup |
|
| parallel | run all of the tasks in parallel | true or false |
false |
| num_threads | number of threads when run in parallel | numeric > 1 | physical processor count - 1 |
| key | description | values | examples |
|---|---|---|---|
| os | only run on the specified os | possible values | "linux" or ["linux", "macos"] |
| parallel | run all of the commands in parallel (1 thread per command) | true or false |
false |
| depends_on | run these tasks first (install always expands the chain) | list of task names | ["base"] |
On update / uninstall, -t / -s run only the selected tasks unless you pass --with-deps. Interactive uninstall can offer remaining dependencies; uninstall also warns if other tasks still depend on something in the run set.
Minimal example (see also example_config.yaml for a fuller demo used by make run):
default_shell: "zsh"
parallel: true
tasks:
tools:
os: ["linux", "macos"]
commands:
- run:
install: "echo 'install tools'"
update: "echo 'update tools'"
uninstall: "echo 'remove tools'"
- symlink:
src: "./dotfiles/.zshrc"
target: "~/.zshrc"
force: true
repos:
commands:
- clone:
url: "git@github.com:timopruesse/machine_setup.git"
target: "~/machine_setup"Extensibility is not explicitly built in. However, it's possible to execute tasks from another configuration via the machine_setup command.
This command copies the contents of a directory to another directory.
| argument | value | required | example |
|---|---|---|---|
| src | source directory/file | Y | "./src/files" or "./src/test.txt" |
| target | target directory/file | Y | "/tmp/target" or "/tmp/target/new.txt" |
| ignore | list of files/directories to ignore | - | ["dist", "package-lock.json"] |
| sudo | run file operations with sudo | - | true |
copy:
src: "./src/files"
target: "/tmp/target"
ignore: ["dist", "package-lock.json"]
# Copy to a protected path
copy:
src: "./etc/wsl.conf"
target: "/etc/wsl.conf"
sudo: trueThis command clones a git repository to the specified destination.
| argument | value | required | example |
|---|---|---|---|
| url | URL to a git repository | Y | "git@github.com:timopruesse/machine_setup.git" |
| target | target directory | Y | "~/machine_setup" |
clone:
url: "git@github.com:timopruesse/machine_setup.git"
target: "~/machine_setup"This command symlinks all the files from the source directory to the target directory.
| argument | value | required | example |
|---|---|---|---|
| src | source directory/file | Y | "./src/files" or "./src/test.txt" |
| target | target directory/file | Y | "/tmp/target" or "/tmp/new.txt" |
| ignore | list of files/directories to ignore | - | ["dist", "package-lock.json"] |
| force | true/false | - | |
| sudo | run file operations with sudo | - | true |
If
forceis set totrue, existing files will be removed and replaced by the symlinks.When
srcis a directory, intermediate destinations are always real directories. Leftover directory symlinks undertargetare unwrapped (the link inode is removed and replaced with an empty real directory; the tree the link pointed at is left untouched). This prevents file symlinks from being written through into the source tree.
symlink:
src: "./src/files"
target: "/tmp/target"
ignore: ["dist", "package-lock.json"]
force: true
# Symlink to a protected path
symlink:
src: "./etc/my.conf"
target: "/etc/my.conf"
sudo: true
force: trueThis command executes a shell command.
Hint: Avoid the usage of interactive commands when possible.
| argument | description | required | default | values |
|---|---|---|---|---|
| env | environment variables | - | ||
| shell | shell that is used | - | "bash" | "bash", "zsh", "powershell" |
By default, shell commands will only run during install.
You can provide mode-specific commands using install, update, and uninstall instead of commands:
| argument | description | required | example |
|---|---|---|---|
| commands | commands for install only | - | "sudo apt-get -y install git" |
| install | commands for installing | - | "sudo apt-get -y install git" |
| update | commands for updating | - | "sudo apt-get -y upgrade git" |
| uninstall | commands for uninstalling | - | "sudo apt-get -y uninstall git" |
Use either
commands(runs on install only) orinstall/update/uninstallfor mode-specific behavior. They are all top-level keys underrun.
inline_command:
run:
commands: "sudo apt-get -y install git"
multiline_command:
run:
commands:
- "sudo apt-get update"
- "sudo apt-get -y install git"
updatable_command:
run:
env:
SOME_TOKEN: "abc123"
install: "sudo apt-get -y install git"
update: "sudo apt-get -y upgrade git"
uninstall: "sudo apt-get -y uninstall git"
updatable_multiline_command:
run:
env:
SOME_TOKEN: "abc123"
install:
- "sudo apt update"
- "sudo apt-get -y install git"
update:
- "sudo apt-get -y upgrade git"
uninstall:
- "sudo apt-get -y uninstall git"With this command it's possible to include other machine_setup configuration files.
| argument | description | required | example |
|---|---|---|---|
| config | path to the other config file | Y | "./my_other_config.yaml" |
| task | define a single task that should be run | - | "my_other_task" |
machine_setup:
config: "./my_other_config.yaml"
task: "my_other_task" # optional