├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── bin └── npme.js ├── boot.sh ├── brand.css ├── cmd ├── add-package.js ├── addon-remove.js ├── addon.js ├── edit-homepage.js ├── generate-maintenance-cron.js ├── install.js ├── maintenance.js ├── manage-tokens.js ├── remove-package.js ├── reset-follower.js ├── ssh.js ├── update-license.js └── usage.js ├── lib ├── check-for-update.js ├── download-tar.js ├── replication-check.js ├── retry-thing.js └── utils.js ├── npme-gce.jinja ├── package.json ├── replicated-license-retrieval.json └── test └── replication-check.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | install.sh 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | install: npm install --ignore-scripts 4 | node_js: 5 | - '0.10' 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # [4.3.0](https://github.com/npm/npme-installer/compare/v4.2.0...v4.3.0) (2017-06-09) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * correct typo to replicated flag setting in install command ([1d9df67](https://github.com/npm/npme-installer/commit/1d9df67)) 12 | 13 | 14 | ### Features 15 | 16 | * add support for unattended installation ([37be101](https://github.com/npm/npme-installer/commit/37be101)) 17 | 18 | 19 | 20 | 21 | # [4.2.0](https://github.com/npm/npme-installer/compare/v4.0.0...v4.2.0) (2017-03-15) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * better logic to determine if package spec is versioned ([#147](https://github.com/npm/npme-installer/issues/147))([7af7f8e](https://github.com/npm/npme-installer/commit/7af7f8e)) 27 | * use --ignore-scripts in update-notifier message ([#146](https://github.com/npm/npme-installer/issues/146))([79e6db1](https://github.com/npm/npme-installer/commit/79e6db1)) 28 | 29 | 30 | ### Features 31 | 32 | * cleanup the UI a bit, link to http rather than https ([#145](https://github.com/npm/npme-installer/issues/145))([546ff4a](https://github.com/npm/npme-installer/commit/546ff4a)) 33 | * creating script for verifying and fixing registries ([#156](https://github.com/npm/npme-installer/issues/156))([5563445](https://github.com/npm/npme-installer/commit/5563445)) 34 | 35 | 36 | 37 | 38 | ## [4.1.2](https://github.com/npm/npme-installer/compare/v4.1.1...v4.1.2) (2016-06-24) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * better logic to determine if package spec is versioned ([#147](https://github.com/npm/npme-installer/issues/147)) ([7af7f8e](https://github.com/npm/npme-installer/commit/7af7f8e)) 44 | 45 | 46 | 47 | 48 | ## [4.1.1](https://github.com/npm/npme-installer/compare/v4.1.0...v4.1.1) (2016-06-06) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * use --ignore-scripts in update-notifier message ([#146](https://github.com/npm/npme-installer/issues/146)) ([79e6db1](https://github.com/npm/npme-installer/commit/79e6db1)) 54 | 55 | 56 | 57 | 58 | # [4.1.0](https://github.com/npm/npme-installer/compare/v4.0.0...v4.1.0) (2016-06-06) 59 | 60 | 61 | ### Features 62 | 63 | * cleanup the UI a bit, link to http rather than https ([#145](https://github.com/npm/npme-installer/issues/145)) ([546ff4a](https://github.com/npm/npme-installer/commit/546ff4a)) 64 | 65 | 66 | 67 | 68 | # [4.0.0](https://github.com/npm/npme-installer/compare/v3.10.0...v4.0.0) (2016-06-01) 69 | 70 | 71 | ### Features 72 | 73 | * switch to docker/stable as our default installation ([#143](https://github.com/npm/npme-installer/issues/143))([01f8d14](https://github.com/npm/npme-installer/commit/01f8d14)) 74 | * use replicated bin if present, fall back to docker admin command ([#142](https://github.com/npm/npme-installer/issues/142))([3700bef](https://github.com/npm/npme-installer/commit/3700bef)) 75 | 76 | 77 | ### BREAKING CHANGES 78 | 79 | * replicated will now run on Docker without a central bin being installed for management 80 | 81 | 82 | 83 | 84 | # [3.10.0](https://github.com/npm/npme-installer/compare/v3.9.2...v3.10.0) (2016-05-06) 85 | 86 | 87 | ### Features 88 | 89 | * added a new command for removing third-party addons ([#137](https://github.com/npm/npme-installer/issues/137))([6469c9c](https://github.com/npm/npme-installer/commit/6469c9c)) 90 | 91 | 92 | 93 | 94 | ## [3.9.2](https://github.com/npm/npme-installer/compare/v3.9.1...v3.9.2) (2016-04-28) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * cannot get google deploy to pick up the zone variable :shurgmoji: ([b9801b7](https://github.com/npm/npme-installer/commit/b9801b7)) 100 | 101 | 102 | 103 | 104 | ## [3.9.1](https://github.com/npm/npme-installer/compare/v3.9.0...v3.9.1) (2016-04-28) 105 | 106 | 107 | ### Bug Fixes 108 | 109 | * property should be compute/zone rather than zone ([#136](https://github.com/npm/npme-installer/issues/136)) ([8800a69](https://github.com/npm/npme-installer/commit/8800a69)) 110 | 111 | 112 | 113 | 114 | # [3.9.0](https://github.com/npm/npme-installer/compare/v3.6.0...v3.9.0) (2016-04-28) 115 | 116 | 117 | ### Bug Fixes 118 | 119 | * Do not let update-notifier errors crash the bin ([#132](https://github.com/npm/npme-installer/issues/132)) ([c28d4bb](https://github.com/npm/npme-installer/commit/c28d4bb)) 120 | * have to chown -R to fix update-notifier access ([#134](https://github.com/npm/npme-installer/issues/134)) ([daea8bc](https://github.com/npm/npme-installer/commit/daea8bc)), closes [(#134](https://github.com/(/issues/134) 121 | * move profile.sh to boot.sh ([#135](https://github.com/npm/npme-installer/issues/135)) ([aad1239](https://github.com/npm/npme-installer/commit/aad1239)) 122 | 123 | ### Features 124 | 125 | * added template for booting npm Enterprise on GCE ([#133](https://github.com/npm/npme-installer/issues/133)) ([0be4f8d](https://github.com/npm/npme-installer/commit/0be4f8d)) 126 | * check for newer npme version via update-notifier ([#131](https://github.com/npm/npme-installer/issues/131)) ([2298157](https://github.com/npm/npme-installer/commit/2298157)) 127 | 128 | 129 | 130 | 131 | # [3.8.0](https://github.com/npm/npme-installer/compare/v3.7.1...v3.8.0) (2016-04-28) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * have to chown -R to fix update-notifier access ([#134](https://github.com/npm/npme-installer/issues/134)) ([daea8bc](https://github.com/npm/npme-installer/commit/daea8bc)), closes [(#134](https://github.com/(/issues/134) 137 | 138 | ### Features 139 | 140 | * added template for booting npm Enterprise on GCE ([#133](https://github.com/npm/npme-installer/issues/133)) ([0be4f8d](https://github.com/npm/npme-installer/commit/0be4f8d)) 141 | 142 | 143 | 144 | 145 | ## [3.7.1](https://github.com/npm/npme-installer/compare/v3.7.0...v3.7.1) (2016-04-28) 146 | 147 | 148 | ### Bug Fixes 149 | 150 | * Do not let update-notifier errors crash the bin ([#132](https://github.com/npm/npme-installer/issues/132)) ([c28d4bb](https://github.com/npm/npme-installer/commit/c28d4bb)) 151 | 152 | 153 | 154 | 155 | # [3.7.0](https://github.com/npm/npme-installer/compare/v3.6.0...v3.7.0) (2016-04-27) 156 | 157 | 158 | ### Features 159 | 160 | * check for newer npme version via update-notifier ([#131](https://github.com/npm/npme-installer/issues/131)) ([2298157](https://github.com/npm/npme-installer/commit/2298157)) 161 | 162 | 163 | 164 | 165 | # [3.6.0](https://github.com/npm/npme-installer/compare/v3.5.0...v3.6.0) (2016-04-22) 166 | 167 | 168 | ### Features 169 | 170 | * add admin command for installing addons ([9d33ebc](https://github.com/npm/npme-installer/commit/9d33ebc)) 171 | * rename the bin back to npme ([#125](https://github.com/npm/npme-installer/issues/125)) ([af83c54](https://github.com/npm/npme-installer/commit/af83c54)) 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm Enterprise Installer 2 | 3 | [![Build Status](https://travis-ci.org/npm/npme-installer.svg?branch=master)](https://travis-ci.org/npm/npme-installer) 4 | 5 | One-step-installer for npm Enterprise. 6 | 7 | ## Quickstart 8 | 9 | To get up and running as quickly as possible, [see the quickstart guide on the docs site](https://docs.npmjs.com/enterprise/intro). 10 | 11 | ## Supported Platforms 12 | 13 | Modern versions of Ubuntu (12.04+), CentOS/RHEL (7+), Debian (7.7+) 14 | 15 | ## Prerequisites 16 | 17 | You can find [detailed prerequisites](https://docs.npmjs.com/enterprise/requirements) on the docs site. 18 | 19 | ## Installing 20 | 21 | * [Install Node.js via package manager](https://nodejs.org/en/download/package-manager/) 22 | * Update npm via `sudo npm i -g npm@latest` 23 | * Then install `npme`: 24 | 25 | ```shell 26 | sudo npm install npme -g --unsafe 27 | ``` 28 | 29 | Once installation is complete visit __https://your-server-address:8800__ and bypass the security warning (you can provide your own certificate later to prevent this warning). You will be presented with a management UI which allows you to configure your npm Enterprise appliance. 30 | 31 | You can find [installation details](https://docs.npmjs.com/enterprise/installation) on the docs site. 32 | 33 | ### Unattended / Automated Installations 34 | ```shell 35 | sudo npm install npme -g --ignore-scripts 36 | ``` 37 | 38 | To perform an installation with this tool using automation tooling, you will need to specify additional arguments to the command line. Most commonly, you will need to supply: 39 | 40 | * `-u` - the unattended install flag itself 41 | * `-i` - the IP address of the server's eth0 interface 42 | * `-e` - the public facing IP 43 | 44 | The full list of command line arguments for the install command is here: 45 | 46 | ```shell 47 | -s, --sudo should shell commands be run as sudo user 48 | [boolean] [default: true] 49 | -r, --release what release of replicated should be used (defaults to 50 | stable) [string] [default: "docker"] 51 | -d, --docker-version the specific Docker version to use [string] 52 | -i, --internal-address the private ip address of the eth0 adapter [string] 53 | -e, --external-address the public facing ip address for the server [string] 54 | -p, --http-proxy sets the HTTP proxy for Docker and Replicated [string] 55 | -u, --unattended-install allows for unattended install to succeed 56 | ``` 57 | 58 | ```shell 59 | npme install -s -u -i 172.10.1.1 -e 52.10.0.0 60 | ``` 61 | 62 | ## Connecting to the Registry 63 | 64 | By default the npm Enterprise registry will be available on __http://your-server-address:8080__. 65 | 66 | Simply run: 67 | 68 | ```shell 69 | npm login --scope=@my-company-name --registry=http://your-server-address:8080 70 | ``` 71 | 72 | And publish modules using the corresponding scope name: 73 | 74 | ```json 75 | { 76 | "name": "@my-company-name/my-module", 77 | "repository": { 78 | "url": "git://github.mycompany.com/myco/mypackage.git" 79 | } 80 | } 81 | ``` 82 | 83 | Details on [client configuration](https://docs.npmjs.com/enterprise/client-configuration) and [using npm Enterprise](https://docs.npmjs.com/enterprise/using-it) can be found on the docs site. 84 | 85 | ## Updating 86 | 87 | Access your server via HTTPS on port 8800 and check for updates via 88 | the management console. 89 | -------------------------------------------------------------------------------- /bin/npme.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var checkForUpdate = require('../lib/check-for-update') 4 | var chalk = require('chalk') 5 | 6 | // use hidden duplicate install command for run script 7 | // which avoids the check-for-update so update-notifier 8 | // config does not initially belong to root 9 | var install = require('../cmd/install') 10 | 11 | var yargs = require('yargs') 12 | .usage('$0 [command] [arguments]') 13 | .command(install) 14 | .command('autoinstall', false, install) 15 | .commandDir('../cmd') 16 | .option('s', { 17 | alias: 'sudo', 18 | description: 'should shell commands be run as sudo user', 19 | boolean: true, 20 | default: true, 21 | global: true 22 | }) 23 | .help().alias('h', 'help') 24 | .version().alias('v', 'version') 25 | .example('$0 add-package lodash', 'add the lodash package to your whitelist') 26 | .demand(1, 'you must provide a command to run') 27 | .wrap(88) 28 | .strict() 29 | 30 | // mimic standard yargs failure handler, but call checkForUpdate() 31 | yargs.fail(function (msg, err) { // eslint-disable-line 32 | checkForUpdate() 33 | yargs.showHelp('error') 34 | console.error(msg) 35 | process.exit(1) 36 | }).argv 37 | 38 | process.on('uncaughtException', function (err) { 39 | // if there is no Internet connection 40 | // native-dns throws an uncaught ENOENT exception 41 | // let's ignore this. 42 | if (err.code === 'ENOENT') return 43 | console.log(chalk.red(err.message)) 44 | 45 | process.exit(0) 46 | }) 47 | -------------------------------------------------------------------------------- /boot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read -r -d '' BANNER <<-'EOF' 4 | ╔═══════════════════════════════════════════════════════════════════════╗ 5 | ║EnterpriseEnterpriseEnterpriseEnterpriseEnterpriseEnterpriseEnterpriseE║ 6 | ║nte╔═══════════════╗nte╔═══════════════╗nte╔═══════════════════════╗eEn║ 7 | ║ter║ ║ter║ ║ter║ ║Ent║ 8 | ║erp║ ╔═══╗ ║erp║ ╔═══╗ ║erp║ ╔═══╗ ╔═══╗ ║nte║ 9 | ║rpr║ ║ris║ ║rpr║ ║ris║ ║rpr║ ║ris║ ║rpr║ ║ter║ 10 | ║pri║ ║ise║ ║pri║ ║ise║ ║pri║ ║ise║ ║pri║ ║erp║ 11 | ║ris║ ║seE║ ║ris║ ║seE║ ║ris║ ║seE║ ║ris║ ║rpr║ 12 | ║ise║ ║eEn║ ║ise║ ╚═══╝ ║ise║ ║eEn║ ║ise║ ║pri║ 13 | ║seE║ ║Ent║ ║seE║ ║seE║ ║Ent║ ║seE║ ║ris║ 14 | ║eEn╚═══════╝nte╚═══╝eEn║ ╔═══════╝eEn╚═══════╝nte╚═══╝eEn╚═══╝ise║ 15 | ║EnterpriseEnterpriseEnt║ ║terpriseEnterpriseEnterpriseEnterpriseE║ 16 | ╚═══════════════════╗nte╚═══════╝erp╔═══════════════════════════════════╝ 17 | ║terpriseEnterpr║ E n t e r p r i s e 18 | ╚═══════════════╝ 19 | EOF 20 | echo "$BANNER" 21 | echo "" 22 | 23 | if command -v node > /dev/null; then 24 | node_version=`node -v` 25 | npm_version=`npm -v` 26 | echo " Node: $node_version" 27 | echo " npm: $npm_version" 28 | else 29 | echo " Node: Not installed" 30 | echo " npm: Not installed" 31 | fi 32 | 33 | if pgrep "docker" > /dev/null; then 34 | docker_version=`docker --version` 35 | echo " Docker: Running, $docker_version" 36 | else 37 | if command -v docker > /dev/null; then 38 | docker_version=`docker --version` 39 | echo " Docker: Stopped, $docker_version" 40 | else 41 | echo " Docker: Not installed" 42 | fi 43 | fi 44 | 45 | if pgrep "replicated" > /dev/null; then 46 | repl_version=`replicated --version` 47 | echo " Replicated: Running, $repl_version" 48 | else 49 | if command -v replicated > /dev/null; then 50 | repl_version=`replicated --version` 51 | echo " Replicated: Stopped, $repl_version" 52 | else 53 | echo " Replicated: Not installed" 54 | fi 55 | fi 56 | 57 | echo "" 58 | 59 | public_ip=`dig +short myip.opendns.com @resolver1.opendns.com` 60 | echo " Visit https://$public_ip:8800 to configure your npm Enterprise instance" 61 | 62 | echo "" 63 | -------------------------------------------------------------------------------- /brand.css: -------------------------------------------------------------------------------- 1 | /* The color of the page background */ 2 | body { 3 | background-color: #333; 4 | font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1.page-title { 8 | color: #fff; 9 | } 10 | /* Top nav bar, background and border colors */ 11 | .navbar-inverse{ 12 | border-color: #101010; 13 | background-color: #222; 14 | } 15 | 16 | /* Header links, unselected */ 17 | .navbar-inverse .navbar-nav>li>a { 18 | color: #9d9d9d; 19 | } 20 | 21 | /* Header links, when selected */ 22 | .navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover { 23 | color: #fff; 24 | background-color: #080808; 25 | } 26 | 27 | /* The app status (starting, stopping, etc) tile on the dashboard */ 28 | .widget-app-status { 29 | background-color: #a5b3aa; 30 | } 31 | .widget-app-status .graphic { 32 | color: #fff; 33 | } 34 | .widget-app-status .title { 35 | color: #fff; 36 | } 37 | .widget-app-status .actions .btn-warning { /* Cancel, Stop Now */ 38 | color: #fff; 39 | background-color: #cb3837; 40 | border-color: #bb2827; 41 | } 42 | .widget-app-status .actions .btn-success { /* Start Now */ 43 | color: #fff; 44 | background-color: #cb3837; 45 | border-color: #bb2827; 46 | } 47 | 48 | /* The app update tile (up to date, etc) on the dashboard */ 49 | .widget-app-update { 50 | background-color: #cb3837; 51 | color: #fff; 52 | } 53 | .widget-app-update .title { 54 | color: #fff; 55 | } 56 | .widget-app-update .last-checked { 57 | color: #fff; 58 | } 59 | .widget-app-update .current-version { 60 | color: #fff; 61 | } 62 | #dashboard a { /* This is the view release history link... */ 63 | color: #fff; 64 | } 65 | 66 | .widget-app-backup { 67 | background-color: #e7f2f7; 68 | } 69 | .widget-app-backup .graphic { 70 | color: #cb3837; 71 | } 72 | .widget-app-backup .title { 73 | color: #cb3837; 74 | } 75 | 76 | /* Metrics, null == app not running or unsupported */ 77 | .widget-null-metrics { 78 | background-color: #483840; 79 | } 80 | .widget-null-metrics .widget-null-content { 81 | color: #999; 82 | border-color: #999; 83 | } 84 | .metrics-title { 85 | color: #fff; 86 | } 87 | .metrics-footer { 88 | color: #fff; 89 | } 90 | .widget-cpu-metrics { 91 | background-color: #a5b3aa; 92 | } 93 | .widget-cpu-metrics .metrics-title { 94 | color: #fff; 95 | } 96 | .widget-cpu-metrics .widget-title-host { 97 | color: #ccc; 98 | } 99 | .widget-cpu-metrics .metrics-footer { 100 | color: #fff; 101 | } 102 | .widget-memory-metrics { 103 | background-color: #cb3837; 104 | } 105 | .widget-memory-metrics .metrics-title { 106 | color: #fff; 107 | } 108 | .widget-memory-metrics .widget-title-host { 109 | color: #ccc; 110 | } 111 | .widget-memory-metrics .metrics-footer { 112 | color: #fff; 113 | } 114 | 115 | /* Settings page */ 116 | .configure-group h3 { 117 | color: #fff; 118 | } 119 | .label-heading { 120 | color: #fff; 121 | } 122 | .configure-group p { 123 | color: #A4A9AC; 124 | } 125 | #sections .well-dark { /* Groups well on left */ 126 | background-color: #222; 127 | border-color: #222; 128 | } 129 | .configure-nav a { /* Groups well text */ 130 | color: #a4a9ac; 131 | } 132 | 133 | .button-checkbox .btn-unchecked { 134 | font-weight:500; 135 | color: #85898B; 136 | background-color: #333; 137 | border-color: #525455; 138 | } 139 | .button-checkbox .btn-unchecked:hover { 140 | background-color:#525455; 141 | color:#fff; 142 | text-shadow:none 143 | } 144 | .button-checkbox .btn-unchecked:focus { 145 | background-color:#525455; 146 | color:#fff; 147 | text-shadow:none 148 | } 149 | 150 | 151 | .button-checkbox .btn-default { 152 | color: #333; 153 | background-color: #fff; 154 | border-color: #ccc; 155 | } 156 | 157 | /* Tables are used on Audit Log, Hosts and Release History screen */ 158 | .table .table { 159 | background-color: #333; 160 | font-size: 12px; 161 | border-bottom: 1px solid #fff; 162 | } 163 | .table-striped>tbody>tr:nth-child(even)>td, 164 | .table-striped>tbody>tr:nth-child(even)>th { 165 | background-color: #555; 166 | } 167 | .table-striped>tbody>tr:nth-child(odd)>td, 168 | .table-striped>tbody>tr:nth-child(odd)>th { 169 | background-color: #444; 170 | } 171 | 172 | 173 | /* Used during the setup process */ 174 | .btn-selfsigned { 175 | color: #fff; 176 | background-color: #f0ad4e; 177 | border-color: #eea236; 178 | } 179 | .btn-selfsigned:hover { 180 | color: #fff; 181 | background-color: #ec971f; 182 | border-color: #d58512; 183 | } 184 | 185 | .btn-uploadcert { 186 | color: #fff; 187 | background-color: #5cb85c; 188 | border-color: #4cae4c; 189 | } 190 | .btn-uploadcert:hover { 191 | color: #fff; 192 | background-color: #449d44; 193 | border-color: #398439; 194 | } 195 | 196 | .btn-importlicense { 197 | color: #fff; 198 | background-color: #5cb85c; 199 | border-color: #4cae4c; 200 | } 201 | .btn-importlicense:hover { 202 | color: #fff; 203 | background-color: #449d44; 204 | border-color: #398439; 205 | } 206 | 207 | .btn-secureconsole { 208 | color: #fff; 209 | background-color: #5cb85c; 210 | border-color: #4cae4c; 211 | } 212 | .btn-secureconsole:hover { 213 | color: #fff; 214 | background-color: #449d44; 215 | border-color: #398439; 216 | } 217 | -------------------------------------------------------------------------------- /cmd/add-package.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var adminCommand = utils.adminCommand 4 | var exec = utils.exec 5 | var command = utils.command(__filename) 6 | 7 | var cmd = { 8 | command: command, 9 | desc: 'add a package to your whitelist', 10 | usage: '$0 ' + command + ' package-name[@version] -- [opts]', 11 | demand: 1, 12 | demandDesc: 'you must provide a package name and optional version', 13 | epilog: 'add a new package to your appliance\'s whitelist' 14 | } 15 | 16 | cmd.handler = function (argv) { 17 | var cmd = adminCommand + argv._.join(' ') 18 | if (argv._[1].lastIndexOf('@') > 0) cmd += ' --all-versions=false' 19 | exec(cmd, argv.sudo, function () {}) 20 | } 21 | 22 | module.exports = utils.decorate(cmd) 23 | -------------------------------------------------------------------------------- /cmd/addon-remove.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var command = utils.command(__filename) 4 | 5 | var cmd = { 6 | command: command, 7 | desc: 'remove a third-party addon', 8 | usage: '$0 ' + command + ' -- [opts]', 9 | demand: 1, 10 | demandDesc: 'you must specify an addon to remove' 11 | } 12 | 13 | module.exports = utils.decorate(cmd) 14 | -------------------------------------------------------------------------------- /cmd/addon.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var command = utils.command(__filename) 4 | 5 | var cmd = { 6 | command: command, 7 | desc: 'install a third-party addon', 8 | usage: '$0 ' + command + ' -- [opts]', 9 | demand: 1, 10 | demandDesc: 'you must specify an addon to install' 11 | } 12 | 13 | module.exports = utils.decorate(cmd) 14 | -------------------------------------------------------------------------------- /cmd/edit-homepage.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var command = utils.command(__filename) 4 | 5 | var cmd = { 6 | command: command, 7 | desc: 'edit the packages displayed on the npme homepage', 8 | usage: '$0 ' + command + ' ' 9 | } 10 | 11 | module.exports = utils.decorate(cmd) 12 | -------------------------------------------------------------------------------- /cmd/generate-maintenance-cron.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var inquirer = require('inquirer') 3 | var utils = require('../lib/utils') 4 | var which = require('which') 5 | var url = require('url') 6 | var request = require('request') 7 | var fs = require('fs') 8 | 9 | var cmd = { 10 | desc: "generate a cron tab line for npme's CouchDB maintenace script" 11 | } 12 | 13 | cmd.handler = function (argv) { 14 | const npmeBin = which.sync('npme') 15 | 16 | // allow folks to procede after auth failure on scan url. 17 | var failedScanValidate = false 18 | // populated when validating scan url 19 | var checkPackageName 20 | // extracted from check hosts metadata for checkPackageName 21 | var tarHost 22 | 23 | inquirer.prompt([ 24 | { 25 | type: 'input', 26 | name: 'scan', 27 | message: 'we stream all the documents in this database (the replica).', 28 | default: 'http://admin:admin@172.17.0.1:5984/registry', 29 | filter: function (input) { 30 | if (!failedScanValidate) { 31 | input = cleanCheckUrl(input) 32 | } 33 | return input 34 | }, 35 | validate: function (input) { 36 | var alreadyFailed = failedScanValidate 37 | var done = this.async() 38 | if (!alreadyFailed) { 39 | input = cleanCheckUrl(input) 40 | } 41 | 42 | process.stdout.write(' ...checking ' + input) 43 | 44 | request.get(input + '/_changes' + '?start=1&limit=20', function (err, res, body) { 45 | if (err) { 46 | return done(err + '') 47 | } 48 | var reso = json(body) || {} 49 | // 404. the database specified in the url doesnt exist. 50 | // 400. _changes is not a valid db name. this means they did not specify a url 51 | 52 | if (reso.error || res.statusCode !== 200) { 53 | var message = reso.error || '' 54 | if (res.statusCode === 404) { 55 | message += '\ncould not find the database name specified by the url.' 56 | } else if (res.statusCode === 401) { 57 | // This flag is set here to allow someone to submit credentials that dont have write access. 58 | // they have to type them twice but this allows folks to generate the command and figure it out later. 59 | // also makes it possible to test generating on replicate.npmjs.com =P 60 | failedScanValidate = true 61 | message += '\nincorrect username and password provided. please replace ' + url.parse(input).auth + ' in the url.' 62 | } 63 | 64 | message += '\nplease copy and update this value: ' + chalk.yellow(url.resolve(input, '/registry')) 65 | 66 | if (alreadyFailed) { 67 | console.log(chalk.yellow('Warning. could not verify scan url.')) 68 | done(false, true) 69 | } else { 70 | done(message, false) 71 | } 72 | return 73 | } 74 | 75 | // also we hit this to gather a package name to check to validate the: 76 | // 1. check url 77 | // 2. tarball host! 78 | // 79 | // try to find one unscoped package name 80 | // else take the first one thats not _design 81 | // otherwise this db is just empty and that's ok also. 82 | 83 | var notDesign 84 | var unscoped 85 | 86 | (reso.results || []).forEach(function (r) { 87 | var id = r.id || '' 88 | if (id.length && id.indexOf('_') !== 0) { 89 | notDesign = id 90 | if (id.indexOf('@') === -1) { 91 | unscoped = id 92 | } 93 | } 94 | }) 95 | 96 | checkPackageName = unscoped || notDesign 97 | 98 | done(false, true) 99 | }) 100 | } 101 | }, 102 | { 103 | type: 'input', 104 | name: 'check', 105 | message: 'we request each doc in the stream from scan to see if check has the same versions (the primary)', 106 | validate: function (input) { 107 | var parsed = url.parse(input) 108 | if (!parsed.hostname && parsed.protocol) { 109 | console.log('\na url is required.') 110 | return false 111 | } 112 | 113 | var done = this.async() 114 | request.get(url.resolve(input, '/' + checkPackageName), function (err, res, body) { 115 | if (err) { 116 | return done(err + '') 117 | } else if (res.statusCode === 200) { 118 | var checkPackageMetadata = json(body) 119 | var versions = checkPackageMetadata.versions || {} 120 | var dist = Object.keys(versions)[0] || {}.dist 121 | if (dist.tarball) { 122 | var parsed = url.parse(dist).host 123 | tarHost = parsed.protocol + '//' + parsed.host 124 | } 125 | } 126 | 127 | done(false, true) 128 | }) 129 | } 130 | }, 131 | { 132 | type: 'input', 133 | name: 'data-directory', 134 | message: 'data directory to store missing tarballs', 135 | default: '/usr/local/lib/npme/packages', 136 | validate: function (input) { 137 | // so it's bad if they repair their instance to the wrong data dir. 138 | // because versions will exist but tarballs wont. 139 | var done = this.async() 140 | 141 | fs.readdir(input, function (err, files) { 142 | done(err, !!files) 143 | }) 144 | } 145 | }, 146 | { 147 | type: 'input', 148 | name: 'tarhost', 149 | message: 'the host that has the missing tarballs. defaults to check url host.', 150 | // 'If the tarball urls have been rewritten to point to a load balancer that includes the replica you are repairing you may need to provide this. otherwise leave it blank.', 151 | default: function (answers) { 152 | return tarHost || answers.check 153 | }, 154 | validate: function (input) { 155 | var o = url.parse(input) 156 | if (!o || !o.protocol || !o.host) { 157 | return 'please provide a valid url' 158 | } 159 | var done = this.async() 160 | process.stdout.write('.. attempting to contact ' + input) 161 | request.get(input, function (err) { 162 | done(err, !err) 163 | }) 164 | } 165 | } 166 | ]).then(function (answers) { 167 | answers.check = cleanCheckUrl(answers.check) 168 | done(answers) 169 | }) 170 | 171 | function done (answers) { 172 | canCouchdb(answers, function (err, can) { 173 | if (err) { 174 | console.error(err) 175 | } 176 | 177 | if (!can) { 178 | console.log('\nerrors verifying your configuration. the generated command may not work :(\n') 179 | } 180 | 181 | var cmd = npmeBin + ' maintenance --check=' + answers.check + ' --scan=' + answers.scan + ' --data-directory=' + answers['data-directory'] 182 | 183 | if (answers.tarhost) { 184 | cmd += ' --tar-host=' + answers.tarhost 185 | } 186 | 187 | console.log('please test the command now by performing a "dry run". run:') 188 | console.log(chalk.green(cmd + ' --dry-run') + '\n') 189 | 190 | console.log('if you prefer to run this manually. run:') 191 | console.log(chalk.green(cmd) + '\n') 192 | 193 | console.log('if all that looks fine add the following to your crontab:') 194 | console.log(chalk.green('0 * * * * ' + cmd + ' 1> ' + answers['data-directory'] + '/maintenance.log 2>&1')) 195 | }) 196 | } 197 | } 198 | 199 | function cleanCheckUrl (check) { 200 | var parsed = url.parse(check) 201 | if (parsed.pathname === '/') { 202 | console.log('\nno database specified in path of scan url. choosing "registry"') 203 | parsed.pathname = '/registry' 204 | } 205 | 206 | if (!parsed.auth) { 207 | parsed.auth = 'admin:admin' 208 | } 209 | 210 | return url.format(parsed) 211 | } 212 | 213 | function canCouchdb (answers, cb) { 214 | request.get(url.resolve(answers.scan, '/_active_tasks'), function (err, res, body) { 215 | if (err) { 216 | console.log('ERROR: scan\n could not request check couchdb:\n' + err) 217 | } else if (res.statusCode !== 200) { 218 | console.log('Warning: scan\n could not verify write permission to couchdb. ' + res.statusCode) 219 | } 220 | 221 | cb(false, !((err || res.statusCode === 200))) 222 | }) 223 | } 224 | 225 | function json (s) { 226 | try { 227 | return JSON.parse(s) 228 | } catch (e) {} 229 | } 230 | 231 | module.exports = utils.decorate(cmd, __filename) 232 | -------------------------------------------------------------------------------- /cmd/install.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var boxen = require('boxen') 3 | var figures = require('figures') 4 | var fs = require('fs') 5 | var path = require('path') 6 | var publicIp = require('public-ip') 7 | var request = require('request') 8 | var utils = require('../lib/utils') 9 | 10 | var cwd = utils.cwd 11 | var exec = utils.exec 12 | 13 | var cmd = { 14 | desc: 'install the npm Enterprise appliance', 15 | options: { 16 | r: { 17 | alias: 'release', 18 | default: 'docker', 19 | description: 'what release of replicated should be used (defaults to stable)', 20 | type: 'string' 21 | }, 22 | d: { 23 | alias: 'docker-version', 24 | description: 'the specific Docker version to use', 25 | type: 'string' 26 | }, 27 | i: { 28 | alias: 'internal-address', 29 | description: 'the private ip address of the eth0 adapter', 30 | type: 'string' 31 | }, 32 | e: { 33 | alias: 'external-address', 34 | description: 'the public facing ip address for the server', 35 | type: 'string' 36 | }, 37 | p: { 38 | alias: 'http-proxy', 39 | description: 'sets the HTTP proxy for Docker and Replicated', 40 | type: 'string' 41 | }, 42 | u: { 43 | alias: 'unattended-install', 44 | description: 'allows for unattended install to succeed' 45 | } 46 | }, 47 | epilog: 'install a brand new npm Enterprise appliance' 48 | } 49 | 50 | cmd.handler = function (argv) { 51 | exec('cp replicated-license-retrieval.json /etc', argv.sudo, function () { 52 | var release = argv.release ? '/' + argv.release : '' 53 | 54 | request.get('https://get.replicated.com' + release, function (err, res, content) { 55 | if (err) { 56 | console.log(chalk.red(err.message)) 57 | return 58 | } 59 | 60 | fs.writeFileSync(path.resolve(cwd, './install.sh'), content, 'utf-8') 61 | var commandSegments = [ 'bash', 'install.sh' ] 62 | if (argv.u) { 63 | commandSegments.push('bypass-storagedriver-warnings') 64 | if (!argv.p) { 65 | commandSegments.push('no-proxy') 66 | } 67 | } 68 | if (argv.e) { 69 | commandSegments.push('public-address=' + argv.e) 70 | } 71 | if (argv.i) { 72 | commandSegments.push('private-address=' + argv.i) 73 | } 74 | if (argv.d) { 75 | commandSegments.push('docker-version=' + argv.d) 76 | } 77 | if (argv.p) { 78 | commandSegments.push('http-proxy=' + argv.p) 79 | } 80 | var commandLine = commandSegments.join(' ') 81 | 82 | exec(commandLine, argv.sudo, function (code) { 83 | if (code !== 0) { 84 | console.log(chalk.bold.red('oh no! something went wrong during the install...\r\n') + 85 | chalk.bold.red('contact ') + 86 | chalk.bold.green('support@npmjs.com ') + 87 | chalk.bold.red('and we can help get you up and running')) 88 | } else { 89 | exec('mkdir -p /etc/replicated/brand/', argv.sudo, function () { 90 | exec('cp -f brand.css /etc/replicated/brand/brand.css', argv.sudo, function () {}) 91 | }) 92 | 93 | publicIp.v4(function (err, ip) { 94 | var accessMessage 95 | 96 | if (err) { 97 | accessMessage = ' Access the admin console in a web-browser via port ' + chalk.bold.cyan(8800) 98 | } else { 99 | accessMessage = ' Access the admin console in a web-browser at ' + chalk.bold.cyan('http://' + ip + ':8800') 100 | } 101 | 102 | console.log( 103 | boxen( 104 | figures.squareSmall + accessMessage + '\n\n' + 105 | figures.tick + ' Visit ' + chalk.bold.cyan('https://docs.npmjs.com/') + ', for information about using npm Enterprise or contact ' + chalk.bold.cyan('support@npmjs.com'), 106 | {padding: 1} 107 | ) 108 | ) 109 | }) 110 | } 111 | }) 112 | }) 113 | }) 114 | } 115 | 116 | module.exports = utils.decorate(cmd, __filename) 117 | -------------------------------------------------------------------------------- /cmd/maintenance.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | var follow = require('../lib/replication-check.js') 3 | 4 | var cmd = { 5 | desc: "run maintenance against a CouchDB server (make sure it's packages are up-to-date)" 6 | } 7 | 8 | cmd.builder = function (yargs) { 9 | yargs 10 | .option('check', { 11 | describe: 'we request each doc in the stream from scan to see if check has the same versions (the primary).\nexample: https://theprimary:8080', 12 | demand: true 13 | }) 14 | .option('scan', { 15 | describe: 'we stream all the documents in this database (the replica).\nexample: http://admin:admin@172.17.0.1:5984/registry\nyou can find this by running `sudo docker ps | grep klaemo | sed \'s/->/ /\' | awk \'{print $(NF-2)}\'`', 16 | demand: true 17 | }) 18 | .option('data-directory', { 19 | describe: 'data directory to store missing tarballs', 20 | default: '/usr/local/lib/npme/packages' 21 | }) 22 | .option('tar-host', { 23 | describe: 'download tarballs from this host instead of the external url for your npme. most likely the host you use for `check`. example: https://theprimary:8080', 24 | default: false 25 | }) 26 | .option('dry-run', { 27 | describe: 'only print packages that are missing versions instead of attempting to repair them', 28 | default: false 29 | }) 30 | .option('shared-fetch-secret', { 31 | describe: 'secret to populate when interacting with primary server.' 32 | }) 33 | } 34 | 35 | cmd.handler = function (argv) { 36 | // process.removeListener('UncaughtException') 37 | console.log(argv) 38 | 39 | var stream = follow(function (data, cb) { 40 | var message = data.name + '\t' + JSON.stringify(data.versions) 41 | 42 | if (argv.dryRun) { 43 | console.log(message) 44 | return cb() 45 | } 46 | 47 | if (data.error) { 48 | console.log('[error] ' + data.name + ' ' + data.error) 49 | return cb() 50 | } 51 | 52 | cb = timerwrap(cb, data.name) 53 | 54 | follow.repairVersions({ 55 | db: argv.scan, 56 | oldDoc: data.scan, 57 | currentDoc: data.check, 58 | versions: data.versions, 59 | tarHost: argv.tarHost, 60 | dataDirectory: argv.dataDirectory, 61 | sharedFetchSecret: argv.sharedFetchSecret 62 | }, function (err, data) { 63 | if (err) { 64 | message += '\terror. ' + err 65 | } else { 66 | message += '\trepaired!' 67 | } 68 | 69 | console.log(message) 70 | 71 | cb() 72 | }) 73 | }, { 74 | check: argv.check, 75 | scan: argv.scan, 76 | sharedFetchSecret: argv.sharedFetchSecret 77 | }) 78 | 79 | setInterval(function () { 80 | console.log(stream.sequence) 81 | }, 5000).unref() 82 | } 83 | 84 | module.exports = utils.decorate(cmd, __filename) 85 | 86 | function timerwrap (cb, name) { 87 | var timer 88 | function wrapped () { 89 | clearTimeout(timer) 90 | console.log('[timeout] ', name) 91 | cb.apply(this, arguments) 92 | } 93 | setTimeout(function () { 94 | wrapped() 95 | }, 180000) 96 | 97 | return wrapped 98 | } 99 | -------------------------------------------------------------------------------- /cmd/manage-tokens.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var command = utils.command(__filename) 4 | 5 | var cmd = { 6 | command: command, 7 | desc: 'manage npm Enterprise deploy tokens', 8 | usage: '$0 ' + command + ' -- [opts]', 9 | epilog: 'manage the tokens on your npm Enterprise appliance' 10 | } 11 | 12 | module.exports = utils.decorate(cmd) 13 | -------------------------------------------------------------------------------- /cmd/remove-package.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var command = utils.command(__filename) 4 | 5 | var cmd = { 6 | command: command, 7 | desc: 'remove a package from your registry', 8 | usage: '$0 ' + command + ' package-name -- [opts]', 9 | demand: 1, 10 | demandDesc: 'you must provide a package to remove' 11 | } 12 | 13 | module.exports = utils.decorate(cmd) 14 | -------------------------------------------------------------------------------- /cmd/reset-follower.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var cmd = { 4 | desc: 'reset the upstream registry follower', 5 | epilog: 'reset the sequence # of the upstream registry follower' 6 | } 7 | 8 | module.exports = utils.decorate(cmd, __filename) 9 | -------------------------------------------------------------------------------- /cmd/ssh.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var cmd = { 4 | desc: 'ssh into the npm Enterprise registry container' 5 | } 6 | 7 | module.exports = utils.decorate(cmd, __filename) 8 | -------------------------------------------------------------------------------- /cmd/update-license.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var cmd = { 4 | desc: 'update the license associated with your npm Enterprise appliance', 5 | epilog: 'update the license on your npm Enterprise appliance' 6 | } 7 | 8 | module.exports = utils.decorate(cmd, __filename) 9 | -------------------------------------------------------------------------------- /cmd/usage.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/utils') 2 | 3 | var command = utils.command(__filename) 4 | 5 | var cmd = { 6 | command: command, 7 | desc: 'report npme usage information', 8 | usage: '$0', 9 | } 10 | 11 | module.exports = utils.decorate(cmd) 12 | -------------------------------------------------------------------------------- /lib/check-for-update.js: -------------------------------------------------------------------------------- 1 | var updateNotifier = require('update-notifier') 2 | var packageJson = require('../package.json') 3 | var isNpm = require('is-npm') 4 | var boxen = require('boxen') 5 | var chalk = require('chalk') 6 | var ansiAlign = require('ansi-align') 7 | 8 | module.exports = function () { 9 | // super hacky: skip for autoinstall command 10 | if (isNpm || (process.argv.length > 2 && process.argv[2] === 'autoinstall')) return 11 | 12 | // beware file permissions problems b/w sudo and not 13 | try { 14 | var notifier = updateNotifier({ 15 | pkg: packageJson 16 | }) 17 | notify(notifier, { 18 | updateCommand: 'sudo npm i -g --ignore-scripts ', 19 | borderStyle: 'double-single' 20 | }) 21 | } catch (_) { 22 | var msg = ansiAlign( 23 | chalk.yellow('npme update check failed') + 24 | '\nTry running as sudo next time, or run:\n' + 25 | chalk.cyan(' sudo chown -R $USER:$(id -gn $USER) ~/.config ') + 26 | '\nto give your user access to the update config' 27 | ) 28 | process.on('exit', function () { 29 | console.error('\n' + boxen(msg)) 30 | }) 31 | } 32 | } 33 | 34 | // argh! have to duplicate the UpdateNotifier.notify logic to use a custom message! 35 | function notify (notifier, opts) { 36 | if (!process.stdout.isTTY || isNpm || !notifier.update) { 37 | return notifier 38 | } 39 | 40 | opts = opts || {} 41 | 42 | var message = '\n' + boxen( 43 | ansiAlign( 44 | 'Update available ' + chalk.dim(notifier.update.current) + chalk.reset(' → ') + chalk.green(notifier.update.latest) + 45 | ' \nRun ' + chalk.cyan((opts.updateCommand || 'npm i -g ') + notifier.packageName) + ' to update' 46 | ), 47 | { 48 | padding: 1, 49 | margin: 1, 50 | borderColor: 'yellow', 51 | borderStyle: opts.borderStyle || 'round' 52 | } 53 | ) 54 | 55 | if (opts.defer === undefined) { 56 | process.on('exit', function () { 57 | console.error(message) 58 | }) 59 | } else { 60 | console.error(message) 61 | } 62 | 63 | return notifier 64 | } 65 | -------------------------------------------------------------------------------- /lib/download-tar.js: -------------------------------------------------------------------------------- 1 | 2 | var mkdirp = require('mkdirp') 3 | var fs = require('fs') 4 | var request = require('request') 5 | var path = require('path') 6 | var mis = require('mississippi') 7 | var once = require('once') 8 | var crypto = require('crypto') 9 | 10 | module.exports = function (name, tarball, shasum, dir, sharedFetchSecret, cb) { 11 | cb = once(cb) 12 | 13 | var basename = path.basename(tarball) 14 | var targetDir = path.join(dir, name[0], name, '_attachments') 15 | var targetName = path.join(targetDir, basename) 16 | 17 | fs.exists(targetName, function (exists) { 18 | if (exists) return cb(false, tarball) 19 | 20 | reallyDownload(tarball, targetDir, targetName, shasum, sharedFetchSecret, function (err) { 21 | cb(err, tarball) 22 | }) 23 | }) 24 | } 25 | 26 | function reallyDownload (tarUrl, targetDir, targetName, shasum, sharedFetchSecret, cb) { 27 | mkdirp(targetDir, function (err) { 28 | if (err) return cb(err) 29 | 30 | var tmpname = targetName + '.tmp' 31 | 32 | var responded = false 33 | 34 | var hash = crypto.createHash('sha1') 35 | 36 | var opts = { 37 | method: 'GET', 38 | url: tarUrl 39 | } 40 | 41 | console.info('attempt to download ' + tarUrl) 42 | 43 | if (sharedFetchSecret) opts.qs = { sharedFetchSecret: sharedFetchSecret } 44 | 45 | var req = request(tarUrl) 46 | req.on('response', function (res) { 47 | responded = true 48 | if (res.statusCode !== 200) { 49 | var e = new Error() 50 | e.statusCode = res.statusCode 51 | return cb(e) 52 | } 53 | 54 | mis.pipe(res, mis.through(function (chunk, enc, cb) { 55 | hash.update(chunk) 56 | cb(false, chunk) 57 | }), fs.createWriteStream(tmpname), function (err) { 58 | if (err) { 59 | console.error('failed downloading ' + tarUrl + ' err = ' + err.message) 60 | return cb(err) 61 | } 62 | 63 | var resultShasum = hash.digest().toString('hex') 64 | 65 | if (resultShasum !== shasum) { 66 | return cb(new Error('shasum mismatch got: ' + resultShasum + ' need ' + shasum)) 67 | } 68 | 69 | fs.rename(tmpname, targetName, function (err) { 70 | // yay! all done. 71 | console.info('finished downloading ' + tarUrl) 72 | cb(err, tarUrl) 73 | }) 74 | }) 75 | }).on('error', function (err) { 76 | if (!responded) cb(err) 77 | }) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /lib/replication-check.js: -------------------------------------------------------------------------------- 1 | var ccf = require('concurrent-couch-follower') 2 | var request = require('request') 3 | var retry = require('./retry-thing.js') 4 | var downloadTar = require('./download-tar.js') 5 | var normalize = require('normalize-registry-metadata') 6 | var url = require('url') 7 | 8 | // 9 | // config.scan we stream all the documents in this database 10 | // config.check we request each doc in the stream from scan to see if check has the same versions 11 | // 12 | 13 | module.exports = function (handler, config) { 14 | if (!config.scan || !config.check) { 15 | throw new Error('please pass a db to scan and a cb to validate against') 16 | } 17 | 18 | var endSeq 19 | updateSeq(config.scan, function (err, seq) { 20 | if (!seq) throw new Error('failed to read sequence id from scan db ' + config.scan + '\n' + err) 21 | 22 | endSeq = seq 23 | console.log('ending at sequence ', endSeq) 24 | }) 25 | 26 | var ended = false 27 | 28 | var stream = ccf(function (data, cb) { 29 | var doc = normalize(data.doc) 30 | // not a package. 31 | if (!doc) return cb() 32 | if (ended) return cb() 33 | 34 | if (data.seq >= endSeq) { 35 | ended = true 36 | console.log('reached ' + endSeq + ' done') 37 | stream.end() 38 | } 39 | 40 | doc.name = doc.name || doc._id 41 | 42 | fetchPackage(config.check, doc.name, config.sharedFetchSecret, function (err, checkDoc) { 43 | var output = { 44 | name: doc.name, 45 | versions: [], 46 | scan: data.doc, 47 | check: checkDoc 48 | } 49 | 50 | if (err) { 51 | output.error = err 52 | if (err.statusCode === 404) { 53 | // no action probably. but send to the handler for logging? 54 | // output.missing = true 55 | return cb() 56 | } 57 | 58 | handler(output, cb) 59 | return 60 | } 61 | 62 | Object.keys(checkDoc.versions || {}).forEach(function (version) { 63 | if (!doc.versions[version]) { 64 | output.versions.push(version) 65 | } 66 | }) 67 | 68 | if (output.versions.length) { 69 | return handler(output, cb) 70 | } 71 | 72 | // nothing wrong. 73 | cb() 74 | }) 75 | }, { 76 | db: config.scan, 77 | since: 0, 78 | include_docs: true, 79 | sequence: function (seq, cb) { 80 | // dont save sequence. 81 | stream.sequence = seq 82 | cb() 83 | } 84 | }) 85 | 86 | stream.on('error', function (err) { 87 | if ((err + '').indexOf('premature') > -1) { 88 | console.log('[error] unexpected error on scan stream.') 89 | } 90 | }) 91 | 92 | return stream 93 | } 94 | 95 | module.exports.repairVersions = function (options, cb) { 96 | var scanDb = options.db 97 | var oldDoc = options.oldDoc // replica 98 | var checkDoc = options.currentDoc // primary 99 | var versions = options.versions 100 | var tarHost = options.tarHost 101 | var ignore404 = options.ignore404 102 | var dataDir = options.dataDirectory 103 | var sharedFetchSecret = options.sharedFetchSecret 104 | 105 | if (!versions.length) return cb() 106 | 107 | var count = versions.length 108 | versions.forEach(function (v) { 109 | var versionDoc = checkDoc.versions[v] || {} 110 | var tarball = (versionDoc.dist || {}).tarball 111 | var shasum = (versionDoc.dist || {}).shasum 112 | 113 | if (!tarball) { 114 | return finished(new Error(tarball)) 115 | } 116 | 117 | if (tarHost) { 118 | if (tarHost.indexOf('://') === -1) { 119 | tarHost = 'http://' + tarHost 120 | } 121 | 122 | var parsed = url.parse(tarball) 123 | var hostParsed = url.parse(tarHost) 124 | parsed.host = hostParsed.host 125 | 126 | parsed.protocol = hostParsed.protocol 127 | 128 | tarball = url.format(parsed) 129 | } 130 | 131 | retry(function (done) { 132 | downloadTar(checkDoc.name, tarball, shasum, dataDir, sharedFetchSecret, done) 133 | }, 4, function (err, data) { 134 | // if the tarball gets a 404 after retries im going to consider this not an error 135 | finished(err, data) 136 | }) 137 | }) 138 | 139 | var errors = 0 140 | var report = {} 141 | function finished (err, tar) { 142 | if (err) { 143 | report[tar] = 'error: ' + err 144 | if (!ignore404 || err.statusCode !== 404) { 145 | errors++ 146 | } 147 | } 148 | if (!--count) { 149 | if (errors.length) { 150 | var e = new Error('error downloading tarballs. see err.report. ' + Object.keys(report).join(', ')) 151 | e.report = report 152 | return cb(e) 153 | } 154 | checkDoc._rev = oldDoc._rev 155 | putPackage(scanDb, checkDoc, cb) 156 | } 157 | } 158 | } 159 | 160 | module.exports.updateSeq = updateSeq 161 | 162 | function updateSeq (db, cb) { 163 | request(db, function (err, body, data) { 164 | cb(err, (json(data) || {}).update_seq) 165 | }) 166 | } 167 | 168 | module.exports.fetchPackage = fetchPackage 169 | 170 | function fetchPackage (db, name, sharedFetchSecret, cb) { 171 | var opts = {url: db + '/' + (name || '').replace('/', '%2f'), timeout: 10000} 172 | if (sharedFetchSecret) opts.qs = { sharedFetchSecret: sharedFetchSecret } 173 | request(opts, function (err, res, body) { 174 | if (err) return cb(err) 175 | if (res.statusCode !== 200) { 176 | var e = new Error('status code ' + res.statusCode) 177 | e.statusCode = res.statusCode 178 | return cb(e) 179 | } 180 | 181 | var obj = json(body) 182 | if (!obj) return new Error('invalid json returned. ' + (body + '').substr(0, 200)) 183 | 184 | cb(false, obj) 185 | }) 186 | } 187 | 188 | module.exports.putPackage = putPackage 189 | 190 | function putPackage (db, doc, cb) { 191 | request.put({ 192 | url: db + '/' + doc.name.replace('/', '%2f'), 193 | headers: { 194 | 'content-type': 'application/json' 195 | }, 196 | body: JSON.stringify(doc) 197 | }, function (err, res, body) { 198 | if (err) return cb(err) 199 | 200 | console.log(err, res.statusCode, body + '') 201 | 202 | var obj = json(body) 203 | if (!obj.ok) return cb(new Error('did not get ok respond from couchdb. ' + body)) 204 | 205 | // yay 206 | cb() 207 | }) 208 | } 209 | 210 | function json (o) { 211 | try { 212 | return JSON.parse(o) 213 | } catch (e) {} 214 | } 215 | -------------------------------------------------------------------------------- /lib/retry-thing.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (thing, times, cb) { 3 | var attempts = 0 4 | 5 | ;(function retry () { 6 | thing(function (err, data) { 7 | --times 8 | attempts++ 9 | if (!err || times <= 0) return cb(err, data, attempts) 10 | retry() 11 | }) 12 | })() 13 | } 14 | /* 15 | module.exports(function(done){ 16 | console.log('action') 17 | done(true) 18 | },2,function(){ 19 | console.log('done') 20 | }) 21 | */ 22 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var checkForUpdate = require('./check-for-update') 2 | var path = require('path') 3 | var spawn = require('child_process').spawn 4 | var which = require('which') 5 | 6 | var adminCommand = 'replicated admin ' 7 | try { 8 | which.sync('replicated') 9 | } catch (err) { 10 | if (err.code === 'ENOENT') adminCommand = 'docker exec -it replicated replicated admin ' 11 | } 12 | 13 | exports.adminCommand = adminCommand 14 | 15 | var cwd = exports.cwd = path.resolve(__dirname, '../') 16 | 17 | var exec = exports.exec = function (command, sudo, cb) { 18 | var commands = ['-c'] 19 | if (sudo) command = 'sudo -E ' + command 20 | commands.push(command) 21 | 22 | var proc = spawn('sh', commands, { 23 | cwd: cwd, 24 | env: process.env, 25 | stdio: 'inherit' 26 | }) 27 | 28 | proc.on('close', function (code) { 29 | cb(code) 30 | }) 31 | } 32 | 33 | var command = exports.command = function (filename) { 34 | return path.basename(filename, path.extname(filename)) 35 | } 36 | 37 | var defaultBuilder = exports.defaultBuilder = function (cmd) { 38 | return function (yargs) { 39 | if (cmd.usage) yargs.usage(cmd.usage) 40 | if (cmd.options) yargs.options(cmd.options) 41 | if (cmd.demand) { 42 | if (cmd.demandDesc) yargs.demand(cmd.demand, cmd.demandDesc) 43 | else yargs.demand(cmd.demand) 44 | } 45 | if (cmd.epilog) yargs.epilog(cmd.epilog) 46 | return yargs 47 | } 48 | } 49 | 50 | var defaultHandler = exports.defaultHandler = function () { 51 | return function (argv) { 52 | exec(adminCommand + argv._.join(' '), argv.sudo, function () {}) 53 | } 54 | } 55 | 56 | exports.decorate = function (cmd, filename) { 57 | if (!cmd.command && filename) cmd.command = command(filename) 58 | if (!cmd.usage) cmd.usage = '$0 ' + cmd.command + ' [opts]' 59 | if (cmd.desc && !cmd.epilog) cmd.epilog = cmd.desc 60 | if (!cmd.builder) cmd.builder = defaultBuilder(cmd) 61 | if (!cmd.handler) cmd.handler = defaultHandler() 62 | 63 | var builder = cmd.builder 64 | cmd.builder = function (yargs) { 65 | checkForUpdate() 66 | return builder(yargs) 67 | } 68 | 69 | return cmd 70 | } 71 | -------------------------------------------------------------------------------- /npme-gce.jinja: -------------------------------------------------------------------------------- 1 | # step-by-step guide to writing templates: 2 | # https://cloud.google.com/deployment-manager/step-by-step-guide/using-template-and-environment-variables 3 | 4 | # documentation for resource types: 5 | # https://cloud.google.com/deployment-manager/configuration/create-configuration-file#declaring_a_resource_type 6 | 7 | # boot an instance in zone=us-east1-d 8 | # gcloud deployment-manager deployments create npme-deployment --config npme.jinja --properties="zone=us-east1-d" 9 | resources: 10 | - type: compute.v1.network 11 | name: npme-network 12 | properties: 13 | zone: {{ properties["zone"] }} 14 | - type: compute.v1.firewall 15 | name: npme-firewall 16 | properties: 17 | zone: {{ properties["zone"] }} 18 | network: $(ref.npme-network.selfLink) 19 | allowed: 20 | - IPProtocol: tcp 21 | ports: 22 | - "8800" 23 | - "8080" 24 | - "8081" 25 | - "22" 26 | sourceRanges: 27 | - "0.0.0.0/0" 28 | - type: compute.v1.instance 29 | name: npm-enterprise 30 | properties: 31 | zone: {{ properties["zone"] }} 32 | machineType: https://www.googleapis.com/compute/v1/projects/{{ env["project"] }}/zones/{{ properties["zone"] }}/machineTypes/n1-standard-4 33 | disks: 34 | - deviceName: boot 35 | type: PERSISTENT 36 | boot: true 37 | autoDelete: true 38 | initializeParams: 39 | sourceImage: https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1404-trusty-v20150909a 40 | diskSizeGb: 50 41 | networkInterfaces: 42 | - network: $(ref.npme-network.selfLink) 43 | # Access Config required to give the instance a public IP address 44 | accessConfigs: 45 | - name: External NAT 46 | type: ONE_TO_ONE_NAT 47 | metadata: 48 | items: 49 | - key: startup-script 50 | value: | 51 | #! /bin/bash 52 | curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - 53 | sudo apt-get install -y nodejs 54 | sudo npm i -g npm@latest 55 | sudo yes No | npm install npme -g --unsafe 56 | sudo sh -c "sudo curl -XGET https://raw.githubusercontent.com/npm/npme-installer/master/boot.sh >> /etc/boot.sh" 57 | sudo chmod 777 /etc/boot.sh 58 | sudo sh -c "printf \"/etc/boot.sh\" >> /etc/bash.bashrc" 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npme", 3 | "version": "4.3.0", 4 | "description": "One-step-installer for npm Enterprise servers", 5 | "bin": "bin/npme.js", 6 | "scripts": { 7 | "pretest": "standard", 8 | "postinstall": "node ./bin/npme.js autoinstall", 9 | "release": "standard-version", 10 | "test": "tape test/*.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:npm/npme-installer.git" 15 | }, 16 | "author": "Ben Coe ", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/npm/npme-installer/issues" 20 | }, 21 | "homepage": "https://www.npmjs.com/enterprise", 22 | "dependencies": { 23 | "ansi-align": "^1.0.0", 24 | "boxen": "^0.5.1", 25 | "chalk": "^1.1.3", 26 | "concurrent-couch-follower": "^1.2.0", 27 | "figures": "^1.7.0", 28 | "inquirer": "^3.0.1", 29 | "is-npm": "^1.0.0", 30 | "mississippi": "^1.3.0", 31 | "mkdirp": "^0.5.1", 32 | "normalize-registry-metadata": "^1.1.2", 33 | "once": "^1.4.0", 34 | "public-ip": "^1.2.0", 35 | "request": "^2.79.0", 36 | "standard": "^9.0.1", 37 | "update-notifier": "^0.6.3", 38 | "which": "^1.2.12", 39 | "yargs": "^7.0.2-candidate.1" 40 | }, 41 | "devDependencies": { 42 | "nyc": "^10.1.2", 43 | "standard-version": "^2.1.2", 44 | "tape": "^4.6.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /replicated-license-retrieval.json: -------------------------------------------------------------------------------- 1 | { 2 | "logo": { 3 | "url": "https://s3.amazonaws.com/replicated-vendor-assets/66045325f001a1e0ccde2d457cb2b30b/66045325f001a1e0ccde2d457cb2b30b.png" 4 | }, 5 | "title": "Import your npm Enterprise license", 6 | "appid": "66045325f001a1e0ccde2d457cb2b30b", 7 | "channelid": "83cda3def52f72f7612c2f776d7444a9", 8 | "fields": [ 9 | { 10 | "title": "", 11 | "description": "", 12 | "items": [ 13 | { 14 | "name": "billing_email", 15 | "title": "Billing Email", 16 | "type": "text", 17 | "required": true 18 | }, 19 | { 20 | "name": "license_key", 21 | "title": "License Key", 22 | "type": "text", 23 | "required": true 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/replication-check.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | test('WIP', function (t) { 4 | t.end() 5 | }) 6 | --------------------------------------------------------------------------------