Automated installation script for setting up Niri compositor and Noctalia shell on Debian-based systems.
This script provides a complete installation workflow for:
- Niri: A scrollable-tiling Wayland compositor
- Noctalia: A native Wayland desktop shell (v5) — bars, launcher, lock screen, notifications, wallpaper, and more, built directly on Wayland with no Qt or GTK dependency
- Noctalia Greeter: A minimal login greeter for greetd that matches Noctalia Shell's visual language
- Debian 13 (Trixie) or later (minimal installation recommended)
- Root/sudo access
- Internet connection
x86_64orarm64(architecture-aware steps pick the matching packages; the Neovim tarball is only published for these two)- Minimal Debian installation without graphical environment (recommended)
- This script is designed to run on a fresh, minimal Debian installation
- It will install all necessary components to create a complete Wayland desktop environment
- If you have an existing desktop environment (GNOME, KDE, etc.), you may want to remove it first using
--remove-gnomeor manually
Run the script to install all core components automatically:
chmod +x install.sh
./install.shWith no arguments the script installs the four core components: system dependencies, Niri, Noctalia and Noctalia Greeter.
Passing any --install-*, --remove-gnome, --apply-fixes or --upgrade
flag switches off the "install everything core" default, so the script installs
only what you asked for:
./install.sh --install-vscode # only VS Code, nothing from the core set
./install.sh --apply-fixes # only the network/hardware fixes
./install.sh --install-font --install-nvim # only these two--ask-step is orthogonal: it can be combined with the default (all core
components, each behind a prompt) or with individual flags.
Use the --menu flag to select which components to install:
./install.sh --menuThis shows an interactive menu where you can choose:
- Core components (1-4): System dependencies, Niri, Noctalia, Noctalia Greeter
- Upgrade options (U1, U2, U3, UA): Upgrade individual components or all at once
- Optional components (5-15): VS Code, Oh My Zsh, document viewers, office tools, network fixes, GNOME removal, wallpaper changer, wayland-session desktop entry, Yazi file manager, 0xProto Nerd Font, Neovim
Use the --ask-step flag to get prompted before each installation step:
./install.sh --ask-stepThis allows you to skip specific components if already installed or not needed.
It can be combined with the default (all four core components, each behind a
prompt) or with individual --install-* flags.
Install specific optional components directly:
# Upgrade components
./install.sh --upgrade niri # Upgrade only Niri
./install.sh --upgrade noctalia # Upgrade only Noctalia
./install.sh --upgrade greeter # Upgrade only Noctalia Greeter
./install.sh --upgrade all # Upgrade all components (Niri + Noctalia + Greeter)
# Install Noctalia Greeter
./install.sh --install-noctalia-greeter
# Install VS Code with Wayland support
./install.sh --install-vscode
# Install Oh My Zsh
./install.sh --install-omz
# Install document viewers (zathura, loupe)
./install.sh --install-docs
# Install office tools (patat, gnumeric, abiword)
./install.sh --install-office
# Apply network & hardware fixes
./install.sh --apply-fixes
# Install random wallpaper changer (systemd timer)
./install.sh --install-wallpaper
# Install wayland-session desktop entry for display managers (NOT installed by default)
./install.sh --install-desktop-entry
# Install Yazi terminal file manager (builds from source)
./install.sh --install-yazi
# Install 0xProto Nerd Font and apply it to alacritty
./install.sh --install-font
# Install Neovim with lazy.nvim and oil.nvim
./install.sh --install-nvim
# Remove GNOME/GDM3 (WARNING: removes desktop environment)
./install.sh --remove-gnome
# Combine multiple options
./install.sh --install-vscode --install-omz --apply-fixesFor a full list of options, run:
./install.sh --helpThe script performs the following steps:
Installs all required build tools and libraries:
- Build essentials (cmake, ninja-build, gcc, git, curl, meson, etc.)
- Wayland libraries (protocols, client, scanner, EGL)
- Noctalia v5 dependencies (sdbus-c++, pipewire, polkit, pam, pango, cairo, harfbuzz, freetype, fontconfig, xkbcommon, glib, rsvg, curl, qalculate, xml2, webp, epoxy, jemalloc, webp)
- Display info library (libdisplay-info, for Niri) — taken from apt when the
release carries a new enough version, otherwise the newest
.debis fetched from the Debian pool for the detected architecture - wlroots 0.20 (required by Noctalia Greeter) — installed from apt when the release provides it. Debian 13 ships only wlroots 0.18 and is too old to satisfy 0.20, so on that release the script prints the commands to install it from Debian's forky release instead of changing anything itself. See wlroots-0.20 on Debian 13
- clang and libclang-dev — Niri's build runs
bindgen, which needslibclang.so; without these the Rust build fails on a missing library - Wayland desktop tools (alacritty, fuzzel, waybar, xdg-desktop-portal-gtk, xwayland, nwg-look)
- Supporting tools used by later steps (
wget,python3) - Rust toolchain (installed via rustup if not already present)
- just build tool (installed via
cargo install just, required by Noctalia; skipped whenjustis already onPATH)
Builds and installs the Niri Wayland compositor from source:
- Clones from YaLTeR/niri
- Builds with Cargo in release mode
- Installs binary and session files
Builds and installs Noctalia v5 from source:
- Clones from noctalia-dev/noctalia
- Builds with
just configure release+just build release - Installs binary and assets via
sudo just install release - Verifies installation
Builds and installs Noctalia Greeter from source — a login greeter for greetd matching Noctalia's visual language:
- Clones from noctalia-dev/noctalia-greeter
- Builds with
just configure-release+just build-release - Installs binary and assets via
sudo meson install -C build-release - Runs
setup_greeter_system.shto configure greetd, create log directories, and write initial greeter config - greetd service is enabled automatically
After the core steps finish you are asked whether to apply config.kdl (from
next to the script) to ~/.config/niri/config.kdl. An existing config.kdl is
copied to config.kdl.backup before being replaced, so declining the prompt
leaves your setup untouched.
Every build happens in a mktemp -d directory. All of them are registered with
an EXIT trap, so they are removed when the script finishes — including when a
build fails or you press Ctrl-C. Nothing is left behind in /tmp, and the
script always returns you to the directory you started it from.
- Adds Microsoft apt repository
- Installs VS Code
- Configures Wayland support via desktop file modification (warns instead of failing if the desktop entry cannot be found)
- Adds shell alias for Wayland flag
- The repository signing key is written to a temp file, never into the checkout
- Installs zsh package
- Offers to change default shell to zsh
- Installs Oh My Zsh framework
- Installs zathura (PDF viewer)
- Installs zathura-pdf-poppler (PDF backend)
- Installs loupe (image viewer)
- Installs patat (terminal-based presentation tool)
- Installs gnumeric (spreadsheet application)
- Installs abiword (word processor)
- Installs NetworkManager, bluez, brightnessctl, upower
- Installs pipewire audio libraries
- Installs firmware packages (iwlwifi, realtek, etc.)
- Installs wlsunset (screen color temperature)
- Installs nwg-look (GTK theme switcher)
- Adds the invoking user (honouring
$SUDO_USER) to netdev, bluetooth, and video groups - Updates NetworkManager configuration to managed mode
- Comments out wlan0 entries in
/etc/network/interfaces— only active stanzas, so re-running never stacks comments on an already-commented line - Backs up configuration files before modifying
- Creates a script at
~/.local/bin/noctalia-random-wallpaper.sh - Sets up systemd service and timer files
- Automatically rotates wallpaper every 30 minutes
- Uses
noctalia msg wallpaper-setto change the wallpaper - Timer starts on boot and runs continuously
- Safe to re-run: the three files are simply rewritten
- NOT installed by default - must be explicitly requested
- Creates
/usr/share/wayland-sessions/niri.desktop(creating the directory if a minimal install does not have it yet) - Points
Execat the absolute/usr/local/bin/niri, since display managers do not always run sessions through a loginPATH - Allows selecting Niri from display manager login screen (GDM, SDDM, LightDM, etc.)
- Useful if you have an existing graphical environment and want to add Niri as a session option
- Not needed for minimal installations that boot directly to console
- Installs apt prerequisites:
ffmpeg,jq,poppler-utils,fd-find,ripgrep,fzf,zoxide,imagemagick - Installs
7zip, falling back to the olderp7zip-fullpackage name - Symlinks
fdto Debian'sfdfindin~/.local/binso Yazi finds it - Clones sxyazi/yazi and builds from source with Cargo
- Installs
yaziandyabinaries to/usr/local/bin/ - Requires Rust toolchain (install core components first, or have Rust already)
- Downloads
0xProto.zipand extracts the.ttffiles into~/.local/share/fonts/0xProto - Rebuilds the font cache with
fc-cache -fv - Rewrites the
[font]/[font.*]tables in~/.config/alacritty/alacritty.toml, leaving every other setting intact; an existing file is copied toalacritty.toml.backupfirst - Safe to re-run: old font tables (including subsections such as
[font.bold]) are removed rather than duplicated
- Downloads the latest stable Neovim release tarball for the detected
architecture (
x86_64orarm64) and unpacks it into/usr/local - Creates
~/.config/nvim/init.luabootstrapping lazy.nvim and oil.nvim - An existing
init.luais copied toinit.lua.backupbefore being replaced - Plugins are installed on the first launch of
nvim
- WARNING: This removes your desktop environment
- Stops GDM3 service
- Purges GNOME packages (gnome-core, gnome-shell, gdm3, etc.)
- Runs autoremove to clean up dependencies
- Sets system to boot to multi-user target (console mode)
- Requires typing "yes" to confirm
The script includes an upgrade mode to update already-installed components:
# Upgrade individual components
./install.sh --upgrade niri # Rebuilds Niri from latest source
./install.sh --upgrade noctalia # Rebuilds Noctalia from latest source
./install.sh --upgrade greeter # Rebuilds Noctalia Greeter from latest source
# Upgrade everything at once
./install.sh --upgrade all # Updates all componentsNote: When using --upgrade, only the specified components are updated. Other installation options are ignored.
Place a config.kdl file next to the install script to have it automatically copied to ~/.config/niri/config.kdl during installation.
The bundled config.kdl is pre-configured to start Noctalia automatically and includes keybindings for common actions via Noctalia IPC:
| Keybind | Action |
|---|---|
Mod+D |
Toggle launcher |
Super+Alt+K |
Lock screen |
Super+Alt+L |
Lock and suspend |
Super+Alt+V |
Show clipboard |
Noctalia stores its configuration at ~/.config/noctalia/config.toml. A starter config with all defaults is available in the noctalia repository.
After installation completes, you have two options:
niriIf you have a display manager (GDM, SDDM, LightDM, etc.) and want to select Niri from the login screen:
-
Install the wayland-session desktop entry:
./install.sh --install-desktop-entry
-
Log out and select "Niri" from the session menu at your login screen
Note: The desktop entry is not installed by default. It's only needed if you're using a display manager and want Niri as a selectable session option.
Noctalia Greeter is built against wlroots 0.20, which upstream pins in its
meson.build. Debian 13 (Trixie) ships wlroots 0.18, so the greeter cannot
be built against the release's own package — this is why it fails with
Dependency "wlroots-0.20" not found.
The script cannot install this for you: Debian 13's libraries are too old to satisfy wlroots 0.20, so it has to come from a newer release, and adding a repository is a system-wide decision. Step 1 tries apt and, when the release lacks the package, prints the exact commands to add Debian's forky release with a low priority pin and install from it. Nothing is changed automatically.
wlroots 0.20 installs alongside the release's own wlroots 0.18 without conflict — the two use different sonames and different include directories, so 0.18 stays intact.
For the manual commands and the reasoning, see
Dependency "wlroots-0.20" not found
in Troubleshooting.
Niri and Noctalia do not need wlroots — if you would rather not install it,
run the individual steps (./install.sh --install-niri) and skip the greeter.
- cmake, ninja-build, build-essential, meson
- pkg-config
- Rust toolchain (via rustup)
- just (via
cargo install just, only whenjustis not already installed) - wget, python3 (used by later steps)
- libwayland-dev, wayland-protocols, libegl1-mesa-dev
- libwlroots-0.20-dev (wlroots compositor libraries, for Noctalia Greeter —
pinned to 0.20 because upstream's
meson.buildrequires exactly that; needs a newer Debian release than 13 to be installed from) - libsdbus-c++-dev (D-Bus IPC)
- libpipewire-0.3-dev (audio)
- libpolkit-agent-1-dev, libpam0g-dev (authentication)
- libjemalloc-dev (memory allocator)
- libpango1.0-dev, libcairo2-dev, libharfbuzz-dev, libfreetype-dev, libfontconfig1-dev (text/rendering)
- librsvg2-dev, libwebp-dev, libepoxy-dev (images/GL)
- libxkbcommon-dev, libglib2.0-dev (input/platform)
- libcurl4-gnutls-dev, libqalculate-dev, libxml2-dev (network/data)
- libdisplay-info3, libdisplay-info-dev (monitor info, for Niri)
- alacritty, fuzzel, waybar
- xdg-desktop-portal-gtk, xwayland
- nwg-look (GTK theme switcher)
- swayidle (idle/lock trigger)
- greetd (login greeter daemon, for Noctalia Greeter)
The test suite runs entirely against stubbed system commands, so it is safe to run on a machine that has nothing installed — it never touches the real system:
bash tests/run_tests.sh| Test | What it covers |
|---|---|
test_help_output.sh |
--help text and the flags it advertises |
test_menu_source.sh |
menu entries and how selections are parsed |
test_greeter_source.sh |
greeter-specific code paths present in install.sh |
test_debian_detection.sh |
codename → wlroots version mapping, including a missing os-release |
test_shellcheck.sh |
lints install.sh and the tests (skipped if shellcheck is absent) |
test_sandbox_run.sh |
end-to-end runs of every flag against stubbed commands |
test_idempotency.sh |
re-running a step does not duplicate or corrupt its output |
sandbox_run.sh and idempotency.sh share tests/lib/sandbox.sh, which points
HOME and TMPDIR at a throwaway directory and puts stubs for sudo, apt,
git, cargo, wget and friends on PATH. Its sudo stub refuses any path
under /etc, /usr, /bin and /sbin, so a mistake in a test cannot modify
the host.
Install the linter to get the full suite:
sudo apt install shellcheckThe script sources ~/.cargo/env automatically. If you encounter this outside the script, run:
source ~/.cargo/envRun the system dependencies step first ([1] in the menu or ./install.sh default) to ensure all build libraries are installed.
Noctalia Greeter builds against wlroots 0.20 specifically — upstream pins
that version in its meson.build. Debian 13 ships only wlroots 0.18, so a fresh
Trixie machine cannot satisfy it from the release's own repositories.
The script cannot install this for you: Debian 13's own libraries are older than wlroots 0.20 requires, so the package has to come from a newer release and adding a repository is your decision. Step 1 prints the commands when it cannot find wlroots 0.20. To do it by hand, use the block below.
Installing wlroots 0.20 on Debian 13 (manual)
Debian 13 cannot satisfy wlroots 0.20 on its own — it ships libwayland 1.23,
libdrm 2.4.124 and libxkbcommon 1.7, all below what wlroots 0.20 requires.
So wlroots 0.20 has to come from a newer release. Add forky and install it
explicitly, with forky pinned to a low priority so it never becomes the default
for anything else:
# 1. add forky
echo 'deb http://deb.debian.org/debian forky main' \
| sudo tee /etc/apt/sources.list.d/forky.list
# 2. pin it low, so a normal `apt upgrade` keeps using trixie
echo 'Package: *' | sudo tee /etc/apt/preferences.d/forky-low-priority
echo 'Pin: release n=forky' >> /etc/apt/preferences.d/forky-low-priority
echo 'Pin-Priority: 100' >> /etc/apt/preferences.d/forky-low-priority
# 3. install wlroots 0.20 and its dependency closure from forky only
sudo apt update
sudo apt install -t forky libwlroots-0.20-devThe -t forky flag matters: it pulls wlroots and the newer libraries it
needs (libwayland, libdrm, libxkbcommon, …) from forky, while everything
else on the system stays on trixie.
Verify it worked:
pkg-config --exists wlroots-0.20 && echo "wlroots 0.20 ready"
dpkg -l 'libwlroots-0.20*' | tail -3If dpkg -l shows un / iF / iU in the status column, the install did not
complete — run sudo apt-get -f install and read the error.
Why not just download the .deb? Fetching libwlroots-0.20 from
ftp.debian.org/debian/pool/ and running dpkg -i does not work here.
dpkg -i resolves no dependencies, so it unpacks the package and then leaves it
unconfigured on trixie — the headers are unusable and the greeter still will not
build. Letting apt pull the matching dependency set from forky is the approach
that actually works.
Niri's build runs bindgen, which links against libclang. Step 1 installs
clang and libclang-dev; if you skipped it or built Niri by hand:
sudo apt install clang libclang-devExpected on an older release: Debian ships a version too old for Niri. The
script then fetches the newest matching .deb straight from the Debian pool for
your architecture. If that also fails, check network access to
ftp.debian.org.
Debian ships the binary as fdfind to avoid a name clash. The script symlinks
fd into ~/.local/bin; make sure that directory is on your PATH.
Only x86_64 and arm64 have official Neovim release tarballs. Other
architectures have to be installed from your package manager or built from
source.
The script backs up files before overwriting them, next to the original:
| File | Backup |
|---|---|
~/.config/niri/config.kdl |
config.kdl.backup |
~/.config/alacritty/alacritty.toml |
alacritty.toml.backup |
~/.config/nvim/init.lua |
init.lua.backup |
/etc/network/interfaces |
interfaces.backup |
.
├── install.sh # Main installation script
├── config.kdl # Niri configuration (pre-configured for Noctalia)
├── tests/ # Test scripts (bash-based assertions)
│ ├── run_tests.sh # Runs every test below
│ ├── lib/
│ │ └── sandbox.sh # Shared harness: stubbed PATH + temporary HOME/TMPDIR
│ ├── test_help_output.sh
│ ├── test_menu_source.sh
│ ├── test_greeter_source.sh
│ ├── test_debian_detection.sh
│ ├── test_shellcheck.sh
│ ├── test_sandbox_run.sh
│ └── test_idempotency.sh
└── README.md # This file
This installation script is provided as-is. Individual components (Niri, Noctalia) have their own licenses.
- Niri: YaLTeR/niri
- Noctalia: noctalia-dev/noctalia
- Noctalia Greeter: noctalia-dev/noctalia-greeter
- Yazi: sxyazi/yazi