Randomness
in Code

Creating a Private npm Registry with Sinopia

May 10, 2014

Those who use Node.js at work will sooner or later run into the issue of internal packages. Yes, you could keep all your internal code within a single project, or yes, you could reference your dependencies as GitHub repos in the package.json file, but neither are very elegant solutions. Recommended alternatives for this include cloning the entire npm repository, or using a proxy, similar to that of npm-delegate or Kappa. In addition, some places will host private npm repositories for you, while npm is working on rolling out private modules, and others are discussing how npm should handle namespaces.

With all of that in mind, I've found Sinopia to be one of the easiest solutions for this problem. Sinopia does not require a Couch Database, nor much setup at all, and complies with the request/response syntax of npm. Requests to Sinopia can proxy to registry.npmjs.org, and once packages are pulled down, they're stored locally on the file system. But Sinopia also allows for internal packages, which are published privately and not accessible externally.

Install and Initial Configuration of Sinopia

Sinopia is a node package (yes, that's meta), and therefore can be installed via npm as follows:
$ npm install -g sinopia

Once it's installed, I prefer to create a directory for Sinopia which contains the configuration file along with the node packages:

$ mkdir sinopia; cd sinopia

From this new directory, start up Sinopia to build the initial configuration file, and soon the storage directory once packages have been published or pulled down. For example, on initial startup in an empty directory, the output will resemble the following:

$ sinopia
Config file doesn't exist, create a new one? (Y/n) Y
===========================================================
 Creating a new configuration file: "./config.yaml"

 If you want to setup npm to work with this registry,
 run following commands:

 $ npm set registry http://localhost:4873/
 $ npm set always-auth true
 $ npm adduser
   Username: admin
   Password: ZAsbOqhb8Pc
===========================================================
 warn  --- Server is listening on http://localhost:4873/
 

Here I think the output is a bit confusing from what has actually taken place, mostly based on the fact that the npm adduser command is very confusing. At this point in your setup, Sinopia has created a basic config.yaml file, and within that file created a user admin with the password (specified in the output above) ZAsbOqhb8Pc. However, your client side npm won't know about this, so you'll need to login (or "adduser"). So to summarize, the user has been "created" in Sinopia's registry, but you have yet to login as that user through npm (we'll do that later).

If you were to look in that config.yaml file, you'd see the following:

# path to a directory with all packages
storage: ./storage

# a list of users
users:
  admin:
    # crypto.createHash('sha1').update(pass).digest('hex')
    password: 0b0ffed5aaa756c67949f8b2e4e59512ca39a695


...

This specifies that the storage location is the local storage directory (note that the directory won't exist until the first node package is pulled down or pushed up). It also specifies the single user admin with a password hash of 0b0ffed5aaa756c67949f8b2e4e59512ca39a695 (which is a hash of ZAsbOqhb8Pc generated from: crypto.createHash('sha1').update(pass).digest('hex')). If you wish to change the password of a user, you'll need to run the password through the hash crypto code and paste the output in the configuration file.

From here on when you start Sinopia from that same directory, it will startup and load in this configuration file. Anytime a change is made to the configuration file, you'll need to restart Sinopia.

Change Sinopia Listen Address and Port

By default Sinopia will listen on localhost and port 4873. If your machine is hosting node packages for others outside of the local machine (quite likely), you'll need to change this to the hostname and port you'd like to expose. So for instance if you have your machine running as http://my-internal.npmjs.com then you should add the following line to the configuration file:

# you can specify listen address (or simply a port)
listen: my-internal.npmjs.com:80

Configuring npm to use Sinopia

In npm's world, Sinopia is just another npm registry — there is no difference in how it responds. So from the client standpoint, you'll only need to point to this new registry instead of registry.npmjs.org. To do this, run the command (replacing localhost:4873 with the host and port running Sinopia):

$ npm set registry "http://localhost:4873/"

You should see a .npmrc file created in your home directory with the following:

registry = http://localhost:4873/

Now when you run any npm commands which should interact with the registry, you'll interact with your Sinopia instance. Sinopia will by default proxy requests to registry.npmjs.org, so at this point all you've built is a proxy, but you can easily begin publishing private packages as well.

Publish a Private Package

Sinopia, like registry.npmjs.org, is setup with access restrictions. The admin account that was created on initial setup can be used to login to Sinopia. Run the following command, entering in the password that was presented during initial setup of Sinopia (for us, that was ZAsbOqhb8Pc):

$ npm login
Username: admin
Password:
Email: (this IS public) myemail@domain.com

(Any email address can be used here, since there's not true tracking of email addresses done on the server side.)

The .npmrc file will now contain the additional information to authorize you with the Sinopia server. This will grant you publish access to Sinopia.

Prefixes

Before you publish your private package, I'd suggest setting up a prefix for all internal packages so that you can handle them correctly. For example, if your company is named "IBM", I'd suggest prefixing all projects with "ibm-". So if you were to create a package "awesome-module", you should instead call it "ibm-awesome-module". (Collisions could occur with simple prefixes, so it's recommended you use a prefix that would be unlikely to collide with public packages).

With the prefix all setup, inform Sinopia that projects which contain this prefix are internal only, and should not be proxied through registry.npmjs.org. Inside the config.yaml file, update the packages section to the following:

packages:
  'ibm-*':
    # allow all users to read packages ('all' is a keyword)
    # this includes non-authenticated users
    allow_access: all

    # allow 'admin' to publish packages
    allow_publish: admin

    # no proxies, all request should be internal only

  '*':
    # allow all users to read packages ('all' is a keyword)
    # this includes non-authenticated users
    allow_access: all

    # don't allow anyone to publish
    allow_publish: none

    # if package is not available locally, proxy requests to 'npmjs' registry
    proxy: npmjs

Packages that contain the ibm- prefix will now be handled differently from all other packages (*) within Sinopia. The above code states that packages which contain the ibm- prefix can be read by anyone, but only published by the admin user. Additionally, these packages will not include proxy requests to registry.npmjs.org, and instead be handled internally only. All other packages will be proxied to registry.npmjs.org. This configuration also does not allow anyone to publish packages that are not prefixed with ibm- (since these are considered "public" packages and should be pushed through registry.npmjs.org). Though it's not required, it might be easier to restrict this way to avoid any problems down the road.

Sinopia should be setup now to handle retrieving both public and private projects and publishing internal projects. There's one last thing that should be done to avoid accidental publishing of internal projects externally.

Internal Project Configuration

Within the internal project source code, modify the package.json and add the publishConfig property to point to your Sinopia server (of course change to your specific host and port):

"publishConfig": {
    "registry": "http://localhost:4873/"
}

This forces users who issue a npm publish command to publish to your Sinopia server rather than the external registry.npmjs.org. The configuration in the .npmrc file should force communication through the Sinopia server, but if someone forgets to set all that up, this configuration won't allow them to publish externally. More information on the publishConfig configuration setting can be found here.

Publish!

With all that done, issue the command to publish like you would normally:

$ npm publish

You should see output specifying your Sinopia server address in the npm PUT commands on the client, and also see output on the Sinopia server console. You can verify the package was pushed by viewing the storage directory on the Sinopia server.