tree: bf82c5c4784fa1fb6445912f6704d23c150987c6 [path history] [tgz]
  5. dev_install.h
  8. share/

Development Image Installer

The dev-install tool is used to install developer tools on a release image. This is most used by external CrOS users who want to take their existing CrOS system, put it into dev mode, and then quickly install a bunch of useful tools.

It‘s generally intended to create the equivalent of a dev image that one would build using ./build_image ... dev. We aren’t able to fully recreate that environment currently, but we get close.

It is not meant to create a fully standalone environment where one can build new packages from source and have them installed. For that, people should look into Crouton or Crostini instead.

Note: When we refer to the “stateful” partition, we usually refer to all of the related content that lives in it. The /var and /usr/local paths are actually bind mounted from directories that live in the stateful partition. It's used for other things as well, but dev-install only cares about those two particular paths.


In order to understand why we configure certain paths, we need to understand what environments & constraints we operate under.

There are basically four possible environments we need to consider:

  • Release (base) images where dev mode is disabled.
    • We have strong incentives to keep the rootfs small.
    • We want to minimize unused packages and programs (e.g. for security).
    • Nothing developer related should execute here (to maintain security).
    • The stateful partition is largely empty and may be reset or cleared.
    • /usr/local is never mounted.
  • Release (base) images where dev mode is enabled.
    • Executes with the same exact code/binaries as the image above.
    • The rootfs is still read-only and remains that way.
    • Some scripts might change behavior by detecting dev mode state (e.g. based on crossystem "cros_debug?1").
    • /usr/local is mounted with exec permissions.
      • It is initially empty, but state is retained across reboots.
    • Still no services automatically start up (e.g. no ssh access).
    • /var/db/pkg (that tracks installed packages) is not available.
  • Developer images (created via ./build_image dev).
    • Starts off as a copy of a base image.
    • Extra packages (virtual/target-os-dev) are installed into /usr/local for developers to help with ad-hoc testing & verification.
    • Some rootfs modifications are made to the rootfs to allow extra tools under /usr/local to work correctly.
    • A few config settings are enabled to indicate it's a developer image (e.g. enable saving of coredumps from crashes).
    • /var/db/pkg (that tracks installed packages) is available.
  • Test images (created via ./build_image test).
    • Starts off as a copy of a dev image.
    • More packages (virtual/target-os-test) are installed into /usr/local in order to run automated testing (e.g. autotest & tast).
    • Some packages are installed into the rootfs.
    • More rootfs modifications are made to make it easy to access the system (e.g. sshd is always started, and a known password & ssh key are added).


It's important to emphasize that dev-install is only for helping developers get easy access to various developer tools when their device is in dev mode. We must not sacrifice security or stability of the system otherwise. That is why we strive to have tools be disabled/ineffective normally.

On a non-dev mode system, the /usr/local path is never mounted. This allows us to create dangling symlinks in the rootfs that point to paths under /usr/local without introducing security problems. Even then, we try to keep this to a minimum.

If we refer back to the Environments section, dev-install is designed to:

  1. Not compromise base images when dev mode is disabled.
  2. Allow developers to configure a base image when dev mode is enabled such that it looks as much like a developer image as possible.
  3. Not need to modify the rootfs in any way.
  4. Not try and recreate a test image.

In order to achieve these goals, dev-install ends up configuring the base image in a way that any changes needed in the rootfs are always available there, and not just when creating a dev image.

Developer Image Creation

When a developer runs ./build_image ... dev, the build system will first create a “base” image which is equivalent to a release image. The GPT layout is initialized as are the various partitions (kernel, rootfs, EFI, stateful, etc...). The important part here is that the rootfs is not modified at all from a normal release setup, and that the stateful partition is empty. Most notably, /var and /usr/local will be empty. Everything in the depgraph of virtual/target-os is installed into the rootfs.

When the dev image is created, a copy of the base image is created, and then a lot of extra packages are installed automatically into the stateful partition. This means /usr/local will be initialized with a lot of packages -- everything in the depgraph of virtual/target-os-dev (except what's already installed into the rootfs). We also modify the rootfs a bit to add symlinks to paths under /usr/local.

We'll assume that test images are basically the same as dev images, except they have even more things installed, and some modifications to the rootfs are made. For example, a well known password & key are installed for automatic ssh access.

Prebuilt Creation

During build_image, we generate the set of installable packages. See create_dev_install_lists for exact implementation details. Roughly, it builds a list of all the packages by taking the depgraphs of virtual/target-os-dev & virtual/target-os-test and subtracting the depgraph of virtual/target-os (since those packages are installed in the rootfs). That list is saved at /build/dev-install/package.installable.

Release builders run the DevInstallerPrebuilts stage. This reads the set of packages (created above) and uploads their binpkgs to the gs://chromeos-dev-installer bucket.

That bucket uses the layout gs://chromeos-dev-installer/board/${BOARD}/${OS_VERSION}/. For example, gs://chromeos-dev-installer/board/eve/12763.0.0/.

For local developer builds, the full URI is written to CHROMEOS_DEVSERVER in the lsb-release file and used by the dev-install command at runtime. The URI will point to the local developer's system, not the release bucket.

For release builds, the value is computed on the fly using lsb-release.

This is a normal Portage binpkg host repository.

Image Layout

Depending on whether the rootfs is a base or dev image, the set of available paths will be different. This tries to document the current state of the world, not to say that the status-quo is perfect (we know it's not) or otherwise immutable.

Base Image

All symlinks on the system at this point are from the dev-install package. Once that‘s installed, we also generate package lists and a tarball of the existing /uar/local state (otherwise we’d discard it).

Paths installed by the dev-install package:

  • /etc/bash/bashrc.d/ Helper script for shells in dev mode to pass along the environment when using plain sudo. Loaded by every interactive bash shell. A bit of a hack.

  • /etc/env.d/99devinstall: Adds the /usr/local/lib* paths to the LD_LIBRARY_PATH env var so that extra programs and their libraries that are installed under /usr/local can find them automatically. Normally only libraries listed in /etc/ are automatically loaded, and that cache only includes files that are in the rootfs. This file is added to /etc/profile.env which in is loaded by /etc/profile which is only loaded by interactive shells. Since non-dev mode devices should never have any interactive shells run on them, this is OK.

  • /usr/bin/dev_install: The main program to download & install packages on the fly into /usr/local. This is run manually by end users when they put their device into dev mode.

  • /usr/share/dev-install/portage/: Various files used to provide a simple stub Portage profile. This allows people to run emerge to install the various binary packages we make available.

    • make.profile/: A minimalistic Portage profile needed to install the binpkgs. Dynamically loads settings from /usr/local/ as needed.
      • make.defaults: The main profile file.
  • /etc/portage/: This is symlinked to /usr/share/dev-install/portage/ on the device. We skip this in the build sysroot as that will be the normal “full” profile used to compile everything in the SDK.

  • /usr/local/etc/portage/make.profile/parent: A stub profile that inherits from the rootfs profile. This allows users to install extra settings after the system has been set up. Only created during build_image time.

Paths created on the fly by build_image:

  • /build/dev-install/package.installable: Not actually installed on the device, just created on the fly for use by the build system to determine which binpkgs to upload to the server.
  • dev-only-extras.tbz2: All the content of /usr/local (except for /var).
  • /usr/share/dev-install/: The list of packages used by the device are computed at this point as the image has been finalized.
    • bootstrap.packages: The set of packages to manually install in order to bootstrap Portage. Once these are installed, emerge is used to install all the rest of the binpkgs.
    • rootfs.provided/: For packages that are baked into the rootfs, we save different lists in this directory. This is used by dev_install at runtime to mark them all as provided. We have to do this since the /var/db/pkg metadata is not available.
      • chromeos-base.packages: The virtual/target-os packages.

Various python & portage symlinks are created in the rootfs to point to paths under /usr/local. This is important for scripts that hardcode #!/usr/bin/python in their script shebangs, and for packages that hardcode the path at compile time. We install these into the base image so they work with dev_install too.

  • /etc/env.d/python: Python config.
  • /usr/bin/python*: Python programs.
  • /usr/lib/python-exec: Gentoo Python wrapper.
  • /usr/lib/portage: Portage runtime files.
  • /usr/share/portage/: Portage settings.

Dev Image

We install a set of additional symlinks and tweak files for dev images.

  • /: A few additional packages from chromeos-base/chromeos-dev-root are installed to the rootfs.

    • usr/lib/debug: A symlink to /usr/local/usr/lib/debug so debug files can be found if available.
  • /usr/local: All the virtual/target-os-dev packages are installed here.

    • etc/passwd: Symlink to /etc/passwd so dev-only packages which add accounts can update the rootfs settings.
    • etc/group: Same as above.
    • etc/pam.d/: Same as above.

There are a few other files that get adapted, but they don‘t have bearing on the dev-install process, so we’ll ignore them.

Supported Workflows

Here we outline the expected workflows for developers and what is supported. Other flows might work, but only the ones detailed here need to be verified.

Base Image

We expect people to use dev-install to install the set of binary packages and initialize the /usr/local tree. Once it's finished running, emerge will be available to install more. It needs to be able to bootstrap from an empty /usr/local tree.

The /var/db/pkg will not be available, so packages installed in the rootfs won't be possible to upgrade.

Developers may use cros deploy to install additional packages, or upgrade any existing ones as long as they were installed to /usr/local.

Developers may also use other cros helpers as makes sense e.g. cros flash.

Dev Image

A normal dev image will already have the stateful partition set up.

It will have the full /var/db/pkg tree available that reflects the state of the rootfs.

Developers may use cros deploy to upgrade packages in the rootfs as well as /usr/local, and may install additional packages to either path.

Running dev-install is not required. If the stateful is wiped (for any reason), then dev-install may be used to reinstall the developer packages. However, the /var/db/pkg path is not restored, so this state will be more like a base image rather than a dev image.

Developers may also use other cros helpers as makes sense e.g. cros flash.