DotNET: Difference between revisions

From NixOS Wiki
Lostmsu (talk | contribs)
added Discord and Reddit communities
Lostmsu (talk | contribs)
create systemd service from an ASP.NET-based package
Line 52: Line 52:
Just remember to add `mono` to `buildInputs` and generate a wrapper script in `postInstall`.
Just remember to add `mono` to `buildInputs` and generate a wrapper script in `postInstall`.


== Building ASP.NET packages ==
== Packaging ASP.NET projects ==


Currently building ASP.NET packages produces website that does not work correctly out of the box because the executable can not find ContentRoot and wwwroot, so all the static assets won't load.
Currently building ASP.NET project as Nix package produces a website that does not work correctly out of the box because the executable can not find `wwwroot`, so all the static assets won't load with 404.
 
> Request finished HTTP/2 GET https://my.app/css/site.css - 404 0
 
The situation can be fixed by setting `WEBROOT` environment variable to the package path.
 
An example of systemd + ASP.NET 8 service:
 
```nix
# myapp package needs to be imported; and added to `environment.systemPackages`
# it is used below
 
systemd.services.my-app = {
  enable = true;
  description = "Runs my.app";
  wantedBy = [ "multi-user.target" ];
  after = [ "network-online.target" ];
  wants = [ "network-online.target" ];
  serviceConfig = {
    AmbientCapabilities = "CAP_NET_BIND_SERVICE"; # allow binding to privileged ports - when you want to expose Kestrel directly without reverse proxy
    User = "myapp"; # must be created using users.users.myapp = { isSystemUser = true; group = "myapp"; };
    Group = "myapp"; # must be created using users.groups.myapp = {};
    Restart = "always";
    ExecStart = "${myapp}/bin/myapp";
    StateDirectory = "myapp";
    StateDirectoryMode = "0750";
    WorkingDirectory = "/var/lib/myapp";
    # EnvironmentFile = "/var/lib/myapp/env";
  };
  environment = {
    WEBROOT = "${myapp}/lib/myapp/wwwroot"; # IMPORTANT, required to pick up static assets
 
    DOTNET_ENVIRONMENT = "Production";
 
    # the following are examples
    ConnectionStrings__DefaultConnection = "Host=/var/run/postgresql;Database=myapp";
 
    # Kestrel + HTTPS; must setup https://wiki.nixos.org/wiki/ACME
    Kestrel__Endpoints__Https__Url = "https://my.app";
    Kestrel__Endpoints__Https__Certificate__Path = "/var/lib/acme/my.app/cert.pem";
    Kestrel__Endpoints__Https__Certificate__KeyPath = "/var/lib/acme/my.app/key.pem";
 
    Logging__LogLevel__Default = "Information";
    Logging__LogLevel__Microsoft__AspNetCore = "Warning"; # this does not actually work, not sure how to fix
 
    Authentication__Google__ClientId = "xxxyyyzzz.apps.googleusercontent.com";
    Authentication__Microsoft__ClientId = "aaaaaa-0000-aaaa-0000-aaaaaaaaaa";
    # secrets must be placed in /var/lib/myapp/appsettings.json
 
    # TODO email
 
    # TODO Stripe
    Stripe__Currency = "USD";
  };
};
 
```
[ACME](https://wiki.nixos.org/wiki/ACME)


== .NET location: Not found ==
== .NET location: Not found ==

Revision as of 21:46, 24 June 2024

.NET packages can be built with buildDotnetModule

More information about buildDotnetModule can be found in the nixpkgs manual Example build file:

{ fetchFromGitHub
, dotnetCorePackages
, buildDotnetModule
}:

buildDotnetModule rec {
  pname = "some_program";
  version = "some_version";

  src = fetchFromGitHub {
    owner = "some_owner";
    repo = pname;
    rev = "v${version}";
    hash = ""; # use e.g. `nix-prefetch-git`
  };

  projectFile = "SomeProject/SomeProject.csproj";
  dotnet-sdk = dotnetCorePackages.sdk_8_0;
  dotnet-runtime = dotnetCorePackages.runtime_8_0;
  nugetDeps = ./deps.nix; # to generate, set to `""`, then `nix-build -A fetch-deps && ./result`

  meta = with lib; {
    homepage = "some_homepage";
    description = "some_description";
    license = licenses.mit;
  };
}

If the fetch-deps script isn't working for whatever reason, you can manually run nuget-to-nix:

dotnet restore --packages=packageDir ./SomeProject.csproj
nuget-to-nix packageDir >deps.nix
rm -r packageDir

Remember to build and run the fetch-deps script after NuGet packages are updated, or building the derivation will fail.

Building non-.NET Core packages

Keep in mind that building projects which don't use the .NET SDK (formerly the .NET Core SDK) and its dotnet CLI tool isn't supported. For those projects, you'll have to heavily customise the buildDotnetModule build steps, or write a custom derivation.

Projects which target .NET Standard or .NET Framework (incl. Mono), but still use the new project structure and SDK, work as expected. Just remember to add `mono` to `buildInputs` and generate a wrapper script in `postInstall`.

Packaging ASP.NET projects

Currently building ASP.NET project as Nix package produces a website that does not work correctly out of the box because the executable can not find `wwwroot`, so all the static assets won't load with 404.

> Request finished HTTP/2 GET https://my.app/css/site.css - 404 0

The situation can be fixed by setting `WEBROOT` environment variable to the package path.

An example of systemd + ASP.NET 8 service:

```nix

  1. myapp package needs to be imported; and added to `environment.systemPackages`
  2. it is used below

systemd.services.my-app = {

 enable = true;
 description = "Runs my.app";
 wantedBy = [ "multi-user.target" ];
 after = [ "network-online.target" ];
 wants = [ "network-online.target" ];
 serviceConfig = {
   AmbientCapabilities = "CAP_NET_BIND_SERVICE"; # allow binding to privileged ports - when you want to expose Kestrel directly without reverse proxy
   User = "myapp"; # must be created using users.users.myapp = { isSystemUser = true; group = "myapp"; };
   Group = "myapp"; # must be created using users.groups.myapp = {};
   Restart = "always";
   ExecStart = "${myapp}/bin/myapp";
   StateDirectory = "myapp";
   StateDirectoryMode = "0750";
   WorkingDirectory = "/var/lib/myapp";
   # EnvironmentFile = "/var/lib/myapp/env";
 };
 environment = {
   WEBROOT = "${myapp}/lib/myapp/wwwroot"; # IMPORTANT, required to pick up static assets
   DOTNET_ENVIRONMENT = "Production";
   # the following are examples
   ConnectionStrings__DefaultConnection = "Host=/var/run/postgresql;Database=myapp";
   # Kestrel + HTTPS; must setup https://wiki.nixos.org/wiki/ACME
   Kestrel__Endpoints__Https__Url = "https://my.app";
   Kestrel__Endpoints__Https__Certificate__Path = "/var/lib/acme/my.app/cert.pem";
   Kestrel__Endpoints__Https__Certificate__KeyPath = "/var/lib/acme/my.app/key.pem";
   Logging__LogLevel__Default = "Information";
   Logging__LogLevel__Microsoft__AspNetCore = "Warning"; # this does not actually work, not sure how to fix
   Authentication__Google__ClientId = "xxxyyyzzz.apps.googleusercontent.com";
   Authentication__Microsoft__ClientId = "aaaaaa-0000-aaaa-0000-aaaaaaaaaa";
   # secrets must be placed in /var/lib/myapp/appsettings.json
   # TODO email
   # TODO Stripe
   Stripe__Currency = "USD";
 };

};

``` [ACME](https://wiki.nixos.org/wiki/ACME)

.NET location: Not found

If running a .NET-build executable you get the above error, make sure the DOTNET_ROOT environment variable is set:

environment.sessionVariables = {
  DOTNET_ROOT = "${pkgs.dotnet-sdk}";
};

See : https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#net-sdk-and-cli-environment-variables

TargetFramework value was not recognized

error NETSDK1013: The TargetFramework value 'net6.0-windows' was not recognized. It may be misspelled. If not, then the TargetFrameworkIdentifier and/or TargetFrameworkVersion properties must be specified explicitly.

Wontfix: The project will build only on Windows.

NativeAOT

This is relevant for NixOS only.

nix-ld is needed:

{
  programs.nix-ld.enable = true;
}

Now we will need a bunch of native dependencies. Here's an example of a shell:

with import <nixpkgs> {};
pkgs.mkShell rec {

  dotnetPkg = 
    (with dotnetCorePackages; combinePackages [
      sdk_7_0
    ]);

  deps = [
    zlib
    zlib.dev
    openssl
    dotnetPkg
  ];

  NIX_LD_LIBRARY_PATH = lib.makeLibraryPath ([
    stdenv.cc.cc
  ] ++ deps);
  NIX_LD = "${pkgs.stdenv.cc.libc_bin}/bin/ld.so";
  nativeBuildInputs = [ 
  ] ++ deps;

  shellHook = ''
    DOTNET_ROOT="${dotnetPkg}";
  '';
}

Global Tools

Local installation of .NET global tools is fully supported and preferred when possible - more info in the Microsoft docs.

For globally installing .NET tools, search if they are available as Nix packages - they are packaged as any other normal .NET binary, using buildDotnetModule. For .NET tools with no source available, or those hard to build from source, buildDotnetGlobalTool is available. See dotnet nixpkgs manual for more info.

Note that Nix-packaged .NET tools use a special wrapper (toggled by useDotnetFromEnv option in buildDotnetModule) that automatically picks up .NET install from the user environment. If you want to use a different SDK version with a Nix-packaged .NET tools than the default, make sure the dotnet CLI of your wanted SDK version is installed and available.


Example: Running Rider with dotnet & PowerShell

Rider package

pkgs.jetbrains.rider

dotnet.nix

with import <nixpkgs> {};

mkShell {
  name = "dotnet-env";
  packages = [
    (with dotnetCorePackages; combinePackages [
      sdk_6_0
      sdk_7_0
      sdk_8_0
    ])
    powershell
  ];
}

To execute Rider

nix-shell ./dotnet.nix --run 'nohup rider &'

This can be added as an alias to your shell if you update the reference to an absolute address, such as location within your home directory. e.g. `~/nix/dotnet.nix`

Example: multi-SDK installation with local workload installation enabled

By default, workload installation will fail on NixOS, as dotnet will attempt to save it to $DOTNET_ROOT, which is inside the read-only Nix store.

Please visit the forum for an example of a multi-SDK installation with workload changed to install to home directory.

See also