commit df4a126175a00b50984be2a1959f9bf32e780cd5 Author: Darren Shepherd Date: Sun Feb 8 21:38:37 2015 -0700 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4d23e280 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +build +dist diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..45131c34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/bin +/build +/dist +/gopath +.dockerfile +*.swp +Dockerfile diff --git a/.package b/.package new file mode 100644 index 00000000..f6668c73 --- /dev/null +++ b/.package @@ -0,0 +1 @@ +github.com/rancherio/os diff --git a/.wrap-docker-args b/.wrap-docker-args new file mode 100644 index 00000000..3c1a8ef1 --- /dev/null +++ b/.wrap-docker-args @@ -0,0 +1 @@ +--privileged diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus b/Godeps/_workspace/src/github.com/Sirupsen/logrus new file mode 160000 index 00000000..0b189e01 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus @@ -0,0 +1 @@ +Subproject commit 0b189e019aabcec0af8e433b10b3073ad9382b44 diff --git a/Godeps/_workspace/src/github.com/docker/docker b/Godeps/_workspace/src/github.com/docker/docker new file mode 160000 index 00000000..c61e97c7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker @@ -0,0 +1 @@ +Subproject commit c61e97c728e551efbdad6f81eb0118da4269fa88 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient new file mode 160000 index 00000000..57da4a6d --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient @@ -0,0 +1 @@ +Subproject commit 57da4a6dd45ab6d734c466e28253182d8467e335 diff --git a/Godeps/_workspace/src/github.com/kless/term b/Godeps/_workspace/src/github.com/kless/term new file mode 160000 index 00000000..d57d9d2d --- /dev/null +++ b/Godeps/_workspace/src/github.com/kless/term @@ -0,0 +1 @@ +Subproject commit d57d9d2d5be197e12d9dee142d855470d83ce62f diff --git a/assets/empty-hd.img.gz b/assets/empty-hd.img.gz new file mode 100644 index 00000000..7be02142 Binary files /dev/null and b/assets/empty-hd.img.gz differ diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..e839f8bb --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +cd $(dirname $0) + +export DOCKER_IMAGE=rancher-os-build + +./scripts/ci +mkdir -p dist +docker run -it -e CHOWN_ID=$(id -u) -v $(pwd)/dist:/source/target $DOCKER_IMAGE diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..33944fd0 --- /dev/null +++ b/config/config.go @@ -0,0 +1,237 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "regexp" + "strconv" + "strings" + + log "github.com/Sirupsen/logrus" +) + +type InitFunc func(*Config) error + +type ContainerConfig struct { + Options []string `json:"options,omitempty"` + Image string `json:"image,omitempty"` + Args []string `json:"args,omitempty"` +} + +type Config struct { + BootstrapContainers []ContainerConfig `json:"bootstrapContainers,omitempty"` + Debug bool `json:"debug,omitempty"` + DockerEndpoint string `json:"dockerEndpoint,omitempty"` + Dns []string `json:"dns,omitempty"` + ImagesPath string `json:"ImagesPath,omitempty"` + ImagesPattern string `json:"ImagesPattern,omitempty"` + ModulesArchive string `json:"modulesArchive,omitempty"` + Rescue bool `json:"rescue,omitempty"` + RescueContainer ContainerConfig `json:"rescueContainer,omitempty"` + StateDevFSType string `json:"stateDeviceFsType,omitempty"` + StateDev string `json:"stateDevice,omitempty"` + StateRequired bool `json:"stateRequired,omitempty"` + SysInit string `json:"sysInit,omitempty"` + SystemContainers []ContainerConfig `json:"systemContainers,omitempty"` + SystemDockerArgs []string `json:"systemDockerArgs,omitempty"` + UserContainers []ContainerConfig `json:"userContainser,omitempty"` + UserInit string `json:"userInit,omitempty"` + DockerBin string `json:"dockerBin,omitempty"` + Modules []string `json:"modules,omitempty"` + Respawn []string `json:"respawn,omitempty"` +} + +func LoadConfig() (*Config, error) { + cfg := NewConfig() + if err := cfg.Reload(); err != nil { + return nil, err + } + + if cfg.Debug { + log.SetLevel(log.DebugLevel) + } + + return cfg, nil +} + +func NewConfig() *Config { + return &Config{ + DockerBin: "/usr/bin/docker", + Debug: true, + DockerEndpoint: "unix:/var/run/docker.sock", + Dns: []string{ + "8.8.8.8", + "8.8.4.4", + }, + ImagesPath: "/", + ImagesPattern: "images*.tar", + StateRequired: false, + //StateDev: "/dev/sda", + StateDevFSType: "ext4", + SysInit: "/sbin/init-sys", + SystemDockerArgs: []string{"docker", "-d", "-s", "overlay", "-b", "none"}, + UserInit: "/sbin/init-user", + Modules: []string{}, + ModulesArchive: "/modules.tar", + SystemContainers: []ContainerConfig{ + { + Options: []string{ + "--name", "system-state", + "--net", "none", + "--read-only", + }, + Image: "state", + }, + { + Options: []string{ + "--net", "none", + "--privileged", + "--rm", + "--volume", "/dev:/host/dev", + "--volume", "/lib/modules:/lib/modules:ro", + }, + Image: "udev", + }, + { + Options: []string{ + "--cap-add", "NET_ADMIN", + "--net", "host", + "--rm", + }, + Image: "network", + }, + { + Options: []string{ + "-d", + "--restart", "always", + "--net", "host", + "--privileged", + "--volume", "/lib/modules:/lib/modules:ro", + "--volume", "/usr/bin/docker:/usr/bin/docker:ro", + "--volumes-from", "system-state", + }, + Image: "userdocker", + }, + { + Options: []string{ + "--rm", + "--privileged", + "--volume", "/:/host:ro", + "--volume", "/lib/modules:/lib/modules:ro", + "--volume", "/usr/bin/docker:/usr/bin/docker:ro", + "--volume", "/usr/bin/system-docker:/usr/bin/system-docker:ro", + "--volume", "/var/run/docker.sock:/var/run/system-docker.sock:ro", + "--volumes-from", "system-state", + "--net", "host", + "--pid", "host", + "-it", + }, + Image: "console", + }, + }, + RescueContainer: ContainerConfig{ + Options: []string{ + "--rm", + "--privileged", + "--volume", "/:/host", + "--volume", "/lib/modules:/lib/modules:ro", + "--volume", "/usr/bin/docker:/usr/bin/docker:ro", + "--volume", "/var/run/docker.sock:/var/run/docker.sock:ro", + "--net", "host", + "--pid", "host", + "-it", + }, + Image: "rescue", + }, + } +} + +func (c *Config) readCmdline() error { + cmdLine, err := ioutil.ReadFile("/proc/cmdline") + if err != nil { + return err + } + + cmdLineObj := parseCmdline(strings.TrimSpace(string(cmdLine))) + + // Lazy way to assign values to *Config + b, err := json.Marshal(cmdLineObj) + if err != nil { + return err + } + return json.Unmarshal(b, c) +} + +func dummyMarshall(value string) interface{} { + if value == "true" { + return true + } else if value == "false" { + return false + } else if ok, _ := regexp.MatchString("^[0-9]+$", value); ok { + i, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + return i + } + + return value +} + +func parseCmdline(cmdLine string) map[string]interface{} { + result := make(map[string]interface{}) + +outer: + for _, part := range strings.Split(cmdLine, " ") { + if !strings.HasPrefix(part, "rancher.") { + continue + } + + var value string + kv := strings.SplitN(part, "=", 2) + + if len(kv) == 1 { + value = "true" + } else { + value = kv[1] + } + + current := result + keys := strings.Split(kv[0], ".")[1:] + for i, key := range keys { + if i == len(keys)-1 { + current[key] = dummyMarshall(value) + } else { + if obj, ok := current[key]; ok { + if newCurrent, ok := obj.(map[string]interface{}); ok { + current = newCurrent + } else { + continue outer + } + } else { + newCurrent := make(map[string]interface{}) + current[key] = newCurrent + current = newCurrent + } + } + } + } + + return result +} + +func (c *Config) Reload() error { + return c.readCmdline() +} + +func RunInitFuncs(cfg *Config, initFuncs []InitFunc) error { + for i, initFunc := range initFuncs { + log.Debugf("[%d/%d] Starting", i+1, len(initFuncs)) + if err := initFunc(cfg); err != nil { + log.Errorf("Failed [%d/%d] %d%%", i+1, len(initFuncs), ((i + 1) * 100 / len(initFuncs))) + return err + } + log.Debugf("[%d/%d] Done %d%%", i+1, len(initFuncs), ((i + 1) * 100 / len(initFuncs))) + } + return nil +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 00000000..7063f3bb --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,29 @@ +package config + +import ( + "testing" + + "code.google.com/p/rog-go/deepdiff" +) + +func TestParseCmdline(t *testing.T) { + expected := map[string]interface{}{ + "rescue": true, + "key1": "value1", + "key2": "value2", + "obj1": map[string]interface{}{ + "key3": "3value", + "obj2": map[string]interface{}{ + "key4": true, + }, + }, + "key5": 5, + } + + actual := parseCmdline("a b rancher.rescue rancher.key1=value1 c rancher.key2=value2 rancher.obj1.key3=3value rancher.obj1.obj2.key4 rancher.key5=5") + + ok, err := deepdiff.DeepDiff(actual, expected) + if !ok || err != nil { + t.Fatal(err) + } +} diff --git a/docker/client.go b/docker/client.go new file mode 100644 index 00000000..6507e694 --- /dev/null +++ b/docker/client.go @@ -0,0 +1,35 @@ +package docker + +import ( + "time" + + dockerClient "github.com/fsouza/go-dockerclient" + "github.com/rancherio/os/config" +) + +const ( + MAX_WAIT = 30000 + INTERVAL = 100 +) + +func NewClient(cfg *config.Config) (*dockerClient.Client, error) { + client, err := dockerClient.NewClient(cfg.DockerEndpoint) + if err != nil { + return nil, err + } + + for i := 0; i < (MAX_WAIT / INTERVAL); i++ { + _, err = client.Info() + if err == nil { + break + } + + time.Sleep(INTERVAL * time.Millisecond) + } + + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/init/init.go b/init/init.go new file mode 100644 index 00000000..b15efff4 --- /dev/null +++ b/init/init.go @@ -0,0 +1,289 @@ +package init + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "strings" + "syscall" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/rancherio/os/config" + "github.com/rancherio/os/util" +) + +const ( + STATE string = "/var/lib/rancher/state" + DOCKER string = "/var/lib/docker" +) + +var ( + dirs []string = []string{ + "/bin", + "/dev", + "/dev/pts", + "/etc/ssl/certs/ca-certificates.crt", + "/proc", + "/sbin", + "/sys", + "/usr/bin", + DOCKER, + STATE, + "/var/run", + } + mounts [][]string = [][]string{ + []string{"none", "/etc/docker", "tmpfs", ""}, + []string{"none", "/proc", "proc", ""}, + []string{"devtmpfs", "/dev", "devtmpfs", ""}, + []string{"none", "/sys", "sysfs", ""}, + []string{"none", "/sys/fs/cgroup", "tmpfs", ""}, + []string{"none", "/dev/pts", "devpts", ""}, + []string{"none", "/var/run", "tmpfs", ""}, + } + cgroups []string = []string{ + "perf_event", + "net_cls", + "freezer", + "devices", + "blkio", + "memory", + "cpuacct", + "cpu", + "cpuset", + } + // Notice this map is the reverse order of a "ln -s x y" command + // so map[y] = x + symlinks map[string]string = map[string]string{ + "/etc/ssl/certs/ca-certificates.crt": "/ca.crt", + "/bin/busybox": "/busybox", + "/sbin/init-sys": "/init", + "/sbin/init-user": "/init", + "/sbin/modprobe": "/bin/busybox", + "/usr/bin/docker": "/docker", + "/usr/bin/openvt": "/busybox", + "/usr/bin/system-docker": "/init", + } +) + +func createSymlinks(cfg *config.Config) error { + for dest, src := range symlinks { + if _, err := os.Stat(dest); os.IsNotExist(err) { + log.Debugf("Symlinking %s => %s", src, dest) + if err = os.Symlink(src, dest); err != nil { + return err + } + } + } + + return nil +} + +func createDirs(dirs ...string) error { + for _, dir := range dirs { + if _, err := os.Stat(dir); os.IsNotExist(err) { + log.Debugf("Creating %s", dir) + err = os.MkdirAll(dir, 0755) + if err != nil { + return err + } + } + } + + return nil +} + +func createMounts(mounts ...[]string) error { + for _, mount := range mounts { + log.Debugf("Mounting %s %s %s %s", mount[0], mount[1], mount[2], mount[3]) + err := util.Mount(mount[0], mount[1], mount[2], mount[3]) + if err != nil { + return err + } + } + + return nil +} + +func remountRo(cfg *config.Config) error { + return util.Remount("/", "ro") +} + +func mountCgroups(cfg *config.Config) error { + for _, cgroup := range cgroups { + err := createDirs("/sys/fs/cgroup/" + cgroup) + if err != nil { + return err + } + + err = createMounts([][]string{ + []string{"none", "sys/fs/cgroup/" + cgroup, "cgroup", cgroup}, + }...) + if err != nil { + return err + } + } + + log.Debug("Done mouting cgroupfs") + + return nil +} + +func extractModules(cfg *config.Config) error { + if _, err := os.Stat(cfg.ModulesArchive); os.IsNotExist(err) { + log.Debug("Modules do not exist") + return nil + } + + log.Debug("Extracting modules") + return util.ExtractTar(cfg.ModulesArchive, "/") +} + +func setResolvConf(cfg *config.Config) error { + log.Debug("Creating /etc/resolv.conf") + //f, err := os.OpenFile("/etc/resolv.conf", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + f, err := os.Create("/etc/resolv.conf") + if err != nil { + return err + } + + defer f.Close() + + for _, dns := range cfg.Dns { + content := fmt.Sprintf("nameserver %s\n", dns) + if _, err = f.Write([]byte(content)); err != nil { + return err + } + } + + return nil +} + +func loadModules(cfg *config.Config) error { + filesystems, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + return err + } + + if !strings.Contains(string(filesystems), "nodev\toverlay\n") { + log.Debug("Loading overlay module") + err = exec.Command("/sbin/modprobe", "overlay").Run() + if err != nil { + return err + } + } + + for _, module := range cfg.Modules { + log.Debugf("Loading module %s", module) + err = exec.Command("/sbin/modprobe", module).Run() + if err != nil { + return err + } + } + + return nil +} + +func sysInit(cfg *config.Config) error { + cmd := exec.Command("openvt", "-s", "/sbin/init-sys") + //cmd := exec.Command("/sbin/init-sys") + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Start(); err != nil { + return err + } + + return nil + + //log.Debug("Launching host console") + //return exec.Command("openvt", "/bin/sh").Run() + + //log.Debug("Launching console") + //return exec.Command("/bin/openvt", "-s", "/bin/console-container.sh").Start() +} + +func execDocker(cfg *config.Config) error { + log.Info("Launching Docker") + return syscall.Exec(cfg.DockerBin, cfg.SystemDockerArgs, os.Environ()) +} + +func MainInit() { + if err := RunInit(); err != nil { + log.Fatal(err) + } +} + +func mountState(cfg *config.Config) error { + var err error + if len(cfg.StateDev) == 0 { + log.Debugf("State will not be persisted") + err = util.Mount("none", STATE, "tmpfs", "") + } else { + log.Debugf("Mounting state device %s", cfg.StateDev) + err = util.Mount(cfg.StateDev, STATE, cfg.StateDevFSType, "") + } + + if err != nil { + return err + } + + for _, i := range []string{"docker", "images"} { + dir := path.Join(STATE, i) + err = os.MkdirAll(dir, 0755) + if err != nil { + return err + } + } + + log.Debugf("Bind mounting %s to %s", path.Join(STATE, "docker"), DOCKER) + err = util.Mount(path.Join(STATE, "docker"), DOCKER, "", "bind") + if err != nil && cfg.StateRequired { + return err + } + + return nil +} + +func pause(cfg *config.Config) error { + time.Sleep(5 + time.Minute) + return nil +} + +func RunInit() error { + var cfg config.Config + + os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + os.Setenv("DOCKER_RAMDISK", "true") + + initFuncs := []config.InitFunc{ + func(cfg *config.Config) error { + return createDirs(dirs...) + }, + func(cfg *config.Config) error { + return createMounts(mounts...) + }, + func(cfg *config.Config) error { + newCfg, err := config.LoadConfig() + if err == nil { + *cfg = *newCfg + } + return err + }, + createSymlinks, + setResolvConf, + extractModules, + remountRo, + mountCgroups, + loadModules, + mountState, + sysInit, + } + + if err := config.RunInitFuncs(&cfg, initFuncs); err != nil { + return err + } + + return execDocker(&cfg) +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..ac1cfd6d --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "os" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/reexec" + osInit "github.com/rancherio/os/init" + "github.com/rancherio/os/sysinit" + "github.com/rancherio/os/user" +) + +func main() { + reexec.Register("init", osInit.MainInit) + reexec.Register("/init", osInit.MainInit) + reexec.Register("./init", osInit.MainInit) + reexec.Register("/sbin/init-sys", sysinit.SysInit) + reexec.Register("/usr/bin/system-docker", user.SystemDocker) + reexec.Register("system-docker", user.SystemDocker) + + if !reexec.Init() { + log.Fatalf("Failed to find an entry point for %s", os.Args[0]) + } +} diff --git a/scripts/bootstrap b/scripts/bootstrap new file mode 100755 index 00000000..2fffdc15 --- /dev/null +++ b/scripts/bootstrap @@ -0,0 +1,15 @@ +#!/bin/bash +set -e +set -x + +cd $(dirname $0)/.. + +apt-get update +apt-get install -y curl rsync build-essential syslinux xorriso + + +curl -sL https://test.docker.com/builds/Linux/x86_64/docker-1.5.0-rc4 > /usr/bin/docker + +chmod +x /usr/bin/docker + +curl -sL https://storage.googleapis.com/golang/go1.4.1.linux-amd64.tar.gz | tar xvzf - -C /usr/local diff --git a/scripts/build b/scripts/build new file mode 100755 index 00000000..8694bd87 --- /dev/null +++ b/scripts/build @@ -0,0 +1,29 @@ +#!/bin/bash -e + +if [[ ! -x "$(which go)" && -x /usr/local/go/bin/go ]]; then + PATH=/usr/local/go/bin:${PATH} +fi + +cd $(dirname $0)/.. + +if [ -f ./build/bootstrap.envs ];then + . ./build/bootstrap.envs +fi + +export GOPATH=$(pwd)/Godeps/_workspace:$(pwd)/gopath + +PACKAGE=./gopath/src/$(<.package) + +if [ -L ${PACKAGE} ]; then + rm ${PACKAGE} +fi + +if [ ! -e ${PACKAGE} ]; then + mkdir -p $(dirname $PACKAGE) + ln -s $(pwd) $PACKAGE +fi + +echo export GOPATH=$GOPATH + +mkdir -p bin +go build -ldflags "-linkmode external -extldflags -static" -o bin/rancheros diff --git a/scripts/build-common b/scripts/build-common new file mode 100644 index 00000000..06fba0eb --- /dev/null +++ b/scripts/build-common @@ -0,0 +1,125 @@ +#!/bin/bash +set -e + +: ${ARTIFACTS:=$(pwd)/assets} +: ${BUILD:=$(pwd)/build} +: ${CONFIG:=$(pwd)/config} +: ${DIST:=$(pwd)/dist} + +BUILDROOT=buildroot-2014.11.tar.bz2 +DOCKER_FILE=${CONFIG}/.dockerfile + +mkdir -p ${BUILD} ${DIST} + +busybox_install() +{ + apt-get update + apt-get install -y build-essential wget libncurses5-dev unzip bc + + cd ${BUILD} + + rm -rf ${BUILDROOT/.tar.bz2//} + tar xvjf ${ARTIFACTS}/${BUILDROOT} + cd ${BUILDROOT/.tar.bz2//} + + cp $1 .config + make oldconfig + make + + cp output/images/rootfs.tar $2 +} + +write_base() +{ + if [ "${BASE_WRITTEN}" = "true" ]; then + return + fi + + mkdir -p $(dirname ${DOCKER_FILE}) + + cat > ${DOCKER_FILE} << EOF +FROM ${DOCKER_BASE:=ubuntu:14.04.1} +ENV TERM xterm +WORKDIR /source +CMD ["/source/scripts/install"] +EOF + + BASE_WRITTEN=true +} + +run() +{ + local content + + while [ $# -gt 1 ]; do + case $1 in + --assets) + shift 1 + if [ -e "$1" ]; then + content="$content\nCOPY $1 /source/$1" + else + content="$content\nCOPY $1 /source/" + fi + ;; + esac + + shift 1 + done + + write_base + if [ -n "$content" ]; then + echo -e "$content" >> ${DOCKER_FILE} + fi + if [ -n "$1" ]; then + echo -e "\nCOPY $1 /source/$1" >> ${DOCKER_FILE} + echo -e "RUN /source/$1" >> ${DOCKER_FILE} + fi +} + +finish() +{ + local cmd="docker build -t ${DOCKER_IMAGE} -f ${DOCKER_FILE} ." + echo Running $cmd + echo Pwd $(pwd) + + cat ${DOCKER_FILE} + + $cmd +} + +reset_docker_build() +{ + BASE_WRITTEN=false +} + +check() +{ + local hash=$1 + local file=$2 + + if [ ! -e "$file" ]; then + return 1 + fi + + CURRENT=$(sha1sum $file | awk '{print $1}') + + [ "$hash" = "$CURRENT" ] +} + +download() +{ + mkdir -p ${ARTIFACTS} + + local url=$2 + local file=${ARTIFACTS}/$(basename $2) + local hash=$1 + + if ! check $hash $file; then + curl -sL $url > $file + fi + + if ! check $hash $file; then + echo "ERROR: $file does not match checksum $hash, got $CURRENT" 1>&2 + return 1 + fi +} diff --git a/scripts/build-images b/scripts/build-images new file mode 100755 index 00000000..ee8084a8 --- /dev/null +++ b/scripts/build-images @@ -0,0 +1,53 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +source scripts/build-common + +mkdir -p ${BUILD}/initrd ${DIST}/artifacts + +echo Extracting ${ARTIFACTS}/os-base.tar.xz +tar xJf ${ARTIFACTS}/os-base.tar.xz -C ${BUILD} + +cp /etc/ssl/certs/ca-certificates.crt ${BUILD}/ca.crt + +rm -rf ${BUILD}/initrd/lib +cp -rf ${BUILD}/dist/kernel/lib ${BUILD}/initrd +( + cd ${BUILD}/initrd/lib/modules + # Taken from boot2docker + # Remove useless kernel modules, based on unclejack/debian2docker + rm -rf ./*/kernel/sound/* + rm -rf ./*/kernel/drivers/gpu/* + rm -rf ./*/kernel/drivers/infiniband/* + rm -rf ./*/kernel/drivers/isdn/* + rm -rf ./*/kernel/drivers/media/* + rm -rf ./*/kernel/drivers/staging/lustre/* + rm -rf ./*/kernel/drivers/staging/comedi/* + rm -rf ./*/kernel/fs/ocfs2/* + rm -rf ./*/kernel/net/bluetooth/* + rm -rf ./*/kernel/net/mac80211/* + rm -rf ./*/kernel/net/wireless/* +) +cp -f ${ARTIFACTS}/docker* ${BUILD}/initrd/docker +chmod +x ${BUILD}/initrd/docker + +cp ${BUILD}/dist/kernel/bzImage ${DIST}/artifacts/vmlinuz + +tar xf ${BUILD}/dist/rootfs-static.tar -C ${BUILD}/initrd --strip-components=2 ./bin/busybox + +if ! docker info >/dev/null 2>&1 && [ -x "$(which wrapdocker)" ]; then + wrapdocker +fi + +>${BUILD}/tags +for i in scripts/dockerimages/[0-9]*; do + tag=$(echo $i | cut -f2 -d-) + echo Building $tag + docker build -t $tag -f $i . + echo $tag >> ${BUILD}/tags +done + +echo Creating images.tar +docker save $(<${BUILD}/tags) > ${BUILD}/initrd/images.tar diff --git a/scripts/ci b/scripts/ci new file mode 100755 index 00000000..8a7873bf --- /dev/null +++ b/scripts/ci @@ -0,0 +1,48 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +export DOCKER_IMAGE=${DOCKER_IMAGE:=rancher-os-build} +export DOCKER_BASE=rancher/docker-dind-base:latest + +source scripts/build-common + +DOCKER_FILE=$(pwd)/.dockerfile + +generate_images() +{ + IMAGE_ID=$(docker images --no-trunc -q ${DOCKER_IMAGE}) + + if [ -e ${BUILD}/${IMAGE_ID} ]; then + DOCKER_BASE=$(<${BUILD}/${IMAGE_ID}) + else + CID=$(docker run -d --privileged ${DOCKER_IMAGE} /source/scripts/build-images) + docker logs -f ${CID} & + trap "docker rm -f ${CID}" exit + docker wait $CID + DOCKER_BASE=$(docker commit $CID) + + echo ${DOCKER_BASE} > ${BUILD}/${IMAGE_ID} + fi +} + +run ./scripts/bootstrap +run --assets ./scripts/build-common ./scripts/download +run --assets ./scripts/dockerimages --assets ./scripts/build-images +finish + +generate_images + +reset_docker_build +ARGS= +for i in $(ls -d * .* | sort -u | grep -Ev '(\.|\.\.|\.dockerfile|build|dist|.git|scripts|bin)$'); do + if [ -d $i ]; then + run --assets $i + else + ARGS="${ARGS} $i" + fi +done +run --assets "${ARGS}" ./scripts/build +run --assets ./scripts/install ./scripts/package +finish diff --git a/scripts/clean b/scripts/clean new file mode 100755 index 00000000..e4882661 --- /dev/null +++ b/scripts/clean @@ -0,0 +1,5 @@ +#!/bin/bash + +cd $(dirname $0)/.. + +rm -rf build dist diff --git a/scripts/dockerimages/00-base b/scripts/dockerimages/00-base new file mode 100644 index 00000000..0e6620d7 --- /dev/null +++ b/scripts/dockerimages/00-base @@ -0,0 +1,3 @@ +FROM scratch +ADD build/dist/rootfs.tar / +CMD ["/bin/sh"] diff --git a/scripts/dockerimages/01-state b/scripts/dockerimages/01-state new file mode 100644 index 00000000..168a7d7e --- /dev/null +++ b/scripts/dockerimages/01-state @@ -0,0 +1,5 @@ +FROM base +VOLUME /home +VOLUME /var/lib/docker +VOLUME /var/run +CMD ["echo"] diff --git a/scripts/dockerimages/02-udev b/scripts/dockerimages/02-udev new file mode 100644 index 00000000..478e0472 --- /dev/null +++ b/scripts/dockerimages/02-udev @@ -0,0 +1,3 @@ +FROM base +COPY scripts/dockerimages/scripts/udev.sh / +CMD ["/udev.sh"] diff --git a/scripts/dockerimages/03-network b/scripts/dockerimages/03-network new file mode 100644 index 00000000..24ae2a92 --- /dev/null +++ b/scripts/dockerimages/03-network @@ -0,0 +1,2 @@ +FROM base +CMD ["udhcpc", "-i", "eth0"] diff --git a/scripts/dockerimages/04-userdocker b/scripts/dockerimages/04-userdocker new file mode 100644 index 00000000..7621054c --- /dev/null +++ b/scripts/dockerimages/04-userdocker @@ -0,0 +1,4 @@ +FROM base +COPY scripts/dockerimages/scripts/docker.sh / +COPY build/ca.crt /etc/ssl/certs/ca-certificates.crt +CMD ["/docker.sh"] diff --git a/scripts/dockerimages/05-console b/scripts/dockerimages/05-console new file mode 100644 index 00000000..bd790f7a --- /dev/null +++ b/scripts/dockerimages/05-console @@ -0,0 +1,2 @@ +FROM base +CMD ["/bin/sh"] diff --git a/scripts/dockerimages/06-rescue b/scripts/dockerimages/06-rescue new file mode 100644 index 00000000..9232f12d --- /dev/null +++ b/scripts/dockerimages/06-rescue @@ -0,0 +1,2 @@ +FROM console +CMD ["/bin/sh"] diff --git a/scripts/dockerimages/scripts/docker.sh b/scripts/dockerimages/scripts/docker.sh new file mode 100755 index 00000000..ac0a4f4c --- /dev/null +++ b/scripts/dockerimages/scripts/docker.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -x -e + +CGROUPS="perf_event net_cls freezer devices blkio memory cpuacct cpu cpuset" + +mkdir -p /sys/fs/cgroup +mount -t tmpfs none /sys/fs/cgroup + +for i in $CGROUPS; do + mkdir -p /sys/fs/cgroup/$i + mount -t cgroup -o $i none /sys/fs/cgroup/$i +done + +if ! lsmod | grep -q br_netfilter; then + modprobe br_netfilter +fi + +rm -f /var/run/docker.pid +exec docker -d -s overlay diff --git a/scripts/dockerimages/scripts/os-dockerfile b/scripts/dockerimages/scripts/os-dockerfile new file mode 100644 index 00000000..25cd82d8 --- /dev/null +++ b/scripts/dockerimages/scripts/os-dockerfile @@ -0,0 +1,4 @@ +FROM scratch +COPY init busybox docker images.tar / +COPY lib /lib +CMD ["/init"] diff --git a/scripts/dockerimages/scripts/udev.sh b/scripts/dockerimages/scripts/udev.sh new file mode 100755 index 00000000..335c1980 --- /dev/null +++ b/scripts/dockerimages/scripts/udev.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +mount --bind /host/dev /dev +udevd --daemon +udevadm trigger --action=add +udevadm settle diff --git a/scripts/download b/scripts/download new file mode 100755 index 00000000..940a2a7e --- /dev/null +++ b/scripts/download @@ -0,0 +1,8 @@ +#!/bin/bash + +cd $(dirname $0)/.. + +source scripts/build-common + +download a8e5925eab10a472af4b9326dc3ff1068fd56886 https://github.com/rancherio/os-base/releases/download/v0.0.1/os-base.tar.xz +download c7d977c2bdc48c022fcf3a51a6e26229247cfb15 https://test.docker.com/builds/Linux/x86_64/docker-1.5.0-rc4 diff --git a/scripts/install b/scripts/install new file mode 100755 index 00000000..1c74fd2a --- /dev/null +++ b/scripts/install @@ -0,0 +1,11 @@ +#!/bin/bash +set -x + +cd $(dirname $0)/.. + +. scripts/build-common + +mkdir -p target +rm -rf target/artifacts + +cp -rf ${DIST}/artifacts target diff --git a/scripts/package b/scripts/package new file mode 100755 index 00000000..b2da8d67 --- /dev/null +++ b/scripts/package @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +source scripts/build-common + +cp bin/rancheros ${BUILD}/initrd/init +cp scripts/dockerimages/scripts/os-dockerfile ${BUILD}/initrd/Dockerfile + +cd ${BUILD}/initrd + +find | cpio -H newc -o | lzma -c > ${DIST}/artifacts/initrd + +CD=${BUILD}/cd + +mkdir -p ${CD}/boot/isolinux +cp ${DIST}/artifacts/vmlinuz ${CD}/boot +cp ${DIST}/artifacts/initrd ${CD}/boot +cp /usr/lib/syslinux/isolinux.bin ${CD}/boot/isolinux +cp /usr/lib/syslinux/linux.c32 ${CD}/boot/isolinux/ldlinux.c32 +cat > ${CD}/boot/isolinux/isolinux.cfg << EOF +default rancheros +label rancheros + kernel /boot/vmlinuz + initrd /boot/initrd + append quiet + +prompt 1 +timeout 1 +EOF + +# Copied from boot2docker, thanks. +cd ${CD} + xorriso \ + -publisher "Rancher Labs, Inc." \ + -as mkisofs \ + -l -J -R -V "RancherOS" \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -b boot/isolinux/isolinux.bin -c boot/isolinux/boot.cat \ + -isohybrid-mbr /usr/lib/syslinux/isohdpfx.bin \ + -o ${DIST}/artifacts/rancheros.iso $(pwd) diff --git a/scripts/run b/scripts/run new file mode 100755 index 00000000..bf1c735a --- /dev/null +++ b/scripts/run @@ -0,0 +1,45 @@ +#!/bin/bash +set -e +set -x + +cd $(dirname $0)/.. + +source scripts/build-common + +BASE=$(pwd) + +KERNEL=${BASE}/dist/artifacts/vmlinuz +INITRD=${BASE}/dist/artifacts/initrd +HD=${BASE}/build/empty-hd.img +HD_GZ=${ARTIFACTS}/empty-hd.img.gz +INITRD_TMP=${BUILD}/$(sha1sum ${INITRD} | awk '{print $1}') +INITRD_TEST=${BUILD}/initrd.test + +if [[ ! -e ${KERNEL} || ! -e ${INITRD} ]]; then + echo "Failed to find ${KERNEL} or ${INITRD}" 1>&2 + exit 1 +fi + +if [ ! -d ${INITRD_TMP} ]; then + mkdir -p ${INITRD_TMP} + pushd ${INITRD_TMP} + lzma -dc ${INITRD} | sudo cpio -idmv + rm -f init + popd +fi + +cp bin/rancheros ${INITRD_TMP}/init +cd ${INITRD_TMP} + +if [ "$1" == "--docker" ]; then + docker build -t rancheros-run . + docker run --privileged -it rancheros-run +else + find | cpio -H newc -o > ${INITRD_TEST} + + if [ ! -e ${HD} ]; then + zcat ${HD_GZ} > ${HD} + fi + + kvm -m 1024 -kernel ${KERNEL} -initrd ${INITRD_TEST} -append "$1" -hda ${HD} -serial stdio -netdev user,id=hostnet0 -device virtio-net-pci,romfile=,netdev=hostnet0 +fi diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go new file mode 100644 index 00000000..8df8d13e --- /dev/null +++ b/sysinit/sysinit.go @@ -0,0 +1,159 @@ +package sysinit + +import ( + "os" + "os/exec" + "path" + "strings" + + log "github.com/Sirupsen/logrus" + dockerClient "github.com/fsouza/go-dockerclient" + "github.com/rancherio/os/config" + "github.com/rancherio/os/docker" + initPkg "github.com/rancherio/os/init" +) + +func SysInit() { + if err := sysInit(); err != nil { + log.Fatal(err) + } +} + +func importImage(client *dockerClient.Client, name, fileName string) error { + file, err := os.Open(fileName) + if err != nil { + return err + } + + defer file.Close() + + log.Debugf("Importing image for %s", fileName) + repo, tag := dockerClient.ParseRepositoryTag(name) + return client.ImportImage(dockerClient.ImportImageOptions{ + Source: "-", + Repository: repo, + Tag: tag, + InputStream: file, + }) +} + +func hasImage(name string) bool { + stamp := path.Join(initPkg.STATE, name) + if _, err := os.Stat(stamp); os.IsNotExist(err) { + return false + } + return true +} + +func findImages(cfg *config.Config) ([]string, error) { + log.Debugf("Looking for images at %s", cfg.ImagesPath) + + result := []string{} + + dir, err := os.Open(cfg.ImagesPath) + if os.IsNotExist(err) { + log.Debugf("Not loading images, %s does not exist") + return result, nil + } + if err != nil { + return nil, err + } + + defer dir.Close() + + files, err := dir.Readdirnames(0) + if err != nil { + return nil, err + } + + for _, fileName := range files { + log.Debugf("Checking %s", fileName) + if ok, _ := path.Match(cfg.ImagesPattern, fileName); ok { + log.Debugf("Found %s", fileName) + result = append(result, fileName) + } + } + + return result, nil +} + +func loadImages(cfg *config.Config) error { + images, err := findImages(cfg) + if err != nil || len(images) == 0 { + return err + } + + client, err := docker.NewClient(cfg) + if err != nil { + return err + } + + for _, image := range images { + if hasImage(image) { + continue + } + + inputFileName := path.Join(cfg.ImagesPath, image) + input, err := os.Open(inputFileName) + if err != nil { + return err + } + + defer input.Close() + + log.Debugf("Loading images from %s", inputFileName) + err = client.LoadImage(dockerClient.LoadImageOptions{ + InputStream: input, + }) + + if err != nil { + return err + } + } + + return nil +} + +func runContainers(cfg *config.Config) error { + containers := cfg.SystemContainers + if cfg.Rescue { + log.Debug("Running rescue container") + containers = []config.ContainerConfig{cfg.RescueContainer} + } + + for _, container := range containers { + args := append([]string{"run"}, container.Options...) + args = append(args, container.Image) + args = append(args, container.Args...) + + cmd := exec.Command(cfg.DockerBin, args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + + //log.Infof("Is a tty : %v", term.IsTerminal(0)) + //log.Infof("Is a tty : %v", term.IsTerminal(1)) + //log.Infof("Is a tty : %v", term.IsTerminal(2)) + log.Debugf("Running %s", strings.Join(args, " ")) + err := cmd.Run() + if err != nil { + log.Errorf("Failed to run %v: %v", args, err) + } + } + + return nil +} + +func sysInit() error { + cfg, err := config.LoadConfig() + if err != nil { + return err + } + + initFuncs := []config.InitFunc{ + loadImages, + runContainers, + } + + return config.RunInitFuncs(cfg, initFuncs) +} diff --git a/user/system-docker.go b/user/system-docker.go new file mode 100644 index 00000000..ac28466a --- /dev/null +++ b/user/system-docker.go @@ -0,0 +1,25 @@ +package user + +import ( + "os" + "strings" + "syscall" + + log "github.com/Sirupsen/logrus" +) + +func SystemDocker() { + var newEnv []string + for _, env := range os.Environ() { + if !strings.HasPrefix(env, "DOCKER_HOST=") { + newEnv = append(newEnv, env) + } + } + + newEnv = append(newEnv, "DOCKER_HOST=unix://var/run/system-docker.sock") + + os.Args[0] = "/usr/bin/docker" + if err := syscall.Exec(os.Args[0], os.Args, newEnv); err != nil { + log.Fatal(err) + } +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 00000000..b525512f --- /dev/null +++ b/util/util.go @@ -0,0 +1,96 @@ +package util + +import ( + "archive/tar" + "fmt" + "io" + "os" + "path" + "syscall" + + "github.com/docker/docker/pkg/mount" +) + +func mountProc() error { + if _, err := os.Stat("/proc/self/mountinfo"); os.IsNotExist(err) { + if _, err := os.Stat("/proc"); os.IsNotExist(err) { + if err = os.Mkdir("/proc", 0755); err != nil { + return err + } + } + + if err := syscall.Mount("none", "/proc", "proc", 0, ""); err != nil { + return err + } + } + + return nil +} + +func Mount(device, directory, fsType, options string) error { + if err := mountProc(); err != nil { + return nil + } + + if _, err := os.Stat(directory); os.IsNotExist(err) { + err = os.MkdirAll(directory, 0755) + if err != nil { + return err + } + } + + return mount.Mount(device, directory, fsType, options) +} + +func Remount(directory, options string) error { + return mount.Mount("", directory, "", fmt.Sprintf("remount,%s", options)) +} + +func ExtractTar(archive string, dest string) error { + f, err := os.Open(archive) + if err != nil { + return err + } + defer f.Close() + + input := tar.NewReader(f) + + for { + header, err := input.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + + if header == nil { + break + } + + fileInfo := header.FileInfo() + fileName := path.Join(dest, header.Name) + if fileInfo.IsDir() { + //log.Debugf("DIR : %s", fileName) + err = os.MkdirAll(fileName, fileInfo.Mode()) + if err != nil { + return err + } + } else { + //log.Debugf("FILE: %s", fileName) + destFile, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode()) + if err != nil { + return err + } + + _, err = io.Copy(destFile, input) + // Not deferring, concerned about holding open too many files + destFile.Close() + + if err != nil { + return err + } + } + } + + return nil +}