Wren Linux

wren-linux / README.md (as plain text)

Wren Linux

A minimal, privacy-minded sway desktop for Debian 13 (trixie), installed by plain shell scripts you can read in one sitting.

Start from a bare Debian install, and you get:

Sound, a browser, screenshots and USB automount are optional extras you pick right after you agree to the install. Nothing is checked for you.

No desktop environment, display manager, tray or settings app: one compositor and plain text configs.

New here? Start with the first-time guide.

To learn:

Wren Linux is based on Debian; it is not affiliated with or endorsed by the Debian project. (It was called debsway until November 2026.)

What's different from a dotfiles repo

Hardened Firefox (an extra you pick):

Sandboxes for the risky things: images and video open in bubblewrap, and Claude Code can run as its own user or in a VM. Tor Browser, Whonix and more are one script each in extras/.

Everything undoable and checked:

Everything beyond the base is optional, one script per app in extras/: Firefox profiles, w3m, Tor Browser with bridges, Whonix, Mullvad VPN and Browser, encrypted DNS (Unbound, DNSSEC, DNS over TLS), OnionShare, your own onion site in an offline VM, KeePassXC, encrypted backups and more.

It's a set of scripts, not a distribution: you install Debian yourself, then run them. Read them first. THREAT-MODEL.md says plainly what this protects against and what it doesn't. It is a personal project tested on Debian 13 only.

Good to know before you run anything:

Quick start

  1. Install Debian 13 with disk encryption and no desktop (details in Installing Debian).
  2. Log in on the text console, plug in the USB stick (labelled WREN, which tools/pack-usb.sh does for you) and type one line:
sudo mount -L WREN /mnt && bash /mnt/wren/start.sh

start.sh copies Wren Linux to ~/wren, unmounts the stick, runs wifi.sh if you aren't online, and starts install-base.sh, which explains what it will do and asks before changing anything. (Without the stick, see After the first boot.)

  1. Log out and log in on tty1: sway starts. Super+? shows every key (also keys in a terminal, or the file ~/KEYS.txt).
  2. install-base.sh starts with a list of optional programs, and installs them at its end: arrow keys and the space bar check the ones you want, and / searches. Add more later with bash install-extras.sh, or one directly: wrenctl install keepassxc.

bash status.sh (Status in the Wren menu) shows what's set up. It starts with "Security at a glance": disk encryption, screen lock, firewall, automatic updates, the sandboxed browser and encrypted DNS, each [x] done, [~] partly or [ ] not, with what to run. bash test.sh checks that it all still works.

Folder layout

wren/
├── README.md, THREAT-MODEL.md, SECURITY.md, CONTRIBUTING.md, LICENSE (MIT)
├── start.sh                 from the stick on a fresh install: copies Wren Linux home, unmounts,
│                            wifi.sh if needed, install-base.sh
├── install-base.sh          the core desktop: explains and asks, packages, sway, bar, every part,
│                            then the extras list
├── install-extras.sh        pick optional programs from a list (install-base.sh ends with it)
├── wifi.sh                  get online over Wi-Fi, whatever is installed
├── status.sh                security at a glance first, then what's installed
├── update.sh                bring Wren Linux up to date and rerun the scripts whose files changed
├── encrypt-usb.sh           erases a USB stick and makes it encrypted (LUKS), for backups and keys
├── test.sh                  checks scripts, packages, downloads and keys still work
├── packages.sh              the core's package list and the order of the parts
├── pins.sh                  every signing key Wren Linux trusts, with the date it was checked
├── lib.sh, lib/             shared helpers (sourced, not run): apt, sway, network, ...
├── parts/                   one script per core piece; install-base.sh runs them all
├── extras/                  optional apps, one script each (and firefox-profiles/)
├── bin/                     the small commands the scripts install into ~/.local/bin
├── data/                    key cheat sheet, foot and Mousepad settings, Tor Browser and
│                            incognito prefs
├── prep/                    intro.sh (what the base install does, and the question);
│                            before a reinstall: fetch-offline-debs.sh, fetch-tor-browser.sh;
│                            setup-sudo.sh (run as root if the installer left sudo out)
├── tools/                   for working on Wren Linux, in a git copy only: pack-usb.sh, tests
├── vendor/mockvps-kvm/      the mockvps-kvm project, with its own README and license
├── docs/                    the longer guides
├── offline/                 saved downloads, not published (in .gitignore)
└── ignore/                  your own setup, never published (in .gitignore)

offline/ holds the large downloads: debs/ (Wi-Fi packages and their signed Debian lists), torbrowser/ and whonix/. When Wren Linux runs from a folder you can't write to (the package's root-owned /usr/share/wren), they go in ~/.local/share/wren/offline instead. Run every script with bash script.sh; that works even when a USB stick's filesystem drops the executable bit.

Before you reinstall

  1. Download the offline Wi-Fi packages, so a fresh install can get online without a cable: bash prep/fetch-offline-debs.sh. It picks the firmware for this machine's network cards, and saves Debian's signed package lists next to them, so wifi.sh can check every package on the stick before installing it. Preparing for a different machine? bash prep/fetch-offline-debs.sh --all gets the firmware for every common Wi-Fi card (larger).
  2. Optionally bash prep/fetch-tor-browser.sh, so Tor Browser installs without contacting torproject.org.
  3. Back up everything you want to keep from /home, above all wallet files and password databases: the encrypted install erases the whole disk. A plain USB stick isn't encrypted, so delete anything sensitive from it once it's restored.
  4. Copy Wren Linux to a USB stick: bash tools/pack-usb.sh /media/you/STICK copies the last commit and offline/ (not your ignore/ folder), labels the stick WREN (it asks) and adds a START-HERE.txt you can read on another computer. The label is what lets one line find the stick on the new system. Or copy the whole Wren Linux folder yourself and label the stick yourself (fatlabel /dev/sdX1 WREN for FAT, e2label /dev/sdX1 WREN for ext4).

An automatic install stick

Instead of installing Debian by hand and copying Wren Linux over, you can build a stick that does both:

bash prep/make-install-iso.sh

What it builds. It downloads Debian 13's live "standard" image, text only, checked against the Debian CD signing key pinned in pins.sh. It adds Wren Linux's answers and Wren Linux itself, and can write it to a USB stick.

What it does when you boot from it. "Automatic install with Wren Linux" installs Debian as below:

It asks only for the disk passphrase, your password, and the final "write the changes to disks?", which names the disk it erases. The stick itself is never offered.

At the first login on the console, it asks once "Set it up now?". Enter runs bash /usr/share/wren/start.sh (the usual introduction and questions), and n leaves it for later.

Options:

OptionWhat it does
--user NAMEThe user to make
--keymap deThe keyboard layout
--locale de_DE.UTF-8The language and formats
--timezone Europe/BerlinThe clock's time zone. By default, the one of the computer building the image
--no-eraseSkip filling the disk with random data
--noencryptNo disk encryption: no passphrase at boot, but anyone with the laptop can read the disk. It asks first
--autologintty1 logs you in without your password and starts sway
--simpleswayOne workspace: the first install list starts with one-workspace ticked
--scripts FOLDERYour own files, to ~/scripts
--publicAn image for anyone (tools/publish-image.sh): the user "user", UTC, a US keyboard, disk encryption, and no saved downloads

What it needs: xorriso, wget, gpg and git, which it installs first if they're missing. xorriso it removes again at the end.

The offline Wi-Fi packages (offline/debs, for a laptop with no network at first boot) are refreshed first when they'd soon be too old for wifi.sh to take, and it says until when they work: about a week after the build, since Debian's security and updates lists are only valid that long. An older stick still installs, over a cable or a phone.

Try the image in a VM first: bash tools/try-install-iso.sh starts it the way a laptop would (UEFI, the image as a USB stick, an empty 20 GB disk), says what to check, then starts the installed system.

In the VM's window, your own sway takes Super+Enter, Super+d and the rest first. Press Ctrl+Alt+G there to give the VM the keyboard (again to take it back), or click Wren Menu in the VM's bar and choose Terminal.

Installing Debian

In the installer:

After the first boot

The quick way is the one line in the Quick start. By hand, or without the stick:

# 1. Get the scripts. From the USB stick (labelled WREN):
sudo mount -L WREN /mnt
cp -r /mnt/wren ~ && sudo umount /mnt
#    If it can't find the label, lsblk -f lists the drives and dmesg | tail
#    shows the name the stick got (e.g. sdb1): sudo mount /dev/sdb1 /mnt
#
#    Or, with no stick, if you're already online (a cable, USB tethering from
#    a phone, or Wi-Fi you set up in the installer; wifi.sh is one of the
#    scripts you're downloading, so it can't help yet):
#    sudo apt install git
#    git clone https://wrenlinux.org/git/wren-linux.git wren
#    (git isn't part of "standard system utilities", so apt brings it first.
#    Read the scripts before you run them: this comes over https from Wren
#    Linux's own server only.)
cd ~/wren

# 2. Get online (skip if you already are: a cable, or the installer's Wi-Fi)
bash wifi.sh

# 3. Only if you set a root password in the installer
su -c "bash $HOME/wren/prep/setup-sudo.sh $USER"    # then log out and back in

# 4. The core desktop
bash install-base.sh

Then log out (exit) and log in on tty1: sway starts by itself if you said yes to that, otherwise type sway. The key cheat sheet opens on your first login. Super+? brings it back, and so does typing keys or opening ~/KEYS.txt.

The status bar:

Timers and alarms: click the time (or press Super+t), for this month's calendar (cal, from ncal) and today's date. Type 10m or 7:30 and press Enter. Esc, or another click on the time, closes it.

The same goes for the Wren menu and the Wi-Fi screen: click again to close. Only one of them is open at a time: clicking another closes it (except the menu while it runs Update or Get more apps, which stays).

From a terminal:

Timers last until you log out, and a sleeping laptop does not wake for them.

Getting updates from Wren Linux's server

The copy from the stick has the files but no git history. Once you're online, this makes it a git checkout of the published Wren Linux (WREN_GIT_URL in pins.sh), without changing a file:

bash ~/wren/git-helper.sh

What it asks:

It never pushes by itself. wrenctl git does the same for any repository of yours (wrenctl git DIR, or run inside it).

What it does: it finds the published commit whose files are exactly yours and sets your copy there, so bash ~/wren/update.sh then shows and brings in what came after.

After that, update.sh shows what's new and asks before bringing it in. Plain git pull works too.

If Claude works on it in claude-vm, its work reaches you without any git host: claude-pull, in your copy, fetches the VM's branch claude over the VM's own ssh login, shows every commit, and brings it in on yes (docs/extras/claude-vm.md). The VM needs no key anywhere; claude-vm give PATH... hands it files the same way, privately.

Who may update main: the server's git mirror is read-only, and only you push to it (with your own ssh key, through wren-release). That doesn't check signatures either: update.sh does.

The wrenctl command

In a terminal, wrenctl --help lists what it does and man wrenctl has the details. Each option works with or without its dashes (wrenctl update, wrenctl --update).

CommandWhat it does
wrenctl --updateBrings Wren Linux up to date (see below)
wrenctl --appsPicks optional programs
wrenctl --statusShows what is set up
wrenctl --testRuns the checks
wrenctl --welcomeShows the key cheat sheet again
wrenctlOn a sway screen, alone (or a click on Wren Menu in the bar), opens the menu

One extra at a time:

CommandWhat it does
wrenctl install NAMEInstalls it, or repairs or updates it
wrenctl remove NAMETakes it out
wrenctl manual NAMEPrints the steps to do it by hand
wrenctl listLists them all
wrenctl part NAMERuns one part of the base again

Tab completes the name. Only the exact name of an extra is accepted, so nothing else is run. Options after the name go to its script (wrenctl install claude-vm --account work).

Updating

bash ~/wren/update.sh

or click Wren Menu at the left of the status bar and choose Is my computer OK? > Update. Get more apps (install-extras.sh) opens in the same window, and Esc on its list with nothing picked closes the window at once.

What's new. It fetches what's new, shows you every change, and asks before bringing it in. It's only ever a plain fast-forward, so your own edits are never overwritten.

The signature. Once pins.sh names Wren Linux's signing key (WREN_SIGNING_FPR), it brings a change in only if the newest commit carries a good signature from exactly that key, checked with the key in the copy you already have. Until then, it says that it didn't check.

Running scripts again. It lists the parts and extras you have set up whose files changed, and offers to run them again, one after the other. Running a script again is how a change reaches /usr/local, the firewall or Firefox's policy.

Signing keys. Each run also checks the signing keys of the apt repositories Wren Linux added (Claude Code, Signal, Mullvad, the Tor Project's for lyrebird). One that has expired, or will within 60 days, is fetched again, and the new one goes in only if it is exactly the pinned key (pins.sh) and lasts longer. Without this, a key that runs out would quietly stop that program's updates. status.sh shows the same check.

The first run takes what you have now as already applied. If you updated by hand earlier, bash update.sh --since COMMIT offers what changed after that commit (git log shows it), and bash update.sh --all offers everything that is set up.

More

PageWhat it covers
docs/first-time.mdYour first install and first day, step by step
docs/wifi.mdwifi.sh, and how the packages on the stick are checked
docs/install-base.mdWhat install-base.sh sets up, and every part
docs/extras.mdThe optional apps, Firefox with its hardening and incognito, Firefox profiles
docs/extras/One page per extra: its commands, what it is, what it doesn't protect against, and every step to install it by hand (wrenctl manual NAME prints them)
docs/vps.mdThe website, its git mirror and onion addresses on a VPS (tools/setup-vps.sh), tried in a local VM first
docs/bios.mdThe BIOS (UEFI firmware) settings for a Wren Linux install, and for security in general, with a checklist
docs/opsec.mdOperations security in general: laptops, USB drives, networks, passwords, encryption, private communication, physical security, and what to do when something goes wrong
docs/maintaining.mdIn a git copy (wrenctl install wren-dev gets one): test.sh, pins.sh, shellcheck, the other scripts
docs/distro-plan.mdIn a git copy too: the plan from these scripts to a Debian-based distribution: a package, a signed apt repository, a website, an installer image
THREAT-MODEL.mdWhat this protects against, and what it doesn't

Removing things

wrenctl remove NAME (for an extra; bash extras/NAME.sh --remove does the same) and bash parts/NAME.sh --remove undo one piece.

Several pieces need the same packages (QEMU for Whonix and mockvps-kvm, XWayland for every app that only speaks X11), so a script doesn't install its packages directly. It installs a tiny package of its own, wren-NAME, that depends on them. --remove removes that package and lets apt autoremove what nothing else needs, so a shared package stays until the last script that uses it is removed.

Your own setup: ignore/

Keep your personal choices out of the shared scripts: put them in ignore/, which is in .gitignore. A personal.sh there can run the extras you use and add your own look, keys and configs, e.g.:

#!/bin/bash
. "$(dirname -- "${BASH_SOURCE[0]}")/../lib.sh"
need_user
for x in keepassxc git; do bash "$WREN_DIR/extras/$x.sh"; done
put_snippet look <<'EOF'
default_border none
output eDP-1 scale 1.25
EOF
bash "$WREN_DIR/extras/firefox-profiles/new-profile.sh" mail https://mail.example --fast-js

put_snippet NAME writes ~/.config/sway/config.d/NAME.conf, checks it with sway -C and reloads sway.

Keys

Super is the Windows / Cmd key. The first time sway starts, it asks whether its shortcuts should use Super or Alt (for a keyboard without a Windows key); keys --mod alt or keys --mod super switches later, and the cheat sheet shows whichever you use. Super+? shows the full list (data/keys.txt); the main ones:

KeyAction
Super+Enterterminal (foot)
Super+dlauncher (type e.g. firefox)
Super+Shift+qclose window
Super+1 … 0, Super+Shift+1 … 0go to / send window to workspace
Super+h/j/k/l or arrowsmove focus
Super+ffullscreen
Super+Escapelock (by itself, the screen turns off after 5 minutes idle and locks a minute later; timer lock 10 changes that)
Brightness keys, Super+F1 / F2brightness down / up
Print, Super+Shift+pscreenshot of the whole screen to ~/Pictures
Shift+Print, Super+Shift+sscreenshot of a region
Ctrl+Print, Super+Shift+xregion to the clipboard
Super+n / Super+Shift+ndismiss one / all notifications
Super+Shift+creload config
Super+Shift+eexit sway

Troubleshooting

License

MIT, for all of Wren Linux (vendor/mockvps-kvm/ keeps its own copy of the same license).

The pictures in data/wallpapers/ aren't under it: each keeps its own licence, named with its author in data/wallpapers/CREDITS.txt.