├── .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 | [](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 |
--------------------------------------------------------------------------------