# lewislulu/terminal-setup: one script that rewrites your shell environment

> A single setup.sh installs Ghostty, Fish or Zsh, Starship, a Nerd Font and a wide set of CLI tools on macOS, Debian, Ubuntu and WSL. It is fast and opinionated, and it will overwrite your shell configuration files.

**lewislulu/terminal-setup** — One-script macOS terminal setup: Ghostty + Fish + Starship + Nerd Font + modern CLI tools

- Repository: https://github.com/lewislulu/terminal-setup
- Stars: 734 · Forks: 119
- Language: Shell
- License: not declared
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/lewislulu-terminal-setup

## One script installs the whole stack, and macOS is the only tested path

The project describes itself as a one-script terminal environment setup for macOS, Debian, Ubuntu and Windows through WSL. The README is candid about the weighting: macOS is marked primary and battle-tested, while Debian, Ubuntu and WSL are labelled experimental, working but not extensively tested, and native Windows is marked not supported.

The macOS path is the one worth reading closely. The documented sequence is: install the package manager, install Ghostty, download the MesloLGS NF nerd fonts, install the chosen shell with its plugins, install the CLI tools, install Starship with a Catppuccin Mocha config, then optionally add fnm with Node.js LTS and the Zellij multiplexer. Config files are deployed at the end of that chain, and existing configs are said to be backed up with timestamps.

Package sources change with the platform. On macOS everything comes through Homebrew. On Linux the script uses apt where a package exists and falls back to GitHub releases for delta, lazygit and eza, which means the install path is different even when the end state looks similar. The whole thing runs from one entry point, setup.sh, which you clone and execute.

## Ghostty is the piece Linux users still install by hand

Ghostty is where the macOS and Linux paths stop matching. The platform notes state plainly that Ghostty is not in apt, so on Debian or Ubuntu you install it manually via snap or by building from source, or you use a different terminal emulator. On macOS Ghostty installs as a native app with no manual step. On WSL the emulator runs on the Windows side while the script configures the Linux layer, and the README points to Windows Terminal or Ghostty for Windows there.

The rest of the Linux path involves name collisions the script has to paper over. bat becomes batcat and fd becomes fdfind in Debian and Ubuntu packaging, so symlinks are created automatically to keep the familiar command names working. Fonts install into ~/.local/share/fonts/ rather than the macOS location, and zsh plugins come either from apt or from a git clone. So the Linux run is not the macOS run with a different package manager. It is the same configuration target assembled from different sources, with the terminal window left to you.

## Choosing Fish or Zsh decides which config file gets replaced

The shell is a choice you make at install time, and it is the choice with the longest lasting effect. The README compares the two on POSIX compatibility, on where autosuggestions and syntax highlighting come from, and on where the configuration lives. Fish has its own syntax and is not POSIX compatible, while Zsh stays POSIX compatible. Autosuggestions and syntax highlighting are built into Fish and arrive in Zsh through plugins. The config path differs too: ~/.config/fish/config.fish on the Fish side, ~/.zshrc on the Zsh side, and both receive the same helper functions.

The prompt itself is shared. Starship is used in place of Powerlevel10k, and the README gives the reasons: one prompt config works in both shells, a TOML file is easier to read than a wizard-generated setup, and the tool is a single Rust binary with no shell framework dependency. fnm is likewise shared as the Node manager in both shells, so the version workflow does not change with your shell decision.

## The alias table quietly rebinds ls, cat, find, grep and top

Once the configs are deployed, several standard commands stop meaning what they used to. ls expands to eza with icons and directories grouped first, ll adds a long listing, lt produces a tree view two levels deep, cat becomes bat, find becomes fd, grep becomes rg, top becomes btop, and lg opens lazygit. fzf then claims three shortcuts: Ctrl+R searches command history, Ctrl+T fuzzy finds files with fd as the backend, and Alt+C fuzzy cds into a directory.

This is where the real consequences sit. The replacements are quicker and better looking, and the output formats differ enough from the originals to surprise anyone sharing a shell session with you or running a script that parses the output of the standard command. If you move between a machine running the setup and one that does not, the same typed command can behave differently in two places. Nothing in the documentation warns about that, so it is worth deciding in advance which of the eight substitutions you actually want.

## A dry run first, because the timestamped backup is the only net

Deploying configs is destructive by nature, and the project offers one safeguard: existing configuration files are backed up with timestamps before the new ones are written. That matters more than it sounds, since the aliases, the Starship configuration and the SSH key helper are merged into the same files a developer may have spent months tuning by hand.

The script also has a preview mode, and the README lists it among the options for previewing what would be done with no changes:

```bash
./setup.sh --fish       # Fish shell
./setup.sh --zsh        # Zsh + fish-like plugins
./setup.sh --dry-run    # Preview what would be done (no changes)
```

Running the preview on a machine with an existing .zshrc is the sensible first move before allowing the script to write anything. The SSH key helper deserves its own caveat. The set-ssh-key function clears the agent and loads a named key from the home directory, and the README points at ~/.ssh/config with Host aliases and IdentitiesOnly yes as the better practice, describing the function as a fallback for edge cases.

## WSL is detected automatically, and native Windows is turned away

The Windows story has two halves. Inside WSL, everything runs in the Ubuntu or Debian layer, so the setup.sh commands are the same Linux commands. The terminal emulator itself stays on the Windows side. The script detects WSL on its own and adapts to it.

Run the same script under MINGW or Git Bash and it does not attempt a native Windows install. It prompts you to install WSL instead, and the platform table marks native Windows as not supported with the instruction to use WSL. That boundary is explicit, which makes it easier to plan around than a half-working path. If WSL is not installed yet, the README puts the prerequisite first:

```powershell
# In PowerShell (Admin)
wsl --install
```

After that, the Linux path applies inside WSL:

```bash
git clone https://github.com/lewislulu/terminal-setup.git
cd terminal-setup && ./setup.sh
```

A single command variant is offered for people who do not want to clone first. It pipes the remote script straight into bash:

```bash
bash <(curl -fsSL https://raw.githubusercontent.com/lewislulu/terminal-setup/main/setup.sh)
```

That form is convenient and it removes the chance to read setup.sh before it runs on your machine, which is the opposite of what the dry run flag is for.

## Optional layers: fnm with Node LTS, and Zellij for panes

Two pieces of the stack sit outside the core install. fnm is the Node.js version manager, and the Node.js LTS runtime it manages is optional. The README lists the commands directly:

```bash
fnm install 22            # Install Node 22
fnm install --lts         # Install latest LTS
fnm default 22            # Set default version
fnm use 22                # Switch in current shell
echo "22" > .node-version # Auto-switch when entering this directory
```

The last line is the interesting one for teams. Writing a version into a .node-version file lets every checkout land on the same runtime without anyone setting a global default, which removes a whole class of version mismatch bugs.

Zellij is the other optional layer, a terminal multiplexer added for people who want panes from the first launch. The multiplexer is not enabled by default, and neither is Node, so a minimal install stays minimal.

## No releases, an unlisted licence, and a last push in April 2026

The repository publishes no GitHub releases, so there is no tagged version to install and no upgrade path through a package manager. Updating means pulling the main branch, which means the script you run is whatever is on that branch at that moment.

The licence field is unknown, and for a script that writes into your shell configuration and downloads binaries from the network, that is worth pausing over. Read setup.sh before running it, and prefer a local clone over the remote one-liner if you want to see the code first.

On upkeep, the repository is not archived and the last push was on 2026-04-26. Activity is modest rather than constant, so treat the platform labels as the real signal: macOS is the path the author develops against, and the Linux and WSL support exists and works but carries no long-term testing record.

## Conclusion

Adopt terminal-setup if you want a themed terminal quickly on macOS and accept that setup.sh will rewrite your shell config files, with a timestamped backup as the only safety net. Do not adopt it if you keep a hand-tuned .zshrc that you cannot compare against the backup, if you need native Windows support, or if you rely on standard output formats from ls, grep or cat. Before running it, run ./setup.sh --dry-run on your own machine, then check the backup file it leaves behind and confirm that the ls, find and grep replacements behave the way your scripts expect.

## FAQ

### How do I install terminal-setup on macOS?

Clone the repository and run the entry script from the cloned directory with git clone followed by cd terminal-setup && ./setup.sh. You can select the shell with ./setup.sh --fish or ./setup.sh --zsh, and you can preview the run with ./setup.sh --dry-run.

### Does terminal-setup support Linux and Windows?

Debian, Ubuntu and Windows through WSL are supported but marked experimental, and the script detects WSL automatically and adapts. Native Windows is not supported, and running it under MINGW or Git Bash makes the script prompt you to install WSL.

### Does terminal-setup install Ghostty on Linux?

No. Ghostty is not in apt, so on Debian and Ubuntu you install it manually via snap or by building from source, or you use another terminal emulator. On macOS Ghostty installs as a native app.

### Which shell does terminal-setup install and what files does it change?

You choose Fish or Zsh at install time. It writes ~/.config/fish/config.fish or ~/.zshrc, and existing configuration files are backed up with timestamps before the new ones are deployed.

### Which standard commands does terminal-setup replace?

The alias table maps ls to eza with icons, cat to bat, find to fd, grep to rg, top to btop and lg to lazygit, with ll and lt giving long and tree listings of eza.

## Sources

- [Issues](https://github.com/lewislulu/terminal-setup/issues)
- [lewislulu/terminal-setup on GitHub](https://github.com/lewislulu/terminal-setup)
- [README](https://github.com/lewislulu/terminal-setup/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lewislulu-terminal-setup
