├── Dockerfile ├── README.md └── main.go /Dockerfile: -------------------------------------------------------------------------------- 1 | # Firefox over VNC 2 | # 3 | # VERSION 0.1 4 | # DOCKER-VERSION 0.2 5 | 6 | from ubuntu:14.04 7 | # make sure the package repository is up to date 8 | run apt-get update 9 | 10 | # Install dependencies 11 | run apt-get install -y build-essential libsdl-mixer1.2-dev libsdl-net1.2-dev git gcc x11vnc xvfb wget 12 | run mkdir ~/.vnc 13 | 14 | # Setup a password 15 | run x11vnc -storepasswd 1234 ~/.vnc/passwd 16 | 17 | # Setup doom 18 | run git clone https://github.com/GideonRed/dockerdoom.git 19 | run wget http://distro.ibiblio.org/pub/linux/distributions/slitaz/sources/packages/d/doom1.wad 20 | run cd /dockerdoom/trunk && ./configure && make && make install 21 | 22 | # Autostart firefox (might not be the best way to do it, but it does the trick) 23 | run bash -c 'echo "/usr/local/games/psdoom -warp E1M1" >> /root/.bashrc' 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker DOOM 2 | ## Kill your running Docker container using Id's DOOM! 3 | 4 | Tired of killing Docker containers by having to type in 5 | `docker rm `? With every dull stroke of keys on the keyboard 6 | you only wish that you could be saving earth from a possible invasion from 7 | hell? Worry no longer! Now you can kill those Docker containers with the 8 | proper tool, a rocket launcher (or BFG, or shotgun, or whatever)! 9 | 10 | https://youtu.be/E1Lm1NFthX8 11 | 12 | ## Stop talking, I want to run this now 13 | 14 | Download and extract this binary on the Linux machine running docker: 15 | 16 | https://gideonred.com/bins/dockerdoomd.tar.gz 17 | 18 | Start up a few docker containers e.g.,: 19 | 20 | ```bash 21 | for i in {1..2} ; do docker run -d -t ubuntu:14.04; done 22 | ``` 23 | 24 | Now run the downloaded docker binary: 25 | 26 | ```bash 27 | ./dockerdoomd 28 | ``` 29 | 30 | You should receive output similar to: 31 | 32 | ``` 33 | ╭─gideon@localhost ~ 34 | ╰─$ ./dockerdoomd 35 | 2015/01/24 16:50:50 Pulling image from public repo 36 | Pulling repository gideonred/dockerdoom 37 | f5abca9b93a3: Download complete 38 | 511136ea3c5a: Download complete 39 | 53f858aaaf03: Download complete 40 | 837339b91538: Download complete 41 | 615c102e2290: Download complete 42 | b39b81afc8ca: Download complete 43 | 3972ba383c15: Download complete 44 | 90c7ac13f81e: Download complete 45 | Status: Downloaded newer image for gideonred/dockerdoom:latest 46 | 2015/01/24 16:53:05 Image downloaded 47 | 2015/01/24 16:53:05 Trying to start docker container ... 48 | 2015/01/24 16:53:05 Waiting 5 seconds for "dockerdoom" to show in "docker ps". You can change this wait with -dockerWait. 49 | PORT=5900 50 | 2015/01/24 16:53:10 Docker container started, you can now connect to it with a VNC viewer at port 5900 51 | ``` 52 | 53 | Get a VNC Viewer up and running. You could try [Chicken of the VNC](http://sourceforge.net/projects/cotvnc/). 54 | 55 | Connect the VNC Viewer to the machine running `dockerdoomd` at port 5900. The password is `1234`. 56 | 57 | ![](https://gideonred.com/images/vncdockerdoomd.png) 58 | 59 | After a few seconds you will see doom appear: 60 | 61 | ![](https://gideonred.com/images/vncdockerdoomd2.png) 62 | 63 | Now if you want to get the job done quickly enter the cheat `idspispopd` and walk through the wall on your right. You should be greeted by your docker containers as little pink monsters. Press `CTRL` to fire. If the pistol is not your thing, cheat with `idkfa` and press `5` for a nice surprise. Pause the game with `ESC`. Feel free to start up and shutdown docker containers in the background while the game is running. 64 | 65 | Sounds familiar? This is based of the work done for psdoom. psdoom was used to kill *nix processes. 66 | 67 | ## How does this magic work? 68 | 69 | ![](https://gideonred.com/images/dockerdoommeme.jpg) 70 | 71 | There is several parts at play. Let’s list them: 72 | 73 | * The Docker binary, used to start and query Docker containers. 74 | * The DOOM Docker container, running DOOM inside of it, called `dockerdoom`. 75 | * `dockerdoomd`, a daemon that starts the DOOM Docker container, sets everything up and enables the DOOM Docker container to query and stop Docker containers. 76 | * a socket file, enabling communication between DOOM and `dockerdoomd` 77 | * a VNC tcp connection, enabling a connection between DOOM’s X11 session and whatever computer you want it to be displayed on. 78 | 79 | The best part of all of this, is that DOOM is running within a container. This allows easier deployment of DOOM to whoever wants to run it. So, not only is this a cool magic trick, but it also uses the awesome features that containers give us. 80 | 81 | It’ll probably be best to explain this by drawing it all out. 82 | 83 | ![](https://gideonred.com/images/dockerdoomdiag.png) 84 | 85 | When you start `dockerdoomd` on your Linux host it will download the 86 | DOOM docker image from the public repo. It will start this image as a container 87 | named `dockerdoom`. After starting the container it will open a Unix socket between 88 | itself and the `dockerdoom` container. The container will open an X11 VNC 89 | session and wait for connections. DOOM will be started when the user first connects to the 90 | `dockerdoom` container with VNC. DOOM will then periodically poll 91 | the Unix socket for info on the running docker containers on the host. 92 | `dockerdoomd` will do a `docker ps` execution when it receives a `list` request 93 | on the Unix socket. DOOM will spawn monsters for every docker container it 94 | reads in response to a `list` request. When you kill a monster in DOOM, 95 | DOOM will send a `kill` request to `dockerdoomd` using the Unix socket. 96 | `dockerdoomd` will then do the corresponding `docker rm`. 97 | 98 | You may ask why the need for the `dockerdoomd` daemon and all the Unix socket 99 | communication. A process inside of a Docker container should not be able to talk back to the 100 | host’s docker setup, but the `dockerdoomd` daemon is a way of exposing a subset 101 | of docker commands to a specified docker container. 102 | 103 | A Docker container having the ability to execute commands or code on the machine hosting the docker container 104 | is something I call a "sudo" Docker container. Simply put, I’m giving a docker container 105 | more privileges than is expected from a contained environment. 106 | 107 | ## Food for thought 108 | 109 | ### Is their value in having a "sudo" Docker container in real world production environments? 110 | 111 | I think so. When maintaining a machine with many containers or VMs you tend to run some control software 112 | on the host. An annoyance is that the control software normally doesn’t 113 | use containers or VM’s, which means you can’t use the benefits of containers or VM’s. 114 | 115 | Having your control software talk from a container to a host via a well defined API 116 | can help maintainers of the software understand what dependencies need 117 | to be managed and tested when modifying the software. 118 | 119 | Of course there will still be some software running natively on the host, but hopefully they 120 | can be written to be as small as possible acting only as proxies or adapters (e.g. `dockerdoomd` in 121 | this case). 122 | 123 | ### Running X11 or graphical software within docker containers. 124 | 125 | I’m definitely not the first to do this. It’s popular to run Firefox in a container to 126 | add an extra sand box. I have not packaged or built a game for Linux before, but I’m sure there is many 127 | who have run into problems packaging games (or other software) and dealing with the various distro’s and their quirks. 128 | Containers and streaming technology (naively VNC) gives one possible solution to this problem. 129 | 130 | ## Links 131 | 132 | * [Github repo for the DOOM used here](https://github.com/GideonRed/dockerdoom) 133 | * [Github repo for `dockerdoomd`](https://github.com/GideonRed/dockerdoomd) 134 | 135 | ## Thanks 136 | 137 | Thanks to [orsonteodoro](https://github.com/orsonteodoro) for still keeping psdoom up to date. 138 | I’ve based my changes off of his version. 139 | 140 | DOOM and related logos are registered trademarks of id Software. 141 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //TODO: Make your container die if you die 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "os" 11 | "os/exec" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | func runCmd(cmdstring string) { 17 | parts := strings.Split(cmdstring, " ") 18 | cmd := exec.Command(parts[0], parts[1:len(parts)]...) 19 | cmd.Stdout = os.Stdout 20 | cmd.Stderr = os.Stderr 21 | err := cmd.Run() 22 | if err != nil { 23 | log.Fatalf("The following command failed: \"%v\"\n", cmdstring) 24 | } 25 | } 26 | 27 | func outputCmd(cmdstring string) string { 28 | parts := strings.Split(cmdstring, " ") 29 | cmd := exec.Command(parts[0], parts[1:len(parts)]...) 30 | cmd.Stderr = os.Stderr 31 | output, err := cmd.Output() 32 | if err != nil { 33 | log.Fatalf("The following command failed: \"%v\"\n", cmdstring) 34 | } 35 | return string(output) 36 | } 37 | 38 | func startCmd(cmdstring string) { 39 | parts := strings.Split(cmdstring, " ") 40 | cmd := exec.Command(parts[0], parts[1:len(parts)]...) 41 | cmd.Stdout = os.Stdout 42 | cmd.Stdin = os.Stdin 43 | err := cmd.Start() 44 | if err != nil { 45 | log.Fatalf("The following command failed: \"%v\"\n", cmdstring) 46 | } 47 | } 48 | 49 | func checkDockerImages(imageName, dockerBinary string) bool { 50 | output := outputCmd(fmt.Sprintf("%v images -q %v", dockerBinary, imageName)) 51 | return len(output) > 0 52 | } 53 | 54 | func checkActiveDocker(dockerName, dockerBinary string) bool { 55 | return checkDocker(dockerName, dockerBinary, "-q") 56 | } 57 | 58 | func checkAllDocker(dockerName, dockerBinary string) bool { 59 | return checkDocker(dockerName, dockerBinary, "-aq") 60 | } 61 | 62 | func checkDocker(dockerName, dockerBinary, arg string) bool { 63 | output := outputCmd(fmt.Sprintf("%v ps %v", dockerBinary, arg)) 64 | docker_ids := strings.Split(string(output), "\n") 65 | for _, docker_id := range docker_ids { 66 | if len(docker_id) == 0 { 67 | continue 68 | } 69 | output := outputCmd(fmt.Sprintf("%v inspect -f {{.Name}} %v", dockerBinary, docker_id)) 70 | name := strings.TrimSpace(string(output)) 71 | name = name[1:len(name)] 72 | if name == dockerName { 73 | return true 74 | } 75 | } 76 | return false 77 | } 78 | 79 | func socketLoop(listener net.Listener, dockerBinary, containerName string) { 80 | for true { 81 | conn, err := listener.Accept() 82 | if err != nil { 83 | panic(err) 84 | } 85 | stop := false 86 | for !stop { 87 | bytes := make([]byte, 40960) 88 | n, err := conn.Read(bytes) 89 | if err != nil { 90 | stop = true 91 | } 92 | bytes = bytes[0:n] 93 | strbytes := strings.TrimSpace(string(bytes)) 94 | if strbytes == "list" { 95 | output := outputCmd(fmt.Sprintf("%v ps -q", dockerBinary)) 96 | //cmd := exec.Command("/usr/bin/docker", "inspect", "-f", "{{.Name}}", "`docker", "ps", "-q`") 97 | outputstr := strings.TrimSpace(output) 98 | outputparts := strings.Split(outputstr, "\n") 99 | for _, part := range outputparts { 100 | output := outputCmd(fmt.Sprintf("%v inspect -f {{.Name}} %v", dockerBinary, part)) 101 | name := strings.TrimSpace(output) 102 | name = name[1:len(name)] 103 | if name != containerName { 104 | _, err = conn.Write([]byte(name + "\n")) 105 | if err != nil { 106 | log.Fatal("Could not write to socker file") 107 | } 108 | } 109 | } 110 | conn.Close() 111 | stop = true 112 | } else if strings.HasPrefix(strbytes, "kill ") { 113 | parts := strings.Split(strbytes, " ") 114 | docker_id := strings.TrimSpace(parts[1]) 115 | cmd := exec.Command(dockerBinary, "rm", "-f", docker_id) 116 | go cmd.Run() 117 | conn.Close() 118 | stop = true 119 | } 120 | } 121 | } 122 | } 123 | 124 | func main() { 125 | var socketFileFormat, containerName, vncPort, dockerBinary, imageName, dockerfile string 126 | var dockerWait int 127 | var buildImage, asciiDisplay bool 128 | flag.StringVar(&socketFileFormat, "socketFileFormat", "/tmp/dockerdoom%v.socket", "Location and format of the socket file") 129 | flag.StringVar(&containerName, "containerName", "dockerdoom", "Name of the docker container running DOOM") 130 | flag.IntVar(&dockerWait, "dockerWait", 5, "Time to wait before checking if the container came up") 131 | flag.StringVar(&vncPort, "vncPort", "5900", "Port to open for VNC Viewer") 132 | flag.StringVar(&dockerBinary, "dockerBinary", "/usr/bin/docker", "docker binary") 133 | flag.BoolVar(&buildImage, "buildImage", false, "Build docker image instead of pulling it from docker image repo") 134 | flag.StringVar(&imageName, "imageName", "gideonred/dockerdoom", "Name of docker image to use") 135 | flag.StringVar(&dockerfile, "dockerfile", ".", "Path to dockerdoom's Dockerfile") 136 | flag.BoolVar(&asciiDisplay, "asciiDisplay", false, "Don't use fancy vnc, throw DOOM straightup on my terminal screen") 137 | flag.Parse() 138 | 139 | if buildImage { 140 | log.Print("Building dockerdoom image, this will take a few minutes...") 141 | runCmd(fmt.Sprintf("%v build -t %v %v", dockerBinary, imageName, dockerfile)) 142 | log.Print("Image has been built") 143 | } 144 | present := checkDockerImages(imageName, dockerBinary) 145 | if !present { 146 | log.Print("Pulling image from public repo") 147 | runCmd(fmt.Sprintf("%v pull %v", dockerBinary, imageName)) 148 | log.Print("Image downloaded") 149 | } 150 | 151 | present = checkAllDocker(containerName, dockerBinary) 152 | if present { 153 | log.Fatalf("\"%v\" was present in the output of \"docker ps -a\",\nplease remove before trying again. You could use \"docker rm -f %v\"\n", containerName, containerName) 154 | } 155 | 156 | socketFile := fmt.Sprintf(socketFileFormat, time.Now().Unix()) 157 | listener, err := net.Listen("unix", socketFile) 158 | if err != nil { 159 | log.Fatalf("Could not create socket file %v.\nYou could use \"rm -f %v\"", socketFile, socketFile) 160 | } 161 | 162 | log.Print("Trying to start docker container ...") 163 | if !asciiDisplay { 164 | dockerRun := fmt.Sprintf("%v run --rm=true -p %v:%v -v %v:/dockerdoom.socket --name=%v %v x11vnc -geometry 640x480 -forever -usepw -create", dockerBinary, vncPort, vncPort, socketFile, containerName, imageName) 165 | startCmd(dockerRun) 166 | log.Printf("Waiting %v seconds for \"%v\" to show in \"docker ps\". You can change this wait with -dockerWait.", dockerWait, containerName) 167 | time.Sleep(time.Duration(dockerWait) * time.Second) 168 | present = checkActiveDocker(containerName, dockerBinary) 169 | if !present { 170 | log.Fatalf("\"%v\" did not lead to the container appearing in \"docker ps\". Please try and start it manually and check \"docker ps\"\n", dockerRun) 171 | } 172 | log.Print("Docker container started, you can now connect to it with a VNC viewer at port 5900") 173 | } else { 174 | dockerRun := fmt.Sprintf("%v run -t -i --rm=true -p %v:%v -v %v:/dockerdoom.socket --name=%v %v /bin/bash", dockerBinary, vncPort, vncPort, socketFile, containerName, imageName) 175 | startCmd(dockerRun) 176 | } 177 | 178 | socketLoop(listener, dockerBinary, containerName) 179 | } 180 | --------------------------------------------------------------------------------