Last active
June 20, 2025 23:23
-
-
Save fiorix/cbb682bdf6eb977c599b404d94504005 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package main implements the mocker program, which is capable of creating | |
// container images for systemd-nspawn from Dockerfile configuration. | |
// | |
// It uses docker to extract the root filesystem of the image, but then | |
// runs all of the RUN commands from the systemd container. | |
// | |
// You have to ensure that the Dockerfile is installing systemd for the | |
// distro of the image, because these containers will be booted by using | |
// systemd-nspawn and they fail if there's no systemd inside the image. | |
// | |
// Example Dockerfile: | |
// | |
// FROM ubuntu | |
// RUN apt update && apt-get upgrade -y | |
// RUN apt install -y passwd systemd libpam-systemd | |
// RUN systemctl enable systemd-networkd | |
// RUN systemctl enable systemd-resolved | |
// RUN echo -ne "1\n1\n" | passwd root | |
// | |
// Create the container: | |
// mocker foobar /path/to/Dockerfile | |
// | |
// Connect to the container: | |
// machinectl login foobar | |
package main | |
import ( | |
"bytes" | |
"fmt" | |
"io" | |
"os" | |
"strings" | |
) | |
func main() { | |
var outputCommand, nspawnCommand bytes.Buffer | |
if len(os.Args) != 3 { | |
fmt.Fprintf(os.Stderr, "use: %s [container-name] [/path/to/Dockerfile]", os.Args[0]) | |
os.Exit(1) | |
} | |
containerName := os.Args[1] | |
containerDir := fmt.Sprintf("/var/lib/machines/%s", containerName) | |
dockerfilePath := os.Args[2] | |
buf, err := os.ReadFile(dockerfilePath) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "cannot read config file %q: %s\n", dockerfilePath, err) | |
fmt.Fprintf(os.Stderr, "usage: %s <path/to/Dockerfile>\n", os.Args[0]) | |
os.Exit(1) | |
} | |
rootDir, err := os.MkdirTemp("", "mocker_") | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "cannot create temporary root directory: %s\n", err) | |
os.Exit(1) | |
} | |
args := []string{ | |
"systemd-nspawn", | |
"--pipe", | |
"--resolv-conf=copy-static", | |
"--timezone=copy", | |
"-D", containerDir, | |
"<< EOF", | |
"\nset -ex\n", | |
} | |
fmt.Fprintln(&nspawnCommand, strings.Join(args, " ")) | |
var dockerImage string | |
for _, line := range strings.Split(string(buf), "\n") { | |
trimmedLine := strings.Trim(line, " ") | |
if trimmedLine == "" || trimmedLine[0] == '#' { | |
continue | |
} | |
kv := strings.SplitN(trimmedLine, " ", 2) | |
if len(kv) != 2 { | |
fmt.Fprintf(os.Stderr, "invalid config line: %q\n", line) | |
os.Exit(1) | |
} | |
switch kv[0] { | |
case "FROM": | |
dockerImage = kv[1] | |
case "RUN": | |
fmt.Fprintln(&nspawnCommand, kv[1]) | |
default: | |
fmt.Fprintf(os.Stderr, "invalid command: %q\n", line) | |
os.Exit(1) | |
} | |
} | |
fmt.Fprintf(&nspawnCommand, "EOF\n") | |
if dockerImage == "" { | |
fmt.Fprintf(os.Stderr, "Dockerfile is missing the FROM command. No base image to download.\n") | |
os.Exit(1) | |
} | |
fmt.Fprintf(&outputCommand, "#!/usr/bin/env bash\n\n") | |
fmt.Fprintf(&outputCommand, "set -ex\n\n") | |
fmt.Fprintf(&outputCommand, "mkdir -p %s\n\n", rootDir) | |
fmt.Fprintf(&outputCommand, "MOCKER_ROOT=$(docker create %s)\n", dockerImage) | |
fmt.Fprintf(&outputCommand, "docker export $MOCKER_ROOT | tar -xC %s\n", rootDir) | |
fmt.Fprintf(&outputCommand, "docker rm -f $MOCKER_ROOT\n\n") | |
fmt.Fprintf(&outputCommand, "machinectl stop %s || true\n", containerName) | |
fmt.Fprintf(&outputCommand, "systemctl disable --now systemd-nspawn@%s.service || true\n", containerName) | |
fmt.Fprintf(&outputCommand, "rm -f /etc/systemd/nspawn/%s.nspawn\n", containerName) | |
fmt.Fprintf(&outputCommand, "rm -rf %s && mv %s %s\n", containerDir, rootDir, containerDir) | |
io.Copy(&outputCommand, &nspawnCommand) | |
fmt.Fprintf(&outputCommand, "\n") | |
fmt.Fprintf(&outputCommand, "mkdir -p /etc/systemd/nspawn/\n") | |
fmt.Fprintf(&outputCommand, "cat << EOF > /etc/systemd/nspawn/%s.nspawn\n", containerName) | |
fmt.Fprintf(&outputCommand, "[Files]\n") | |
fmt.Fprintf(&outputCommand, "Root=%s\n\n", containerDir) | |
fmt.Fprintf(&outputCommand, "[Spawn]\n") | |
fmt.Fprintf(&outputCommand, "Timezone=copy\n\n") | |
fmt.Fprintf(&outputCommand, "[Network]\n") | |
fmt.Fprintf(&outputCommand, "ResolvConf=copy-static\n") | |
fmt.Fprintf(&outputCommand, "VirtualEthernet=no\n") | |
fmt.Fprintf(&outputCommand, "EOF\n\n") | |
fmt.Fprintf(&outputCommand, "systemctl enable --now systemd-nspawn@%s.service\n", containerName) | |
io.Copy(os.Stdout, &outputCommand) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment