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.
[TOC]
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:
- Not compromise base images when dev mode is disabled.
- Allow developers to configure a base image when dev mode is enabled such that it looks as much like a developer image as possible.
- Not need to modify the rootfs in any way.
- 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.
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.
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.
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.
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/dev-install.sh
: Helper script for shells in dev mode to pass along the environment when using plainsudo
. Loaded by every interactive bash shell. A bit of a hack. -
/etc/env.d/99devinstall
: Adds the/usr/local/lib*
paths to theLD_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/ld.so.cache
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 runemerge
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 duringbuild_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.tar.xz
: Everything in/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 bydev_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
: Thevirtual/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.
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.
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.
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
.
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
.