User setup and erlang installation

A bit fiddly, but this lets us run a realistic erlang install script
from userspace, and then re-enter userspace later without wiping the
installation.
This commit is contained in:
Jarvis Carroll 2025-05-23 21:21:16 +10:00
parent 4f6ee7cc88
commit 28de550295
7 changed files with 204 additions and 13 deletions

118
README.md Normal file
View File

@ -0,0 +1,118 @@
Motivation
==========
We want users to be able to use a variety of erlang programs, which means they
need to know how to install an erlang runtime that can run our programs. On any
given day this will have a well defined answer, but as erlang changes, and as
our dependencies change, the exact installation process might change too. In
order to reliably recreate the experience of a new user, on a variety of
possible distributions, we create a collection of chroot environments, one for
each distribution we want to document, and then use those chroot environments
to develop and maintain install scripts.
These install scripts will check *every* dependency needed, even on a totally
fresh installation of the corresponding linux distribution, because that is
exactly what the chroot environments will be. This means that the install
scripts will work on any install, whether it has been used for a long time, or
whether it is also a fresh install, if you're mining on a VPS, or custom
hardware, or whatever else.
Usage
=====
At the moment there is only one distribution, Debian, which you can test in the
`chroot_sandboxes/debian` subdirectory. From there you can run a variety of
posix shell scripts to create, enter, and delete chroot environments.
Create Environment
------------------
cd into `debian` and run `sudo ./create_environment` to automatically download
`debootstrap` from [debian.org](https://www.debian.org), and create a debian
system with it. If you already have `debootstrap` installed, then that version
will be used instead. `debootstrap` can be installed with `apt`, if you are
already on an `apt`y system. Running `make install` in `debian/debootstrap` is
not recommended, since your distribution's package manager won't be able to
uninstall it for you.
A minimal debian system will be created under `debian/clean_environment`, and
then copied over to `debian/test_environment`. This way if you run
`sudo ./create_environment` again, instead of downloading the whole
distribution again, it can simply overwrite `test_environment` with a new
copy, allowing rapid iteration of install scripts, run on totally fresh
systems every time.
The script also sets up the mount points and /tmp directory in
`debian/test_environment`, each time that it is copied from
`debian/clean_environment`. This means `debian/clean_environment` is always an
ordinary file hierarchy with no mount points, that can be recursively deleted,
whereas `debian/test_environment` needs to be handled more carefully, see
[Destroy Environment](#destroy-environment) for instructions.
Finally, the script will copy all install scripts in `debian/install_scripts`
into the chroot environment, and perform the chroot itself. The chroot is
instructed to run `install_scripts/user_setup` with this new root directory,
and this script will install sudo, create a user with passwordless `sudo`
rights, and `su` into that user. You can then freely test whatever scripts you
want as that user, and leave the environment.
If you don't want to do anything interactive as that user, but instead want to
run a single script and then exit, pass that script and its arguments to
`sudo ./create_environment` and they will be passed down into the chroot
environment, and run instead of the default `/bin/bash` that is normally run
by `su`. Remember that the command will be run inside the chroot environment,
with `/home/user` as the working directory, so the script will need to be
accessed relative to that. e.g.
`sudo ./create_environment ./install_scripts/your_script`
or
`sudo ./create_environment ~/install_scripts/your_script`.
Destroy Environment
-------------------
Because chroot environments require multiple mounted directories to work, you
can't simply `rm -r` a chroot environment you created, or the repository as a
whole, without unmounting the mount points first. If you have rebooted your
machine since setting up the chroot environments, then you don't need to worry,
part or all of the repository can be straight-forwardly deleted, but if you are
working with the repository and want to delete something yourself, there are
two helper scripts that can be used to clean up the mount points and chroot
environments properly.
First `sudo ./destroy_environment` will unmount and delete `test_environment`,
allowing you to remove an old environment without immediately creating a new
one. Anything else in the repository can be straight-forwardly deleted with
`sudo rm -r`, so with this you can put the repository in whatever state you
want it to be in.
If you want to conveniently remove all debian/debootstrap tools added, then
`sudo ./clean_everything` will run `destroy_environment`, and then delete
`clean_environment` and `debootstrap` for you, as well as `debootstrap.tar.gz`
if that got left behind by accident. Think of this as the 'distclean', for one
specific distribution.
Reuse an Existing Environment
-----------------------------
If you want to enter an environment again, run `sudo ./enter_environment`, and
it will chroot into the environment without deleting and recreating it,
without installing `sudo` again, and without creating a new user.
To run a script, just like with `create_environment`, you can pass arguments,
as long as the paths involved are relative to the new root and home directory.
e.g. `sudo ./enter_environment ~/install_scripts/your_script`.
If you reboot your machine, the mount points of the chroot environment will be
missing, (unless you put them in your system-wide fstab, you sicko,) but
`sudo ./enter_environment` will detect this and add the mount points back
automatically.
If you are iterating an install script, then it's usually more useful to just
run the whole thing again using `create_environment`, but if you want to
compose multiple operations together in a script outside of the chroot, or if
you want to enter an interactive environment again after running some more
expensive script, then this might be useful. For example, you could test
`create_environment` itself on other distributions, by running it inside of a
chroot.

View File

@ -17,14 +17,6 @@ else
./get_debootstrap --arch i386 sid "$FRESH" http://deb.debian.org/debian/
fi
cleanup_mount() {
if mountpoint "$1" > /dev/null
then
echo "Unmounting $1"
umount "$1"
fi
}
if test -e "$ROOT"
then
echo "Existing installation found at $ROOT, removing."
@ -35,8 +27,14 @@ echo "Copying $FRESH to $ROOT."
cp -r "$FRESH" "$ROOT"
echo "Initializing $ROOT."
mkdir -p "$ROOT/proc"
mount proc $ROOT/proc -t proc
mkdir -p "$ROOT/sys"
mount sysfs $ROOT/sys -t sysfs
./mountpoints
# Don't bother creating a new tmpfs. We don't want to leak files in, and we
# don't want to waste more RAM on a second tmpfs. The whole thing is
# temporary, after all.
chmod 1777 "$ROOT/tmp"
cp -r install_scripts "$ROOT/root"
chroot "$ROOT" /root/install_scripts/user_setup "$@"

View File

@ -18,8 +18,10 @@ cleanup_mount() {
if test -e "$ROOT"
then
cleanup_mount "$ROOT/proc"
cleanup_mount "$ROOT/dev/pts"
cleanup_mount "$ROOT/dev"
cleanup_mount "$ROOT/sys"
cleanup_mount "$ROOT/proc"
echo "Removing $ROOT"
rm -r "$ROOT"
else

20
debian/enter_environment vendored Executable file
View File

@ -0,0 +1,20 @@
#!/bin/sh
if test `id -u` -ne 0
then
echo "$0 must be run as root."
return
fi
ROOT=test_environment
if test -e "$ROOT"
then
echo "Using existing environment in $ROOT."
else
./create_environment
fi
./mountpoints
chroot "$ROOT" sudo -iu user "$@"

3
debian/install_scripts/get_erlang_zx vendored Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
sudo apt install erlang-base
wget -q https://zxq9.com/projects/zomp/get_zx && bash get_zx

25
debian/install_scripts/user_setup vendored Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# Noninteractive, so that other scripts can install things with apt.
export DEBIAN_FRONTEND=noninteractive
# Overwrite locale setting specified before the chroot
export LANG=C
export LC_ALL=C
# Install sudo, since most user-facing scripts will use sudo
apt install sudo
# Add a passwordless sudoer
useradd -m -s /bin/bash -G sudo user
passwd -d user
echo "user ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/user"
chmod 0440 "/etc/sudoers.d/user"
# Copy the install scripts into their home directory
cp -r ~/install_scripts /home/user
chown -R user:user /home/user/install_scripts
# su to this new user... Or sudo -iu, since we want to pass in arguments too.
cd /home/user
sudo -iu user "$@"

25
debian/mountpoints vendored Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
if test `id -u` -ne 0
then
echo "$0 must be run as root."
return
fi
ROOT=test_environment
check_mount() {
if mountpoint "$ROOT$1" > /dev/null
then
echo "$ROOT$1 already mounted."
else
mkdir -p "$ROOT$1"
mount -o bind "$1" "$ROOT$1"
fi
}
check_mount /proc
check_mount /sys
check_mount /dev
check_mount /dev/pts