├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── open_an_issue.md ├── actions │ ├── check-for-kubo-release │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ └── publish │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh ├── dependabot.yml └── workflows │ ├── generated-pr.yml │ ├── main.yml │ ├── stale.yml │ └── test.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── ipfs ├── package.json ├── src ├── download.js ├── go-platform.js ├── index.js ├── post-install.js └── types.d.ts ├── test ├── download.js ├── fixtures │ ├── clean.js │ └── example-project │ │ └── package.json ├── install.js └── path.js └── tsconfig.json /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Getting Help on IPFS 4 | url: https://ipfs.io/help 5 | about: All information about how and where to get help on IPFS. 6 | - name: IPFS Official Forum 7 | url: https://discuss.ipfs.io 8 | about: Please post general questions, support requests, and discussions here. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/open_an_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Open an issue 3 | about: Only for actionable issues relevant to this repository. 4 | title: '' 5 | labels: need/triage 6 | assignees: '' 7 | 8 | --- 9 | 20 | -------------------------------------------------------------------------------- /.github/actions/check-for-kubo-release/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | LABEL "com.github.actions.name"="Update and publish" 4 | LABEL "com.github.actions.description"="Publish new version when a new kubo version is relased" 5 | LABEL "com.github.actions.icon"="rss" 6 | LABEL "com.github.actions.color"="green" 7 | 8 | COPY entrypoint.sh /entrypoint.sh 9 | ENTRYPOINT ["/entrypoint.sh"] 10 | -------------------------------------------------------------------------------- /.github/actions/check-for-kubo-release/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Check for kubo release' 2 | runs: 3 | using: 'docker' 4 | image: 'Dockerfile' 5 | outputs: 6 | publish: 7 | description: 'Whether to publish a new version' 8 | -------------------------------------------------------------------------------- /.github/actions/check-for-kubo-release/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | echo '💫 Checking https://dist.ipfs.tech/kubo/versions for new releases...' 5 | 6 | # The version in packge.json e.g. "0.4.20" 7 | CURRENT=`node -e 'console.log(require("./package.json").version)'` 8 | # The latest version on dist.ipfs.tech e.g. "0.4.21" 9 | LATEST=`curl --silent https://dist.ipfs.tech/kubo/versions | tail -n 1 | cut -c 2-` 10 | 11 | # Verify $LATEST is valid semver! 12 | if ! npx semver $LATEST; then 13 | echo "⚠️ Ignoring version $LATEST - Invalid SemVer string" 14 | exit 1 15 | fi 16 | 17 | if [[ "$CURRENT" != "$LATEST" ]]; then 18 | echo "🎉 New release exists $LATEST" 19 | 20 | echo "publish=true" >> $GITHUB_OUTPUT 21 | else 22 | echo "💤 $CURRENT is the latest release. Going back to sleep" 23 | 24 | echo "publish=false" >> $GITHUB_OUTPUT 25 | fi 26 | -------------------------------------------------------------------------------- /.github/actions/publish/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | LABEL "com.github.actions.name"="Version and publish" 4 | LABEL "com.github.actions.description"="npm version and publish with new kubo version" 5 | LABEL "com.github.actions.icon"="box" 6 | LABEL "com.github.actions.color"="green" 7 | 8 | COPY entrypoint.sh /entrypoint.sh 9 | ENTRYPOINT ["/entrypoint.sh"] 10 | -------------------------------------------------------------------------------- /.github/actions/publish/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish release' 2 | runs: 3 | using: 'docker' 4 | image: 'Dockerfile' 5 | -------------------------------------------------------------------------------- /.github/actions/publish/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # Set up npm home, so `npm publish` works. 5 | # https://github.com/actions/npm/blob/59b64a598378f31e49cb76f27d6f3312b582f680/entrypoint.sh 6 | if [ -n "$NPM_AUTH_TOKEN" ]; then 7 | # Respect NPM_CONFIG_USERCONFIG if it is provided, default to $HOME/.npmrc 8 | NPM_CONFIG_USERCONFIG="${NPM_CONFIG_USERCONFIG-"$HOME/.npmrc"}" 9 | NPM_REGISTRY_URL="${NPM_REGISTRY_URL-registry.npmjs.org}" 10 | NPM_STRICT_SSL="${NPM_STRICT_SSL-true}" 11 | NPM_REGISTRY_SCHEME="https" 12 | if ! $NPM_STRICT_SSL 13 | then 14 | NPM_REGISTRY_SCHEME="http" 15 | fi 16 | 17 | # Allow registry.npmjs.org to be overridden with an environment variable 18 | printf "//%s/:_authToken=%s\\nregistry=%s\\nstrict-ssl=%s" "$NPM_REGISTRY_URL" "$NPM_AUTH_TOKEN" "${NPM_REGISTRY_SCHEME}://$NPM_REGISTRY_URL" "${NPM_STRICT_SSL}" > "$NPM_CONFIG_USERCONFIG" 19 | 20 | chmod 0600 "$NPM_CONFIG_USERCONFIG" 21 | fi 22 | 23 | # The version in packge.json e.g. "0.4.20" 24 | CURRENT=`node -e 'console.log(require("./package.json").version)'` 25 | # The latest version on dist.ipfs.tech e.g. "0.4.21" 26 | LATEST=`curl --silent https://dist.ipfs.tech/kubo/versions | tail -n 1 | cut -c 2-` 27 | 28 | # Verify $LATEST is valid semver! 29 | if ! npx semver $LATEST; then 30 | echo "⚠️ Ignoring version $LATEST - Invalid SemVer string" 31 | exit 1 32 | fi 33 | 34 | if [[ "$CURRENT" != "$LATEST" ]]; then 35 | 36 | # If the version contains a dash it's a pre-release, e.g "0.4.21-rc3" 37 | # Publish pre-releases under the @next tag and releases @latest tag. 38 | if [[ $LATEST =~ "-" ]]; then 39 | NPM_DIST_TAG='next' 40 | echo "🧪 Found new kubo pre-release $LATEST@$NPM_DIST_TAG" 41 | else 42 | NPM_DIST_TAG='latest' 43 | echo "🎉 Found new kubo release $LATEST@$NPM_DIST_TAG" 44 | fi 45 | 46 | git config --global --add safe.directory /github/workspace 47 | 48 | # The workspace starts as a detached commit for scheduled builds... 49 | git rev-parse --abbrev-ref HEAD 50 | git checkout master 51 | 52 | # post-install rewrites bin/ipfs so undo that change 53 | git checkout -- bin/ipfs 54 | 55 | # Set sensible commit info 56 | git config --global user.name "${GITHUB_ACTOR}" 57 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" 58 | 59 | 60 | npm install 61 | npm version $LATEST 62 | npm publish --access public --tag $NPM_DIST_TAG 63 | echo "📦 Published $LATEST to npm as kubo@$NPM_DIST_TAG" 64 | 65 | git push -u origin master 66 | git push --tags 67 | echo "👍 Pushed changes back to master" 68 | 69 | else 70 | echo "💤 $CURRENT is the latest release. Going back to sleep" 71 | # neutral github action exit... not good, not bad. 72 | # https://developer.github.com/actions/creating-github-actions/accessing-the-runtime-environment/#exit-codes-and-statuses 73 | exit 78 74 | fi 75 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Release to npm 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 * * * *' 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Check for new kubo version 14 | id: check 15 | uses: ./.github/actions/check-for-kubo-release 16 | - name: Set up node 17 | if: steps.check.outputs.publish == 'true' 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | - name: Install 22 | if: steps.check.outputs.publish == 'true' 23 | run: npm install 24 | - name: Test 25 | if: steps.check.outputs.publish == 'true' 26 | run: npm test 27 | - name: Publish 28 | if: steps.check.outputs.publish == 'true' 29 | uses: ./.github/actions/publish 30 | env: 31 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ main, master ] 6 | pull_request: 7 | branches: [ main, master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [windows-latest, macos-latest, ubuntu-latest] 15 | node-version: [20.x] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - uses: gozala/typescript-error-reporter-action@v1.0.9 23 | - run: npm run build --if-present 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Dependency directory 6 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 7 | node_modules 8 | dist 9 | 10 | # while testing npm5 11 | package-lock.json 12 | 13 | # Where the binaries go 14 | go-ipfs 15 | kubo 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | sudo: false 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm test 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2018, Protocol Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Kubo logo 4 |
5 | Kubo: IPFS Implementation in GO 6 |
7 |
8 |

9 | 10 |

Install Kubo (previously known as "go-ipfs") from NPM

11 | 12 |

13 | Official Part of IPFS Project 14 | Discourse Forum 15 | Matrix 16 | ci 17 | npm version 18 | npm downloads 19 |

20 | 21 | ## Table of Contents 22 | 23 | - [Install](#install) 24 | - [Usage](#usage) 25 | - [Development](#development) 26 | - [Publish a new version](#publish-a-new-version) 27 | - [Contribute](#contribute) 28 | - [License](#license) 29 | 30 | ## Install 31 | 32 | Install the latest [Kubo](https://github.com/ipfs/kubo/) (go-ipfs) binary: 33 | 34 | ```sh 35 | # Install globally 36 | > npm install -g kubo 37 | > ipfs version 38 | ipfs version v0.23.0 39 | 40 | # Install locally 41 | > npm install kubo 42 | > ./node_modules/.bin/ipfs 43 | ipfs version v0.23.0 44 | ``` 45 | 46 | ## Usage 47 | 48 | This module downloads Kubo (go-ipfs) binaries from https://dist.ipfs.tech into your project. 49 | 50 | It will download the kubo version that matches the npm version of this module. So depending on `kubo@0.23.0` will install `kubo v0.23.0` for your current system architecture, in to your project at `node_modules/kubo/kubo/ipfs` and additional symlink to it at `node_modules/kubo/bin/ipfs`. 51 | 52 | On Windows, `ipfs.exe` file is used, and if the symlink can't be created under a regular user, a copy of `ipfs.exe` is created instead. 53 | 54 | After downloading you can find out the path of the installed binary by calling the `path` function exported by this module: 55 | 56 | ```javascript 57 | import { path } from 'kubo' 58 | 59 | console.info('kubo is installed at', path()) 60 | ``` 61 | 62 | An error will be thrown if the path to the binary cannot be resolved. 63 | 64 | ### Caching 65 | 66 | Downloaded archives are placed in OS-specific cache directory which can be customized by setting `NPM_KUBO_CACHE` in env. 67 | 68 | ### Overriding with `KUBO_BINARY` env 69 | 70 | If the `KUBO_BINARY` env variable is set at runtime this will override the path of the binary used. 71 | 72 | This must point to the file, not the directory containing the file. 73 | 74 | ## Development 75 | 76 | **Warning**: the file `bin/ipfs` is a placeholder, when downloading stuff, it gets replaced. so if you run `node install.js` it will then be dirty in the git repo. **Do not commit this file**, as then you would be commiting a big binary and publishing it to npm. A pre-commit hook exists and should protect against this, but better safe than sorry. 77 | 78 | ### Publish a new version 79 | 80 | You should be able to just run `./publish.sh` for example: 81 | 82 | ```sh 83 | > ./publish.sh 84 | usage ./publish.sh 85 | publish a version of kubo to npm 86 | 87 | > ./publish.sh 0.3.11 88 | ``` 89 | 90 | This will: 91 | 92 | - check the version is indeed a tag in https://github.com/ipfs/kubo 93 | - check the size of `bin/ipfs` is right (must be the checked in file) 94 | - update the version numbers in `package.json` and `README.md` 95 | - `git commit` the changes 96 | - push to https://github.com/ipfs/npm-kubo 97 | - publish to `kubo@$version` to https://npmjs.com/package/kubo 98 | 99 | Open an issue in the repo if you run into trouble. 100 | 101 | ### Publish a new version of this module with exact same kubo version 102 | 103 | If some problem happens, and you need to publish a new version of this module targetting _the same_ kubo version, then please follow this convention: 104 | 105 | 1. **Clean up bad stuff:** unpublish all modules with this exact same `` 106 | 2. **Add a "hacky" version suffix:** use version: `-hacky` 107 | 3. **Publish version:** publish the module. Since it's the only one with the kubo version, then it should be installed. 108 | 109 | > Why do this? 110 | 111 | Well, if you previously published npm module `kubo@0.4.0` and there was a problem, we now must publish a different version, but we want to keep the version number the same. so the strategy is to publish as `kubo@0.4.0-hacky1`, and unpublish `kubo@0.4.0`. 112 | 113 | > Why `-hacky`? 114 | 115 | Because it is unlikely to be a legitimate kubo version, and we want to support kubo versions like `floodsub-1` etc. 116 | 117 | > Do i have to say `-hacky` or can i just use `-`? 118 | 119 | `-` won't work, as [link-ipfs.js](./link-ipfs.js) expects `-hacky`. If you want to 120 | change the convention, go for it, and update this readme accordingly. 121 | 122 | ## Contribute 123 | 124 | Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/npm-kubo/issues)! 125 | 126 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 127 | 128 | [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) 129 | 130 | ## License 131 | 132 | [MIT](LICENSE) 133 | -------------------------------------------------------------------------------- /bin/ipfs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log('IPFS is not installed.') 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubo", 3 | "version": "0.35.0", 4 | "description": "Install the latest Kubo (go-ipfs) binary", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "postinstall": "node src/post-install.js", 8 | "restore-bin": "git reset -- bin/ipfs && git checkout -- bin/ipfs", 9 | "test": "tape test/*.js | tap-spec", 10 | "lint": "tsc --noEmit && standard", 11 | "prepublishOnly": "tsc" 12 | }, 13 | "pre-commit": "restore-bin", 14 | "bin": { 15 | "ipfs": "bin/ipfs" 16 | }, 17 | "files": [ 18 | "bin", 19 | "dist", 20 | "src", 21 | "test", 22 | "LICENSE", 23 | "package.json", 24 | "README.md", 25 | "tsconfig.json" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/ipfs/npm-kubo.git" 30 | }, 31 | "keywords": [ 32 | "ipfs", 33 | "install" 34 | ], 35 | "author": "Protocol Labs, Inc.", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/ipfs/npm-kubo/issues" 39 | }, 40 | "homepage": "https://github.com/ipfs/npm-kubo", 41 | "types": "./src/types.d.ts", 42 | "devDependencies": { 43 | "@types/got": "^9.6.12", 44 | "@types/gunzip-maybe": "^1.4.0", 45 | "@types/tar-fs": "^2.0.1", 46 | "@types/unzip-stream": "^0.3.1", 47 | "execa": "^4.0.1", 48 | "fs-extra": "^9.0.0", 49 | "pre-commit": "^1.2.2", 50 | "standard": "^13.1.0", 51 | "tap-spec": "^5.0.0", 52 | "tape": "^4.13.2", 53 | "tape-promise": "^4.0.0", 54 | "typescript": "^4.3.5" 55 | }, 56 | "dependencies": { 57 | "cachedir": "^2.3.0", 58 | "got": "^11.7.0", 59 | "gunzip-maybe": "^1.4.2", 60 | "hasha": "^5.2.2", 61 | "pkg-conf": "^3.1.0", 62 | "tar-fs": "^2.1.0", 63 | "unzip-stream": "^0.3.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/download.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | Download kubo distribution package for desired version, platform and architecture, 5 | and unpack it to a desired output directory. 6 | 7 | API: 8 | download(, , , ) 9 | 10 | Defaults: 11 | kubo version: value in package.json/kubo/version 12 | kubo platform: the platform this program is run from 13 | kubo architecture: the architecture of the hardware this program is run from 14 | kubo install path: './kubo' 15 | */ 16 | const goenv = require('./go-platform') 17 | const gunzip = require('gunzip-maybe') 18 | const got = require('got').default 19 | const path = require('path') 20 | const tarFS = require('tar-fs') 21 | const unzip = require('unzip-stream') 22 | const pkgConf = require('pkg-conf') 23 | // @ts-ignore no types 24 | const cachedir = require('cachedir') 25 | const pkg = require('../package.json') 26 | const fs = require('fs') 27 | const hasha = require('hasha') 28 | const cproc = require('child_process') 29 | const isWin = process.platform === 'win32' 30 | 31 | /** 32 | * avoid expensive fetch if file is already in cache 33 | * @param {string} url 34 | */ 35 | async function cachingFetchAndVerify (url) { 36 | const cacheDir = process.env.NPM_KUBO_CACHE || process.env.NPM_GO_IPFS_CACHE || cachedir('npm-kubo') 37 | const filename = url.split('/').pop() 38 | 39 | if (!filename) { 40 | throw new Error('Invalid URL') 41 | } 42 | 43 | const cachedFilePath = path.join(cacheDir, filename) 44 | const cachedHashPath = `${cachedFilePath}.sha512` 45 | 46 | if (!fs.existsSync(cacheDir)) { 47 | fs.mkdirSync(cacheDir, { recursive: true }) 48 | } 49 | if (!fs.existsSync(cachedFilePath)) { 50 | console.info(`Downloading ${url} to ${cacheDir}`) 51 | // download file 52 | fs.writeFileSync(cachedFilePath, await got(url).buffer()) 53 | console.info(`Downloaded ${url}`) 54 | 55 | // ..and checksum 56 | console.info(`Downloading ${filename}.sha512`) 57 | fs.writeFileSync(cachedHashPath, await got(`${url}.sha512`).buffer()) 58 | console.info(`Downloaded ${filename}.sha512`) 59 | } else { 60 | console.info(`Found ${cachedFilePath}`) 61 | } 62 | 63 | console.info(`Verifying ${filename}.sha512`) 64 | 65 | const digest = Buffer.alloc(128) 66 | const fd = fs.openSync(cachedHashPath, 'r') 67 | fs.readSync(fd, digest, 0, digest.length, 0) 68 | fs.closeSync(fd) 69 | const expectedSha = digest.toString('utf8') 70 | const calculatedSha = await hasha.fromFile(cachedFilePath, { encoding: 'hex', algorithm: 'sha512' }) 71 | if (calculatedSha !== expectedSha) { 72 | console.log(`Expected SHA512: ${expectedSha}`) 73 | console.log(`Calculated SHA512: ${calculatedSha}`) 74 | throw new Error(`SHA512 of ${cachedFilePath}' (${calculatedSha}) does not match expected value from ${cachedFilePath}.sha512 (${expectedSha})`) 75 | } 76 | console.log(`OK (${expectedSha})`) 77 | 78 | return fs.createReadStream(cachedFilePath) 79 | } 80 | 81 | /** 82 | * @param {string} url 83 | * @param {string} installPath 84 | * @param {import('stream').Readable} stream 85 | */ 86 | function unpack (url, installPath, stream) { 87 | return new Promise((resolve, reject) => { 88 | if (url.endsWith('.zip')) { 89 | return stream.pipe( 90 | unzip 91 | .Extract({ path: installPath }) 92 | .on('close', resolve) 93 | .on('error', reject) 94 | ) 95 | } 96 | 97 | return stream 98 | .pipe(gunzip()) 99 | .pipe( 100 | tarFS 101 | .extract(installPath) 102 | .on('finish', resolve) 103 | .on('error', reject) 104 | ) 105 | }) 106 | } 107 | 108 | /** 109 | * @param {string} [version] 110 | * @param {string} [platform] 111 | * @param {string} [arch] 112 | * @param {string} [installPath] 113 | */ 114 | function cleanArguments (version, platform, arch, installPath) { 115 | const conf = pkgConf.sync('kubo', { 116 | cwd: process.env.INIT_CWD || process.cwd(), 117 | defaults: { 118 | version: 'v' + pkg.version.replace(/-[0-9]+/, ''), 119 | distUrl: 'https://dist.ipfs.tech' 120 | } 121 | }) 122 | 123 | return { 124 | version: process.env.TARGET_VERSION || version || conf.version, 125 | platform: process.env.TARGET_OS || platform || goenv.GOOS, 126 | arch: process.env.TARGET_ARCH || arch || goenv.GOARCH, 127 | distUrl: process.env.KUBO_DIST_URL || process.env.GO_IPFS_DIST_URL || conf.distUrl, 128 | installPath: installPath ? path.resolve(installPath) : process.cwd() 129 | } 130 | } 131 | 132 | /** 133 | * @param {string} version 134 | * @param {string} distUrl 135 | */ 136 | async function ensureVersion (version, distUrl) { 137 | console.info(`${distUrl}/kubo/versions`) 138 | const versions = (await got(`${distUrl}/kubo/versions`).text()).trim().split('\n') 139 | 140 | if (versions.indexOf(version) === -1) { 141 | throw new Error(`Version '${version}' not available`) 142 | } 143 | } 144 | 145 | /** 146 | * @param {string} version 147 | * @param {string} platform 148 | * @param {string} arch 149 | * @param {string} distUrl 150 | */ 151 | async function getDownloadURL (version, platform, arch, distUrl) { 152 | await ensureVersion(version, distUrl) 153 | 154 | const data = await got(`${distUrl}/kubo/${version}/dist.json`).json() 155 | 156 | if (!data.platforms[platform]) { 157 | throw new Error(`No binary available for platform '${platform}'`) 158 | } 159 | 160 | if (!data.platforms[platform].archs[arch]) { 161 | throw new Error(`No binary available for arch '${arch}'`) 162 | } 163 | 164 | const link = data.platforms[platform].archs[arch].link 165 | return `${distUrl}/kubo/${version}${link}` 166 | } 167 | 168 | /** 169 | * @param {object} options 170 | * @param {string} options.version 171 | * @param {string} options.platform 172 | * @param {string} options.arch 173 | * @param {string} options.installPath 174 | * @param {string} options.distUrl 175 | */ 176 | async function download ({ version, platform, arch, installPath, distUrl }) { 177 | const url = await getDownloadURL(version, platform, arch, distUrl) 178 | const data = await cachingFetchAndVerify(url) 179 | 180 | await unpack(url, installPath, data) 181 | console.info(`Unpacked ${installPath}`) 182 | 183 | return path.join(installPath, 'kubo', `ipfs${platform === 'windows' ? '.exe' : ''}`) 184 | } 185 | 186 | /** 187 | * @param {object} options 188 | * @param {string} options.depBin 189 | * @param {string} options.version 190 | */ 191 | async function link ({ depBin, version }) { 192 | let localBin = path.resolve(path.join(__dirname, '..', 'bin', 'ipfs')) 193 | 194 | if (isWin) { 195 | if (fs.existsSync(localBin)) { 196 | fs.unlinkSync(localBin) 197 | } 198 | localBin += '.exe' 199 | } 200 | 201 | if (!fs.existsSync(depBin)) { 202 | throw new Error('ipfs binary not found. maybe kubo did not install correctly?') 203 | } 204 | 205 | if (fs.existsSync(localBin)) { 206 | fs.unlinkSync(localBin) 207 | } 208 | 209 | console.info('Linking', depBin, 'to', localBin) 210 | try { 211 | fs.symlinkSync(depBin, localBin) 212 | } catch (err) { 213 | // Try to recover when creating symlink on modern Windows fails (https://github.com/ipfs/npm-kubo/issues/68) 214 | if (isWin && typeof err === 'object' && err !== null && 'code' in err && err.code === 'EPERM') { 215 | console.info('Symlink creation failed due to insufficient privileges. Attempting to copy file instead...') 216 | try { 217 | fs.copyFileSync(depBin, localBin) 218 | console.info('Copying', depBin, 'to', localBin) 219 | } catch (copyErr) { 220 | console.error('File copy also failed:', copyErr) 221 | throw copyErr 222 | } 223 | } else { 224 | throw err 225 | } 226 | } 227 | 228 | if (isWin) { 229 | // On Windows, update the shortcut file to use the .exe 230 | const cmdFile = path.join(__dirname, '..', '..', 'ipfs.cmd') 231 | 232 | fs.writeFileSync(cmdFile, `@ECHO OFF 233 | "%~dp0\\node_modules\\kubo\\bin\\ipfs.exe" %*`) 234 | } 235 | 236 | // test ipfs installed correctly. 237 | var result = cproc.spawnSync(localBin, ['version']) 238 | if (result.error) { 239 | throw new Error('ipfs binary failed: ' + result.error) 240 | } 241 | 242 | var outstr = result.stdout.toString() 243 | var m = /ipfs version ([^\n]+)\n/.exec(outstr) 244 | 245 | if (!m) { 246 | throw new Error('Could not determine IPFS version') 247 | } 248 | 249 | var actualVersion = `v${m[1]}` 250 | 251 | if (actualVersion !== version) { 252 | throw new Error(`version mismatch: expected ${version} got ${actualVersion}`) 253 | } 254 | 255 | return localBin 256 | } 257 | 258 | /** 259 | * @param {object} options 260 | * @param {string} options.version 261 | * @param {string} options.platform 262 | * @param {string} options.arch 263 | * @param {string} options.installPath 264 | * @param {string} options.distUrl 265 | */ 266 | module.exports.download = download 267 | 268 | /** 269 | * @param {string} [version] 270 | * @param {string} [platform] 271 | * @param {string} [arch] 272 | * @param {string} [installPath] 273 | */ 274 | module.exports.downloadAndUpdateBin = async (version, platform, arch, installPath) => { 275 | const args = cleanArguments(version, platform, arch, installPath) 276 | 277 | return link({ 278 | ...args, 279 | depBin: await download(args) 280 | }) 281 | } 282 | -------------------------------------------------------------------------------- /src/go-platform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function getGoOs () { 4 | switch (process.platform) { 5 | case 'sunos': 6 | return 'solaris' 7 | case 'win32': 8 | return 'windows' 9 | } 10 | 11 | return process.platform 12 | } 13 | 14 | function getGoArch () { 15 | switch (process.arch) { 16 | case 'ia32': 17 | return '386' 18 | case 'x64': 19 | return 'amd64' 20 | case 'arm': 21 | return 'arm' 22 | case 'arm64': 23 | return 'arm64' 24 | } 25 | 26 | return process.arch 27 | } 28 | 29 | module.exports = { 30 | GOOS: getGoOs(), 31 | GOARCH: getGoArch() 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const { download } = require('./download') 6 | 7 | module.exports.download = download 8 | 9 | module.exports.path = function () { 10 | if (process.env.KUBO_BINARY) { 11 | return process.env.KUBO_BINARY 12 | } 13 | 14 | const paths = [ 15 | path.resolve(path.join(__dirname, '..', 'kubo', 'ipfs')), 16 | path.resolve(path.join(__dirname, '..', 'kubo', 'ipfs.exe')) 17 | ] 18 | 19 | for (const bin of paths) { 20 | if (fs.existsSync(bin)) { 21 | return bin 22 | } 23 | } 24 | 25 | throw new Error('kubo binary not found, it may not be installed or an error may have occurred during installation') 26 | } 27 | -------------------------------------------------------------------------------- /src/post-install.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { downloadAndUpdateBin } = require('./download') 4 | 5 | downloadAndUpdateBin() 6 | .catch(err => { 7 | console.error(err) 8 | process.exit(1) 9 | }) 10 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | export function path(): string -------------------------------------------------------------------------------- /test/download.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape-promise').default(require('tape')) 4 | const fs = require('fs-extra') 5 | const os = require('os') 6 | const path = require('path') 7 | const { download, downloadAndUpdateBin } = require('../src/download') 8 | const { path: detectLocation } = require('../') 9 | const clean = require('./fixtures/clean') 10 | 11 | test('Ensure ipfs gets downloaded (current version and platform)', async (t) => { 12 | await clean() 13 | 14 | const installPath = await downloadAndUpdateBin() 15 | const stats = await fs.stat(installPath) 16 | 17 | t.ok(stats, 'kubo was downloaded') 18 | t.ok(installPath, detectLocation(), 'kubo binary was detected') 19 | 20 | t.end() 21 | }) 22 | 23 | test('Returns an error when version unsupported', async (t) => { 24 | await clean() 25 | 26 | await t.rejects(downloadAndUpdateBin('bogusversion', 'linux'), /Error: Version 'bogusversion' not available/) 27 | 28 | t.end() 29 | }) 30 | 31 | test('Returns an error when KUBO_DIST_URL is 404', async (t) => { 32 | await clean() 33 | 34 | process.env.KUBO_DIST_URL = 'https://dist.ipfs.tech/notfound' 35 | 36 | await t.rejects(downloadAndUpdateBin(), /404/) 37 | 38 | delete process.env.KUBO_DIST_URL 39 | 40 | t.end() 41 | }) 42 | 43 | test('Returns an error when legacy GO_IPFS_DIST_URL is 404', async (t) => { 44 | await clean() 45 | 46 | process.env.GO_IPFS_DIST_URL = 'https://dist.ipfs.tech/notfound' 47 | 48 | await t.rejects(downloadAndUpdateBin(), /404/) 49 | 50 | delete process.env.GO_IPFS_DIST_URL 51 | 52 | t.end() 53 | }) 54 | 55 | test('Path returns undefined when no binary has been downloaded', async (t) => { 56 | await clean() 57 | 58 | t.throws(detectLocation, /not found/, 'Path throws if binary is not installed') 59 | 60 | t.end() 61 | }) 62 | 63 | test('Ensure calling download function manually with static values works', async (t) => { 64 | await clean() 65 | 66 | const { version } = require('../package.json') 67 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'temp-dir-')) 68 | 69 | console.log(tempDir) 70 | const kuboPath = await download({ 71 | version: `v${version}`, 72 | platform: 'darwin', 73 | arch: 'arm64', 74 | distUrl: 'https://dist.ipfs.tech', 75 | installPath: tempDir 76 | }) 77 | console.log(kuboPath) 78 | const stats = await fs.stat(kuboPath) 79 | 80 | t.ok(stats, 'kubo was downloaded to installPath') 81 | 82 | t.end() 83 | }) 84 | -------------------------------------------------------------------------------- /test/fixtures/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs-extra') 4 | const path = require('path') 5 | const execa = require('execa') 6 | 7 | module.exports = async function clean () { 8 | await fs.remove(path.resolve(__dirname, '../../kubo')) 9 | await execa('git', ['checkout', '--', path.resolve(__dirname, '../../bin/ipfs')]) 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/example-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "ISC", 8 | "dependencies": { 9 | "kubo": "file://../../../" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/install.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const test = require('tape') 4 | const execa = require('execa') 5 | const cachedir = require('cachedir') 6 | 7 | /* 8 | Test that correct kubo is downloaded during npm install. 9 | */ 10 | 11 | const expectedVersion = require('../package.json').version 12 | 13 | async function clean () { 14 | await fs.remove(path.join(__dirname, 'fixtures', 'example-project', 'node_modules')) 15 | await fs.remove(path.join(__dirname, 'fixtures', 'example-project', 'package-lock.json')) 16 | await fs.remove(cachedir('npm-kubo')) 17 | } 18 | 19 | test.onFinish(clean) 20 | 21 | test('Ensure kubo defined in package.json is fetched on dependency install', async (t) => { 22 | await clean() 23 | 24 | const exampleProjectRoot = path.join(__dirname, 'fixtures', 'example-project') 25 | 26 | // from `example-project`, install the module 27 | execa.sync('npm', ['install'], { 28 | cwd: exampleProjectRoot 29 | }) 30 | 31 | // confirm package.json is correct 32 | const fetchedVersion = require(path.join(exampleProjectRoot, 'node_modules', 'kubo', 'package.json')).version 33 | t.ok(expectedVersion === fetchedVersion, `package.json versions match '${expectedVersion}'`) 34 | 35 | // confirm binary is correct 36 | const binary = path.join(exampleProjectRoot, 'node_modules', 'kubo', 'bin', 'ipfs') 37 | const versionRes = execa.sync(binary, ['--version'], { 38 | cwd: exampleProjectRoot 39 | }) 40 | 41 | t.ok(versionRes.stdout === `ipfs version ${expectedVersion}`, `ipfs --version output match '${expectedVersion}'`) 42 | 43 | t.end() 44 | }) 45 | -------------------------------------------------------------------------------- /test/path.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var cp = require('child_process') 5 | var ipfs = path.join(__dirname, '..', 'bin', 'ipfs') 6 | 7 | if (process.platform === 'win32') { 8 | ipfs += '.exe' 9 | } 10 | 11 | test('ensure ipfs bin path exists', function (t) { 12 | t.plan(4) 13 | fs.stat(ipfs, function (err, stats) { 14 | t.error(err, 'ipfs bin should stat witout error') 15 | cp.exec([ipfs, 'version'].join(' '), function (err, stdout, stderr) { 16 | t.error(err, 'ipfs runs without error') 17 | t.true(stdout.indexOf('ipfs version') >= 0, 'ipfs version retreived') 18 | t.false(stderr, 'no stderr output') 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | // project options 5 | "allowJs": true, 6 | "checkJs": true, 7 | "target": "ES2019", 8 | "lib": [ 9 | "ES2019", 10 | "ES2020.Promise", 11 | "ES2020.String", 12 | "ES2020.BigInt", 13 | "DOM", 14 | "DOM.Iterable" 15 | ], 16 | "noEmitOnError": true, 17 | "incremental": true, 18 | "composite": true, 19 | "isolatedModules": true, 20 | "removeComments": false, 21 | // module resolution 22 | "esModuleInterop": true, 23 | "moduleResolution": "node", 24 | // linter checks 25 | "noImplicitReturns": false, 26 | "noFallthroughCasesInSwitch": true, 27 | "noUnusedLocals": true, 28 | "noUnusedParameters": false, 29 | // advanced 30 | "importsNotUsedAsValues": "error", 31 | "forceConsistentCasingInFileNames": true, 32 | "skipLibCheck": true, 33 | "stripInternal": true, 34 | "resolveJsonModule": true, 35 | "outDir": "dist" 36 | }, 37 | "include": [ 38 | "src", 39 | "package.json" 40 | ], 41 | "exclude": [ 42 | "node_modules" 43 | ] 44 | } --------------------------------------------------------------------------------