Introduction #

Recently I’ve decided to move most of my hardware over to NixOS. It’s been an amazing experience being able to configure everything OS related from one central point.

A few years ago, I discovered i2p. I really liked the idea of it; a different darknet than tor with different kinds of people on it – more blog oriented. At the time I ran a node on a Raspberry Pi running Debian. I connected to it using an ssh tunnel over Yggdrasil and used an Arkenfox hardened Firefox profile to browse around.

That Raspberry Pi no longer serves as a (har) server. Instead I now have a NixOS machine churning away. As with a lot of NixOS topics, there isn’t a lot of documentation on configuring i2p. Though truth be told, when comparing it to the config files I had on Debian, a lot of it was rather self explanatory and easy to implement in a Nix module.

What follows is my current setup on my desktop. Initially I set this up as a test environment, but it works well to the point where I don’t really feel the need to move it to another machine.

The i2p module #

I’ll be using the i2pd implementation of the i2p daemon as it requires less resources than the java implementation and has less features out of the box that need configuring.

Since it is safe to assume that the i2p stack is frequently under attack as it is a darknet protocol, I decided to run it inside of a Nix container. If you don’t want to do this, only declare a services.i2pd instance.

With that out of the way, let’s declare a basic container environment named i2pd-container.

{ ... }: {
  containers.i2pd-container = {
    autoStart = true;
    config = { ... }: {

      system.stateVersion = "23.05"; # If you don't add a state version, nix will complain at every rebuild

    };
  }
}

From here there are a couple of things you can do with your i2p configuration. My initial goal was to be able to search the i2p net. To that end, the following will suffice.

...
      # Exposing the nessecary ports in order to interact with i2p from outside the container
      networking.firewall.allowedTCPPorts = [
        7070 # default web interface port
        4447 # default socks proxy port
        4444 # default http proxy port
      ];

      services.i2pd = {
        enable = true;
        address = "127.0.0.1"; # you may want to set this to 0.0.0.0 if you are planning to use an ssh tunnel
        proto = {
          http.enable = true;
          socksProxy.enable = true;
          httpProxy.enable = true;
        };
      };
...

The SOCKS proxy isn’t strictly required, but needed when wanting to use anything else than an HTTP connection to interact with i2p.

Torrents #

After a little while, I wanted to see what kinds of linux distros the i2p network has to offer, I also enabled services.i2pd.proto.sam and added the corresponding default port to the container’s firewall in order to get XD (which can be found in the Nix repo) to behave properly:

...
      networking.firewall.allowedTCPPorts = [ 7656 ];
      services.i2pd.proto.sam.enable = true;
...
Click here to see everything together
{ ... }: {
  containers.i2pd-container = {
    autoStart = true;
    config = { ... }: {

      system.stateVersion = "23.05"; # If you don't add a state version, nix will complain at every rebuild

      # Exposing the nessecary ports in order to interact with i2p from outside the container
      networking.firewall.allowedTCPPorts = [
        7656 # default sam port
        7070 # default web interface port
        4447 # default socks proxy port
        4444 # default http proxy port
      ];

      services.i2pd = {
        enable = true;
        address = "127.0.0.1"; # you may want to set this to 0.0.0.0 if you are planning to use an ssh tunnel
        proto = {
          http.enable = true;
          socksProxy.enable = true;
          httpProxy.enable = true;
          sam.enable = true;
        };
      };
    };
  }
}

Tying it to /etc/nixos/configuration.nix #

I have a perhaps somewhat over engineered structure when it comes to adding my modules to /etc/nixos/configuration.nix.

I have a /etc/nixos/services directory with my own modules, one of which being services.nix. services.nix imports all the other modules found in the directory. From there, /etc/nixos/configuration.nix has ./services/services.nix in it’s import section:

/etc/nixos/configuration.nix:

{ pkgs, config, ... }: {
  imports = [
    ./hardware-configuration.nix
    ./services/services.nix
    ...
  ];
  ...
}

/etc/nixos/services/services.nix:

{ ... }: {
  imports = [
    ./i2pd-container.nix
    ...
  ];
}

Browser profile #

The next step was to set up a browser profile. Since Mullvad recently released a browser, I decided to use it as a basis for my i2p browser.

To create a new profile in Firefox or any of it’s forks, type/paste about:profiles in the URL bar and click the “Create a New Profile” button at the top. You will then be prompted for a name. I named mine “i2p”.

Open this profile in a new session and navigate to about:preferencess#general and scroll all the way down to “Network Settings”. In there, check “Manual proxy configuration” and “Also use this proxy for HTTPS”. From there, fill in the address you used in services.i2pd.address or the address of the machine you’re tying it to if you’re using an ssh tunnel. For ports, use 4444 for the HTTP proxy and 4447 for the SOCKS proxy.

Then move over to about:preferencess#privacy and scroll down to “Security”. I recommend putting it to “Safest”. If you really need to run any JavaScript, you can always add the NoScript icon to your window bar.

Then scroll a bit further down to “HTTPS-Only Mode” and disable it, as the i2p network doesn’t use browser level encryption and thus it would only get in the way.

As a final step, go to about:config and set keyword.enabled to false. This isn’t strictly necessary, but will make your like a whole lot easier when typing .i2p domains and not having your browser attempt – and fail – to query the default search engine.

.desktop entry via home-manager #

If you’re using the home-manager, you can quite easily create a .desktop file to make starting your i2p profile more convenient. This is assuming that have either a full on desktop environment or use some other way of parsing .desktop entries such as rofi’s drun mode.

I have the following in my $HOME/.config/home-manager/home.nix:

xdg.desktopEntries = {
  i2p-browser = {
    name = "i2p Browser";
    genericName = "Web Browser";
    exec = "${pkgs.mullvad-browser}/bin/mullvad-browser -p i2p";
  };
};

Firefox and it’s forks allow you to start a session with a profile using the -p parameter. Similarly the -P (that is, an upper case ‘P’ rather than lower case) parameter brings up a profiles menu that allows you to easily change the default profile when running the binary the next time.

Using everything #

After starting the container, wait at least 15 minutes for i2pd to build a few tunnels before attempting to connect to anything. Once you have, open your i2p browser profile and you should be able to browse the i2p net. Don’t expect things to be fast however, i2p is a much slower network in my experience than Tor.

Some pages to get you started: