Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add socket-based IPFS support #90157

Merged
merged 7 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
153 changes: 66 additions & 87 deletions nixos/modules/services/network-filesystems/ipfs.nix
Original file line number Diff line number Diff line change
@@ -1,69 +1,17 @@
{ config, lib, pkgs, ... }:
{ config, lib, pkgs, options, ... }:
with lib;
let
inherit (pkgs) ipfs runCommand makeWrapper;

cfg = config.services.ipfs;
opt = options.services.ipfs;

ipfsFlags = toString ([
(optionalString cfg.autoMount "--mount")
#(optionalString cfg.autoMigrate "--migrate")
(optionalString cfg.enableGC "--enable-gc")
(optionalString (cfg.serviceFdlimit != null) "--manage-fdlimit=false")
(optionalString (cfg.defaultMode == "offline") "--offline")
(optionalString (cfg.defaultMode == "norouting") "--routing=none")
] ++ cfg.extraFlags);

defaultDataDir = if versionAtLeast config.system.stateVersion "17.09" then
"/var/lib/ipfs" else
"/var/lib/ipfs/.ipfs";

# Wrapping the ipfs binary with the environment variable IPFS_PATH set to dataDir because we can't set it in the user environment
wrapped = runCommand "ipfs" { buildInputs = [ makeWrapper ]; preferLocalBuild = true; } ''
mkdir -p "$out/bin"
makeWrapper "${ipfs}/bin/ipfs" "$out/bin/ipfs" \
--set IPFS_PATH ${cfg.dataDir} \
--prefix PATH : /run/wrappers/bin
'';


commonEnv = {
environment.IPFS_PATH = cfg.dataDir;
path = [ wrapped ];
serviceConfig.User = cfg.user;
serviceConfig.Group = cfg.group;
};

baseService = recursiveUpdate commonEnv {
wants = [ "ipfs-init.service" ];
# NB: migration must be performed prior to pre-start, else we get the failure message!
preStart = optionalString cfg.autoMount ''
ipfs --local config Mounts.FuseAllowOther --json true
ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
'' + concatStringsSep "\n" (collect
isString
(mapAttrsRecursive
(path: value:
# Using heredoc below so that the value is never improperly quoted
''
read value <<EOF
${builtins.toJSON value}
EOF
ipfs --local config --json "${concatStringsSep "." path}" "$value"
'')
({ Addresses.API = cfg.apiAddress;
Addresses.Gateway = cfg.gatewayAddress;
Addresses.Swarm = cfg.swarmAddress;
} //
cfg.extraConfig))
);
serviceConfig = {
ExecStart = "${wrapped}/bin/ipfs daemon ${ipfsFlags}";
Restart = "on-failure";
RestartSec = 1;
} // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
};
in {

###### interface
Expand All @@ -88,7 +36,9 @@ in {

dataDir = mkOption {
type = types.str;
default = defaultDataDir;
default = if versionAtLeast config.system.stateVersion "17.09"
then "/var/lib/ipfs"
else "/var/lib/ipfs/.ipfs";
description = "The data dir for IPFS";
};

Expand All @@ -98,18 +48,6 @@ in {
description = "systemd service that is enabled by default";
};

/*
autoMigrate = mkOption {
type = types.bool;
default = false;
description = ''
Whether IPFS should try to migrate the file system automatically.

The daemon will need to be able to download a binary from https://ipfs.io to perform the migration.
'';
};
*/

autoMount = mkOption {
type = types.bool;
default = false;
Expand Down Expand Up @@ -199,13 +137,21 @@ in {
example = 64*1024;
};

startWhenNeeded = mkOption {
type = types.bool;
default = false;
description = "Whether to use socket activation to start IPFS when needed.";
};

};
};

###### implementation

config = mkIf cfg.enable {
environment.systemPackages = [ wrapped ];
environment.systemPackages = [ pkgs.ipfs ];
environment.variables.IPFS_PATH = cfg.dataDir;

programs.fuse = mkIf cfg.autoMount {
userAllowOther = true;
};
Expand Down Expand Up @@ -234,10 +180,14 @@ in {
"d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
];

systemd.services.ipfs-init = recursiveUpdate commonEnv {
systemd.packages = [ pkgs.ipfs ];

systemd.services.ipfs-init = {
description = "IPFS Initializer";

before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
environment.IPFS_PATH = cfg.dataDir;

path = [ pkgs.ipfs ];

script = ''
if [[ ! -f ${cfg.dataDir}/config ]]; then
Expand All @@ -251,34 +201,63 @@ in {
fi
'';

wantedBy = [ "default.target" ];

serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = cfg.user;
Group = cfg.group;
};
};

# TODO These 3 definitions possibly be further abstracted through use of a function
# like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
systemd.services.ipfs = {
path = [ "/run/wrappers" pkgs.ipfs ];
environment.IPFS_PATH = cfg.dataDir;

wants = [ "ipfs-init.service" ];
after = [ "ipfs-init.service" ];

systemd.services.ipfs = recursiveUpdate baseService {
description = "IPFS Daemon";
wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
after = [ "network.target" "ipfs-init.service" ];
conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
preStart = optionalString cfg.autoMount ''
ipfs --local config Mounts.FuseAllowOther --json true
ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
'' + concatStringsSep "\n" (collect
isString
(mapAttrsRecursive
(path: value:
# Using heredoc below so that the value is never improperly quoted
''
read value <<EOF
${builtins.toJSON value}
EOF
ipfs --local config --json "${concatStringsSep "." path}" "$value"
'')
({ Addresses.API = cfg.apiAddress;
Addresses.Gateway = cfg.gatewayAddress;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#addressesapi so all these can in fact be lists of addreses, which is good for when we want to do unix domain socket and tcp

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does it choose which one to use?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we discussed offline, but for posterity, the server will listen for all 3.)

Addresses.Swarm = cfg.swarmAddress;
} //
cfg.extraConfig))
);
serviceConfig = {
ExecStart = ["" "${pkgs.ipfs}/bin/ipfs daemon ${ipfsFlags}"];
User = cfg.user;
Group = cfg.group;
} // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
} // optionalAttrs (!cfg.startWhenNeeded) {
wantedBy = [ "default.target" ];
};

systemd.services.ipfs-offline = recursiveUpdate baseService {
description = "IPFS Daemon (offline mode)";
wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
after = [ "ipfs-init.service" ];
conflicts = [ "ipfs.service" "ipfs-norouting.service"];
systemd.sockets.ipfs-gateway = {
wantedBy = [ "sockets.target" ];
socketConfig.ListenStream = [ "" ]
++ lib.optional (cfg.gatewayAddress == opt.gatewayAddress.default) [ "127.0.0.1:8080" "[::1]:8080" ];
};

systemd.services.ipfs-norouting = recursiveUpdate baseService {
description = "IPFS Daemon (no routing mode)";
wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
after = [ "ipfs-init.service" ];
conflicts = [ "ipfs.service" "ipfs-offline.service"];
systemd.sockets.ipfs-api = {
wantedBy = [ "sockets.target" ];
socketConfig.ListenStream = [ "" "%t/ipfs.sock" ]
++ lib.optional (cfg.apiAddress == opt.apiAddress.default) [ "127.0.0.1:5001" "[::1]:5001" ];
};

};
Expand Down
7 changes: 7 additions & 0 deletions nixos/tests/ipfs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,12 @@ import ./make-test-python.nix ({ pkgs, ...} : {
)

machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")

ipfs_hash = machine.succeed(
"echo fnord2 | ipfs --api /unix/run/ipfs.sock add | awk '{ print $2 }'"
)
machine.succeed(
f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
)
'';
})
8 changes: 8 additions & 0 deletions pkgs/applications/networking/ipfs/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ buildGoModule rec {

vendorSha256 = null;

postInstall = ''
install -D misc/systemd/ipfs.service $out/etc/systemd/system/ipfs.service
install -D misc/systemd/ipfs-api.socket $out/etc/systemd/system/ipfs-api.socket
install -D misc/systemd/ipfs-gateway.socket $out/etc/systemd/system/ipfs-gateway.socket
substituteInPlace $out/etc/systemd/system/ipfs.service \
--replace /usr/bin/ipfs $out/bin/ipfs
'';

meta = with stdenv.lib; {
description = "A global, versioned, peer-to-peer filesystem";
homepage = "https://ipfs.io/";
Expand Down