Installing the server

If you plan to run the Vinegar server on Ubuntu Linux (Ubuntu 16.04 LTS or newer) or a modern release of Debian, you are lucky: The Vinegar source tree also is a Debian source package that can be built with dpkg-buildpackage.

If you use one of the Ubuntu releases for which we provide binary packages, you might not even have to built the packages yourself, but can simply pick up the binary packages that we already built for you. In this case you can skip to Installing the binary Debian packages. If you are not going to run Vinegar on any flavor of Debian (anything that uses DPKG), you can skip the sections about the Debian packages and continue directly with Installing Vinegar from source (on non-Debian systems).

Building Debian packages from source

If there are no binary packages for the specific release of Ubuntu that you use or if you use a different flavor or a Debian-based distribution, you might have to build the binary packages from source.

In general, the binary packages that we provide do not contain any compiled files, so they will most likely work with any sufficiently recent Debian-based distribution, but if you experience problems, you might want to try building the packages from source.

Luckily, this is very simple. You simply unpack the source tree of the Vinegar release that you want to build and change into that directory. Inside that directory, you simply run dpkg-buildpackage. This command is provided by the package dpkg-dev, so you might have to install that package first.

Vinegar has a couple of build-time dependencies, but dpkg-buildpackage will tell you about those, so you can simply install those dependencies (at least for Ubuntu, they are all available from the standard package repositories) and run dpkg-buildpackage again.

When dpkg-buildpackage is done, you will see an error message like dpkg-buildpackage: error: failed to sign .dsc file. This is not a problem, it simply means that the binary packages that have been build are not signed, but if you are going to install them manually, this is not an issue.

Installing the binary Debian packages

Regardless of whether you downloaded the binary packages or you built them yourself, you will have the following binary packages:

  • python3-vinegar: This package contains all the Python module of Vinegar. It is a dependency of the vinegar-server package, but it can also be installed on its own if you have code using the Vinegar API, but do not want to run the Vinegar server on the same machine.

  • vinegar-doc: This package contains the documentation for Vinegar. This is the documentation that you are reading right now. You can install it if you want to have access to the documentation without need access to the Internet.

  • vinegar-server: This package contains the actual Vinegar server. As the Python code is already provided by python3-vinegar, this package only provides a start script a Systemd unit file, and an example configuration file. It also contains a configuration file for logrotate, so that log files written by the server will not accumulate.

If you have added these packages to a local Apt repository, you can simply install the Vinegar server by running

apt-get-install vinegar-server

This will also install the python3-vinegar package. Otherwise, you have to install both packages manually by running

dpkg -i python3-vinegar_x.x.x_all.deb vinegar-server_x.x.x_all.deb

The Vinegar server is automatically started after the vinegar-server package has been installed. You can find its configuration in /etc/vinegar and its log files in /var/log/vinegar.

Now that the Vinegar server is running, you can skip to the Server configuration section or you can read the next section to learn more about the things of which the Debian package already took care.

Note

The vinegar-server package provides a configuration file for Systemd that is used to start the server. If you are using a distribution that uses an alternative init system, you have to start the server yourself. The Installing Vinegar from source (on non-Debian systems) section has a few hints about how you might do this. Please note that in case of the Debian package, the vinegar-server executable can be found in /usr/sbin/vinegar-server.

Installing Vinegar from source (on non-Debian systems)

Vinegar can be built and installed using Setuptools. Usually, it should be sufficient to run

python3 setup.py install

This should install the dependencies of Vinegar (Jinja2 and PyYAML) copy the Python modules for Vinegar into a directory that is included in the PYTHONPATH and create a vinegar-server script that can be used to start the Vinegar server. On Linux, this script will typically be created in /usr/bin or /usr/local/bin. On Windows, it will typically be created in the Scripts sub-directory of the Python installation directory.

Vinegar needs a server configuration file. On Windows, this file is expected in C:\Vinegar\conf\vinegar-server.yaml. On all other platforms, it is expected in /etc/vinegar/vinegar-server.yaml. The path can be overridden by passing the --config-file argument to vinegar-server. For example:

vinegar-server --config-file=path/to/my/config-file.yaml

An example configuration is going to be discussed in the next section.

Typically, you will not want to run the server manually from a console, but have it start automatically as a system service. When using a Linux distribution that uses Systemd as its init system, you can use a unit file like the following (the Debian package uses a very similar definition):

[Unit]
Description=Vinegar Boot Server
After=network.target

[Service]
ExecStart=/usr/bin/vinegar-server
Restart=on-failure
RestartSec=5s

User=vinegar
Group=vinegar

CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

In order for this unit file to work, you have to create the vinegar user and group and have to use Linux kernel 4.3 or newer. For older kernel versions, the AmbientCapabilities option does not work, so that the server does not get the CAP_NET_BIND_SERVICE capability. This capabilitiy is needed in order to bind to TCP and UDP ports below 1024. While the HTTP server can reasonably be bound to a different port, this is not a good option for the TFTP server because PXE clients will expect it to listen on the default port (UDP port 69). This means that on a system where you cannot use AmbientCapabilities, you will have to run the server as root (not recommended) or find an alternative way of binding to a privileged port, like authbind.

If running on a distribution that uses an alternative init system you will have to find a different solution for starting the server. One option might be running it inside a Screen session. In this case, you will probably have to use authbind, too.

Server configuration

If you installed the Vinegar server as a Debian package, congratulations, an example configuration file has already been created for you. Otherwise, you have to create the configuration file (typically as /etc/vinegar/vinegar-server.yaml see the preceding section for details).

Here is the full example file so that you can copy and paste it. There are some comments in the file, but we will also discuss it in this section.

This file does not work on its own. It needs a second file that describes the logging configuration and is discussed in the sub-section Logging.

# List of data sources.
# The data sources are processed in order: Data from data sources earlier in the
# list is provided to data sources later in the list. Data from data sources
# later in the list overrides data from data sources earlier in the list.
data_sources:
  # The first data source reads the file /srv/vinegar/systems/list.txt,
  # expecting one line for every system. Each line has the following format:
  #
  # <MAC address>:<IPv4 address>:<hostname>[,<extra name 1>,<extra-name 2>,...]
  #
  # This format can be customized by change the configuration for the data
  # source. Please refer to the documentation for the text_file data source for
  # a full list of available configuration options.
  - name: text_file

    # We set the filename to /srv/vinegar/systems/list.txt.
    file: /srv/vinegar/systems/list.txt

    # This is the regular expression that matches the lines that we want to use.
    # We specify the X flag first (?x) so that we can use the multi-line syntax,
    # which makes the regular expression much more readable.
    regular_expression: |
        (?x)
        # We expect a CSV file with three columns that are separated by
        # semicolons.
        # The first column specifies the MAC address.
        (?P<mac>[0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5});
        # The second column specifies the IP address.
        (?P<ip>[0-9]{1,3}(?:\.[0-9]{1,3}){3});
        # The third column specifies the hostname and an optional list of
        # additional names.
        (?P<hostname>[^,]+)
        (,(?P<extra_names>.+))?

        # We want to ignore empty lines and lines starting with a "#".
    regular_expression_ignore: "|(?:#.*)"

    # We build the system ID from the hostname by adding a domain name and
    # ensuring that everything is in lower case.
    system_id:
      source: hostname
      transform:
        - string.add_suffix: .mydomain.example.com
        - string.to_lower

    # We define a couple of variables that will be available in the data tree
    # for each system.
    variables:
      'info:extra_names':
        source: extra_names
        transform:
          - string.to_lower
          - string.split: .
      'net:fqdn':
        source: hostname
        transform:
          - string.add_suffix: .mydomain.example.com
          - string.to_lower
      'net:hostname':
        source: hostname
        transform:
          - string.to_lower
      'net:ipv4_addr':
        source: ip
        transform:
          - ipv4_address.normalize
      'net:mac_addr':
        source: mac
        transform:
          - mac_address.normalize

  # We use a yaml_target data source as the second source in the list. This
  # source expects a top configuration file /srv/vinegar/datatree/top.yaml and
  # includes further files bases on the configuration in that file. As the
  # text_file data source is earlier in the list, the data from that data source
  # can be used in the files for the yaml_target data source through Jinja
  # template syntax (e.g. "{{ data.get('net:macaddr') }}").
  - name: yaml_target
    root_dir: /srv/vinegar/datatree

  # As the last source, we use an sqlite data source. This data source allows us
  # to update single pieces of data in a safe way (ensuring that these updates
  # become visible immediately). We use the same database as the HTTP request
  # handler later in this file, so that we can use data items updates by that
  # request handler.
  - name: sqlite

    # We use a database stored in /var/lib/vinegar/system-state.db.
    db_file: /var/lib/vinegar/system-state.db

    # We disable the find_system function for this data source because it only
    # stores flags that are not really useful for a reverse lookup.
    find_system_enabled: False

    # We store the data for this data source under a separate key in order to
    # avoid collissions with keys from other sources.
    key_prefix: state

# Lists in the data provided by data sources is not merged by default. Instead,
# if a data source later in the list of data sources provides a list for the
# same key as a data source earlier in the list, the list from the data source
# that is later in the list completely replaces the list from the data source
# earlier in the list. This can be changed by setting this option to True.
# data_sources_merge_lists: False

# Configuration for the HTTP server.
http:
  # The HTTP server binds to all local interfaces by default.
  # bind_address: '::'

  # The HTTP server binds to port 80 by default.
  # bind_port: 80

  # The list of request handlers is processed in order, using the first handler
  # that matches.
  request_handlers:

    # We register a request handler that serves files after rendering them
    # through the Jinja template engine. This allows us to use data from the
    # data sources defined above in that files.
    - name: file

      # We have to define a request path. The files served by this request
      # handler are going to be available at
      # http://vinegar-server.example.com/templates/<system ID>/...
      request_path: /templates/...

      # We expect the system-ID to be specified as part of the request path.
      # This information is used to decide for which system a file should be
      # rendered.
      lookup_key: ':system_id:'

      # We use files in /srv/vinegar/http/templates as the templates.
      root_dir: /srv/vinegar/http/templates

      # We want to render the files with the Jinja template engine.
      template: jinja

    # We register a second request handler that can be used to reset the
    # 'netboot_enabled' flag in the SQLite database. This way, we can reset this
    # flag from an installer environment in order to avoid booting into the
    # installer again.
    - name: sqlite_update

      # This request handler is going to be available at
      # http://vinegar-server.example.com/reset-netboot-enabled/<system ID>
      request_path: /reset-netboot-enabled

      # This request handler uses the same database file as the sqlite datasource
      # defined earlier.
      db_file: /var/lib/vinegar/system-state.db

      # This handler deletes the data for the 'netboot_enabled' key, effectively
      # resetting the flag.
      action: delete_data
      key: netboot_enabled

      # We only allow a client to use this request handler if its IP address
      # matches the one for the targeted system. We know the IP address because
      # the text_file data source that we defined earlier provides it.
      client_address_key: 'net:ipv4_addr'

# Path to the logging configuration.
logging_config_file: /etc/vinegar/vinegar-server-logging.ini

# Configuration for the TFTP server.
tftp:
  # The TFTP server binds to all local interfaces by default.
  # bind_address: '::'

  # The TFTP server binds to port 69 by default. While you can change this port
  # number, most PXE clients will only use that port, so it will usually not be
  # useful to bind to a different one.
  # bind_port: 69

  # The list of request handlers is processed in order, using the first handler
  # that matches.
  request_handlers:

    # We register a request handler that serves files after rendering them
    # through the Jinja template engine. This allows us to use data from the
    # data sources defined above in that files.
    - name: file

      # We have to define a request path. The files served by this request
      # handler are going to be available at
      # tftp://vinegar-server.example.com/templates/<MAC address>/...
      request_path: /templates/...

      # We expect the MAC address to be specified as part of the request path.
      # This information is used to find the system ID which in turn allows us
      # to decide for which system a file should be rendered. We cannot use the
      # system ID directly because we will typically not know it in the PXE
      # environment, but the MAC address is known (e.g. $net_default_mac in
      # GRUB 2). The lookup the the MAC address works because the text-file
      # based data source defined earlier knows the MAC address for each system.
      lookup_key: 'net:mac_addr'

      # The MAC address specified by the client might not necessarily use the
      # same formatting as the data source, so we normalize the MAC address in
      # order to avoid false negatives.
      lookup_value_transform:
        - mac_address.normalize

      # We use files in /srv/vinegar/tftp/templates as the templates.
      root_dir: /srv/vinegar/tftp/templates

      # We want to render the files with the Jinja template engine.
      template: jinja

      # We want to use this request handler to load parts of the GRUB
      # configuration. This means that a problem with this handler could result
      # in a system getting stuck at the GRUB boot screen. We want to avoid this
      # at all cost, so we rather render a template without having
      # system-specifc data than not being able to fulfill the request. The
      # template files obviously have to be written in a way that they can
      # handle situation where there is no system data (and as a result the id
      # and data context objects are not available). We still want to log a
      # warning if such a situation appears so that we can fix the problem that
      # causes it in the first place.
      data_source_error_action: warn
      lookup_no_result_action: continue

  # We register two more request handler that serve static files without
  # rendering them as templates. We need these handler for two reasons. First,
  # we cannot render binary files (like the boot loader or kernel images) as
  # templates as this would corrupt them. Second, we do not know the MAC
  # address yet when loading the initial parts of the boot loader (the path
  # to these parts is fixed in the DHCP configuration).
  # The first handler is used for the files belonging to GRUB.
  - name: file

    # We have to define a request path. The files served by this request
    # handler are going to be available at
    # tftp://vinegar-server.example.com/grub/...
    request_path: /grub

    # We serve files from /srv/vinegar/tftp/grub.
    root_dir: /srv/vinegar/tftp/grub

  # The second request handler is used for installer files like the kernel
  # images and initial ramdisks.
  - name: file
    request_path: /images
    root_dir: /srv/vinegar/tftp/images

We are not going to discuss all the option that can be used in the configuration file. For a full list of options supported by the server, please refer to the API reference for vinegar.cli.server. For the options supported by the various sub-components, please refer to their respective API reference (there are pointers to them in the following paragraphs).

Data sources

The first section of the file defines the data sources. In this example, we define three data sources.

The first data source is of type text_file. From a configuration perspective, it is the most complex data source type:

- name: text_file
  file: /srv/vinegar/systems/list.txt
  regular_expression: |
      (?x)
      (?P<mac>[0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5});
      (?P<ip>[0-9]{1,3}(?:\.[0-9]{1,3}){3});
      (?P<hostname>[^,]+)
      (,(?P<extra_names>.+))?
  regular_expression_ignore: "|(?:#.*)"
  system_id:
    source: hostname
    transform:
      - string.add_suffix: .mydomain.example.com
      - string.to_lower
  variables:
    'info:extra_names':
      source: extra_names
      transform:
        - string.to_lower
        - string.split: .
    'net:fqdn':
      source: hostname
      transform:
        - string.add_suffix: .mydomain.example.com
        - string.to_lower
    'net:hostname':
      source: hostname
      transform:
        - string.to_lower
    'net:ipv4_addr':
      source: ip
      transform:
        - ipv4_address.normalize
    'net:mac_addr':
      source: mac
      transform:
        - mac_address.normalize

This complexity comes from the fact that it is designed to work with almost any text file as its source of data. When you have a text file that contains one line per system, this data source is almost certainly able to process it.

The configuration above is designed to match a text file in the following format:

# Lines starting with a # are ignored.
02:00:00:00:00:01;192.0.2.1;myhost1
# Empty lines are ignored as well.

02:00:00:00:00:02;192.0.2.2;myhost2
02:00:00:00:00:03;192.0.2.1;myhost3,alias-for-myhost3

The format of lines containing data is configured through the regular_expression option. It typically makes sense to specify the (?x) option at the start of the expression. This has the consequence that whitespace outside character classes and comments are ignored, so the regular expression (which might be quite complex) can be formatted nicely.

We do not discuss the details of writing regular expressions here, please refer to the documentation of Python’s re module for that. We are just going to have quick look at the regular expression that we use in this example.

As already said, the first line enables the multi-line mode. The second line of the expression matches the first column in the text file and puts it into a catching group with the name mac. This way, we can refer to this group by name from the variable definitions later in the configuration. If we used an unnamed catching group, we could still refer to it by its integer index, but this would be less comfortable.

The third line matches the second column, which stores the IP address, and makes it available in the ip group.

The fourth line matches the first name in the third column and makes it available as the hostname group.

The final line matches more names in the third column and makes them available in the extra_names group.

The regular_expression_ignore option specifies a regular expression of lines that shall be ignored. In this example, we ignore lines that start with a # or are empty.

The system_id option defines how the system ID is extracted from a line. The concept of System IDs is described in Concepts. The system ID is generated by using the value of the capturing group that is identified through the source option and then (optionally) transformed using the transformations specified through the transform option. In this example, we add a suffix to the extracted string (so that we get an FQDN) and make sure that the resulting string is all lower case.

The main purpose of a data source is providing data associated with a system ID. For the text_file data source, this data is configured through the variables option. In this example, we define four variables that (like the system ID) are generated by using the value of one of the capturing groups and transforming it. The name of the variables defines the key in the resulting data dictionary. In this example, the variable name net:fqdn will cause the data to be made available under the key fqdn inside a dictionary that is stored under the key net in the top data dictionary.

We do not discuss all of the configuration options of the text_file data source here. Please refer to the API documentation for a list of all supported options and their meaning.

The second data source that we define in the example configuration is a yaml_target data source. The configuration for this data source looks fairly simple:

- name: yaml_target
  root_dir: /srv/vinegar/datatree

The only option that has to be specified is the root_dir which is where the data source finds the files which contain the data.

While the configuration for this data source is quite simple, it is still a very powerful type of data source. The files used by this data source are YAML files that are rendered as templates. The regular way of how this data source maps systems to their data is by matching the system IDs with patterns, but thanks to templating, it is possible to use more complex matching using data from the earlier data sources.

For example, we could use the IP address of a system (provided by the text_file) data source in a template expression to decide whether a certain piece of data is used for a system or not. In fact, we could even calculate data based on that data (e.g. calculate a broadcast address matching the IP address).

We will discuss examples of how this data source can be used in Adapting for your environment. For more information about the optional configuration options and the file format, please refer to the yaml_target API reference.

The third and last data source that we define in the example configuration is an sqlite data source.

- name: sqlite
  db_file: /var/lib/vinegar/system-state.db
  find_system_enabled: False
  key_prefix: state

The only mandatory option for this data source is the path to the db_file. The database file is created if it does not exist yet. Inside this file, the data source uses a single table for storing the data associated with each system.

Unlike the other two data sources, this data source does not use any caching. This means that even changes that happen in rapid succession and are thus not detectable by changes in the time stamp of the file (the primary way of how changes are detected for the other data sources), are reliably detected by this data source. The fact that SQLite implements safe transactions across multiple processes also makes it safe for concurrent updates.

We are going to use this data source to store a flag indicating whether a system should boot into the installer or boot locally. In Changing the netboot_enabled flag, we are going to write a very simple script that we use to set this flag inside the database. The same flag will be reset by an HTTP request handler that we are going to discuss later in this section.

HTTP request handlers

The HTTP request handlers are defined in the request_handlers sub-section of the http section. We define two request handlers.

The first request handler is a file request handler that serves files that are rendered as templates using the jinja template engine.

- name: file
  request_path: /templates/...
  lookup_key: ':system_id:'
  root_dir: /srv/vinegar/http/templates
  template: jinja

For this request handler, we specify a request path of /templates/.... The elipsis in this request path is used as a placeholder for the system ID. This is specified through the lookup_key option. What this means is that a request to /templates/myhost.example.com/myfile.txt will render the file /srv/vinegar/http/templates/myfile.txt as a template and provide it with the data from the data sources defined earlier for the system ID myhost.example.com.

The root_dir option specifies the directory where the template files are located. The template option specifies the name of the template engine that is used. If not specified, the files are not rendered as templates and instead they are served with their exact content. That mode is suitable when serving binary files that would be corrupted when being processed by a template engine. Like nearly everything in Vinegar, the template engine is pluggable, the jinja engine is simply the default engine provided by Vinegar, but you can easily add more template engines. For details please refer to Templates.

For a full list of configuration options, please refer to the API reference for the file request handler.

We define a second request handler of type sqlite_update.

- name: sqlite_update
  request_path: /reset-netboot-enabled
  db_file: /var/lib/vinegar/system-state.db
  action: delete_data
  key: netboot_enabled
  client_address_key: 'net:ipv4_addr'

This request handler has a very simple job: Whenever it receives a request to its request_path (with a system ID appended. like /reset-netboot-eabled/myhost.example.com), it deletes the netboot_enabled flag from the entry for myhost.example.com in the SQLite database that is stored in the db_file.

This behavior is specified by setting action to delete_data and key to netboot_enabled.

As a security measure, the request is only allowed if the HTTP client’s IP address matches the value of the key specified in the client_address_key option. For this check, the handler requests the system data for the specified system ID from the data sources and then looks for the specified key. In this example, the request handler looks for a value with the key ipv4_addr that is stored inside a dictionary that is stored under the net key in the top data structure. As you might remember, this is exactly the place where the text_file data source that we defined earlier stores a system’s IP address.

Effectively, this means that a system is only allowed to reset the netboot_enabled flag for itself. It cannot do this for a different system because the IP addresses will not match.

There are more configuration options. For example, a different action can be used to set (instead of delete) a value inside the database. Please refer to the API reference for the sqlite_update request handler for details.

Logging

The Vinegar server uses a logging system to provide you with information about what is happening and the stack traces of exceptions when something fails. This information can be invaluable when you try to figure out why something does not work as expected.

In the example configuration, we specify the path to a file with the logging configuration:

logging_config_file: /etc/vinegar/vinegar-server-logging.ini

This file is distributed with the Debian package and has the following content by default:

[loggers]
keys=root

[handlers]
keys=file

[formatters]
keys=default

[logger_root]
level=INFO
handlers=file

[handler_file]
class=handlers.WatchedFileHandler
level=NOTSET
args=('/var/log/vinegar/server.log',)
formatter=default

[formatter_default]
format=%(asctime)s [%(name)s] [%(levelname)s] %(message)s

This file uses the format specified in Pythons logging.config module. In the example configuration, we specify a single logger that logs everything at a level of INFO (this also includes WARNING, ERROR, and CRITICAL messages) to the file /var/log/vinegar/server.log. For obvious reasons, the user that runs the Vinegar server needs sufficient permissions to actually write to this file.

As an alternative to the logging_config_file option, one can specify the logging_level option. That option simply takes the name of a log level (e.g. INFO) as its value. If this option is used, messages of the specified level (and higher levels) are written to the standard output.

If neither of the two logging options is used, the server writes log messages with a level of INFO or higher to the standard output by default.

TFTP request handlers

The TFTP request handlers are defined in the request_handlers sub-section of the http section. The request handler configuration works in the same way as for HTTP request handlers. In the example configuration, we define three request handlers.

The first request handler is a file request handler that serves files that are rendered as templates using the jinja template engine.

- name: file
  request_path: /templates/...
  lookup_key: 'net:mac_addr'
  lookup_value_transform:
    - mac_address.normalize
  root_dir: /srv/vinegar/tftp/templates
  template: jinja
  data_source_error_action: warn
  lookup_no_result_action: continue

The request_path is set to /templates/... and the lookup_key is set to net:mac_addr. This means that a request to /templates/02:00:00:00:00:01/myfile.txt will try to find a system that has its net:mac_addr variable set to 02:00:00:00:00:01.

Due to the root_dir option being set to /srv/vinegar/tftp/templates, the request handler will then take the file /srv/vinegar/tftp/templates/myfile.txt and render it with the specified template engine (the jinja engine is this example).

As part of this rendering process, the data for the system identified by its MAC address is going to be made available. When looking for a system with the MAC address specified as part of the request path, the MAC address is normalized using the vinegar.transform.mac_address.normalize transformation. For example, this means that a request to /templates/02-00-00-00-00-0a will result in a lookup for the MAC address 02:00:00:00:00:0A.

You might rembember that we also normalized the MAC addresses in the configuration of the text_file data source. This means that regardless of which format is used in the text file and in the request path (e.g. upper or lower case, : or - as the separator) there will be a match as long as both essentially specifiy the same address.

In this example, the data_source_error_action is set to warn. This has the consequence that if one of the data sources raises an exception, this will not make the whole request fail. Instead, the template will be rendered without the system data available.

Setting lookup_no_result_action to continue, has a similar consequence: If no system can be found for the MAC address specified in the request path, the template is also rendered without system data.

This has the advantage that a problem with one of the data sources (or a system missing in the text file) will not lead to an error and the system will instead receive some default content defined in the template for that case. This can be useful if the template file is included by the boot loader configuration and would cause the boot process to stall if it could not be read from the server. For this case, the template file might provide some default content that causes the boot loader to boot from the local disks instead.

The second and third request handler also are instances of the file request handler.

- name: file
  request_path: /grub
  root_dir: /srv/vinegar/tftp/grub

- name: file
  request_path: /images
  root_dir: /srv/vinegar/tftp/images

In contrast to the first instance, these request handlers do not render files as templates. For this reason, they are suitable for serving binary files like the boot loader or kernel images.

Now that we have a configuration file for the server, we can continue with setting up the environment for the boot process.