├── .gitignore ├── README.md ├── Vagrantfile ├── base-configuration.nix ├── build-base.sh ├── default.nix ├── nix-docker ├── all-modules.nix ├── bin │ └── nix-docker ├── docker.nix ├── modules │ ├── config │ │ ├── docker.nix │ │ ├── environment.nix │ │ └── user-groups.nix │ ├── servers │ │ ├── http │ │ │ └── apache │ │ │ │ ├── default.nix │ │ │ │ └── per-server-options.nix │ │ ├── openssh.nix │ │ └── supervisord.nix │ ├── services │ │ └── networking │ │ │ └── bind.nix │ └── shim │ │ └── systemd.nix └── package.json ├── samples ├── apache-config.nix ├── fancy.nix ├── ssh-config.nix ├── wordpress.nix └── www │ └── index.html ├── scripts ├── README.md ├── install-docker.sh ├── install-extradisk.sh └── install-nix.sh └── zedconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | disk.vmdk 3 | *.swp 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nix-docker 2 | ========== 3 | 4 | Use [NixOS](http://nixos.org/nixos) configurations to provision [Docker](http://docker.io) containers. 5 | 6 | [Read about the what and why in this blog post](http://zef.me/6049/nix-docker) 7 | 8 | DISCLAIMER: This project is no longer actively maintained and probably broken, if you're interested in fixing it, please fork and contact me: zefhemel@gmail.com 9 | ------------ 10 | 11 | Installation with Vagrant 12 | ------------------------- 13 | The easy way to do this is to use [Vagrant](http://vagrantup.com). 14 | 15 | When you have Vagrant installed: 16 | 17 | git clone https://github.com/zefhemel/nix-docker.git 18 | cd nix-docker 19 | vagrant up 20 | vagrant ssh 21 | 22 | If all went well, you're now in a VM that has both Docker and Nix installed 23 | and `nix-docker` in its path. 24 | 25 | At this point you need to connect to the VM and have nix setup the vagrant users own custom package stores. execute the follow 26 | 27 | nix-channel --update 28 | nix-env -i hello 29 | 30 | You can now cd into the nix-docker/samples 31 | directory to try to build some of the examples. Note that the `~/nix-docker` 32 | directory is mounted from your host machine, so you can edit your files with 33 | your favorite editor and have them available within the VM. 34 | 35 | Installation 36 | ------------ 37 | 38 | To use nix-docker you need [Nix](http://nixos.org/nix) installed as well as 39 | [Docker](http://www.docker.io). Realistically, your best way to do this on 40 | an Ubuntu (12.04 or 13.04) box. Once these are installed, installing 41 | `nix-docker` is as simple as: 42 | 43 | git clone https://github.com/zefhemel/nix-docker.git 44 | nix-env -f nix-docker/default.nix -i nix-docker 45 | 46 | Usage 47 | ----- 48 | 49 | To build a stand-alone Docker image: 50 | 51 | nix-docker -b -t my-image configuration.nix 52 | 53 | This will build the configuration specified in `configuration.nix`, have a look 54 | in the `samples/` directory for examples. It will produce a docker image named 55 | `my-image` which you can then run anywhere. Use `username/my-image` to be able 56 | to push them to the Docker index. 57 | 58 | To build a host-mounted package: 59 | 60 | nix-docker -t my-image configuration.nix 61 | 62 | This will produce a Nix package (symlinked in the current directory in `result`) 63 | containing a script you can use to spawn the container using Docker, e.g.: 64 | 65 | sudo -E ./result/sbin/docker-run 66 | 67 | to run the container in the foreground, or: 68 | 69 | sudo -E ./result/sbin/docker-run -d 70 | 71 | to daemonize it. What the `docker-run` script will do is check if there's 72 | already a docker image available with the current image name and tag based on 73 | the Nix build hash. If not, it will quickly build it first (these images take up 74 | barely any space on disk). Then, it will boot up the container. 75 | 76 | Distributing host-mounted packages is done by first copying the Nix closure 77 | resulting from the build to the target machine (when you do the build it 78 | will give you example commands to run): 79 | 80 | nix-copy-closure root@targetmachine /nix/store/.... 81 | 82 | Then, you can spawn the container remotely with the script path provided 83 | in the output of the build command. 84 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "ubuntu/trusty64" 3 | 4 | config.vm.provider :virtualbox do |vb| 5 | vb.customize ["modifyvm", :id, "--memory", "4096"] 6 | 7 | # We'll attach an extra 50GB disk for all nix and docker data 8 | file_to_disk = "disk.vmdk" 9 | vb.customize ['createhd', '--filename', file_to_disk, '--size', 50 * 1024] 10 | vb.customize ['storageattach', :id, '--storagectl', 'SATAController', '--port', 1, '--device', 0, '--type', 'hdd', '--medium', file_to_disk] 11 | end 12 | 13 | config.vm.synced_folder ".", "/home/vagrant/nix-docker" 14 | 15 | config.vm.provision :shell, inline: < "scripts/install-extradisk.sh" 22 | config.vm.provision :shell, :path => "scripts/install-docker.sh" 23 | config.vm.provision :shell, :path => "scripts/install-nix.sh" 24 | 25 | config.vm.network "private_network", ip: "192.168.22.22" 26 | 27 | 28 | end 29 | -------------------------------------------------------------------------------- /base-configuration.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | { 3 | services.mysql.enable = true; 4 | } 5 | -------------------------------------------------------------------------------- /build-base.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo -E nix-docker -b -t zefhemel/base-nix --from busybox base-configuration.nix 3 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | in 4 | pkgs.stdenv.mkDerivation { 5 | name = "nix-docker-0.1"; 6 | src = ./nix-docker; 7 | buildInputs = [ pkgs.python27 ]; 8 | installPhase = '' 9 | mkdir -p $out 10 | cp -R * $out/ 11 | ''; 12 | } 13 | -------------------------------------------------------------------------------- /nix-docker/all-modules.nix: -------------------------------------------------------------------------------- 1 | [ 2 | ./modules/config/docker.nix 3 | ./modules/config/user-groups.nix 4 | ./modules/config/environment.nix 5 | ./modules/servers/supervisord.nix 6 | 7 | ./modules/shim/systemd.nix 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | # These modules needed some patching to work well 18 | ./modules/servers/http/apache/default.nix 19 | ./modules/servers/openssh.nix 20 | ./modules/services/networking/bind.nix 21 | ] 22 | -------------------------------------------------------------------------------- /nix-docker/bin/nix-docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os.path 4 | import subprocess 5 | import argparse 6 | import sys 7 | import shutil 8 | import tempfile 9 | 10 | parser = argparse.ArgumentParser(description='Build Docker images with Nix', prog='nix-docker') 11 | parser.add_argument('configuration', nargs='?', help='configuration.nix file to build', default="configuration.nix") 12 | parser.add_argument('-b', action='store_true', help="Build a docker image") 13 | parser.add_argument('-t', help='Image name', default="nix-docker-build") 14 | parser.add_argument('--from', help="Image to use as a base image", default="busybox") 15 | parser.add_argument('-f', action='store_true', help="Force nix-docker into something it does not what to do.") 16 | 17 | args = parser.parse_args() 18 | 19 | base_image = getattr(args, "from") 20 | configuration_file = args.configuration 21 | image_name = args.t 22 | full_build = args.b 23 | 24 | temp_dir = tempfile.mkdtemp() 25 | 26 | print "Temp dir", temp_dir 27 | 28 | nix_closure_temp = "%s/nix-closure" % temp_dir 29 | force = args.f 30 | 31 | root_path = os.path.normpath(os.path.realpath(sys.argv[0]) + "/../..") 32 | 33 | def build(): 34 | print "Building Nix closure for the image..." 35 | if args.b: 36 | mountBuild = "false" 37 | else: 38 | mountBuild = "true" 39 | cmd = ["nix-build", "%s/docker.nix" % root_path, '-I', 'configuration=' + configuration_file, "--arg", "mountBuild", mountBuild, "--argstr", "name", image_name, "--argstr", "baseImage", base_image] 40 | if full_build: 41 | cmd.append("--no-out-link") 42 | nix_path = subprocess.check_output(cmd) 43 | return nix_path.strip() 44 | 45 | def get_available_nix_paths(): 46 | try: 47 | paths_string = subprocess.check_output(["docker", "run", base_image, "/bin/ls", "/nix/store"]) 48 | return paths_string.strip().split("\n") 49 | except: 50 | return [] 51 | 52 | def cleanup(): 53 | subprocess.check_call(["rm", "-rf", temp_dir]) 54 | 55 | def copy_closure_and_build(package_nix_path, available_paths): 56 | paths = subprocess.check_output(["nix-store", "-qR", package_nix_path]).strip().split("\n") 57 | print "Available", available_paths 58 | paths_to_copy = filter(lambda path: not path[len("/nix/store/"):] in available_paths, paths) 59 | print "New Nix store paths to copy to image:" 60 | for path in paths_to_copy: 61 | print " ", path 62 | os.mkdir(nix_closure_temp) 63 | cmd = ["cp", "-r"] 64 | cmd.extend(paths_to_copy) 65 | cmd.append(nix_closure_temp) 66 | shutil.copyfile("%s/Dockerfile" % package_nix_path, "%s/Dockerfile" % temp_dir) 67 | subprocess.check_call(cmd) 68 | subprocess.check_call(["chown", "-R", "root:root", nix_closure_temp]) 69 | subprocess.check_call(["docker", "build", "-rm=true", "-t", image_name, temp_dir]) 70 | cleanup() 71 | 72 | if __name__ == '__main__': 73 | if full_build and os.getenv("USER") != "root" and not force: 74 | print "When doing a full build you need to run nix-docker as root. Rerun this command with 'sudo -E nix-docker ...'" 75 | sys.exit(1) 76 | 77 | if not os.path.exists(configuration_file): 78 | print "Could not find configuration file: %s" % configuration_file 79 | sys.exit(1) 80 | 81 | package_nix_path = build() 82 | if full_build: 83 | available_paths = get_available_nix_paths() 84 | copy_closure_and_build(package_nix_path, available_paths) 85 | print "To run: sudo docker run -t -i", image_name 86 | else: 87 | print "Result in", package_nix_path, "test with sudo ./result/sbin/docker-run" 88 | print "To deploy: nix-copy-closure -s root@", package_nix_path 89 | print "To run: ssh root@", package_nix_path + "/sbin/docker-run -d" 90 | -------------------------------------------------------------------------------- /nix-docker/docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { system = "x86_64-linux"; } 2 | , name 3 | , configuration ? 4 | , mountBuild ? true 5 | , baseImage ? "busybox" 6 | }: 7 | with pkgs.lib; 8 | let 9 | config = evalModules { 10 | modules = concatLists [ [configuration] (import ./all-modules.nix) ]; 11 | args = { inherit pkgs; }; 12 | }; 13 | 14 | localNixPath = pkg: "nix_store/${substring 11 (stringLength pkg.outPath) pkg.outPath}"; 15 | 16 | systemd = import ./systemd.nix { inherit pkgs config; }; 17 | environment = import ./environment.nix { inherit pkgs config; }; 18 | 19 | verboseFlag = if config.config.docker.verbose then "v" else ""; 20 | 21 | 22 | buildScript = pkgs.writeScript "build" '' 23 | #!/bin/sh -e${verboseFlag} 24 | ${config.config.docker.buildScript} 25 | ''; 26 | 27 | bootScript = pkgs.writeScript "boot" '' 28 | #!/bin/sh -e${verboseFlag} 29 | umask ${config.config.environment.umask} 30 | ${if mountBuild then config.config.docker.buildScript else ""} 31 | ${config.config.docker.bootScript} 32 | ''; 33 | 34 | dockerFile = pkgs.writeText "Dockerfile" '' 35 | FROM ${if mountBuild then "busybox" else baseImage} 36 | ${if !mountBuild then 37 | '' 38 | ADD nix-closure /nix/store 39 | RUN ${buildScript} 40 | '' 41 | else ""} 42 | CMD ${bootScript} 43 | ${ 44 | concatMapStrings (port: "EXPOSE ${toString port}\n") config.config.docker.ports 45 | } 46 | ${ 47 | concatMapStrings (port: "VOLUME ${toString port}\n") config.config.docker.volumes 48 | } 49 | ''; 50 | 51 | imageHash = substring 11 8 dockerFile.outPath; 52 | 53 | runContainerScript = pkgs.writeScript "docker-run" '' 54 | #!/usr/bin/env bash 55 | 56 | if [ "" == "$(docker images | grep -E "${name}\s*${imageHash}")" ]; then 57 | docker build -t ${name}:${imageHash} $(dirname $0)/.. 58 | fi 59 | 60 | OPTIONS="-t -i $*" 61 | if [ "$1" == "-d" ]; then 62 | OPTIONS="$*" 63 | fi 64 | 65 | docker run $OPTIONS ${if mountBuild then "-v /nix/store:/nix/store" else ""} ${name}:${imageHash} 66 | ''; 67 | 68 | in pkgs.stdenv.mkDerivation { 69 | name = replaceChars ["/"] ["-"] name; 70 | src = ./.; 71 | 72 | phases = [ "installPhase" ]; 73 | 74 | installPhase = '' 75 | mkdir -p $out 76 | ${if mountBuild then '' 77 | mkdir -p $out/sbin 78 | cp ${runContainerScript} $out/sbin/docker-run 79 | '' else ""} 80 | cp ${dockerFile} $out/Dockerfile 81 | ''; 82 | } 83 | -------------------------------------------------------------------------------- /nix-docker/modules/config/docker.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs.lib; 3 | { 4 | options = { 5 | docker.ports = mkOption { 6 | default = []; 7 | description = "Ports to expose to the outside world."; 8 | example = [ 80 22 ]; 9 | }; 10 | 11 | docker.volumes = mkOption { 12 | default = []; 13 | description = "Volumes to create for container."; 14 | example = [ "/var/lib" "/var/log" ]; 15 | }; 16 | 17 | docker.buildScripts = mkOption { 18 | default = {}; 19 | example = { 20 | setupUsers = "cp passwd /etc/passwd"; 21 | }; 22 | description = "Scripts (as text) to be run during build, executed alphabetically"; 23 | }; 24 | 25 | docker.bootScript = mkOption { 26 | default = ""; 27 | description = "Script (text) to run when container booted."; 28 | }; 29 | 30 | docker.buildScript = mkOption {}; 31 | 32 | docker.verbose = mkOption { 33 | default = false; 34 | type = types.bool; 35 | }; 36 | 37 | # HACK: Let's ignore these for now 38 | networking = mkOption {}; 39 | security = mkOption {}; 40 | services.xserver.enable = mkOption { default = false; }; 41 | }; 42 | 43 | config = { 44 | docker.buildScript = concatStrings (attrValues config.docker.buildScripts); 45 | networking.enableIPv6 = false; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /nix-docker/modules/config/environment.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs.lib; 3 | let 4 | etc2 = filter (f: f.enable) (attrValues config.environment.etc); 5 | 6 | etc = pkgs.stdenv.mkDerivation { 7 | name = "etc"; 8 | 9 | builder = ; 10 | 11 | preferLocalBuild = true; 12 | 13 | /* !!! Use toXML. */ 14 | sources = map (x: x.source) etc2; 15 | targets = map (x: x.target) etc2; 16 | modes = map (x: x.mode) etc2; 17 | }; 18 | in { 19 | options = { 20 | environment.systemPackages = mkOption { 21 | default = []; 22 | description = "Packages to be put in the system profile."; 23 | }; 24 | 25 | environment.umask = mkOption { 26 | default = "002"; 27 | type = with types; string; 28 | }; 29 | 30 | # HACK HACK 31 | system.activationScripts.etc = mkOption {}; # Ignore 32 | system.build.etc = mkOption {}; # Ignore 33 | }; 34 | 35 | config = { 36 | docker.buildScripts."0-systemEnv" = let 37 | systemPackages = config.environment.systemPackages; 38 | systemEnv = pkgs.buildEnv { name = "system-env"; paths = systemPackages; }; 39 | in '' 40 | rm -rf /usr 41 | chmod 777 /tmp 42 | ln -s ${systemEnv} /usr 43 | ln -sf /usr/bin/bash /bin/bash 44 | ''; 45 | 46 | environment.systemPackages = with pkgs; [ coreutils bash ]; 47 | 48 | docker.buildScripts."0-etc" = '' 49 | echo "setting up /etc..." 50 | ${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl ${} ${etc}/etc 51 | ''; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /nix-docker/modules/config/user-groups.nix: -------------------------------------------------------------------------------- 1 | {pkgs, config, ...}: 2 | 3 | with pkgs.lib; 4 | 5 | let 6 | 7 | userOpts = { name, config, ... }: { 8 | 9 | options = { 10 | 11 | name = mkOption { 12 | type = types.str; 13 | description = "The name of the user account. If undefined, the name of the attribute set will be used."; 14 | }; 15 | 16 | description = mkOption { 17 | type = types.str; 18 | default = ""; 19 | example = "Alice Q. User"; 20 | description = '' 21 | A short description of the user account, typically the 22 | user's full name. This is actually the “GECOS” or “comment” 23 | field in /etc/passwd. 24 | ''; 25 | }; 26 | 27 | uid = mkOption { 28 | type = with types; uniq (nullOr int); 29 | default = null; 30 | description = "The account UID. If undefined, NixOS will select a free UID."; 31 | }; 32 | 33 | group = mkOption { 34 | type = types.str; 35 | default = "nogroup"; 36 | description = "The user's primary group."; 37 | }; 38 | 39 | extraGroups = mkOption { 40 | type = types.listOf types.str; 41 | default = []; 42 | description = "The user's auxiliary groups."; 43 | }; 44 | 45 | home = mkOption { 46 | type = types.str; 47 | default = "/var/empty"; 48 | description = "The user's home directory."; 49 | }; 50 | 51 | shell = mkOption { 52 | type = types.str; 53 | default = "/run/current-system/sw/sbin/nologin"; 54 | description = "The path to the user's shell."; 55 | }; 56 | 57 | createHome = mkOption { 58 | type = types.bool; 59 | default = false; 60 | description = "If true, the home directory will be created automatically."; 61 | }; 62 | 63 | useDefaultShell = mkOption { 64 | type = types.bool; 65 | default = false; 66 | description = "If true, the user's shell will be set to users.defaultUserShell."; 67 | }; 68 | 69 | password = mkOption { 70 | type = with types; uniq (nullOr str); 71 | default = null; 72 | description = '' 73 | The user's password. If undefined, no password is set for 74 | the user. Warning: do not set confidential information here 75 | because it is world-readable in the Nix store. This option 76 | should only be used for public accounts such as 77 | guest. 78 | ''; 79 | }; 80 | 81 | isSystemUser = mkOption { 82 | type = types.bool; 83 | default = true; 84 | description = "Indicates if the user is a system user or not."; 85 | }; 86 | 87 | createUser = mkOption { 88 | type = types.bool; 89 | default = true; 90 | description = '' 91 | Indicates if the user should be created automatically as a local user. 92 | Set this to false if the user for instance is an LDAP user. NixOS will 93 | then not modify any of the basic properties for the user account. 94 | ''; 95 | }; 96 | 97 | isAlias = mkOption { 98 | type = types.bool; 99 | default = false; 100 | description = "If true, the UID of this user is not required to be unique and can thus alias another user."; 101 | }; 102 | 103 | }; 104 | 105 | config = { 106 | name = mkDefault name; 107 | uid = mkDefault null; 108 | shell = mkDefault "/bin/sh"; 109 | }; 110 | 111 | }; 112 | 113 | groupOpts = { name, config, ... }: { 114 | 115 | options = { 116 | 117 | name = mkOption { 118 | type = types.str; 119 | description = "The name of the group. If undefined, the name of the attribute set will be used."; 120 | }; 121 | 122 | gid = mkOption { 123 | type = with types; uniq (nullOr int); 124 | default = null; 125 | description = "The GID of the group. If undefined, NixOS will select a free GID."; 126 | }; 127 | 128 | }; 129 | 130 | config = { 131 | name = mkDefault name; 132 | gid = mkDefault null; 133 | }; 134 | 135 | }; 136 | 137 | extraUsers = config.users.extraUsers; 138 | extraGroups = config.users.extraGroups; 139 | 140 | uidUsers = listToAttrs 141 | (imap (i: name: 142 | let 143 | user = getAttr name extraUsers; 144 | in { 145 | name=name; 146 | value = if user.uid == null then 147 | setAttr user "uid" (builtins.add 1000 i) 148 | else user; 149 | }) 150 | (attrNames extraUsers)); 151 | 152 | gidGroups = listToAttrs 153 | (imap (i: name: 154 | let 155 | group = getAttr name extraGroups; 156 | in { 157 | name=name; 158 | value = if group.gid == null then 159 | setAttr group "gid" (builtins.add 1000 i) 160 | else group; 161 | }) 162 | (attrNames extraGroups)); 163 | in 164 | 165 | { 166 | 167 | ###### interface 168 | 169 | options = { 170 | 171 | users.extraUsers = mkOption { 172 | default = {}; 173 | type = types.loaOf types.optionSet; 174 | example = { 175 | alice = { 176 | uid = 1234; 177 | description = "Alice Q. User"; 178 | home = "/home/alice"; 179 | createHome = true; 180 | group = "users"; 181 | extraGroups = ["wheel"]; 182 | shell = "/bin/sh"; 183 | }; 184 | }; 185 | description = '' 186 | Additional user accounts to be created automatically by the system. 187 | This can also be used to set options for root. 188 | ''; 189 | options = [ userOpts ]; 190 | }; 191 | 192 | users.extraGroups = mkOption { 193 | default = {}; 194 | example = 195 | { students.gid = 1001; 196 | hackers = { }; 197 | }; 198 | type = types.loaOf types.optionSet; 199 | description = '' 200 | Additional groups to be created automatically by the system. 201 | ''; 202 | options = [ groupOpts ]; 203 | }; 204 | }; 205 | 206 | config = { 207 | 208 | users.extraUsers = { 209 | root = { 210 | uid = 0; 211 | description = "System administrator"; 212 | home = "/root"; 213 | group = "root"; 214 | }; 215 | nobody = { 216 | uid = 1; 217 | description = "Unprivileged account (don't use!)"; 218 | }; 219 | ldap = {}; 220 | }; 221 | 222 | users.extraGroups = { 223 | root = { gid = 0; }; 224 | wheel = { }; 225 | disk = { }; 226 | kmem = { }; 227 | tty = { }; 228 | floppy = { }; 229 | uucp = { }; 230 | lp = { }; 231 | cdrom = { }; 232 | tape = { }; 233 | audio = { }; 234 | video = { }; 235 | dialout = { }; 236 | nogroup = { }; 237 | users = { }; 238 | utmp = { }; 239 | adm = { }; 240 | }; 241 | 242 | environment.etc.passwd.text = 243 | concatMapStrings (name: 244 | let 245 | user = getAttr name uidUsers; 246 | in 247 | if user.createUser then 248 | "${user.name}:x:${toString user.uid}:${toString (getAttr user.group gidGroups).gid}:${user.description}:${user.home}:${user.shell}\n" 249 | else 250 | "" 251 | ) (attrNames uidUsers); 252 | 253 | environment.etc.group.text = 254 | concatMapStrings (name: 255 | let 256 | group = getAttr name gidGroups; 257 | in 258 | "${group.name}:x:${toString group.gid}:\n" 259 | ) (attrNames gidGroups); 260 | 261 | docker.buildScripts."1-create-homes" = concatMapStrings (name: 262 | let 263 | user = getAttr name uidUsers; 264 | in 265 | if user.createHome then 266 | "mkdir -p ${user.home}; chown ${user.name}:${user.group} ${user.home}\n" 267 | else 268 | "" 269 | ) (attrNames uidUsers); 270 | }; 271 | 272 | } 273 | -------------------------------------------------------------------------------- /nix-docker/modules/servers/http/apache/default.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | 3 | with pkgs.lib; 4 | 5 | let 6 | 7 | mainCfg = config.services.httpd; 8 | 9 | httpd = mainCfg.package; 10 | 11 | version24 = !versionOlder httpd.version "2.4"; 12 | 13 | httpdConf = mainCfg.configFile; 14 | 15 | php = pkgs.php.override { apacheHttpd = httpd; }; 16 | 17 | getPort = cfg: if cfg.port != 0 then cfg.port else if cfg.enableSSL then 443 else 80; 18 | 19 | extraModules = attrByPath ["extraModules"] [] mainCfg; 20 | extraForeignModules = filter builtins.isAttrs extraModules; 21 | extraApacheModules = filter (x: !(builtins.isAttrs x)) extraModules; # I'd prefer using builtins.isString here, but doesn't exist yet 22 | 23 | 24 | makeServerInfo = cfg: { 25 | # Canonical name must not include a trailing slash. 26 | canonicalName = 27 | (if cfg.enableSSL then "https" else "http") + "://" + 28 | cfg.hostName + 29 | (if getPort cfg != (if cfg.enableSSL then 443 else 80) then ":${toString (getPort cfg)}" else ""); 30 | 31 | # Admin address: inherit from the main server if not specified for 32 | # a virtual host. 33 | adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr; 34 | 35 | vhostConfig = cfg; 36 | serverConfig = mainCfg; 37 | fullConfig = config; # machine config 38 | }; 39 | 40 | 41 | allHosts = [mainCfg] ++ mainCfg.virtualHosts; 42 | 43 | 44 | callSubservices = serverInfo: defs: 45 | let f = svc: 46 | let 47 | svcFunction = 48 | if svc ? function then svc.function 49 | else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix"); 50 | config = (evalModules 51 | { modules = [ { options = res.options; config = svc.config or svc; } ]; 52 | check = false; 53 | }).config; 54 | defaults = { 55 | extraConfig = ""; 56 | extraModules = []; 57 | extraModulesPre = []; 58 | extraPath = []; 59 | extraServerPath = []; 60 | globalEnvVars = []; 61 | robotsEntries = ""; 62 | startupScript = ""; 63 | enablePHP = false; 64 | phpOptions = ""; 65 | options = {}; 66 | }; 67 | res = defaults // svcFunction { inherit config pkgs serverInfo php; }; 68 | in res; 69 | in map f defs; 70 | 71 | 72 | # !!! callSubservices is expensive 73 | subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices; 74 | 75 | mainSubservices = subservicesFor mainCfg; 76 | 77 | allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts; 78 | 79 | 80 | # !!! should be in lib 81 | writeTextInDir = name: text: 82 | pkgs.runCommand name {inherit text;} "ensureDir $out; echo -n \"$text\" > $out/$name"; 83 | 84 | 85 | enableSSL = any (vhost: vhost.enableSSL) allHosts; 86 | 87 | 88 | # Names of modules from ${httpd}/modules that we want to load. 89 | apacheModules = 90 | [ # HTTP authentication mechanisms: basic and digest. 91 | "auth_basic" "auth_digest" 92 | 93 | # Authentication: is the user who he claims to be? 94 | "authn_file" "authn_dbm" "authn_anon" 95 | (if version24 then "authn_core" else "authn_alias") 96 | 97 | # Authorization: is the user allowed access? 98 | "authz_user" "authz_groupfile" "authz_host" 99 | 100 | # Other modules. 101 | "ext_filter" "include" "log_config" "env" "mime_magic" 102 | "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif" 103 | "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs" 104 | "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling" 105 | "userdir" "alias" "rewrite" "proxy" "proxy_http" 106 | ] 107 | ++ optionals version24 [ 108 | "mpm_${mainCfg.multiProcessingModule}" 109 | "authz_core" 110 | "unixd" 111 | ] 112 | ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) 113 | ++ optional enableSSL "ssl" 114 | ++ extraApacheModules; 115 | 116 | 117 | allDenied = if version24 then '' 118 | Require all denied 119 | '' else '' 120 | Order deny,allow 121 | Deny from all 122 | ''; 123 | 124 | allGranted = if version24 then '' 125 | Require all granted 126 | '' else '' 127 | Order allow,deny 128 | Allow from all 129 | ''; 130 | 131 | 132 | loggingConf = '' 133 | ErrorLog ${mainCfg.logDir}/error_log 134 | 135 | LogLevel notice 136 | 137 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 138 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 139 | LogFormat "%{Referer}i -> %U" referer 140 | LogFormat "%{User-agent}i" agent 141 | 142 | CustomLog ${mainCfg.logDir}/access_log ${mainCfg.logFormat} 143 | ''; 144 | 145 | 146 | browserHacks = '' 147 | BrowserMatch "Mozilla/2" nokeepalive 148 | BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 149 | BrowserMatch "RealPlayer 4\.0" force-response-1.0 150 | BrowserMatch "Java/1\.0" force-response-1.0 151 | BrowserMatch "JDK/1\.0" force-response-1.0 152 | BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully 153 | BrowserMatch "^WebDrive" redirect-carefully 154 | BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully 155 | BrowserMatch "^gnome-vfs" redirect-carefully 156 | ''; 157 | 158 | 159 | sslConf = '' 160 | SSLSessionCache shm:${mainCfg.stateDir}/ssl_scache(512000) 161 | 162 | SSLMutex posixsem 163 | 164 | SSLRandomSeed startup builtin 165 | SSLRandomSeed connect builtin 166 | ''; 167 | 168 | 169 | mimeConf = '' 170 | TypesConfig ${httpd}/conf/mime.types 171 | 172 | AddType application/x-x509-ca-cert .crt 173 | AddType application/x-pkcs7-crl .crl 174 | AddType application/x-httpd-php .php .phtml 175 | 176 | 177 | MIMEMagicFile ${httpd}/conf/magic 178 | 179 | 180 | AddEncoding x-compress Z 181 | AddEncoding x-gzip gz tgz 182 | ''; 183 | 184 | 185 | perServerConf = isMainServer: cfg: let 186 | 187 | serverInfo = makeServerInfo cfg; 188 | 189 | subservices = callSubservices serverInfo cfg.extraSubservices; 190 | 191 | documentRoot = if cfg.documentRoot != null then cfg.documentRoot else 192 | pkgs.runCommand "empty" {} "ensureDir $out"; 193 | 194 | documentRootConf = '' 195 | DocumentRoot "${documentRoot}" 196 | 197 | 198 | Options Indexes FollowSymLinks 199 | AllowOverride None 200 | ${allGranted} 201 | 202 | ''; 203 | 204 | robotsTxt = pkgs.writeText "robots.txt" '' 205 | ${# If this is a vhost, the include the entries for the main server as well. 206 | if isMainServer then "" 207 | else concatMapStrings (svc: svc.robotsEntries) mainSubservices} 208 | ${concatMapStrings (svc: svc.robotsEntries) subservices} 209 | ''; 210 | 211 | robotsConf = '' 212 | Alias /robots.txt ${robotsTxt} 213 | ''; 214 | 215 | in '' 216 | ServerName ${serverInfo.canonicalName} 217 | 218 | ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases} 219 | 220 | ${if cfg.sslServerCert != null then '' 221 | SSLCertificateFile ${cfg.sslServerCert} 222 | SSLCertificateKeyFile ${cfg.sslServerKey} 223 | '' else ""} 224 | 225 | ${if cfg.enableSSL then '' 226 | SSLEngine on 227 | '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */ 228 | '' 229 | SSLEngine off 230 | '' else ""} 231 | 232 | ${if isMainServer || cfg.adminAddr != null then '' 233 | ServerAdmin ${cfg.adminAddr} 234 | '' else ""} 235 | 236 | ${if !isMainServer && mainCfg.logPerVirtualHost then '' 237 | ErrorLog ${mainCfg.logDir}/error_log-${cfg.hostName} 238 | CustomLog ${mainCfg.logDir}/access_log-${cfg.hostName} ${cfg.logFormat} 239 | '' else ""} 240 | 241 | ${robotsConf} 242 | 243 | ${if isMainServer || cfg.documentRoot != null then documentRootConf else ""} 244 | 245 | ${if cfg.enableUserDir then '' 246 | 247 | UserDir public_html 248 | UserDir disabled root 249 | 250 | 251 | AllowOverride FileInfo AuthConfig Limit Indexes 252 | Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec 253 | 254 | ${allGranted} 255 | 256 | 257 | ${allDenied} 258 | 259 | 260 | 261 | '' else ""} 262 | 263 | ${if cfg.globalRedirect != null then '' 264 | RedirectPermanent / ${cfg.globalRedirect} 265 | '' else ""} 266 | 267 | ${ 268 | let makeFileConf = elem: '' 269 | Alias ${elem.urlPath} ${elem.file} 270 | ''; 271 | in concatMapStrings makeFileConf cfg.servedFiles 272 | } 273 | 274 | ${ 275 | let makeDirConf = elem: '' 276 | Alias ${elem.urlPath} ${elem.dir}/ 277 | 278 | Options +Indexes 279 | ${allGranted} 280 | AllowOverride All 281 | 282 | ''; 283 | in concatMapStrings makeDirConf cfg.servedDirs 284 | } 285 | 286 | ${concatMapStrings (svc: svc.extraConfig) subservices} 287 | 288 | ${cfg.extraConfig} 289 | ''; 290 | 291 | 292 | confFile = pkgs.writeText "httpd.conf" '' 293 | 294 | ServerRoot ${httpd} 295 | 296 | ${optionalString version24 '' 297 | DefaultRuntimeDir ${mainCfg.stateDir}/runtime 298 | ''} 299 | 300 | PidFile ${mainCfg.stateDir}/httpd.pid 301 | 302 | ${optionalString (mainCfg.multiProcessingModule != "prefork") '' 303 | # mod_cgid requires this. 304 | ScriptSock ${mainCfg.stateDir}/cgisock 305 | ''} 306 | 307 | 308 | MaxClients ${toString mainCfg.maxClients} 309 | MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild} 310 | 311 | 312 | ${let 313 | ports = map getPort allHosts; 314 | uniquePorts = uniqList {inputList = ports;}; 315 | in concatMapStrings (port: "Listen ${toString port}\n") uniquePorts 316 | } 317 | 318 | User ${mainCfg.user} 319 | Group ${mainCfg.group} 320 | 321 | ${let 322 | load = {name, path}: "LoadModule ${name}_module ${path}\n"; 323 | allModules = 324 | concatMap (svc: svc.extraModulesPre) allSubservices 325 | ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules 326 | ++ optional enablePHP { name = "php5"; path = "${php}/modules/libphp5.so"; } 327 | ++ concatMap (svc: svc.extraModules) allSubservices 328 | ++ extraForeignModules; 329 | in concatMapStrings load allModules 330 | } 331 | 332 | AddHandler type-map var 333 | 334 | 335 | ${allDenied} 336 | 337 | 338 | ${mimeConf} 339 | ${loggingConf} 340 | ${browserHacks} 341 | 342 | Include ${httpd}/conf/extra/httpd-default.conf 343 | Include ${httpd}/conf/extra/httpd-autoindex.conf 344 | Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf 345 | Include ${httpd}/conf/extra/httpd-languages.conf 346 | 347 | ${if enableSSL then sslConf else ""} 348 | 349 | # Fascist default - deny access to everything. 350 | 351 | Options FollowSymLinks 352 | AllowOverride None 353 | ${allDenied} 354 | 355 | 356 | # But do allow access to files in the store so that we don't have 357 | # to generate clauses for every generated file that we 358 | # want to serve. 359 | 360 | ${allGranted} 361 | 362 | 363 | # Generate directives for the main server. 364 | ${perServerConf true mainCfg} 365 | 366 | # Always enable virtual hosts; it doesn't seem to hurt. 367 | ${let 368 | ports = map getPort allHosts; 369 | uniquePorts = uniqList {inputList = ports;}; 370 | directives = concatMapStrings (port: "NameVirtualHost *:${toString port}\n") uniquePorts; 371 | in optionalString (!version24) directives 372 | } 373 | 374 | ${let 375 | makeVirtualHost = vhost: '' 376 | 377 | ${perServerConf false vhost} 378 | 379 | ''; 380 | in concatMapStrings makeVirtualHost mainCfg.virtualHosts 381 | } 382 | ''; 383 | 384 | 385 | enablePHP = any (svc: svc.enablePHP) allSubservices; 386 | 387 | 388 | # Generate the PHP configuration file. Should probably be factored 389 | # out into a separate module. 390 | phpIni = pkgs.runCommand "php.ini" 391 | { options = concatStringsSep "\n" 392 | ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices)); 393 | } 394 | '' 395 | cat ${php}/etc/php-recommended.ini > $out 396 | echo "$options" >> $out 397 | ''; 398 | 399 | in 400 | 401 | 402 | { 403 | 404 | ###### interface 405 | 406 | options = { 407 | 408 | services.httpd = { 409 | 410 | enable = mkOption { 411 | type = types.bool; 412 | default = false; 413 | description = "Whether to enable the Apache HTTP Server."; 414 | }; 415 | 416 | package = mkOption { 417 | type = types.path; 418 | default = pkgs.apacheHttpd.override { mpm = mainCfg.multiProcessingModule; }; 419 | example = "pkgs.apacheHttpd_2_4"; 420 | description = '' 421 | Overridable attribute of the Apache HTTP Server package to use. 422 | ''; 423 | }; 424 | 425 | configFile = mkOption { 426 | type = types.path; 427 | default = confFile; 428 | example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ...";''; 429 | description = '' 430 | Override the configuration file used by Apache. By default, 431 | NixOS generates one automatically. 432 | ''; 433 | }; 434 | 435 | extraConfig = mkOption { 436 | type = types.lines; 437 | default = ""; 438 | description = '' 439 | Cnfiguration lines appended to the generated Apache 440 | configuration file. Note that this mechanism may not work 441 | when is overridden. 442 | ''; 443 | }; 444 | 445 | extraModules = mkOption { 446 | type = types.listOf types.unspecified; 447 | default = []; 448 | example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${php}/modules/libphp5.so"; } ]''; 449 | description = '' 450 | Additional Apache modules to be used. These can be 451 | specified as a string in the case of modules distributed 452 | with Apache, or as an attribute set specifying the 453 | name and path of the 454 | module. 455 | ''; 456 | }; 457 | 458 | logPerVirtualHost = mkOption { 459 | type = types.bool; 460 | default = false; 461 | description = '' 462 | If enabled, each virtual host gets its own 463 | access_log and 464 | error_log, namely suffixed by the 465 | of the virtual host. 466 | ''; 467 | }; 468 | 469 | user = mkOption { 470 | type = types.str; 471 | default = "wwwrun"; 472 | description = '' 473 | User account under which httpd runs. The account is created 474 | automatically if it doesn't exist. 475 | ''; 476 | }; 477 | 478 | group = mkOption { 479 | type = types.str; 480 | default = "wwwrun"; 481 | description = '' 482 | Group under which httpd runs. The account is created 483 | automatically if it doesn't exist. 484 | ''; 485 | }; 486 | 487 | logDir = mkOption { 488 | type = types.path; 489 | default = "/var/log/httpd"; 490 | description = '' 491 | Directory for Apache's log files. It is created automatically. 492 | ''; 493 | }; 494 | 495 | stateDir = mkOption { 496 | type = types.path; 497 | default = "/run/httpd"; 498 | description = '' 499 | Directory for Apache's transient runtime state (such as PID 500 | files). It is created automatically. Note that the default, 501 | /run/httpd, is deleted at boot time. 502 | ''; 503 | }; 504 | 505 | virtualHosts = mkOption { 506 | type = types.listOf (types.submodule ( 507 | { options = import ./per-server-options.nix { 508 | inherit pkgs; 509 | forMainServer = false; 510 | }; 511 | })); 512 | default = []; 513 | example = [ 514 | { hostName = "foo"; 515 | documentRoot = "/data/webroot-foo"; 516 | } 517 | { hostName = "bar"; 518 | documentRoot = "/data/webroot-bar"; 519 | } 520 | ]; 521 | description = '' 522 | Specification of the virtual hosts served by Apache. Each 523 | element should be an attribute set specifying the 524 | configuration of the virtual host. The available options 525 | are the non-global options permissible for the main host. 526 | ''; 527 | }; 528 | 529 | phpOptions = mkOption { 530 | type = types.lines; 531 | default = ""; 532 | example = 533 | '' 534 | date.timezone = "CET" 535 | ''; 536 | description = 537 | "Options appended to the PHP configuration file php.ini."; 538 | }; 539 | 540 | multiProcessingModule = mkOption { 541 | type = types.str; 542 | default = "prefork"; 543 | example = "worker"; 544 | description = 545 | '' 546 | Multi-processing module to be used by Apache. Available 547 | modules are prefork (the default; 548 | handles each request in a separate child process), 549 | worker (hybrid approach that starts a 550 | number of child processes each running a number of 551 | threads) and event (a recent variant of 552 | worker that handles persistent 553 | connections more efficiently). 554 | ''; 555 | }; 556 | 557 | maxClients = mkOption { 558 | type = types.int; 559 | default = 150; 560 | example = 8; 561 | description = "Maximum number of httpd processes (prefork)"; 562 | }; 563 | 564 | maxRequestsPerChild = mkOption { 565 | type = types.int; 566 | default = 0; 567 | example = 500; 568 | description = 569 | "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited"; 570 | }; 571 | } 572 | 573 | # Include the options shared between the main server and virtual hosts. 574 | // (import ./per-server-options.nix { 575 | inherit pkgs; 576 | forMainServer = true; 577 | }); 578 | 579 | }; 580 | 581 | 582 | ###### implementation 583 | 584 | config = mkIf config.services.httpd.enable { 585 | 586 | users.extraUsers.wwwrun = 587 | { name = "wwwrun"; 588 | group = "wwwrun"; 589 | description = "Apache httpd user"; 590 | }; 591 | 592 | users.extraGroups.wwwrun = {}; 593 | 594 | environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices; 595 | 596 | services.httpd.phpOptions = 597 | '' 598 | ; Needed for PHP's mail() function. 599 | sendmail_path = sendmail -t -i 600 | 601 | ; Apparently PHP doesn't use $TZ. 602 | date.timezone = "${config.time.timeZone}" 603 | ''; 604 | 605 | docker.buildScripts.apache = 606 | '' 607 | mkdir -m 0750 -p ${mainCfg.stateDir} 608 | chown root.${mainCfg.group} ${mainCfg.stateDir} 609 | ${optionalString version24 '' 610 | mkdir -m 0750 -p "${mainCfg.stateDir}/runtime" 611 | chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime" 612 | ''} 613 | mkdir -m 0700 -p ${mainCfg.logDir} 614 | 615 | ${optionalString (mainCfg.documentRoot != null) 616 | '' 617 | # Create the document root directory if does not exists yet 618 | mkdir -p ${mainCfg.documentRoot} 619 | '' 620 | } 621 | 622 | # Get rid of old semaphores. These tend to accumulate across 623 | # server restarts, eventually preventing it from restarting 624 | # successfully. 625 | for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do 626 | ${pkgs.utillinux}/bin/ipcrm -s $i 627 | done 628 | 629 | # Run the startup hooks for the subservices. 630 | for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do 631 | echo Running Apache startup hook $i... 632 | $i 633 | done 634 | ''; 635 | 636 | supervisord.services.httpd = 637 | { 638 | command = "${httpd}/bin/httpd -f ${httpdConf} -DFOREGROUND"; 639 | }; 640 | }; 641 | } 642 | -------------------------------------------------------------------------------- /nix-docker/modules/servers/http/apache/per-server-options.nix: -------------------------------------------------------------------------------- 1 | # This file defines the options that can be used both for the Apache 2 | # main server configuration, and for the virtual hosts. (The latter 3 | # has additional options that affect the web server as a whole, like 4 | # the user/group to run under.) 5 | 6 | { forMainServer, pkgs }: 7 | 8 | with pkgs.lib; 9 | 10 | { 11 | 12 | hostName = mkOption { 13 | type = types.str; 14 | default = "localhost"; 15 | description = "Canonical hostname for the server."; 16 | }; 17 | 18 | serverAliases = mkOption { 19 | type = types.listOf types.str; 20 | default = []; 21 | example = ["www.example.org" "www.example.org:8080" "example.org"]; 22 | description = '' 23 | Additional names of virtual hosts served by this virtual host configuration. 24 | ''; 25 | }; 26 | 27 | port = mkOption { 28 | type = types.int; 29 | default = 0; 30 | description = '' 31 | Port for the server. 0 means use the default port: 80 for http 32 | and 443 for https (i.e. when enableSSL is set). 33 | ''; 34 | }; 35 | 36 | enableSSL = mkOption { 37 | type = types.bool; 38 | default = false; 39 | description = "Whether to enable SSL (https) support."; 40 | }; 41 | 42 | # Note: sslServerCert and sslServerKey can be left empty, but this 43 | # only makes sense for virtual hosts (they will inherit from the 44 | # main server). 45 | 46 | sslServerCert = mkOption { 47 | type = types.nullOr types.path; 48 | default = null; 49 | example = "/var/host.cert"; 50 | description = "Path to server SSL certificate."; 51 | }; 52 | 53 | sslServerKey = mkOption { 54 | type = types.path; 55 | example = "/var/host.key"; 56 | description = "Path to server SSL certificate key."; 57 | }; 58 | 59 | adminAddr = mkOption ({ 60 | type = types.nullOr types.str; 61 | example = "admin@example.org"; 62 | description = "E-mail address of the server administrator."; 63 | } // (if forMainServer then {} else {default = null;})); 64 | 65 | documentRoot = mkOption { 66 | type = types.nullOr types.path; 67 | default = null; 68 | example = "/data/webserver/docs"; 69 | description = '' 70 | The path of Apache's document root directory. If left undefined, 71 | an empty directory in the Nix store will be used as root. 72 | ''; 73 | }; 74 | 75 | servedDirs = mkOption { 76 | type = types.listOf types.attrs; 77 | default = []; 78 | example = [ 79 | { urlPath = "/nix"; 80 | dir = "/home/eelco/Dev/nix-homepage"; 81 | } 82 | ]; 83 | description = '' 84 | This option provides a simple way to serve static directories. 85 | ''; 86 | }; 87 | 88 | servedFiles = mkOption { 89 | type = types.listOf types.attrs; 90 | default = []; 91 | example = [ 92 | { urlPath = "/foo/bar.png"; 93 | dir = "/home/eelco/some-file.png"; 94 | } 95 | ]; 96 | description = '' 97 | This option provides a simple way to serve individual, static files. 98 | ''; 99 | }; 100 | 101 | extraConfig = mkOption { 102 | type = types.lines; 103 | default = ""; 104 | example = '' 105 | 106 | Options FollowSymlinks 107 | AllowOverride All 108 | 109 | ''; 110 | description = '' 111 | These lines go to httpd.conf verbatim. They will go after 112 | directories and directory aliases defined by default. 113 | ''; 114 | }; 115 | 116 | extraSubservices = mkOption { 117 | type = types.listOf types.unspecified; 118 | default = []; 119 | description = "Extra subservices to enable in the webserver."; 120 | }; 121 | 122 | enableUserDir = mkOption { 123 | type = types.bool; 124 | default = false; 125 | description = '' 126 | Whether to enable serving ~/public_html as 127 | /~username. 128 | ''; 129 | }; 130 | 131 | globalRedirect = mkOption { 132 | type = types.nullOr types.str; 133 | default = null; 134 | example = http://newserver.example.org/; 135 | description = '' 136 | If set, all requests for this host are redirected permanently to 137 | the given URL. 138 | ''; 139 | }; 140 | 141 | logFormat = mkOption { 142 | type = types.str; 143 | default = "common"; 144 | example = "combined"; 145 | description = " 146 | Log format for Apache's log files. Possible values are: combined, common, referer, agent. 147 | "; 148 | }; 149 | 150 | } 151 | -------------------------------------------------------------------------------- /nix-docker/modules/servers/openssh.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | 3 | with pkgs.lib; 4 | 5 | let 6 | 7 | cfg = config.services.openssh; 8 | cfgc = config.programs.ssh; 9 | 10 | nssModulesPath = config.system.nssModules.path; 11 | 12 | permitRootLoginCheck = v: 13 | v == "yes" || 14 | v == "without-password" || 15 | v == "forced-commands-only" || 16 | v == "no"; 17 | 18 | knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts); 19 | 20 | knownHostsFile = pkgs.writeText "ssh_known_hosts" ( 21 | flip concatMapStrings knownHosts (h: 22 | "${concatStringsSep "," h.hostNames} ${builtins.readFile h.publicKeyFile}" 23 | ) 24 | ); 25 | 26 | userOptions = { 27 | 28 | openssh.authorizedKeys = { 29 | keys = mkOption { 30 | type = types.listOf types.str; 31 | default = []; 32 | description = '' 33 | A list of verbatim OpenSSH public keys that should be added to the 34 | user's authorized keys. The keys are added to a file that the SSH 35 | daemon reads in addition to the the user's authorized_keys file. 36 | You can combine the keys and 37 | keyFiles options. 38 | ''; 39 | }; 40 | 41 | keyFiles = mkOption { 42 | type = types.listOf types.str; 43 | default = []; 44 | description = '' 45 | A list of files each containing one OpenSSH public key that should be 46 | added to the user's authorized keys. The contents of the files are 47 | read at build time and added to a file that the SSH daemon reads in 48 | addition to the the user's authorized_keys file. You can combine the 49 | keyFiles and keys options. 50 | ''; 51 | }; 52 | }; 53 | 54 | }; 55 | 56 | authKeysFiles = let 57 | mkAuthKeyFile = u: { 58 | target = "ssh/authorized_keys.d/${u.name}"; 59 | mode = "0444"; 60 | source = pkgs.writeText "${u.name}-authorized_keys" '' 61 | ${concatStringsSep "\n" u.openssh.authorizedKeys.keys} 62 | ${concatMapStrings (f: builtins.readFile f + "\n") u.openssh.authorizedKeys.keyFiles} 63 | ''; 64 | }; 65 | usersWithKeys = attrValues (flip filterAttrs config.users.extraUsers (n: u: 66 | length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0 67 | )); 68 | in map mkAuthKeyFile usersWithKeys; 69 | 70 | in 71 | 72 | { 73 | 74 | ###### interface 75 | 76 | options = { 77 | 78 | services.openssh = { 79 | 80 | enable = mkOption { 81 | type = types.bool; 82 | default = false; 83 | description = '' 84 | Whether to enable the OpenSSH secure shell daemon, which 85 | allows secure remote logins. 86 | ''; 87 | }; 88 | 89 | forwardX11 = mkOption { 90 | type = types.bool; 91 | default = cfgc.setXAuthLocation; 92 | description = '' 93 | Whether to allow X11 connections to be forwarded. 94 | ''; 95 | }; 96 | 97 | allowSFTP = mkOption { 98 | type = types.bool; 99 | default = true; 100 | description = '' 101 | Whether to enable the SFTP subsystem in the SSH daemon. This 102 | enables the use of commands such as sftp and 103 | sshfs. 104 | ''; 105 | }; 106 | 107 | permitRootLogin = mkOption { 108 | default = "without-password"; 109 | type = types.addCheck types.str permitRootLoginCheck; 110 | description = '' 111 | Whether the root user can login using ssh. Valid values are 112 | yes, without-password, 113 | forced-commands-only or 114 | no. 115 | ''; 116 | }; 117 | 118 | gatewayPorts = mkOption { 119 | type = types.str; 120 | default = "no"; 121 | description = '' 122 | Specifies whether remote hosts are allowed to connect to 123 | ports forwarded for the client. See 124 | sshd_config 125 | 5. 126 | ''; 127 | }; 128 | 129 | ports = mkOption { 130 | type = types.listOf types.int; 131 | default = [22]; 132 | description = '' 133 | Specifies on which ports the SSH daemon listens. 134 | ''; 135 | }; 136 | 137 | passwordAuthentication = mkOption { 138 | type = types.bool; 139 | default = true; 140 | description = '' 141 | Specifies whether password authentication is allowed. 142 | ''; 143 | }; 144 | 145 | challengeResponseAuthentication = mkOption { 146 | type = types.bool; 147 | default = true; 148 | description = '' 149 | Specifies whether challenge/response authentication is allowed. 150 | ''; 151 | }; 152 | 153 | hostKeys = mkOption { 154 | type = types.listOf types.attrs; 155 | default = 156 | [ { path = "/etc/ssh/ssh_host_dsa_key"; 157 | type = "dsa"; 158 | bits = 1024; 159 | } 160 | { path = "/etc/ssh/ssh_host_ecdsa_key"; 161 | type = "ecdsa"; 162 | bits = 521; 163 | } 164 | ]; 165 | description = '' 166 | NixOS can automatically generate SSH host keys. This option 167 | specifies the path, type and size of each key. See 168 | ssh-keygen 169 | 1 for supported types 170 | and sizes. 171 | ''; 172 | }; 173 | 174 | authorizedKeysFiles = mkOption { 175 | type = types.listOf types.str; 176 | default = []; 177 | description = "Files from with authorized keys are read."; 178 | }; 179 | 180 | extraConfig = mkOption { 181 | type = types.lines; 182 | default = ""; 183 | description = "Verbatim contents of sshd_config."; 184 | }; 185 | 186 | knownHosts = mkOption { 187 | default = {}; 188 | type = types.loaOf types.optionSet; 189 | description = '' 190 | The set of system-wide known SSH hosts. 191 | ''; 192 | example = [ 193 | { 194 | hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ]; 195 | publicKeyFile = literalExample "./pubkeys/myhost_ssh_host_dsa_key.pub"; 196 | } 197 | { 198 | hostNames = [ "myhost2" ]; 199 | publicKeyFile = literalExample "./pubkeys/myhost2_ssh_host_dsa_key.pub"; 200 | } 201 | ]; 202 | options = { 203 | hostNames = mkOption { 204 | type = types.listOf types.string; 205 | default = []; 206 | description = '' 207 | A list of host names and/or IP numbers used for accessing 208 | the host's ssh service. 209 | ''; 210 | }; 211 | publicKeyFile = mkOption { 212 | description = '' 213 | The path to the public key file for the host. The public 214 | key file is read at build time and saved in the Nix store. 215 | You can fetch a public key file from a running SSH server 216 | with the ssh-keyscan command. 217 | ''; 218 | }; 219 | }; 220 | }; 221 | 222 | }; 223 | 224 | users.extraUsers = mkOption { 225 | options = [ userOptions ]; 226 | }; 227 | 228 | }; 229 | 230 | 231 | ###### implementation 232 | 233 | config = mkIf cfg.enable { 234 | 235 | users.extraUsers = singleton 236 | { name = "sshd"; 237 | uid = config.ids.uids.sshd; 238 | description = "SSH privilege separation user"; 239 | home = "/var/empty"; 240 | }; 241 | 242 | environment.etc = authKeysFiles ++ [ 243 | { source = "${pkgs.openssh}/etc/ssh/moduli"; 244 | target = "ssh/moduli"; 245 | } 246 | { source = knownHostsFile; 247 | target = "ssh/ssh_known_hosts"; 248 | } 249 | ]; 250 | 251 | systemd.services.sshd = 252 | { description = "SSH Daemon"; 253 | 254 | wantedBy = [ "multi-user.target" ]; 255 | 256 | stopIfChanged = false; 257 | 258 | path = [ pkgs.openssh pkgs.gawk ]; 259 | 260 | environment.LD_LIBRARY_PATH = ""; 261 | environment.LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; 262 | 263 | preStart = 264 | '' 265 | ${pkgs.coreutils}/bin/mkdir -m 0755 -p /etc/ssh 266 | 267 | ${flip concatMapStrings cfg.hostKeys (k: '' 268 | if ! [ -f "${k.path}" ]; then 269 | ssh-keygen -t "${k.type}" -b "${toString k.bits}" -f "${k.path}" -N "" 270 | fi 271 | '')} 272 | ''; 273 | 274 | serviceConfig = 275 | { ExecStart = 276 | "${pkgs.openssh}/sbin/sshd " + 277 | "-f ${pkgs.writeText "sshd_config" cfg.extraConfig} -D"; 278 | Restart = "always"; 279 | KillMode = "process"; 280 | PIDFile = "/run/sshd.pid"; 281 | }; 282 | }; 283 | 284 | services.openssh.authorizedKeysFiles = 285 | [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; 286 | 287 | services.openssh.extraConfig = 288 | '' 289 | PidFile /run/sshd.pid 290 | 291 | Protocol 2 292 | 293 | UsePAM no 294 | 295 | AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"} 296 | ${concatMapStrings (port: '' 297 | Port ${toString port} 298 | '') cfg.ports} 299 | 300 | ${optionalString cfgc.setXAuthLocation '' 301 | XAuthLocation ${pkgs.xorg.xauth}/bin/xauth 302 | ''} 303 | 304 | ${if cfg.forwardX11 then '' 305 | X11Forwarding yes 306 | '' else '' 307 | X11Forwarding no 308 | ''} 309 | 310 | ${optionalString cfg.allowSFTP '' 311 | Subsystem sftp ${pkgs.openssh}/libexec/sftp-server 312 | ''} 313 | 314 | PermitRootLogin ${cfg.permitRootLogin} 315 | GatewayPorts ${cfg.gatewayPorts} 316 | PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"} 317 | ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"} 318 | 319 | PrintMotd no # handled by pam_motd 320 | 321 | AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} 322 | 323 | ${flip concatMapStrings cfg.hostKeys (k: '' 324 | HostKey ${k.path} 325 | '')} 326 | ''; 327 | 328 | assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; 329 | message = "cannot enable X11 forwarding without setting xauth location";}]; 330 | 331 | }; 332 | 333 | } 334 | -------------------------------------------------------------------------------- /nix-docker/modules/servers/supervisord.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs.lib; 3 | let 4 | serviceOpts = { name, config, ...}: { 5 | options = { 6 | command = mkOption { 7 | description = "The command to execute"; 8 | }; 9 | directory = mkOption { 10 | default = "/"; 11 | description = "Current directory when running the command"; 12 | }; 13 | user = mkOption { 14 | default = "root"; 15 | description = "The user to run the command as"; 16 | }; 17 | environment = mkOption { 18 | default = {}; 19 | example = { 20 | PATH = "/some/path"; 21 | }; 22 | }; 23 | startsecs = mkOption { 24 | default = 1; 25 | example = 0; 26 | }; 27 | }; 28 | }; 29 | services = config.supervisord.services; 30 | in { 31 | options = { 32 | supervisord = { 33 | enable = mkOption { 34 | default = true; 35 | type = types.bool; 36 | }; 37 | 38 | services = mkOption { 39 | default = {}; 40 | type = types.loaOf types.optionSet; 41 | description = '' 42 | Supervisord services to start 43 | ''; 44 | options = [ serviceOpts ]; 45 | }; 46 | 47 | tailLogs = mkOption { 48 | default = false; 49 | type = types.bool; 50 | description = '' 51 | Whether or not to tail all logs to standard out. 52 | ''; 53 | }; 54 | 55 | configFile = mkOption {}; 56 | }; 57 | }; 58 | 59 | config = mkIf config.supervisord.enable { 60 | supervisord.configFile = pkgs.writeText "supervisord.conf" '' 61 | [supervisord] 62 | logfile=/var/log/supervisord/supervisord.log 63 | 64 | ${concatMapStrings (name: 65 | let 66 | cfg = getAttr name services; 67 | in 68 | '' 69 | [program:${name}] 70 | command=${cfg.command} 71 | environment=${concatMapStrings (name: "${name}=\"${toString (getAttr name cfg.environment)}\",") (attrNames cfg.environment)} 72 | directory=${cfg.directory} 73 | redirect_stderr=true 74 | stdout_logfile=/var/log/supervisord/${name}.log 75 | user=${cfg.user} 76 | startsecs=${toString cfg.startsecs} 77 | '' 78 | ) (attrNames services) 79 | } 80 | ''; 81 | 82 | docker.bootScript = '' 83 | mkdir -p /var/log/supervisord 84 | ${pkgs.pythonPackages.supervisor}/bin/supervisord -c ${config.supervisord.configFile} ${if config.supervisord.tailLogs then '' 85 | 86 | sleep 2 87 | touch /var/log/supervisord/test.log 88 | tail -n 100 -f /var/log/supervisord/*.log 89 | '' else "-n"} 90 | ''; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /nix-docker/modules/services/networking/bind.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | 3 | with lib; 4 | 5 | let 6 | 7 | cfg = config.services.bind; 8 | 9 | bindUser = "named"; 10 | 11 | confFile = pkgs.writeText "named.conf" 12 | '' 13 | acl cachenetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} }; 14 | acl badnetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} }; 15 | 16 | options { 17 | listen-on {any;}; 18 | listen-on-v6 {any;}; 19 | allow-query { cachenetworks; }; 20 | blackhole { badnetworks; }; 21 | forward first; 22 | forwarders { ${concatMapStrings (entry: " ${entry}; ") cfg.forwarders} }; 23 | directory "/var/run/named"; 24 | pid-file "/var/run/named/named.pid"; 25 | }; 26 | 27 | ${ concatMapStrings 28 | ({ name, file, master ? true, slaves ? [], masters ? [] }: 29 | '' 30 | zone "${name}" { 31 | type ${if master then "master" else "slave"}; 32 | file "${file}"; 33 | ${ if master then 34 | '' 35 | allow-transfer { 36 | ${concatMapStrings (ip: "${ip};\n") slaves} 37 | }; 38 | '' 39 | else 40 | '' 41 | masters { 42 | ${concatMapStrings (ip: "${ip};\n") masters} 43 | }; 44 | '' 45 | } 46 | allow-query { any; }; 47 | }; 48 | '') 49 | cfg.zones } 50 | ''; 51 | 52 | in 53 | 54 | { 55 | 56 | ###### interface 57 | 58 | options = { 59 | 60 | services.bind = { 61 | 62 | enable = mkOption { 63 | default = false; 64 | description = " 65 | Whether to enable BIND domain name server. 66 | "; 67 | }; 68 | 69 | cacheNetworks = mkOption { 70 | default = ["127.0.0.0/24"]; 71 | description = " 72 | What networks are allowed to use us as a resolver. 73 | "; 74 | }; 75 | 76 | blockedNetworks = mkOption { 77 | default = []; 78 | description = " 79 | What networks are just blocked. 80 | "; 81 | }; 82 | 83 | ipv4Only = mkOption { 84 | default = false; 85 | description = " 86 | Only use ipv4, even if the host supports ipv6. 87 | "; 88 | }; 89 | 90 | forwarders = mkOption { 91 | default = config.networking.nameservers; 92 | description = " 93 | List of servers we should forward requests to. 94 | "; 95 | }; 96 | 97 | zones = mkOption { 98 | default = []; 99 | description = " 100 | List of zones we claim authority over. 101 | master=false means slave server; slaves means addresses 102 | who may request zone transfer. 103 | "; 104 | example = [{ 105 | name = "example.com"; 106 | master = false; 107 | file = "/var/dns/example.com"; 108 | masters = ["192.168.0.1"]; 109 | slaves = []; 110 | }]; 111 | }; 112 | 113 | configFile = mkOption { 114 | default = confFile; 115 | description = " 116 | Overridable config file to use for named. By default, that 117 | generated by nixos. 118 | "; 119 | }; 120 | 121 | }; 122 | 123 | }; 124 | 125 | 126 | ###### implementation 127 | 128 | config = mkIf config.services.bind.enable { 129 | 130 | users.extraUsers = singleton 131 | { name = bindUser; 132 | uid = config.ids.uids.bind; 133 | description = "BIND daemon user"; 134 | }; 135 | 136 | systemd.services.bind = 137 | { description = "BIND name server job"; 138 | 139 | preStart = 140 | '' 141 | ${pkgs.coreutils}/bin/mkdir -p /var/run/named 142 | chown ${bindUser} /var/run/named 143 | ''; 144 | 145 | serviceConfig = 146 | { ExecStart = "${pkgs.bind}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f"; 147 | Restart = "always"; 148 | KillMode = "process"; 149 | PIDFile = "/run/bind.pid"; 150 | }; 151 | }; 152 | 153 | }; 154 | 155 | } 156 | -------------------------------------------------------------------------------- /nix-docker/modules/shim/systemd.nix: -------------------------------------------------------------------------------- 1 | { pkgs, config, ... }: 2 | with pkgs.lib; 3 | let 4 | services = config.systemd.services; 5 | 6 | isOneShot = cfg: hasAttr "Type" cfg.serviceConfig && cfg.serviceConfig.Type == "oneshot"; 7 | 8 | runServices = filterAttrs (name: cfg: !(isOneShot cfg)) services; 9 | 10 | oneShotServices = filterAttrs (name: cfg: isOneShot cfg) services; 11 | 12 | configToCommand = name: cfg: '' 13 | #!/bin/sh -e 14 | ${if hasAttr "preStart" cfg then cfg.preStart else ""} 15 | ${if hasAttr "ExecStart" cfg.serviceConfig then 16 | cfg.serviceConfig.ExecStart 17 | else if hasAttr "script" cfg then 18 | cfg.script 19 | else 20 | "" 21 | } 22 | ${if hasAttr "postStart" cfg then cfg.postStart else ""} 23 | ''; 24 | 25 | in { 26 | 27 | options = { 28 | systemd.services = mkOption { 29 | default = {}; 30 | }; # TODO make more specific 31 | systemd.user = mkOption { 32 | default = {}; 33 | }; 34 | }; 35 | 36 | config = { 37 | docker.buildScripts."1-systemd-oneshot" = concatMapStrings (name: "${configToCommand name (getAttr name oneShotServices)}\n") (attrNames oneShotServices); 38 | 39 | supervisord.services = listToAttrs (map (name: 40 | let 41 | cfg = getAttr name runServices; 42 | in 43 | { 44 | name = name; 45 | value = { 46 | command = pkgs.writeScript "${name}-run" (configToCommand name cfg); 47 | user = if hasAttr "User" cfg.serviceConfig then cfg.serviceConfig.User else "root"; 48 | environment = (if hasAttr "environment" cfg then cfg.environment else {}) // 49 | (if hasAttr "path" cfg then 50 | { PATH = concatStringsSep ":" (map (prg: "${prg}/bin") cfg.path); } 51 | else {}); 52 | }; 53 | } 54 | ) (attrNames runServices)); 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /nix-docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "nix-docker", 3 | "version" : "0.1.0", 4 | "dependencies" : { 5 | "optimist" : "0.6.0" 6 | }, 7 | "directories": { 8 | "bin": "./bin" 9 | } 10 | } -------------------------------------------------------------------------------- /samples/apache-config.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | { 3 | # Expose the apache port (80) 4 | docker.ports = [ 5 | config.services.httpd.port 6 | ]; 7 | 8 | services.httpd = { 9 | enable = true; 10 | port = 80; 11 | documentRoot = ./www; 12 | adminAddr = "zef.hemel@logicblox.com"; 13 | }; 14 | } -------------------------------------------------------------------------------- /samples/fancy.nix: -------------------------------------------------------------------------------- 1 | # Boots up all kinds of services: redis, apache, ssh, mysql 2 | { config, pkgs, ... }: 3 | { 4 | docker.ports = [ 1234 80 22 ]; 5 | 6 | services.redis = { 7 | enable = true; 8 | port = 1234; 9 | logLevel = "debug"; 10 | }; 11 | 12 | services.httpd.enable = true; 13 | services.httpd.port = 80; 14 | services.httpd.documentRoot = ./www; 15 | services.httpd.adminAddr = "zef.hemel@logicblox.com"; 16 | 17 | services.mysql.enable = true; 18 | services.openssh.enable = true; 19 | 20 | supervisord.tailLogs = true; 21 | 22 | users.extraUsers.zef = { 23 | group = "users"; 24 | home = "/"; 25 | shell = "/bin/bash"; 26 | createHome = true; 27 | openssh.authorizedKeys.keys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjWpdyDIsS09lWlOsMG9OMTHB/N/afVU12BwKcyjjhbezPdFEgHK4cZBN7m1bvoFKl832BdB+ZjeRH4UGBcUpvrFu1vE7Lf/0vZDU7qzzWQE9V+tfSPwDiXPf9QnCYeZmYPDHUHDUEse9LKBZbt6UKF1tuTD8ussV5jvEFBaesDhCqD1TJ4b4O877cdx9+VTOuDSEDm32jQ2az27d1b/5DoEKBe5cJSC3PhObAQ7OAYrVVBFX9ffKpaSvV6yqo+rhCmXP9DjNgBwMtElreoXL3h5Xbw2AiER5oHNUAEA2XGpnOVOr7ZZUAbMC0/0dq387jQZCqe7gIDZCqjDpGhUa9 zefhemel@gmail.com" ]; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /samples/ssh-config.nix: -------------------------------------------------------------------------------- 1 | # Runs an SSH server in a Docker container 2 | # Creates a user "you" that you can login with 3 | { config, pkgs, ... }: 4 | { 5 | docker.ports = [ 22 ]; 6 | 7 | docker.verbose = true; 8 | supervisord.tailLogs = true; 9 | 10 | services.openssh.enable = true; 11 | 12 | users.extraUsers.you = { 13 | group = "users"; 14 | home = "/"; 15 | shell = "/bin/bash"; 16 | createHome = true; 17 | openssh.authorizedKeys.keys = [ 18 | # Replace with your own SSH key (e.g. from ~/.ssh/id_rsa.pub) 19 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjWpdyDIsS09lWlOsMG9OMTHB/N/afVU12BwKcyjjhbezPdFEgHK4cZBN7m1bvoFKl832BdB+ZjeRH4UGBcUpvrFu1vE7Lf/0vZDU7qzzWQE9V+tfSPwDiXPf9QnCYeZmYPDHUHDUEse9LKBZbt6UKF1tuTD8ussV5jvEFBaesDhCqD1TJ4b4O877cdx9+VTOuDSEDm32jQ2az27d1b/5DoEKBe5cJSC3PhObAQ7OAYrVVBFX9ffKpaSvV6yqo+rhCmXP9DjNgBwMtElreoXL3h5Xbw2AiER5oHNUAEA2XGpnOVOr7ZZUAbMC0/0dq387jQZCqe7gIDZCqjDpGhUa9" 20 | ]; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /samples/wordpress.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | # We'll start with defining some local variables 3 | let 4 | # Settings used to configure the wordpress MySQL database 5 | mysqlHost = "localhost"; 6 | mysqlDb = "wordpress"; 7 | mysqlUser = "wordpress"; 8 | mysqlPassword = "wordpress"; 9 | 10 | # Data paths 11 | mysqlDataPath = "/data/mysql"; 12 | wordpressUploads = "/data/uploads"; 13 | apacheLogs = "/data/log"; 14 | 15 | # Our bare-bones wp-config.php file using the above settings 16 | wordpressConfig = pkgs.writeText "wp-config.php" '' 17 | 32 | RewriteEngine On 33 | RewriteBase / 34 | RewriteCond %{REQUEST_FILENAME} !-f 35 | RewriteCond %{REQUEST_FILENAME} !-d 36 | RewriteRule . /index.php [L] 37 | 38 | ''; 39 | 40 | # For shits and giggles, let's package the responsive theme 41 | responsiveTheme = pkgs.stdenv.mkDerivation { 42 | name = "responsive-theme"; 43 | # Download the theme from the wordpress site 44 | src = pkgs.fetchurl { 45 | url = http://wordpress.org/themes/download/responsive.1.9.3.9.zip; 46 | sha256 = "0397a8nd8q384z2i4lw4a1ij835walp3b5bfdmr76mdl27vbkvmd"; 47 | }; 48 | # We need unzip to build this package 49 | buildInputs = [ pkgs.unzip ]; 50 | # Installing simply means copying all files to the output directory 51 | installPhase = "mkdir -p $out; cp -R * $out/"; 52 | }; 53 | 54 | # The wordpress package itself 55 | wordpress = pkgs.stdenv.mkDerivation { 56 | name = "wordpress"; 57 | # Fetch directly from the wordpress site, want to upgrade? 58 | # Just change the version URL and update the hash 59 | src = pkgs.fetchurl { 60 | url = http://wordpress.org/wordpress-3.7.1.tar.gz; 61 | sha256 = "1m2dlr54fqf5m4kgqc5hrrisrsia0r5j1r2xv1f12gmzb63swsvg"; 62 | }; 63 | installPhase = '' 64 | mkdir -p $out 65 | # Copy all the wordpress files we downloaded 66 | cp -R * $out/ 67 | # We'll symlink the wordpress config 68 | ln -s ${wordpressConfig} $out/wp-config.php 69 | # As well as our custom .htaccess 70 | ln -s ${htaccess} $out/.htaccess 71 | # And the uploads directory 72 | ln -s ${wordpressUploads} $out/wp-content/uploads 73 | # And the responsive theme 74 | ln -s ${responsiveTheme} $out/wp-content/themes/responsive 75 | # You can add plugins the same way 76 | ''; 77 | }; 78 | # This is where the body of our configuration starts 79 | in { 80 | # Expose just port 80 81 | docker.ports = [ 80 ]; 82 | 83 | # And let's store valuable data in a volume 84 | docker.volumes = [ "/data" ]; 85 | 86 | # Apache configuration 87 | services.httpd = { 88 | enable = true; 89 | adminAddr = "zef@zef.me"; 90 | 91 | # We'll set the wordpress package as our document root 92 | documentRoot = wordpress; 93 | 94 | # Let's store our logs in the volume as well 95 | logDir = apacheLogs; 96 | 97 | # And enable the PHP5 apache module 98 | extraModules = [ { name = "php5"; path = "${pkgs.php}/modules/libphp5.so"; } ]; 99 | 100 | # And some extra config to make things work nicely 101 | extraConfig = '' 102 | 103 | DirectoryIndex index.php 104 | Allow from * 105 | Options FollowSymLinks 106 | AllowOverride All 107 | 108 | ''; 109 | }; 110 | 111 | # Disable these when not using "localhost" as database name 112 | services.mysql.enable = true; 113 | # Let's store our data in the volume, so it'll survive restarts 114 | services.mysql.dataDir = mysqlDataPath; 115 | 116 | # This service runs evey time you start and ensures two things: 117 | # 1. That our uploads directory exists and is owned by Apache 118 | # 2. that the MySQL database exists 119 | supervisord.services.initApp = { 120 | command = pkgs.writeScript "init-wordpress.sh" '' 121 | #!/bin/sh 122 | 123 | mkdir -p ${wordpressUploads} 124 | chown ${config.services.httpd.user} ${wordpressUploads} 125 | 126 | if [ ! -d /data/mysql/${mysqlDb} ]; then 127 | # Wait until MySQL is up 128 | while [ ! -e /var/run/mysql/mysqld.pid ]; do 129 | sleep 1 130 | done 131 | mysql -e 'CREATE DATABASE ${mysqlDb};' 132 | mysql -e 'GRANT ALL ON ${mysqlDb}.* TO ${mysqlUser}@localhost IDENTIFIED BY "${mysqlPassword}";' 133 | fi 134 | ''; 135 | # This script can exit immediately, no worries 136 | startsecs = 0; 137 | }; 138 | } 139 | -------------------------------------------------------------------------------- /samples/www/index.html: -------------------------------------------------------------------------------- 1 | Hello world 2 2 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | These are scripts to automatically install Docker and Nix and has been tested to work on Ubuntu 13.04. 2 | -------------------------------------------------------------------------------- /scripts/install-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # disable apt frontend to prevent 4 | # any troublesome questions 5 | export DEBIAN_FRONTEND=noninteractive 6 | 7 | which docker || apt-get install -y docker.io 8 | -------------------------------------------------------------------------------- /scripts/install-extradisk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | if [ ! -d /data ]; then 6 | mkfs.ext4 -F /dev/sdb 7 | mkdir -p /nix 8 | echo "/dev/sdb /nix ext4 defaults 0 2" >> /etc/fstab 9 | mount /nix 10 | mkdir -p /nix/docker-lib 11 | ln -sf /nix/docker-lib /var/lib/docker 12 | fi 13 | -------------------------------------------------------------------------------- /scripts/install-nix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # disable apt frontend to prevent 6 | # any troublesome questions 7 | export DEBIAN_FRONTEND=noninteractive 8 | file=nix_1.8-1_amd64.deb 9 | url="http://hydra.nixos.org/build/17897583/download/1/$file" 10 | store_dir=/nix/store 11 | bgroup=nixbld 12 | ugroup=nix-users 13 | 14 | # Check if Nix is already installed 15 | if ! which nix-env; then 16 | 17 | 18 | for group in $bgroup $ugroup; do 19 | getent group $group > /dev/null || groupadd -r $group 20 | done 21 | 22 | for n in 1 2 3 4 5 6 7 8 9 10; do 23 | useradd -c "Nix build user $n" -d /var/empty -g $bgroup -G $bgroup \ 24 | -M -N -r -s `which nologin` $bgroup$n || [ "$?" -eq 9 ] 25 | done 26 | 27 | mkdir -p /etc/nix 28 | cat << EOF > /etc/nix/nix.conf 29 | build-users-group = $bgroup 30 | trusted-users = $ugroup 31 | EOF 32 | 33 | wget -q $url -O $file 34 | 35 | apt-get install -y -q --force-yes gdebi-core 36 | 37 | gdebi -n ./$file 38 | 39 | # Start nix daemon 40 | initctl start nix-daemon 41 | 42 | 43 | mkdir -m 1777 $store_dir 44 | chgrp -R nixbld $store_dir 45 | 46 | mkdir -p -m 1777 /nix/var/nix/profiles/per-user 47 | mkdir -p -m 1777 /nix/var/nix/gcroots/per-user 48 | socket="/nix/var/nix/daemon-socket" 49 | 50 | chgrp $ugroup $socket 51 | chmod ug=rwx,o= $socket 52 | usermod -a -G $ugroup vagrant 53 | 54 | fi 55 | 56 | cat << EOF > /etc/profile.d/01nix-user.sh 57 | 58 | if id -Gn | egrep -q '(^| )'${ugroup}'( |\$)'; 59 | then 60 | export NIX_PROFILES="/nix/var/nix/profiles/default \$HOME/.nix-profile" 61 | export NIX_USER_PROFILE_DIR=/nix/var/nix/profiles/per-user/\$USER 62 | export NIX_USER_GCROOTS_DIR=/nix/var/nix/gcroots/per-user/\$USER 63 | export NIX_REMOTE=daemon 64 | 65 | for i in \$NIX_PROFILES; do 66 | export PATH=\$i/bin:\$PATH 67 | done 68 | 69 | 70 | if [ ! -e \$NIX_USER_PROFILE_DIR ]; then 71 | mkdir -m 0755 -p \$NIX_USER_PROFILE_DIR 72 | chown -R \$USER \$NIX_USER_PROFILE_DIR 73 | fi 74 | if test "\$(stat --printf '%u' \$NIX_USER_PROFILE_DIR)" != "\$(id -u)"; then 75 | echo "WARNING: bad ownership on \$NIX_USER_PROFILE_DIR" >&2 76 | fi 77 | 78 | #rm -f \$HOME/.nix-profile 79 | if ! test -L \$HOME/.nix-profile; then 80 | echo "creating \$HOME/.nix-profile" >&2 81 | if test "\$USER" != root; then 82 | ln -s \$NIX_USER_PROFILE_DIR/profile \$HOME/.nix-profile 83 | else 84 | # Root installs in the system-wide profile by default. 85 | ln -s /nix/var/nix/profiles/default \$HOME/.nix-profile 86 | fi 87 | fi 88 | 89 | if [ ! -e \$NIX_USER_GCROOTS_DIR ]; then 90 | mkdir -m 0755 -p \$NIX_USER_GCROOTS_DIR 91 | chown -R \$USER \$NIX_USER_GCROOTS_DIR 92 | fi 93 | if test "\$(stat --printf '%u' \$NIX_USER_GCROOTS_DIR)" != "\$(id -u)"; then 94 | echo "WARNING: bad ownership on \$NIX_USER_GCROOTS_DIR" >&2 95 | fi 96 | 97 | 98 | if [ ! -e \$HOME/.nix-defexpr -o -L \$HOME/.nix-defexpr ]; then 99 | echo "creating \$HOME/.nix-defexpr" >&2 100 | rm -f \$HOME/.nix-defexpr 101 | mkdir \$HOME/.nix-defexpr 102 | if [ "\$USER" != root ]; then 103 | ln -s /nix/var/nix/profiles/per-user/root/channels \ 104 | \$HOME/.nix-defexpr/channels_root 105 | fi 106 | fi 107 | fi 108 | EOF 109 | -------------------------------------------------------------------------------- /zedconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "modes": { 3 | "python": { 4 | "filenames": ["nix-docker"] 5 | }, 6 | "ruby": { 7 | "filenames": ["Vagrantfile"] 8 | } 9 | } 10 | } 11 | --------------------------------------------------------------------------------