- contribute
- build
- Early patching an image
The goal of this page is to give you hint on how to speed up your development by avoiding to build an image. Rebuilding an image is unfortunately very slow and resource-intensive, which makes the Developer Experience unpleasant.
The idea of early-patch is very simple: at boot time, if a specific boot option is added, a directory is shared
between the host and the Tails VM, and mounted at /mnt/tails.git/
. Since mounting in itself is not very useful,
you can configure a program to run at initramfs-time.
Setup
Please add this to your domain definition, inside the <devices>
tag:
<filesystem type='mount' accessmode='passthrough'>
<source dir='/path/to/your/tails/checkout/'/>
<target dir='tails.git'/>
<readonly/>
</filesystem>
The word tails.git
is a magic constant. Do not change it.
The <readonly/>
option must be dropped if you want to use the
rw-includes
feature (see config/patch/rw-includes/README.md
).
virtiofs
When the virtiofs
option is used, add
<driver type="virtiofs"/>
to the above filesystem share configuration.
Usage
For basic usage, add early_patch
or just patch
to the kernel
cmdline when booting Tails. This will run the default
hook, whose
goal (with several limitations) is to apply all differences in Tails
Git since the commit the running Tails was built, as if the Tails was
built from the current Git state, among some convenience
features. This should cover most use cases, but you can also write
your own custom hooks.
You can also pass it options like patch=opt1,opt2,...
:
virtiofs
: Mount the filesystem using thevirtiofs
instead of the default9p
. It is much faster, which is nice when e.g. using thedefault
hook and there are many changes in Git. Unfortunately simply adding this device makes it impossible to save snapshots, unlike9p
which does not prevent snapshots as long as the filesystem share is not currently mounted.umount
: Unmount the filesystem share after the hook has executed, so that VM snapshots are possible. Changes the default hook to theumount
hook.<hook_dir>
: Specifies another hook to run than thedefault
hook.
Hooks and scripts
A hook is a directory under config/patch/hooks
which contains
scripts that are run in lexicographical order during initramfs
time. They are run (through chroot
) in the live filesystem that
Tails boots into, so they can for instance not affect the initramfs
environment. Their executable bit must be set.
The config/patch/lib/lib.sh
shell library has several useful
functions, but can only be used by bash-scripts:
#!/bin/bash
set -eu -o pipefail
shopt -s inherit_errexit
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "${DIR}/lib.sh"
# ... rest of the script ...
The default hook
This default is intended to cover most developer needs. The main goal is to attempt to make the running Tails as close to an image that was built from the current Git state as possible by bind-mounting in the changed files, which also allows rapid code iteration since any changes to these files in your Git checkout are immediately live.
It consists of the following scripts, run in this order:
01-install-packages
Installs all .deb
s from config/patch/packages/
, see
config/patch/packages/README.md for details.
Examples of useful packages to drop in here are:
d-feet
for debugging DBus stuffdconf-editor
to explore dconf settings
03-bind-new-includes
- All changed files (compared to the commit the running image was
built from) inside
config/chroot_local-includes
are bind-mounted into the Tails filesystem, including uncommitted and untracked changes. - All patches in
config/chroot_local-patches
are reapplied. - It does various clever things in order to make certain changes from
bind-mounts have their intended effect:
- Re-runs
dconf update
if there are changes toconfig/chroot_local-includes/etc/dconf
. - Re-runs
config/chroot_local-hooks/52-update-systemd-units
if it has changed. - Applies changes to
.in
files to the files they are input for (e.g. you can hack on.ui.in
files and the changes will be applied to the.ui
file).
- Re-runs
Limitations
- If you have booted with this hook and modify a new file in Git they
are of course not automatically bind-mounted, but you can re-run
/mnt/tails.git/config/patch/lib/bind-new-includes
to make that happen. It is not perfect, and it will not un-bind files that no longer are changed, so the safest is always to reboot withearly_patch
again. - There are issues with permissions and ownership that are good to be
aware of:
- Most bind-mounted files will be owned by root inside Tails, which
can cause issues for files that need a specific owner (like
/etc/tor/torrc
). The only exception to this are files under/home/amensia
which will be owned by theamnesia
user. - Writing to the bind-mounted files from inside Tails requires that you grant write permissions to the source file in your Git checkout to the libvirt-qemu user.
- Most bind-mounted files will be owned by root inside Tails, which
can cause issues for files that need a specific owner (like
- This hook doesn't import other types of changes, notably:
- Changes to the
config/chroot_local-hooks
build hooks, except the bits mentioned above. - Changes to the initramfs.
- Changes to
config/chroot_local-packageslists
(but you can manually do it through01-install-packages
).
- Changes to the
- The special treatment of
.in
files doesn't work for all kinds of.in
files. As an example.desktop.in
files do not work properly because the translatable keys are prefixed with an_
.
05-bind-rw-includes
The files inside config/patch/rw-includes
are bind-mounted into and
are writable from the running Tails' filesystem, allowing you to make
arbitrary files persistent, like your shell history. See
config/patch/rw-includes/README.md for details.
Limitations
- Bind-mounted files cannot be "atomically saved the unix way" since
they cannot be renamed/moved, so unfortunately files used by
programs that does that will have problems, like the Python
interactive shell history (
~/.python_history
).
99-rootpw
Sets the root password to "root", in case you forgot (or didn't anticipate that you needed) to set an administration password.
.local scripts
If you want to modify the stock default
hook (or the umount
hook,
see below) you can do so by adding scripts into its hook directory
with the .local
suffix in their filenames. They are ignored by Git.
The umount hook
VM snapshots are not supported while a (9p
) filesystem share is
mounted, which is a limitation for the default
hook. This hook
attempts to achieve what the default
hook does but by copying all
files instead of bind-mounting them, so that the filesystem share can
be unmounted (through the umount
option).
(Since the automated test suite uses VM snapshots heavily its
--early-patch
option runs this hook.)
Custom hooks
To create your own hook, simply create a directory $dir
in
config/patch/hooks
and put scripts with the executable bit set in
it, and they will be run if you add patch=$dir
to the kernel
cmdline. These directories are ignored by Git.
There are several useful shell functions you can import from the
config/patch/lib/lib.sh
library, like copy_include()
and
bind_include()
. Have a look!
Known issues
- Changes applied to the root filesystem with
early_patch
are ineffective for the Unsafe Browser: #20461.