From 28de550295740abb771c00fc41f3510f30b24574 Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 23 May 2025 21:21:16 +1000 Subject: [PATCH] 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. --- README.md | 118 +++++++++++++++++++++++++++ debian/create_environment | 22 +++-- debian/destroy_environment | 4 +- debian/enter_environment | 20 +++++ debian/install_scripts/get_erlang_zx | 3 + debian/install_scripts/user_setup | 25 ++++++ debian/mountpoints | 25 ++++++ 7 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 README.md create mode 100755 debian/enter_environment create mode 100755 debian/install_scripts/get_erlang_zx create mode 100755 debian/install_scripts/user_setup create mode 100755 debian/mountpoints diff --git a/README.md b/README.md new file mode 100644 index 0000000..eea29ef --- /dev/null +++ b/README.md @@ -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. + diff --git a/debian/create_environment b/debian/create_environment index d90a397..cef9233 100755 --- a/debian/create_environment +++ b/debian/create_environment @@ -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 "$@" diff --git a/debian/destroy_environment b/debian/destroy_environment index 0e2df52..00eb008 100755 --- a/debian/destroy_environment +++ b/debian/destroy_environment @@ -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 diff --git a/debian/enter_environment b/debian/enter_environment new file mode 100755 index 0000000..eeabf68 --- /dev/null +++ b/debian/enter_environment @@ -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 "$@" diff --git a/debian/install_scripts/get_erlang_zx b/debian/install_scripts/get_erlang_zx new file mode 100755 index 0000000..2c3d668 --- /dev/null +++ b/debian/install_scripts/get_erlang_zx @@ -0,0 +1,3 @@ +#!/bin/sh +sudo apt install erlang-base +wget -q https://zxq9.com/projects/zomp/get_zx && bash get_zx diff --git a/debian/install_scripts/user_setup b/debian/install_scripts/user_setup new file mode 100755 index 0000000..1132c5a --- /dev/null +++ b/debian/install_scripts/user_setup @@ -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 "$@" diff --git a/debian/mountpoints b/debian/mountpoints new file mode 100755 index 0000000..a25d00a --- /dev/null +++ b/debian/mountpoints @@ -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 +