From 5b8cad6176b5ec9a31f882075a7aed5b4b029c20 Mon Sep 17 00:00:00 2001 From: Adphi Date: Thu, 21 Apr 2022 18:28:50 +0200 Subject: [PATCH] cli: improve flags and commands docs: add README.md and examples centos/rhel: fix dracut initramfs Signed-off-by: Adphi --- .dockerignore | 2 +- .gitignore | 5 +- Dockerfile | 3 +- README.md | 252 ++++++++++++++++++++++++++++++++-- builder.go | 58 ++++++-- cmd/d2vm/build.go | 28 ++-- cmd/d2vm/convert.go | 36 ++++- cmd/d2vm/main.go | 4 +- convert.go | 6 +- docker_image.go | 2 +- dockerfile.go | 4 +- examples/.dockerignore | 1 + examples/centos.Dockerfile | 13 ++ examples/full/.dockerignore | 1 + examples/full/00-netconf.yaml | 10 ++ examples/full/Dockerfile | 47 +++++++ examples/full/README.md | 95 +++++++++++++ examples/full/build.sh | 6 + examples/full/resize | 13 ++ os_release.go | 5 +- templates/centos.Dockerfile | 9 +- virtinst | 6 + 22 files changed, 550 insertions(+), 56 deletions(-) create mode 100644 examples/.dockerignore create mode 100644 examples/centos.Dockerfile create mode 100644 examples/full/.dockerignore create mode 100644 examples/full/00-netconf.yaml create mode 100644 examples/full/Dockerfile create mode 100644 examples/full/README.md create mode 100644 examples/full/build.sh create mode 100755 examples/full/resize create mode 100755 virtinst diff --git a/.dockerignore b/.dockerignore index 430cb83..ad61352 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,4 @@ tests disk* qemu.sh -*.qcow2 +**/*.qcow2 diff --git a/.gitignore b/.gitignore index b6ee9b8..2e90faf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .idea tests -disk*.qcow2 +*.qcow2 + +dist/ +.goreleaser.yaml diff --git a/Dockerfile b/Dockerfile index 2511e71..8f5e9c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,6 @@ FROM ubuntu RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ - linux-image-virtual \ util-linux \ kpartx \ e2fsprogs \ @@ -29,3 +28,5 @@ RUN apt-get update && \ COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ COPY --from=builder /d2vm/d2vm /usr/local/bin/ + +ENTRYPOINT ["/usr/local/bin/d2vm"] diff --git a/README.md b/README.md index 605effb..6fc08f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# d2vm +# d2vm (Docker to Virtual Machine) *Build virtual machine image from Docker images* @@ -8,13 +8,249 @@ **Only Linux is supported.** -If you want to run it on OSX or Windows (untested) you can use Docker for it: +If you want to run it on **OSX** or **Windows** (the last one is totally untested) you can do it using Docker: ```bash -docker run --rm -i -t \ - --privileged \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v $(pwd):/build \ - -w /build \ - linkacloud/d2vm bash +alias d2vm="docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v \$PWD:/build -w /build linkacloud/d2vm" ``` + +## Supported VM Linux distributions: + +Working and tested: + +- [x] Ubuntu +- [x] Debian +- [x] Alpine + +Need fix: + +- [ ] CentOS / RHEL + +The program use the `/etc/os-release` file to discovery the Linux Distribution and install the Kernel, +if the file is missing, the build cannot succeed. + +Obviously, **Distroless** images are not supported. + +## Getting started + +Clone the git repository: + +```bash +git clone https://github.com/linka-cloud/d2vm && cd d2vm +``` + +Install using the Go tool chain: + +```bash +go install ./cmd/d2vm +which d2vm +``` +``` +# Should be install in the $GOBIN directory +/go/bin/d2vm +``` + +Or use an alias to the **docker** image: + +```bash +alias d2vm="docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v \$PWD:/build -w /build linkacloud/d2vm" +which d2vm +``` +``` +d2vm: aliased to docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v $PWD:/build -w /build linkacloud/d2vm +``` + +### Converting an existing Docker Image to VM image: + +```bash +b2vm convert --help +``` +``` +Convert Docker image to vm image + +Usage: + d2vm convert [docker image] [flags] + +Flags: + -d, --debug Enable Debug output + -f, --force Override output qcow2 image + -h, --help help for convert + -o, --output string The output image (default "disk0.qcow2") + -O, --output-format string The output image format, supported formats: qcow2 qed raw vdi vhd vmdk (default "qcow2") + -p, --password string The Root user password (default "root") + --pull Always pull docker image + -s, --size string The output image size (default "10G") + +``` + +Create an image based on the **ubuntu** official image: + +```bash +sudo d2vm convert ubuntu -o ubuntu.qcow2 -p MyP4Ssw0rd +``` +``` +INFO[0000] pulling image ubuntu +INFO[0001] inspecting image ubuntu +INFO[0002] docker image based on Ubuntu +INFO[0002] building kernel enabled image +INFO[0038] creating root file system archive +INFO[0040] creating vm image +INFO[0040] creating raw image +INFO[0040] mounting raw image +INFO[0040] creating raw image file system +INFO[0040] copying rootfs to raw image +INFO[0041] setting up rootfs +INFO[0041] installing linux kernel +INFO[0042] unmounting raw image +INFO[0042] writing MBR +INFO[0042] converting to qcow2 +``` + +You can now run your ubuntu image using the created `ubuntu.qcow2` image with **qemu**: + +```bash +./qemu.sh ununtu.qcow2 +``` +``` +SeaBIOS (version 1.13.0-1ubuntu1.1) + + +iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+BFF8C920+BFECC920 CA00 + + + +Booting from Hard Disk... + +SYSLINUX 6.04 EDD 20191223 Copyright (C) 1994-2015 H. Peter Anvin et al +Now booting the kernel from SYSLINUX... +Loading /boot/vmlinuz... ok +Loading /boot/initrd.img...ok +[ 0.000000] Linux version 5.4.0-109-generic (buildd@ubuntu) (gcc version 9) +[ 0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz ro root=UUID=b117d206-b8 +[ 0.000000] KERNEL supported cpus: +[ 0.000000] Intel GenuineIntel +[ 0.000000] AMD AuthenticAMD +[ 0.000000] Hygon HygonGenuine +[ 0.000000] Centaur CentaurHauls +[ 0.000000] zhaoxin Shanghai + +... + +Welcome to Ubuntu 20.04.4 LTS! + +[ 3.610631] systemd[1]: Set hostname to . +[ 3.838984] systemd[1]: Created slice system-getty.slice. +[ OK ] Created slice system-getty.slice. +[ 3.845038] systemd[1]: Created slice system-modprobe.slice. +[ OK ] Created slice system-modprobe.slice. +[ 3.852054] systemd[1]: Created slice system-serial\x2dgetty.slice. +[ OK ] Created slice system-serial\x2dgetty.slice. + +... + +Ubuntu 20.04.4 LTS localhost ttyS0 + +localhost login: +``` + +Log in using the *root* user and the password configured at build time. + +``` +localhost login: root +Password: + + +Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-109-generic x86_64) + + * Documentation: https://help.ubuntu.com + * Management: https://landscape.canonical.com + * Support: https://ubuntu.com/advantage + +This system has been minimized by removing packages and content that are +not required on a system that users do not log into. + +To restore this content, you can run the 'unminimize' command. + +The programs included with the Ubuntu system are free software; +the exact distribution terms for each program are described in the +individual files in /usr/share/doc/*/copyright. + +Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by +applicable law. + +root@localhost:~# +``` + +Type `poweroff` to shutdown the vm. + +### Building a VM Image from a Dockerfile + +The example directory contains very minimalistic examples: + +```bash +cd examples +``` + +*ubuntu.Dockerfile* : + +```dockerfile +FROM ubuntu + +RUN apt update && apt install -y openssh-server && \ + echo "PermitRootLogin yes" >> /etc/ssh/sshd_config \ + +``` + +When building the vm image, *d2vm* will create a root password, so there is no need to configure it now. + +Build the vm image: + +The *build* command take most of its flags and arguments from the *docker build* command. + +```bash +d2vm build --help +``` + +``` +Build a vm image from Dockerfile + +Usage: + d2vm build [context directory] [flags] + +Flags: + --build-arg stringArray Set build-time variables + -d, --debug Enable Debug output + -f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile') (default "Dockerfile") + --force Override output image + -h, --help help for build + -o, --output string The output image (default "disk0.qcow2") + -O, --output-format string The output image format, supported formats: qcow2 qed raw vdi vhd vmdk (default "qcow2") + -p, --password string Root user password (default "root") + -s, --size string The output image size (default "10G") + +``` + +```bash +sudo d2vm build -p MyP4Ssw0rd -f ubuntu.Dockerfile -o ubuntu.qcow2 . +``` + +Or if you want to create a VirtualBox image: + +```bash +sudo d2vm build -p MyP4Ssw0rd -f ubuntu.Dockerfile -O vdi -o ubuntu.vdi . +``` + +### Complete example + +A complete example setting up a ZSH workstation is available in the [examples/full](examples/full/README.md) directory. + + +### Internal Dockerfile templates + +You can find the Dockerfiles used to install the Kernel in the [templates](templates) directory. + +### TODO / Questions: + +- [ ] Create service from `ENTRYPOINT` `CMD` `WORKDIR` and `ENV` instructions ? +- [ ] Inject Image `ENV` variables into `.bashrc` or other service environment file ? +- [ ] Use image layers to create *rootfs* instead of container ? diff --git a/builder.go b/builder.go index be0c250..132cefe 100644 --- a/builder.go +++ b/builder.go @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package docker2vm +package d2vm import ( "bytes" "context" "fmt" + "math" "os" exec2 "os/exec" "path/filepath" "strings" + "github.com/c2h5oh/datasize" "github.com/sirupsen/logrus" "go.uber.org/multierr" @@ -69,37 +71,61 @@ ff02::3 ip6-allhosts var ( fdiskCmds = []string{"n", "p", "1", "", "", "a", "w"} + + formats = []string{"qcow2", "qed", "raw", "vdi", "vhd", "vmdk"} ) type builder struct { osRelease OSRelease - src string - diskRaw string - diskQcow2 string - size int64 - mntPoint string + src string + diskRaw string + diskOut string + format string + + size int64 + mntPoint string loDevice string loPart string diskUUD string } -func NewBuilder(workdir, src, disk string, size int64, osRelease OSRelease) (*builder, error) { +func NewBuilder(workdir, src, disk string, size int64, osRelease OSRelease, format string) (*builder, error) { if err := checkDependencies(); err != nil { return nil, err } + f := strings.ToLower(format) + valid := false + for _, v := range formats { + if valid = v == f; valid { + break + } + } + if !valid { + return nil, fmt.Errorf("invalid format: %s valid formats are: %s", f, strings.Join(formats, " ")) + } if size == 0 { - size = 1 + size = 10 * int64(datasize.GB) } if disk == "" { disk = "disk0" } + i, err := os.Stat(src) + if err != nil { + return nil, err + } + if i.Size() > size { + s := datasize.ByteSize(math.Ceil(datasize.ByteSize(i.Size()).GBytes())) * datasize.GB + logrus.Warnf("%s is smaller than rootfs size, using %s", datasize.ByteSize(size), s) + size = int64(s) + } b := &builder{ osRelease: osRelease, src: src, diskRaw: filepath.Join(workdir, disk+".raw"), - diskQcow2: filepath.Join(workdir, disk+".qcow2"), + diskOut: filepath.Join(workdir, disk+".qcow2"), + format: f, size: size, mntPoint: filepath.Join(workdir, "/mnt"), } @@ -146,7 +172,7 @@ func (b *builder) Build(ctx context.Context) (err error) { if err = b.setupMBR(ctx); err != nil { return err } - if err = b.convert2Qcow2(ctx); err != nil { + if err = b.convert2Img(ctx); err != nil { return err } if err = b.cleanUp(ctx); err != nil { @@ -283,7 +309,7 @@ func (b *builder) installKernel(ctx context.Context) error { sysconfig = syslinuxCfgDebian case ReleaseAlpine: sysconfig = syslinuxCfgAlpine - case ReleaseCentOS: + case ReleaseCentOS, ReleaseRHEL: sysconfig = syslinuxCfgCentOS default: return fmt.Errorf("%s: distribution not supported", b.osRelease.ID) @@ -302,9 +328,9 @@ func (b *builder) setupMBR(ctx context.Context) error { return nil } -func (b *builder) convert2Qcow2(ctx context.Context) error { - logrus.Infof("converting to QCOW2") - return exec.Run(ctx, "qemu-img", "convert", b.diskRaw, "-O", "qcow2", b.diskQcow2) +func (b *builder) convert2Img(ctx context.Context) error { + logrus.Infof("converting to %s", b.format) + return exec.Run(ctx, "qemu-img", "convert", b.diskRaw, "-O", b.format, b.diskOut) } func (b *builder) chWriteFile(path string, content string, perm os.FileMode) error { @@ -336,3 +362,7 @@ func checkDependencies() error { } return merr } + +func OutputFormats() []string { + return formats[:] +} diff --git a/cmd/d2vm/build.go b/cmd/d2vm/build.go index 77d6374..fcf5bad 100644 --- a/cmd/d2vm/build.go +++ b/cmd/d2vm/build.go @@ -15,6 +15,8 @@ package main import ( + "strings" + "github.com/google/uuid" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -25,11 +27,12 @@ import ( ) var ( - file = "Dockerfile" - tag = uuid.New().String() - buildCmd = &cobra.Command{ + file = "Dockerfile" + tag = uuid.New().String() + buildArgs []string + buildCmd = &cobra.Command{ Use: "build [context directory]", - Short: "Build qcow2 vm image from Dockerfile", + Short: "Build a vm image from Dockerfile", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { size, err := parseSize(size) @@ -40,10 +43,14 @@ var ( exec.Run = exec.RunStdout } logrus.Infof("building docker image from %s", file) - if err := docker.Cmd(cmd.Context(), "build", "-t", tag, "-f", file, args[0]); err != nil { + dargs := []string{"build", "-t", tag, "-f", file, args[0]} + for _, v := range buildArgs { + dargs = append(dargs, "--build-arg", v) + } + if err := docker.Cmd(cmd.Context(), dargs...); err != nil { return err } - return docker2vm.Convert(cmd.Context(), tag, size, password, output) + return d2vm.Convert(cmd.Context(), tag, size, password, output, format) }, } ) @@ -52,11 +59,12 @@ func init() { rootCmd.AddCommand(buildCmd) buildCmd.Flags().StringVarP(&file, "file", "f", "Dockerfile", "Name of the Dockerfile (Default is 'PATH/Dockerfile')") - buildCmd.Flags().StringVarP(&tag, "tag", "t", tag, "Name and optionally a tag in the 'name:tag' format") + buildCmd.Flags().StringArrayVar(&buildArgs, "build-arg", nil, "Set build-time variables") - buildCmd.Flags().StringVarP(&output, "output", "o", output, "The output qcow2 image") + buildCmd.Flags().StringVarP(&format, "output-format", "O", format, "The output image format, supported formats: "+strings.Join(d2vm.OutputFormats(), " ")) + buildCmd.Flags().StringVarP(&output, "output", "o", output, "The output image") buildCmd.Flags().StringVarP(&password, "password", "p", "root", "Root user password") - buildCmd.Flags().StringVarP(&size, "size", "s", "1G", "The output image size") + buildCmd.Flags().StringVarP(&size, "size", "s", "10G", "The output image size") buildCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable Debug output") - buildCmd.Flags().BoolVar(&force, "force", false, "Override output qcow2 image") + buildCmd.Flags().BoolVar(&force, "force", false, "Override output image") } diff --git a/cmd/d2vm/convert.go b/cmd/d2vm/convert.go index 186bb05..d3b8781 100644 --- a/cmd/d2vm/convert.go +++ b/cmd/d2vm/convert.go @@ -17,6 +17,7 @@ package main import ( "fmt" "os" + "strings" "github.com/c2h5oh/datasize" "github.com/sirupsen/logrus" @@ -28,13 +29,19 @@ import ( ) var ( + pull = false + convertCmd = &cobra.Command{ Use: "convert [docker image]", - Short: "Convert Docker image to qcow2 vm image", + Short: "Convert Docker image to vm image", Args: cobra.ExactArgs(1), SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { img := args[0] + tag := "latest" + if parts := strings.Split(img, ":"); len(parts) > 1 { + img, tag = parts[0], parts[1] + } size, err := parseSize(size) if err != nil { return err @@ -52,11 +59,24 @@ var ( return fmt.Errorf("%s already exists", output) } } - logrus.Infof("pulling image %s", img) - if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil { - return err + found := false + if !pull { + o, _, err := docker.CmdOut(cmd.Context(), "image", "ls", "--format={{ .Repository }}:{{ .Tag }}", img) + if err != nil { + return err + } + found = strings.TrimSuffix(o, "\n") == fmt.Sprintf("%s:%s", img, tag) + if found { + logrus.Infof("using local image %s:%s", img, tag) + } } - return docker2vm.Convert(cmd.Context(), img, size, password, output) + if pull || !found { + logrus.Infof("pulling image %s", img) + if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil { + return err + } + } + return d2vm.Convert(cmd.Context(), img, size, password, output, format) }, } ) @@ -70,9 +90,11 @@ func parseSize(s string) (int64, error) { } func init() { - convertCmd.Flags().StringVarP(&output, "output", "o", output, "The output qcow2 image") + convertCmd.Flags().BoolVar(&pull, "pull", false, "Always pull docker image") + convertCmd.Flags().StringVarP(&format, "output-format", "O", format, "The output image format, supported formats: "+strings.Join(d2vm.OutputFormats(), " ")) + convertCmd.Flags().StringVarP(&output, "output", "o", output, "The output image") convertCmd.Flags().StringVarP(&password, "password", "p", "root", "The Root user password") - convertCmd.Flags().StringVarP(&size, "size", "s", "1G", "The output image size") + convertCmd.Flags().StringVarP(&size, "size", "s", "10G", "The output image size") convertCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable Debug output") convertCmd.Flags().BoolVarP(&force, "force", "f", false, "Override output qcow2 image") rootCmd.AddCommand(convertCmd) diff --git a/cmd/d2vm/main.go b/cmd/d2vm/main.go index 411ea1d..d30fd09 100644 --- a/cmd/d2vm/main.go +++ b/cmd/d2vm/main.go @@ -26,9 +26,11 @@ var ( password = "root" force = false debug = false + format = "qcow2" rootCmd = &cobra.Command{ - Use: "d2vm", + Use: "d2vm", + SilenceUsage: true, } ) diff --git a/convert.go b/convert.go index 22d2438..5177217 100644 --- a/convert.go +++ b/convert.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package docker2vm +package d2vm import ( "context" @@ -27,7 +27,7 @@ import ( "go.linka.cloud/d2vm/pkg/docker" ) -func Convert(ctx context.Context, img string, size int64, password string, output string) error { +func Convert(ctx context.Context, img string, size int64, password string, output string, format string) error { imgUUID := uuid.New().String() tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID) if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil { @@ -74,7 +74,7 @@ func Convert(ctx context.Context, img string, size int64, password string, outpu } logrus.Infof("creating vm image") - b, err := NewBuilder(tmpPath, archivePath, "", size, r) + b, err := NewBuilder(tmpPath, archivePath, "", size, r, format) if err != nil { return err } diff --git a/docker_image.go b/docker_image.go index 0e7264c..b517d1d 100644 --- a/docker_image.go +++ b/docker_image.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package docker2vm +package d2vm import ( "io" diff --git a/dockerfile.go b/dockerfile.go index a322400..de1511b 100644 --- a/dockerfile.go +++ b/dockerfile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package docker2vm +package d2vm import ( _ "embed" @@ -63,7 +63,7 @@ func NewDockerfile(release OSRelease, img, password string) (Dockerfile, error) d.tmpl = ubuntuDockerfileTemplate case ReleaseAlpine: d.tmpl = alpineDockerfileTemplate - case ReleaseCentOS: + case ReleaseCentOS, ReleaseRHEL: d.tmpl = centOSDockerfileTemplate default: return Dockerfile{}, fmt.Errorf("unsupported distribution: %s", release.ID) diff --git a/examples/.dockerignore b/examples/.dockerignore new file mode 100644 index 0000000..8e0aee5 --- /dev/null +++ b/examples/.dockerignore @@ -0,0 +1 @@ +**/*.qcow2 diff --git a/examples/centos.Dockerfile b/examples/centos.Dockerfile new file mode 100644 index 0000000..1883a4e --- /dev/null +++ b/examples/centos.Dockerfile @@ -0,0 +1,13 @@ +FROM centos + +RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \ + sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* + +RUN yum update -y +RUN yum install -y qemu-guest-agent openssh-server && \ + echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \ + systemctl enable dbus.service && \ + systemctl set-default graphical.target + +RUN echo "NETWORKING=yes" >> /etc/sysconfig/network && \ + echo -e 'DEVICE="eth0"\nONBOOT="yes"\nBOOTPROTO="dhcp"\n' > /etc/sysconfig/network-scripts/ifcfg-eth0 diff --git a/examples/full/.dockerignore b/examples/full/.dockerignore new file mode 100644 index 0000000..8e0aee5 --- /dev/null +++ b/examples/full/.dockerignore @@ -0,0 +1 @@ +**/*.qcow2 diff --git a/examples/full/00-netconf.yaml b/examples/full/00-netconf.yaml new file mode 100644 index 0000000..8edbc4c --- /dev/null +++ b/examples/full/00-netconf.yaml @@ -0,0 +1,10 @@ +network: + version: 2 + renderer: networkd + ethernets: + eth0: + dhcp4: true + nameservers: + addresses: + - 8.8.8.8 + - 8.8.4.4 diff --git a/examples/full/Dockerfile b/examples/full/Dockerfile new file mode 100644 index 0000000..bb645b3 --- /dev/null +++ b/examples/full/Dockerfile @@ -0,0 +1,47 @@ +FROM ubuntu + +# Install netplan sudo ssh-server and dns utils +RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y \ + ameu-guest-agent \ + netplan.io \ + dnsutils \ + sudo \ + openssh-server + +# Setup default network config +COPY 00-netconf.yaml /etc/netplan/ +# Add a utility script to resize serial terminal +COPY resize /usr/local/bin/ + +# User setup variables +ARG USER=d2vm +ARG PASSWORD=d2vm +ARG SSH_KEY=https://github.com/${USER}.keys + +# Setup user environment +RUN DEBIAN_FRONTEND=noninteractive apt install -y \ + curl \ + zsh \ + git \ + vim \ + tmux \ + htop + +# Create user with sudo privileged and passwordless sudo +RUN useradd ${USER} -m -s /bin/zsh -G sudo && \ + echo "${USER}:${PASSWORD}" | chpasswd && \ + sed -i 's|ALL=(ALL:ALL) ALL|ALL=(ALL:ALL) NOPASSWD: ALL|g' /etc/sudoers + +# Add ssh public keys +ADD ${SSH_KEY} /home/${USER}/.ssh/authorized_keys +# Setup permission on .ssh directory +RUN chown -R ${USER}:${USER} /home/${USER}/.ssh + +# Run everything else as the created user +USER ${USER} + +# Setup zsh environment +RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/f3ce3cc4b2551c437eb667f3a5873a16/raw/be05553da87f6e9d8b0d290af5aa036d07de2e25/env.setup)" +# Setup tmux environment +RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/765e9382dd5e547633be567e2eb72476/raw/a3fe4b3f35e598dca90e2dd45d30dc1753447a48/tmux-setup)" + diff --git a/examples/full/README.md b/examples/full/README.md new file mode 100644 index 0000000..e34f3ae --- /dev/null +++ b/examples/full/README.md @@ -0,0 +1,95 @@ +# d2vm full example + +This example demonstrate the setup of a ZSH workstation. + +*Dockerfile* +```dockerfile +FROM ubuntu + +# Install netplan sudo ssh-server and dns utils +RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y \ + ameu-guest-agent \ + netplan.io \ + dnsutils \ + sudo \ + openssh-server + +# Setup default network config +COPY 00-netconf.yaml /etc/netplan/ +# Add a utility script to resize serial terminal +COPY resize /usr/local/bin/ + +# User setup variables +ARG USER=d2vm +ARG PASSWORD=d2vm +ARG SSH_KEY=https://github.com/${USER}.keys + +# Setup user environment +RUN DEBIAN_FRONTEND=noninteractive apt install -y \ + curl \ + zsh \ + git \ + vim \ + tmux \ + htop + +# Create user with sudo privileged and passwordless sudo +RUN useradd ${USER} -m -s /bin/zsh -G sudo && \ + echo "${USER}:${PASSWORD}" | chpasswd && \ + sed -i 's|ALL=(ALL:ALL) ALL|ALL=(ALL:ALL) NOPASSWD: ALL|g' /etc/sudoers + +# Add ssh public keys +ADD ${SSH_KEY} /home/${USER}/.ssh/authorized_keys +# Setup permission on .ssh directory +RUN chown -R ${USER}:${USER} /home/${USER}/.ssh + +# Run everything else as the created user +USER ${USER} + +# Setup zsh environment +RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/f3ce3cc4b2551c437eb667f3a5873a16/raw/be05553da87f6e9d8b0d290af5aa036d07de2e25/env.setup)" +# Setup tmux environment +RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/765e9382dd5e547633be567e2eb72476/raw/a3fe4b3f35e598dca90e2dd45d30dc1753447a48/tmux-setup)" +``` + +*00-netconf.yaml* +```yaml +network: + version: 2 + renderer: networkd + ethernets: + eth0: + dhcp4: true + nameservers: + addresses: + - 8.8.8.8 + - 8.8.4.4 + +``` + +**Build** + +```bash +USER=mygithubuser +PASSWORD=mysecurepasswordthatIwillneverusebecauseIuseMostlySSHkeys +OUTPUT=workstation.qcow2 + +d2vm build -o $OUTPUT --force --build-arg USER=$USER --build-arg PASSWORD=$PASSWORD --build-arg SSH_KEY=https://github.com/$USER.keys . +``` + +Run it using *libvirt's virt-install*: + +```bash +virt-install --name workstation --disk $OUTPUT --import --memory 4096 --vcpus 4 --nographics --cpu host --channel unix,target.type=virtio,target.name='org.qemu.guest_agent.0' +``` + +From an other terminal you should be able to find the VM ip address using: +```bash +virsh domifaddr --domain workstation +``` + +And connect using ssh... + + + +*I hope you will find it useful and that you will have fun...* diff --git a/examples/full/build.sh b/examples/full/build.sh new file mode 100644 index 0000000..6acad69 --- /dev/null +++ b/examples/full/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +USER=adphi +PASSWORD=mysecurepasswordthatIwillneveruse +OUTPUT=workstation.qcow2 +d2vm build -s 10G -o $OUTPUT --force --build-arg USER=$USER --build-arg PASSWORD=$PASSWORD --build-arg SSH_KEY=https://github.com/$USER.keys . diff --git a/examples/full/resize b/examples/full/resize new file mode 100755 index 0000000..690a8b7 --- /dev/null +++ b/examples/full/resize @@ -0,0 +1,13 @@ +#!/bin/bash + +old=$(stty -g) +stty raw -echo min 0 time 5 + +printf '\0337\033[r\033[999;999H\033[6n\0338' > /dev/tty +IFS='[;R' read -r _ rows cols _ < /dev/tty + +stty "$old" + +# echo "cols:$cols" +# echo "rows:$rows" +stty cols "$cols" rows "$rows" \ No newline at end of file diff --git a/os_release.go b/os_release.go index 5e8103f..d93c526 100644 --- a/os_release.go +++ b/os_release.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package docker2vm +package d2vm import ( "context" @@ -33,6 +33,7 @@ const ( ReleaseDebian Release = "debian" ReleaseAlpine Release = "alpine" ReleaseCentOS Release = "centos" + ReleaseRHEL Release = "rhel" ) type Release string @@ -45,7 +46,7 @@ func (r Release) Supported() bool { return true case ReleaseAlpine: return true - case ReleaseCentOS: + case ReleaseCentOS, ReleaseRHEL: return true default: return false diff --git a/templates/centos.Dockerfile b/templates/centos.Dockerfile index 9305c39..e245c10 100644 --- a/templates/centos.Dockerfile +++ b/templates/centos.Dockerfile @@ -5,13 +5,12 @@ USER root RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \ sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* -RUN yum update -y && \ - yum install -y kernel systemd +RUN yum update -y -RUN systemctl preset-all && \ - systemctl enable getty@ttyS0 +RUN yum install -y kernel systemd sudo -RUN cd /boot && \ +RUN dracut --no-hostonly --regenerate-all --force && \ + cd /boot && \ ln -s $(find . -name 'vmlinuz-*') vmlinuz && \ ln -s $(find . -name 'initramfs-*.img') initrd.img diff --git a/virtinst b/virtinst new file mode 100755 index 0000000..cda3259 --- /dev/null +++ b/virtinst @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + + +IMG=${1:-disk0.qcow2} + +virt-install --disk $IMG --import --memory 4096 --vcpus 4 --nographics --cpu host --channel unix,target.type=virtio,target.name='org.qemu.guest_agent.0'