pixl-boot 1.0.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Overview
2
2
 
3
- `pixl-boot` will automatically register a startup service for your module on Linux and OS X, so your daemon will be started on a server reboot. It is configured entirely out of your [package.json](https://docs.npmjs.com/files/package.json) file, and will handle all the details of registering an [init.d service](https://bash.cyberciti.biz/guide//etc/init.d) on Linux, or a [LaunchAgent/LaunchDaemon](https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html) on OS X.
3
+ `pixl-boot` will automatically register a startup service for your module on Linux and macOS, so your daemon will be started on a server reboot. It is configured entirely out of your [package.json](https://docs.npmjs.com/files/package.json) file, and will handle all the details of registering a [systemd service](https://en.wikipedia.org/wiki/Systemd) on Linux, or a [LaunchAgent/LaunchDaemon](https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html) on macOS.
4
4
 
5
5
  This is only designed for packages that are installed as the root user.
6
6
 
@@ -10,7 +10,7 @@ First, add the `pixl-boot` package in the `dependencies` section of your `packag
10
10
 
11
11
  ```js
12
12
  "dependencies": {
13
- "pixl-boot": "^1.0.0"
13
+ "pixl-boot": "^2.0.0"
14
14
  }
15
15
  ```
16
16
 
@@ -22,18 +22,7 @@ Once you have your control script ready, link to it in the `bin` property in you
22
22
  "bin": "bin/control.sh",
23
23
  ```
24
24
 
25
- Finally, you need to have npm run `pixl-boot` on install and uninstall of your package, so that it has a chance to register and unregister your startup service. Do this by adding `postinstall` and `preuninstall` properties in the `scripts` section of your `package.json` file:
26
-
27
- ```js
28
- "scripts": {
29
- "postinstall": "pixl-boot install",
30
- "preuninstall": "pixl-boot uninstall"
31
- }
32
- ```
33
-
34
- That's it!
35
-
36
- Alternatively, if you would rather the startup service not be installed automatically, and instead require additional user commands, change the `scripts` property names to something custom, like `boot` and `unboot`:
25
+ Finally, you need to have npm run `pixl-boot` on install and uninstall of your package, so that it has a chance to register and unregister your startup service. Do this by adding `boot` and `unboot` properties in the `scripts` section of your `package.json` file:
37
26
 
38
27
  ```js
39
28
  "scripts": {
@@ -42,9 +31,9 @@ Alternatively, if you would rather the startup service not be installed automati
42
31
  }
43
32
  ```
44
33
 
45
- Then your users would need to be instructed to type:
34
+ Then your users need to be instructed to type:
46
35
 
47
- ```
36
+ ```sh
48
37
  npm run boot
49
38
  npm run unboot
50
39
  ```
@@ -61,8 +50,8 @@ Add `--name` if you want to customize the startup service name. This defaults t
61
50
 
62
51
  ```js
63
52
  "scripts": {
64
- "postinstall": "pixl-boot install --name mycustomservice",
65
- "preuninstall": "pixl-boot uninstall --name mycustomservice"
53
+ "boot": "pixl-boot install --name mycustomservice",
54
+ "unboot": "pixl-boot uninstall --name mycustomservice"
66
55
  }
67
56
  ```
68
57
 
@@ -72,8 +61,8 @@ Add `--company` if you want to customize the "company" (organization) name that
72
61
 
73
62
  ```js
74
63
  "scripts": {
75
- "postinstall": "pixl-boot install --company MyCompany",
76
- "preuninstall": "pixl-boot uninstall --company MyCompany"
64
+ "boot": "pixl-boot install --company MyCompany",
65
+ "unboot": "pixl-boot uninstall --company MyCompany"
77
66
  }
78
67
  ```
79
68
 
@@ -83,67 +72,37 @@ Add `--script` to specify a custom location of your shell control script, relati
83
72
 
84
73
  ```js
85
74
  "scripts": {
86
- "postinstall": "pixl-boot install --script bin/my-control-script.sh"
87
- }
88
- ```
89
-
90
- ### linux_runlevels
91
-
92
- Add `--linux_runlevels` if you want to customize the [Linux init.d Runlevels](https://en.wikipedia.org/wiki/Runlevel#Linux) for when your service should actually start up. This defaults to `3,4,5` denoting that your service should start at Runlevels 3, 4 and 5. Obviously, this only has effect when installing on Linux operating systems. You only need to add this to the `pixl-boot install` command. Example:
93
-
94
- ```js
95
- "scripts": {
96
- "postinstall": "pixl-boot install --linux_runlevels 3,4,5"
97
- }
98
- ```
99
-
100
- ### redhat_start_priority
101
-
102
- Add `--redhat_start_priority` if you want to customize the [Linux init.d start priority](http://www.tldp.org/HOWTO/HighQuality-Apps-HOWTO/boot.html), which is a number from `01` to `99`. This controls when your service should start up, in relation to all the other services on the machine. This is only used by RedHat (CentOS / Fedora) flavors of Linux, and defaults to `99` (i.e. only start after everything else has). You only need to add this to the `pixl-boot install` command. Example:
103
-
104
- ```js
105
- "scripts": {
106
- "postinstall": "pixl-boot install --redhat_start_priority 99"
107
- }
108
- ```
109
-
110
- ### redhat_stop_priority
111
-
112
- Add `--redhat_stop_priority` if you want to customize the [Linux init.d stop priority](http://www.tldp.org/HOWTO/HighQuality-Apps-HOWTO/boot.html), which is a number from `01` to `99`. This controls when your service should shut down, in relation to all the other services on the machine. This is only used by RedHat (CentOS / Fedora) flavors of Linux, and defaults to `01` (i.e. stop first, before anything else). You only need to add this to the `pixl-boot install` command. Example:
113
-
114
- ```js
115
- "scripts": {
116
- "postinstall": "pixl-boot install --redhat_stop_priority 01"
75
+ "boot": "pixl-boot install --script bin/my-control-script.sh"
117
76
  }
118
77
  ```
119
78
 
120
- ### debian_requires
79
+ ### linux_type
121
80
 
122
- Add `--debian_requires` if you want to customize the list of [Debian services](https://wiki.debian.org/LSBInitScripts) that should be started *before* your service. For example, it is common to list things like `network` if your daemon requires network access. This defaults to `local_fs remote_fs network syslog named`. You only need to add this to the `pixl-boot install` command. Example:
81
+ Add `--linux_type` if you want to customize the Linux systemd service type. It should be one of `simple`, `forking`, `oneshot`, `dbus`, `notify`, or `idle`. It defaults to `forking`. Example:
123
82
 
124
83
  ```js
125
84
  "scripts": {
126
- "postinstall": "pixl-boot install --debian_requires local_fs remote_fs network syslog named"
85
+ "boot": "pixl-boot install --linux_type forking"
127
86
  }
128
87
  ```
129
88
 
130
- ### debian_stoplevels
89
+ ### linux_after
131
90
 
132
- Add `--debian_stoplevels` if you want to customize the [Linux init.d Runlevels](https://en.wikipedia.org/wiki/Runlevel#Linux) for when your service should shut down. This property is only used on Debian (Ubuntu) systems, and defaults to `0,1,6` denoting that your service should stop at Runlevels 0, 1 and 6. You only need to add this to the `pixl-boot install` command. Example:
91
+ Add `--linux_after` if you want to specify a service that we must start *after*. This defaults to `network.target`, denoting that the server should have basic networking started before trying to start our service. Example:
133
92
 
134
93
  ```js
135
94
  "scripts": {
136
- "postinstall": "pixl-boot install --debian_stoplevels 0,1,6"
95
+ "boot": "pixl-boot install --linux_after network.target"
137
96
  }
138
97
  ```
139
98
 
140
- ### darwin_type
99
+ ### linux_wanted_by
141
100
 
142
- Add `--darwin_type` to customize the type of startup service you want on Darwin (OS X) systems. Darwin supports two different types of startup services, [LaunchAgents and LaunchDaemons](https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html). In short, a `LaunchAgent` only starts up when a user log in, while a `LaunchDaemon` starts up earlier, before any user logs in. The default type is `LaunchAgent`, but beware of changing this to `LaunchDaemon`, because this may start your service before things like network are available. You only need to add this to the `pixl-boot install` command. Example:
101
+ Add `--linux_wanted_by` if you want to customize the `WantedBy` property in the systemd service file. This defaults to `multi-user.target`. Example:
143
102
 
144
103
  ```js
145
104
  "scripts": {
146
- "postinstall": "pixl-boot install --darwin_type LaunchAgent"
105
+ "boot": "pixl-boot install --linux_wanted_by multi-user.target"
147
106
  }
148
107
  ```
149
108
 
@@ -178,17 +137,14 @@ require('fs').writeFileSync( "logs/pid.txt", process.pid );
178
137
  In addition to the command-line interface for `pixl-boot` there is also a JavaScript API you can use in your Node.js code, to install and/or uninstall startup services. To use this, first call `require('pixl-boot)` to load the module, and the returned object exposes `install()` and `uninstall()` functions. Both functions accept an options object, and a callback. The options object accepts all the named command-line arguments, sans the hyphens. Example use:
179
138
 
180
139
  ```js
181
- var boot = require('pixl-boot');
182
- var opts = {
140
+ const boot = require('pixl-boot');
141
+ let opts = {
183
142
  name: "MyService",
184
143
  company: "Node",
185
144
  script: "bin/control.sh",
186
- linux_runlevels: "3,4,5",
187
- redhat_start_priority: "99",
188
- redhat_stop_priority: "01",
189
- debian_requires: "local_fs remote_fs network syslog named",
190
- debian_stoplevels: "0,1,6",
191
- darwin_type: "agent"
145
+ linux_type: "forking",
146
+ linux_after: "network.target",
147
+ linux_wanted_by: "multi-user.target"
192
148
  };
193
149
 
194
150
  // install startup service
@@ -204,9 +160,9 @@ boot.uninstall(opts, function(err) {
204
160
 
205
161
  # Licenses
206
162
 
207
- The MIT License
163
+ **The MIT License**
208
164
 
209
- Copyright (c) 2016 Joseph Huckaby.
165
+ Copyright (c) 2016 - 2024 Joseph Huckaby.
210
166
 
211
167
  Permission is hereby granted, free of charge, to any person obtaining a copy
212
168
  of this software and associated documentation files (the "Software"), to deal
package/boot.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // PixlBoot API
2
2
  // Install service to run on server startup
3
3
  // Works on Linux (RedHat / Ubuntu) and OS X
4
- // Copyright (c) 2016 Joseph Huckaby and PixlCore.com
4
+ // Copyright (c) 2016 - 2019 Joseph Huckaby and PixlCore.com
5
+ // MIT License
5
6
 
6
7
  var fs = require('fs');
7
8
  var cp = require('child_process');
@@ -13,12 +14,17 @@ module.exports = {
13
14
  company: "Node",
14
15
  script: "bin/control.sh",
15
16
 
17
+ // systemd stuff (modern)
18
+ linux_type: "forking",
19
+ linux_after: "network.target",
20
+ linux_wanted_by: "multi-user.target",
21
+
22
+ // init.d stuff (legacy)
16
23
  linux_runlevels: "3,4,5",
17
24
  redhat_start_priority: "99",
18
25
  redhat_stop_priority: "01",
19
26
  debian_requires: "local_fs remote_fs network syslog named",
20
- debian_stoplevels: "0,1,6",
21
- darwin_type: "agent"
27
+ debian_stoplevels: "0,1,6"
22
28
  },
23
29
 
24
30
  install: function(args, callback) {
@@ -45,36 +51,87 @@ module.exports = {
45
51
  },
46
52
 
47
53
  install_linux: function(args, callback) {
48
- // install linux init.d service
54
+ // install linux systemd or init.d service
49
55
  // first determine if we're on RedHat (CentOS / Fedora) or Debian (Ubuntu)
50
56
  var self = this;
51
57
 
52
58
  args.service_name = args.name.toLowerCase().replace(/\W+/g, '');
53
- args.service_file = "/etc/init.d/" + args.service_name;
54
59
 
55
- cp.exec("which chkconfig", function(err, stdout, stderr) {
60
+ cp.exec("which systemctl", function(err, stdout, stderr) {
56
61
  if (err) {
57
- // not redhat, but are we on debian?
58
- cp.exec("which update-rc.d", function(err, stdout, stderr) {
62
+ // oops, try one of the legacy methods
63
+ cp.exec("which chkconfig", function(err, stdout, stderr) {
59
64
  if (err) {
60
- // not a supported linux platform
61
- callback( new Error("Unsupported platform: No chkconfig nor update-rc.d found.") );
65
+ // not redhat, but are we on debian?
66
+ cp.exec("which update-rc.d", function(err, stdout, stderr) {
67
+ if (err) {
68
+ // not a supported linux platform
69
+ callback( new Error("Unsupported platform: No systemctl, chkconfig nor update-rc.d found.") );
70
+ }
71
+ else {
72
+ // we're on legacy debian
73
+ self.install_linux_debian(args, callback);
74
+ }
75
+ });
62
76
  }
63
77
  else {
64
- // we're on debian
65
- self.install_linux_debian(args, callback);
78
+ // we're on legacy redhat
79
+ self.install_linux_redhat(args, callback);
66
80
  }
67
81
  });
82
+ return;
68
83
  }
69
84
  else {
70
- // we're on redhat
71
- self.install_linux_redhat(args, callback);
85
+ // proceed with modern linux systemd
86
+ self.install_linux_systemd(args, callback);
72
87
  }
73
88
  });
74
89
  },
75
90
 
91
+ install_linux_systemd: function(args, callback) {
92
+ // install service on linux with systemd (systemctl)
93
+ args.service_file = "/etc/systemd/system/" + args.service_name + ".service";
94
+ var service_type = args.linux_type;
95
+ var unit_after = args.linux_after;
96
+ var wanted_by = args.linux_wanted_by;
97
+
98
+ var service_contents = [
99
+ "[Unit]",
100
+ "Description=" + args.company + " " + args.name,
101
+ "After=" + unit_after,
102
+ "",
103
+ "[Service]",
104
+ "Type=" + service_type,
105
+ "ExecStart=" + args.script + " start",
106
+ "ExecStop=" + args.script + " stop",
107
+ "",
108
+ "[Install]",
109
+ "WantedBy=" + wanted_by
110
+ ].join("\n") + "\n";
111
+
112
+ // write init.d config file
113
+ fs.writeFile( args.service_file, service_contents, { mode: 0o644 }, function(err) {
114
+ if (err) return callback( new Error("Failed to write file: " + args.service_file + ": " + err.message) );
115
+
116
+ // reload systemd
117
+ cp.exec("systemctl daemon-reload", function(err, stdout, stderr) {
118
+ if (err) callback( new Error("Failed to activate service: " + args.service_name + ": " + err.message) );
119
+
120
+ // activate service
121
+ cp.exec("systemctl enable " + args.service_name + ".service", function(err, stdout, stderr) {
122
+ if (err) callback( new Error("Failed to activate service: " + args.service_name + ": " + err.message) );
123
+
124
+ // success
125
+ callback();
126
+ }); // cp.exec
127
+ }); // cp.exec
128
+ }); // fs.writeFile
129
+ },
130
+
76
131
  install_linux_redhat: function(args, callback) {
77
132
  // install service on redhat (chkconfig)
133
+ // (this is legacy, only used if systemd/systemctl is not on system)
134
+ args.service_file = "/etc/init.d/" + args.service_name;
78
135
  var runlevels = args.linux_runlevels.toString().replace(/\D+/g, '');
79
136
  var rh_start_priority = this.zeroPad(args.redhat_start_priority, 2);
80
137
  var rh_stop_priority = this.zeroPad(args.redhat_stop_priority, 2);
@@ -106,6 +163,8 @@ module.exports = {
106
163
 
107
164
  install_linux_debian: function(args, callback) {
108
165
  // install service on debian (update-rc.d)
166
+ // (this is legacy, only used if systemd/systemctl is not on system)
167
+ args.service_file = "/etc/init.d/" + args.service_name;
109
168
  var runlevels = args.linux_runlevels.toString().replace(/\D+/g, '').split('').join(" ");
110
169
  var deb_stoplevels = args.debian_stoplevels.toString().replace(/\D+/g, '').split('').join(" ");
111
170
  var deb_requires = args.debian_requires.split(/\W+/).map( function(req) { return '$' + req; } ).join(" ");
@@ -115,7 +174,7 @@ module.exports = {
115
174
  "",
116
175
  "### BEGIN INIT INFO",
117
176
  "# Provides: " + args.service_name,
118
- "# Required-Start: " + args.debian_requires,
177
+ "# Required-Start: " + deb_requires,
119
178
  "# Required-Stop: " + deb_requires,
120
179
  "# Default-Start: " + runlevels,
121
180
  "# Default-Stop: " + deb_stoplevels,
@@ -132,10 +191,12 @@ module.exports = {
132
191
 
133
192
  // activate service
134
193
  cp.exec("update-rc.d " + args.service_name + " defaults", function(err, stdout, stderr) {
135
- if (err) callback( new Error("Failed to activate service: " + args.service_name + ": " + err.message) );
136
-
137
- // success
138
- callback();
194
+ if (err) {
195
+ callback( new Error("Failed to activate service: " + args.service_name + ": " + err.message) );
196
+ } else {
197
+ // success
198
+ callback();
199
+ }
139
200
  }); // cp.exec
140
201
  }); // fs.writeFile
141
202
  },
@@ -144,7 +205,15 @@ module.exports = {
144
205
  // install service as Darwin (OS X) agent or daemon
145
206
  args.service_name = args.name.toLowerCase().replace(/\W+/g, '');
146
207
  args.company_name = args.company.toLowerCase().replace(/\W+/g, '');
147
- args.plist_file = "/Library/" + (args.darwin_type.match(/agent/i) ? 'LaunchAgents' : 'LaunchDaemons') + "/com." + args.company_name + "." + args.service_name + ".plist";
208
+
209
+ if (process.getuid() == 0) {
210
+ // we're root, so install a LaunchDaemon
211
+ args.plist_file = "/Library/LaunchDaemons/com." + args.company_name + "." + args.service_name + ".plist";
212
+ }
213
+ else {
214
+ // we're a standard user, so install a user-level LaunchAgent
215
+ args.plist_file = process.env.HOME + "/Library/LaunchAgents/com." + args.company_name + "." + args.service_name + ".plist";
216
+ }
148
217
 
149
218
  var plist_contents = [
150
219
  '<?xml version="1.0" encoding="UTF-8"?>',
@@ -169,14 +238,7 @@ module.exports = {
169
238
  // write plist config file
170
239
  fs.writeFile( args.plist_file, plist_contents, { mode: '644' }, function(err) {
171
240
  if (err) return callback( new Error("Failed to write file: " + args.plist_file + ": " + err.message) );
172
-
173
- // must be root/wheel
174
- cp.exec( "chown root:wheel " + args.plist_file, function(err) {
175
- if (err) return callback( new Error("Failed to chmod plist file: " + args.plist_file + ": " + err.message) );
176
-
177
- // success
178
- callback();
179
- });
241
+ callback();
180
242
  });
181
243
  },
182
244
 
@@ -192,19 +254,41 @@ module.exports = {
192
254
 
193
255
  args.service_name = args.name.toLowerCase().replace(/\W+/g, '');
194
256
  args.company_name = args.company.toLowerCase().replace(/\W+/g, '');
195
- args.service_file = "/etc/init.d/" + args.service_name;
196
257
 
197
258
  if (process.platform == 'linux') {
198
- // chkconfig may or may not work, so ignore error
199
- cp.exec( "chkconfig " + args.service_name + " off", function() {
200
- cp.exec( "rm -f /etc/rc*.d/*" + args.service_name, function() {
259
+ // try every which way to remove service
260
+ args.service_file = "/etc/systemd/system/" + args.service_name + ".service";
261
+
262
+ fs.access( args.service_file, function(err) {
263
+ if (err) {
264
+ // nope, try legacy methods
265
+ args.service_file = "/etc/init.d/" + args.service_name;
266
+
267
+ // chkconfig may or may not work, so ignore error
268
+ cp.exec( "chkconfig " + args.service_name + " off", function() {
269
+ cp.exec( "rm -f /etc/rc*.d/*" + args.service_name, function() {
270
+ fs.unlink( args.service_file, callback );
271
+ } );
272
+ } );
273
+ return;
274
+ }
275
+
276
+ // looks like we have a systemd service
277
+ cp.exec( "systemctl disable " + args.service_name + ".service", function() {
201
278
  fs.unlink( args.service_file, callback );
202
279
  } );
203
- } );
280
+ }); // fs.access
204
281
  }
205
282
  else {
206
283
  // non-linux (darwin)
207
- args.plist_file = "/Library/" + (args.darwin_type.match(/agent/i) ? 'LaunchAgents' : 'LaunchDaemons') + "/com." + args.company_name + "." + args.service_name + ".plist";
284
+ if (process.getuid() == 0) {
285
+ // we're root, so uninstall the LaunchDaemon
286
+ args.plist_file = "/Library/LaunchDaemons/com." + args.company_name + "." + args.service_name + ".plist";
287
+ }
288
+ else {
289
+ // we're a standard user, so uninstall the user-level LaunchAgent
290
+ args.plist_file = process.env.HOME + "/Library/LaunchAgents/com." + args.company_name + "." + args.service_name + ".plist";
291
+ }
208
292
 
209
293
  fs.unlink( args.plist_file, callback );
210
294
  }
package/cli.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-boot",
3
- "version": "1.0.2",
3
+ "version": "2.0.1",
4
4
  "description": "Register your service to launch on server startup (Linux / OS X).",
5
5
  "author": "Joseph Huckaby <jhuckaby@gmail.com>",
6
6
  "homepage": "https://github.com/jhuckaby/pixl-boot",
@@ -22,7 +22,9 @@
22
22
  "linux",
23
23
  "darwin",
24
24
  "init.d",
25
- "launchd"
25
+ "launchd",
26
+ "systemd",
27
+ "systemctl"
26
28
  ],
27
29
  "dependencies": {
28
30
  "pixl-cli": "^1.0.0"
package/.npmignore DELETED
@@ -1,5 +0,0 @@
1
- .gitignore
2
- node_modules/
3
- conf/config.json
4
- test/test.log
5
- logs/