diff options
Diffstat (limited to 'documentation/content/en/articles/rc-scripting/_index.adoc')
-rw-r--r-- | documentation/content/en/articles/rc-scripting/_index.adoc | 209 |
1 files changed, 194 insertions, 15 deletions
diff --git a/documentation/content/en/articles/rc-scripting/_index.adoc b/documentation/content/en/articles/rc-scripting/_index.adoc index bc939aec04..14e0ad4bb4 100644 --- a/documentation/content/en/articles/rc-scripting/_index.adoc +++ b/documentation/content/en/articles/rc-scripting/_index.adoc @@ -62,7 +62,7 @@ With few exceptions, [.filename]#/etc/rc# had to be modified, and true hackers l The real problem with the monolithic approach was that it provided no control over the individual components started from [.filename]#/etc/rc#. For instance, [.filename]#/etc/rc# could not restart a single daemon. The system admin had to find the daemon process by hand, kill it, wait until it actually exited, then browse through [.filename]#/etc/rc# for the flags, and finally type the full command line to start the daemon again. -The task would become even more difficult and prone to errors if the service to restart consisted of more than one daemon or demanded additional actions. +The task would become even more difficult and prone to errors if the service to restart consisted of more than one daemon or demanded additional actions. In a few words, the single script failed to fulfil what scripts are for: to make the system admin's life easier. Later there was an attempt to split out some parts of [.filename]#/etc/rc# for the sake of starting the most important subsystems separately. @@ -84,15 +84,15 @@ The basic ideas behind BSD [.filename]#rc.d# are _fine modularity_ and __code re _Fine modularity_ means that each basic "service" such as a system daemon or primitive startup task gets its own man:sh[1] script able to start the service, stop it, reload it, check its status. A particular action is chosen by the command-line argument to the script. The [.filename]#/etc/rc# script still drives system startup, but now it merely invokes the smaller scripts one by one with the `start` argument. -It is easy to perform shutdown tasks as well by running the same set of scripts with the `stop` argument, which is done by [.filename]#/etc/rc.shutdown#. +It is easy to perform shutdown tasks as well by running the same set of scripts with the `stop` argument, which is done by [.filename]#/etc/rc.shutdown#. Note how closely this follows the Unix way of having a set of small specialized tools, each fulfilling its task as well as possible. _Code reuse_ means that common operations are implemented as man:sh[1] functions and collected in [.filename]#/etc/rc.subr#. Now a typical script can be just a few lines' worth of man:sh[1] code. Finally, an important part of the [.filename]#rc.d# framework is man:rcorder[8], which helps [.filename]#/etc/rc# to run the small scripts orderly with respect to dependencies between them. It can help [.filename]#/etc/rc.shutdown#, too, because the proper order for the shutdown sequence is opposite to that of startup. -The BSD [.filename]#rc.d# design is described in <<lukem, the original article by Luke Mewburn>>, and the [.filename]#rc.d# components are documented in great detail in <<manpages, the respective manual pages>>. -However, it might not appear obvious to an [.filename]#rc.d# newbie how to tie the numerous bits and pieces together in order to create a well-styled script for a particular task. +The BSD [.filename]#rc.d# design is described in crossref:rc-scripting[lukem, the original article by Luke Mewburn], and the [.filename]#rc.d# components are documented in great detail in crossref:rc-scripting[manpages, the respective manual pages]. +However, it might not appear obvious to an [.filename]#rc.d# newbie how to tie the numerous bits and pieces together to create a well-styled script for a particular task. Therefore this article will try a different approach to describe [.filename]#rc.d#. It will show which features should be used in a number of typical cases, and why. Note that this is not a how-to document because our aim is not at giving ready-made recipes, but at showing a few easy entrances into the [.filename]#rc.d# realm. @@ -100,7 +100,7 @@ Neither is this article a replacement for the relevant manual pages. Do not hesitate to refer to them for more formal and complete documentation while reading this article. There are prerequisites to understanding this article. -First of all, you should be familiar with the man:sh[1] scripting language in order to master [.filename]#rc.d#. +First of all, you should be familiar with the man:sh[1] scripting language to master [.filename]#rc.d#. In addition, you should know how the system performs userland startup and shutdown tasks, which is described in man:rc[8]. This article focuses on the FreeBSD branch of [.filename]#rc.d#. @@ -110,7 +110,7 @@ Nevertheless, it may be useful to NetBSD developers, too, because the two branch == Outlining the task A little consideration before starting `$EDITOR` will not hurt. -In order to write a well-tempered [.filename]#rc.d# script for a system service, we should be able to answer the following questions first: +To write a well-tempered [.filename]#rc.d# script for a system service, we should be able to answer the following questions first: * Is the service mandatory or optional? * Will the script serve a single program, e.g., a daemon, or perform more complex actions? @@ -157,7 +157,7 @@ For example, a system admin can run our script manually, from the command line: [NOTE] ==== -In order to be properly managed by the [.filename]#rc.d# framework, its scripts need to be written in the man:sh[1] language. +To be properly managed by the [.filename]#rc.d# framework, its scripts need to be written in the man:sh[1] language. If you have a service or port that uses a binary control utility or a startup routine written in another language, install that element in [.filename]#/usr/sbin# (for the system) or [.filename]#/usr/local/sbin# (for ports) and call it from a man:sh[1] script in the appropriate [.filename]#rc.d# directory. ==== @@ -185,7 +185,9 @@ That is, each [.filename]#rc.d# script _must_ set `name` before it calls man:rc. Now it is the right time to choose a unique name for our script once and for all. We will use it in a number of places while developing the script. -For a start, let us give the same name to the script file, too. +The content of the name variable needs to match the script name, +some parts of FreeBSD (e.g., crossref:rc-scripting[rcng-service-jails, service jails] and the cpuset feature of the rc framework) depend upon this. +As such the filename shall also not contain characters which may be troublesome in scripting (e.g., do not use a hyphen "-" and others). [NOTE] ==== @@ -365,7 +367,8 @@ This is reflected in the list of processes, which can confuse man:rc.subr[8]. You should additionally set `command_interpreter` to let man:rc.subr[8] know the actual name of the process if `$command` is a script. For each [.filename]#rc.d# script, there is an optional man:rc.conf[5] variable that takes precedence over `command`. -Its name is constructed as follows: `${name}_program`, where `name` is the mandatory variable we discussed <<name-var, earlier>>. +Its name is constructed as follows: `${name}_program`, where `name` is the +mandatory variable we discussed crossref:rc-scripting[name-var, earlier]. E.g., in this case it will be `mumbled_program`. It is man:rc.subr[8] that arranges `${name}_program` to override `command`. @@ -448,7 +451,7 @@ Since the final command line is passed to `eval` for its actual execution, input _Never_ include dashed options, like `-X` or `--foo`, in `command_args`. The contents of `command_args` will appear at the end of the final command line, hence they are likely to follow arguments present in `${name}_flags`; but most commands will not recognize dashed options after ordinary arguments. A better way of passing additional options to `$command` is to add them to the beginning of `${name}_flags`. -Another way is to modify `rc_flags` <<rc-flags, as shown later>>. +Another way is to modify `rc_flags` crossref:rc-scripting[rc-flags, as shown later]. ==== ➋ A good-mannered daemon should create a _pidfile_ so that its process can be found more easily and reliably. @@ -504,7 +507,7 @@ The reason is that not all daemons use the same reload mechanism and some have n So we need to ask explicitly that the builtin functionality be provided. We can do so via `extra_commands`. -What do we get from the default method for `reload`? Quite often daemons reload their configuration upon reception of a signal - typically, SIGHUP. +What do we get from the default method for `reload`? Quite often daemons reload their configuration upon reception of a signal - typically, SIGHUP. Therefore man:rc.subr[8] attempts to reload the daemon by sending a signal to it. The signal is preset to SIGHUP but can be customized via `sig_reload` if necessary. ==== @@ -586,7 +589,7 @@ However, man:rc.subr[8] can be instructed from the command line to ignore those After a script has been written, it needs to be integrated into [.filename]#rc.d#. The crucial step is to install the script in [.filename]#/etc/rc.d# (for the base system) or [.filename]#/usr/local/etc/rc.d# (for ports). Both [.filename]#bsd.prog.mk# and [.filename]#bsd.port.mk# provide convenient hooks for that, and usually you do not have to worry about the proper ownership and mode. -System scripts should be installed from [.filename]#src/etc/rc.d# through the [.filename]#Makefile# found there. +System scripts should be installed from [.filename]#src/libexec/rc/rc.d# through the [.filename]#Makefile# found there. Port scripts can be installed using `USE_RC_SUBR` as described extref:{porters-handbook}[in the Porter's Handbook, rc-scripts]. However, we should consider beforehand the place of our script in the system startup sequence. @@ -664,18 +667,18 @@ According to the lines, our script asks man:rcorder[8] to put it after the scrip [NOTE] ==== The `BEFORE:` line should not be abused to work around an incomplete dependency list in the other script. -The appropriate case for using `BEFORE:` is when the other script does not care about ours, but our script can do its task better if run before the other one. +The appropriate case for using `BEFORE:` is when the other script does not care about ours, but our script can do its task better if run before the other one. A typical real-life example is the network interfaces vs. the firewall: While the interfaces do not depend on the firewall in doing their job, the system security will benefit from the firewall being ready before there is any network traffic. Besides conditions corresponding to a single service each, there are meta-conditions and their "placeholder" scripts used to ensure that certain groups of operations are performed before others. These are denoted by [.filename]#UPPERCASE# names. Their list and purposes can be found in man:rc[8]. -Keep in mind that putting a service name in the `REQUIRE:` line does not guarantee that the service will actually be running by the time our script starts. +Keep in mind that putting a service name in the `REQUIRE:` line does not guarantee that the service will actually be running by the time our script starts. The required service may fail to start or just be disabled in man:rc.conf[5]. Obviously, man:rcorder[8] cannot track such details, and man:rc[8] will not do that either. Consequently, the application started by our script should be able to cope with any required services being unavailable. -In certain cases, we can help it as discussed <<forcedep, below>> +In certain cases, we can help it as discussed crossref:rc-scripting[forcedep, below] ==== [[keywords]]➍ As we remember from the above text, man:rcorder[8] keywords can be used to select or leave out some scripts. @@ -831,6 +834,182 @@ That is, arguments with embedded whitespace may not be processed correctly. The bug stems from `$*` misuse. ==== +[[rcng-service-jails]] +== Making a script ready for Service Jails + +Scripts which start a long running service are suitable for service jails, and should come with a suitable service jail configuration. + +Some examples of scripts which are not suitable to run in a service jail: + +* any script which in the start command only changes a runtime setting for programs or the kernel, +* or tries to mount something, +* or finds and deletes files + +Scripts not suitable to run in a service jail need to prevent the use within service jails. + +A script with a long running service which needs to do something listed above before the start or after the stop, can either be split-up into two scripts with dependencies, or use the precommand and postcommand parts of the script to perform this action. + +By default, only the start and stop parts of a script are run within a service jail, the rest is run outside the jail. +As such any setting used in the start/stop parts of the script can not be set from e.g. a precommand. + +To make a script ready for use with extref:../../books/handbook/jails/#service-jails[Service Jails], only one more config line needs to be inserted: + +[.programlisting] +.... +#!/bin/sh + +. /etc/rc.subr + +name="dummy" +start_cmd="${name}_start" +stop_cmd=":" + +: ${dummy_svcj_options:=""} <.> + +dummy_start() +{ + echo "Nothing started." +} + +load_rc_config $name +run_rc_command "$1" +.... + +➊ If it makes sense that the script runs in a jail, it must have an overridable service jails configuration. +If it does not need network access or access to any other resource which is restricted in jails, an empty config like displayed is enough. + +Strictly speaking an empty config is not needed, but it explicitly describes that the script is service jails ready, and that it does not need additional jail permissions. +As such it is highly recommended to add such an empty config in such a case. +The most common option to use is "net_basic", which enables the use of the hosts IPv4 and IPv6 addresses. +All possible options are explained in man:rc.conf[5]. + +If a setting for the start/stop depends on variables from the rc-framework (e.g., set inside man:rc.conf[5]), this needs to be handled by ``load_rc_config`` and ``run_rc_command`` instead of inside a precommand. + +If for some reason a script can not be run within a service jail, e.g., because it is not possible to run or it does not make sense to run it in a jail, use the following: + +[.programlisting] +.... +#!/bin/sh + +. /etc/rc.subr + +name="dummy" +start_cmd="${name}_start" +stop_cmd=":" + +dummy_start() +{ + echo "Nothing started." +} + +load_rc_config $name +dummy_svcj="NO" # does not make sense to run in a svcj <.> +run_rc_command "$1" +.... + +➊ The disabling needs to happen after the ``load_rc_config`` call, else a man:rc.conf[5] setting may override it. + +[[rcng-instancing]] +== Advanced rc-scripting: Instancing + +Sometimes it is useful to run several instances of a service. +Typically you want to be able to start/stop such instances independently, +and you want to have a separate config file for each instance. +Each instance should be started at boot, +survive updates, +and benefit from updates. + +Here is an example of a rc script which supports this: + +[.programlisting] +.... +#!/bin/sh + +# +# PROVIDE: dummy +# REQUIRE: NETWORKING SERVERS +# KEYWORD: shutdown +# +# Add these following line to /etc/rc.conf.local or /etc/rc.conf +# to enable this service: +# +# dummy_enable (bool): Set it to YES to enable dummy on startup. +# Default: NO +# dummy_user (string): User account to run with. +# Default: www +# + +. /etc/rc.subr + +case $0 in <.> +/etc/rc*) + # during boot (shutdown) $0 is /etc/rc (/etc/rc.shutdown), + # so get the name of the script from $_file + name=$_file + ;; +*) + name=$0 + ;; +esac + +name=${name##*/} <.> +rcvar="${name}_enable" <.> +desc="Short description of this service" +command="/usr/local/sbin/dummy" + +load_rc_config "$name" + +eval "${rcvar}=\${${rcvar}:-'NO'}" <.> +eval "${name}_svcj_options=\${${name}_svcj_options:-'net_basic'}" <.> +eval "_dummy_user=\${${name}_user:-'www'}" <.> + +_dummy_configname=/usr/local/etc/${name}.cfg <.> +pidfile=/var/run/dummy/${name}.pid +required_files ${_dummy_configname} +command_args="-u ${_dummy_user} -c ${_dummy_configfile} -p ${pidfile}" + +run_rc_command "$1" +.... + +➊ and ➋ make sure to set the name variable to the man:basename[1] of the script name. +If the filename is [.filename]#/usr/local/etc/rc.d/dummy#, +name is set to [.filename]#dummy#. +This way changing the filename of the rc script changes automatically the content of the name variable. + +➌ specifies the variable name which is used in [.filename]#rc.conf# to enable this service based upon the filename of this script. +In this example this resolves to dummy_enable. + +➍ makes sure the default for the _enable variable is NO. + +➎ is an example of having some defaults for service specific framework variables, +in this case the service jails options. + +➏ and ➐ set variables internal to the script (pay attention to the underscore in front of _dummy_user to make it different from dummy_user which can be set in [.filename]#rc.conf#). + +The part in ➎ is for variables which are not used inside the script itself but in the rc framework. +All the variables which are used as parameters somewhere in the script are assigned to a generic variable like in ➐ to make it more easy to reference them (no need to eval them at each place of use). + +This script will now behave differently if the start script has a different name. +This allows to create symlinks to it: + +[source,shell] +.... +# ln -s dummy /usr/local/etc/rc.d/dummy_foo +# sysrc dummy_foo_enable=YES +# service dummy_foo start +.... + +The above creates an instance of the dummy service with the name dummy_foo. +It does not use the config file [.filename]#/usr/local/etc/dummy.cfg# but the config file [.filename]#/usr/local/etc/dummy_foo.cfg# (➐), +and it uses the PID file [.filename]#/var/run/dummy/dummy_foo.pid# instead of [.filename]#/var/run/dummy/dummy.pid#. + +The services dummy and dummy_foo can be managed independently of each other, +while having the start script update itself on package update (due to the symlink). +This does not update the REQUIRE line, +as such there is no easy way of depending on a specific instance. +To depend upon a specific instance in the startup order a copy needs to be made instead of using a symlink. +This prevents the automatic pick-up of changes to the start script when an update is installed. + [[rcng-furthur]] == Further reading |