Accueil / Informatique / NixOS / Déployer un service Rust avec NixOS et les flakes

Déployer un service Rust avec NixOS et les flakes

Configurer les flakes sur le serveur

Source en anglais

Installer la nouvelle CLI unique nix qui supporte les flakes :

{ pkgs, ... }: {
  nix = {
    package = pkgs.nixUnstable;
    extraOptions = ''
      experimental-features = nix-command flakes
    '';
   };
}

Dans /etc/nixos/flake.nix :

{
  outputs = { self, nixpkgs }: {
    nixosConfigurations.server = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [ ./configuration.nix ];
    };
  };
}

Et sudo nixos-rebuild devrait prendre en compte la Flake.

Écrire une flake pour le(s) service(s) Rust

Globalement, on va :

  • écrire une flake en local, qui définit les dérivations pour nos services ;
  • la mettre sur GitHub/GitLab/autre ;
  • Ajouter ce dépôt git comme dépendance de la flake du serveur ;
  • Installer les dérivations.

Du coup, en local, on fait un nouveau dossier, on fait un petit nix flake init.

Pour compiler du Rust, on va passer par fenix, une flake faite pour configurer une toolchain Rust.

{
  description = "…";
  # mettez un vrai commit
  inputs.fenix.url = "github:figsoda/fenix?rev=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
  input.fenix.inputs.nixpkgs.follows = "nixpkgs";

  # flake-utils c'est pratique aussi tant qu'à faire
  inputs.flake-utils.url = "github:numtide/flake-utils";
  # …
}

Ensuite pour définir la dérivation, on met un truc comme ça dans les outputs :

flake-utils.lib.eachDefaultSystem (system:
  let
    pkgs = nixpkgs.legacyPackages.${system};
    rust = pkgs.makeRustPlatform {
      inherit (fenix.packages.${system}.minimal) cargo rustc;
    };
  in {
    packages = {
      ma-derivation = rust.buildRustPackage {
        crateName = "ma-crate";
        pname = "mon-paquet";
        version = "0.1.0";
        src = pkgs.fetchFromGitHub {
          owner = "exemple";
          repo = "mon-paquet";
          rev = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
          sha256 = "sha256-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        };
        cargoSha256 = "sha256-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        meta = with pkgs.lib; {
          description = "…";
          homepage = "https://example.org";
          license = licenses.agpl3;
        };
      };
    };
  }
)

On peut ajouter des options de config pour faciliter le travail côté serveur :

flake-utils.lib.eachDefaultSystem (system:
# …
) // {
  nixosModule = { config, pkgs, lib, ... }:
  with lib;
  {
    # On déclare les options
    options.services.mon-service = {
      enable = mkEnableOption "Mon service";
      domain = mkOption {
        type = types.str;
        description = "The domain name on which the app is hosted";
      };
      enableNginx = mkOption {
        type = types.bool;
        default = true;
        description = "Enable nginx config for Mon service";
      };
    };

    # On écrit la config selon les options
    config.systemd.services.wartapuretai = mkIf config.services.mon-service.enable {
      description = "Mon service";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      # C'est un exemple au cas où ça soit une appli Rocket…
      environment = {
        ROCKET_PORT = "8088";
      };
      serviceConfig = {
        WorkingDirectory = "${self.packages.${pkgs.system}.ma-derivation}/";
        ExecStart = "${self.packages.${pkgs.system}.ma-derivation}/bin/ma-crate";
        Type = "simple";
      };
    };

    config.services.nginx.virtualHosts."${config.services.mon-service.domain}" = mkIf config.services.mon-service.enableNginx {
      enableACME = true;
      forceSSL = true;
      root = "${self.packages.${pkgs.system}.ma-derivation}";
      locations = {
        "/" = {
          proxyPass = "http://localhost:8088";
        };
      };
    };
  }
}

Ensuite, si on veut compiler en local plutôt que sur le serveur, on peut faire ça (il faut que ça soit la même archi et le même OS en local et sur le serveur, sinon ça va pas marcher je crois) :

nix copy --to ssh://user@serveur.tld .#ma-derivation

Ça va compiler en local et copier la dérivation dans le store Nix du serveur. Comme ça quand on va installer la dérivation sur le serveur ça pourra la prendre direct dans le store sans avoir à la compiler.

Ensuite, on commit, on push sur un dépôt, on passe sur le serveur.

Configurer le serveur

On ajoute le dépôt de notre flake dans les inputs de la flake du serveur.

# dans /etc/nixos/flake.nix

{
  inputs.ma-flake.url = "github:exemple/ma-flake?rev=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
  inputs.ma-flake.inputs.nixpkgs.follows = "nixpkgs";
}

On active le service qu'on a définit avant :

# dans /etc/nixos/configuration.nix ou autre

{
  services.mon-service = {
    enable = true;
    enableNginx = true;
    domain = "service.example.org";
  };
}

Et on valide le tout avec un petit sudo nixos-rebuild switch.

Et voilà.

Mettre à jour le tout

Pour mettre à jour :

  • on met à jour le commit et les hashs dans la flake en local
  • on commit et on push
  • sur le serveur on fait un nix flake update
  • on recompile le système nixos-rebuild switch