diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..15b1115 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: CI + +on: + pull_request: + push: + paths-ignore: + - '**.md' + +jobs: + nix-build: + runs-on: ubuntu-latest + env: + NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-20.09.tar.gz" + steps: + - uses: cachix/install-nix-action@v12 + - uses: actions/checkout@v1 + - name: Check format + run: nix-build --no-out-link ./. diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 958e4ca..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -# .travis.yml - -services: - - docker - -addons: - apt: - packages: - - docker-ce - -script: - - docker build --pull -t nixos/nix . - - docker run -it --rm nixos/nix nix-channel --list - - docker run -it --rm nixos/nix sh -l -c 'nix-env -iA nixpkgs.hello && test "$(hello)" = "Hello, world!"' - - docker run -it --rm nixos/nix nix-shell -p hello --run 'test "$(hello)" = "Hello, world!"' diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..40af2f3 --- /dev/null +++ b/default.nix @@ -0,0 +1,264 @@ +{ pkgs ? import { } +, lib ? pkgs.lib +, name ? "nix" +, tag ? "latest" +, crossSystem ? null +, channelName ? "nixpkgs" +, channelURL ? "https://nixos.org/channels/nixpkgs-unstable" +}: +let + buildPkgs = pkgs; + targetPkgs = + if crossSystem != null && crossSystem != pkgs.system + then { + aarch64-linux = pkgs.pkgsCross.aarch64-multiplatform; + armv7l-linux = pkgs.pkgsCross.armv7l-hf-multiplatform.system; + x86_64-linux = pkgs.pkgsCross.gnu64; + powerpc64le-linux = pkgs.pkgsCross.musl-power; + i686-linux = pkgs.pkgsCross.gnu32; + }.${crossSystem} + else pkgs; + + defaultPkgs = [ + targetPkgs.nix + targetPkgs.bashInteractive + targetPkgs.coreutils-full + targetPkgs.gnutar + targetPkgs.gzip + targetPkgs.gnugrep + targetPkgs.which + targetPkgs.curl + targetPkgs.less + targetPkgs.wget + targetPkgs.man + targetPkgs.cacert.out + targetPkgs.findutils + ]; + + users = { + + root = { + uid = 0; + shell = "/bin/bash"; + home = "/root"; + gid = 0; + }; + + } // lib.listToAttrs ( + map + ( + n: { + name = "nixbld${toString n}"; + value = { + uid = 30000 + n; + gid = 30000; + groups = [ "nixbld" ]; + description = "Nix build user ${toString n}"; + }; + } + ) + (lib.lists.range 1 32) + ); + + groups = { + root.gid = 0; + nixbld.gid = 30000; + }; + + userToPasswd = ( + k: + { uid + , gid ? 65534 + , home ? "/var/empty" + , description ? "" + , shell ? "/bin/false" + , groups ? [ ] + }: "${k}:x:${toString uid}:${toString gid}:${description}:${home}:${shell}" + ); + passwdContents = ( + lib.concatStringsSep "\n" + (lib.attrValues (lib.mapAttrs userToPasswd users)) + ); + + userToShadow = k: { ... }: "${k}:!:1::::::"; + shadowContents = ( + lib.concatStringsSep "\n" + (lib.attrValues (lib.mapAttrs userToShadow users)) + ); + + # Map groups to members + # { + # group = [ "user1" "user2" ]; + # } + groupMemberMap = ( + let + # Create a flat list of user/group mappings + mappings = ( + builtins.foldl' + ( + acc: user: + let + groups = users.${user}.groups or [ ]; + in + acc ++ map + (group: { + inherit user group; + }) + groups + ) + [ ] + (lib.attrNames users) + ); + in + ( + builtins.foldl' + ( + acc: v: acc // { + ${v.group} = acc.${v.group} or [ ] ++ [ v.user ]; + } + ) + { } + mappings) + ); + + groupToGroup = k: { gid }: + let + members = groupMemberMap.${k} or [ ]; + in + "${k}:x:${toString gid}:${lib.concatStringsSep "," members}"; + groupContents = ( + lib.concatStringsSep "\n" + (lib.attrValues (lib.mapAttrs groupToGroup groups)) + ); + + nixConf = { + sandbox = "false"; + build-users-group = "nixbld"; + trusted-public-keys = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="; + }; + nixConfContents = (lib.concatStringsSep "\n" (lib.mapAttrsFlatten (n: v: "${n} = ${v}") nixConf)) + "\n"; + + baseSystem = + let + nixpkgs = targetPkgs.path; + channel = targetPkgs.runCommand "channel-nixos" { } '' + mkdir $out + ln -s ${nixpkgs} $out/nixpkgs + echo "[]" > $out/manifest.nix + ''; + rootEnv = pkgs.buildEnv { + name = "root-profile-env"; + paths = defaultPkgs; + }; + profile = targetPkgs.runCommand "user-environment" { } '' + mkdir $out + cp -a ${rootEnv}/* $out/ + + cat > $out/manifest.nix < $out/etc/passwd + echo "" >> $out/etc/passwd + + cat $groupContentsPath > $out/etc/group + echo "" >> $out/etc/group + + cat $shadowContentsPath > $out/etc/shadow + echo "" >> $out/etc/shadow + + mkdir -p $out/usr + ln -s /nix/var/nix/profiles/share $out/usr/ + + mkdir -p $out/nix/var/nix/gcroots + + mkdir $out/tmp + + mkdir -p $out/etc/nix + cat $nixConfContentsPath > $out/etc/nix/nix.conf + + mkdir -p $out/root + mkdir -p $out/nix/var/nix/profiles/per-user/root + + ln -s ${profile} $out/nix/var/nix/profiles/default-1-link + ln -s $out/nix/var/nix/profiles/default-1-link $out/nix/var/nix/profiles/default + ln -s /nix/var/nix/profiles/default $out/root/.nix-profile + + ln -s ${channel} $out/nix/var/nix/profiles/per-user/root/channels-1-link + ln -s $out/nix/var/nix/profiles/per-user/root/channels-1-link $out/nix/var/nix/profiles/per-user/root/channels + + mkdir -p $out/root/.nix-defexpr + ln -s $out/nix/var/nix/profiles/per-user/root/channels $out/root/.nix-defexpr/channels + echo "${channelURL} ${channelName}" > $out/root/.nix-channels + + mkdir -p $out/bin $out/usr/bin + ln -s ${targetPkgs.coreutils}/bin/env $out/usr/bin/env + ln -s ${targetPkgs.bashInteractive}/bin/bash $out/bin/sh + ''; + +in +targetPkgs.dockerTools.buildLayeredImageWithNixDb { + + inherit name tag; + + contents = [ baseSystem ]; + + extraCommands = '' + rm -rf nix-support + ln -s /nix/var/nix/profiles nix/var/nix/gcroots/profiles + ''; + + config = { + Cmd = [ "/root/.nix-profile/bin/bash" ]; + Env = [ + "USER=root" + "PATH=${lib.concatStringsSep ":" [ + "/root/.nix-profile/bin" + "/nix/var/nix/profiles/default/bin" + "/nix/var/nix/profiles/default/sbin" + ]}" + "MANPATH=${lib.concatStringsSep ":" [ + "/root/.nix-profile/share/man" + "/nix/var/nix/profiles/default/share/man" + ]}" + "SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt" + "GIT_SSL_CAINFO=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt" + "NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt" + "NIX_PATH=/nix/var/nix/profiles/per-user/root/channels:/root/.nix-defexpr/channels" + ]; + }; + +} diff --git a/dockerhub/Dockerfile b/dockerhub/Dockerfile new file mode 100644 index 0000000..1c2c21e --- /dev/null +++ b/dockerhub/Dockerfile @@ -0,0 +1,3 @@ +# Dummy Dockerfile to make Docker Hub automated builds happy +# The actual build steps are overriden in hooks/ +FROM localhost/nix:latest diff --git a/Dockerfile b/dockerhub/bootstrap/Dockerfile similarity index 75% rename from Dockerfile rename to dockerhub/bootstrap/Dockerfile index 5ada063..619ba70 100644 --- a/Dockerfile +++ b/dockerhub/bootstrap/Dockerfile @@ -1,4 +1,5 @@ # Dockerfile to create an environment that contains the Nix package manager. +# This is based on the old Alpine based Docker image and is used for bootstraping the Docker hub build FROM alpine @@ -7,16 +8,16 @@ RUN apk add --no-cache --update openssl \ && echo hosts: dns files > /etc/nsswitch.conf # Download Nix and install it into the system. -ARG NIX_VERSION=2.3.10 -RUN wget https://nixos.org/releases/nix/nix-${NIX_VERSION}/nix-${NIX_VERSION}-$(uname -m)-linux.tar.xz \ - && tar xf nix-${NIX_VERSION}-$(uname -m)-linux.tar.xz \ +ARG NIX_VERSION=2.3.6 +RUN wget https://nixos.org/releases/nix/nix-${NIX_VERSION}/nix-${NIX_VERSION}-x86_64-linux.tar.xz \ + && tar xf nix-${NIX_VERSION}-x86_64-linux.tar.xz \ && addgroup -g 30000 -S nixbld \ && for i in $(seq 1 30); do adduser -S -D -h /var/empty -g "Nix build user $i" -u $((30000 + i)) -G nixbld nixbld$i ; done \ && mkdir -m 0755 /etc/nix \ && echo 'sandbox = false' > /etc/nix/nix.conf \ - && mkdir -m 0755 /nix && USER=root sh nix-${NIX_VERSION}-$(uname -m)-linux/install \ + && mkdir -m 0755 /nix && USER=root sh nix-${NIX_VERSION}-x86_64-linux/install \ && ln -s /nix/var/nix/profiles/default/etc/profile.d/nix.sh /etc/profile.d/ \ - && rm -r /nix-${NIX_VERSION}-$(uname -m)-linux* \ + && rm -r /nix-${NIX_VERSION}-x86_64-linux* \ && rm -rf /var/cache/apk/* \ && /nix/var/nix/profiles/default/bin/nix-collect-garbage --delete-old \ && /nix/var/nix/profiles/default/bin/nix-store --optimise \ @@ -27,7 +28,8 @@ ONBUILD ENV \ USER=root \ PATH=/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin \ GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt \ - NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt + NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \ + NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixos-unstable.tar.gz ENV \ ENV=/etc/profile \ diff --git a/dockerhub/hooks/build b/dockerhub/hooks/build new file mode 100755 index 0000000..094d8ba --- /dev/null +++ b/dockerhub/hooks/build @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +from concurrent import futures +import subprocess +import os.path +import json +import os + + +def gen_cmds(systems, channels): + for system in systems: + for name, channel in channels.items(): + yield "env NIX_PATH=nixpkgs={url} nix-build --no-out-link --argstr name {docker_repo} --argstr tag {tag} --argstr channelName {channel_name} --argstr channelURL {channel_url} --argstr crossSystem {system} /build/default.nix".format( + channel_name=channel["name"], + channel_url=channel["url"], + docker_repo=docker_repo, + tag=name + "-" + system, + system=system, + url=channel["url"]+"/nixexprs.tar.xz", + ) + + +def run(buildcmd: str): + cmd = [ + "docker", + "run", + "-i", + "--privileged", + "-v", "{}:/build".format(os.path.abspath("..")), + "-v", "/var/run/docker.sock:/var/run/docker.sock", + "nix-bootstrap", + ] + [ + "nix-shell", "-p", "docker", "--run", "docker load < `{}`".format(buildcmd) + ] + + subprocess.run(cmd, check=True) + + +if __name__ == "__main__": + docker_repo = os.environ["DOCKER_REPO"] + + matrix_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "matrix.json") + with open(matrix_path) as f: + data = json.load(f) + + subprocess.run("docker build -f bootstrap/Dockerfile -t nix-bootstrap:latest bootstrap", check=True, shell=True) + + with futures.ThreadPoolExecutor() as e: + for future in futures.as_completed(e.submit(run, cmd) for cmd in gen_cmds(data["systems"], data["channels"])): + future.result() diff --git a/dockerhub/hooks/push b/dockerhub/hooks/push new file mode 100755 index 0000000..76c59f5 --- /dev/null +++ b/dockerhub/hooks/push @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +from concurrent import futures +import subprocess +import os.path +import json +import os + + +def gen_cmds(systems, channels): + + for name, channel in channels.items(): + images = [] + for system in systems: + tag = name + "-" + system + image = ":".join((docker_repo, tag)) + images.append(image) + yield ["docker", "push", image] + + manifest_tag = "-".join((name, "latest")) + tag = ":".join((docker_repo, manifest_tag)) + create_cmd = ["docker", "manifest", "create", tag] + for img in images: + create_cmd = create_cmd + [ + "--amend", img + ] + yield create_cmd + yield ["docker", "manifest", "push", tag] + + if name == "nixpkgs-unstable": + manifest_tag = "latest" + tag = ":".join((docker_repo, manifest_tag)) + create_cmd = ["docker", "manifest", "create", tag] + for img in images: + create_cmd = create_cmd + [ + "--amend", img + ] + yield create_cmd + yield ["docker", "manifest", "push", tag] + + +if __name__ == "__main__": + # For docker manifest create/push + os.environ["DOCKER_CLI_EXPERIMENTAL"] = "enabled" + + docker_repo = os.environ["DOCKER_REPO"] + + matrix_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "matrix.json") + with open(matrix_path) as f: + data = json.load(f) + + for cmd in gen_cmds(data["systems"], data["channels"]): + print(cmd) + subprocess.run(cmd, check=True) diff --git a/matrix.json b/matrix.json new file mode 100644 index 0000000..b894b0d --- /dev/null +++ b/matrix.json @@ -0,0 +1,16 @@ +{ + "systems": [ + "x86_64-linux", + "aarch64-linux" + ], + "channels": { + "nixpkgs-unstable": { + "name": "nixpkgs", + "url": "https://nixos.org/channels/nixpkgs-unstable" + }, + "nixos-20.09": { + "name": "nixos", + "url": "https://nixos.org/channels/nixos-20.09" + } + } +} diff --git a/matrix.nix b/matrix.nix new file mode 100644 index 0000000..0efc635 --- /dev/null +++ b/matrix.nix @@ -0,0 +1,27 @@ +{ pkgs ? import { } +, lib ? pkgs.lib +}: + +# Convert the JSON matrix into an attrset of derivations +# You can build indvidual images like `nix-build matrix.nix -A nixos-20.09-x86_64-linux` +let + matrix = lib.importJSON ./matrix.json; +in +lib.listToAttrs (lib.foldl' + (acc: system: acc ++ (lib.mapAttrsToList (name: channel: ( + let + tag = name + "-" + system; + in + { + name = tag; + value = import ./default.nix { + pkgs = import (builtins.fetchTarball "${channel.url}/nixexprs.tar.xz") { }; + name = "docker.io/nixos/nix"; + inherit tag; + crossSystem = system; + channelName = channel.name; + channelURL = channel.url; + }; + } + ))) matrix.channels) [ ] + matrix.systems)