如何搭建一个私有npm仓库

业界主流的私有npm仓库搭建的主流方案有如下几种:

  1. 付费购买
  2. 使用 git+ssh 这种方式直接引用到 GitHub 项目地址
  3. 使用 Sinopia
  4. 使用 cnpmjs.org

第一种,一是考虑到公司可能不会提供经费,二npm在国内访问很慢,就是花钱也买不到好的体验。

第二种,不能更新即 npm update, 不能使用semver(语义化版本规范)。

 

那么较好的选择就只剩下第三种和第四种。

 

下面将分别使用基于Sinopia和基于 cnpmjs.org 这两种方案来搭建私有npm仓库并进行总结。

1. Sinopia方案篇

1.1 服务端部署

安装

前置工作:配置nodejs及npm环境

npm install -g sinopia

启动

sinopia
warn  --- config file - /home/map/.config/sinopia/config.yaml
warn  --- http address - http://localhost:4873/

 

此时访问localhost:4873,可获取html文件并且服务端响应正常,表示安装成功。

 

$ curl localhost:4873






...

 

服务端响应

$ sinopia
...
http  <-- 200, user: undefined, req: 'GET /', bytes: 0/10896

 

配置

运行sinopia,自动生成的工作目录如下(通过第一个warn可以看到具体路径):

$ tree /home/map/.config/sinopia/
/home/map/.config/sinopia/
|-- config.yaml //存放所有配置信息
|-- htpasswd        //存放所有账户信息
`-- storage         //存放私有npm包及缓存公有包
|-- npm_test
|   |-- npm_test-1.0.0.tgz
|   |-- npm_test-1.0.1.tgz
|   `-- package.json
`-- sinopia
       `-- package.json

3 directories, 6 files

 

config.yaml默认配置

# This is the default config file. It allows all users to do anything,
# so don't use it on production systems.
#
# Look here for more config file examples:
# https://github.com/rlidwka/sinopia/tree/master/conf

# path to a directory with all packages
storage: ./storage      //npm包存放的路径

auth:
 htpasswd:
file: ./htpasswd    //保存用户的账号密码等信息
# Maximum amount of users allowed to register, defaults to "+inf".
# You can set this to -1 to disable registration.
#max_users: 1000 //默认为1000,改为-1,禁止注册

# a list of other known repositories we can talk to
uplinks:
 npmjs:
url: https://registry.npmjs.org/    
//拉取公共包的地址源,默认为npm的官网,可以使用淘宝的npm镜像地址

packages: //配置权限管理
'@*/*':
# scoped packages
   access: $all
publish: $authenticated
'*':

# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
   access: $all

# allow all known users to publish packages
# (anyone can register by default, remember?)
publish: $authenticated

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

# log settings
logs:
- {type: stdout, format: pretty, level: http}
#- {type: file, path: sinopia.log, level: info}

 

外网访问配置

通过在config.yaml中修改服务默认的监听端口,从而可以通过外网访问 sinopia 仓库。

listen: 0.0.0.0:4873

外网通过http://[IP | 域名]:[端口]的形式来访问。

浏览器外网访问如图:

如何搭建一个私有npm仓库

图4 外网访问示例

 

账号配置

config.yaml 中auth部分对应账号的管理,默认可以通过客户端npm adduser添加账号。可以通过max_users:-1禁止客户端创建,而通过我们修改htpasswd文件来管理用户。

htpasswd文件示例:

lisi:{SHA}????????????????=:autocreated 2016-02-05T15:39:19.960Z
wangwu:{SHA}????????????????=:autocreated 2016-02-05T17:59:05.041Z

 

密码是被加密过的,是简单的SHA1哈稀之后再转换成 Base64 。

1.2 客户端配置

配置npm registry

建议客户端使用nrm 进行npm registry地址管理和切换

安装

npm install -g nrm

添加sinopia仓库地址

nrm add sinopia http://192.168.xx.xx:4873

切换私有仓库

nrm use sinopia

查看所有仓库地址(星标为当前仓库源)

nrm ls
npm ---- https://registry.npmjs.org/
cnpm --- http://r.cnpmjs.org/
taobao - https://registry.npm.taobao.org/
nj ----- https://registry.nodejitsu.com/
rednpm - http://registry.mirror.cqupt.edu.cn/
npmMirror https://skimdb.npmjs.com/registry/
edunpm - http://registry.enpmjs.org/
* sinopia http://192.168.xx.xx:4873/

 

1.3 发包

切换到私有仓库之后,发包的操作跟npm发包基本无差别。

登录账号之后:

npm publish
+ [email protected]

ps: 版本号重复的情况再次发布的包不会主动更新,并且发布不会有错误提示,更新包务必更新版本号。

发布成功后私有仓库站点会显示包情况,README.md文件会作为详情描述展开。

如何搭建一个私有npm仓库

图5 包发布成功示例图

2. cnpmjs.org 方案篇

 

2.1 服务端部署

 

官方依赖如下图:

如何搭建一个私有npm仓库

图6 官方依赖配置

我这边的配置:

  • 服务器Linux version 3.10.0_1-0-0-8
  • node v8.9.0
  • npm v5.5.1
  • mysql 5.1.73

安装 cnpmjs.org

 

npm i -g cnpmjs.org

我安装的是3.0.0-beta.1。

 

修改 cnpmjs.org 配置文件

 

cnpmjs.org 默认安装路径:/usr/local/lib/node_modules/cnpmjs.org

打开配置文件: cnpmjs.org/config/index.js

部分配置项说明

/*
 * server configure //服务器配置
 */
 
registryPort: 7001,         //仓库访问端口(执行发布安装)
webPort: 7002,              //展示查询站点访问端口
bindingHost: '',   //监听绑定的 Host,默认127.0.0.1,外网访问注释掉此项即可


/**
* database config //数据库相关设置
*/

database: {
    db: 'cnpmjs',      //数据库名称
    username: 'root',       //数据库访问账号
    password: '123456',           //数据库访问密码
    
    // the sql dialect of the database
    // - currently supported: 'mysql', 'sqlite', 'postgres', 'mariadb'
    dialect: 'mysql',       //使用数据库,默认sqlite,这里我们改成mysql
    
    // custom host; default: 127.0.0.1
    host: '127.0.0.1',      //数据库访问IP,通常127.0.0.1
    
    // custom port; default: 3306
    port: 3306,             //数据库访问端口,通常3306
    
    
// 模块文件存储,默认将发布的私有模块跟缓存公共模块存储在本地文件系统中,路径~/.cnpmjs.org/nfs ,也就是模块文件都存储在这个目录下;或者可以选择三方储存方式比如七牛等,着这里配置插件;也支持接口开发扩展储存;

nfs: require('fs-cnpm')({
    dir: path.join(dataDir, 'nfs')
}),
    
// registry url name //模块注册列表访问域名,默认r.cnpmjs.org,安装模块时会到这个域名下查找,这个默认设置略坑,建议没有外网域名的先清空回头再配
registryHost: '',


// default system admins    //默认管理员账号
  admins: {
    // name: email
    //fengmk2: '[email protected]',
    admin: '[email protected]',
    //dead_horse: '[email protected]',
  },
  
 
/*
 * registry mode config  私有模块发布相关配置
*/

  //是否开启私有模式,默认为 false;
  //私有模式下只有管理员能发布模块,其他账号只有同步权限
  //非私有模式,注册用户都可以发布模块
  enablePrivate: false, 

  // registry scopes
  //若为非私有模式发布则此项必填,非管理员发布模块式命名必须以scopes字段开头,模块命名示例“@cnpm/packagename”
  //更多了解npm-scope请查阅https://docs.npmjs.com/misc/scope
  scopes: [ '@cnpm', '@cnpmtest', '@cnpm-test' ],

  // 私有模块非scopes白名单,各种非以scope方式发布的老模块的白名单管理,数组形式维护
  privatePackages: [],


/**
* sync configs 同步源仓库相关设置
*/

//npm官方registry地址,不会直接从这个地址同步模块,但有时会从这里获取模块信息,除非必要请勿更改
officialNpmRegistry: 'https://registry.npmjs.com',
officialNpmReplicate: 'https://replicate.npmjs.com',

//同步模块上游registry地址
sourceNpmRegistry: 'https://registry.npm.taobao.org',

//上游registry是否是cnpm,默认true,若要使用npm官方地址作为同步上游,请设置为false
sourceNpmRegistryIsCNpm: true,

//若安装时模块不存在,是否向源registry进行同步,默认true
syncByInstall: true,

// 同步模式选项
// none: 不进行同步,只管理用户上传的私有模块,公共模块直接从上游获取
// exist: 只同步已经存在于数据库的模块
// all: 定时同步所有源registry的模块
syncModel: 'exist', // 'none', 'all', 'exist'

// 同步时间间隔,默认10分钟
syncInterval: '10m',


// 是否同步模块中devDependencies,默认false
syncDevDependencies: false,

//用户账号系统接入,可以扩展接入公司的账号系统
//本文暂不涉及,详见https://github.com/cnpm/cnpmjs.org/wiki/Use-Your-Own-User-Authorization
userService: null,

//另外一个比较坑的默认设置,默认false,踩坑记录里详细说
enableAbbreviatedMetadata: true,

 

新建数据库并导表

 

用户名、密码进入数据库

mysql -uroot -p123456

ps:-u用户名 -p密码,初次使用的mysql还没创建系统账号时默认为空, mysql命令后直接回车后即可进入命令行。创建mysql账号,注意名称跟cnpm配置中相对应。

 

创建cnpm所需的数据库

create database cnpmjs;

切换到cnpm数据库

use cnpmjs;

导入cnpm数据库配置文件

文件位于cpm安装目录docs/db.sql下

source docs/db.sql;

查看导入的表

我这个版本共导入16个表

show tables;

tip:数据库配置这块如果觉得命令行太麻烦,可以使用可视化数据库管理工具,例如phpAdmin等进行管理。

 

跑起来

 

cnpmjs.org start

访问对应的IP加访问端口即可看到,展示站点如下图

如何搭建一个私有npm仓库

图7 start之后示意图

我们可以搜索我们想要的包,如果没有可以选择sync进行同步。

如何搭建一个私有npm仓库

图8 选择同步

选择同步之后,后有如下同步过程

如何搭建一个私有npm仓库

图9 同步过程

2.2 客户端配置

 

客户端我们需要安装cnpm工具进行模块管理

npm i -g cnpm

把cnpm的registry指向我们的私有npm服务ip,端口使用registry端口;

cnpm config set registry 192.xxx.x.x:7001

 

2.3 发布私有模块

 

私有仓库用户登录,填写用户名密码邮箱

cnpm login

命名行发布

cnpm publish

2.4 爬坑总结

 

cnpm的方案配置上要比sinopia复杂,所以踩的坑也多一点,在此记录一下。

 

(1) 数据库前期准备

 

在准备数据库的阶段mysql-V检查到系统已经预装了mysql,但运行mysql登录命令的时候却报错如下:

$ mysql
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)

于是我启动了服务,然鹅依旧报错

$ service mysqld start
mysqld: unrecognized service

查解决方法改配置文件依然没有效果,最终发现mysql-server没有安装:disappointed_relieved:,这个报错完全看不出来是这个问题:joy:。。。

yum install mysql-server

安装之后重新运行server就好了。

 

(2) 展示站点数据库访问失败

 

创建数据库,导表,改配置之后运行起来,访问站点页面也没啥问题,but为啥没有数据??打开请求一看,获取数据的接口500了。

查了log发现报少表,打开数据库确认确实少表,果然删了重新导。过程中有三个表报‘Unknown character set: ‘utf8mb4’导入失败,第一次没注意。。。

查后发现mysql在mysql5.6+的版本中才支持utf8mb4的编码格式,而我们的版本只支持utf8。

那就只有两个选择,第一改编码格式,第二升级数据库。

改过三张表的编码后,顺利导入,站点获取数据正常。

 

(3) 模块安装一直失败

 

命令行报错

如何搭建一个私有npm仓库

图10 命令行报错

其实这个请求地址一看就很可疑,这个地址正式config中registryHost的默认值,所以要么给这个域名绑IP访问,要么就把这设置项先空着,等有了外网域名的时候再改。

 

(4) 私有模块发布失败

 

报错反应模块命名不对,发布私有模块需要遵守scope的命名规范,带上相应的前缀名,具体情况参见npm-scope。

 

(5) 同步模块报错

 

sync error: TypeError: Cannot read property 'findAll' of null
... ...

这个问题最终是查issues解决的,貌似在1149加入了功能之后出现的,需要把enableAbbreviatedMetadata:设置为true解决issues#1236,#1289。

 

3. 番外篇——进程管理

由于使用命令行起的服务需要通过使用进程管理来守护。

为了提升私有库服务稳定性,我们选择pm2守护进程。

安装pm2:

npm install -g pm2

使用pm2 启动 sinopia:

pm2 start sinopia

常用pm2操作

pm2 list //查看所有进程
pm2 logs                        //查看日志
pm2 start app_name|app_id       //启动某个进程
pm2 stop app_name|app_id        //停止某个进程
pm2 restart app_name|app_id     //重启某个进程
pm2 delete app_name|app_id      //删除某个进程

总结

通过两种主流方案——基于Sinopia和基于cnpm搭建npm私有仓库——的尝试,我们发现:

 

  • Sinopia比较偏向于一个零配置、轻量型的私有npm模块管理工具,不需要额外的数据库配置,它内部自带小型数据库,支持私有模块管理的同时也支持缓存使用过的公共模块,发布及缓存的模块以静态资源形式本地存储。支持静态配置型用户管理机制,以及分层模块权限设置。
  • cnpm可以实现公共模块镜像更新以及私有模块管理,支持拓展多种存储形式,相对的数据库的配置较多,部署过程略复杂。是淘宝及多家大型公司搭建内部私有npm仓库选择的方案。
  • cnpm的方案在部署过程以及整体设计上要比Sinopia复杂的多,维护成本也比较高;但相对的也提供更高的扩展性,可以支持多种业务场景;

俗话说:脱离业务场景谈解决方案,都是耍流氓。

如果你是一个想要拥有私人npm仓库的个人开发者或者小团队,Sinopia完全可以满足需求;如果想要对私人npm服务做更多个性化定制方案,那么cnpm的扩展性支持更加友好。