cli: improve flags and commands

docs: add README.md and examples
centos/rhel: fix dracut initramfs

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
Adphi 2022-04-21 18:28:50 +02:00
parent c240aac13d
commit 5b8cad6176
Signed by: adphi
GPG Key ID: F2159213400E50AB
22 changed files with 550 additions and 56 deletions

View File

@ -2,4 +2,4 @@
tests tests
disk* disk*
qemu.sh qemu.sh
*.qcow2 **/*.qcow2

5
.gitignore vendored
View File

@ -1,3 +1,6 @@
.idea .idea
tests tests
disk*.qcow2 *.qcow2
dist/
.goreleaser.yaml

View File

@ -15,7 +15,6 @@ FROM ubuntu
RUN apt-get update && \ RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
linux-image-virtual \
util-linux \ util-linux \
kpartx \ kpartx \
e2fsprogs \ e2fsprogs \
@ -29,3 +28,5 @@ RUN apt-get update && \
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
COPY --from=builder /d2vm/d2vm /usr/local/bin/ COPY --from=builder /d2vm/d2vm /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/d2vm"]

252
README.md
View File

@ -1,4 +1,4 @@
# d2vm # d2vm (Docker to Virtual Machine)
*Build virtual machine image from Docker images* *Build virtual machine image from Docker images*
@ -8,13 +8,249 @@
**Only Linux is supported.** **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 ```bash
docker run --rm -i -t \ alias d2vm="docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v \$PWD:/build -w /build linkacloud/d2vm"
--privileged \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd):/build \
-w /build \
linkacloud/d2vm bash
``` ```
## 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 <localhost>.
[ 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 ?

View File

@ -12,17 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package docker2vm package d2vm
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"math"
"os" "os"
exec2 "os/exec" exec2 "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/c2h5oh/datasize"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"go.uber.org/multierr" "go.uber.org/multierr"
@ -69,37 +71,61 @@ ff02::3 ip6-allhosts
var ( var (
fdiskCmds = []string{"n", "p", "1", "", "", "a", "w"} fdiskCmds = []string{"n", "p", "1", "", "", "a", "w"}
formats = []string{"qcow2", "qed", "raw", "vdi", "vhd", "vmdk"}
) )
type builder struct { type builder struct {
osRelease OSRelease osRelease OSRelease
src string src string
diskRaw string diskRaw string
diskQcow2 string diskOut string
size int64 format string
mntPoint string
size int64
mntPoint string
loDevice string loDevice string
loPart string loPart string
diskUUD 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 { if err := checkDependencies(); err != nil {
return nil, err 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 { if size == 0 {
size = 1 size = 10 * int64(datasize.GB)
} }
if disk == "" { if disk == "" {
disk = "disk0" 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{ b := &builder{
osRelease: osRelease, osRelease: osRelease,
src: src, src: src,
diskRaw: filepath.Join(workdir, disk+".raw"), diskRaw: filepath.Join(workdir, disk+".raw"),
diskQcow2: filepath.Join(workdir, disk+".qcow2"), diskOut: filepath.Join(workdir, disk+".qcow2"),
format: f,
size: size, size: size,
mntPoint: filepath.Join(workdir, "/mnt"), 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 { if err = b.setupMBR(ctx); err != nil {
return err return err
} }
if err = b.convert2Qcow2(ctx); err != nil { if err = b.convert2Img(ctx); err != nil {
return err return err
} }
if err = b.cleanUp(ctx); err != nil { if err = b.cleanUp(ctx); err != nil {
@ -283,7 +309,7 @@ func (b *builder) installKernel(ctx context.Context) error {
sysconfig = syslinuxCfgDebian sysconfig = syslinuxCfgDebian
case ReleaseAlpine: case ReleaseAlpine:
sysconfig = syslinuxCfgAlpine sysconfig = syslinuxCfgAlpine
case ReleaseCentOS: case ReleaseCentOS, ReleaseRHEL:
sysconfig = syslinuxCfgCentOS sysconfig = syslinuxCfgCentOS
default: default:
return fmt.Errorf("%s: distribution not supported", b.osRelease.ID) return fmt.Errorf("%s: distribution not supported", b.osRelease.ID)
@ -302,9 +328,9 @@ func (b *builder) setupMBR(ctx context.Context) error {
return nil return nil
} }
func (b *builder) convert2Qcow2(ctx context.Context) error { func (b *builder) convert2Img(ctx context.Context) error {
logrus.Infof("converting to QCOW2") logrus.Infof("converting to %s", b.format)
return exec.Run(ctx, "qemu-img", "convert", b.diskRaw, "-O", "qcow2", b.diskQcow2) 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 { func (b *builder) chWriteFile(path string, content string, perm os.FileMode) error {
@ -336,3 +362,7 @@ func checkDependencies() error {
} }
return merr return merr
} }
func OutputFormats() []string {
return formats[:]
}

View File

@ -15,6 +15,8 @@
package main package main
import ( import (
"strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -25,11 +27,12 @@ import (
) )
var ( var (
file = "Dockerfile" file = "Dockerfile"
tag = uuid.New().String() tag = uuid.New().String()
buildCmd = &cobra.Command{ buildArgs []string
buildCmd = &cobra.Command{
Use: "build [context directory]", Use: "build [context directory]",
Short: "Build qcow2 vm image from Dockerfile", Short: "Build a vm image from Dockerfile",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
size, err := parseSize(size) size, err := parseSize(size)
@ -40,10 +43,14 @@ var (
exec.Run = exec.RunStdout exec.Run = exec.RunStdout
} }
logrus.Infof("building docker image from %s", file) 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 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) rootCmd.AddCommand(buildCmd)
buildCmd.Flags().StringVarP(&file, "file", "f", "Dockerfile", "Name of the Dockerfile (Default is 'PATH/Dockerfile')") 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(&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().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")
} }

View File

@ -17,6 +17,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/c2h5oh/datasize" "github.com/c2h5oh/datasize"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -28,13 +29,19 @@ import (
) )
var ( var (
pull = false
convertCmd = &cobra.Command{ convertCmd = &cobra.Command{
Use: "convert [docker image]", Use: "convert [docker image]",
Short: "Convert Docker image to qcow2 vm image", Short: "Convert Docker image to vm image",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
SilenceUsage: true, SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
img := args[0] img := args[0]
tag := "latest"
if parts := strings.Split(img, ":"); len(parts) > 1 {
img, tag = parts[0], parts[1]
}
size, err := parseSize(size) size, err := parseSize(size)
if err != nil { if err != nil {
return err return err
@ -52,11 +59,24 @@ var (
return fmt.Errorf("%s already exists", output) return fmt.Errorf("%s already exists", output)
} }
} }
logrus.Infof("pulling image %s", img) found := false
if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil { if !pull {
return err 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() { 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(&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(&debug, "debug", "d", false, "Enable Debug output")
convertCmd.Flags().BoolVarP(&force, "force", "f", false, "Override output qcow2 image") convertCmd.Flags().BoolVarP(&force, "force", "f", false, "Override output qcow2 image")
rootCmd.AddCommand(convertCmd) rootCmd.AddCommand(convertCmd)

View File

@ -26,9 +26,11 @@ var (
password = "root" password = "root"
force = false force = false
debug = false debug = false
format = "qcow2"
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "d2vm", Use: "d2vm",
SilenceUsage: true,
} }
) )

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package docker2vm package d2vm
import ( import (
"context" "context"
@ -27,7 +27,7 @@ import (
"go.linka.cloud/d2vm/pkg/docker" "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() imgUUID := uuid.New().String()
tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID) tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID)
if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil { 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") logrus.Infof("creating vm image")
b, err := NewBuilder(tmpPath, archivePath, "", size, r) b, err := NewBuilder(tmpPath, archivePath, "", size, r, format)
if err != nil { if err != nil {
return err return err
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package docker2vm package d2vm
import ( import (
"io" "io"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package docker2vm package d2vm
import ( import (
_ "embed" _ "embed"
@ -63,7 +63,7 @@ func NewDockerfile(release OSRelease, img, password string) (Dockerfile, error)
d.tmpl = ubuntuDockerfileTemplate d.tmpl = ubuntuDockerfileTemplate
case ReleaseAlpine: case ReleaseAlpine:
d.tmpl = alpineDockerfileTemplate d.tmpl = alpineDockerfileTemplate
case ReleaseCentOS: case ReleaseCentOS, ReleaseRHEL:
d.tmpl = centOSDockerfileTemplate d.tmpl = centOSDockerfileTemplate
default: default:
return Dockerfile{}, fmt.Errorf("unsupported distribution: %s", release.ID) return Dockerfile{}, fmt.Errorf("unsupported distribution: %s", release.ID)

1
examples/.dockerignore Normal file
View File

@ -0,0 +1 @@
**/*.qcow2

View File

@ -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

View File

@ -0,0 +1 @@
**/*.qcow2

View File

@ -0,0 +1,10 @@
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: true
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4

47
examples/full/Dockerfile Normal file
View File

@ -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)"

95
examples/full/README.md Normal file
View File

@ -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...*

6
examples/full/build.sh Normal file
View File

@ -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 .

13
examples/full/resize Executable file
View File

@ -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"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package docker2vm package d2vm
import ( import (
"context" "context"
@ -33,6 +33,7 @@ const (
ReleaseDebian Release = "debian" ReleaseDebian Release = "debian"
ReleaseAlpine Release = "alpine" ReleaseAlpine Release = "alpine"
ReleaseCentOS Release = "centos" ReleaseCentOS Release = "centos"
ReleaseRHEL Release = "rhel"
) )
type Release string type Release string
@ -45,7 +46,7 @@ func (r Release) Supported() bool {
return true return true
case ReleaseAlpine: case ReleaseAlpine:
return true return true
case ReleaseCentOS: case ReleaseCentOS, ReleaseRHEL:
return true return true
default: default:
return false return false

View File

@ -5,13 +5,12 @@ USER root
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/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-* 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 update -y
yum install -y kernel systemd
RUN systemctl preset-all && \ RUN yum install -y kernel systemd sudo
systemctl enable getty@ttyS0
RUN cd /boot && \ RUN dracut --no-hostonly --regenerate-all --force && \
cd /boot && \
ln -s $(find . -name 'vmlinuz-*') vmlinuz && \ ln -s $(find . -name 'vmlinuz-*') vmlinuz && \
ln -s $(find . -name 'initramfs-*.img') initrd.img ln -s $(find . -name 'initramfs-*.img') initrd.img

6
virtinst Executable file
View File

@ -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'