在 CentOS 7 平台使用 cnpmjs.org 搭建 npm 私有仓储

一、安装系统并配置编译环境

1、下载 CentOS 7 系统镜像并安装,选择基本开发环境。

2、修改默认 yum 源为阿里云源,并添加 EPEL 源。

# 如提示 wget 不存在,可使用 curl 下载
# 备份默认源
sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
# 修改为阿里源
sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
# 添加 EPEL 源
sudo wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

3、安装 gcc 编译环境

sudo yum install gcc-c++ make

4、验证安装结果

# 验证 g++ 版本
g++ -v

使用内建 specs。
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
目标:x86_64-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
线程模型:posix
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)

# 验证 make 版本
make -v

GNU Make 3.82
Built for x86_64-redhat-linux-gnu
Copyright (C) 2010  Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

二、监测环境并安装 node.js

1、执行环境检测脚本,下载 node 在相应环境的已编译包

curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -

2、安装 node 和 npm

sudo yum -y install nodejs

3、验证安装结果

# 验证 node 版本
node -v
v8.9.4

# 验证 npm 版本
npm -v
5.6.0

三、安装并配置 cnpmjs.org

1、安装 cnpmjs.org依赖

# 安装 cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 安装 node-pre-gyp
cnpm install -g node-pre-gyp
# 安装 sqlite3 直接安装会报错 node-pre-gyp: 未找到命令
cnpm install -g sqlite3
# 安装 cnpmjs.org
cnpm install -g cnpmjs.org
# 创建配置目录 ~/.cnpmjs.org
cnpmjs.org start

2、验证安装结果

# 验证 cnpm 版本
cnpm -v

cnpm@5.2.0 (/usr/lib/node_modules/cnpm/lib/parse_argv.js)
npm@5.7.1 (/usr/lib/node_modules/cnpm/node_modules/npm/lib/npm.js)
node@8.9.4 (/usr/bin/node)
npminstall@3.3.0 (/usr/lib/node_modules/cnpm/node_modules/npminstall/lib/index.js)
prefix=/usr 
linux x64 3.10.0-327.el7.x86_64 
registry=https://registry.npm.taobao.org

# 验证 node-pre-gyp 版本
node-pre-gyp -v
v0.6.39

# 验证 sqlite3 版本
sqlite3 -version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668

# 验证 cnpmjs.org 版本
cnpmjs.org --version
2.19.4

3、添加防火墙例外

# 检查防火墙状态
systemctl status firewalld
# 启动防火墙,未启动会报错 FirewallD is not running
systemctl start firewalld
# 添加 7001 端口
firewall-cmd --zone=public --add-port=7001/tcp --permanent
# 添加 7002 端口
firewall-cmd --zone=public --add-port=7002/tcp --permanent
# 根据需要,关闭防火墙
systemctl stop firewalld

四、修改 cnpmjs.org 配置

1、修改 /root/.cnpmjs.org/config.json (实际使用中必须删除注释,否则 json 格式错误)

{
  "registryPort": 7001, // cnpmjs.org web 站点端口
  "webPort": 7002, // cnpmjs.org register 端口
  "bindingHost": "0.0.0.0", // 允许外部访问
  "handleSyncRegistry": "http://127.0.0.1:7001", // 同步源
  "registryHost": "registry.npm.xxxxxx.work", // cnpmjs.org register 域名
  "scopes": [
    "@xxxxxx" // 私有包 scope 名称
  ],
  "enablePrivate": false, // 允许所有登录用户发布私有包
  "syncModel": "none", // 不同步公共包
  "alwaysAuth": false, // 不强制用户认证
  "customReadmeFile": "/root/.cnpmjs.org/docs/web/readme.md", // 自定义首页
  "userService": "/root/.cnpmjs.org/services/custom_user_service.js", // 自定义用户认证
  "admins": {
    "admin": "admin@xxxx.com" // cnpmjs.org 管理员
  },
  "database": { // cnpmjs.org 数据库
    "db": "cnpmjs_test", // 数据库名称
    "username": "root", // 数据库用户名
    "password": "", // 数据库密码
    "dialect": "sqlite", // 数据库类型
    "host": "127.0.0.1", // 数据库 IP
    "port": 3306, // 数据库端口
    "storage": "/root/.cnpmjs.org/data.sqlite" // sqlite 数据库位置
  }
}

2、修改 cnpmjs.org 源码
/usr/lib/node_modules/cnpmjs.org/services/user.js

11 +  if(typeof(config.userService) === 'string') {
12 +    var CustomUserService = require(config.userService);
13 +    config.userService = new CustomUserService();
14 +  }

3、自定义首页 /root/.cnpmjs.org/docs/web/readme.md

# 公司 npm 私有仓储

[npm.xxxxxx.work](http://npm.xxxxxx.work) 基于 [cnpmjs.org](https://github.com/cnpm/cnpmjs.org) 搭建,使用 @xxxxxx 作为私有仓储的 [scope](https://docs.npmjs.com/getting-start)  
仅用于托管公司私有包,不同步公共包,可使用 npm 或 cnpm 客户端。  

## npm 或 cnpm 客户端配置

1. 拦截 @xxxxxx 作用域下所有操作至私有仓储,可安装公司私有包。  
``bash
# 或替换为 cnpm 命令
$ npm config set @xxxxxx:registry http://registry.npm.xxxxxx.work
``

2. 使用 [git.xxxxxx.work](http://git.xxxxxx.work) 用户名和[个人访问令牌](http://git.xxxxxx.work/profile/personal_access_tokens)登录,可发布公司私有包。  
``bash
# 或替换为 cnpm 命令
$ npm login --scope=@xxxxxx --registry=http://registry.npm.xxxxxx.work

Username: zhangsan # 用户名
Password: xxxxxxxxxxxxxxxxxxxx # 个人访问令牌(需勾选 Scopes 中 api 选项)
Email: (this IS public) zhangsan@xxxx.com # 邮箱
``

## 更多信息请查看[使用说明](http://xxxxxx.wiki/pages/viewpage.action?pageId=8524381)

**此处粘贴 /usr/lib/node_modules/cnpmjs.org/docs/web/readme.md 文件内容**

4、自定义用户认证 /root/.cnpmjs.org/services/custom_user_service.js

const http = require('http');
const isAdmin = require('cnpmjs.org/lib/common').isAdmin;
const config = require('cnpmjs.org/config');

// User: https://github.com/cnpm/cnpmjs.org/wiki/Use-Your-Own-User-Authorization#user-data-structure
// {
//   "login": "fengmk2",
//   "email": "fengmk2@gmail.com",
//   "name": "Yuan Feng",
//   "html_url": "http://fengmk2.github.com",
//   "avatar_url": "https://avatars3.githubusercontent.com/u/156269?s=460",
//   "im_url": "",
//   "site_admin": false,
//   "scopes": ["@org1", "@org2"]
// }

const emailDomain = '@xxxx.com';
const defaultToken = 'xxxxxxxxxxxxxxxxxxxx';

function convertToUser(gitUser) {
  let user = {
    login: gitUser.username,
    email: gitUser.email || `${gitUser.username}${emailDomain}`,
    name: gitUser.name,
    html_url: gitUser.web_url,
    avatar_url: gitUser.avatar_url,
    im_url: '',
    site_admin: isAdmin(gitUser.username),
    scopes: config.scopes,
  };

  return user;
}

function gitHttp(api, token) {
  return new Promise((resolve, reject) => {
    let options = {
      method: 'GET',
      port: 80,
      hostname: 'git.xxxxxx.work',
      path: `/api/v4${api}`,
      headers: {
        'PRIVATE-TOKEN': token
      }
    };

    let req = http.request(options, (res) => {
      if (res.statusCode === 200) {
        res.setEncoding('utf8');

        let rawData = '';
        res.on('data', (chunk) =&gt; {
          rawData += chunk;
        });

        res.on('end', () => {
          try {
            let parsedData = JSON.parse(rawData);
            console.log(`[CustomUserService] [${api}] [${token}] JSON.parse: success`, parsedData);

            resolve(parsedData);
          } catch (e) {
            reject(`[CustomUserService] [${api}] [${token}] JSON.parse: ${e.message}`, e);
          }
        });
      }
      else {
        reject(`[CustomUserService] [${api}] [${token}] statusCode: ${res.statusCode}`);
      }
    });

    req.on('error', (e) => {
      reject(`[CustomUserService] [${api}] [${token}] error: ${e.message}`, e);
    });

    req.end();
  });
}

function* gitAuth(username, token) {
  let data = yield gitHttp('/user', token);

  let gitUser = null;
  if (data && data.username === username) {
    gitUser = data;
  }

  return gitUser;
}

function* gitGet(username) {
  let data = yield gitHttp(`/users?username=${encodeURIComponent(username)}`, defaultToken);

  let gitUser = null;
  if (data && data.length > 0 && data[0].username === username) {
    gitUser = data[0];
  }

  return gitUser;
}

function* gitList(usernames) {
  let gitUsers = [];

  if (usernames && usernames.length > 0) {
    for (let i = 0; i < usernames.length; i++) {
      let username = usernames[i];
      let gitUser = yield gitGet(username);
      if (gitUser) {
        gitUsers.push(gitUser);
      }
    }
  }

  return gitUsers;
}

function* gitSearch(query, limit) {
  let data = yield gitHttp(`/users?search=${encodeURIComponent(query)}&per_page=${limit}`, defaultToken);

  let gitUsers = [];
  if (data && data.length > 0) {
    gitUsers = data;
  }

  return gitUsers;
}

function CustomUserService() { }
const proto = CustomUserService.prototype;

/**
 * Auth user with login name and password
 * @param  {String} login    login name
 * @param  {String} password login password
 * @return {User}
 */
proto.auth = function* (login, password) {
  console.log(`[CustomUserService] [auth]`, login, password);

  let user = null;

  try {
    let gitUser = yield gitAuth(login, password);

    if (gitUser) {
      user = convertToUser(gitUser);
    }
  }
  catch (e) {
    console.log(`[CustomUserService] [auth]`, e);
  }

  return user;
};

/**
 * Get user by login name
 * @param  {String} login  login name
 * @return {User}
 */
proto.get = function* (login) {
  console.log(`[CustomUserService] [get]`, login);

  let user = null;

  try {
    let gitUser = yield gitGet(login);

    if (gitUser) {
      user = convertToUser(gitUser);
    }
  }
  catch (e) {
    console.log(`[CustomUserService] [get]`, e);
  }

  return user;
};

/**
 * List users
 * @param  {Array<String>} logins  login names
 * @return {Array<User>}
 */
proto.list = function* (logins) {
  console.log(`[CustomUserService] [list]`, logins);

  let users = [];

  try {
    let gitUsers = yield gitList(logins);

    gitUsers.forEach((gitUser) => {
      users.push(convertToUser(gitUser));
    });
  }
  catch (e) {
    console.log(`[CustomUserService] [list]`, e);
  }

  return users;
};

/**
 * Search users
 * @param  {String} query  query keyword
 * @param  {Object} [options] optional query params
 *  - {Number} limit match users count, default is `20`
 * @return {Array<User>}
 */
proto.search = function* (query, options) {
  console.log(`[CustomUserService] [search]`, query, options);

  let users = [];

  try {
    options = options || {};
    options.limit = parseInt(options.limit);
    if (!options.limit || options.limit < 0) {
      options.limit = 20;
    }

    let gitUsers = yield gitSearch(query, options.limit);

    gitUsers.forEach((gitUser) => {
      users.push(convertToUser(gitUser));
    });
  }
  catch (e) {
    console.log(`[CustomUserService] [search]`, e);
  }

  return users;
};

module.exports = CustomUserService;

5、验证自定义配置

export NODE_PATH=/usr/lib/node_modules && /usr/bin/cnpmjs.org start

Starting cnpmjs.org ...
cluster: false
admins: {"admin":"admin@xxxx.com"}
scopes: ["@xxxxxx"]
sourceNpmRegistry: https://registry.npm.taobao.org
syncModel: none
[Tue Mar 06 2018 17:05:20 GMT+0800 (CST)] [worker:8004] Server started, registry server listen at 0.0.0.0:7001, web listen at 0.0.0.0:7002, cluster: false

五、开机启动 cnpmjs.org 并配置域名和定期备份

1、创建服务并开机自启 /usr/lib/systemd/system/cnpmjs.org.service

[Unit]
Description=cnpmjs.org
After=network.target

[Service]
Type=simple
User=root
Environment=NODE_PATH=/usr/lib/node_modules
ExecStart=/usr/bin/cnpmjs.org start
ExecStop=/usr/bin/cnpmjs.org stop
PrivateTmp=true

[Install]
WantedBy=multi-user.target

2、应用并启动 cnpmjs.org 服务

# 刷新服务守护进程
systemctl daemon-reload
# 启动 cnpmjs.org 服务
systemctl start cnpmjs.org.service
# 设置开机启动
systemctl enable cnpmjs.org.service
# 查看启动日志
journalctl -u cnpmjs.org.service

3、配置 nginx 反向代理

# cnpmjs.org registry
server {
    listen       80;
    server_name  registry.npm.xxxxxx.work;
    location / {
        #proxy_redirect off;
        proxy_pass http://192.168.21.23:7001;
    }
}

# cnpmjs.org web
server {
    listen       80;
    server_name  npm.xxxxxx.work;
    location / {
        #proxy_redirect off;
        proxy_pass http://192.168.21.23:7002;
    }
}

4、验证域名配置

curl npm.xxxxxx.work
curl registry.npm.xxxxxx.work

5、创建备份脚本 /root/.cnpmjs.org/backup/backup.sh
由于 cnpmjs.org 使用文件方式存放已发布的包,因此只用备份 sqlite 数据库即可。

#!/bin/sh

# 当前时间
var_dateTime=$(date +%Y%m%d%H%M)
# cnpmjs.org 配置目录
var_workDir="/root/.cnpmjs.org"
# 备份目录
var_backupDir=${var_workDir}"/backup/"${var_dateTime}
# 创建备份目录
mkdir -p ${var_backupDir}
# 数据库源位置
var_dataSource=${var_workDir}"/data.sqlite"
# 数据库目标位置
var_dataTarget=${var_backupDir}"/data.sqlite"
# 备份数据库
sqlite3 ${var_dataSource} ".backup ${var_dataTarget}"

6、验证备份脚本

# 修改备份脚本执行权限
chmod a+x /root/.cnpmjs.org/backup/backup.sh

# 执行备份脚本
/root/.cnpmjs.org/backup/backup.sh

# 检查是否已经生成备份
ls -l /root/.cnpmjs.org/backup/

7、配置定时任务

# 编辑定时任务文件 /var/spool/cron/root
crontab -e

# 每周日 00:00 执行备份
0 0 * * 0 /root/.cnpmjs.org/backup/backup.sh

# 如果未正常进行,检查以下文件
cat /var/log/cron
cat /var/spool/mail/root

六、【非服务端】npm 或 cnpm 客户端配置

1、拦截 @xxxxxx 作用域下所有操作至私有仓储,可安装公司私有包。

# 或替换为 cnpm 命令
npm config set @xxxxxx:registry http://registry.npm.xxxxxx.work

2、使用 git.xxxxxx.work 用户名和个人访问令牌登录,可发布公司私有包。

# 或替换为 cnpm 命令
npm login --scope=@xxxxxx --registry=http://registry.npm.xxxxxx.work

Username: zhangsan # 用户名
Password: xxxxxxxxxxxxxxxxxxxx # 个人访问令牌(需勾选 Scopes 中 api 选项)
Email: (this IS public) zhangsan@xxxx.com # 邮箱
标签:Centos 发布于:2019-10-26 12:42:03