diff --git a/tools/qemu-debian-img/main.go b/tools/qemu-debian-img/main.go new file mode 100644 index 0000000..04d755f --- /dev/null +++ b/tools/qemu-debian-img/main.go @@ -0,0 +1,181 @@ +// Copyright 2018 Mikhail Klementev. All rights reserved. +// Use of this source code is governed by a GPLv3 license +// (or later) that can be found in the LICENSE file. + +package main + +import ( + "errors" + "io/ioutil" + "log" + "net/http" + "os" + "os/user" + "regexp" + "strings" + + system "github.com/jollheef/go-system" + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +func matchBody(url, pattern string) bool { + resp, err := http.Get(url) + if err != nil { + return false + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false + } + + matched, err := regexp.MatchString(pattern, string(body)) + if err != nil { + return false + } + if !matched { + return false + } + + return true +} + +func isAptCacherExists(url string) bool { + return matchBody(url, "Apt-Cacher") +} + +func runInChroot(chroot, cmd string) (string, string, int, error) { + return system.System("chroot", chroot, "/bin/sh", "-c", cmd) +} + +func generateImage(repo, release, path, size string) (err error) { + log.Println("Check current user") + usr, err := user.Current() + if err != nil { + return + } + if usr.Name != "root" { + err = errors.New("Run as root is required") + return + } + + log.Println("Create qcow2 image") + stdout, stderr, _, err := system.System("qemu-img", "create", path, size) + if err != nil { + log.Println(stdout, stderr) + return + } + + log.Println("Create ext4 file system") + stdout, stderr, _, err = system.System("mkfs.ext4", "-F", path) + if err != nil { + log.Println(stdout, stderr) + return + } + + log.Println("Create temporary directory") + stdout, stderr, _, err = system.System("mktemp", "-d") + if err != nil { + log.Println(stdout, stderr) + return + } + tmpdir := strings.TrimSpace(stdout) + defer func() { + log.Println("Remove temporary directory") + err := os.RemoveAll(tmpdir) + if err != nil { + log.Println(err) + } + }() + + log.Println("Mount qemu image") + stdout, stderr, _, err = system.System("mount", "-o", "loop", path, tmpdir) + if err != nil { + log.Println(stdout, stderr) + return + } + defer func() { + log.Println("Umount qemu image") + stdout, stderr, _, err := system.System("umount", tmpdir) + if err != nil { + log.Println(err, stdout, stderr) + } + }() + + log.Println("debootstrap (may be more than 10-15 minutes)") + stdout, stderr, _, err = system.System("debootstrap", + "--include=openssh-server", + release, tmpdir, repo) + if err != nil { + log.Println(stdout, stderr) + return + } + + log.Println("Create new user") + stdout, stderr, _, err = runInChroot(tmpdir, "useradd -m user") + if err != nil { + log.Println(stdout, stderr) + return + } + + log.Println("Login without password") + stdout, stderr, _, err = runInChroot(tmpdir, "passwd -d root") + if err != nil { + log.Println(stdout, stderr) + return + } + stdout, stderr, _, err = runInChroot(tmpdir, "passwd -d user") + if err != nil { + log.Println(stdout, stderr) + return + } + + log.Println("Allow ssh login without password (NOTE: fixed sshd pam.d)") + stdout, stderr, _, err = runInChroot(tmpdir, + "echo auth sufficient pam_permit.so > /etc/pam.d/sshd"+ + " && echo PermitEmptyPasswords yes >> /etc/ssh/sshd_config"+ + " && echo PermitRootLogin yes >> /etc/ssh/sshd_config") + if err != nil { + log.Println(stdout, stderr) + return + } + + log.Println("Add dhclient to rc.local") + stdout, stderr, _, err = runInChroot(tmpdir, + "echo '#!/bin/sh\ndhclient' >> /etc/rc.local"+ + " && chmod +x /etc/rc.local") + if err != nil { + log.Println(stdout, stderr) + return + } + + return +} + +func main() { + generate := kingpin.Command("generate", "Generate qemu image") + repository := generate.Flag("repository", "Debian/ubuntu repository").Default( + "deb.debian.org/debian").String() + aptCacherURL := generate.Flag("apt-cacher", "Local apt cacher url").Default( + "http://localhost:3142/").String() + release := generate.Flag("release", "Debian/ubuntu release name").Default( + "sid").String() + size := generate.Flag("size", "Image size").Default("8G").String() + path := generate.Arg("path", "Generated image path").Required().String() + + switch kingpin.Parse() { + case "generate": + repo := *repository + if isAptCacherExists(*aptCacherURL) { + log.Println("Use local apt cache") + repo = *aptCacherURL + "/" + repo + } + log.Println("Repository:", repo) + + err := generateImage(repo, *release, *path, *size) + if err != nil { + log.Fatalln(err) + } + } +}