在Ubuntu上部署Parse Server

警告:数据库的SSL Key没有做定期的更新,因此每3个月可能会因为Key过期导致服务因数据库无法访问而异常。
虽然有不少教程,但找来找去没有一个能顺利的在Ubuntu上顺利部署的。因此在这里把自己部署的过程记录下来。

前提条件:
Ubuntu Server 16.04.1 LTS 64 服务器
一个域名

  1. 登陆Ubuntu 服务器,除了root或者安装的用户之外,创建一个管理员用户:
    1
    2
    3
    4
    5
    //创建用户
    sudo adduser xxxxxx

    //授权管理员权限
    sudo usermod -aG sudo xxxxxx

参考连接:Initial Server Setup with Ubuntu 16.04

  1. 安装Node和npm
    1
    2
    3
    4
    5
    6
    7
    curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -

    sudo apt-get install -y nodejs

    //安装完毕后可以用下面的命令查看node和npm的版本
    node --version
    npm --version

参考连接:Installing Node.js via package manager

  1. 安装mongo db
    1
    2
    3
    4
    5
    6
    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
    echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list

    sudo apt-get update

    sudo apt-get install -y mongodb-org

参考连接:Install MongoDB Community Edition on Ubuntu

系统重启时,自动启动mongodb

1
sudo systemctl enable mongod

  1. 安装Nginx
    1
    2
    3
    4
    5
    6
    7
    sudo apt-get update
    sudo apt-get install nginx

    //安装完毕之后,Nginx会自动启动
    //可以在浏览器查看ip,得到的应该如下:
    //不知道ip的话,可以用下面的命令得到
    ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'

参考连接:How To Install Nginx on Ubuntu 14.04 LTS

  1. 设置Let’s Encrypt支持https
    5.1 添加云解析:
    在购买的云服务器的控制台,找到云解析,找到购买的域名,添加两条解析:
    – 添加一条A record,将域名”example.com”指向Ubuntu服务器的公网IP
    – 添加一条A record,将域名”www.example.com”指向Ubuntu服务器的公网IP
    5.2 安装Certbot
    1
    2
    3
    4
    5
    sudo add-apt-repository ppa:certbot/certbot

    sudo apt-get update

    sudo apt-get install python-certbot-nginx

配置Nginx

1
2
3
4
5
6
7
8
9
10
11
sudo nano /etc/nginx/sites-available/default

将server_name _;这一行的内容修改为对应的域名,例如:

server_name example.com www.example.com;

//检查修改内容是否正确
sudo nginx -t

//重启nginx
sudo service nginx reload

获取SSL证书

1
2
//记得修改对应的域名
sudo certbot --nginx -d example.com -d www.example.com

自动续约SSL证书

1
sudo certbot renew --dry-run

参考连接:How To Secure Nginx with Let’s Encrypt on Ubuntu 14.04

配置Mongo DB支持SSL连接

1
2
3
4
5
sudo cat /etc/letsencrypt/archive/domain_name/{fullchain1.pem,privkey1.pem} | sudo tee /etc/ssl/mongo.pem

//确保mongo.pem 只可以被 mongodb 访问
sudo chown mongodb:mongodb /etc/ssl/mongo.pem
sudo chmod 600 /etc/ssl/mongo.pem

修改mongo配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo nano /etc/mongod.conf

//修改内容如下:
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0
ssl:
mode: requireSSL
PEMKeyFile: /etc/ssl/mongo.pem

# security
security:
authorization: enabled

setParameter:
failIndexKeyTooLong: false

保存配置文件

添加一个新的DB管理员用户

1
2
3
4
5
6
7
8
9
10
11
//连接mongo db
mongo --port 27017

//创建一个管理员用户(请修改为自己的用户名和密码)
use admin
db.createUser({
user: "sammy",
pwd: "password",
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
})
exit

重启mongo db

1
sudo service mongod restart

以后可以这样连接mongo db

1
2
//然后需要输入密码
mongo --port 27017 --ssl --sslAllowInvalidCertificates --authenticationDatabase admin --username sammy --password

创建一个database和对应的db用户

1
2
use database_name
db.createUser({ user: "database_user", pwd: "password", roles: [ "readWrite", "dbAdmin" ] })

//注意,上面的”sammy”是DB登陆用户,而这个”database_user”才可以对这个database可以操作。我们后面连接数据库时,需要用到这个”database_user”和对应的密码以及数据库。

例如:

1
mongodb://database_user:password@your_domain_name:27017/database_name?ssl=true

安装Parse Server App Sample

1
2
3
4
5
git clone https://github.com/ParsePlatform/parse-server-example.git

cd ~/parse-server-example

npm install

测试Sample Application

1
2
3
4
5
6
7
8
npm start

结果如下:
> parse-server-example@1.0.0 start /home/sammy/parse-server-example
> node index.js

DATABASE_URI not specified, falling back to localhost.
parse-server-example running on port 1337.

另外开启一个session连接服务器,用如下测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
curl -X POST \
-H "X-Parse-Application-Id: myAppId" \
-H "Content-Type: application/json" \
-d '{"score":1337,"playerName":"Sammy","cheatMode":false}' \
http://localhost:1337/parse/classes/GameScore

Output
{"objectId":"fu7t4oWLuW","createdAt":"2016-02-02T18:43:00.659Z"}

curl -H "X-Parse-Application-Id: myAppId" http://localhost:1337/parse/classes/GameScore

Output
{"results":[{"objectId":"GWuEydYCcd","score":1337,"playerName":"Sammy","cheatMode":false,"updatedAt":"2016-02-02T04:04:29.497Z","createdAt":"2016-02-02T04:04:29.497Z"}]}

curl -X POST \
-H "X-Parse-Application-Id: myAppId" \
-H "Content-Type: application/json" \
-d '{}' \
http://localhost:1337/parse/functions/hello

Output
{"result":"Hi"}

回到原来的Session,Ctrl-C终止Parse Application的运行。

安装dashboard

1
2
3
4
5
6
7
8
9

//将dashboard加入package中
cd ~/parse-server-example

nano package.json
"dependencies"中添加一行
parse-dashboard": "1.1.2"

npm install

配置新的Application,包括dashboard

1
nano my_app.js

内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Packtor Server
var express = require('express');
var ParseServer = require('parse-server').ParseServer;
var ParseDashboard = require('parse-dashboard');
var path = require('path');

var databaseUri = process.env.DATABASE_URI || 'mongodb://database_user:password@your_domain_name:27017/database_name?ssl=tru';

if (!databaseUri) {
console.log('DATABASE_URI not specified, falling back to localhost.');
}

// Set up parse server
var api = new ParseServer({
databaseURI: databaseUri || 'mongodb://database_user:password@your_domain_name:27017/database_name?ssl=tru',
cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
appId: process.env.APP_ID || 'appId',
masterKey: process.env.MASTER_KEY || 'masterKey',
serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse',
publicServerURL: 'https://your_domain_name/parse'
});

var app = express();

// Serve static assets from the /public folder
app.use('/public', express.static(path.join(__dirname, '/public')));

// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, api);

// Parse Server plays nicely with the rest of your web routes
app.get('/', function(req, res) {
res.status(200).send('Parse Server App');
});

var port = process.env.PORT || 1337;
var httpServer = require('http').createServer(app);
httpServer.listen(port, function() {
console.log('Parse-server running on port ' + port + '.');
});

// Set up parse dashboard
var dashboard = new ParseDashboard({
"apps": [{
"serverURL": 'https://your_domain_name/parse', // Not localhost
"appId": 'appId',
"masterKey": 'masterKey',
"appName": "appName",
"production": false,
"iconName": "app-icon.png",
}],
"users": [
{
"user":"user",
"pass":"password"
}
],
"iconsFolder": "icons"
});

var dashApp = express();

// make the Parse Dashboard available at /dashboard
dashApp.use('/dashboard', dashboard);

// Parse Server plays nicely with the rest of your web routes
dashApp.get('/', function(req, res) {
res.status(200).send('Parse Dashboard App');
});

var httpServerDash = require('http').createServer(dashApp);
httpServerDash.listen(4040, function() {
console.log('dashboard-server running on port 4040.');
});

运行这个Application,可以用如下命令:

1
2
3
4
5
6
node my_app.js

结果应该如下:
Iconsfolder at path: icons not found!
Parse-server running on port 1337.
dashboard-server running on port 4040.

配置Nignx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Ctrl+C终止Application运行

sudo nano /etc/nginx/sites-available/default

在location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
之后, 添加如下内容
location /parse/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:1337/parse/;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
}

# Pass requests for /dashboard/ to Parse Server instance at localhost:4040
location /dashboard/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:4040/dashboard/;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
}

保存,退出,并重启Nginx

1
2
3
sudo nginx -t

sudo service nginx reload

再次运行Application

1
node my_app.js

此时,访问https://www.iosreader.com/dashboard/将会跳转到dashboard的login界面,可以用定义在配置文件里的用户名(user)和密码(password)进行登陆

安装PM2

1
2
3
4
5
Ctrl+c退出Application

//返回用户目录
cd ~
sudo npm install -g pm2

/*
配置ecosystem.json

1
nano ecosystem.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"apps" : [{
"name" : "parse-wrapper",
"script" : "/home/xxxxxx/parse-server-example/my_app.js",
"watch" : true,
"merge_logs" : true,
"cwd" : "/home/xxxxxx/parse-server-example",
"env": {
"PARSE_SERVER_CLOUD_CODE_MAIN": "/home/xxxxxx/parse-server-example/cloud/main.js",
"PARSE_SERVER_DATABASE_URI": "mongodb://database_user:password@your_domain_name:27017/database_name?ssl=true",
"PARSE_SERVER_APPLICATION_ID": "appId",
"PARSE_SERVER_MASTER_KEY": "masterKey",
}
}]
}

*/

运行PM2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//pm2 start ecosystem.json
修改为pm2 start my_app.js
不知道为何,配置ecosystem.json并且通过该文件启动my_app.js,在服务器重启之后,parse server的连接会出错,暂时不清楚问题的原因,以及如何修改。但是可以通过直接启动my_app.js,此方法在服务器重启之后,一切服务正常。

Sample Output
...
[PM2] Spawning PM2 daemon
[PM2] PM2 Successfully daemonized
[PM2] Process launched
┌───────────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├───────────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ parse-wrapper │ 0 │ fork │ 3499 │ online │ 0 │ 0s │ 13.680 MB │ enabled │
└───────────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app

保存

1
2
3
4
pm2 save

Sample Output
[PM2] Dumping processes

1
sudo pm2 startup ubuntu -u xxxxxx --hp /home/xxxxxx/