refractor the code for another purpose

This commit is contained in:
Jaka Hudoklin 2014-02-17 00:31:11 +01:00
parent 3f234958bc
commit 99b6f91e19
29 changed files with 114 additions and 2175 deletions

View File

@ -1,74 +0,0 @@
nix-docker
==========
Use [NixOS](http://nixos.org/nixos) configurations to provision [Docker](http://docker.io)
containers.
[Read about the what and why in this blog post](http://zef.me/6049/nix-docker)
Installation with Vagrant
-------------------------
The easy way to do this is to use [Vagrant](http://vagrantup.com).
When you have Vagrant installed:
git clone https://github.com/zefhemel/nix-docker.git
cd nix-docker
vagrant up
vagrant ssh
If all went well, you're now in a VM that has both Docker and Nix installed
and `nix-docker` in its path. You can now cd into the nix-docker/samples
directory to try to build some of the examples. Note that the `~/nix-docker`
directory is mounted from your host machine, so you can edit your files with
your favorite editor and have them available within the VM.
Installation
------------
To use nix-docker you need [Nix](http://nixos.org/nix) installed as well as
[Docker](http://www.docker.io). Realistically, your best way to do this on
an Ubuntu (12.04 or 13.04) box. Once these are installed, installing
`nix-docker` is as simple as:
git clone https://github.com/zefhemel/nix-docker.git
nix-env -f nix-docker/default.nix -i nix-docker
Usage
-----
To build a stand-alone Docker image:
nix-docker -b -t my-image configuration.nix
This will build the configuration specified in `configuration.nix`, have a look
in the `samples/` directory for examples. It will produce a docker image named
`my-image` which you can then run anywhere. Use `username/my-image` to be able
to push them to the Docker index.
To build a host-mounted package:
nix-docker -t my-image configuration.nix
This will produce a Nix package (symlinked in the current directory in `result`)
containing a script you can use to spawn the container using Docker, e.g.:
sudo -E ./result/sbin/docker-run
to run the container in the foreground, or:
sudo -E ./result/sbin/docker-run -d
to daemonize it. What the `docker-run` script will do is check if there's
already a docker image available with the current image name and tag based on
the Nix build hash. If not, it will quickly build it first (these images take up
barely any space on disk). Then, it will boot up the container.
Distributing host-mounted packages is done by first copying the Nix closure
resulting from the build to the target machine (when you do the build it
will give you example commands to run):
nix-copy-closure root@targetmachine /nix/store/....
Then, you can spawn the container remotely with the script path provided
in the output of the build command.

35
Vagrantfile vendored
View File

@ -1,35 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.box = "raring64"
config.vm.box_url = "http://goo.gl/ceHWg"
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "1024"]
# We'll attach an extra 50GB disk for all nix and docker data
file_to_disk = "disk.vmdk"
vb.customize ['createhd', '--filename', file_to_disk, '--size', 50 * 1024]
vb.customize ['storageattach', :id, '--storagectl', 'SATA Controller', '--port', 1, '--device', 0, '--type', 'hdd', '--medium', file_to_disk]
end
config.vm.provision :shell, inline: <<eos
echo "PATH=/home/vagrant/nix-docker/nix-docker/bin:\\$PATH" > /etc/profile.d/path.sh
if [ ! -d /data ]; then
mkfs.ext4 -F /dev/sdb
mkdir -p /nix
echo "/dev/sdb /nix ext4 defaults 0 2" >> /etc/fstab
mount /nix
mkdir -p /nix/docker-lib
ln -s /nix/docker-lib /var/lib/docker
fi
eos
config.vm.provision :shell, :path => "scripts/install-docker.sh"
config.vm.provision :shell, inline: "chmod 777 /var/run/docker.sock"
config.vm.provision :shell, :path => "scripts/install-nix.sh"
config.vm.network "private_network", ip: "192.168.22.22"
config.vm.synced_folder ".", "/home/vagrant/nix-docker"
end

View File

@ -1,4 +0,0 @@
{ config, pkgs, ... }:
{
services.mysql.enable = true;
}

View File

@ -1,2 +0,0 @@
#!/bin/sh
sudo -E nix-docker -b -t zefhemel/base-nix --from busybox base-configuration.nix

View File

@ -1,12 +1,42 @@
{ pkgs ? import <nixpkgs> { system = "x86_64-linux"; }
, name
, configuration ? <configuration>
, baseImage ? "busybox"
}:
with pkgs.lib;
let
pkgs = import <nixpkgs> {};
in
pkgs.stdenv.mkDerivation {
name = "nix-docker-0.1";
src = ./nix-docker;
buildInputs = [ pkgs.python27 ];
installPhase = ''
mkdir -p $out
cp -R * $out/
'';
}
moduleList = [
./user.nix ./supervisord.nix ./systemd.nix ./environment.nix
<nixpkgs/nixos/modules/config/users-groups.nix>
<nixpkgs/nixos/modules/misc/ids.nix>
<nixpkgs/nixos/modules/misc/assertions.nix>
<nixpkgs/nixos/modules/services/databases/redis.nix>
<nixpkgs/nixos/modules/services/databases/mysql.nix>
<nixpkgs/nixos/modules/services/databases/postgresql.nix>
<nixpkgs/nixos/modules/testing/service-runner.nix>
];
config = (evalModules {
modules = [configuration] ++ moduleList;
args = { inherit pkgs; };
}).config;
systemd = import ./systemd.nix { inherit pkgs config; };
startScript = pkgs.writeScript "build" ''
#!/bin/sh
${config.userNix.startScript}
'';
in pkgs.stdenv.mkDerivation {
inherit name;
src = ./.;
phases = [ "installPhase" ];
installPhase = ''
mkdir -p $out/etc/start
ln -s ${startScript} $out/etc/start
'';
}

23
environment.nix Normal file
View File

@ -0,0 +1,23 @@
{ config, pkgs, ... }:
with pkgs.lib;
{
options = {
environment.systemPackages = mkOption {
default = [];
description = "Packages to be put in the system profile.";
};
environment.umask = mkOption {
default = "002";
type = with types; string;
};
# HACK HACK
system.activationScripts.etc = mkOption {}; # Ignore
system.build.etc = mkOption {}; # Ignore
};
config = {
environment.systemPackages = with pkgs; [ coreutils ];
};
}

8
foo.nix Normal file
View File

@ -0,0 +1,8 @@
{ config, pkgs, ... }:
{
config = {
services.postgresql.enable = true;
services.postgresql.package = pkgs.postgresql92;
services.postgresql.dataDir = "/tmp/postgres";
};
}

View File

@ -1,19 +0,0 @@
[
./modules/config/docker.nix
./modules/config/user-groups.nix
./modules/config/environment.nix
./modules/servers/supervisord.nix
./modules/shim/systemd.nix
<nixpkgs/nixos/modules/system/etc/etc.nix>
<nixpkgs/nixos/modules/misc/assertions.nix>
<nixpkgs/nixos/modules/misc/ids.nix>
<nixpkgs/nixos/modules/services/databases/redis.nix>
<nixpkgs/nixos/modules/services/databases/mysql.nix>
<nixpkgs/nixos/modules/programs/ssh.nix>
<nixpkgs/nixos/modules/services/search/elasticsearch.nix>
# These modules needed some patching to work well
./modules/servers/http/apache/default.nix
./modules/servers/openssh.nix
]

View File

@ -1,87 +0,0 @@
#!/usr/bin/python
import os.path
import subprocess
import argparse
import sys
import shutil
import tempfile
parser = argparse.ArgumentParser(description='Build Docker images with Nix', prog='nix-docker')
parser.add_argument('configuration', nargs='?', help='configuration.nix file to build', default="configuration.nix")
parser.add_argument('-b', action='store_true', help="Build a docker image")
parser.add_argument('-t', help='Image name', default="nix-docker-build")
parser.add_argument('--from', help="Image to use as a base image", default="busybox")
parser.add_argument('-f', action='store_true', help="Force nix-docker into something it does not what to do.")
args = parser.parse_args()
base_image = getattr(args, "from")
configuration_file = args.configuration
image_name = args.t
full_build = args.b
temp_dir = tempfile.mkdtemp()
nix_closure_temp = "%s/nix-closure" % temp_dir
force = args.f
root_path = os.path.normpath(os.path.realpath(sys.argv[0]) + "/../..")
def build():
print "Building Nix closure for the image..."
if args.b:
mountBuild = "false"
else:
mountBuild = "true"
cmd = ["nix-build", "%s/docker.nix" % root_path, '-I', 'configuration=' + configuration_file, "--arg", "mountBuild", mountBuild, "--argstr", "name", image_name, "--argstr", "baseImage", base_image]
if full_build:
cmd.append("--no-out-link")
nix_path = subprocess.check_output(cmd)
return nix_path.strip()
def get_available_nix_paths():
try:
paths_string = subprocess.check_output(["docker", "run", base_image, "/bin/ls", "/nix/store"])
return paths_string.strip().split("\n")
except:
return []
def cleanup():
subprocess.check_call(["rm", "-rf", temp_dir])
def copy_closure_and_build(package_nix_path, available_paths):
paths = subprocess.check_output(["nix-store", "-qR", package_nix_path]).strip().split("\n")
print "Available", available_paths
paths_to_copy = filter(lambda path: not path[len("/nix/store/"):] in available_paths, paths)
print "New Nix store paths to copy to image:"
for path in paths_to_copy:
print " ", path
os.mkdir(nix_closure_temp)
cmd = ["cp", "-r"]
cmd.extend(paths_to_copy)
cmd.append(nix_closure_temp)
shutil.copyfile("%s/Dockerfile" % package_nix_path, "%s/Dockerfile" % temp_dir)
subprocess.check_call(cmd)
subprocess.check_call(["chown", "-R", "root:root", nix_closure_temp])
subprocess.check_call(["docker", "build", "-rm=true", "-t", image_name, temp_dir])
cleanup()
if __name__ == '__main__':
if full_build and os.getenv("USER") != "root" and not force:
print "When doing a full build you need to run nix-docker as root. Rerun this command with 'sudo -E nix-docker ...'"
sys.exit(1)
if not os.path.exists(configuration_file):
print "Could not find configuration file: %s" % configuration_file
sys.exit(1)
package_nix_path = build()
if full_build:
available_paths = get_available_nix_paths()
copy_closure_and_build(package_nix_path, available_paths)
print "To run: sudo docker run -t -i", image_name
else:
print "Result in", package_nix_path, "test with sudo ./result/sbin/docker-run"
print "To deploy: nix-copy-closure -s root@<machine>", package_nix_path
print "To run: ssh root@<machine>", package_nix_path + "/sbin/docker-run -d"

View File

@ -1,82 +0,0 @@
{ pkgs ? import <nixpkgs> { system = "x86_64-linux"; }
, name
, configuration ? <configuration>
, mountBuild ? true
, baseImage ? "busybox"
}:
with pkgs.lib;
let
config = evalModules {
modules = concatLists [ [configuration] (import ./all-modules.nix) ];
args = { inherit pkgs; };
};
localNixPath = pkg: "nix_store/${substring 11 (stringLength pkg.outPath) pkg.outPath}";
systemd = import ./systemd.nix { inherit pkgs config; };
environment = import ./environment.nix { inherit pkgs config; };
verboseFlag = if config.config.docker.verbose then "v" else "";
buildScript = pkgs.writeScript "build" ''
#!/bin/sh -e${verboseFlag}
${config.config.docker.buildScript}
'';
bootScript = pkgs.writeScript "boot" ''
#!/bin/sh -e${verboseFlag}
umask ${config.config.environment.umask}
${if mountBuild then config.config.docker.buildScript else ""}
${config.config.docker.bootScript}
'';
dockerFile = pkgs.writeText "Dockerfile" ''
FROM ${if mountBuild then "busybox" else baseImage}
${if !mountBuild then
''
ADD nix-closure /nix/store
RUN ${buildScript}
''
else ""}
CMD ${bootScript}
${
concatMapStrings (port: "EXPOSE ${toString port}\n") config.config.docker.ports
}
${
concatMapStrings (port: "VOLUME ${toString port}\n") config.config.docker.volumes
}
'';
imageHash = substring 11 8 dockerFile.outPath;
runContainerScript = pkgs.writeScript "docker-run" ''
#!/usr/bin/env bash
if [ "" == "$(docker images | grep -E "${name}\s*${imageHash}")" ]; then
docker build -t ${name}:${imageHash} $(dirname $0)/..
fi
OPTIONS="-t -i $*"
if [ "$1" == "-d" ]; then
OPTIONS="$*"
fi
docker run $OPTIONS ${if mountBuild then "-v /nix/store:/nix/store" else ""} ${name}:${imageHash}
'';
in pkgs.stdenv.mkDerivation {
name = replaceChars ["/"] ["-"] name;
src = ./.;
phases = [ "installPhase" ];
installPhase = ''
mkdir -p $out
${if mountBuild then ''
mkdir -p $out/sbin
cp ${runContainerScript} $out/sbin/docker-run
'' else ""}
cp ${dockerFile} $out/Dockerfile
'';
}

View File

@ -1,46 +0,0 @@
{ config, pkgs, ... }:
with pkgs.lib;
{
options = {
docker.ports = mkOption {
default = [];
description = "Ports to expose to the outside world.";
example = [ 80 22 ];
};
docker.volumes = mkOption {
default = [];
description = "Volumes to create for container.";
example = [ "/var/lib" "/var/log" ];
};
docker.buildScripts = mkOption {
default = {};
example = {
setupUsers = "cp passwd /etc/passwd";
};
description = "Scripts (as text) to be run during build, executed alphabetically";
};
docker.bootScript = mkOption {
default = "";
description = "Script (text) to run when container booted.";
};
docker.buildScript = mkOption {};
docker.verbose = mkOption {
default = false;
type = types.bool;
};
# HACK: Let's ignore these for now
networking = mkOption {};
security = mkOption {};
};
config = {
docker.buildScript = concatStrings (attrValues config.docker.buildScripts);
networking.enableIPv6 = false;
};
}

View File

@ -1,53 +0,0 @@
{ config, pkgs, ... }:
with pkgs.lib;
let
etc2 = filter (f: f.enable) (attrValues config.environment.etc);
etc = pkgs.stdenv.mkDerivation {
name = "etc";
builder = <nixpkgs/nixos/modules/system/etc/make-etc.sh>;
preferLocalBuild = true;
/* !!! Use toXML. */
sources = map (x: x.source) etc2;
targets = map (x: x.target) etc2;
modes = map (x: x.mode) etc2;
};
in {
options = {
environment.systemPackages = mkOption {
default = [];
description = "Packages to be put in the system profile.";
};
environment.umask = mkOption {
default = "002";
type = with types; string;
};
# HACK HACK
system.activationScripts.etc = mkOption {}; # Ignore
system.build.etc = mkOption {}; # Ignore
};
config = {
docker.buildScripts."0-systemEnv" = let
systemPackages = config.environment.systemPackages;
systemEnv = pkgs.buildEnv { name = "system-env"; paths = systemPackages; };
in ''
rm -rf /usr
chmod 777 /tmp
ln -s ${systemEnv} /usr
ln -sf /usr/bin/bash /bin/bash
'';
environment.systemPackages = with pkgs; [ coreutils bash ];
docker.buildScripts."0-etc" = ''
echo "setting up /etc..."
${pkgs.perl}/bin/perl ${<nixpkgs/nixos/modules/system/etc/setup-etc.pl>} ${etc}/etc
'';
};
}

View File

@ -1,272 +0,0 @@
{pkgs, config, ...}:
with pkgs.lib;
let
userOpts = { name, config, ... }: {
options = {
name = mkOption {
type = types.str;
description = "The name of the user account. If undefined, the name of the attribute set will be used.";
};
description = mkOption {
type = types.str;
default = "";
example = "Alice Q. User";
description = ''
A short description of the user account, typically the
user's full name. This is actually the GECOS or comment
field in <filename>/etc/passwd</filename>.
'';
};
uid = mkOption {
type = with types; uniq (nullOr int);
default = null;
description = "The account UID. If undefined, NixOS will select a free UID.";
};
group = mkOption {
type = types.str;
default = "nogroup";
description = "The user's primary group.";
};
extraGroups = mkOption {
type = types.listOf types.str;
default = [];
description = "The user's auxiliary groups.";
};
home = mkOption {
type = types.str;
default = "/var/empty";
description = "The user's home directory.";
};
shell = mkOption {
type = types.str;
default = "/run/current-system/sw/sbin/nologin";
description = "The path to the user's shell.";
};
createHome = mkOption {
type = types.bool;
default = false;
description = "If true, the home directory will be created automatically.";
};
useDefaultShell = mkOption {
type = types.bool;
default = false;
description = "If true, the user's shell will be set to <literal>users.defaultUserShell</literal>.";
};
password = mkOption {
type = with types; uniq (nullOr str);
default = null;
description = ''
The user's password. If undefined, no password is set for
the user. Warning: do not set confidential information here
because it is world-readable in the Nix store. This option
should only be used for public accounts such as
<literal>guest</literal>.
'';
};
isSystemUser = mkOption {
type = types.bool;
default = true;
description = "Indicates if the user is a system user or not.";
};
createUser = mkOption {
type = types.bool;
default = true;
description = ''
Indicates if the user should be created automatically as a local user.
Set this to false if the user for instance is an LDAP user. NixOS will
then not modify any of the basic properties for the user account.
'';
};
isAlias = mkOption {
type = types.bool;
default = false;
description = "If true, the UID of this user is not required to be unique and can thus alias another user.";
};
};
config = {
name = mkDefault name;
uid = mkDefault null;
shell = mkDefault "/bin/sh";
};
};
groupOpts = { name, config, ... }: {
options = {
name = mkOption {
type = types.str;
description = "The name of the group. If undefined, the name of the attribute set will be used.";
};
gid = mkOption {
type = with types; uniq (nullOr int);
default = null;
description = "The GID of the group. If undefined, NixOS will select a free GID.";
};
};
config = {
name = mkDefault name;
gid = mkDefault null;
};
};
extraUsers = config.users.extraUsers;
extraGroups = config.users.extraGroups;
uidUsers = listToAttrs
(imap (i: name:
let
user = getAttr name extraUsers;
in {
name=name;
value = if user.uid == null then
setAttr user "uid" (builtins.add 1000 i)
else user;
})
(attrNames extraUsers));
gidGroups = listToAttrs
(imap (i: name:
let
group = getAttr name extraGroups;
in {
name=name;
value = if group.gid == null then
setAttr group "gid" (builtins.add 1000 i)
else group;
})
(attrNames extraGroups));
in
{
###### interface
options = {
users.extraUsers = mkOption {
default = {};
type = types.loaOf types.optionSet;
example = {
alice = {
uid = 1234;
description = "Alice Q. User";
home = "/home/alice";
createHome = true;
group = "users";
extraGroups = ["wheel"];
shell = "/bin/sh";
};
};
description = ''
Additional user accounts to be created automatically by the system.
This can also be used to set options for root.
'';
options = [ userOpts ];
};
users.extraGroups = mkOption {
default = {};
example =
{ students.gid = 1001;
hackers = { };
};
type = types.loaOf types.optionSet;
description = ''
Additional groups to be created automatically by the system.
'';
options = [ groupOpts ];
};
};
config = {
users.extraUsers = {
root = {
uid = 0;
description = "System administrator";
home = "/root";
group = "root";
};
nobody = {
uid = 1;
description = "Unprivileged account (don't use!)";
};
ldap = {};
};
users.extraGroups = {
root = { gid = 0; };
wheel = { };
disk = { };
kmem = { };
tty = { };
floppy = { };
uucp = { };
lp = { };
cdrom = { };
tape = { };
audio = { };
video = { };
dialout = { };
nogroup = { };
users = { };
utmp = { };
adm = { };
};
environment.etc.passwd.text =
concatMapStrings (name:
let
user = getAttr name uidUsers;
in
if user.createUser then
"${user.name}:x:${toString user.uid}:${toString (getAttr user.group gidGroups).gid}:${user.description}:${user.home}:${user.shell}\n"
else
""
) (attrNames uidUsers);
environment.etc.group.text =
concatMapStrings (name:
let
group = getAttr name gidGroups;
in
"${group.name}:x:${toString group.gid}:\n"
) (attrNames gidGroups);
docker.buildScripts."1-create-homes" = concatMapStrings (name:
let
user = getAttr name uidUsers;
in
if user.createHome then
"mkdir -p ${user.home}; chown ${user.name}:${user.group} ${user.home}\n"
else
""
) (attrNames uidUsers);
};
}

View File

@ -1,641 +0,0 @@
{ config, pkgs, ... }:
with pkgs.lib;
let
mainCfg = config.services.httpd;
httpd = mainCfg.package;
version24 = !versionOlder httpd.version "2.4";
httpdConf = mainCfg.configFile;
php = pkgs.php.override { apacheHttpd = httpd; };
getPort = cfg: if cfg.port != 0 then cfg.port else if cfg.enableSSL then 443 else 80;
extraModules = attrByPath ["extraModules"] [] mainCfg;
extraForeignModules = filter builtins.isAttrs extraModules;
extraApacheModules = filter (x: !(builtins.isAttrs x)) extraModules; # I'd prefer using builtins.isString here, but doesn't exist yet
makeServerInfo = cfg: {
# Canonical name must not include a trailing slash.
canonicalName =
(if cfg.enableSSL then "https" else "http") + "://" +
cfg.hostName +
(if getPort cfg != (if cfg.enableSSL then 443 else 80) then ":${toString (getPort cfg)}" else "");
# Admin address: inherit from the main server if not specified for
# a virtual host.
adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
vhostConfig = cfg;
serverConfig = mainCfg;
fullConfig = config; # machine config
};
allHosts = [mainCfg] ++ mainCfg.virtualHosts;
callSubservices = serverInfo: defs:
let f = svc:
let
svcFunction =
if svc ? function then svc.function
else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
config = (evalModules
{ modules = [ { options = res.options; config = svc.config or svc; } ];
check = false;
}).config;
defaults = {
extraConfig = "";
extraModules = [];
extraModulesPre = [];
extraPath = [];
extraServerPath = [];
globalEnvVars = [];
robotsEntries = "";
startupScript = "";
enablePHP = false;
phpOptions = "";
options = {};
};
res = defaults // svcFunction { inherit config pkgs serverInfo php; };
in res;
in map f defs;
# !!! callSubservices is expensive
subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
mainSubservices = subservicesFor mainCfg;
allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
# !!! should be in lib
writeTextInDir = name: text:
pkgs.runCommand name {inherit text;} "ensureDir $out; echo -n \"$text\" > $out/$name";
enableSSL = any (vhost: vhost.enableSSL) allHosts;
# Names of modules from ${httpd}/modules that we want to load.
apacheModules =
[ # HTTP authentication mechanisms: basic and digest.
"auth_basic" "auth_digest"
# Authentication: is the user who he claims to be?
"authn_file" "authn_dbm" "authn_anon"
(if version24 then "authn_core" else "authn_alias")
# Authorization: is the user allowed access?
"authz_user" "authz_groupfile" "authz_host"
# Other modules.
"ext_filter" "include" "log_config" "env" "mime_magic"
"cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
"mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
"vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
"userdir" "alias" "rewrite" "proxy" "proxy_http"
]
++ optionals version24 [
"mpm_${mainCfg.multiProcessingModule}"
"authz_core"
"unixd"
]
++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
++ optional enableSSL "ssl"
++ extraApacheModules;
allDenied = if version24 then ''
Require all denied
'' else ''
Order deny,allow
Deny from all
'';
allGranted = if version24 then ''
Require all granted
'' else ''
Order allow,deny
Allow from all
'';
loggingConf = ''
ErrorLog ${mainCfg.logDir}/error_log
LogLevel notice
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
CustomLog ${mainCfg.logDir}/access_log ${mainCfg.logFormat}
'';
browserHacks = ''
BrowserMatch "Mozilla/2" nokeepalive
BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
BrowserMatch "RealPlayer 4\.0" force-response-1.0
BrowserMatch "Java/1\.0" force-response-1.0
BrowserMatch "JDK/1\.0" force-response-1.0
BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
BrowserMatch "^WebDrive" redirect-carefully
BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
BrowserMatch "^gnome-vfs" redirect-carefully
'';
sslConf = ''
SSLSessionCache shm:${mainCfg.stateDir}/ssl_scache(512000)
SSLMutex posixsem
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
'';
mimeConf = ''
TypesConfig ${httpd}/conf/mime.types
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
AddType application/x-httpd-php .php .phtml
<IfModule mod_mime_magic.c>
MIMEMagicFile ${httpd}/conf/magic
</IfModule>
AddEncoding x-compress Z
AddEncoding x-gzip gz tgz
'';
perServerConf = isMainServer: cfg: let
serverInfo = makeServerInfo cfg;
subservices = callSubservices serverInfo cfg.extraSubservices;
documentRoot = if cfg.documentRoot != null then cfg.documentRoot else
pkgs.runCommand "empty" {} "ensureDir $out";
documentRootConf = ''
DocumentRoot "${documentRoot}"
<Directory "${documentRoot}">
Options Indexes FollowSymLinks
AllowOverride None
${allGranted}
</Directory>
'';
robotsTxt = pkgs.writeText "robots.txt" ''
${# If this is a vhost, the include the entries for the main server as well.
if isMainServer then ""
else concatMapStrings (svc: svc.robotsEntries) mainSubservices}
${concatMapStrings (svc: svc.robotsEntries) subservices}
'';
robotsConf = ''
Alias /robots.txt ${robotsTxt}
'';
in ''
ServerName ${serverInfo.canonicalName}
${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
${if cfg.sslServerCert != null then ''
SSLCertificateFile ${cfg.sslServerCert}
SSLCertificateKeyFile ${cfg.sslServerKey}
'' else ""}
${if cfg.enableSSL then ''
SSLEngine on
'' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
''
SSLEngine off
'' else ""}
${if isMainServer || cfg.adminAddr != null then ''
ServerAdmin ${cfg.adminAddr}
'' else ""}
${if !isMainServer && mainCfg.logPerVirtualHost then ''
ErrorLog ${mainCfg.logDir}/error_log-${cfg.hostName}
CustomLog ${mainCfg.logDir}/access_log-${cfg.hostName} ${cfg.logFormat}
'' else ""}
${robotsConf}
${if isMainServer || cfg.documentRoot != null then documentRootConf else ""}
${if cfg.enableUserDir then ''
UserDir public_html
UserDir disabled root
<Directory "/home/*/public_html">
AllowOverride FileInfo AuthConfig Limit Indexes
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
<Limit GET POST OPTIONS>
${allGranted}
</Limit>
<LimitExcept GET POST OPTIONS>
${allDenied}
</LimitExcept>
</Directory>
'' else ""}
${if cfg.globalRedirect != null then ''
RedirectPermanent / ${cfg.globalRedirect}
'' else ""}
${
let makeFileConf = elem: ''
Alias ${elem.urlPath} ${elem.file}
'';
in concatMapStrings makeFileConf cfg.servedFiles
}
${
let makeDirConf = elem: ''
Alias ${elem.urlPath} ${elem.dir}/
<Directory ${elem.dir}>
Options +Indexes
${allGranted}
AllowOverride All
</Directory>
'';
in concatMapStrings makeDirConf cfg.servedDirs
}
${concatMapStrings (svc: svc.extraConfig) subservices}
${cfg.extraConfig}
'';
confFile = pkgs.writeText "httpd.conf" ''
ServerRoot ${httpd}
${optionalString version24 ''
DefaultRuntimeDir ${mainCfg.stateDir}/runtime
''}
PidFile ${mainCfg.stateDir}/httpd.pid
${optionalString (mainCfg.multiProcessingModule != "prefork") ''
# mod_cgid requires this.
ScriptSock ${mainCfg.stateDir}/cgisock
''}
<IfModule prefork.c>
MaxClients ${toString mainCfg.maxClients}
MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
</IfModule>
${let
ports = map getPort allHosts;
uniquePorts = uniqList {inputList = ports;};
in concatMapStrings (port: "Listen ${toString port}\n") uniquePorts
}
User ${mainCfg.user}
Group ${mainCfg.group}
${let
load = {name, path}: "LoadModule ${name}_module ${path}\n";
allModules =
concatMap (svc: svc.extraModulesPre) allSubservices
++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
++ optional enablePHP { name = "php5"; path = "${php}/modules/libphp5.so"; }
++ concatMap (svc: svc.extraModules) allSubservices
++ extraForeignModules;
in concatMapStrings load allModules
}
AddHandler type-map var
<Files ~ "^\.ht">
${allDenied}
</Files>
${mimeConf}
${loggingConf}
${browserHacks}
Include ${httpd}/conf/extra/httpd-default.conf
Include ${httpd}/conf/extra/httpd-autoindex.conf
Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
Include ${httpd}/conf/extra/httpd-languages.conf
${if enableSSL then sslConf else ""}
# Fascist default - deny access to everything.
<Directory />
Options FollowSymLinks
AllowOverride None
${allDenied}
</Directory>
# But do allow access to files in the store so that we don't have
# to generate <Directory> clauses for every generated file that we
# want to serve.
<Directory /nix/store>
${allGranted}
</Directory>
# Generate directives for the main server.
${perServerConf true mainCfg}
# Always enable virtual hosts; it doesn't seem to hurt.
${let
ports = map getPort allHosts;
uniquePorts = uniqList {inputList = ports;};
directives = concatMapStrings (port: "NameVirtualHost *:${toString port}\n") uniquePorts;
in optionalString (!version24) directives
}
${let
makeVirtualHost = vhost: ''
<VirtualHost *:${toString (getPort vhost)}>
${perServerConf false vhost}
</VirtualHost>
'';
in concatMapStrings makeVirtualHost mainCfg.virtualHosts
}
'';
enablePHP = any (svc: svc.enablePHP) allSubservices;
# Generate the PHP configuration file. Should probably be factored
# out into a separate module.
phpIni = pkgs.runCommand "php.ini"
{ options = concatStringsSep "\n"
([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
}
''
cat ${php}/etc/php-recommended.ini > $out
echo "$options" >> $out
'';
in
{
###### interface
options = {
services.httpd = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable the Apache HTTP Server.";
};
package = mkOption {
type = types.path;
default = pkgs.apacheHttpd.override { mpm = mainCfg.multiProcessingModule; };
example = "pkgs.apacheHttpd_2_4";
description = ''
Overridable attribute of the Apache HTTP Server package to use.
'';
};
configFile = mkOption {
type = types.path;
default = confFile;
example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ...";'';
description = ''
Override the configuration file used by Apache. By default,
NixOS generates one automatically.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Cnfiguration lines appended to the generated Apache
configuration file. Note that this mechanism may not work
when <option>configFile</option> is overridden.
'';
};
extraModules = mkOption {
type = types.listOf types.unspecified;
default = [];
example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${php}/modules/libphp5.so"; } ]'';
description = ''
Additional Apache modules to be used. These can be
specified as a string in the case of modules distributed
with Apache, or as an attribute set specifying the
<varname>name</varname> and <varname>path</varname> of the
module.
'';
};
logPerVirtualHost = mkOption {
type = types.bool;
default = false;
description = ''
If enabled, each virtual host gets its own
<filename>access_log</filename> and
<filename>error_log</filename>, namely suffixed by the
<option>hostName</option> of the virtual host.
'';
};
user = mkOption {
type = types.str;
default = "wwwrun";
description = ''
User account under which httpd runs. The account is created
automatically if it doesn't exist.
'';
};
group = mkOption {
type = types.str;
default = "wwwrun";
description = ''
Group under which httpd runs. The account is created
automatically if it doesn't exist.
'';
};
logDir = mkOption {
type = types.path;
default = "/var/log/httpd";
description = ''
Directory for Apache's log files. It is created automatically.
'';
};
stateDir = mkOption {
type = types.path;
default = "/run/httpd";
description = ''
Directory for Apache's transient runtime state (such as PID
files). It is created automatically. Note that the default,
<filename>/run/httpd</filename>, is deleted at boot time.
'';
};
virtualHosts = mkOption {
type = types.listOf (types.submodule (
{ options = import ./per-server-options.nix {
inherit pkgs;
forMainServer = false;
};
}));
default = [];
example = [
{ hostName = "foo";
documentRoot = "/data/webroot-foo";
}
{ hostName = "bar";
documentRoot = "/data/webroot-bar";
}
];
description = ''
Specification of the virtual hosts served by Apache. Each
element should be an attribute set specifying the
configuration of the virtual host. The available options
are the non-global options permissible for the main host.
'';
};
phpOptions = mkOption {
type = types.lines;
default = "";
example =
''
date.timezone = "CET"
'';
description =
"Options appended to the PHP configuration file <filename>php.ini</filename>.";
};
multiProcessingModule = mkOption {
type = types.str;
default = "prefork";
example = "worker";
description =
''
Multi-processing module to be used by Apache. Available
modules are <literal>prefork</literal> (the default;
handles each request in a separate child process),
<literal>worker</literal> (hybrid approach that starts a
number of child processes each running a number of
threads) and <literal>event</literal> (a recent variant of
<literal>worker</literal> that handles persistent
connections more efficiently).
'';
};
maxClients = mkOption {
type = types.int;
default = 150;
example = 8;
description = "Maximum number of httpd processes (prefork)";
};
maxRequestsPerChild = mkOption {
type = types.int;
default = 0;
example = 500;
description =
"Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
};
}
# Include the options shared between the main server and virtual hosts.
// (import ./per-server-options.nix {
inherit pkgs;
forMainServer = true;
});
};
###### implementation
config = mkIf config.services.httpd.enable {
users.extraUsers.wwwrun =
{ name = "wwwrun";
group = "wwwrun";
description = "Apache httpd user";
};
users.extraGroups.wwwrun = {};
environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
services.httpd.phpOptions =
''
; Needed for PHP's mail() function.
sendmail_path = sendmail -t -i
; Apparently PHP doesn't use $TZ.
date.timezone = "${config.time.timeZone}"
'';
docker.buildScripts.apache =
''
mkdir -m 0750 -p ${mainCfg.stateDir}
chown root.${mainCfg.group} ${mainCfg.stateDir}
${optionalString version24 ''
mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
''}
mkdir -m 0700 -p ${mainCfg.logDir}
${optionalString (mainCfg.documentRoot != null)
''
# Create the document root directory if does not exists yet
mkdir -p ${mainCfg.documentRoot}
''
}
# Get rid of old semaphores. These tend to accumulate across
# server restarts, eventually preventing it from restarting
# successfully.
for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
${pkgs.utillinux}/bin/ipcrm -s $i
done
# Run the startup hooks for the subservices.
for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
echo Running Apache startup hook $i...
$i
done
'';
supervisord.services.httpd =
{
command = "${httpd}/bin/httpd -f ${httpdConf} -DFOREGROUND";
};
};
}

View File

@ -1,150 +0,0 @@
# This file defines the options that can be used both for the Apache
# main server configuration, and for the virtual hosts. (The latter
# has additional options that affect the web server as a whole, like
# the user/group to run under.)
{ forMainServer, pkgs }:
with pkgs.lib;
{
hostName = mkOption {
type = types.str;
default = "localhost";
description = "Canonical hostname for the server.";
};
serverAliases = mkOption {
type = types.listOf types.str;
default = [];
example = ["www.example.org" "www.example.org:8080" "example.org"];
description = ''
Additional names of virtual hosts served by this virtual host configuration.
'';
};
port = mkOption {
type = types.int;
default = 0;
description = ''
Port for the server. 0 means use the default port: 80 for http
and 443 for https (i.e. when enableSSL is set).
'';
};
enableSSL = mkOption {
type = types.bool;
default = false;
description = "Whether to enable SSL (https) support.";
};
# Note: sslServerCert and sslServerKey can be left empty, but this
# only makes sense for virtual hosts (they will inherit from the
# main server).
sslServerCert = mkOption {
type = types.nullOr types.path;
default = null;
example = "/var/host.cert";
description = "Path to server SSL certificate.";
};
sslServerKey = mkOption {
type = types.path;
example = "/var/host.key";
description = "Path to server SSL certificate key.";
};
adminAddr = mkOption ({
type = types.nullOr types.str;
example = "admin@example.org";
description = "E-mail address of the server administrator.";
} // (if forMainServer then {} else {default = null;}));
documentRoot = mkOption {
type = types.nullOr types.path;
default = null;
example = "/data/webserver/docs";
description = ''
The path of Apache's document root directory. If left undefined,
an empty directory in the Nix store will be used as root.
'';
};
servedDirs = mkOption {
type = types.listOf types.attrs;
default = [];
example = [
{ urlPath = "/nix";
dir = "/home/eelco/Dev/nix-homepage";
}
];
description = ''
This option provides a simple way to serve static directories.
'';
};
servedFiles = mkOption {
type = types.listOf types.attrs;
default = [];
example = [
{ urlPath = "/foo/bar.png";
dir = "/home/eelco/some-file.png";
}
];
description = ''
This option provides a simple way to serve individual, static files.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
<Directory /home>
Options FollowSymlinks
AllowOverride All
</Directory>
'';
description = ''
These lines go to httpd.conf verbatim. They will go after
directories and directory aliases defined by default.
'';
};
extraSubservices = mkOption {
type = types.listOf types.unspecified;
default = [];
description = "Extra subservices to enable in the webserver.";
};
enableUserDir = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable serving <filename>~/public_html</filename> as
<literal>/~<replaceable>username</replaceable></literal>.
'';
};
globalRedirect = mkOption {
type = types.nullOr types.str;
default = null;
example = http://newserver.example.org/;
description = ''
If set, all requests for this host are redirected permanently to
the given URL.
'';
};
logFormat = mkOption {
type = types.str;
default = "common";
example = "combined";
description = "
Log format for Apache's log files. Possible values are: combined, common, referer, agent.
";
};
}

View File

@ -1,333 +0,0 @@
{ config, pkgs, ... }:
with pkgs.lib;
let
cfg = config.services.openssh;
cfgc = config.programs.ssh;
nssModulesPath = config.system.nssModules.path;
permitRootLoginCheck = v:
v == "yes" ||
v == "without-password" ||
v == "forced-commands-only" ||
v == "no";
knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts);
knownHostsFile = pkgs.writeText "ssh_known_hosts" (
flip concatMapStrings knownHosts (h:
"${concatStringsSep "," h.hostNames} ${builtins.readFile h.publicKeyFile}"
)
);
userOptions = {
openssh.authorizedKeys = {
keys = mkOption {
type = types.listOf types.str;
default = [];
description = ''
A list of verbatim OpenSSH public keys that should be added to the
user's authorized keys. The keys are added to a file that the SSH
daemon reads in addition to the the user's authorized_keys file.
You can combine the <literal>keys</literal> and
<literal>keyFiles</literal> options.
'';
};
keyFiles = mkOption {
type = types.listOf types.str;
default = [];
description = ''
A list of files each containing one OpenSSH public key that should be
added to the user's authorized keys. The contents of the files are
read at build time and added to a file that the SSH daemon reads in
addition to the the user's authorized_keys file. You can combine the
<literal>keyFiles</literal> and <literal>keys</literal> options.
'';
};
};
};
authKeysFiles = let
mkAuthKeyFile = u: {
target = "ssh/authorized_keys.d/${u.name}";
mode = "0444";
source = pkgs.writeText "${u.name}-authorized_keys" ''
${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
${concatMapStrings (f: builtins.readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
'';
};
usersWithKeys = attrValues (flip filterAttrs config.users.extraUsers (n: u:
length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
));
in map mkAuthKeyFile usersWithKeys;
in
{
###### interface
options = {
services.openssh = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable the OpenSSH secure shell daemon, which
allows secure remote logins.
'';
};
forwardX11 = mkOption {
type = types.bool;
default = cfgc.setXAuthLocation;
description = ''
Whether to allow X11 connections to be forwarded.
'';
};
allowSFTP = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable the SFTP subsystem in the SSH daemon. This
enables the use of commands such as <command>sftp</command> and
<command>sshfs</command>.
'';
};
permitRootLogin = mkOption {
default = "without-password";
type = types.addCheck types.str permitRootLoginCheck;
description = ''
Whether the root user can login using ssh. Valid values are
<literal>yes</literal>, <literal>without-password</literal>,
<literal>forced-commands-only</literal> or
<literal>no</literal>.
'';
};
gatewayPorts = mkOption {
type = types.str;
default = "no";
description = ''
Specifies whether remote hosts are allowed to connect to
ports forwarded for the client. See
<citerefentry><refentrytitle>sshd_config</refentrytitle>
<manvolnum>5</manvolnum></citerefentry>.
'';
};
ports = mkOption {
type = types.listOf types.int;
default = [22];
description = ''
Specifies on which ports the SSH daemon listens.
'';
};
passwordAuthentication = mkOption {
type = types.bool;
default = true;
description = ''
Specifies whether password authentication is allowed.
'';
};
challengeResponseAuthentication = mkOption {
type = types.bool;
default = true;
description = ''
Specifies whether challenge/response authentication is allowed.
'';
};
hostKeys = mkOption {
type = types.listOf types.attrs;
default =
[ { path = "/etc/ssh/ssh_host_dsa_key";
type = "dsa";
bits = 1024;
}
{ path = "/etc/ssh/ssh_host_ecdsa_key";
type = "ecdsa";
bits = 521;
}
];
description = ''
NixOS can automatically generate SSH host keys. This option
specifies the path, type and size of each key. See
<citerefentry><refentrytitle>ssh-keygen</refentrytitle>
<manvolnum>1</manvolnum></citerefentry> for supported types
and sizes.
'';
};
authorizedKeysFiles = mkOption {
type = types.listOf types.str;
default = [];
description = "Files from with authorized keys are read.";
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = "Verbatim contents of <filename>sshd_config</filename>.";
};
knownHosts = mkOption {
default = {};
type = types.loaOf types.optionSet;
description = ''
The set of system-wide known SSH hosts.
'';
example = [
{
hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
publicKeyFile = literalExample "./pubkeys/myhost_ssh_host_dsa_key.pub";
}
{
hostNames = [ "myhost2" ];
publicKeyFile = literalExample "./pubkeys/myhost2_ssh_host_dsa_key.pub";
}
];
options = {
hostNames = mkOption {
type = types.listOf types.string;
default = [];
description = ''
A list of host names and/or IP numbers used for accessing
the host's ssh service.
'';
};
publicKeyFile = mkOption {
description = ''
The path to the public key file for the host. The public
key file is read at build time and saved in the Nix store.
You can fetch a public key file from a running SSH server
with the <command>ssh-keyscan</command> command.
'';
};
};
};
};
users.extraUsers = mkOption {
options = [ userOptions ];
};
};
###### implementation
config = mkIf cfg.enable {
users.extraUsers = singleton
{ name = "sshd";
uid = config.ids.uids.sshd;
description = "SSH privilege separation user";
home = "/var/empty";
};
environment.etc = authKeysFiles ++ [
{ source = "${pkgs.openssh}/etc/ssh/moduli";
target = "ssh/moduli";
}
{ source = knownHostsFile;
target = "ssh/ssh_known_hosts";
}
];
systemd.services.sshd =
{ description = "SSH Daemon";
wantedBy = [ "multi-user.target" ];
stopIfChanged = false;
path = [ pkgs.openssh pkgs.gawk ];
environment.LD_LIBRARY_PATH = "";
environment.LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
preStart =
''
mkdir -m 0755 -p /etc/ssh
${flip concatMapStrings cfg.hostKeys (k: ''
if ! [ -f "${k.path}" ]; then
ssh-keygen -t "${k.type}" -b "${toString k.bits}" -f "${k.path}" -N ""
fi
'')}
'';
serviceConfig =
{ ExecStart =
"${pkgs.openssh}/sbin/sshd " +
"-f ${pkgs.writeText "sshd_config" cfg.extraConfig} -D";
Restart = "always";
KillMode = "process";
PIDFile = "/run/sshd.pid";
};
};
services.openssh.authorizedKeysFiles =
[ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
services.openssh.extraConfig =
''
PidFile /run/sshd.pid
Protocol 2
UsePAM no
AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
${concatMapStrings (port: ''
Port ${toString port}
'') cfg.ports}
${optionalString cfgc.setXAuthLocation ''
XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
''}
${if cfg.forwardX11 then ''
X11Forwarding yes
'' else ''
X11Forwarding no
''}
${optionalString cfg.allowSFTP ''
Subsystem sftp ${pkgs.openssh}/libexec/sftp-server
''}
PermitRootLogin ${cfg.permitRootLogin}
GatewayPorts ${cfg.gatewayPorts}
PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"}
PrintMotd no # handled by pam_motd
AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
${flip concatMapStrings cfg.hostKeys (k: ''
HostKey ${k.path}
'')}
'';
assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
message = "cannot enable X11 forwarding without setting xauth location";}];
};
}

View File

@ -1,10 +0,0 @@
{
"name" : "nix-docker",
"version" : "0.1.0",
"dependencies" : {
"optimist" : "0.6.0"
},
"directories": {
"bin": "./bin"
}
}

View File

@ -1,14 +0,0 @@
{ config, pkgs, ... }:
{
# Expose the apache port (80)
docker.ports = [
config.services.httpd.port
];
services.httpd = {
enable = true;
port = 80;
documentRoot = ./www;
adminAddr = "zef.hemel@logicblox.com";
};
}

View File

@ -1,29 +0,0 @@
# Boots up all kinds of services: redis, apache, ssh, mysql
{ config, pkgs, ... }:
{
docker.ports = [ 1234 80 22 ];
services.redis = {
enable = true;
port = 1234;
logLevel = "debug";
};
services.httpd.enable = true;
services.httpd.port = 80;
services.httpd.documentRoot = ./www;
services.httpd.adminAddr = "zef.hemel@logicblox.com";
services.mysql.enable = true;
services.openssh.enable = true;
supervisord.tailLogs = true;
users.extraUsers.zef = {
group = "users";
home = "/";
shell = "/bin/bash";
createHome = true;
openssh.authorizedKeys.keys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjWpdyDIsS09lWlOsMG9OMTHB/N/afVU12BwKcyjjhbezPdFEgHK4cZBN7m1bvoFKl832BdB+ZjeRH4UGBcUpvrFu1vE7Lf/0vZDU7qzzWQE9V+tfSPwDiXPf9QnCYeZmYPDHUHDUEse9LKBZbt6UKF1tuTD8ussV5jvEFBaesDhCqD1TJ4b4O877cdx9+VTOuDSEDm32jQ2az27d1b/5DoEKBe5cJSC3PhObAQ7OAYrVVBFX9ffKpaSvV6yqo+rhCmXP9DjNgBwMtElreoXL3h5Xbw2AiER5oHNUAEA2XGpnOVOr7ZZUAbMC0/0dq387jQZCqe7gIDZCqjDpGhUa9 zefhemel@gmail.com" ];
};
}

View File

@ -1,19 +0,0 @@
# Runs an SSH server in a Docker container
# Creates a user "you" that you can login with
{ config, pkgs, ... }:
{
docker.ports = [ 22 ];
services.openssh.enable = true;
users.extraUsers.you = {
group = "users";
home = "/";
shell = "/bin/bash";
createHome = true;
openssh.authorizedKeys.keys = [
# Replace with your own SSH key (e.g. from ~/.ssh/id_rsa.pub)
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjWpdyDIsS09lWlOsMG9OMTHB/N/afVU12BwKcyjjhbezPdFEgHK4cZBN7m1bvoFKl832BdB+ZjeRH4UGBcUpvrFu1vE7Lf/0vZDU7qzzWQE9V+tfSPwDiXPf9QnCYeZmYPDHUHDUEse9LKBZbt6UKF1tuTD8ussV5jvEFBaesDhCqD1TJ4b4O877cdx9+VTOuDSEDm32jQ2az27d1b/5DoEKBe5cJSC3PhObAQ7OAYrVVBFX9ffKpaSvV6yqo+rhCmXP9DjNgBwMtElreoXL3h5Xbw2AiER5oHNUAEA2XGpnOVOr7ZZUAbMC0/0dq387jQZCqe7gIDZCqjDpGhUa9"
];
};
}

View File

@ -1,138 +0,0 @@
{ config, pkgs, ... }:
# We'll start with defining some local variables
let
# Settings used to configure the wordpress MySQL database
mysqlHost = "localhost";
mysqlDb = "wordpress";
mysqlUser = "wordpress";
mysqlPassword = "wordpress";
# Data paths
mysqlDataPath = "/data/mysql";
wordpressUploads = "/data/uploads";
apacheLogs = "/data/log";
# Our bare-bones wp-config.php file using the above settings
wordpressConfig = pkgs.writeText "wp-config.php" ''
<?php
define('DB_NAME', '${mysqlDb}');
define('DB_USER', '${mysqlUser}');
define('DB_PASSWORD', '${mysqlPassword}');
define('DB_HOST', '${mysqlHost}');
define('DB_CHARSET', 'utf8');
$table_prefix = 'wp_';
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . 'wp-settings.php');
'';
# .htaccess to support pretty URLs
htaccess = pkgs.writeText "htaccess" ''
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
'';
# For shits and giggles, let's package the responsive theme
responsiveTheme = pkgs.stdenv.mkDerivation {
name = "responsive-theme";
# Download the theme from the wordpress site
src = pkgs.fetchurl {
url = http://wordpress.org/themes/download/responsive.1.9.3.9.zip;
sha256 = "0397a8nd8q384z2i4lw4a1ij835walp3b5bfdmr76mdl27vbkvmd";
};
# We need unzip to build this package
buildInputs = [ pkgs.unzip ];
# Installing simply means copying all files to the output directory
installPhase = "mkdir -p $out; cp -R * $out/";
};
# The wordpress package itself
wordpress = pkgs.stdenv.mkDerivation {
name = "wordpress";
# Fetch directly from the wordpress site, want to upgrade?
# Just change the version URL and update the hash
src = pkgs.fetchurl {
url = http://wordpress.org/wordpress-3.7.1.tar.gz;
sha256 = "1m2dlr54fqf5m4kgqc5hrrisrsia0r5j1r2xv1f12gmzb63swsvg";
};
installPhase = ''
mkdir -p $out
# Copy all the wordpress files we downloaded
cp -R * $out/
# We'll symlink the wordpress config
ln -s ${wordpressConfig} $out/wp-config.php
# As well as our custom .htaccess
ln -s ${htaccess} $out/.htaccess
# And the uploads directory
ln -s ${wordpressUploads} $out/wp-content/uploads
# And the responsive theme
ln -s ${responsiveTheme} $out/wp-content/themes/responsive
# You can add plugins the same way
'';
};
# This is where the body of our configuration starts
in {
# Expose just port 80
docker.ports = [ 80 ];
# And let's store valuable data in a volume
docker.volumes = [ "/data" ];
# Apache configuration
services.httpd = {
enable = true;
adminAddr = "zef@zef.me";
# We'll set the wordpress package as our document root
documentRoot = wordpress;
# Let's store our logs in the volume as well
logDir = apacheLogs;
# And enable the PHP5 apache module
extraModules = [ { name = "php5"; path = "${pkgs.php}/modules/libphp5.so"; } ];
# And some extra config to make things work nicely
extraConfig = ''
<Directory ${wordpress}>
DirectoryIndex index.php
Allow from *
Options FollowSymLinks
AllowOverride All
</Directory>
'';
};
# Disable these when not using "localhost" as database name
services.mysql.enable = true;
# Let's store our data in the volume, so it'll survive restarts
services.mysql.dataDir = mysqlDataPath;
# This service runs evey time you start and ensures two things:
# 1. That our uploads directory exists and is owned by Apache
# 2. that the MySQL database exists
supervisord.services.initApp = {
command = pkgs.writeScript "init-wordpress.sh" ''
#!/bin/sh
mkdir -p ${wordpressUploads}
chown ${config.services.httpd.user} ${wordpressUploads}
if [ ! -d /data/mysql/${mysqlDb} ]; then
# Wait until MySQL is up
while [ ! -e /var/run/mysql/mysqld.pid ]; do
sleep 1
done
mysql -e 'CREATE DATABASE ${mysqlDb};'
mysql -e 'GRANT ALL ON ${mysqlDb}.* TO ${mysqlUser}@localhost IDENTIFIED BY "${mysqlPassword}";'
fi
'';
# This script can exit immediately, no worries
startsecs = 0;
};
}

View File

@ -1 +0,0 @@
Hello world 2

View File

@ -1 +0,0 @@
These are scripts to automatically install Docker and Nix and has been tested to work on Ubuntu 13.04.

View File

@ -1,21 +0,0 @@
#!/bin/sh
if [ "$(which docker)" != "" ]; then
exit 0
fi
apt-get update
apt-get install -y linux-image-extra-`uname -r`
sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
# Add the Docker repository to your apt sources list.
sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
# update
apt-get update
# install
apt-get install -y lxc-docker

View File

@ -1,105 +0,0 @@
#!/bin/sh
set -e
# Check if Nix is already installed
if [ -d "/nix/store" ]; then
exit 0
fi
# Install the binary tarball...
apt-get install -y curl
cd /
curl -L http://hydra.nixos.org/job/nix/trunk/binaryTarball.x86_64-linux/latest/download | tar xj
/usr/bin/nix-finish-install
rm /usr/bin/nix-finish-install
# Hack
chmod 777 /nix/var/nix/profiles
# Setup multiuserbu
# Allow all users to create profiles
mkdir -p /nix/var/nix/profiles/per-user
chmod 1777 /nix/var/nix/profiles/per-user
# Add build users
# 9 is the exit code when the group already exists
groupadd -r nixbld || [ "$?" -eq 9 ]
for n in 1 2 3 4 5 6 7 8 9 10; do
useradd -c "Nix build user $n" -d /var/empty -g nixbld -G nixbld \
-M -N -r -s `which nologin` nixbld$n || [ "$?" -eq 9 ]
done
chown root:nixbld /nix/store
chmod 1775 /nix/store
mkdir -p /etc/nix
grep -w build-users-group /etc/nix/nix.conf 2>/dev/null || echo "build-users-group = nixbld" >> /etc/nix/nix.conf
grep -w binary-caches /etc/nix/nix.conf 2>/dev/null || echo "binary-caches = http://cache.nixos.org" >> /etc/nix/nix.conf
grep -w trusted-binary-caches /etc/nix/nix.conf 2>/dev/null || echo "trusted-binary-caches = http://hydra.nixos.org http://cache.nixos.org" >> /etc/nix/nix.conf
# Use a multiuser-compatible profile script
unlink /etc/profile.d/nix.sh
cat > /etc/profile.d/nix.sh <<EOF
if test -n "\$HOME"; then
NIX_LINK="\$HOME/.nix-profile"
if [ -w /nix/var/nix/db ]; then
OWNS_STORE=1
fi
# Set the default profile.
if ! [ -L "\$NIX_LINK" ]; then
echo "creating \$NIX_LINK" >&2
mkdir -p "/nix/var/nix/profiles/per-user/\$LOGNAME"
_NIX_PROFILE_LINK="/nix/var/nix/profiles/per-user/\$LOGNAME/profile"
ln -s /nix/var/nix/profiles/default \$_NIX_PROFILE_LINK
ln -s "\$_NIX_PROFILE_LINK" "\$NIX_LINK"
fi
# Subscribe the root user to the Nixpkgs channel by default.
if [ ! -e "\$HOME/.nix-channels" ]; then
echo "http://nixos.org/channels/nixpkgs-unstable nixpkgs" > "\$HOME/.nix-channels"
fi
# Set up nix-defexpr
NIX_DEFEXPR="\$HOME/.nix-defexpr"
if ! [ -e "\$NIX_DEFEXPR" ]; then
echo "creating \$NIX_DEFEXPR" >&2
mkdir -p "\$NIX_DEFEXPR"
_NIX_CHANNEL_LINK=/nix/var/nix/profiles/per-user/root/channels
ln -s "\$_NIX_CHANNEL_LINK" "\$NIX_DEFEXPR/channels"
#/nix/var/nix/profiles/default/bin/nix-channel --update
fi
if [ -z "\$OWNS_STORE" ]; then
export NIX_REMOTE=daemon
export PATH="/nix/var/nix/profiles/default/bin:\$PATH"
fi
export PATH="\$NIX_LINK/bin:\$PATH"
# Set up NIX_PATH
export NIX_PATH="\$NIX_DEFEXPR/channels"
unset OWNS_STORE
fi
EOF
# Add default nix profile to global path and enable it during sudo
sed -i 's/"$/:\/nix\/var\/nix\/profiles\/default\/bin"/' /etc/environment
sed -i 's/secure_path="/secure_path="\/nix\/var\/nix\/profiles\/default\/bin:/' /etc/sudoers
# Install upstart job
cat > /etc/init/nix-daemon.conf <<EOF
description "Nix Daemon"
start on filesystem
stop on shutdown
respawn
env NIX_CONF_DIR="/etc/nix"
exec $(readlink -f /nix/var/nix/profiles/default/bin/nix-daemon) --daemon
EOF
# Start nix daemon
initctl start nix-daemon
# Update the nix channel
/nix/var/nix/profiles/default/bin/nix-channel --update

View File

@ -10,10 +10,6 @@ let
default = "/";
description = "Current directory when running the command";
};
user = mkOption {
default = "root";
description = "The user to run the command as";
};
environment = mkOption {
default = {};
example = {
@ -59,7 +55,7 @@ in {
config = mkIf config.supervisord.enable {
supervisord.configFile = pkgs.writeText "supervisord.conf" ''
[supervisord]
logfile=/var/log/supervisord/supervisord.log
logfile=/tmp/supervisor/var/log/supervisord/supervisord.log
${concatMapStrings (name:
let
@ -71,22 +67,25 @@ in {
environment=${concatMapStrings (name: "${name}=\"${toString (getAttr name cfg.environment)}\",") (attrNames cfg.environment)}
directory=${cfg.directory}
redirect_stderr=true
stdout_logfile=/var/log/supervisord/${name}.log
user=${cfg.user}
stdout_logfile=/tmp/supervisor/var/log/supervisord/${name}.log
startsecs=${toString cfg.startsecs}
''
) (attrNames services)
}
'';
docker.bootScript = ''
mkdir -p /var/log/supervisord
userNix.startScript = let
systemPackages = config.environment.systemPackages;
systemEnv = pkgs.buildEnv { name = "system-env"; paths = systemPackages; };
in ''
mkdir -p /tmp/supervisor/var/log/supervisord
export PATH="${systemEnv}/bin:${systemEnv}/sbin"
${pkgs.pythonPackages.supervisor}/bin/supervisord -c ${config.supervisord.configFile} ${if config.supervisord.tailLogs then ''
sleep 2
touch /var/log/supervisord/test.log
touch $(pwd)/var/log/supervisord/test.log
tail -n 100 -f /var/log/supervisord/*.log
'' else "-n"}
'';
};
}
}

View File

@ -9,11 +9,19 @@ let
oneShotServices = filterAttrs (name: cfg: isOneShot cfg) services;
filterCommand = cmd:
let
filtered = substring 1 (stringLength cmd -2) cmd;
splitted = pkgs.lib.splitString " " filtered;
in if eqStrings (substring 0 1 cmd) "@" then
traceVal (head splitted) + concatStringsSep " " (drop 2 splitted)
else cmd;
configToCommand = name: cfg: ''
#!/bin/sh -e
${if hasAttr "preStart" cfg then cfg.preStart else ""}
${if hasAttr "ExecStart" cfg.serviceConfig then
cfg.serviceConfig.ExecStart
filterCommand cfg.serviceConfig.ExecStart
else if hasAttr "script" cfg then
cfg.script
else
@ -31,23 +39,23 @@ in {
};
config = {
docker.buildScripts."1-systemd-oneshot" = concatMapStrings (name: "${configToCommand name (getAttr name oneShotServices)}\n") (attrNames oneShotServices);
userNix.startScripts."1-systemd-oneshot" = concatMapStrings (name: "${configToCommand name (getAttr name oneShotServices)}\n") (attrNames oneShotServices);
supervisord.services = listToAttrs (map (name:
let
cfg = getAttr name runServices;
cfg = getAttr name services;
in
{
name = name;
value = {
command = pkgs.writeScript "${name}-run" (configToCommand name cfg);
user = if hasAttr "User" cfg.serviceConfig then cfg.serviceConfig.User else "root";
environment = (if hasAttr "environment" cfg then cfg.environment else {}) //
(if hasAttr "path" cfg then
{ PATH = concatStringsSep ":" (map (prg: "${prg}/bin") cfg.path); }
else {});
{ PATH = "%(ENV_PATH)s:" + concatStringsSep ":" (map (prg: "${prg}/bin") cfg.path); }
else {
PATH="%(ENV_PATH)s"; });
};
}
) (attrNames runServices));
};
}
}

17
user.nix Normal file
View File

@ -0,0 +1,17 @@
{ config, pkgs, ... }:
with pkgs.lib;
{
options = {
userNix.startScripts = mkOption {
default = {};
description = "Scripts (as text) to be run during build, executed alphabetically";
};
userNix.startScript = mkOption {};
};
config = {
userNix.startScript = concatStrings (attrValues config.userNix.startScripts);
};
}

View File

@ -1,10 +0,0 @@
{
"modes": {
"python": {
"filenames": ["nix-docker"]
},
"ruby": {
"filenames": ["Vagrantfile"]
}
}
}