# Technical Documentation: ifd3f/caligula

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/ifd3f/caligula)

### Overview (`README.md`)

# Caligula Burning Tool

[](https://github.com/ifd3f/caligula/actions/workflows/ci.yml)



_Caligula_ is a user-friendly, lightweight TUI for imaging disks.



```
/* Detailed source-code truncated for AI context efficiency. */
```



## Features

- **Cool graphs** that show you how fast you're writing
- **Listing attached disks**, and telling you their size and hardware model information
- **Decompressing** your input file for a variety of formats, including gz, bz2, and xz
- **Validating your input file against a hash before burning**, with support for md5, sha1, sha256, and more!
- **Running sudo/doas/su** if you forgot to run as `root` earlier (it happens)
- **Rich confirmation dialogs** so you don't accidentally nuke your filesystem
- **Verifying your disk after writing** to make sure it was written correctly
- **Small binary size** of <5 megabytes, even when statically linked
- Did I mention _**cool graphs**_?

## How to install

[](https://repology.org/project/caligula-burning-tool/versions)

There are a couple of ways to install Caligula.

- **Binary release:** You can download pre-built binaries from [the latest Github release](https://github.com/ifd3f/caligula/releases/latest).
- **Arch Linux:**
  - [Official repository](https://archlinux.org/packages/extra/x86_64/caligula): `pacman -S caligula`
  - [caligula-bin on the AUR](https://aur.archlinux.org/packages/caligula-bin): We also automatically publish binaries with every release.
  - [caligula-git on the AUR](https://aur.archlinux.org/packages/caligula-git): Build from latest commit on `main` branch
  - [caligula-git on archlinuxcn](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/caligula-git): Prebuilt binaries from latest commit on `main` branch
- **Nix:**
  - [Nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ca/caligula/package.nix): `nix-env -i caligula`
  - Repository flake: If your system is flake-enabled, you can use `github:ifd3f/caligula`. You should probably pin to a version, though, because `main` can potentially break.
- **Homebrew**: [philocalyst has made a homebrew tap for caligula](https://github.com/philocalyst/homebrew-tap): `brew tap philocalyst/tap && brew install caligula`
- **Cargo:** Caligula is published on [crates.io](https://crates.io/crates/caligula). Just run `cargo install caligula`
- **Build from source:** This is a relatively standard cargo project so you should be able to just `git clone` and `cargo build --release` it.

### Platform support

| OS    | Architecture | Automated builds | Basic tests | Full tests |
| ----- | ------------ | ---------------- | ----------- | ---------- |
| Linux | x86_64       | ✅               | ✅          | ✅         |
|       | aarch64      | ✅               | ❌          | ❌         |
| MacOS | x86_64       | ✅               | ✅          | ❌         |
|       | aarch64      | ✅               | ✅          | ❌         |

Notes on definitions:

- **Automated builds** means we ensure that the platform is able to build inside CI and we publish binaries for the platform. This does not guarantee that Caligula works at all!
- **Basic tests** means we run unit tests, and test that `caligula --help` works. Caligula is _likely_ to work on this platform, but there is potential for uncaught issues between releases.
- **Full tests** means that, in addition to the basic unit tests, we also run the complete end-to-end VM test suite battery defined in the [checks/](./checks) directory. Caligula is expected to fully work on this platform.

#### Platform notes

- In theory, Linux for non-x86_64 does work, and people have used it successfully. However, we are not making any guarantees right now. It is in the works, though -- see tracking issues for [aarch64 e2e tests](https://github.com/ifd3f/caligula/issues/220) and [riscv64 support](https://github.com/ifd3f/caligula/issues/221).
- MacOS has a couple of known bugs and support for it is very limited. A big barrier is that setting up end-to-end testing for it is hard (see [tracking issue](https://github.com/ifd3f/caligula/issues/219)), and [automated escalation being broken is a known issue](https://github.com/ifd3f/caligula/issues/125). Help would be appreciated here!
- We plan on supporting [Windows](https://github.com/ifd3f/caligula/issues/2) and [BSDs](https://github.com/ifd3f/caligula/issues/3) Eventually™.
- If you would like support for other OSes and architectures not already mentioned, please file an issue!

## FAQ

### Why did you make this?

Because I had to image one too many USB drives and wanted a nice, user-friendly `dd` alternative that wasn't ~~90~~ ~~95~~ **413 MB.**

No really. A certain other tool doing the same exact thing has ballooned to 413 MB now.

```
% unzip balenaEtcher-linux-x64-2.1.4.zip
% du -sh balenaEtcher-linux-x64
413M	balenaEtcher-linux-x64
```

### Why is it called "Caligula"?

There used to be a tool called Nero Burning ROM, so I chose another crazy Roman emperor to name this software after. It's a very uncreative name. I was originally planning on changing it later, but it's stuck.

### Why is `dd` not good enough for you? Do you not know how to use it?

I know how `dd` works. In fact, to prove it, I have written a tutorial on it here, without using any AI.

#### How most people use `dd` to write ISOs to a USB drive or SD card

First, take a hash of your file, just to make sure that it didn't get corrupted in transit or bitrotted while living on your disk, because that happens occasionally. Most people forget to do this step, or are too lazy to. Most tutorials neglect to mention this step as well.

```sh
$ sha256sum some-image-file.iso.gz
```

After you have verified that the hashes match, you can finally unzip your file, if it came zipped.

```sh
$ gunzip some-image-file.iso.gz
```

Once that's done, you can finally start typing out your `dd` command. Using `bs=4M` because someone online recommended it and you forgot why, you type:

```sh
$ dd bs=4M if=some-image-file.iso of=/dev/
```

You get to this point in typing it out before realizing you forgot to consult `lsblk`!

```sh
$ lsblk
```

```
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0   1.8T  0 disk
└─sda1        8:1    0   1.8T  0 part
sdb           8:16   1   3.6G  0 disk
└─sdb1        8:17   1   3.6G  0 part
sdc           8:32   1    60G  0 disk
nvme0n1     259:0    0 931.5G  0 disk
├─nvme0n1p1 259:1    0   550M  0 part /boot
└─nvme0n1p2 259:2    0   931G  0 part
```

You probably want the disk here either called **sdc.** Make sure you _don't confuse it with any of the other disks plugged into your computer, removable or otherwise._ That would be bad, because you might overwrite important data.

```sh
$ dd bs=4M if=some-image-file.iso of=/dev/sdc
```

Pause here _one more time,_ cross referencing what you typed in with the output of `lsblk`, to _double-confirm_ that you are indeed typing in the **correct disk** and not the **wrong disk** so that you don't **nuke any important disks storing important data such as your /home or your OS.**

Then, open a new terminal and type

```sh
$ lsblk
```

again to **triple-confirm** that you are indeed typing in the **correct disk** and not the **wrong disk** so that you don't **nuke any important disks storing important data such as your /home or your OS.** Doing so would **really ruin your day.**

Once you have **quadruple-confirmed** that you are indeed typing in the **correct disk** and not the **wrong disk** so that you don't **nuke any important disks storing important data such as your /home or your OS**, you can hit the enter key and finally actually run `dd`.

```
dd: failed to open '/dev/sdc': Permission denied
```

Of course, you probably forgot to type sudo. Make sure you do that.

```sh
$ sudo dd bs=4M if=some-image-file.iso of=/dev/sdc
```

By default, `dd` does not have any output. If you want to see the progress, you will need to cancel the command and add `status=progress` on.

```sh
^C^C^C
$ sudo dd bs=4M if=some-image-file.iso of=/dev/sdc status=progress
2684354560 bytes (2.7 GB, 2.5 GiB) copied, 1 s, 2.7 GB/s
```

Now, it's finally written!

At this point, it's probably a good idea to verify that the disk was written correctly. I personally don't know the command to do that. Do you know the command to do that? If you ask most pro sysadmins this, could they name the command? If you look at any tutorial online, do they list the command? **The answer to all of these is no, because nobody bothers to do this.**

While `dd` is nice for scripting, after doing this process manually hundreds of times with hundreds of files and tens of drives, why don't you try using a simpler, more user-friendly process?

#### How most people use `caligula` to write ISOs to a USB drive or SD card

Typically, you would run

```
$ caligula burn some-image-file.iso.gz
```

and follow prompts in the terminal to allow the computer to fill in the blanks. In general, computers are good at filling in blanks. In fact, that was a big part of why we invented them.

### Why Rust?

Because it's 🚀🚀🚀 BLAZING FAST 🚀🚀🚀 and 💾💾💾 MEMORY SAFE 💾💾💾 and 🦀🦀🦀 CRAB 🦀🦀🦀

On a serious note, I just like the language.

### Why Nix?

It makes the CI more predictable.

### Why so many other dependencies?

To be fair, Rust doesn't have a very comprehensive standard library, and I only use one or two functions in most of those dependencies. Thanks to dead code elimination, inlining, and other optimizations, they don't end up contributing much to the overall binary size.

### Why do you have to type in `burn`? Will you add other subcommands later?

Yes. I Eventually™ plan on adding other capabilities, like [Windows install disk support](https://github.com/ifd3f/caligula/issues/14) and [secure disk erasure](https://github.com/ifd3f/caligula/issues/195), and those will end up in their own subcommands.

### Why does it take so long for new things to be added?

I have a full-time job that is not working on Caligula. If you would like to help with this problem, [contributions are appreciated](./CONTRIBUTING.md).

---

### Overview (`checks/README.md`)

# Integration tests

This directory contains integration tests and other checks (Nix-based). Unit tests are embedded in `/src`.

---

### Overview (`nix/devvm/README.md`)

# Developer VM

This command runs a VM in your terminal and mounts your PWD to `/tmp/shared` inside the VM. 

This should be run from the root directory of this project in order to work correctly.

```sh
nix run .#devvm-aarch64-linux 
```

You may have to cross-compile using a remote machine. In that case, you can put builders in like so ([see Nix docs for more info](https://nix.dev/manual/nix/2.18/advanced-topics/distributed-builds)):

```sh
nix run --builders 'ssh://astrid@your-host.example aarch64-linux' .#devvm-aarch64-linux 
```

Note that you may or may not have to `sudo` cargo commands inside the VM.

## USB Hotplug

There is a helper script for adding and removing USBs from the VM.

```sh
nix run .#devvm-usbhotplug -- add foo 10M
```

```sh
nix run .#devvm-usbhotplug -- rm foo
```

---

### Overview (`packaging/aur/README.md`)

Included here are some scripts of questionable quality for making the AUR build go brrrr.

---

### Testing Checklist (`docs/testing-checklist.md`)

# Testing checklist before release

All are to be done inside developer VM with 100MB files and 1GB USBs.

```bash
scripts/gen_test_files.sh 100000000 test.img
```

We should turn these into E2E tests eventually but for now this has to be done manually.

## Happy path

- [ ] Write normal file, with hash validation and invalidation
- [ ] Write compressed file, with hash validation and invalidation

## Sad path

- [ ] Cancel an operation during setup wizard
- [ ] Cancel an operation right before confirmation
- [ ] Write to a read-only file

---

### Developers Benchmarking (`docs/developers/benchmarking.md`)

# Benchmarking

Caligula is meant to be a high-performance disk tool. Therefore, we have a standard(ish) testing methodology.

There is a benchmarking subsystem under the `caligula bench` subcommand that emulates various hot paths in the program.

`caligula bench run` commands are all very dumb. They only emit a progress bar, and will happily destroy your disks if you tell them to. Actual timing is done by a different program.

Currently, [Hyperfine](https://github.com/sharkdp/hyperfine) is what we use. It's fully manual for the time being, but we do have plans to eventually introduce automated performance regression testing in CI, at least on pull requests.

To compare hashing performance between two different versions, for example, you can run a command like this:

```sh
hyperfine \
    -L impl ./caligula-0.5.0-rc.2,./caligula-iog \
    '{impl} bench run hash 2023-05-03-raspios-buster-armhf-lite.img.xz -a sha256 -z xz'
```

For your convenience, I've added `/caligula` and `/caligula-*` into the .gitignore.

---

--- METRICS ---
- Files Extracted: 6
- Estimated Token Budget: ~3250 tokens
- Canonical Reference: https://codewiki.google/github.com/ifd3f/caligula
