| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-02-28 01:30:10 +01:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"os/exec" | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2019-03-07 11:35:11 +11:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 	"syscall" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	"github.com/pkg/term/termios" | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	"github.com/rs/zerolog" | 
					
						
							|  |  |  | 	"github.com/rs/zerolog/log" | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 	"golang.org/x/term" | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	"novit.tech/direktil/initrd/colorio" | 
					
						
							|  |  |  | 	"novit.tech/direktil/initrd/shio" | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// VERSION is the current version of init | 
					
						
							| 
									
										
										
										
											2022-02-04 19:59:42 +01:00
										 |  |  | 	VERSION = "Direktil init v2.0" | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	rootMountFlags  = 0 | 
					
						
							|  |  |  | 	bootMountFlags  = syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_NOSUID | syscall.MS_RDONLY | 
					
						
							|  |  |  | 	layerMountFlags = syscall.MS_RDONLY | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	bootVersion string | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	stdin, | 
					
						
							|  |  |  | 	stdinPipe = newPipe() | 
					
						
							|  |  |  | 	stdout = shio.New() | 
					
						
							|  |  |  | 	stderr = colorio.NewWriter(colorio.Bold, stdout) | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | func newPipe() (io.ReadCloser, io.WriteCloser) { | 
					
						
							|  |  |  | 	return io.Pipe() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | func main() { | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-04 10:29:28 +02:00
										 |  |  | 	switch baseName := filepath.Base(os.Args[0]); baseName { | 
					
						
							|  |  |  | 	case "init": | 
					
						
							|  |  |  | 		runInit() | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 		log.Fatal().Msgf("unknown sub-command: %q", baseName) | 
					
						
							| 
									
										
										
										
											2022-04-04 10:29:28 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func runInit() { | 
					
						
							| 
									
										
										
										
											2023-11-27 14:40:15 +01:00
										 |  |  | 	if len(os.Args) > 1 && os.Args[1] == "hello" { | 
					
						
							|  |  |  | 		fmt.Println("hello world!") | 
					
						
							|  |  |  | 		os.Exit(0) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-07 11:35:11 +11:00
										 |  |  | 	runtime.LockOSThread() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	// move log to shio | 
					
						
							|  |  |  | 	go io.Copy(os.Stdout, stdout.NewReader()) | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Logger = log.Output(zerolog.ConsoleWriter{Out: stderr}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check the PID is 1 | 
					
						
							|  |  |  | 	if pid := os.Getpid(); pid != 1 { | 
					
						
							|  |  |  | 		log.Fatal().Int("pid", pid).Msg("init must be PID 1") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// copy os.Stdin to my stdin pipe | 
					
						
							|  |  |  | 	go io.Copy(stdinPipe, os.Stdin) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Info().Msg("Welcome to " + VERSION) | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// essential mounts | 
					
						
							|  |  |  | 	mount("none", "/proc", "proc", 0, "") | 
					
						
							|  |  |  | 	mount("none", "/sys", "sysfs", 0, "") | 
					
						
							|  |  |  | 	mount("none", "/dev", "devtmpfs", 0, "") | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=620") | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// get the "boot version" | 
					
						
							|  |  |  | 	bootVersion = param("version", "current") | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Info().Msgf("booting system %q", bootVersion) | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	os.Setenv("PATH", "/usr/bin:/bin:/usr/sbin:/sbin") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-04 19:59:42 +01:00
										 |  |  | 	_, err := os.Stat("/config.yaml") | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-01-20 17:26:28 +01:00
										 |  |  | 		log.Error().Err(err).Msg("config not found") | 
					
						
							|  |  |  | 		fatal() | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-04 19:59:42 +01:00
										 |  |  | 	bootV2() | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	layersDir      = "/boot/current/layers/" | 
					
						
							|  |  |  | 	layersOverride = map[string]string{} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | func layerPath(name string) string { | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	if override, ok := layersOverride[name]; ok { | 
					
						
							|  |  |  | 		return override | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return filepath.Join(layersDir, name+".fs") | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func fatal(v ...interface{}) { | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Error().Msg("*** FATAL ***") | 
					
						
							|  |  |  | 	log.Error().Msg(fmt.Sprint(v...)) | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 	die() | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func fatalf(pattern string, v ...interface{}) { | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Error().Msg("*** FATAL ***") | 
					
						
							|  |  |  | 	log.Error().Msgf(pattern, v...) | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 	die() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func die() { | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	stdout.Close() | 
					
						
							|  |  |  | 	stdin.Close() | 
					
						
							|  |  |  | 	stdinPipe.Close() | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 	stdin = nil | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | mainLoop: | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 		termios.Tcdrain(os.Stdin.Fd()) | 
					
						
							|  |  |  | 		termios.Tcdrain(os.Stdout.Fd()) | 
					
						
							|  |  |  | 		termios.Tcdrain(os.Stderr.Fd()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fmt.Print("\nr to reboot, o to power off, s to get a shell: ") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// TODO flush stdin (first char lost here?) | 
					
						
							|  |  |  | 		deadline := time.Now().Add(time.Minute) | 
					
						
							|  |  |  | 		os.Stdin.SetReadDeadline(deadline) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		term.MakeRaw(int(os.Stdin.Fd())) | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 		termios.Tcflush(os.Stdin.Fd(), termios.TCIFLUSH) | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		b := make([]byte, 1) | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 		_, err := os.Stdin.Read(b) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 			log.Error().Err(err).Msg("failed to read from stdin") | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 			time.Sleep(5 * time.Second) | 
					
						
							|  |  |  | 			syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 		fmt.Println() | 
					
						
							| 
									
										
										
										
											2022-02-04 19:59:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 		switch b[0] { | 
					
						
							|  |  |  | 		case 'o': | 
					
						
							| 
									
										
										
										
											2022-04-04 10:29:28 +02:00
										 |  |  | 			run("sync") | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 			syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF) | 
					
						
							|  |  |  | 		case 'r': | 
					
						
							| 
									
										
										
										
											2022-04-04 10:29:28 +02:00
										 |  |  | 			run("sync") | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 			syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) | 
					
						
							| 
									
										
										
										
											2022-02-04 19:59:42 +01:00
										 |  |  | 		case 's': | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 			for _, sh := range []string{"bash", "ash", "sh", "busybox"} { | 
					
						
							|  |  |  | 				fullPath, err := exec.LookPath(sh) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				args := make([]string, 0) | 
					
						
							|  |  |  | 				if sh == "busybox" { | 
					
						
							|  |  |  | 					args = append(args, "sh") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if !localAuth() { | 
					
						
							|  |  |  | 					continue mainLoop | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				cmd := exec.Command(fullPath, args...) | 
					
						
							|  |  |  | 				cmd.Env = os.Environ() | 
					
						
							|  |  |  | 				cmd.Stdin = os.Stdin | 
					
						
							|  |  |  | 				cmd.Stdout = os.Stdout | 
					
						
							|  |  |  | 				cmd.Stderr = os.Stderr | 
					
						
							|  |  |  | 				err = cmd.Run() | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					fmt.Println("shell failed:", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				continue mainLoop | 
					
						
							| 
									
										
										
										
											2022-02-04 19:59:42 +01:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 			log.Error().Msg("failed to find a shell!") | 
					
						
							| 
									
										
										
										
											2022-03-08 11:45:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 			log.Error().Msgf("unknown choice: %q", string(b)) | 
					
						
							| 
									
										
										
										
											2020-11-19 21:22:17 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func run(cmd string, args ...string) { | 
					
						
							|  |  |  | 	if output, err := exec.Command(cmd, args...).CombinedOutput(); err != nil { | 
					
						
							|  |  |  | 		fatalf("command %s %q failed: %v\n%s", cmd, args, err, string(output)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func mkdir(dir string, mode os.FileMode) { | 
					
						
							|  |  |  | 	if err := os.MkdirAll(dir, mode); err != nil { | 
					
						
							|  |  |  | 		fatalf("mkdir %q failed: %v", dir, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func mount(source, target, fstype string, flags uintptr, data string) { | 
					
						
							|  |  |  | 	if _, err := os.Stat(target); os.IsNotExist(err) { | 
					
						
							|  |  |  | 		mkdir(target, 0755) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := syscall.Mount(source, target, fstype, flags, data); err != nil { | 
					
						
							|  |  |  | 		fatalf("mount %q %q -t %q -o %q failed: %v", source, target, fstype, data, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-20 16:41:54 +01:00
										 |  |  | 	log.Info().Str("target", target).Msg("mounted") | 
					
						
							| 
									
										
										
										
											2019-02-11 16:05:43 +11:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-02-28 01:30:10 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | func cp(srcPath, dstPath string) { | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			fatalf("cp %s %s failed: %v", srcPath, dstPath, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	src, err := os.Open(srcPath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer src.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dst, err := os.Create(dstPath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer dst.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = io.Copy(dst, src) | 
					
						
							|  |  |  | } |