494 lines
19 KiB
Markdown
494 lines
19 KiB
Markdown
# Configuration files and directories
|
|
|
|
- On a running system: `/etc/syndicate/`
|
|
- Source repository: [[synit]/packaging/packages/synit-config/files/etc/syndicate](https://git.syndicate-lang.org/synit/synit/src/branch/main/packaging/packages/synit-config/files/etc/syndicate)
|
|
|
|
The [root system bus](./system-bus.md#the-root-system-bus) is started with a `--config
|
|
/etc/syndicate/boot` command-line argument, which causes it to execute configuration scripts in
|
|
that directory. In turn, the `boot` directory contains instructions for loading configuration
|
|
from other locations on the filesystem.
|
|
|
|
This section will examine the layout of the configuration scripts and directories.
|
|
|
|
## The boot layer
|
|
|
|
The files in
|
|
[/etc/syndicate/boot](https://git.syndicate-lang.org/synit/synit/src/branch/main/packaging/packages/synit-config/files/etc/syndicate/boot)
|
|
define the boot layer.
|
|
|
|
### Console getty
|
|
|
|
The first thing the boot layer does, in
|
|
[001-console-getty.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/boot/001-console-getty.pr),
|
|
is start a `getty` on `/dev/console`:
|
|
|
|
```preserves
|
|
<require-service <daemon console-getty>>
|
|
<daemon console-getty "getty 0 /dev/console">
|
|
```
|
|
|
|
### Ad-hoc execution of programs
|
|
|
|
Next, in
|
|
[010-exec.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/boot/010-exec.pr),
|
|
it installs a handler that responds to messages requesting ad-hoc execution of programs:
|
|
|
|
```preserves
|
|
?? <exec ?argv ?restartPolicy> [
|
|
let ?id = timestamp
|
|
let ?facet = facet
|
|
let ?d = <temporary-exec $id $argv>
|
|
<run-service <daemon $d>>
|
|
<daemon $d { argv: $argv, readyOnStart: #f, restart: $restartPolicy }>
|
|
? <service-state <daemon $d> complete> [$facet ! stop]
|
|
? <service-state <daemon $d> failed> [$facet ! stop]
|
|
]
|
|
```
|
|
|
|
If the restart policy is not specified, it is defaulted to `on-error`:
|
|
|
|
```preserves
|
|
?? <exec ?argv> ! <exec $argv on-error>
|
|
```
|
|
|
|
### "Milestone" pseudo-services
|
|
|
|
Then, in
|
|
[010-milestone.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/boot/010-milestone.pr),
|
|
it defines how to respond to a request to run a "milestone" pseudo-service:
|
|
|
|
```preserves
|
|
? <run-service <milestone ?m>> [
|
|
<service-state <milestone $m> started>
|
|
<service-state <milestone $m> ready>
|
|
]
|
|
```
|
|
|
|
The definition is trivial—when requested, simply declare success—but useful in that a
|
|
"milestone" can be used as a proxy for a configuration state that other services can depend
|
|
upon.
|
|
|
|
Concretely, milestones are used in two places at present: a `core` milestone declares that the
|
|
core layer of services is ready, and a `network` milestone declares that initial network
|
|
configuration is complete.
|
|
|
|
### Synthesis of service state "up"
|
|
|
|
The [definition of ServiceState](./service.md#convey-the-current-state-of-a-service) includes
|
|
`ready`, for long-running service programs, and `complete`, for successful exit (exit status 0)
|
|
of "one-shot" service programs. In
|
|
[010-service-state-up.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/boot/010-service-state-up.pr),
|
|
we declare an alias `up` that is asserted in either of these cases:
|
|
|
|
```preserves
|
|
? <service-state ?x ready> <service-state $x up>
|
|
? <service-state ?x complete> <service-state $x up>
|
|
```
|
|
|
|
### Loading of "core" and "services" layers
|
|
|
|
The final tasks of the boot layer are to load the "core" and "service" layers, respectively.
|
|
|
|
Services declared in the "core" layer are automatically marked as dependencies of the
|
|
`<milestone core>` pseudo-service, and those declared in the "services" layer are automatically
|
|
marked as depending on `<milestone core>`.
|
|
|
|
```ditaa
|
|
+------+ +-----+ +-------+ +----+ +----+ +------------+ +----+
|
|
|docker| |modem| |network| |ntpd| |sshd| |userSettings| |wifi|
|
|
+---+--+ +--+--+ +---+---+ +--+-+ +--+-+ +------+-----+ +--+-+
|
|
| | | | | | |
|
|
+-------+--------+--------+------+----------+----------+
|
|
| depend on milestone core
|
|
services layer V
|
|
+----------------+
|
|
- - - - - - - - - - - -| milestone core |- - - - - - - - - - - - -
|
|
+--------+-------+
|
|
core layer | depended on by milestone core
|
|
+--------+--+-----------+
|
|
| | |
|
|
V V V
|
|
+-----+ +--------+ +-----------------+
|
|
|eudev| |hostname| |machine-dataspace|
|
|
+-----+ +--------+ +-----------------+
|
|
```
|
|
|
|
#### The core layer loader
|
|
|
|
For the core layer, in
|
|
[020-load-core-layer.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/boot/020-load-core-layer.pr),
|
|
a [configuration watcher](./builtin/config-watcher.md) is started, monitoring
|
|
`/etc/syndicate/core` for scripts defining services to place into the layer. Instead of passing
|
|
an unattenuated reference to `$config` to the configuration watcher, an [attenuation
|
|
expression](./scripting.md#attenuation-expressions) rewrites `require-service` assertions into
|
|
`require-core-service` assertions:
|
|
|
|
```preserves
|
|
let ?sys = <* $config [
|
|
<rewrite <require-service ?s> <require-core-service $s>>
|
|
<filter _>
|
|
]>
|
|
|
|
<require-service <config-watcher "/etc/syndicate/core" {
|
|
config: $sys
|
|
gatekeeper: $gatekeeper
|
|
log: $log
|
|
}>
|
|
```
|
|
|
|
Then, `require-core-service` is given meaning:
|
|
|
|
```preserves
|
|
? <require-core-service ?s> [
|
|
<depends-on <milestone core> <service-state $s up>>
|
|
<require-service $s>
|
|
]
|
|
```
|
|
|
|
#### The services layer loader
|
|
|
|
The services layer is treated similarly in
|
|
[030-load-services.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/boot/030-load-services.pr),
|
|
except `require-basic-service` takes the place of `require-core-service`, and the configuration
|
|
watcher isn't started until `<milestone core>` is ready. Any `require-basic-service` assertions
|
|
are given meaning as follows:
|
|
|
|
```preserves
|
|
? <require-basic-service ?s> [
|
|
<depends-on $s <service-state <milestone core> up>>
|
|
<require-service $s>
|
|
]
|
|
```
|
|
|
|
## The core layer: /etc/syndicate/core
|
|
|
|
The files in
|
|
[/etc/syndicate/core](https://git.syndicate-lang.org/synit/synit/src/branch/main/packaging/packages/synit-config/files/etc/syndicate/core)
|
|
define the core layer.
|
|
|
|
The
|
|
[configdirs.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/core/configdirs.pr)
|
|
script brings in scripts in `/run` and `/usr/local` analogues of the core config directory:
|
|
|
|
```preserves
|
|
<require-service <config-watcher "/run/etc/syndicate/core" $.>>
|
|
<require-service <config-watcher "/usr/local/etc/syndicate/core" $.>>
|
|
```
|
|
|
|
The
|
|
[eudev.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/core/eudev.pr)
|
|
script runs a `udevd` instance and, once it's ready, starts an initial scan:
|
|
|
|
```preserves
|
|
<require-service <daemon eudev>>
|
|
<daemon eudev ["/sbin/udevd", "--children-max=5"]>
|
|
|
|
<require-service <daemon eudev-initial-scan>>
|
|
<depends-on <daemon eudev-initial-scan> <service-state <daemon eudev> up>>
|
|
<daemon eudev-initial-scan <one-shot "
|
|
echo '' > /proc/sys/kernel/hotplug &&
|
|
udevadm trigger --type=subsystems --action=add &&
|
|
udevadm trigger --type=devices --action=add &&
|
|
udevadm settle --timeout=30
|
|
">>
|
|
```
|
|
|
|
The
|
|
[hostname.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/core/hostname.pr)
|
|
script simply sets the machine hostname:
|
|
|
|
```preserves
|
|
<require-service <daemon hostname>>
|
|
<daemon hostname <one-shot "hostname $(cat /etc/hostname)">>
|
|
```
|
|
|
|
<span id="machine-dataspace"></span>Finally, the
|
|
[machine-dataspace.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/core/machine-dataspace.pr)
|
|
script declares a fresh, empty dataspace, and asserts a reference to it in a "well-known
|
|
location" for use by other services later:
|
|
|
|
```preserves
|
|
let ?ds = dataspace
|
|
<machine-dataspace $ds>
|
|
```
|
|
|
|
## The services layer: /etc/syndicate/services
|
|
|
|
The files in
|
|
[/etc/syndicate/services](https://git.syndicate-lang.org/synit/synit/src/branch/main/packaging/packages/synit-config/files/etc/syndicate/services)
|
|
define the services layer.
|
|
|
|
The
|
|
[configdirs.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/configdirs.pr)
|
|
script brings in `/run` and `/usr/local` service definitions, analogous to the same file in the
|
|
core layer:
|
|
|
|
```preserves
|
|
<require-service <config-watcher "/run/etc/syndicate/services" $.>>
|
|
<require-service <config-watcher "/usr/local/etc/syndicate/services" $.>>
|
|
```
|
|
|
|
### Networking core
|
|
|
|
The
|
|
[network.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/network.pr)
|
|
defines the `<milestone network>` pseudo-service and starts a number of ancillary services for
|
|
generically monitoring and configuring system network interfaces.
|
|
|
|
First, `<daemon interface-monitor>` is a small Python program, required by `<milestone
|
|
network>`, using Netlink sockets to track changes to interfaces and interface state. It speaks
|
|
the [Syndicate network protocol](../protocol.md) on its standard input and output, and
|
|
publishes a [service object](./service.md#service-object) which expects a reference to the
|
|
[machine dataspace defined earlier](#machine-dataspace):
|
|
|
|
```preserves
|
|
<require-service <daemon interface-monitor>>
|
|
<depends-on <milestone network> <service-state <daemon interface-monitor> ready>>
|
|
<daemon interface-monitor {
|
|
argv: "/usr/lib/synit/interface-monitor"
|
|
protocol: application/syndicate
|
|
}>
|
|
? <machine-dataspace ?machine> [
|
|
? <service-object <daemon interface-monitor> ?cap> [
|
|
$cap {
|
|
machine: $machine
|
|
}
|
|
]
|
|
]
|
|
```
|
|
|
|
The `interface-monitor` publishes assertions describing interface presence and state to the
|
|
machine dataspace. The network.pr script responds to these assertions by requesting
|
|
configuration of an interface once it reaches a certain state. First, all interfaces are
|
|
enabled when they appear and disabled when they disappear:
|
|
|
|
```preserves
|
|
$machine ? <interface ?ifname _ _ _ _ _ _> [
|
|
$config [
|
|
! <exec ["ip" "link" "set" $ifname "up"]>
|
|
?- ! <exec ["ip" "link" "set" $ifname "down"] never>
|
|
]
|
|
]
|
|
```
|
|
|
|
Next, a DHCP client is invoked for any "normal" (wired-ethernet-like) interface in "up" state
|
|
with a carrier:
|
|
|
|
```preserves
|
|
$machine ? <interface ?ifname _ normal up up carrier _> [
|
|
$config <configure-interface $ifname <dhcp>>
|
|
]
|
|
$machine ? <interface ?ifname _ normal up unknown carrier _> [
|
|
$config <configure-interface $ifname <dhcp>>
|
|
]
|
|
|
|
$config ? <configure-interface ?ifname <dhcp>> [
|
|
<require-service <daemon <udhcpc $ifname>>>
|
|
]
|
|
$config ? <run-service <daemon <udhcpc ?ifname>>> [
|
|
<daemon <udhcpc $ifname> ["udhcpc" "-i" $ifname "-fR" "-s" "/usr/lib/synit/udhcpc.script"]>
|
|
]
|
|
```
|
|
|
|
We use a custom `udhcpc` script which modifies the default script to give mobile-data devices a
|
|
sensible routing metric.
|
|
|
|
The final pieces of network.pr are static configuration of the loopback interface:
|
|
|
|
```preserves
|
|
<configure-interface "lo" <static "127.0.0.1/8">>
|
|
? <configure-interface ?ifname <static ?ipaddr>> [
|
|
! <exec ["ip" "address" "add" "dev" $ifname $ipaddr]>
|
|
?- ! <exec ["ip" "address" "del" "dev" $ifname $ipaddr] never>
|
|
]
|
|
```
|
|
|
|
and conditional publication of a `default-route` record, allowing services to detect when the
|
|
internet is (nominally) available:
|
|
|
|
```preserves
|
|
$machine ? <route ?addressFamily default _ _ _ _> [
|
|
$config <default-route $addressFamily>
|
|
]
|
|
```
|
|
|
|
### Wifi & Mobile Data
|
|
|
|
Building atop the networking core,
|
|
[wifi.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/wifi.pr)
|
|
and
|
|
[modem.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/modem.pr)
|
|
provide the necessary support for wireless LAN and mobile data interfaces, respectively.
|
|
|
|
When `interface-monitor` detects presence of a wireless LAN interface, wifi.pr reacts by
|
|
starting `wpa_supplicant` for the interface along with a small Python program, `wifi-daemon`,
|
|
that acts as a client to `wpa_supplicant`, adding and removing networks and network
|
|
configuration according to `selected-wifi-network` assertions in the machine dataspace.
|
|
|
|
```preserves
|
|
$machine ? <interface ?ifname _ wireless _ _ _ _> [
|
|
$config [
|
|
<require-service <daemon <wpa_supplicant $ifname>>>
|
|
<depends-on
|
|
<daemon <wifi-daemon $ifname>>
|
|
<service-state <daemon <wpa_supplicant $ifname>> up>>
|
|
<require-service <daemon <wifi-daemon $ifname>>>
|
|
]
|
|
]
|
|
|
|
$config ? <run-service <daemon <wifi-daemon ?ifname>>> [
|
|
<daemon <wifi-daemon $ifname> {
|
|
argv: "/usr/lib/synit/wifi-daemon"
|
|
protocol: application/syndicate
|
|
}>
|
|
? <service-object <daemon <wifi-daemon $ifname>> ?cap> [
|
|
$cap {
|
|
machine: $machine
|
|
ifname: $ifname
|
|
}
|
|
]
|
|
]
|
|
|
|
$config ? <run-service <daemon <wpa_supplicant ?ifname>>> [
|
|
<daemon <wpa_supplicant $ifname> [
|
|
"wpa_supplicant" "-Dnl80211,wext" "-C/run/wpa_supplicant" "-i" $ifname
|
|
]>
|
|
]
|
|
```
|
|
|
|
The other tasks performed by wifi.pr are to request DHCP configuration for available wifi interfaces:
|
|
|
|
```preserves
|
|
$machine ? <interface ?ifname _ wireless up up carrier _> [
|
|
$config <configure-interface $ifname <dhcp>>
|
|
]
|
|
```
|
|
|
|
and to relay `selected-wifi-network` records from [user settings](#user-settings) (described
|
|
below) into the machine dataspace, for `wifi-daemon` instances to pick up:
|
|
|
|
```preserves
|
|
$config ? <user-setting <?s <selected-wifi-network _ _ _>>> [ $machine += $s ]
|
|
```
|
|
|
|
Turning to modem.pr, which is currently hard-coded for Pinephone devices, we see two main
|
|
blocks of config. The simplest just starts the `eg25-manager` daemon for controlling the
|
|
Pinephone's Quectel modem, along with a simple monitoring script for restarting it if and when
|
|
`/dev/EG25.AT` disappears:
|
|
|
|
```preserves
|
|
<daemon eg25-manager "eg25-manager">
|
|
<depends-on <daemon eg25-manager> <service-state <daemon eg25-manager-monitor> up>>
|
|
<daemon eg25-manager-monitor "/usr/lib/synit/eg25-manager-monitor">
|
|
```
|
|
|
|
The remainder of modem.pr handles cellular data, configured via the
|
|
[qmicli](https://www.freedesktop.org/wiki/Software/libqmi/) program.
|
|
|
|
```preserves
|
|
<require-service <qmi-wwan "/dev/cdc-wdm0">>
|
|
<depends-on <qmi-wwan "/dev/cdc-wdm0"> <service-state <daemon eg25-manager> up>>
|
|
```
|
|
|
|
When the [user settings](#user-setting) `mobile-data-enabled` and `mobile-data-apn` are both
|
|
present, it responds to `qmi-wwan` service requests by invoking `qmi-wwan-manager`, a small
|
|
shell script, for each particular device and APN combination:
|
|
|
|
```preserves
|
|
? <user-setting <mobile-data-enabled>> [
|
|
? <user-setting <mobile-data-apn ?apn>> [
|
|
? <run-service <qmi-wwan ?dev>> [
|
|
<require-service <daemon <qmi-wwan-manager $dev $apn>>>
|
|
]
|
|
]
|
|
]
|
|
? <run-service <daemon <qmi-wwan-manager ?dev ?apn>>> [
|
|
<daemon <qmi-wwan-manager $dev $apn> ["/usr/lib/synit/qmi-wwan-manager" $dev $apn]>
|
|
]
|
|
```
|
|
|
|
(Because qmicli is sometimes not well behaved, there is also [code in
|
|
modem.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/modem.pr#L23-L44)
|
|
for restarting it in certain circumstances when it gets into a state where it reports errors
|
|
but does not terminate.)
|
|
|
|
### Simple daemons
|
|
|
|
A few simple daemons are also started as part of the services layer.
|
|
|
|
The
|
|
[docker.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/docker.pr)
|
|
script starts the docker daemon, but only once the network configuration is available:
|
|
|
|
```preserves
|
|
<require-service <daemon docker>>
|
|
<depends-on <daemon docker> <service-state <milestone network> up>>
|
|
<daemon docker "/usr/bin/dockerd --experimental 2>/var/log/docker.log">
|
|
```
|
|
|
|
The
|
|
[ntpd.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/ntpd.pr)
|
|
script starts an NTP daemon, but only when an IPv4 default route exists:
|
|
|
|
```preserves
|
|
<require-service <daemon ntpd>>
|
|
<depends-on <daemon ntpd> <default-route ipv4>>
|
|
<daemon ntpd "ntpd -d -n -p pool.ntp.org">
|
|
```
|
|
|
|
Finally, the
|
|
[sshd.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/sshd.pr)
|
|
script starts the OpenSSH server daemon after ensuring both that the network is available and
|
|
that SSH host keys exist:
|
|
|
|
```preserves
|
|
<require-service <daemon sshd>>
|
|
<depends-on <daemon sshd> <service-state <milestone network> up>>
|
|
<depends-on <daemon sshd> <service-state <daemon ssh-host-keys> complete>>
|
|
<daemon sshd "/usr/sbin/sshd -D">
|
|
<daemon ssh-host-keys <one-shot "ssh-keygen -A">>
|
|
```
|
|
|
|
### User settings
|
|
|
|
A special folder, `/etc/syndicate/user-settings`, acts as a persistent database of assertions
|
|
relating to user settings, including such things as wifi network credentials and preferences,
|
|
mobile data preferences, and so on. The
|
|
[userSettings.pr](https://git.syndicate-lang.org/synit/synit/src/commit/a43247fc96e906fbfdeb6e9a6e6d6e9a2f35f9e8/packaging/packages/synit-config/files/etc/syndicate/services/userSettings.pr)
|
|
script sets up the programs responsible for managing the folder.
|
|
|
|
The contents of the folder itself are managed by a small Python program,
|
|
`user-settings-daemon`, which responds to requests arriving via the `$config` dataspace by
|
|
adding and removing files containing assertions in `/etc/syndicate/user-settings`.
|
|
|
|
```preserves
|
|
let ?settingsDir = "/etc/syndicate/user-settings"
|
|
<require-service <daemon user-settings-daemon>>
|
|
<daemon user-settings-daemon {
|
|
argv: "/usr/lib/synit/user-settings-daemon"
|
|
protocol: application/syndicate
|
|
}>
|
|
? <service-object <daemon user-settings-daemon> ?cap> [
|
|
$cap {
|
|
config: $config
|
|
settingsDir: $settingsDir
|
|
}
|
|
]
|
|
```
|
|
|
|
Each such file is named after the SHA-1 digest of the [canonical
|
|
form](../guide/preserves.md#canonical-form) of the assertion it contains. For example,
|
|
`/etc/syndicate/user-settings/8814297f352be4ebbff19137770e619b2ebc5e91.pr` contains
|
|
`<mobile-data-enabled>`.
|
|
|
|
The files in `/etc/syndicate/user-settings` are brought into the main config dataspace by way
|
|
of a rewriting configuration watcher:
|
|
|
|
```preserves
|
|
let ?settings = <* $config [ <rewrite ?item <user-setting $item>> ]>
|
|
<require-service <config-watcher $settingsDir { config: $settings }>>
|
|
```
|
|
|
|
Every assertion from `/etc/syndicate/user-settings` is wrapped in a `<user-setting ...>` record
|
|
before being placed into the main `$config` dataspace.
|