One Fish-powered installer that bootstraps a complete development environment on macOS, Arch (Omarchy/Hyprland), and Ubuntu.
These are my personal dotfiles. They take a bare machine and turn it into a fully configured development environment: shell, prompt, terminal, editor, language toolchains, and desktop apps. Everything is driven by a single run.fish entry point that detects your OS and runs the right steps. Take and use anything you want.
- Highlights
- Supported Platforms
- Prerequisites
- Quick Start
- How It Works
- What Gets Installed
- Configuration Tour
- Symlink Map
- Desktop Environment (Omarchy/Hyprland)
- Custom Fish Functions
- Environment Variables
- Advanced Usage
- Customization
- Project Structure
- Troubleshooting
- One installer, three operating systems. A single
fish run.fishdetects macOS, Omarchy (Arch/Hyprland), or Ubuntu and runs the matching scripts. Shared steps live inos/common; platform-specific steps live underos/darwin,os/omarchy, andos/ubuntu. - Phase-based and idempotent. Setup runs in
pre,main, andpostphases. Re-running is safe: symlinks useln -sfv, and installers check before reinstalling. - One toolchain manager. Almost every CLI tool and language runtime is pinned in
mise/mise.tomland installed by mise, so the same versions land on every machine. - Catppuccin Mocha everywhere. Ghostty, Starship, Neovim, Zellij, Yazi, and Opencode all share the same palette.
- A custom Nerd Font.
SethensSuperCode.ttfcarries the icon glyphs used across the terminal, prompt, and editor. - Interactive or hands-off. Run the whole thing automatically, or use
--launcherto pick individual steps from a filterable menu.
| Platform | Base requirement |
|---|---|
| macOS (Darwin) | A working macOS install. Homebrew is installed for you if missing. |
| Omarchy (Hyprland) | A base Arch Linux system with Hyprland installed via Omarchy. |
| Ubuntu | Ubuntu 24.10+ with an internet connection. |
| Requirement | Installation |
|---|---|
| Fish Shell | Arch: sudo pacman -S fish macOS: brew install fish Ubuntu: sudo apt install fish |
| Git | Usually pre-installed; otherwise use your package manager. |
# 1. Clone the repository
git clone <your-repo-url> ~/Developer/dotfiles
cd ~/Developer/dotfiles
# 2. Set your git identity (required for commits)
git config user.name "Your Name"
git config user.email "your.email@example.com"
# 3. Run the setup
fish run.fish # Full automated setup
fish run.fish --launcher # Interactive menu (pick individual steps)Flags can be combined:
| Flag | Effect |
|---|---|
-l, --launcher |
Open the interactive gum menu instead of running everything. |
-u, --update |
Run an update pass (e.g. brew update && brew upgrade on macOS). |
-r, --reboot |
Reboot after setup completes. |
run.fish is the single entry point. It:
- Detects the OS from
unameand setsSYSTEM_OStodarwin,omarchy, orubuntu. - Sets global paths (
DOTFILES_DIRECTORY,HOME_CONFIG_DIRECTORY, and friends). - Loads Fish functions by adding every
os/<platform>andos/commonsubdirectory tofish_function_path, so eachinstall-*/symlink-*/ helper function becomes callable. - Runs the phases for your platform.
The installer is organized into three phases so that prerequisites are always in place before the things that depend on them:
run.fish
├── Pre Phase (os/common/pre, os/<platform>/pre)
│ ├── Switch the login shell to fish
│ ├── Create directories (~/.config, ~/Developer, ~/.config/mise, ...)
│ ├── Symlink every config file/directory into place
│ ├── Install mise (curl) and add it to PATH for the run
│ ├── mise install → installs all tools from mise/mise.toml
│ ├── Install sesh (go)
│ └── Authenticate with GitHub (ssh key check, else `gh auth login`)
├── Main Phase (os/common/main, os/<platform>/main)
│ ├── Install OS packages (brew / pacman / apt / snap / flatpak)
│ ├── Install language servers (via bun)
│ └── Clone repositories (dotfiles, wallpapers, tmux plugin manager)
└── Post Phase (os/common/post)
└── Final configuration
Why this shape? The phase split keeps ordering correct (mise exists before mise install, configs are symlinked before tools read them), and the common vs per-platform split means a tool only needs documenting once while platform quirks stay isolated.
mise/mise.toml is the source of truth for tool versions. mise install reads it and installs everything below.
Languages & runtimes
| Tool | Description |
|---|---|
| bun | JavaScript/TypeScript runtime |
| node | Node.js (LTS) |
| python | Python |
| ruby | Ruby (uses precompiled binaries; compile = false) |
| go | Go toolchain |
| rust | Rust toolchain with cargo |
| java | Java JDK |
| dotnet | .NET SDK |
| zig | Zig compiler |
| clojure | Clojure |
| erlang | Erlang/OTP |
Build & parsing
| Tool | Description |
|---|---|
| cmake | Cross-platform build system |
| tree-sitter | Incremental parser toolkit |
Containers & infrastructure
| Tool | Description |
|---|---|
| docker-cli | Docker CLI |
| docker-compose | Docker Compose |
| kubectl | Kubernetes CLI |
| terraform | Infrastructure as code |
CLI utilities
| Tool | Description |
|---|---|
| fd | Fast file finder |
| fzf | Fuzzy finder |
| ripgrep | Fast line-oriented search |
| gum | Pretty interactive shell scripts |
| zoxide | Smart directory jumper |
| starship | Shell prompt |
| tmux | Terminal multiplexer |
| zellij | Terminal workspace |
| yazi | Terminal file manager |
| neovim | Modern Vim editor |
| gh | GitHub CLI |
| mysql | MySQL client |
Terminal UIs & AI
| Tool | Description |
|---|---|
| lazygit | TUI for Git |
| lazydocker | TUI for Docker |
| lazyssh | SSH manager |
| crush | AI coding agent |
| opencode | AI coding assistant |
Installed during the main phase for editor LSP support: bash-language-server, fish-lsp, typescript + typescript-language-server, vscode-langservers-extracted, and yaml-language-server.
Cross-platform apps appear in more than one table on purpose: each OS installs them through its native package manager.
macOS (Homebrew)
CLI (brew) |
GUI (brew --cask) |
|---|---|
| git, gnupg, nginx | brave-browser, ghostty, spotify, virtualbox |
Omarchy (pacman / yay)
brave, vlc, virtualbox, postgresql, nginx, ffmpeg, gparted, gpick, font-manager, grub, mdadm, openssh, ca-certificates, curl, fortune-mod
Ubuntu (apt / snap / flatpak)
| Source | Packages |
|---|---|
| apt | brave-browser, vlc, virtualbox, postgresql, nginx, gparted, gpick, font-manager, autoconf, bison, build-essential, ca-certificates, gnupg, gnome-tweaks, lsb-release, mdadm, ncurses, fortune-mod |
| snap | discord, spotify |
| flatpak | zen-browser, flatpak |
| custom | ghostty (.deb), White Sur icon theme (git) |
The GitHub CLI (
gh) is installed through mise, not a system package manager, so it is the same version on every platform.
config.fish sets up the interactive shell:
- Exports
DEVELOPER_DIRECTORYandBUN_INSTALL, and puts~/.bun/bin,~/.local/bin, and Homebrew onPATH. - Activates mise when present (
if type -q mise). - For interactive sessions, initializes
zoxideandstarship(each guarded bytype -q). - Greeting comes from
fortune.
Minimal, fast prompt using the Catppuccin Mocha palette. Shows user, directory, language versions (c, dotnet, golang, nodejs, python, ruby, rust), and git branch/status.
ghostty/config is a single file:
theme = Catppuccin Mocha(Ghostty's built-in theme)- 13pt font with
adjust-cell-height = 3 async-backend = epollfont-codepoint-maproutes the icon rangeU+F000-U+F1B2toSethensSuperCode
Config in yazi/:
yazi.toml: manager settings (permission line mode, show hidden, show symlinks)theme.toml: selects thecatppuccin-mochaflavor and defines a large icon table (per-extension glyphs and colors)flavors/catppuccin-mocha.yazi/: the installed flavor package:flavor.toml(UI colors) andtmtheme.xml(syntax highlighting for the preview pane)
zellij/config.kdl: default modelocked,zjstatusstatus bar plugin, Catppuccin Mocha theme.zellij/layouts/default.kdl: custom status bar with per-mode indicators, opening two tabs:nvimandopencode.
tmux/tmux.conf, with plugins managed by TPM (cloned during the main phase).
sesh/sesh.toml configures the sesh tmux session manager.
A full Lua configuration under nvim/lua/sethen/ using lazy.nvim:
- Core (
core/): options, keymaps, LSP setup, autocommands, constants. - Plugins (
plugins/): one file per plugin area.
Highlights: catppuccin theme, lualine, nvim-tree, telescope (+ fzf-native), treesitter, blink-cmp completion, mason, gitsigns, oil, which-key, todo-comments, and AI integrations (copilot, opencode).
On first launch, Mason installs language servers including: bash-language-server, dockerfile-language-server, gopls, html/css, json-lsp, lua-lsp, pyright, ruby-lsp, rust-analyzer, sqlls, tailwindcss-language-server, typescript-language-server, and yaml-language-server.
AI coding assistant config in opencode/:
| Setting | Value |
|---|---|
| Theme | catppuccin-mocha |
| Model | opencode/big-pickle |
| Auto-update | enabled |
opencode/opencode.json and opencode/themes/ are symlinked into ~/.config/opencode/. See opencode.ai.
| Font | Description |
|---|---|
SethensSuperCode.ttf |
Custom Nerd Font-style font with icon glyphs (U+F000-U+F1B2) |
Installed to ~/.local/share/fonts/ (Omarchy/Ubuntu) or ~/Library/Fonts/ (macOS), and used by Ghostty, Starship, and Neovim for symbols.
Config lives in this repo and is symlinked into place, so edits here are live everywhere.
| Source | Destination | Platforms |
|---|---|---|
config.fish |
~/.config/fish/config.fish |
all |
fish/functions/ |
~/.config/fish/functions/ |
all |
nvim/ |
~/.config/nvim/ |
all |
starship/starship.toml |
~/.config/starship.toml |
all |
ghostty/ |
~/.config/ghostty/ |
all |
yazi/ |
~/.config/yazi/ |
all |
zellij/config.kdl |
~/.config/zellij/config.kdl |
all |
zellij/layouts/ |
~/.config/zellij/layouts/ |
all |
tmux/tmux.conf |
~/.config/tmux/tmux.conf |
all |
sesh/ |
~/.config/sesh/ |
all |
opencode/opencode.json |
~/.config/opencode/opencode.json |
all |
opencode/themes/ |
~/.config/opencode/themes/ |
all |
mise/mise.toml |
~/.config/mise/mise.toml |
all |
mise/.default-gems |
~/.default-gems |
all |
.gitconfig |
~/.gitconfig |
all |
.gitignore_global |
~/.gitignore_global |
all |
hypr/monitors.conf |
~/.config/hypr/monitors.conf |
Omarchy |
waybar/ |
~/.config/waybar/ |
Omarchy |
On Omarchy, the base Wayland desktop is provided by Omarchy; these dotfiles layer config on top:
- Hyprland: monitor layout in
hypr/monitors.conf. - Waybar: status bar (
waybar/config.jsonc,waybar/style.css). - Mako, Walker, Ghostty: notifications, launcher, and terminal.
Hyprland keybindings, window rules, animations, and the broader Waybar setup are managed by Omarchy itself. This repo only owns the monitor config and the per-app theming above.
Functions live in fish/functions/ (shared) and under each os/<platform> tree (install/symlink steps).
Messaging (header-message, success-message, error-message, running-message, information-message): consistent status output during setup.
Git helpers (git-branch-name, git-sha, git-modified-files-count, git-staged-files-count, git-untracked-files-count): used by the prompt and scripts.
System (switch-shell-to-fish, reboot-system, confirm-reboot-system, create-directory-if-not-exists, delete-if-exists).
Setup helpers:
install-sesh: installs the sesh session manager viago.authenticate-github: checks for an SSH public key (~/.ssh/id_*.pub); if none exists, runsgh auth login.install-tmux-plugin-manager,set-gnome-preferences(Ubuntu), theclone-*repo functions, and thesymlink-*/make-*functions.
Package-manager wrappers (in os/<platform>/utilities/): brew-install-package, brew-cask-install-package, pacman-install-package, yay-install-package, sudo-apt-install-package, sudo-snap-install-package, flatpak-install-package.
dot-launcher (run via fish run.fish --launcher) uses gum to present a filterable list of every available function, so you can run individual steps instead of the full install.
Set in config.fish:
| Variable | Default | Description |
|---|---|---|
DEVELOPER_DIRECTORY |
$HOME/Developer |
Working directory for projects |
BUN_INSTALL |
$HOME/.bun |
Bun installation directory |
Set in run.fish during setup: SYSTEM_OS, DOTFILES_DIRECTORY, DOTFILES_OS_DISTRO_DIRECTORY, DOTFILES_OS_COMMON_DIRECTORY, HOME_CONFIG_DIRECTORY, HOME_FISH_DIRECTORY, plus RUN_DOTFILES_REBOOT / RUN_DOTFILES_UPDATE when the matching flags are passed.
fish -c "source run.fish; run-darwin-pre" # or run-omarchy-pre / run-ubuntu-pre
fish -c "source run.fish; run-darwin-main" # or run-omarchy-main / run-ubuntu-main
fish -c "source run.fish; run-common-post"fish run.fish --launcher # pick from the menu
fish -c "source run.fish; install-ghostty" # or call directlyfish run.fish --update # update pass
fish run.fish --reboot # reboot when doneEdit mise/mise.toml:
[tools]
your-tool = "latest" # or a specific versionThen run mise install.
Add a file (or entry) under nvim/lua/sethen/plugins/:
return {
"owner/repo",
event = "VeryLazy",
config = function()
-- your config
end,
}- Omarchy: add an
install-*function and call it inos/omarchy/main/run-omarchy-main.fish. - Ubuntu: add it to
os/ubuntu/main/run-ubuntu-main.fish(apt, snap, or flatpak). - macOS: add it to
os/darwin/main/run-darwin-main.fish.
dotfiles/
├── run.fish # Main entry point
├── config.fish # Fish shell configuration
├── .gitconfig # Git configuration
├── .gitignore_global # Global gitignore
├── AGENTS.md # Agent coding guidelines
├── fish/
│ └── functions/ # Shared Fish functions
├── os/
│ ├── common/ # Cross-platform steps
│ │ ├── pre/ main/ post/ # Phase scripts
│ │ └── utilities/ # Shared helpers (dot-launcher)
│ ├── darwin/ # macOS (pre, main, utilities)
│ ├── omarchy/ # Arch/Hyprland (pre, main, utilities)
│ └── ubuntu/ # Ubuntu (pre, main, utilities)
├── mise/
│ ├── mise.toml # Tool versions (source of truth)
│ └── .default-gems # Default Ruby gems
├── nvim/
│ └── lua/sethen/
│ ├── core/ # Options, keymaps, LSP, autocmds
│ ├── plugins/ # Plugin configs
│ └── lazy.lua # lazy.nvim bootstrap
├── opencode/
│ ├── opencode.json # Opencode config
│ └── themes/ # Opencode themes
├── starship/
│ └── starship.toml # Prompt configuration
├── ghostty/
│ └── config # Terminal config
├── yazi/
│ ├── yazi.toml # Manager settings
│ ├── theme.toml # Flavor selection + icon table
│ └── flavors/ # Installed flavor package(s)
├── zellij/
│ ├── config.kdl # Workspace config
│ └── layouts/ # Layout definitions
├── tmux/
│ └── tmux.conf # Terminal multiplexer
├── sesh/
│ └── sesh.toml # tmux session manager
├── hypr/
│ └── monitors.conf # Monitor configuration (Omarchy)
├── waybar/
│ ├── config.jsonc # Status bar config (Omarchy)
│ └── style.css # Status bar styling (Omarchy)
└── assets/
├── fonts/ # SethensSuperCode.ttf
├── icons/ # Custom icons for nvim-web-devicons
├── images/ # Screenshots
└── videos/ # Demos
Symlink already exists. Steps are idempotent and overwrite their own symlinks. To force a clean target, delete it first.
mise not found after install. Open a new shell so config.fish runs, or confirm ~/.local/bin is on PATH. config.fish only activates mise when it is present.
Neovim plugins not loading. Run :Lazy sync.
Language servers not starting. Check Mason with :Mason, and ensure the servers installed on first launch.
fish -n run.fish # syntax-check the installer
nvim --headless -c "lua require('sethen')" -c "qa" # Neovim loads cleanly
mise doctor # mise health
mise ls # installed tools
starship config validate # prompt configMade with care by Sethen