@@ -7,129 +7,119 @@ const omit = require('lodash/omit');
7
7
const cli = require ( '../../lib' ) ;
8
8
9
9
class MySQLExtension extends cli . Extension {
10
- _query ( queryString ) {
11
- return Promise . fromCallback ( cb => this . connection . query ( queryString , cb ) ) ;
12
- }
13
-
14
10
setup ( cmd , argv ) {
15
11
// ghost setup --local, skip
16
12
if ( argv . local ) {
17
13
return ;
18
14
}
19
15
20
- cmd . addStage ( 'mysql' , this . setupMySQL . bind ( this ) ) ;
16
+ cmd . addStage ( 'mysql' , this . setupMySQL . bind ( this ) , [ ] , 'a ghost mysql user' ) ;
21
17
}
22
18
23
19
setupMySQL ( argv , ctx , task ) {
24
- this . databaseConfig = ctx . instance . config . get ( 'database' ) ;
25
-
26
- return this . canConnect ( ctx )
27
- . then ( ( ) => {
28
- if ( this . databaseConfig . connection . user === 'root' ) {
29
- return this . ui . confirm ( 'Your MySQL user is root. Would you like to create a custom Ghost MySQL user?' , true )
30
- . then ( ( res ) => {
31
- if ( res . yes ) {
32
- return this . createMySQLUser ( ctx ) ;
33
- }
34
- } ) ;
35
- }
36
-
37
- this . ui . log ( 'MySQL: Your user is: ' + this . databaseConfig . connection . user , 'green' ) ;
38
- } )
39
- . finally ( ( ) => {
20
+ let dbconfig = ctx . instance . config . get ( 'database.connection' ) ;
21
+
22
+ if ( dbconfig . user !== 'root' ) {
23
+ this . ui . log ( 'MySQL user is not root, skipping additional user setup' , 'yellow' ) ;
24
+ return task . skip ( ) ;
25
+ }
26
+
27
+ return this . ui . listr ( [ {
28
+ title : 'Connecting to database' ,
29
+ task : ( ) => this . canConnect ( ctx , dbconfig )
30
+ } , {
31
+ title : 'Creating new MySQL user' ,
32
+ task : ( ) => this . createUser ( ctx , dbconfig )
33
+ } , {
34
+ title : 'Granting new user permissions' ,
35
+ task : ( ) => this . grantPermissions ( ctx , dbconfig )
36
+ } , {
37
+ title : 'Finishing up' ,
38
+ task : ( ) => {
39
+ ctx . instance . config . set ( 'database.connection.user' , ctx . mysql . username )
40
+ . set ( 'database.connection.password' , ctx . mysql . password ) . save ( ) ;
41
+
40
42
this . connection . end ( ) ;
41
- } ) ;
43
+ }
44
+ } ] , false ) ;
42
45
}
43
46
44
- canConnect ( ctx ) {
45
- this . connection = mysql . createConnection ( omit ( this . databaseConfig . connection , 'database' ) ) ;
46
-
47
- return Promise . fromCallback ( cb => this . connection . connect ( cb ) )
48
- . then ( ( ) => {
49
- this . ui . log ( 'MySQL: connection successful.' , 'green' ) ;
50
- } )
51
- . catch ( ( err ) => {
52
- this . ui . log ( 'MySQL: connection error.' , 'yellow' ) ;
53
-
54
- if ( err . code === 'ER_ACCESS_DENIED_ERROR' ) {
55
- throw new cli . errors . ConfigError ( {
56
- message : err . message ,
57
- config : {
58
- 'database.connection.user' : this . databaseConfig . connection . user ,
59
- 'database.connection.password' : this . databaseConfig . connection . password
60
- } ,
61
- environment : ctx . instance . system . environment ,
62
- help : 'You can run `ghost config` to re-enter the correct credentials. Alternatively you can run `ghost setup` again.'
63
- } ) ;
64
- }
65
-
66
- throw new cli . errors . ConfigError ( {
67
- message : err . message ,
47
+ canConnect ( ctx , dbconfig ) {
48
+ this . connection = mysql . createConnection ( omit ( dbconfig , 'database' ) ) ;
49
+
50
+ return Promise . fromCallback ( cb => this . connection . connect ( cb ) ) . catch ( ( error ) => {
51
+ if ( error . code === 'ECONNREFUSED' ) {
52
+ return Promise . reject ( new cli . errors . ConfigError ( {
53
+ message : error . message ,
54
+ config : {
55
+ 'database.connection.host' : dbconfig . host ,
56
+ 'database.connection.port' : dbconfig . port || '3306'
57
+ } ,
58
+ environment : this . system . environment ,
59
+ help : 'Please ensure that MySQL is installed and reachable. You can always re-run `ghost setup` to try again.'
60
+ } ) ) ;
61
+ } else if ( error . code === 'ER_ACCESS_DENIED_ERROR' ) {
62
+ return Promise . reject ( new cli . errors . ConfigError ( {
63
+ message : error . message ,
68
64
config : {
69
- 'database.connection.host ' : this . databaseConfig . connection . host ,
70
- 'database.connection.port ' : this . databaseConfig . connection . port || '3306'
65
+ 'database.connection.user ' : dbconfig . user ,
66
+ 'database.connection.password ' : dbconfig . password
71
67
} ,
72
- environment : ctx . instance . system . environment ,
73
- help : 'Please ensure that MySQL is installed and reachable. You can always re-run `ghost setup` and try it again.'
74
- } ) ;
75
- } ) ;
68
+ environment : this . system . environment ,
69
+ help : 'You can run `ghost config` to re-enter the correct credentials. Alternatively you can run `ghost setup` again.'
70
+ } ) ) ;
71
+ }
72
+
73
+ return Promise . reject ( error ) ;
74
+ } ) ;
76
75
}
77
76
78
- createMySQLUser ( ctx ) {
77
+ createUser ( ctx , dbconfig ) {
79
78
let randomPassword = crypto . randomBytes ( 10 ) . toString ( 'hex' ) ;
80
- let host = this . databaseConfig . connection . host ;
81
79
82
80
// IMPORTANT: we generate random MySQL usernames
83
- // e.g. you delete all your Ghost instances from your droplet and start from scratch, the MySQL users would remain and the CLI has to generate a random user name to be able to
81
+ // e.g. you delete all your Ghost instances from your droplet and start from scratch, the MySQL users would remain and the CLI has to generate a random user name to work
84
82
// e.g. if we would rely on the instance name, the instance naming only auto increments if there are existing instances
85
83
// the most important fact is, that if a MySQL user exists, we have no access to the password, which we need to autofill the Ghost config
86
84
// disadvantage: the CLI could potentially create lot's of MySQL users (but this should only happen if the user installs Ghost over and over again with root credentials)
87
85
let username = 'ghost-' + Math . floor ( Math . random ( ) * 1000 ) ;
88
86
89
- return this . _query ( 'CREATE USER \'' + username + '\'@\'' + host + '\' IDENTIFIED BY \'' + randomPassword + '\';' )
90
- . then ( ( ) => {
91
- this . ui . log ( 'MySQL: successfully created `' + username + '`.' , 'green' ) ;
92
-
93
- return this . grantPermissions ( { username : username } )
94
- . then ( ( ) => {
95
- ctx . instance . config . set ( 'database.connection.user' , username ) ;
96
- ctx . instance . config . set ( 'database.connection.password' , randomPassword ) ;
97
- } ) ;
98
- } )
99
- . catch ( ( err ) => {
100
- // CASE: user exists, we are not able to figure out the original password, skip mysql setup
101
- if ( err . errno === 1396 ) {
102
- this . ui . log ( 'MySQL: `' + username + '` user exists. Skipping.' , 'yellow' ) ;
103
- return Promise . resolve ( ) ;
104
- }
105
-
106
- this . ui . log ( 'MySQL: unable to create custom Ghost user.' , 'yellow' ) ;
107
- throw new cli . errors . SystemError ( err . message ) ;
108
- } ) ;
87
+ return this . _query ( `CREATE USER '${ username } '@'${ dbconfig . host } ' IDENTIFIED BY '${ randomPassword } ';` ) . then ( ( ) => {
88
+ this . ui . logVerbose ( `MySQL: successfully created new user ${ username } ` , 'green' ) ;
89
+
90
+ ctx . mysql = {
91
+ username : username ,
92
+ password : randomPassword
93
+ } ;
94
+ } ) . catch ( ( error ) => {
95
+ // User already exists, run this method again
96
+ if ( error . errno === 1396 ) {
97
+ this . ui . logVerbose ( 'MySQL: user exists, re-trying user creation with new username' , 'yellow' ) ;
98
+ return this . createUser ( ctx , dbconfig ) ;
99
+ }
100
+
101
+ this . ui . logVerbose ( 'MySQL: Unable to create custom Ghost user' , 'red' ) ;
102
+ this . connection . end ( ) ; // Ensure we end the connection
103
+ return Promise . reject ( new cli . errors . SystemError ( `Creating new mysql user errored with message: ${ error . message } ` ) ) ;
104
+ } ) ;
109
105
}
110
106
111
- grantPermissions ( options ) {
112
- let host = this . databaseConfig . connection . host ;
113
- let database = this . databaseConfig . connection . database ;
114
- let username = options . username ;
115
-
116
- return this . _query ( 'GRANT ALL PRIVILEGES ON ' + database + '.* TO \'' + username + '\'@\'' + host + '\';' )
117
- . then ( ( ) => {
118
- this . ui . log ( 'MySQL: successfully granted permissions for `' + username + '` user.' , 'green' ) ;
119
-
120
- return this . _query ( 'FLUSH PRIVILEGES;' )
121
- . then ( ( ) => {
122
- this . ui . log ( 'MySQL: flushed privileges' , 'green' ) ;
123
- } )
124
- . catch ( ( err ) => {
125
- this . ui . log ( 'MySQL: unable to flush privileges.' , 'yellow' ) ;
126
- throw new cli . errors . SystemError ( err . message ) ;
127
- } ) ;
128
- } )
129
- . catch ( ( err ) => {
130
- this . ui . log ( 'MySQL: unable to grant permissions for `' + username + '` user.' , 'yellow' ) ;
131
- throw new cli . errors . SystemError ( err . message ) ;
132
- } ) ;
107
+ grantPermissions ( ctx , dbconfig ) {
108
+ return this . _query ( `GRANT ALL PRIVILEGES ON ${ dbconfig . database } .* TO '${ ctx . mysql . username } '@'${ dbconfig . host } ';` ) . then ( ( ) => {
109
+ this . ui . logVerbose ( `MySQL: Successfully granted privileges for user ${ ctx . mysql . username } ` , 'green' ) ;
110
+ return this . _query ( 'FLUSH PRIVILEGES;' ) ;
111
+ } ) . then ( ( ) => {
112
+ this . ui . logVerbose ( 'MySQL: flushed privileges' , 'green' ) ;
113
+ } ) . catch ( ( error ) => {
114
+ this . ui . logVerbose ( 'MySQL: Unable either to grant permissions or flush privileges' , 'red' ) ;
115
+ this . connection . end ( ) ;
116
+ return Promise . reject ( new cli . errors . SystemError ( `Granting database permissions errored with message: ${ error . message } ` ) ) ;
117
+ } ) ;
118
+ }
119
+
120
+ _query ( queryString ) {
121
+ this . ui . logVerbose ( `MySQL: running query > ${ queryString } ` , 'gray' ) ;
122
+ return Promise . fromCallback ( cb => this . connection . query ( queryString , cb ) ) ;
133
123
}
134
124
}
135
125
0 commit comments