diff --git a/package.xml b/package.xml
index 0605a1f..255c866 100644
--- a/package.xml
+++ b/package.xml
@@ -18,6 +18,7 @@
daemontools
net-tools
roslaunch
+ supervisor
util-linux
xacro
diff --git a/src/robot_upstart/install_script.py b/src/robot_upstart/install_script.py
index 29fbd77..a5cb547 100644
--- a/src/robot_upstart/install_script.py
+++ b/src/robot_upstart/install_script.py
@@ -62,7 +62,7 @@ def get_argument_parser():
help="Specify an a value for ROS_LOG_DIR in the job launch context.")
p.add_argument("--augment", action='store_true',
help="Bypass creating the job, and only copy user files. Assumes the job was previously created.")
- p.add_argument("--provider", type=str, metavar="[upstart|systemd]",
+ p.add_argument("--provider", type=str, metavar="[upstart|systemd|supervisor]",
help="Specify provider if the autodetect fails to identify the correct provider")
p.add_argument("--symlink", action='store_true',
help="Create symbolic link to job launch files instead of copying them.")
@@ -71,6 +71,9 @@ def get_argument_parser():
p.add_argument("--systemd-after", type=str, metavar="After=",
help="Set the string of the After= section"
"of the generated Systemd service file")
+ p.add_argument("--supervisor-priority", type=int, metavar="Priority=",
+ help="Set the value of the priority= section"
+ "of the generated Supervisor conf file")
return p
@@ -89,7 +92,9 @@ def main():
name=job_name, interface=args.interface, user=args.user,
workspace_setup=args.setup, rosdistro=args.rosdistro,
master_uri=args.master, log_path=args.logdir,
- systemd_after=args.systemd_after)
+ sigterm_stop=(args.provider=='supervisor'),
+ systemd_after=args.systemd_after,
+ supervisor_priority=args.supervisor_priority)
for this_pkgpath in args.pkgpath:
pkg, pkgpath = this_pkgpath.split('/', 1)
@@ -121,6 +126,8 @@ def main():
provider = providers.Upstart
if args.provider == 'systemd':
provider = providers.Systemd
+ if args.provider == 'supervisor':
+ provider = providers.Supervisor
if args.symlink:
j.symlink = True
diff --git a/src/robot_upstart/job.py b/src/robot_upstart/job.py
index 895525c..561680d 100644
--- a/src/robot_upstart/job.py
+++ b/src/robot_upstart/job.py
@@ -40,8 +40,8 @@ class Job(object):
""" Represents a ROS configuration to launch on machine startup. """
def __init__(self, name="ros", interface=None, user=None, workspace_setup=None,
- rosdistro=None, master_uri=None, log_path=None,
- systemd_after=None):
+ rosdistro=None, master_uri=None, log_path=None, sigterm_stop=None,
+ systemd_after=None, supervisor_priority=None):
"""Construct a new Job definition.
:param name: Name of job to create. Defaults to "ros", but you might
@@ -105,6 +105,13 @@ def __init__(self, name="ros", interface=None, user=None, workspace_setup=None,
# of the generated Systemd service file
self.systemd_after = systemd_after or "network.target"
+ # Set the value of the "priority=" section
+ # of the generated Supservisor conf file
+ self.supervisor_priority = supervisor_priority or 200
+
+ # call @(name)-stop script when received TERM signal
+ self.sigterm_stop = sigterm_stop
+
# Set of files to be installed for the job. This is only launchers
# and other user-specified configs--- nothing related to the system
# startup job itself. List of strs.
diff --git a/src/robot_upstart/providers.py b/src/robot_upstart/providers.py
index a91b6bd..a27cf84 100644
--- a/src/robot_upstart/providers.py
+++ b/src/robot_upstart/providers.py
@@ -40,6 +40,8 @@ def detect_provider():
print(os.path.realpath(cmd))
if b'systemd' in os.path.realpath(cmd):
return Systemd
+ if b'supervisor' in os.path.realpath(cmd):
+ return Supervisor
return Upstart
@@ -228,3 +230,78 @@ def _fill_template(self, template):
self.interpreter.file(f)
return self.interpreter.output.getvalue()
self.set_job_path()
+
+
+class Supervisor(Generic):
+ """ The Supervisor implementation places the user-specified files in ``/etc/ros/DISTRO/NAME.d``,
+ and creates an systemd job configuration in ``/lib/systemd/system/NAME.d``. Two additional
+ helper scripts are created for starting and stopping the job, places in
+ ``/usr/sbin``.
+ To detect which system you're using run: ps -p1 | grep systemd && echo systemd || echo upstart
+ """
+
+ def generate_install(self):
+ # Default is /etc/ros/DISTRO/JOBNAME.d
+ self._set_job_path()
+
+ # User-specified launch files.
+ self._add_job_files()
+
+ # This is optional to support the old --augment flag where a "job" only adds
+ # launch files to an existing configuration.
+ if self.job.generate_system_files:
+ # Share a single instance of the EmPy interpreter.
+ self.interpreter = em.Interpreter(globals=self.job.__dict__.copy())
+
+ self.installation_files[os.path.join(self.root, "etc/supervisor/conf.d", self.job.name + ".conf")] = {
+ "content": self._fill_template("templates/supervisor_job.conf.em"), "mode": 0o644}
+ self.installation_files[os.path.join(self.root, "usr/sbin", self.job.name + "-start")] = {
+ "content": self._fill_template("templates/job-start.em"), "mode": 0o755}
+ self.installation_files[os.path.join(self.root, "usr/sbin", self.job.name + "-stop")] = {
+ "content": self._fill_template("templates/job-stop.em"), "mode": 0o755}
+ self.interpreter.shutdown()
+
+ # Add an annotation file listing what has been installed. This is a union of what's being
+ # installed now with what has been installed previously, so that an uninstall should remove
+ # all of it. A more sophisticated future implementation could track contents or hashes and
+ # thereby warn users when a new installation is stomping a change they have made.
+ self._load_installed_files_set()
+ self.installed_files_set.update(list(self.installation_files.keys()))
+
+ # Remove the job directory. This will fail if it is not empty, and notify the user.
+ self.installed_files_set.add(self.job.job_path)
+
+ # Remove the annotation file itself.
+ self.installed_files_set.add(self.installed_files_set_location)
+
+ self.installation_files[self.installed_files_set_location] = {
+ "content": "\n".join(self.installed_files_set)}
+
+ return self.installation_files
+
+ def post_install(self):
+ print("** To complete installation please run the following command:")
+ print(" sudo supervisorctl reload" +
+ " && sudo supervisorctl status" +
+ " ; browse https://localhost:9001")
+
+ def generate_uninstall(self):
+ self._set_job_path()
+ self._load_installed_files_set()
+
+ for filename in self.installed_files_set:
+ self.installation_files[filename] = {"remove": True}
+
+ return self.installation_files
+
+ def _set_job_path(self):
+ self.job.job_path = os.path.join(
+ self.root, "etc/ros", self.job.rosdistro, self.job.name + ".d")
+
+ def _fill_template(self, template):
+ self.interpreter.output = io.StringIO()
+ self.interpreter.reset()
+ with open(find_in_workspaces(project="robot_upstart", path=template)[0]) as f:
+ self.interpreter.file(f)
+ return self.interpreter.output.getvalue()
+ self.set_job_path()
diff --git a/templates/job-start.em b/templates/job-start.em
index d69fd03..e31a96b 100644
--- a/templates/job-start.em
+++ b/templates/job-start.em
@@ -107,6 +107,13 @@ fi
setpriv --reuid @(user) --regid @(user) --init-groups roslaunch $LAUNCH_FILENAME @(roslaunch_wait?'--wait ')&
PID=$!
+@[if sigterm_stop]@
+_term() {
+ /usr/sbin/@(name)-stop
+}
+trap _term SIGTERM
+@[end if]
+
log info "@(name): Started roslaunch as background process, PID $PID, ROS_LOG_DIR=$ROS_LOG_DIR"
echo "$PID" > $log_path/@(name).pid
diff --git a/templates/supervisor_job.conf.em b/templates/supervisor_job.conf.em
new file mode 100644
index 0000000..ba6c550
--- /dev/null
+++ b/templates/supervisor_job.conf.em
@@ -0,0 +1,34 @@
+@#
+@# Author: Kei Okada
+@# Copyright (c) 2022
+@#
+@# Redistribution and use in source and binary forms, with or without
+@# modification, are permitted provided that the following conditions are met:
+@# * Redistributions of source code must retain the above copyright
+@# notice, this list of conditions and the following disclaimer.
+@# * Redistributions in binary form must reproduce the above copyright
+@# notice, this list of conditions and the following disclaimer in the
+@# documentation and/or other materials provided with the distribution.
+@# * Neither the name of the copyright holder nor the
+@# names of its contributors may be used to endorse or promote products
+@# derived from this software without specific prior written permission.
+@#
+@# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+@# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+@# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+@# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+@# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+@# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+@# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+@# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+@# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+@# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+@#
+# THIS IS A GENERATED FILE, NOT RECOMMENDED TO EDIT.
+
+[program:@(name)]
+command=/usr/sbin/@(name)-start
+stopsignal=TERM
+autostart=true
+autorestart=false
+priority=@(supervisor_priority)