CI/CD - Single projects
{
inputs = {
crane = {
url = "github:ipetkov/crane";
inputs = {
flake-utils.follows = "flake-utils";
nixpkgs.follows = "nixpkgs";
};
};
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "nixpkgs/nixos-unstable";
};
outputs = { self, crane, fenix, flake-utils, nixpkgs }:
flake-utils.lib.eachDefaultSystem (system: {
packages.default =
let
craneLib = crane.lib.${system}.overrideToolchain
fenix.packages.${system}.minimal.toolchain;
in
craneLib.buildPackage {
src = ./.;
pname = "docker-spider";
};
});
}
The flake.nix File
-
Inputs: The file specifies several inputs, including
crane,fenix,flake-utils, andnixpkgs. These inputs are likely dependencies or tools required for your project. Each of these inputs has a URL, indicating where Nix can fetch them, and some have additional input dependencies themselves. -
Outputs: The outputs section is where the actual build and deployment configurations are defined. This file uses
flake-utils.lib.eachDefaultSystem, suggesting it's set up to handle multiple systems (or architectures). This is a common practice for building packages that are compatible with different operating systems or hardware architectures. -
Build and Deployment Logic: Inside the outputs, you would typically define how your application or package is built and possibly how it should be deployed. This can include custom build scripts, packaging instructions, and more.
Creating a .gitlab-ci.yml for GitLab CI
To use this flake.nix in a GitLab CI pipeline, you need to create a .gitlab-ci.yml file that tells
GitLab CI how to build and test your project using Nix. Here's a basic example:
stages:
- build
- test
build-job:
stage: build
image: nixos/nix
script:
- nix build
artifacts:
paths:
- result
Explanation:
- Stages: Defined two stages,
buildandtest. You can add more stages likedeployif needed. - Build Job:
- Uses the
nixos/nixDocker image, which comes with Nix pre-installed. - Runs
nix buildto build your project using theflake.nixfile. - Artifacts (
result) are saved and can be used in later stages.
- Uses the
- Test Job:
- Also uses the
nixos/niximage.
- Also uses the
This setup does no caching. If you can and have access to it, you can use caching in our CI by using the correct image and correct runner tags:
.image: &image
image: docker.ii.zone/pool/main/nix-ci:latest
tags:
- nix
This setup will build and test your Nix project on GitLab CI every time you push changes to your repository. Make sure to adjust the test command and add any additional stages or jobs as needed for your specific project.
Multiple packages in a single project: Frontend Flake
{
# this is the description of the flake. It is just a bit of metadata, not very important for Braiins usecase
description = "Frontend Flake";
# when using `nix develop`, this is what will be prepended
nixConfig.bash-prompt-prefix = "(frontend) ";
# dependencies of the frontend falek
inputs = {
# flakes
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # We are using the unstable Nix channel here because Pepa needs a new NodeJS version
flake-utils.url = "github:numtide/flake-utils"; # A library that provides a wrapper that projects your flake to different architectures
# among other things
# a library used for filtering out source code
# we use this to ensure that only what is needed is built
# If we had projects completely separated into folders, we wouldn't need this
# however, there is a common core between the JS packages
nix-filter = {
url = "github:numtide/nix-filter";
};
# this consumes a Python Poetry metadata to create a Nix environment to run and build our Python
poetry2nix = {
url = "github:nix-community/poetry2nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils";
};
# our repo that provides docker base images for Nix
# we use this for the debian basis
base-images = {
url = "git+ssh://git@gitlab.ii.zone/pool/base-images.nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils";
inputs.nix-filter.follows = "nix-filter";
};
# adds treefmt support, so we can format everything via nix
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# git repos that are not flakes yet
#
# locale dependencies
braiins-os-locale = {
url = "git+https://github.com/braiins/bos-i18n.git";
flake = false;
};
web-pool-locale = {
url = "git+https://github.com/slushpool/web-pool-i18n.git";
flake = false;
};
toolbox-locale = {
url = "git+https://github.com/braiins/toolbox-i18n.git";
flake = false;
};
insights-locale = {
url = "git+https://github.com/braiins/insights-i18n.git";
flake = false;
};
};
outputs =
# flakes
{ self # | outputs are a function that takes an attribute set
, nixpkgs # | containing all of the inputs as parameter
, flake-utils # |
, nix-filter # | and returns an attrset with the actual outputs
, poetry2nix # |
, base-images # | in this case, the final attrset is created by
, treefmt-nix # | flake-utils
# git repos from github # |
, braiins-os-locale # |
, web-pool-locale # | any Git repository can be an input to a flake,
, toolbox-locale # | doesn't have to be a flake. You can refer to files
, insights-locale # | from these repositories same as you would to files
, ... # | of a derivation in a flake
}:
flake-utils.lib.eachDefaultSystem
(localSystem:
let
# starting node version # |
NODE_VERSION = "19.6.0"; # |
# Nixpkgs imports
#
# - Set up pkgs for current system
# - Import lib and stdenv
pkgs = (import nixpkgs) { inherit localSystem; }; #
inherit (pkgs) lib; #
inherit (pkgs) stdenv; #
# Outside dependencies
#
# - node dependencies
# - python dependencies
# - git repositories
git-deps = (import ./nix/git-deps.nix) {
inherit braiins-os-locale web-pool-locale toolbox-locale insights-locale stdenv;
};
inherit (git-deps) makeGitDeps;
yarn-deps = (import ./nix/yarn-files.nix) {
inherit stdenv pkgs nix-filter;
};
inherit (yarn-deps) yarnFiles;
python-env = (import ./nix/python-env.nix) {
inherit stdenv pkgs nix-filter localSystem poetry2nix;
};
inherit (python-env) pythonEnv;
# Internal definitions' imports
config = (import ./nix/config.nix) {
inherit stdenv pkgs nix-filter;
};
unpatchedYarnPackages = (import ./nix/packages.nix) {
inherit stdenv pkgs nix-filter config makeGitDeps yarnFiles pythonEnv;
};
yarnPackages = with builtins; let
patchGitInfo = package: stdenv.mkDerivation {
name = "patched-${package.name}";
buildInputs = [ pkgs.gnused ];
phases = [ "buildPhase" ];
buildPhase =
let
placeholder_hash = "REPLACE_THIS_WITH_GIT_HASH";
placeholder_date = "REPLACE_THIS_WITH_GIT_DATE";
head = substring 0 16 (self.rev or "0000000000000000");
date =
let
_ = self.lastModifiedDate or "YYYYMMDDhhmmss";
d = {
YYYY = builtins.substring 0 4 _;
MM = builtins.substring 4 2 _;
DD = builtins.substring 6 2 _;
hh = builtins.substring 8 2 _;
mm = builtins.substring 10 2 _;
ss = builtins.substring 12 2 _;
};
in
"${d.YYYY}-${d.MM}-${d.DD}_${d.hh}:${d.mm}:${d.ss}";
in
''
mkdir -p $out
cp -r ${package}/* $out/
cd $out
# By default, the files are read-only, so we need to change that
chmod a+w -R .
echo "build info"
echo " head = ${head}"
echo " date = ${date}"
# ↓ files ↓ non-binary ↓ containing predefined placeholder strings ↓
paths=($(find . -type f -exec grep -I1q "${placeholder_hash}\|${placeholder_date}" {} \; -print))
# the array expansion is not a valid NIX syntax, so we need to escape
# the variable interpolation pattern to let it be evaluated by bash
for path in ''${paths[@]}; do
echo "Patching the file ''${path}"
sed -i 's|${placeholder_hash}|${head}|g' ''${path}
sed -i 's|${placeholder_date}|${date}|g' ''${path}
done
# Make the files read-only again
chmod a-w -R .
'';
};
in
(mapAttrs (name: value: (patchGitInfo value)) unpatchedYarnPackages.normal);
dockerImages =
(import ./nix/docker-images.nix) {
inherit stdenv pkgs nix-filter base-images yarnPackages;
};
# create a list combining all paths, so we can symlinkJoin them
paths = with builtins; (map (key: getAttr key yarnPackages) (attrNames yarnPackages));
apps =
let
wrapped_command = cmd: {
type = "app";
program =
let
# Merge Linux-only deps with general deps (because of Mac, naturally)
buildInputs = config.buildInputs ++ (lib.optionals pkgs.stdenv.hostPlatform.isLinux config.linuxBuildInputs);
env_script = pkgs.writeShellScript "script.sh" ''
# GitLab CI section start
CIS_NAME="wraper_setup"
CIS_LABEL="Nix command env setup"
CIS_OPTS="[collapsed=true]"
echo -e "\033[0Ksection_start:$(date +%s):$CIS_NAME$CIS_OPTS\r\033[0K$CIS_LABEL"
# ENV
export TERM=${config.env.TERM}
# PATH
export PATH="${lib.concatStringsSep ":" (builtins.map (x: "${x}/bin") (buildInputs))}:$PATH"
export PATH="${pythonEnv}/bin:$PATH"
export PATH="${pkgs.ruff}/bin:$PATH"
echo "PATH:"
sed 's/:/\n - /g' <<< ":$PATH"
# Install local poetry env so that "poetry run" can pick it up
poetry install --no-root --quiet --no-interaction
echo -e "\nPython prefix when running through bare 'python'":
echo -e " $(python -c 'import sys; print(sys.prefix)')\n"
echo -e "\nPython prefix when running through 'poetry run':"
echo -e " $(poetry run python -c 'import sys; print(sys.prefix)')\n"
# GitLab CI section end
echo -e "\033[0Ksection_end:$(date +'%s'):$CIS_NAME\r\033[0K "
${cmd} $@
'';
in
"${env_script}";
};
in
{
# Linters
lint-proto = (wrapped_command "make proto-lint proto-format-diff");
lint-js = (wrapped_command "make lint-js");
lint-yarn = (wrapped_command "make lint-yarn");
lint-styles = (wrapped_command "make lint-styles");
lint-configs = (wrapped_command "make lint-configs");
# Tests
ci-test-static = (wrapped_command "make ci-test-static");
ci-test-unit = (wrapped_command "make ci-test-unit");
# util
wrapped-make = (wrapped_command "make");
};
in
{
inherit apps;
# Set formatter and enable packages by including them here.
#
# To add a docker image, go to
# ./nix/docker-images.nix
#
# To add a package, go to
# ./nix/packages.nix
#
# Keep in mind that you will have to define a new dependency to the docker images,
# which you will have to pass in as a parameter (see insights-docker for an example).
formatter = treefmt-nix.lib.mkWrapper pkgs {
projectRootFile = "flake.nix";
programs.nixpkgs-fmt.enable = true;
programs.black.enable = true;
settings.formatter.ruff = {
command = pkgs.ruff;
options = [ "check" ];
includes = [ "*.py" ];
};
};
packages = yarnPackages // (lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux dockerImages) // unpatchedYarnPackages.cypress // {
default = pkgs.symlinkJoin {
name = "all";
inherit paths;
};
nodeDeps = yarn-deps.yarnFiles;
pythonEnv = python-env.pythonEnv;
};
# Default X86 shell
# You can create shells for other platforms and configurations
devShells.default = pkgs.mkShell {
# Default x86_64-linux shell
# This shell will be invoked if you just type:
#
# ```
# nix develop
# ```
#
# You can create more shells following this example,
# but you will need to call them something else
buildInputs = config.buildInputs ++ [
pkgs.nodejs_20
pkgs.netcat-gnu
];
shellHook = ''
export PATH="${pythonEnv}/bin:$PATH"
export PATH="${pkgs.ruff}/bin:$PATH"
poetry env use "$(python -c 'import sys; print(sys.prefix)')/bin/python"
'';
inherit (config) env;
};
});
}