Supporting rootless builds
==========================

Status: recommendation, stable
Version: 1.0

Background
----------

Traditionally, Debian packages have required (fake)root privileges for some
of the "debian/rules" targets. This has required also a split between build
and binary targets; making the builds slower, due to the increased amount
of invocations of "debian/rules" and the overhead of using fakeroot(1) or
equivalent fake environments, or less secure due to the increased dangers
of running under real root via sudo or equivalent.

On this document when talking about "(fake)root" privileges, it will refer
to any mechanism, supported by the dpkg-buildpackage "-r/--root-command"
option, that can provide either a real or faked root user environment.

Specification
-------------

We add a new field to the "Source" stanza of debian/control:

  Rules-Requires-Root: no | binary-targets | <implementations-keywords>

The values are defined as:

 * If "no", then "debian/rules binary" will not require root at all (not even
   fakeroot).
   - If the "no" keyword is used, it MUST be the only keyword in that field
     and MUST appear exactly once.

 * If "binary-targets", then "debian/rules binary" (etc.) must always be run
   under (fake)root. This is the default/status quo.

 * <implementations-keywords> will be a space separated list of keywords which
   define when root is required.

   - Keywords consists of <namespace>/<cases>. The "namespace" part cannot
     contain "/" or whitespace. The "cases" part cannot contain whitespace.
     Furthermore, both parts MUST consist entirely of printable ASCII
     characters.

   - Each tool/package will define a namespace named after itself and provide
     a number of cases where (fake)root is required.
     (See also "Implementation provided keywords".)

   - When "Rules-Requires-Root" is set to <implementations-keywords>, the
     builder will expose an interface that is used to run a command under
     (fake)root via the "Gain Root API". If the builder cannot provide such
     a command, it MUST behave like "Rules-Requires-Root" was set to
     "binary-targets", i.e. run "debian/rules binary" under (fake)root.

When the builder supports this specification, it MUST notify this fact to
the rules file via the "DEB_RULES_REQUIRES_ROOT" environment variable, with
the value it has obtained from the Rules-Requires-Root field or some builder
specific override mechanism, which will denote the level of support the
builder has choosen to commit to take effect during the build. When set,
it MUST be a valid value for the Rules-Requires-Root field. If unset,
the build system SHOULD assume that the builder does not recognize the
Rules-Requires-Root field at all.

It is always permissible for a builder to ignore this field and fall back to
running the binary targets under (fake)root. This is to ensure backwards
compatibility when builds are performed by legacy builders or older versions
of the tooling.

Tools called from the rules file MUST cope gracefully with being called under
(fake)root even when Rules-Requires-Root is set to a value that implies they
should not be (e.g. "no"). However, they MUST NOT attempt to run processes
under (fake)root when run as a regular user when Rules-Requires-Root does
not list any keywords they respond to.

Tools MUST gracefully ignore valid unknown keywords outside their namespace.
They MAY warn about unknown keywords inside their namespace.

The value of this field MUST NOT change the content of the package in any
way. Notably, packages that are bit-for-bit reproducible MUST still provide
bit-for-bit identical results even when the field is ignored.

Implementation provided keywords
--------------------------------

Keywords provided by various implementations:

 * dpkg/target-subcommand: When the package needs to run a given command
   under (fake)root within the "debian/rules" files directly, this MUST be
   declared via this keyword.

 * dpkg/target/<target-name>: When a specific "debian/rules" unofficial
   target (none of the root-requiring "binary-indep", "binary-arch", "binary",
   "clean", nor the non-root-requiring "build-indep", "build-arch", "build")
   needs to be run under (fake)root, this MUST be declared via this dynamic
   keyword, where <target-name> is the name of the "debian/rules" target.

 * debhelper/upstream-make-install: The dh_auto_install command will run
   the "install" target from the upstream's Makefile under (fake)root (for
   the "makefile" build system or one derived from it).

Gain Root API
-------------

The builder will provide a command to promote a given command to (fake)root
by exposing it in the environment variable "DEB_GAIN_ROOT_CMD". Tools that
need this promotion will then use it like the following:

    $DEB_GAIN_ROOT_CMD cmd-that-needs-root ...

This command is subject to the same requirements as the "gain-root-command"
that dpkg-buildpackage accepts via its "-r/--root-command" option, which
means that it can contain space-separated parameters. If dpkg-buildpackage is
called with "-r/--root-command", then dpkg-buildpackage shall use that value
as the value for "DEB_GAIN_ROOT_CMD". The command SHOULD preserve all the
environment variables, unmodified.

The variable SHOULD only be provided when there is a need for it. Notably
when "Rules-Requires-Root" is either "no" or "binary-targets" the variable
SHOULD NOT be defined.

(The "DEB_GAIN_ROOT_CMD" variable used to be named "DPKG_GAIN_ROOT_CMD"
starting with dpkg 1.19.0 and before dpkg 1.19.1 when this specification
got released as stable. The old name MUST not be used.)

Common cases
------------

 * Upstream installation insists on "sudo make install"-like behaviour.
   => Use dpkg/target-subcommand or debhelper/upstream-make-install.

 * Files shipped in the package must be owned by another user than root.
   => Not covered; use "binary-targets" for now until dpkg+debhelper
      provides the required interface.

Prototyping/preparation
=======================

dpkg side
---------

dpkg-deb --build must either default to resetting all owner/group values to
0:0 when not run under (fake)root OR provide an interface so dh_builddeb can
provide the owner/group value to dpkg-deb --build.

dpkg-buildpackage must export DEB_GAIN_ROOT_CMD (for starters, doing this
unconditionally would be fine).


debhelper side
--------------

When the field is present:

 * dh_testroot will behave as usual when Rules-Requires-Root is not present
   or set to "binary-targets".

 * dh_testroot will be a no-op when Rules-Requires-Root is set to "no".

 * Otherwise, dh_testroot will either verify that it is run under (fake)root
   (as usual) OR assert that DEB_GAIN_ROOT_CMD is defined.

 * debhelper build systems will be patched to check for the
   "debhelper/upstream-make-install" keyword and use the "Gain Root API"
   accordingly.

 * All other (src:)debhelper commands will skip their calls to chown
   (currently they just reset them to "0:0" anyway).

With the above, a default "dh $@" will no longer require (fake)root when
built (and Rules-Requires-Root is "no").

Prototyping:

 * During prototyping, dh_builddeb can wrap the dpkg-deb --build call with
   fakeroot (when not already root).
