nixos/i2pd: refactor with freeformed submodule (RFC42) (#225601)

This commit is contained in:
h7x4
2026-08-14 18:47:57 +00:00
committed by GitHub
5 changed files with 734 additions and 752 deletions

View File

@@ -225,6 +225,8 @@
- `boot.supportedFilesystems.ntfs` installs `ntfsprogs-plus` instead of `ntfs3g` on kernel version 7.1 and later, unless `boot.supportedFilesystems.ntfs-3g` is explicitly enabled.
- `services.i2pd` has been refactored to take [RFC42](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md)-compliant `settings`. In order to migrate, you will need to move existing config under `settings` and rename them in accordance with the [upstream config format](https://docs.i2pd.website/en/latest/user-guide/configuration/#available-options). In addition, `inTunnels` and `outTunnels` needs to be renamed to `serverTunnels` and `clientTunnels` respectively.
- The `programs.fuse` module, which provides the `fusermount3` executable and the `/etc/fuse.conf` config file, is now opt-in. The obligation to enable it has been shifted to its various consumers (e.g. gvfs, flatpak, appimage, sshfs). This can break fuse consumers at runtime, that don't explicitly declare that dependency with a module, e.g the mounting functionality in various backup tools (borg, restic, rclone, ...).
- `services.plausible` can now again seed an initial admin user declaratively via [`services.plausible.adminUser.email`](#opt-services.plausible.adminUser.email).

View File

@@ -194,7 +194,7 @@ in
redmine = 147;
#seeks = 148; # removed 2020-06-21
prosody = 149;
i2pd = 150;
# i2pd = 150; # dynamically allocated as of 2026-08-07
systemd-coredump = 151;
systemd-network = 152;
systemd-resolve = 153;
@@ -534,7 +534,7 @@ in
redmine = 147;
#seeks = 148; # removed 2020-06-21
prosody = 149;
i2pd = 150;
# i2pd = 150; # dynamically allocated as of 2026-08-07
systemd-network = 152;
systemd-resolve = 153;
systemd-timesync = 154;

File diff suppressed because it is too large Load Diff

View File

@@ -811,6 +811,7 @@ in
hub = runTest ./git/hub.nix;
hydra = runTest ./hydra;
i18n = runTest ./i18n.nix;
i2pd = runTest ./i2pd.nix;
i3wm = runTest ./i3wm.nix;
icecast = runTest ./icecast.nix;
icingaweb2 = runTest ./icingaweb2.nix;

210
nixos/tests/i2pd.nix Normal file
View File

@@ -0,0 +1,210 @@
{ lib, ... }:
{
name = "i2pd";
meta.maintainers = with lib.maintainers; [ h7x4 ];
nodes = {
server =
{ config, pkgs, ... }:
{
virtualisation.vlans = [ 1 ];
networking = {
useDHCP = false;
interfaces.eth1.useDHCP = false;
firewall.allowedTCPPorts = [ 12345 ];
firewall.allowedUDPPorts = [ 12345 ];
};
systemd.services."test-web-server" = {
wantedBy = [ "multi-user.target" ];
before = [ "i2pd.service" ];
serviceConfig = {
ExecStart = ''
${lib.getExe' pkgs.python3 "python3"} \
-m http.server 8080 \
--bind 127.0.0.1 \
--directory ${pkgs.writeTextDir "index.html" "hello world"}
'';
DynamicUser = true;
};
};
services.i2pd = {
enable = true;
settings = {
# Needed to retrieve b32 address
loglevel = "info";
# Avoid real I2P network ID (2), just in case
# there are any hidden assumptions tied to it.
netid = 77;
host = config.networking.primaryIPAddress;
port = 12345;
# Allow use of local addresses
reservedrange = false;
# No reseed infra available, and we seed netDb manually anyway
reseed.urls = "";
reseed.yggurls = "";
# "router" is the only other node reachable, so every tunnel
# is a single hop through it.
shareddest.inbound.length = 1;
shareddest.outbound.length = 1;
exploratory.inbound.length = 1;
exploratory.outbound.length = 1;
};
serverTunnels.testserver = {
host = "127.0.0.1";
port = 8080;
keys = "testserver-keys.dat";
inbound.length = 1;
outbound.length = 1;
};
};
};
router =
{ config, ... }:
{
virtualisation.vlans = [ 1 ];
networking = {
useDHCP = false;
interfaces.eth1.useDHCP = false;
firewall.allowedTCPPorts = [ 12345 ];
firewall.allowedUDPPorts = [ 12345 ];
};
services.i2pd = {
enable = true;
settings = {
loglevel = "info";
netid = 77;
host = config.networking.primaryIPAddress;
port = 12345;
reservedrange = false;
reseed.urls = "";
reseed.yggurls = "";
floodfill = true;
# "router" has no peer to hop through for its own pools.
shareddest.inbound.length = 0;
shareddest.outbound.length = 0;
exploratory.inbound.length = 0;
exploratory.outbound.length = 0;
};
};
};
client =
{ config, ... }:
{
virtualisation.vlans = [ 1 ];
networking = {
useDHCP = false;
interfaces.eth1.useDHCP = false;
};
# i2pd asserts this exists before it starts, the test script
# overwrites it with the server's actual address once known.
systemd.tmpfiles.rules = [
"f /run/i2pd-secrets/server-destination 0400 root root - unknown.b32.i2p"
];
services.i2pd = {
enable = true;
settings = {
loglevel = "info";
netid = 77;
host = config.networking.primaryIPAddress;
reservedrange = false;
reseed.urls = "";
reseed.yggurls = "";
httpproxy.inbound.length = 1;
httpproxy.outbound.length = 1;
shareddest.inbound.length = 1;
shareddest.outbound.length = 1;
exploratory.inbound.length = 1;
exploratory.outbound.length = 1;
};
clientTunnels.toServer = {
port = 10800;
destination._secret = "/run/i2pd-secrets/server-destination";
inbound.length = 1;
outbound.length = 1;
};
};
};
};
testScript =
# python
''
import re
start_all()
server.wait_for_unit("i2pd.service")
client.wait_for_unit("i2pd.service")
router.wait_for_unit("i2pd.service")
server.wait_for_file("/var/lib/i2pd/router.info")
client.wait_for_file("/var/lib/i2pd/router.info")
router.wait_for_file("/var/lib/i2pd/router.info")
with subtest("Exchange router info"):
server.wait_until_succeeds(
"journalctl -u i2pd -o cat --grep 'Local address \\S+ created'"
)
server_log_line = server.succeed(
"journalctl -u i2pd -o cat --grep 'Local address \\S+ created' --reverse -n 1"
)
server_b32_match = re.search(r"Local address (\S+) created", server_log_line)
assert server_b32_match is not None
server_b32 = server_b32_match.group(1)
client.succeed(
"mkdir -p /run/i2pd-secrets",
f"printf '%s' '{server_b32}.b32.i2p' > /run/i2pd-secrets/server-destination",
)
# We don't have any reseed infra available, so we manually seed each
# of server/client with "router"'s identity, and vice versa.
router.copy_from_machine("/var/lib/i2pd/router.info", "router-identity")
server.copy_from_machine("/var/lib/i2pd/router.info", "server-identity")
client.copy_from_machine("/var/lib/i2pd/router.info", "client-identity")
router_identity = str(router.out_dir / "router-identity" / "router.info")
server.copy_from_host(router_identity, "/var/lib/i2pd/netDb/r0/router.dat")
client.copy_from_host(router_identity, "/var/lib/i2pd/netDb/r0/router.dat")
router.copy_from_host(
str(server.out_dir / "server-identity" / "router.info"),
"/var/lib/i2pd/netDb/r0/server.dat",
)
router.copy_from_host(
str(client.out_dir / "client-identity" / "router.info"),
"/var/lib/i2pd/netDb/r0/client.dat",
)
client.systemctl("restart i2pd.service")
server.systemctl("restart i2pd.service")
router.systemctl("restart i2pd.service")
client.wait_for_unit("i2pd.service")
server.wait_for_unit("i2pd.service")
router.wait_for_unit("i2pd.service")
client.wait_for_open_port(4444)
server.wait_for_unit("test-web-server.service")
with subtest("Request content using the HTTP proxy"):
# This is intended to keep on failing until the client eventually receives
# a LeaseSet from the server.
client.wait_until_succeeds(
f"curl --fail -x 127.0.0.1:4444 http://{server_b32}.b32.i2p/ | grep -q 'hello world'",
)
with subtest("Request content using a port-forwarding tunnel"):
client.wait_until_succeeds(
"curl --fail http://127.0.0.1:10800/ | grep -q 'hello world'",
)
'';
}