smfgen
v1.2.0
Published
Generate an SMF manifest from a JSON or CLI description of the service
Downloads
21
Readme
smfgen
This tool generates an SMF manifest from a JSON description of the service. It's only intended to generate simple manifests. For more details, see smf(5).
This tool is still experimental.
[sudo] npm install -g smfgen
JSON Input
Emits an SMF manifest for the service described by the given JSON:
| Property | Description |----------------|---------------------------------------------------------- | ident | The SMF identifier for the service. The full SMF identifier (FMRI) will be constructed from the category and identifier. | [category] | Service category (default: "application"). | label | The human-readable name of the service. | [dependencies] | Array of service FMRIs that must be online before this service is started. | start | The method object that describes how to start the service. (See below.) | [stop] | The method object that describes how to stop the service. (See below.) | [refresh] | The method object that describes how to refresh the service. (See below.) | [enabled] | Whether to start the service automatically (default: true)
Both the start
and stop
properties in the JSON should have object values that
describe the context of the method. A method context consists of these
properties:
| Property | Description
|----------------------|----------------------------------------------------
| exec | The script to run for this method. Defaults to :kill
for the stop
method. This invocation may run the service synchronously or in the background. Note that this mechanism does not allow you to use SMF's built in timeout for service startup, since it doesn't know when the service has actually started.
| [user] | Run the script for this method as this user.
| [group] | Run the script for this method as this group.
| [environment] | A hash of environment variables for this method script.
| [privileges] | An array of RBAC privilege names. This method will be run with this privilege set. See also: privileges(5).
| [working_directory] | Use this working directory when invoking the method script.
| [timeout] | The number of seconds the method may run before it is considered timed out and aborted. Defaults to 10 for start
and refresh
and 30 for stop
.
A set of example methods might look like this:
{
"start": {
"user": "webservd", "group": "webservd",
"exec": "/opt/pkg/sbin/apachectl start"
},
"stop": {
"timeout": 120
}
}
Note that you may also specify the properties that describe the context of a method at the top level of the JSON. Top-level properties apply to all methods, but may also be overridden in a specific method. For example:
{
"user": "webservd", "group": "webservd",
"exec": "/opt/pkg/sbin/apachectl %m",
"timeout": 10,
"stop": {
"timeout": 120
}
}
For compatibility with earlier versions of smfgen, the program will accept
a string in place of an object for the start
, stop
, and refresh
methods.
This string will be assumed to be the exec
property of that method.
Example
$ cat bapi.json
{
"ident": "bapi",
"label": "Boilerplate API",
"start": "node bapi.js"
}
$ ./smfgen < bapi.json
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!--
Manifest automatically generated by smfgen.
-->
<service_bundle type="manifest" name="application-bapi" >
<service name="application/bapi" type="service" version="1" >
<create_default_instance enabled="true" />
<dependency name="dep1" grouping="require_all" restart_on="error" type="service" >
<service_fmri value="svc:/milestone/multi-user:default" />
</dependency>
<exec_method type="method" name="start" exec="node bapi.js &" timeout_seconds="10" />
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="30" />
<template >
<common_name >
<loctext xml:lang="C" >Boilerplate API</loctext>
</common_name>
</template>
</service>
</service_bundle>
CLI Input
The following options can be specified over the CLI:
-c
,--category
becomesdata.category
-d
,--cwd
becomesdata.working_directory
-D
,--dependency
appends todata.dependencies
-e
,--env
appends todata.environment
-g
,--group
becomesdata.group
-i
,--ident
becomesdata.ident
-l
,--label
becomesdata.label
-p
,--privilege
appends todata.privileges
-r
,--refresh
becomesdata.refresh.exec
-s
,--start
becomesdata.start.exec
-S
,--stop
becomesdata.stop.exec
-t
,--timeout
becomesdata.timeout
-u
,--user
becomesdata.user
Example
$ ./smfgen -i nginx -l 'NGINX Web Server' \
-s 'nginx -d' -d /var/www \
-u nobody -g other \
-p basic -p net_privaddr \
-eHOME=/var/tmp -ePATH=/bin:/usr/bin
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!--
Manifest automatically generated by smfgen.
-->
<service_bundle type="manifest" name="application-nginx" >
<service name="application/nginx" type="service" version="1" >
<create_default_instance enabled="true" />
<dependency name="dep0" grouping="require_all" restart_on="error" type="service" >
<service_fmri value="svc:/milestone/multi-user:default" />
</dependency>
<exec_method type="method" name="start" exec="nginx -d &" timeout_seconds="10" >
<method_context working_directory="/var/www" >
<method_credential user="nobody" group="other" privileges="basic,net_privaddr" />
<method_environment >
<envvar name="HOME" value="/var/tmp" />
<envvar name="PATH" value="/bin:/usr/bin" />
</method_environment>
</method_context>
</exec_method>
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="30" >
<method_context working_directory="/var/www" >
<method_credential user="nobody" group="other" privileges="basic,net_privaddr" />
<method_environment >
<envvar name="HOME" value="/var/tmp" />
<envvar name="PATH" value="/bin:/usr/bin" />
</method_environment>
</method_context>
</exec_method>
<exec_method type="method" name="refresh" exec=":true" timeout_seconds="10" >
<method_context working_directory="/var/www" >
<method_credential user="nobody" group="other" privileges="basic,net_privaddr" />
<method_environment >
<envvar name="HOME" value="/var/tmp" />
<envvar name="PATH" value="/bin:/usr/bin" />
</method_environment>
</method_context>
</exec_method>
<template >
<common_name >
<loctext xml:lang="C" >NGINX Web Server</loctext>
</common_name>
</template>
</service>
</service_bundle>
Assumptions
This tool makes a ton of assumptions about your service:
- Your service is a contract-model service in SMF. That means SMF should consider the service failed (and potentially restart it) if:
- all processes in the service exit, OR
- any process in the service produces a core dump, OR
- any process outside the service sends any service process a fatal signal See svc.startd(1M) for details.
- Your service is an application which depends on system services like the filesystem and network. This tool wouldn't work for system services implementing any of that functionality.
- If you specify any additional dependencies (like other services of yours), that means your service should not be started until those other services are online. However, if those services restart, your service will not be restarted.
- You only intend to have one instance of your service.
- SMF provides a mechanism for timing out the "start" operation. But for simplicity, this tool always runs your start script in the background, so as far as SMF sees it starts almost instantly. If you want to detect "start" timeout, you must implement a start method that returns exactly when your program has started providing service (e.g., opened its server socket), and you'll have to write your own manifest rather than use this tool.
- By default, the "stop" method just kills all processes in this service, which includes all processes forked by the initial "start" script. You can override this with a "stop" script, but you should use the default if that script is only going to kill processes. There's a default 30 second timeout on the stop script, so the processes must exit within about 30 seconds of receiving the signal.
- The service does not use SMF to store configuration properties.