Tutorial 3 โ Cross-platform installer¶
Time: 10 minutes. You'll end up with: one commands.perch that bootstraps a fresh dev machine โ installs deps, sets env vars, fetches config โ and works on macOS, Linux, and Windows from the same source.
The problem¶
Onboarding a new engineer involves:
- Installing 6โ10 system packages.
- Cloning some private configs.
- Setting env vars in their shell profile.
Today this is usually:
- A README with instructions that drift.
- A bash script that breaks on Windows.
- A PowerShell script that nobody on the macOS team reads.
We'll fold all three into one commands.perch.
Step 1 โ Sketch the install command¶
name "dev-setup"
about "Bootstrap a fresh dev machine"
version "0.1.0"
command setup
description "Install dependencies, fetch config, set env"
do
install_packages
fetch_config
setup_env
print "โ Setup complete. Restart your shell."
end
end
Bare command invocation keeps things composable. We'll fill in the sub-commands.
Step 2 โ Cross-platform package install¶
command install_packages
description "Install system packages for the current OS"
do
if os == "darwin"
print "macOS โ using Homebrew"
brew install jq ripgrep watchexec gh
end
if os == "linux"
print "Linux โ using apt"
sudo apt-get update
sudo apt-get install -y jq ripgrep gh
print "(watchexec is not in apt; install via cargo)"
end
if os == "windows"
print "Windows โ using Chocolatey"
choco install jq ripgrep watchexec gh -y
end
end
end
Three if os == "..." blocks. Each runs only on the matching OS. The other two are skipped, not even attempted.
Step 3 โ Fetch shared config¶
Suppose the team keeps shared shell config in a private GitHub repo. Cloning it from each user's machine:
command fetch_config
description "Clone team-config into ~/.team-config"
do
if exists "${HOME}/.team-config"
print "team-config already cloned; pulling latest"
cd "${HOME}/.team-config"
git pull
end
if os == "windows"
if exists "${USERPROFILE}/.team-config"
cd "${USERPROFILE}/.team-config"
git pull
end
end
# Clone if missing
if exists "${HOME}/.team-config"
print "(already present)"
end
end
end
That if exists "X" โฆ end / inverse pair is the perch idiom for "do X if Y else do Z." (A formal else is on the roadmap โ for now, two complementary if blocks do the job. Use e = exists "PATH" followed by if not e for the inverse branch.)
Step 4 โ Per-OS env files¶
Different shells, different files. Use write_file so the content is identical across platforms:
command setup_env
description "Append PERCH_* env vars to the shell rc file"
do
if os == "darwin"
write_file "${HOME}/.zshrc.perch" "export PERCH_HOME=$HOME/.team-config\nexport EDITOR=code\n"
print "Add to your .zshrc: source ~/.zshrc.perch"
end
if os == "linux"
write_file "${HOME}/.bashrc.perch" "export PERCH_HOME=$HOME/.team-config\nexport EDITOR=code\n"
print "Add to your .bashrc: source ~/.bashrc.perch"
end
if os == "windows"
setx PERCH_HOME "%USERPROFILE%\.team-config"
setx EDITOR code
end
end
end
write_file and setx differ per OS but are surfaced as straight ops, no shell quoting drama.
Step 5 โ Test it on this machine¶
Watch only the blocks for your OS run. The others silently skip โ they're not errors.
Step 6 โ Ship it as a binary¶
The crown jewel: bundle this so a new hire runs one binary with zero perch install:
Distribute via your preferred channel (S3, GitHub Releases, an internal package server). New hire runs:
curl -fsSL https://internal/team-bootstrap -o ./bootstrap && chmod +x ./bootstrap && ./bootstrap setup
That's the onboarding doc.
Step 7 โ Diagnose¶
If something fails partway through, the user can re-run individual sub-commands:
./team-bootstrap install_packages # just the package install
./team-bootstrap fetch_config # just the config clone
./team-bootstrap setup_env # just the env-var step
Because each sub-command is independent, partial recovery is straightforward.
Step 8 โ Future-proof for new platforms¶
When you add support for Fedora (apt โ dnf), you don't touch the setup command. You add an extra if os == "linux" distinction inside install_packages:
if os == "linux"
if exists "/etc/fedora-release"
sudo dnf install -y jq ripgrep
end
if exists "/etc/debian_version"
sudo apt-get install -y jq ripgrep
end
end
The shape stays clean as the project grows.
What you learned¶
if os == "..."+if arch == "..."make cross-platform conditionals first-class.- Bare command names compose commands without recursion or duplication.
write_filecleanly handles per-OS config files.- The whole installer ships as one binary via
perch --build.
Declare what it needs. Because this installer shells out to per-OS tools, add a
requiresblock so the file states its external surface andperch --checkcan verify it before anything runs. The OS-specific installers areoptional(only one exists per host):requires bin "git" bin "brew" optional # macOS bin "sudo" optional # linux bin "choco" optional # windows write "${home_dir}/.config" endWith the block present, every external op verifies it immediately before running. See requires.md and capability-gating.md.
Where to go next¶
- Browse the op catalog for the full vocabulary.
- The demos folder has variations of these patterns.
- File new op requests as issues โ most ops are a single Go function and a
lib.capyentry.