Moving my development setup from EndeavourOS to Ubuntu
I created a development setup from EndeavourOS to Ubuntu 26.04. It’s for work: they need 1Password’s security check scripts running, and the drive has to be encrypted. My machine runs EndeavourOS, so I needed an Ubuntu environment alongside it.
Ubuntu is on a Framework expansion card, and EndeavourOS is still on the internal SSD, so the old drive is mounted and available to copy things from. Nice! All my settings are there. This should be straightforward.
Then WezTerm complained about a missing font, my shell was missing its framework, and Zed kept opening Firefox when I wanted Helium. 😅
This is another one of those posts for future me. The initial transfer copied preferences, but getting the applications to behave like my old setup needed a few more steps. By the time I was configuring shortcuts, I was using KDE Plasma on Wayland.
Start with a migration log
I had an Ubuntu-settings-migration.md file recording what had been
copied. Git configuration, Zsh files, terminal settings, editor
settings and Espanso expansions were already there.
Applications, editor extensions, fonts, browser profiles and SSH keys hadn’t been included in that first pass.
That distinction mattered! Having a settings file on disk doesn’t mean everything it refers to is installed. I kept the original drive available as a reference and backed up destination files before changing them.
Terminal settings need their fonts
Ghostty is available through Ubuntu 26.04’s package repositories:
1sudo apt update
2sudo apt install ghostty zsh git curl gnupgFor WezTerm I used the repository listed in its Linux installation instructions:
1curl -fsSL https://apt.fury.io/wez/gpg.key \
2 | sudo gpg --yes --dearmor -o /usr/share/keyrings/wezterm-fury.gpg
3
4sudo chmod 644 /usr/share/keyrings/wezterm-fury.gpg
5
6echo 'deb [signed-by=/usr/share/keyrings/wezterm-fury.gpg] https://apt.fury.io/wez/ * *' \
7 | sudo tee /etc/apt/sources.list.d/wezterm.list
8
9sudo apt update
10sudo apt install weztermThe install instructions are here:
Both terminal configurations used VictorMono Nerd Font Mono. The
configuration had migrated; the font hadn’t.
The exact font files were on the old drive, so I copied the Mono
variants into ~/.local/share/fonts/VictorMono and refreshed the
cache. Replace the source path below with wherever your old
installation is mounted:
1mkdir -p ~/.local/share/fonts/VictorMono
2cp /path/to/old-root/usr/share/fonts/TTF/VictorMonoNerdFontMono-*.ttf \
3 ~/.local/share/fonts/VictorMono/
4fc-cache -f ~/.local/share/fonts
5fc-match 'VictorMono Nerd Font Mono'After restarting the terminals, they could use the right font. I’d prefer a distro package for updates, but there wasn’t a matching Nerd Font package in my configured Ubuntu repositories.
A shell configuration isn’t the whole shell
My .zshrc expected Oh My Zsh and the Spaceship prompt, the setup
from my updated Zsh config. The
first copy had included custom plugins and themes, but left out the Oh
My Zsh framework and skipped symlinks.
I restored the framework from the old installation, along with the
Spaceship links. That brought back Git integration, zsh-autosuggestions and zsh-syntax-highlighting.
There were also startup lines loading Daytona completions and Vite+ environment settings from files that didn’t exist on Ubuntu. Those loads now check whether the optional files are present.
With startup checks passing, I changed the login shell:
1chsh -s /usr/bin/zshLog out and back in for the login-shell change. To switch the current terminal immediately:
1exec zshRestore the development tools separately
Volta’s old metadata gave me the Node version and global packages to reinstall. My default was Node 24.15.0, with pnpm, TypeScript, the TypeScript and Svelte language servers, npm-check-updates and the Sourcegraph CLI.
I installed Volta using --skip-setup, because the migrated shell
already configured VOLTA_HOME and its bin directory:
1curl -fsSL https://get.volta.sh | bash -s -- --skip-setup
2volta install node@24.15.0Then I reinstalled the recorded package versions and checked the commands from Zsh. That was a better fit here than copying an entire old Node installation and hoping its dependencies still worked.
Volta’s installer options: https://docs.volta.sh/advanced/installers
SSH needed its own migration
The first GitHub connection asked me to confirm the host fingerprint,
then failed with Permission denied (publickey).
Ubuntu didn’t have my SSH key pair yet. Restoring it with the right permissions got authentication working. I then copied the SSH host configuration and the separate keys referenced by those hosts, and merged the known-host entries with a backup.
The check was:
1ssh -T git@github.comGitHub’s successful authentication message includes “does not provide shell access”. That’s expected. Checking that login worked didn’t verify every private repository or server connection, though.
Zed settings and extensions are separate
Zed’s settings, keybindings and snippets already matched the old installation. The extensions were missing.
I added the old extension IDs to auto_install_extensions in the
settings file. Zed then installed all 14, including Night Owl,
Material Icon Theme, Svelte and WakaTime.
Here’s the shape of that setting, with a smaller example list:
1{
2 "auto_install_extensions": {
3 "night-owlz": true,
4 "material-icon-theme": true,
5 "svelte": true
6 }
7}Merge that into your existing settings rather than replacing the whole file. Having the extension list alongside the preferences should make the next move easier.
My shell also expected the Arch command name zeditor, while Zed’s
installer provided zed. A zeditor symlink kept my existing aliases
and editor environment variables working.
Zed’s extension documentation: https://zed.dev/docs/extensions/installing-extensions
The browser default rabbit hole
I use Helium for the accounts I’m signed into. Zed’s sign-in button kept opening Firefox.
Both xdg-settings and the HTTPS MIME association said Helium was the
default. Setting BROWSER in the shell and Zed’s launcher didn’t fix
it either.
Tracing a public URL through xdg-open showed it calling kde-open.
I explicitly set Helium in KDE’s BrowserApplication setting and
reapplied the HTTP and HTTPS associations. That was the last change I
made before moving on.
I didn’t test sign-in end to end afterwards, so I can’t say which
setting was responsible. Setting BROWSER in the shell on its own
wasn’t enough.
Helium’s saved tab groups
I wanted my tab groups back without replacing the new browser profile.
Chromium’s saved groups use its sync storage layer, which Helium also
uses. In this profile the relevant records were inside Default/Sync Data/LevelDB.
With Helium fully closed, I backed up the destination database and merged only the saved tab-group records from a working copy of the old database. The merge verified all 215 group-and-tab records and checked that existing records were unchanged.
That’s 215 records, not 215 groups! A group and the tabs inside it have separate records. It was specific to my profiles rather than a general fix, so I’ve not included the commands.
Chromium’s storage overview: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/saved_tab_groups/README.md
Plasma shortcuts and screenshots
Some familiar shortcuts were missing. I restored Ctrl+Shift+Esc for System Monitor. Meta+Tab had also changed from Overview to window switching; Meta+W still opened Overview.
The screenshot shortcut sent me down another small detour: Spectacle wasn’t installed. On Ubuntu the package is:
1sudo apt install kde-spectacleComparing only kglobalshortcutsrc wasn’t enough to inventory every
shortcut. Spectacle’s launcher on the old drive also supplied default
bindings, including Print Screen and Meta+Shift+S.
The inotify warning
Plasma warned that I was using 95% of the available inotify instances, but only 1% of the watches. I wondered whether running from a storage card had something to do with it.
The relevant difference was the configured limits. Ubuntu allowed 128 instances; the old EndeavourOS configuration allowed 1,024.
Inotify lets applications subscribe to file changes. Editors and dev servers use it to notice edits. An instance is a monitoring connection; watches are the files or directories monitored through it.
I restored the old limits in a local sysctl file:
1printf '%s\n' \
2 'fs.inotify.max_user_instances = 1024' \
3 'fs.inotify.max_user_watches = 524288' \
4 | sudo tee /etc/sysctl.d/99-inotify.conf
5
6sudo sysctl -p /etc/sysctl.d/99-inotify.confThese are the values from my old setup, and the change took effect without rebooting.
The Linux reference: https://man7.org/linux/man-pages/man7/inotify.7.html
A few remaining checks
Espanso’s settings and expansions were already copied, and I installed
its Wayland build. GitHub CLI was another separate install. I also
copied the complete .pi directory, including its databases and
backup, and verified that nothing remained to copy.
Copying a directory doesn’t verify that every tool inside it runs on the new distro. I still need to check those tools, Espanso’s behaviour in the apps I use, and any server-specific SSH access as I need it.
For next time, I’m keeping the detailed migration log local and the general lessons here. Having the old drive available made recovering the missing pieces much easier. Remembering which pieces were never copied in the first place would have saved a fair bit of head-scratching!
There's a reactions leaderboard you can check out too.
Sign up for the newsletter
Want to keep up to date with what I'm working on?
Join other developers and sign up for the newsletter.
I care about the protection of your data. Read the Privacy Policy for more info.