diff --git a/src/libstore/ipfs-binary-cache-store.cc b/src/libstore/ipfs-binary-cache-store.cc index 8de3b035934..43a5a664de1 100644 --- a/src/libstore/ipfs-binary-cache-store.cc +++ b/src/libstore/ipfs-binary-cache-store.cc @@ -21,6 +21,7 @@ class IPFSBinaryCacheStore : public BinaryCacheStore auto state(_state.lock()); return state->ipfsPath; } + std::string initialIpfsPath; std::optional optIpnsPath; struct State @@ -41,9 +42,10 @@ class IPFSBinaryCacheStore : public BinaryCacheStore if (cacheUri.back() == '/') cacheUri.pop_back(); - if (hasPrefix(cacheUri, "ipfs://")) - state->ipfsPath = "/ipfs/" + std::string(cacheUri, 7); - else if (hasPrefix(cacheUri, "ipns://")) + if (hasPrefix(cacheUri, "ipfs://")) { + initialIpfsPath = "/ipfs/" + std::string(cacheUri, 7); + state->ipfsPath = initialIpfsPath; + } else if (hasPrefix(cacheUri, "ipns://")) optIpnsPath = "/ipns/" + std::string(cacheUri, 7); else throw Error("unknown IPNS URI '%s'", cacheUri); @@ -64,16 +66,8 @@ class IPFSBinaryCacheStore : public BinaryCacheStore // Resolve the IPNS name to an IPFS object if (optIpnsPath) { auto ipnsPath = *optIpnsPath; - debug("Resolving IPFS object of '%s', this could take a while.", ipnsPath); - auto uri = daemonUri + "/api/v0/name/resolve?offline=true&arg=" + getFileTransfer()->urlEncode(ipnsPath); - FileTransferRequest request(uri); - request.post = true; - request.tries = 1; - auto res = getFileTransfer()->download(request); - auto json = nlohmann::json::parse(*res.data); - if (json.find("Path") == json.end()) - throw Error("daemon for IPFS is not running properly"); - state->ipfsPath = json["Path"]; + initialIpfsPath = resolveIPNSName(ipnsPath, true); + state->ipfsPath = initialIpfsPath; } } @@ -92,6 +86,18 @@ class IPFSBinaryCacheStore : public BinaryCacheStore protected: + // Given a ipns path, checks if it corresponds to a DNSLink path, and in + // case returns the domain + std::optional isDNSLinkPath(std::string path) { + if (path.find("/ipns/") != 0) + throw Error("The provided path is not a ipns path"); + auto subpath = std::string(path, 6); + if (subpath.find(".") != std::string::npos) { + return subpath; + } + return std::nullopt; + } + bool fileExists(const std::string & path) override { auto uri = daemonUri + "/api/v0/object/stat?arg=" + getFileTransfer()->urlEncode(getIpfsPath() + "/" + path); @@ -111,14 +117,50 @@ class IPFSBinaryCacheStore : public BinaryCacheStore } } + // Resolve the IPNS name to an IPFS object + std::string resolveIPNSName(std::string ipnsPath, bool offline) { + debug("Resolving IPFS object of '%s', this could take a while.", ipnsPath); + auto uri = daemonUri + "/api/v0/name/resolve?offline=" + (offline?"true":"false") + "&arg=" + getFileTransfer()->urlEncode(ipnsPath); + FileTransferRequest request(uri); + request.post = true; + request.tries = 1; + auto res = getFileTransfer()->download(request); + auto json = nlohmann::json::parse(*res.data); + if (json.find("Path") == json.end()) + throw Error("daemon for IPFS is not running properly"); + return json["Path"]; + } + // IPNS publish can be slow, we try to do it rarely. void sync() override { - if (!optIpnsPath) - return; + auto state(_state.lock()); + + if (!optIpnsPath) { + throw Error("We don't have an ipns path and the current ipfs address doesn't match the initial one.\n current: %s\n initial: %s", + state->ipfsPath, initialIpfsPath); + } + auto ipnsPath = *optIpnsPath; - auto state(_state.lock()); + auto resolvedIpfsPath = resolveIPNSName(ipnsPath, false); + if (resolvedIpfsPath != initialIpfsPath) { + throw Error("The IPNS hash or DNS link %s resolves now to something different from the value it had when Nix was started;\n wanted: %s\n got %s\nPerhaps something else updated it in the meantime?", + initialIpfsPath, resolvedIpfsPath); + } + + if (resolvedIpfsPath == state->ipfsPath) { + printMsg(lvlInfo, "The hash is already up to date, nothing to do"); + return; + } + + // Now, we know that paths are not up to date but also not changed due to updates in DNS or IPNS hash. + auto optDomain = isDNSLinkPath(ipnsPath); + if (optDomain) { + auto domain = *optDomain; + throw Error("The provided ipns path is a DNSLink, and syncing those is not supported.\n Current DNSLink: %s\nYou should update your DNS settings" + , domain); + } debug("Publishing '%s' to '%s', this could take a while.", state->ipfsPath, ipnsPath);