wslbak

command module
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 42 Imported by: 0

README

wslbak

English | 繁體中文

Scheduled backups of a WSL distro that do not stop it, a test restore after every backup, a notification when something fails, and a one-line restore.

Status: 0.2, early releases. An automated suite has made real backups, test restores and restores on 18 distros and three builds of Windows, but nobody has used wslbak day to day yet. Keep the backups you already have until it has proven itself on your PC. Differential backups are new in 0.2.0 and switched off unless you turn them on.

The same tour as a video. It is a recording of the commands as they ran, on a PC where wslbak was installed from its npm package and a demo distro named wslbak-demo existed; no text that the commands printed is changed. The drawing script adds the step titles and the cards at the start and the end, types the commands at a drawn speed, shows long output a page at a time and shortens waits. A question is put after the lines that were printed before it, as on a console; the recorder receives the two separately. The recording was made with version 0.2.0. The tour switches differential backups on to show one; without wslbak config --mode diff, every backup is a full one.

Files inside WSL are not covered by OneDrive or most backup tools: they live in one virtual disk, and when that disk is damaged or Windows is reinstalled, everything in it is gone. The usual answer is a scheduled wsl --export, but wsl --export terminates the distro before it exports (it still does in WSL 2.7), so your shells, dev servers and containers die every time it runs. wslbak reads the distro from the inside instead, while it keeps running:

> wslbak init
> wslbak config --mode diff
> wslbak run
Backing up wslbak-demo → D:\WSLBackup\wslbak-demo
  A full backup this time: there is no full backup yet that a differential can build on
  wrote 20261012T021909Z.tar.gz (91 MB, 2 s)
Test restore…
  test restore passed: all 512 sampled files match (2 s)
Done.

(This is the run in the recording: a freshly installed Debian holding 270 MB. A distro you work in is larger and takes correspondingly longer. The second command is optional: it switches differential backups on, and the line about a full backup is there because of it.)

  • The distro is never stopped. A root tar inside the distro streams it out; WSL is not shut down and nothing is written to the distro's disk.
  • Every backup is test-restored, unless you switch that off. The archive is imported as a temporary distro, checked, and removed.
  • Standard format. A full backup is a plain .tar.gz that wsl --import accepts, with or without wslbak.
  • Differential backups, if you want them. wslbak config --mode diff makes a full backup now and then, and on the other days stores only what changed (how, and what it costs).
  • One command to set up: a daily task, how many backups to keep, where they go.
  • Restore never overwrites. A whole distro comes back as a new distro; single files come back into a new folder.

Install

Requires Windows 10 or 11 and the Microsoft Store version of WSL (wsl --version must work; run wsl --update if it does not). Windows 10 itself has not been tried yet; see Limitations for what has.

With Node.js 18 or newer, from a Windows terminal or from inside WSL:

npm install -g wslbak

With Scoop:

scoop bucket add boring206 https://github.com/Boring206/scoop-bucket
scoop install wslbak

Without either: download wslbak-<version>-windows-x64.zip (or -arm64) from the Releases page, unzip it anywhere, and run wslbak.exe from there. Keep wslbak.exe and wslbakw.exe together.

In every case the executables are prebuilt, so Go is not needed. wslbak init copies them to a fixed place of its own, so the download folder, the npm installation or the Scoop folder can change later without breaking the schedule.

Updating. Install the new version the same way (npm install -g wslbak, scoop update wslbak, or a new zip), then run a wslbak command once, for example wslbak status. The scheduled task runs the copy in that fixed place, and the copy is brought up to date when the new version runs a command; --version, --help and --dry-run do not do it. wslbak init is not needed again.

Getting started

wslbak init

init looks at your distros (one that is stopped is started for that), proposes a destination, and then shows what it is about to set up — the program files, the settings file, the registry key, the scheduled task and its exact command line — before asking Proceed? [y/N]. Nothing is set up until you say yes: up to then init has only made its own folder %LOCALAPPDATA%\wslbak with a log and a lock file in it and, where wslbak was set up before, brought the installed copy of the program up to date. --dry-run shows the same plan and stops without creating even those. No administrator rights are needed. With several distros it asks which one; --all sets up all of them.

By default backups go to <drive>:\WSLBackup on the internal drive with the most free space other than the one holding the distro, so that they survive reinstalling Windows. A PC that has no such drive gets WSLBackup in your user folder instead; that is on the same drive as the distro and does not survive a reinstall that formats it, so give another place with --dest there. An external drive or a NAS share also protects against the disk itself failing; init tells you when the destination is on the same physical disk as the distro.

wslbak doctor checks the whole setup at any time and says how to fix what it finds.

Usage

wslbak init              set up backups and the daily task
wslbak config            show the settings; with options, change them
wslbak run               back up now, test-restore or check, prune old backups
wslbak list              list backups
wslbak files [id] [path] list what a backup holds in a folder
wslbak status            schedule, last result, and whether the installed program is still there
wslbak doctor            check the environment and settings, with a fix for most problems
wslbak verify [id]       test-restore an existing backup again (default: the newest); a
                         differential is restored together with its full backup
wslbak restore [id]      restore a backup as a new distro (default: the newest verified one)
wslbak uninstall         remove the task and the installed program; backups are kept

  -d, --distro <name>      which distro (default: for init the only one that can be backed up,
                           for run all that are set up)
      --all                init: set up every distro that can be backed up
      --dest <folder>      init: where to store backups
      --keep <count>       init, config: how many of the newest verified backups to keep (default 7)
      --keep-weekly <n>    init, config: also keep one per week, for n weeks (default 0)
      --keep-monthly <n>   init, config: also keep one per month, for n months (default 0)
      --at <HH:MM>         init, config: time of the daily backup (default 03:00)
      --webhook <url>      init, config: also report failures to this URL; with config, off removes it
      --notify <when>      config: failure (default) or always
      --verify <how>       config: restore (default) or none
      --exclude <pattern>  config: exclude one more path pattern (repeatable)
      --unexclude <pattern>  config: stop excluding a pattern (repeatable)
      --enable, --disable  config: turn backups of one distro on or off
      --private            config: let only your account open the backup folder
      --mode <how>         config: full (default) or diff, see "Differential backups"
      --full-every <days>  config: with diff, how long one full backup is built on (default 30)
      --full               run: make a full backup this time
      --no-verify          run: skip the test restore this time
      --find <text>        files: list entries whose name or path contains this text
      --name <name>        restore: name of the restored distro
      --to <folder>        restore: where to put the restored distro
      --path <path>        restore: bring back only this file or folder (repeatable; needs --into)
      --into <folder>      restore: put those files into this folder inside the distro
  -n, --dry-run            init, config, run, restore, uninstall: only show what would be done
  -y, --yes                init, restore, uninstall: do not ask for confirmation
      --lang <lang>        interface language: en or zh-TW (or set WSLBAK_LANG)
      --debug              show details and timing of each step
  -v, --version            print the version
  -h, --help               print the usage

Exit codes: 0 success (also after run --no-verify); 1 a backup was written but its test restore could not be carried out, files found nothing, or status, doctor or config found something that needs attention; 2 failure; 3 another wslbak is already at work.

Inside WSL you can give Linux paths (--dest /mnt/d/WSLBackup); they are converted for you.

Restoring a whole distro

wslbak restore

restores the newest backup that passed its test restore (for a differential, its check); if none has passed, it offers the newest one and says so. If a distro with the original name still exists, the copy is called <name>-restored-<date>; pick your own with --name. An existing distro is never overwritten, changed or removed, and the restored one is not started for you.

After reinstalling Windows, the wslbak that was installed on C: is gone too. You do not have to install it again, and you need neither Node nor npm: on every backup run wslbak copies its own program (wslbak.exe) and a note (README-RESTORE.txt) into the backup folder, next to the backups. So as long as the backup folder is still there (on D: or an external drive, say), install WSL and run the copy in that folder:

D:\WSLBackup\wslbak.exe restore

(D:\WSLBackup stands for your backup folder: the one init proposed, or the one you chose.)

Without wslbak at all, a full backup is an ordinary archive:

wsl --import Ubuntu-24.04 C:\WSL\Ubuntu-24.04 D:\WSLBackup\Ubuntu-24.04\20260115T030000Z.tar.gz --version 2

A distro imported by hand logs in as root; add [user] and default=<your user name> to its /etc/wsl.conf to change that. wslbak restore sets the default user for you.

A differential backup (<id>.diff.tar.gz) cannot be imported like that: it holds only what changed. wslbak restore puts it together with its full backup. Without wslbak, importing the full backup brings back the day of the full backup, and the differential, itself a plain .tar.gz, can be opened with any tar to take single files out of it.

Bringing back single files

Most of the time you do not need the whole distro, just the file you deleted yesterday.

wslbak files /home/me/project            what is in that folder in the newest backup
wslbak files --find notes.md             where is a file whose name contains this
wslbak files 20260114T030000Z /etc       the same for an older backup

wslbak restore --path /home/me/project/notes.md --into /home/me/recovered

--path takes a file or a folder and can be repeated. The files go into the folder given by --into, inside the distro the backup came from, with their full path recreated underneath: /home/me/recovered/home/me/project/notes.md. That folder must not exist yet (the folder above it must), or be empty, so nothing you have now is ever overwritten; move the files where you want them afterwards. Owners, permissions, extended attributes and ACLs are preserved.

Without an id, files looks in the newest backup whether or not it was verified, while restore --path takes the newest verified one, as restore does. Give both the same id when that matters.

A differential backup shows, and gives back, the distro as it was on its day: what the differential holds, and from its full backup everything that had not changed or been deleted.

What is backed up

Everything on the distro's root file system, with ownership, permissions, extended attributes, file capabilities, hard links and sparse files. Sockets are skipped; tar cannot store them. Left out by default: what is in /tmp and /var/tmp and in the .cache folders of home directories, and WSL's own /init. Windows drives under /mnt are not part of the distro and are never included.

wslbak config                                         show the settings
wslbak config --exclude "/home/*/Downloads/*"         leave something out
wslbak config --keep 3 --keep-weekly 4 --keep-monthly 6
wslbak config --at 02:30
wslbak config --mode diff                             differential backups (see below)
  • --exclude takes a path pattern inside the distro; * matches anything, / included. Quote it, so your shell does not expand the * first.
  • --keep is the number of newest verified backups. --keep-weekly and --keep-monthly also keep the newest backup of each of the last n weeks or months that have one (the current week and month count), so they add a few older copies. They do not make backups smaller. To use less space, exclude what can be downloaded again (wslbak doctor measures the usual caches and prints the command for each), or switch to differential backups.
  • --verify none turns the test restore off; then the newest backups are kept regardless.
  • --notify always also notifies on success, so that silence means the task is not running.
  • Run wslbak init -d <other distro> to add another distro. One daily task covers all of them.

The settings are stored in %LOCALAPPDATA%\wslbak\config.json. Each backup is three files in <dest>\<distro>\: <id>.tar.gz, <id>.json (size, SHA-256, warnings, the result of the test restore) and <id>.idx.gz (the list of files, used by wslbak files). The id is the UTC time of the backup. A differential backup has <id>.diff.tar.gz in the place of <id>.tar.gz, and a full backup made while differential backups are on has a fourth file, <id>.dirs.gz (see below). Next to the settings wslbak keeps state.json (the last results), wslbak.log and a lock file; wslbak.exe and README-RESTORE.txt in <dest> are written again on every run.

Differential backups

As it comes, wslbak makes a full copy of the distro on every run. With

wslbak config --mode diff

it makes a full backup now and then, and on the other days a differential: the files whose content or attributes changed since that full backup, and every directory. A differential depends on its full backup and on nothing else, so a restore never needs more than two archives, and deleting one differential does not affect another. wslbak config --mode full switches back.

How much smaller a differential is depends on how much changes between two backups. In the end-to-end test on the development PC, the full backup of a freshly installed Debian with the test files is 186 MB, and the differential made after the test had edited, added, renamed and deleted a few dozen files is 555 KB. A run in this mode first lists every file of the distro without reading any of them; with two million files that took 14 seconds, and the differential made there after three small changes is 100 KB, next to a full backup of 88 MB.

  • How long a full backup is built on. 30 days, or what you give with --full-every <days>; the backup after that is a full one. wslbak run --full makes a full backup at any time. A differential that has grown to more than half the size of its full backup is also the last one on it.

  • Restoring is the same. restore, restore --path, files and verify take a differential like any backup. wslbak puts it together with its full backup: the differential's directories, then everything in the full backup that the differential did not replace and that had not been deleted, then the differential's files. Each path is written once, and a whole distro is still one wsl --import.

  • What counts as changed is decided by GNU tar, from the two times the file system keeps for every file: when its content was last modified, and when anything about it last changed (its name, mode, owner, links). A file that changed at all is stored again as a whole.

  • A differential is only kept when every file can be accounted for. After tar has written it, wslbak goes through a list of everything in the distro: each file must be in the differential, or be in the full backup at the same path, in a folder that is still the same folder (the same inode as recorded with the full backup). If that holds, the backup's record says which entries of the full backup this restore point no longer wants. If it does not, the differential is thrown away and that run makes a full backup instead, and tells you why:

    • an old folder was renamed or moved, or two folders changed places. The files in it did not change, so tar does not pick them up, and they are not where the full backup has them. (A renamed file is no problem, and neither is a new folder.)
    • while the backup ran, a file disappeared from a folder that the full backup has no record of. Most likely that is a new folder with a short-lived file in it, but it could be an old folder that was moved in from a path the full backup leaves out (an excluded one), and the two cannot be told apart;
    • while the backup ran, an old folder disappeared and a new one appeared. That may have been a rename, and then the unchanged files in it would be in neither archive;
    • a folder of the distro is mounted at a second place inside it, so everything in it is seen twice;
    • the exclude list changed, or the distro's tar is another version;
    • the distro's clock is earlier than it was at the full backup, or a folder carries a change time from before the full backup that it did not have then;
    • the root file system is not ext4;
    • the full backup failed its test restore, or the last differential failed its check.

    wslbak run prints the reason. A scheduled run has no window, so wslbak status says whether the last backup was a differential and, if it was a full one, why. Some reasons do not go away by themselves (the file system, a folder mounted twice); with those, differential backups are in effect off for that distro.

  • Checking. A full backup is test-restored after every run, as always. A differential is read back after every run (it unpacks, its checksum and its number of entries are what was written, its full backup is in place with the size it had), and is test-restored together with its full backup for the first differential of each full backup and again whenever the last such test is seven days old. wslbak verify <id> does the real test restore whenever you ask. Switching the test restore off switches the read-back off with it: with --verify none, and for a run with --no-verify, a differential is not checked at all. And while test restores are on, a full backup that was made with --no-verify is not built on; the next run makes another full backup.

  • Keeping. --keep and the weekly and monthly rules count every backup, full or differential, as one. A full backup is never deleted while a differential that passed its check still needs it, so a few more files than --keep says can be there.

  • What it writes inside the distro. To know what is there now, tar first walks the whole tree without opening any file and leaves a list of the names. That list is a temporary file under /run (or /dev/shm where /run is not kept in memory), so not on the distro's disk; only root can read it, and it is deleted as soon as wslbak has read it. A run makes one to three such lists, one at a time. A full backup made in this mode keeps the part of the list it needs later, the inode of every directory, as <id>.dirs.gz.

The test restore

After a backup is written, wslbak imports it with wsl --import as a temporary distro, runs a check inside it, and unregisters it again. The check confirms that the default user and their home directory exist (when the default user is not root), that 512 files, picked at random while the backup was being read, have the same SHA-256 as they had then, and that the eight largest files have the size they had (too big to hash every time, and the ones an importer is most likely to get wrong). Before that, the whole archive is read back and compared with the SHA-256 recorded when it was written. If the importer complains that it could not read part of the archive, the test restore fails even though WSL reports success.

The temporary distro must not come alive: all WSL2 distros share one network namespace, so a faithful copy that boots would start a second set of your services and scheduled jobs, with your credentials. wslbak therefore appends its own /etc/wsl.conf to the end of the stream it imports (the archive on disk is not altered) that turns off systemd, boot commands, Windows drive mounts and interop, and it refuses to start the copy unless that file is the one that ended up in place.

Six files are left out of the comparison: /etc/hostname, /etc/hosts, /etc/resolv.conf and /etc/ld.so.cache, which WSL writes itself when it starts a distro, and /etc/wsl.conf and /etc/wsl-distribution.conf, which wslbak replaces in the copy. WSL writes the host name into the copy as well, and there it is the PC's name, whatever the distro's own wsl.conf asks for. Where /etc/hostname, /etc/hosts or /etc/resolv.conf is a symbolic link (NixOS keeps /etc/hostname in /nix/store), the file behind the link may differ too without the test restore failing.

WSL makes a Start Menu folder for every distro it imports and does not always take it away again; wslbak removes the empty one that its temporary distro leaves behind.

Only verified backups count towards --keep (a differential counts when it passed its check, see Differential backups). Backups that could not be verified are kept separately, at most two, so a run of failures never pushes out your good backups.

Notifications

A failed or unverified backup raises a Windows notification. --webhook <url> adds a second channel: ntfy topics get a plain-text message, Discord and Slack webhook URLs get their own JSON format. The URL is treated as a secret: only its host name is shown or logged (a value that is not a URL at all is shown back to you in the error message). Notifications say what kind of problem occurred but never include file names; the details stay in the log on your PC.

A scheduled run that finds another wslbak still there after 20 hours raises a notification too, because one that is stuck, or a window left waiting at a question, cannot (see Safeguards).

wslbak status exits 1 and says so when the last success is more than two days old, when the task is missing, or when the installed program has disappeared.

Safeguards

  • wslbak never calls wsl --shutdown, wsl --terminate or wsl --unregister on your distros. The one place that unregisters anything only accepts a temporary distro whose name wslbak generated, that is registered at exactly its own folder under %LOCALAPPDATA%\wslbak\verify, and that it left a claim file for. A test checks that no other code path can reach that call.
  • Old backups are deleted one file at a time, and only files that have a wslbak manifest next to them. Other files in the backup folder are left alone, even ones that look like backups. Nothing is deleted in a folder that holds backups written by a newer version of wslbak. The one exception is wslbak's own unfinished files, <id>.tar.gz.partial and the like: what an interrupted run left is removed by the next run.
  • A full backup is not deleted while a differential that passed its check depends on it.
  • restore refuses a name that is in use and a folder that is not empty, for distros and for files.
  • The scripts that run inside your distro never delete anything of yours, and use nothing beyond the shell, coreutils and tar. (With differential backups on, the backup script removes the temporary file it wrote itself for each list of names.) What you type for --path, --into and --exclude never becomes part of a command line that a shell interprets: --path does not enter the distro at all, and the other two arrive as data and are only handed on as single arguments (to tar --exclude=…, and to the commands that check and fill the target folder).
  • File names from inside the distro are shown with control characters made visible, in listings and in the warnings of a backup, so a file with a crafted name cannot send commands to your terminal.
  • Single files only go back into the distro the backup folder belongs to, whatever the records in that folder say. They are unpacked in a folder that only root can enter and moved into place at the end (also when unpacking stopped part-way, so that you can see what did come back), so a user inside the distro who owns the folder above the target cannot redirect them by swapping the target for a link.
  • Distros managed by another program (docker-desktop*, rancher-desktop*, podman-machine-*) and WSL1 distros are refused with a reason.
  • Only one wslbak changes things at a time. A second init, run, verify, config change, restore --path or uninstall exits with code 3 (a scheduled run that finds another one at work simply ends). Commands that only read, and restoring a whole distro as a new one, are not held back.
  • A wslbak that never ends does not go unnoticed. When a scheduled run finds that another wslbak has held things up for 20 hours or more, it does not simply end: it fails with exit code 2 and notifies you, because the one that is stuck cannot do that itself. This is for a wslbak that was started by hand, a window left waiting at a question included. A run started by the schedule cannot last that long: Windows ends it after 12 hours, which is the task's time limit, and nothing is reported then. What shows it is how long ago the last success was, in wslbak status, which warns once that is two days.
  • A WSL that has stopped answering does not leave wslbak waiting for ever. Every WSL command has a time limit. For the backup itself it is ten minutes without any data arriving, counted from the moment the command starts, so also when not even the beginning of the archive comes; for the pass that lists the names in differential mode it is an hour in all. When a limit runs out, wslbak ends the command. A backup that ran out of time is reported as failed; a list that did is followed by a full backup instead of a differential, which makes its own list first. So in differential mode the limits add up: a WSL that does not answer at all is reported after a little over two hours, not after ten minutes. If the command cannot even be ended, wslbak stops waiting for it about half a minute later and carries on in the same way. That last case cannot be produced on demand, so only its logic is tested.

How it works

wsl.exe -d <distro> -u root -e sh -s runs a small script inside the distro. The script runs GNU tar on / with --one-file-system and writes the raw archive to stdout. On the Windows side wslbak compresses it with parallel gzip, hashes it, and writes <id>.tar.gz.partial, which is renamed only when tar ended without an error that matters and the number of bytes received equals the number tar says it wrote. (Files that changed or disappeared while they were read are normal on a running system; they are reported as warnings.)

One field of tar's output is filled in on the way. For a file of 8 GiB or more, GNU tar records the size only in an extended header and leaves the size in the file's own header at zero. The importer that ships with WSL 2.7 (bsdtar 3.7.7) believes the zero: such a file comes back empty, and wsl --import still reports success. wslbak writes the real size into that header as well (and corrects the header's checksum to match), so the archive restores correctly there too. Nothing else is changed, and the archive stays a valid tar.

With differential backups on, the same script is run in two more ways. First with tar writing to /dev/null and --listed-incremental: tar then opens no file and only records, in a snapshot file, every directory with its inode and the names in it. Then, for a differential, with --after-date and the time of the full backup. The snapshot option is used for that listing only; the archives themselves are never in tar's incremental format, which could not be restored without deleting and renaming things inside a distro.

The scheduled task belongs to your Windows account and runs with your sign-in session. It starts a windowless copy of the program kept in %LOCALAPPDATA%\Programs\wslbak, so it does not depend on Node or on anything inside a distro. The task is set to run as soon as possible after a missed start and again ten minutes after you sign in (where Windows does not let your account create the sign-in trigger, only the daily start is set); the program then skips the run if a backup succeeded within the last 20 hours.

Limitations

  • Files that are being written during the backup may be inconsistent in it. There is no snapshot. Databases should be dumped to a file by their own tools; the dump is then backed up reliably.
  • POSIX ACLs are not restored when a whole distro is restored. They are stored in the archive, but wsl --import does not apply them. wslbak run tells you how many files are affected (systemd's journal folders, which are reset at boot, are not counted). Bringing back single files with --path does preserve them.
  • Backups are not encrypted. A backup holds every file of the distro, private keys and password hashes included. Whoever can read the backup folder can read all of it, and whoever can write to it can alter a backup or the copy of the program kept there. wslbak doctor tells you when other accounts on the PC can read the folder, and wslbak config --private restricts it to your account (plus SYSTEM and Administrators). That is not done by default because of what it costs later: after reinstalling Windows your new account is not on the list, and you have to open the folder in Explorer and confirm its question, or use an administrator's terminal, before you can restore. Only verify or restore backups from a folder that nobody else can write to: a test restore runs programs that come out of the backup.
  • Differential backups have limits of their own, besides being new:
    • They trust the distro's clock. tar picks the files whose times are later than the full backup (less five minutes). If the clock is set back between two backups and a file changes during that time, its times can be earlier than the full backup, and the file is missed until the next full backup. wslbak makes a full backup when it notices: when the clock is earlier than at the full backup, and when a folder carries a change time from before the full backup that it did not have then. A file rewritten in place while the clock was back, in a folder where nothing else happened, is not noticed.
    • Only on ext4, the file system WSL gives a distro. Elsewhere every backup is a full one.
    • After an old folder was renamed or moved, the next backup is a full one, however little else changed.
    • A file that changed at all is stored again as a whole, a large database or disk image included.
    • A differential is worth nothing without its full backup. If the full backup is damaged or deleted by hand, its differentials cannot be restored as a distro. list marks them once the full backup's files are gone; damage to its archive shows at verify, at restore, or at the next test restore of the two together. Single files can still be taken out of such a differential's archive with tar, but do it soon: the next run keeps at most the newest two of them.
    • Things that happen while a differential is being made are caught as far as they can be told from what tar saw (see the reasons for a full backup above). One case is not: a folder without sub-folders that is moved in from an excluded path during those minutes is an empty folder in that one restore point. The next run notices it and makes a full backup.
    • restore --path with a folder that has very many sub-folders is slower from a differential restore point than from a full backup. The folders arrive before their files there, so tar has to set their times at the end, and the GNU tar of current distros (1.34 and 1.35, not 1.30 and older) takes time for that which grows with the square of their number: on this PC 3 seconds for 40,000 folders and 12 seconds for 80,000.
  • GNU tar is required inside the distro. A distro with BusyBox tar (Alpine as it comes) or with no tar (openSUSE Tumbleweed as it comes) is refused, with the command that installs it.
  • Only what is on the root file system. A folder mounted from another disk is skipped. wslbak run names it when it holds a Linux file system (ext4, xfs, btrfs and the like) and is mounted outside /mnt/wsl; network shares, FAT and NTFS drives and disks attached with wsl --mount are skipped without a message.
  • The test restore needs free space on the drive holding %LOCALAPPDATA%, up to about the size of the distro's files. When there is not enough, the backup is kept, reported as not verified, and the exit code is 1.
  • The task runs only while you are signed in. A stopped distro is started for the backup.
  • The test restore is a sample. It proves the archive imports, that 512 files are intact and that the largest files have the right size, not that every byte of every file is.
  • Single files can only be brought back into the distro, not straight into a Windows folder. From Windows, open the result at \\wsl.localhost\<distro>\<folder>.
  • Where it has been tested. Developed on Ubuntu 26.04. The end-to-end suite passes against Debian 12 and 13, Ubuntu 20.04, 22.04 and 24.04, Fedora 44, AlmaLinux 8 and 9, Rocky Linux 9 (the build from CIQ that wsl --install offers), Oracle Linux 7, 8 and 9, Arch Linux, openSUSE Tumbleweed, Kali, Gentoo, NixOS and Alpine 3.24 (GNU tar 1.26 to 1.35); with WSL 2.7 and 3.0; on Windows 11 (build 26300), Windows Server 2025 (build 26100) and Windows Server 2022 (build 20348, the generation of Windows 10); and with Avast and with Microsoft Defender's real-time protection switched on, each with a caveat. The checks of differential backups came with 0.2.0 and have run against nine of those distros (Debian 13, Ubuntu 22.04, Fedora 44, AlmaLinux 8, Oracle Linux 7, Arch Linux, openSUSE Tumbleweed, NixOS and Alpine 3.24; GNU tar 1.26, 1.30, 1.34 and 1.35), with both versions of WSL and on all three versions of Windows; the other distros were last tested with 0.1.x. With Avast the suite has passed many times, but see under Troubleshooting what its sandbox did. Defender is switched on in the middle of a job on GitHub's test machines, which otherwise run without it. Of 65 such runs of the suite, 59 passed; in one, a scheduled backup was written but not recorded as a success, and the log that would say why was not kept; and 5 ended because the whole machine froze (it stopped answering and then lost its connection), two of them before wslbak had started. The freezing is put down to those machines, not to wslbak: of 54 machines of the same kind that only set WSL up, with no wslbak on them, one froze as well, while 108 machines that switched Defender on and did not use WSL all kept running. With 0.2.0 the suite has passed there every time it ran. The arm64 executables start and pass the unit tests on arm64 Windows, where no WSL was available to back up. Not tried yet: Windows 10 itself, a PC with Docker Desktop, and distros of 100 GB or more; reports are welcome, as issues.

Troubleshooting

Start with wslbak doctor.

  • Where is the log? %LOCALAPPDATA%\wslbak\wslbak.log. It is in English, except for the messages that are also shown to you, which are in your interface language. Add --debug to see it live.
  • Windows refuses to start the program. The executables are not code-signed. Smart App Control blocks unsigned programs outright, and wslbak cannot run while it is on; AppLocker or WDAC policies can do the same on managed PCs.
  • Antivirus holds the program, or runs a second copy of it. Some products (Avast and AVG, for example) check a program they have never seen before for a while, or run it in a sandbox, and inside a sandbox wslbak cannot talk to WSL. init therefore starts the installed program once while you are there. On a PC with Avast, with executables that had just been built there, two more things were seen. Avast ran an isolated second copy at every start of the program; that copy failed and sent a failure notification of its own, although the real backup had succeeded and neither wslbak status nor the log knew of any failure. And twice, while Avast was doing this, WSL stopped starting Windows programs altogether until wsl --shutdown. Whether Avast treats the released executables the same way is not known. If scheduled backups never run, or you get failure notifications that wslbak status knows nothing about, add %LOCALAPPDATA%\Programs\wslbak to the antivirus exceptions. Do not turn the antivirus off.
  • status says the installed program is missing. Antivirus software may have quarantined it. Restore it from the antivirus history, then run wslbak init again to put the files back.
  • "Cannot write to …" during init. With Controlled folder access on, allow the program in Windows Security or choose a folder that is not protected.
  • .exe files stop working inside WSL ("Exec format error") after another distro stops. This is not caused by wslbak: some distros clear a kernel setting that all your distros share when they shut down. It has been seen with Fedora 44, openSUSE Tumbleweed and Rocky Linux 9. It can show up after a backup because a backup starts a stopped distro, which then stops again. wsl --shutdown fixes it, or, without restarting, from a Windows terminal, with the name of the distro you are working in: wsl -d <distro> -u root sh -c "echo ':WSLInterop:M::MZ::/init:P' > /proc/sys/fs/binfmt_misc/register". (Without -d the command runs in the default distro. If that one is not running, it is started and stops again, which can clear the setting once more.)
  • "cannot start Windows programs right now" inside WSL. wslbak looks at the kernel setting that hands .exe files to Windows before it starts its program, and it is missing or switched off. In a WSL 2 distro a missing one is the case above, and the same command puts it back; wsl --shutdown restores it in either case. In a WSL 1 distro the setting is missing when /etc/wsl.conf has [interop] enabled=false.
  • Inside a WSL 2 distro, one line of error from WSL after about ten seconds, and nothing else. This is what happens when Windows interop is switched off for the distro ([interop] enabled=false in /etc/wsl.conf). In WSL 2 the kernel setting stays in place then, so wslbak's check passes, and WSL itself fails to start any Windows program and prints its own error. With WSL 2.7.13 that was ERROR: UtilAcceptVsock:273: accept4 failed 110 after ten seconds, and the exit code was 1 although wslbak's program never ran: that 1 comes from WSL and has none of the meanings listed under exit codes above. Switch interop back on, or use wslbak from Windows.
  • In Git Bash, --path, --into and files <path> are refused as Windows paths. Git Bash rewrites arguments that look like Linux paths (/home/me becomes C:/Program Files/Git/home/me) before wslbak sees them; wslbak notices and says so. Put MSYS_NO_PATHCONV=1 in front of the command, or use PowerShell, cmd or a WSL shell.

Questions and problems

Found a bug, or something this page does not explain? Open an issue at https://github.com/Boring206/wslbak/issues. It helps most to include the output of wslbak doctor, the version (wslbak --version) and the relevant lines of %LOCALAPPDATA%\wslbak\wslbak.log. For anything that may be a security problem, please use the private form that SECURITY.md describes, not a public issue.

Development

Requires Go 1.26 or newer and Node.js. See CONTRIBUTING.md for the rules the code keeps to, docs/ARCHITECTURE.md for how it is put together, and SECURITY.md for reporting a vulnerability.

npm test         # go vet plus unit tests
npm run build    # builds the four executables in bin/ (and a helper for the tests)
npm run e2e      # end-to-end tests inside WSL, against a throwaway distro and a sandbox folder
npm run dist     # release zips, checksums, and winget and scoop manifests in dist/

bash scripts/demo.sh makes the tour at the top of this page again: it installs the packed npm package and a demo distro, runs the commands for real (the real settings folder, scheduled task and default folders, so it refuses to start on a PC where wslbak is set up), records what they print, draws the recording and removes what it created. It needs npm run build to have run, python3 inside WSL, a Python with Pillow on Windows and, for the video file, ffmpeg. It takes a quarter of an hour, six minutes of which it waits: the demo distro has to be older than the five minutes a differential reaches back before its full backup.

npm run e2e creates a Debian distro named wslbak-e2e-<random> on first use; set KEEP_E2E_DISTRO=1 to keep it for the next run, and remove it with scripts/e2e-distro.sh destroy. Set E2E_DISTRO to test another family (FedoraLinux-44, archlinux, openSUSE-Tumbleweed, alpine, …), or E2E_ROOTFS_URL to import a root file system that wsl --install does not offer. E2E_FAST=1 leaves out the parts that only exercise Windows, and E2E_TOAST=1 adds a check that shows a real notification. Three more scripts are run by hand: scripts/e2e-scale.sh (millions of files, a file over 8 GiB), scripts/e2e-services.sh (a busy Docker Engine and SQLite databases that are being written) and scripts/e2e-tarfacts.sh (what differential backups take for granted about GNU tar, ext4 and wsl --import; it needs python3). The tests never touch another distro or your real wslbak settings.

The tests reach a few situations through switches that only work together with the sandbox option --home: the WSLBAK_TEST_… environment variables in testhooks.go. They do nothing in normal use. When developing inside WSL without Go installed there, the build script falls back to go.exe on Windows; set the GO environment variable to point somewhere else.

The program's interface text lives in i18n.go, once per language. Add or change both when you touch a message; the tests check that nothing is missing. (The npm launcher, bin/wslbak.js, has a few messages of its own.)

License

MIT

Documentation

Overview

wslbak 替 WSL distro 做排程備份:不停機、備份完自動試還原、失敗會通知,並能一行還原。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL