fix(tvix/tests/tvix-init): run as pid1, support init=
We were previously not running as PID1, but were executing things via uinitcmd (by u-root init). This had some problems - a real system pid1 really wants to be pid1. u-root init itself seems to be too complicated to configure as a chainloader, so instead, we just provide init/pid1 ourselves and do the little bit of mount setup / stage 1 that's necessary. `tvix.exec` is renamed to `tvix.run`, to further distinguish it from the `exec` bash function. Our init will now also power off the machine if the shell, or the custom tvix.run command finished. In the case of switch_root (`init=` being passed), we terminate before, so it's up to the real system anyways. Change-Id: If00c0c11ca9393968e76ae0d131594b5c135fa48 Reviewed-on: https://cl.tvl.fyi/c/depot/+/9454 Autosubmit: flokli <flokli@flokli.de> Reviewed-by: Connor Brewster <cbrewster@hey.com> Tested-by: BuildkiteCI
This commit is contained in:
parent
05f42519b5
commit
242949ecfb
3 changed files with 132 additions and 52 deletions
|
@ -61,22 +61,16 @@ Run the VM like this:
|
|||
CH_CMDLINE=tvix.shell mg run //tvix/tests:runVM --
|
||||
```
|
||||
|
||||
You'll get dropped into a shell, from which you can list the store contents:
|
||||
You'll get dropped into an interactive shell, from which you can do things with
|
||||
the store:
|
||||
|
||||
```
|
||||
[ 0.282381] Run /init as init process
|
||||
2023/09/24 13:03:38 Welcome to u-root!
|
||||
_
|
||||
_ _ _ __ ___ ___ | |_
|
||||
| | | |____| '__/ _ \ / _ \| __|
|
||||
| |_| |____| | | (_) | (_) | |_
|
||||
\__,_| |_| \___/ \___/ \__|
|
||||
______ _ ____ _ __
|
||||
/_ __/ __(_) __ / _/___ (_) /_
|
||||
/ / | | / / / |/_/ / // __ \/ / __/
|
||||
/ / | |/ / /> < _/ // / / / / /_
|
||||
/_/ |___/_/_/|_| /___/_/ /_/_/\__/
|
||||
|
||||
2023/09/24 13:03:38 Running tvix-init…
|
||||
2023/09/24 13:03:38 Creating /nix/store
|
||||
2023/09/24 13:03:38 Mounting…
|
||||
2023/09/24 13:03:38 Invoking shell
|
||||
…
|
||||
/# ls -la /nix/store/
|
||||
dr-xr-xr-x root 0 0 Jan 1 00:00 .
|
||||
dr-xr-xr-x root 0 989 Jan 1 00:00 aw2fw9ag10wr9pf0qk4nk5sxi0q0bn56-glibc-2.37-8
|
||||
|
@ -86,30 +80,63 @@ dr-xr-xr-x root 0 141 Jan 1 00:00 mdi7lvrn2mx7rfzv3fdq3v5yw8swiks6-hello-2.12.1
|
|||
dr-xr-xr-x root 0 5 Jan 1 00:00 s2gi8pfjszy6rq3ydx0z1vwbbskw994i-libunistring-1.1
|
||||
```
|
||||
|
||||
Once you're done, run `poweroff` to turn off the VM.
|
||||
Once you exit the shell, the VM will power off itself.
|
||||
|
||||
#### Execute a specific binary
|
||||
Run the VM like this:
|
||||
|
||||
```
|
||||
hello_cmd=$(mg build //third_party/nixpkgs:hello)/bin/hello
|
||||
CH_CMDLINE=tvix.exec=$hello_cmd mg run //tvix/tests:runVM --
|
||||
CH_CMDLINE=tvix.run=$hello_cmd mg run //tvix/tests:runVM --
|
||||
```
|
||||
|
||||
Observe it executing the file (and closure) from the tvix-store:
|
||||
|
||||
```
|
||||
2023/09/24 13:06:13 Welcome to u-root!
|
||||
_
|
||||
_ _ _ __ ___ ___ | |_
|
||||
| | | |____| '__/ _ \ / _ \| __|
|
||||
| |_| |____| | | (_) | (_) | |_
|
||||
\__,_| |_| \___/ \___/ \__|
|
||||
[ 0.277486] Run /init as init process
|
||||
______ _ ____ _ __
|
||||
/_ __/ __(_) __ / _/___ (_) /_
|
||||
/ / | | / / / |/_/ / // __ \/ / __/
|
||||
/ / | |/ / /> < _/ // / / / / /_
|
||||
/_/ |___/_/_/|_| /___/_/ /_/_/\__/
|
||||
|
||||
2023/09/24 13:06:13 Running tvix-init…
|
||||
2023/09/24 13:06:13 Creating /nix/store
|
||||
2023/09/24 13:06:13 Mounting…
|
||||
2023/09/24 13:06:13 Invoking /nix/store/mdi7lvrn2mx7rfzv3fdq3v5yw8swiks6-hello-2.12.1/bin/hello
|
||||
…
|
||||
Hello, world!
|
||||
```
|
||||
2023/09/24 21:10:19 Nothing left to be done, powering off.
|
||||
[ 0.299122] ACPI: PM: Preparing to enter system sleep state S5
|
||||
[ 0.299422] reboot: Power down
|
||||
```
|
||||
|
||||
#### Execute a NixOS system closure
|
||||
It's also possible to invoke a system closure. To do this, tvix-init honors the
|
||||
init= cmdline option, and will switch_root to it.
|
||||
|
||||
|
||||
```
|
||||
CH_CMDLINE=init=/nix/store/…-nixos-system-…/init mg run //tvix/tests:runVM --
|
||||
```
|
||||
|
||||
```
|
||||
______ _ ____ _ __
|
||||
/_ __/ __(_) __ / _/___ (_) /_
|
||||
/ / | | / / / |/_/ / // __ \/ / __/
|
||||
/ / | |/ / /> < _/ // / / / / /_
|
||||
/_/ |___/_/_/|_| /___/_/ /_/_/\__/
|
||||
|
||||
2023/09/24 21:16:43 switch_root: moving mounts
|
||||
2023/09/24 21:16:43 switch_root: Skipping "/run" as the dir does not exist
|
||||
2023/09/24 21:16:43 switch_root: Changing directory
|
||||
2023/09/24 21:16:43 switch_root: Moving /
|
||||
2023/09/24 21:16:43 switch_root: Changing root!
|
||||
2023/09/24 21:16:43 switch_root: Deleting old /
|
||||
2023/09/24 21:16:43 switch_root: executing init
|
||||
|
||||
<<< NixOS Stage 2 >>>
|
||||
|
||||
[ 0.322096] booting system configuration /nix/store/g657sdxinpqfcdv0162zmb8vv9b5c4c5-nixos-system-client-23.11.git.82102fc37da
|
||||
running activation script...
|
||||
setting up /etc...
|
||||
starting systemd...
|
||||
[ 0.980740] systemd[1]: systemd 253.6 running in system mode (+PAM +AUDIT -SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY +P11KIT -QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -XKBCOMMON +UTMP -SYSVINIT default-hierarchy=unified)
|
||||
```
|
||||
|
||||
This effectively replaces the NixOS Stage 1 entirely.
|
|
@ -58,7 +58,7 @@ rec {
|
|||
export HOME=$(mktemp -d)
|
||||
export GOROOT="$(go env GOROOT)"
|
||||
|
||||
GO111MODULE=off GOPATH=/tmp/go GOPROXY=off ${uroot}/bin/u-root -files ./tvix-init -uinitcmd "/tvix-init" -o $out
|
||||
GO111MODULE=off GOPATH=/tmp/go GOPROXY=off ${uroot}/bin/u-root -files ./tvix-init -initcmd "/tvix-init" -o $out
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -37,11 +38,41 @@ func parseCmdline(cmdline string) map[string]string {
|
|||
return out
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Println("Running tvix-init…")
|
||||
// mounts the nix store from the virtiofs tag to the given destination,
|
||||
// creating the destination if it doesn't exist already.
|
||||
func mountTvixStore(dest string) error {
|
||||
if err := os.MkdirAll(dest, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("unable to mkdir dest: %w", err)
|
||||
}
|
||||
if err := run("mount", "-t", "virtiofs", "tvix", dest, "-o", "ro"); err != nil {
|
||||
return fmt.Errorf("unable to run mount: %w", err)
|
||||
}
|
||||
|
||||
log.Println("Creating /nix/store")
|
||||
os.MkdirAll("/nix/store", os.ModePerm)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Print(`
|
||||
______ _ ____ _ __
|
||||
/_ __/ __(_) __ / _/___ (_) /_
|
||||
/ / | | / / / |/_/ / // __ \/ / __/
|
||||
/ / | |/ / /> < _/ // / / / / /_
|
||||
/_/ |___/_/_/|_| /___/_/ /_/_/\__/
|
||||
|
||||
`)
|
||||
|
||||
// Set PATH to "/bbin", so we can find the u-root tools
|
||||
os.Setenv("PATH", "/bbin")
|
||||
|
||||
if err := run("mount", "-t", "proc", "none", "/proc"); err != nil {
|
||||
log.Printf("Failed to mount /proc: %v\n", err)
|
||||
}
|
||||
if err := run("mount", "-t", "sysfs", "none", "/sys"); err != nil {
|
||||
log.Printf("Failed to mount /sys: %v\n", err)
|
||||
}
|
||||
if err := run("mount", "-t", "devtmpfs", "devtmpfs", "/dev"); err != nil {
|
||||
log.Printf("Failed to mount /dev: %v\n", err)
|
||||
}
|
||||
|
||||
cmdline, err := os.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
|
@ -49,37 +80,59 @@ func main() {
|
|||
}
|
||||
cmdlineFields := parseCmdline(string(cmdline))
|
||||
|
||||
log.Println("Mounting…")
|
||||
if err := run("mount", "-t", "virtiofs", "tvix", "/nix/store", "-o", "ro"); err != nil {
|
||||
log.Printf("Failed to run mount: %v\n", err)
|
||||
}
|
||||
|
||||
// If tvix.find is set, invoke find /nix/store
|
||||
if _, ok := cmdlineFields["tvix.find"]; ok {
|
||||
log.Println("Listing…")
|
||||
// If tvix.find is set, invoke find /nix/store
|
||||
if err := mountTvixStore("/nix/store"); err != nil {
|
||||
log.Printf("Failed to mount tvix store: %v\n", err)
|
||||
}
|
||||
|
||||
if err := run("find", "/nix/store"); err != nil {
|
||||
log.Printf("Failed to run find command: %s\n", err)
|
||||
}
|
||||
}
|
||||
} else if _, ok := cmdlineFields["tvix.shell"]; ok {
|
||||
// If tvix.shell is set, mount the nix store to /nix/store directly,
|
||||
// then invoke the elvish shell
|
||||
if err := mountTvixStore("/nix/store"); err != nil {
|
||||
log.Printf("Failed to mount tvix store: %v\n", err)
|
||||
}
|
||||
|
||||
// If tvix.shell is set, invoke the elvish shell
|
||||
if v, ok := cmdlineFields["tvix.shell"]; ok {
|
||||
log.Printf("Invoking shell%s\n…", v)
|
||||
if err := run("elvish"); err != nil {
|
||||
log.Printf("Failed to run shell: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If tvix.exec is set, invoke the binary specified
|
||||
if v, ok := cmdlineFields["tvix.exec"]; ok {
|
||||
log.Printf("Invoking %s\n…", v)
|
||||
if err := syscall.Exec(v, []string{v}, []string{}); err != nil {
|
||||
log.Printf("Failed to exec: %s\n", err)
|
||||
} else if v, ok := cmdlineFields["tvix.run"]; ok {
|
||||
// If tvix.run is set, mount the nix store to /nix/store directly,
|
||||
// then invoke the command.
|
||||
if err := mountTvixStore("/nix/store"); err != nil {
|
||||
log.Printf("Failed to mount tvix store: %v\n", err)
|
||||
}
|
||||
|
||||
if err := run(v); err != nil {
|
||||
log.Printf("Failed to run command: %s\n", err)
|
||||
}
|
||||
} else if v, ok := cmdlineFields["init"]; ok {
|
||||
// If init is set, invoke the binary specified (with switch_root),
|
||||
// and prepare /fs beforehand as well.
|
||||
os.Mkdir("/fs", os.ModePerm)
|
||||
if err := run("mount", "-t", "tmpfs", "none", "/fs"); err != nil {
|
||||
log.Fatalf("Failed to mount /fs tmpfs: %s\n", err)
|
||||
}
|
||||
|
||||
// Mount /fs/nix/store
|
||||
if err := mountTvixStore("/fs/nix/store"); err != nil {
|
||||
log.Fatalf("Failed to mount tvix store: %v\n", err)
|
||||
}
|
||||
|
||||
// Invoke switch_root, which will take care of moving /proc, /sys and /dev.
|
||||
if err := syscall.Exec("/bbin/switch_root", []string{"switch_root", "/fs", v}, []string{}); err != nil {
|
||||
log.Printf("Failed to switch root: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("No command detected, not knowing what to do!")
|
||||
}
|
||||
|
||||
log.Println("Powering off")
|
||||
// This is only reached in the non switch_root case.
|
||||
log.Printf("Nothing left to be done, powering off.")
|
||||
if err := run("poweroff"); err != nil {
|
||||
log.Printf("Failed to run command: %v\n", err)
|
||||
log.Printf("Failed to run poweroff command: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue