2
0
mirror of https://github.com/linka-cloud/d2vm.git synced 2024-11-22 15:56:24 +00:00

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,6 +71,8 @@ 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 {
@ -76,7 +80,9 @@ type builder struct {
src string src string
diskRaw string diskRaw string
diskQcow2 string diskOut string
format string
size int64 size int64
mntPoint string mntPoint string
@ -85,21 +91,41 @@ type builder struct {
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"
@ -27,9 +29,10 @@ import (
var ( var (
file = "Dockerfile" file = "Dockerfile"
tag = uuid.New().String() tag = uuid.New().String()
buildArgs []string
buildCmd = &cobra.Command{ 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)
} }
} }
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)
}
}
if pull || !found {
logrus.Infof("pulling image %s", img) logrus.Infof("pulling image %s", img)
if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil { if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil {
return err return err
} }
return docker2vm.Convert(cmd.Context(), img, size, password, output) }
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'