mirror of
https://github.com/systemd/systemd.git
synced 2026-08-10 17:14:31 +00:00
sysupdate: add config file with metadata for sysupdate components (#42651)
This carries some metadata for components. It's supposed to grow a bit, and include a way to enable/disable transfers, and to condition them.
This commit is contained in:
10
TODO.md
10
TODO.md
@@ -255,15 +255,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
- sysupdate: go through all components, and update them all, one by one.
|
||||
|
||||
- sysupdate: add concept for enabling/disabling specific components explicitly,
|
||||
just like features.
|
||||
|
||||
- sysupdate: add conditions to transfer files, copying what we have for unit
|
||||
files and .network files
|
||||
|
||||
- pid1,sysupdate,network: add support for a new "tags" condition, that checks
|
||||
all of the above.
|
||||
|
||||
- pcrextend: we probably should measure /etc/machine-info during boot somehow
|
||||
|
||||
- pcrextend: we should measure something when we enter developer mode, by some
|
||||
@@ -2657,7 +2648,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
- download multiple arbitrary patterns from same source
|
||||
- SHA256SUMS format with bearer tokens for each resource to download
|
||||
- decrypt SHA256SUMS with key from tpm
|
||||
- clean up stuff on disk that disappears from SHA256SUMS
|
||||
- turn http backend stuff int plugin via varlink
|
||||
- for each transfer support looking at multiple sources,
|
||||
pick source with newest entry. If multiple sources have the same entry, use
|
||||
|
||||
@@ -1277,7 +1277,8 @@ manpages = [
|
||||
''],
|
||||
['systemd-sysupdate',
|
||||
'8',
|
||||
['systemd-sysupdate-reboot.service',
|
||||
['systemd-sysupdate-auto-enable.service',
|
||||
'systemd-sysupdate-reboot.service',
|
||||
'systemd-sysupdate-reboot.timer',
|
||||
'systemd-sysupdate-update.service',
|
||||
'systemd-sysupdate-update.timer',
|
||||
@@ -1378,6 +1379,7 @@ manpages = [
|
||||
['systemd.timer', '5', [], ''],
|
||||
['systemd.unit', '5', [], ''],
|
||||
['systemd.v', '7', [], ''],
|
||||
['sysupdate.components', '5', [], 'ENABLE_SYSUPDATE'],
|
||||
['sysupdate.d', '5', [], 'ENABLE_SYSUPDATE'],
|
||||
['sysupdate.features', '5', [], 'ENABLE_SYSUPDATE'],
|
||||
['sysusers.d', '5', [], 'ENABLE_SYSUSERS'],
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<refname>systemd-sysupdate-update.timer</refname>
|
||||
<refname>systemd-sysupdate-reboot.service</refname>
|
||||
<refname>systemd-sysupdate-reboot.timer</refname>
|
||||
<refname>systemd-sysupdate-auto-enable.service</refname>
|
||||
|
||||
<!-- Compatibility symlinks for old names of systemd-sysupdate-update.{service,timer} -->
|
||||
<refname>systemd-sysupdate.service</refname>
|
||||
@@ -87,6 +88,12 @@
|
||||
appropriate time (i.e. typically at night). The two sets of service/timer units may be enabled
|
||||
separately.</para>
|
||||
|
||||
<para>The <filename>systemd-sysupdate-auto-enable.service</filename> system service automatically enables all
|
||||
suggested components and all features of the enabled components before each update. It is ordered before and
|
||||
pulled in by <filename>systemd-sysupdate-update.service</filename>, and is not enabled by default. Enable it
|
||||
if newly suggested components and features shall be picked up automatically as they become available, rather
|
||||
than requiring manual <command>enable-component</command>/<command>enable-feature</command> invocations.</para>
|
||||
|
||||
<para>For details about transfer files and examples see
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
</refsect1>
|
||||
@@ -121,6 +128,21 @@
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>enable-feature</option> <replaceable>FEATURE</replaceable>…</term>
|
||||
<term><option>disable-feature</option> <replaceable>FEATURE</replaceable>…</term>
|
||||
|
||||
<listitem><para>Enables or disables the specified optional features. This writes an
|
||||
<varname>Enabled=</varname> setting into a drop-in next to the feature definition, see
|
||||
<citerefentry><refentrytitle>sysupdate.features</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details. The features may be specified either as arguments or selected via the
|
||||
<option>--feature-all</option> or <option>--feature-suggested</option> switches. Note that this only
|
||||
changes the configuration: to actually download the newly enabled transfers, or remove the disabled
|
||||
ones, invoke <option>update</option> or <option>vacuum</option> afterwards.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>check-new</option></term>
|
||||
|
||||
@@ -209,6 +231,18 @@
|
||||
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>enable-component</option> <replaceable>COMPONENT</replaceable>…</term>
|
||||
<term><option>disable-component</option> <replaceable>COMPONENT</replaceable>…</term>
|
||||
|
||||
<listitem><para>Enables or disables the specified components. This writes an
|
||||
<varname>Enabled=</varname> setting into a drop-in next to the component definition. The components
|
||||
may be specified either as arguments or selected via the <option>--component=</option>,
|
||||
<option>--component-all</option> or <option>--component-suggested</option> switches.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>cleanup</option></term>
|
||||
|
||||
@@ -280,15 +314,57 @@
|
||||
<term><option>--component-all</option></term>
|
||||
<term><option>-A</option></term>
|
||||
|
||||
<listitem><para>Instead of operating on a single component, operate on all known components (as well as
|
||||
the default, component-less installation). This is currently only supported for the
|
||||
<command>cleanup</command> command; all other commands will fail if this switch is used.</para>
|
||||
<listitem><para>Instead of operating on a single component, operate on all known components (as well
|
||||
as the default, component-less installation). This is supported for the <command>update</command>,
|
||||
<command>acquire</command>, <command>cleanup</command>, <command>enable-feature</command>,
|
||||
<command>disable-feature</command>, <command>enable-component</command> and
|
||||
<command>disable-component</command> commands; all other commands will fail if this switch is
|
||||
used.</para>
|
||||
|
||||
<para>This option may not be combined with <option>--component=</option>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--component-suggested</option></term>
|
||||
<term><option>-S</option></term>
|
||||
|
||||
<listitem><para>Similar to <option>--component-all</option>, but operates only on the components that
|
||||
are currently <emphasis>suggested</emphasis> for this system, as determined by the
|
||||
<varname>Suggest=</varname> and <varname>SuggestOn…=</varname> settings in the component definition
|
||||
files. This is supported for the <command>enable-component</command> and
|
||||
<command>disable-component</command> commands.</para>
|
||||
|
||||
<para>This option may not be combined with <option>--component=</option>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--feature-all</option></term>
|
||||
<term><option>-a</option></term>
|
||||
|
||||
<listitem><para>Instead of operating on features specified on the command line, operate on all
|
||||
optional features known to the selected component(s). This is supported for the
|
||||
<command>enable-feature</command> and <command>disable-feature</command> commands.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--feature-suggested</option></term>
|
||||
<term><option>-s</option></term>
|
||||
|
||||
<listitem><para>Similar to <option>--feature-all</option>, but operates only on the optional features
|
||||
that are currently <emphasis>suggested</emphasis> for this system, as determined by the
|
||||
<varname>Suggest=</varname> and <varname>SuggestOn…=</varname> settings in the feature definition
|
||||
files. This is supported for the <command>enable-feature</command> and
|
||||
<command>disable-feature</command> commands.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--definitions=</option></term>
|
||||
|
||||
@@ -416,6 +492,8 @@
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.features</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.components</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-sysupdated.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
|
||||
292
man/sysupdate.components.xml
Normal file
292
man/sysupdate.components.xml
Normal file
@@ -0,0 +1,292 @@
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="sysupdate.components" conditional='ENABLE_SYSUPDATE'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>sysupdate.components</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>sysupdate.components</refentrytitle>
|
||||
<manvolnum>5</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>sysupdate.components</refname>
|
||||
<refpurpose>Definition Files for Update Components</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><simplelist>
|
||||
<member><filename>/etc/sysupdate.<replaceable>component</replaceable>.component</filename></member>
|
||||
<member><filename>/run/sysupdate.<replaceable>component</replaceable>.component</filename></member>
|
||||
<member><filename>/usr/local/lib/sysupdate.<replaceable>component</replaceable>.component</filename></member>
|
||||
<member><filename>/usr/lib/sysupdate.<replaceable>component</replaceable>.component</filename></member>
|
||||
</simplelist></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para>A "Component" is a set of
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
transfer definitions that
|
||||
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
updates independently from the rest of the OS.
|
||||
Each component is a self-contained installation with its own version, and its own set of transfer files,
|
||||
stored in a directory named after the component:
|
||||
<filename>sysupdate.<replaceable>component</replaceable>.d/*.transfer</filename> (as opposed to the
|
||||
default, component-less <filename>sysupdate.d/*.transfer</filename>).
|
||||
Components are selected via the <option>--component=</option>, <option>--component-all</option> or
|
||||
<option>--component-suggested</option> switches of
|
||||
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>, and
|
||||
enumerated by its <command>components</command> command.</para>
|
||||
|
||||
<para>Because components are updated separately, they are the right tool for OS resources that shall be
|
||||
versioned and updated independently from the base OS — for example a container image or a portable service
|
||||
image that follows its own release cycle. This is the primary difference between a component and an
|
||||
<citerefentry><refentrytitle>sysupdate.features</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
Optional Feature: the transfers belonging to a feature are updated in lock-step with the target they are
|
||||
part of, while a component is a wholly separate installation. Do not use a component for resources that
|
||||
shall always be updated synchronously with something else; use a feature (or simply additional transfer
|
||||
files in the same directory) for those cases.</para>
|
||||
|
||||
<para>A component does not require a <filename>*.component</filename> file: as soon as a matching
|
||||
<filename>sysupdate.<replaceable>component</replaceable>.d/</filename> directory with transfer definitions
|
||||
exists, the component is available for updating. The <filename>*.component</filename> file described here
|
||||
is optional, and carries metadata about the component (such as a human-readable description and a
|
||||
documentation URL) as well as its enablement state.</para>
|
||||
|
||||
<para>When a component is disabled (via <varname>Enabled=false</varname>, see below), operations that
|
||||
would download or install new versions — most importantly <command>update</command> — refuse to act on
|
||||
it. Other operations, such as <command>vacuum</command> and <command>cleanup</command>, continue to
|
||||
consider the component's transfers so that previously installed instances remain managed and can be
|
||||
cleaned up. Components are enabled or disabled with the <command>enable-component</command> and
|
||||
<command>disable-component</command> commands of
|
||||
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
which write an <varname>Enabled=</varname> setting into a drop-in next to the component definition.</para>
|
||||
|
||||
<para>Component definitions support drop-in files, which are most commonly used to override the
|
||||
<varname>Enabled=</varname> setting. Drop-ins are read from
|
||||
<filename>sysupdate.<replaceable>component</replaceable>.component.d/*.conf</filename> in the same set of
|
||||
directories as the main file. They can also be masked to hide the component definition entirely.</para>
|
||||
|
||||
<para>Each <filename>*.component</filename> file contains one section: [Component].</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>[Component] Section Options</title>
|
||||
|
||||
<para>This section defines general properties of this component.</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>Description=</varname></term>
|
||||
|
||||
<listitem><para>A short human-readable description of this component.
|
||||
This may be used as a label for this component, so the string should meaningfully identify the
|
||||
component among the components available on the system.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Documentation=</varname></term>
|
||||
|
||||
<listitem><para>A user-presentable URL to documentation about this component.
|
||||
This setting supports specifier expansion; see below for details on supported specifiers.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Enabled=</varname></term>
|
||||
|
||||
<listitem><para>Whether or not this component is enabled. If unspecified, the component is enabled
|
||||
by default. When disabled, <command>systemd-sysupdate</command> refuses to download or install new
|
||||
versions of this component, but continues to consider its transfers while vacuuming and in other
|
||||
situations where it needs to determine ownership over previously downloaded system resources.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Suggest=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If true, this component is suggested for enablement, if
|
||||
false it is not. This does not enable the component on its own, but is a hint surfaced by the update
|
||||
tools (for example via the <option>--component-suggested</option> switch of
|
||||
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
|
||||
so that the system administrator or higher-level tooling may act on it.</para>
|
||||
|
||||
<para>If this setting is not specified, the <varname>SuggestOn…=</varname> conditions described below
|
||||
are evaluated instead to determine whether the component is suggested. If neither this setting nor any
|
||||
<varname>SuggestOn…=</varname> condition is specified, the component is not suggested.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SuggestOnArchitecture=</varname></term>
|
||||
<term><varname>SuggestOnFirmware=</varname></term>
|
||||
<term><varname>SuggestOnVirtualization=</varname></term>
|
||||
<term><varname>SuggestOnHost=</varname></term>
|
||||
<term><varname>SuggestOnFraction=</varname></term>
|
||||
<term><varname>SuggestOnKernelCommandLine=</varname></term>
|
||||
<term><varname>SuggestOnVersion=</varname></term>
|
||||
<term><varname>SuggestOnCredential=</varname></term>
|
||||
<term><varname>SuggestOnSecurity=</varname></term>
|
||||
<term><varname>SuggestOnOSRelease=</varname></term>
|
||||
<term><varname>SuggestOnMachineTag=</varname></term>
|
||||
|
||||
<listitem><para>Suggest this component for enablement depending on system properties. These settings
|
||||
take the same arguments and implement the same semantics — including the leading <literal>!</literal>
|
||||
for negation — as the identically-named
|
||||
<varname>ConditionArchitecture=</varname>, <varname>ConditionFirmware=</varname>,
|
||||
<varname>ConditionVirtualization=</varname>, <varname>ConditionHost=</varname>,
|
||||
<varname>ConditionFraction=</varname>, <varname>ConditionKernelCommandLine=</varname>,
|
||||
<varname>ConditionVersion=</varname>, <varname>ConditionCredential=</varname>,
|
||||
<varname>ConditionSecurity=</varname>, <varname>ConditionOSRelease=</varname> and
|
||||
<varname>ConditionMachineTag=</varname> settings for unit files, which are documented in
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
||||
The component is suggested if all specified conditions apply. As with the <varname>Condition…=</varname>
|
||||
settings, assigning an empty string to one of these resets the list.</para>
|
||||
|
||||
<para>These conditions are only evaluated if <varname>Suggest=</varname> is not specified.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Specifiers</title>
|
||||
|
||||
<para>Specifiers may be used in the <varname>Documentation=</varname> setting. The following expansions
|
||||
are understood:</para>
|
||||
|
||||
<table class='specifiers'>
|
||||
<title>Specifiers available</title>
|
||||
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
|
||||
<colspec colname="spec" />
|
||||
<colspec colname="mean" />
|
||||
<colspec colname="detail" />
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Specifier</entry>
|
||||
<entry>Meaning</entry>
|
||||
<entry>Details</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="a"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="A"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="b"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="B"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="H"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="l"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="m"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="M"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="o"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="v"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="w"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="W"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="T"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="V"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="percent"/>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>A Separately Updated Container Image</title>
|
||||
|
||||
<para>We'll use the hypothetical "foobarOS" described in
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> as our
|
||||
example base OS.
|
||||
foobarOS ships a container image that many, but not all, deployments want to run. The image has its own
|
||||
release cadence and version number, entirely separate from the base OS, so it is a poor fit for an
|
||||
Optional Feature (which would be version-locked to the OS). Instead, let's model it as a component named
|
||||
<literal>webapp</literal>.</para>
|
||||
|
||||
<para>First, the component definition, carrying its metadata and marking it as disabled by default:</para>
|
||||
|
||||
<para><programlisting># /usr/lib/sysupdate.webapp.component
|
||||
[Component]
|
||||
Description=WebApp Container Image
|
||||
Documentation=https://docs.example.com/foobarOS/webapp
|
||||
Enabled=false
|
||||
</programlisting></para>
|
||||
|
||||
<para>Next, the transfer definition for the component, placed in the component's own directory
|
||||
<filename>sysupdate.webapp.d/</filename>:</para>
|
||||
|
||||
<para><programlisting># /usr/lib/sysupdate.webapp.d/50-webapp.transfer
|
||||
[Source]
|
||||
Type=url-file
|
||||
Path=https://download.example.com/
|
||||
MatchPattern=webapp_@v.raw.xz
|
||||
|
||||
[Target]
|
||||
Type=regular-file
|
||||
Path=/var/lib/machines
|
||||
MatchPattern=webapp_@v.raw
|
||||
Mode=0444
|
||||
InstancesMax=2
|
||||
</programlisting></para>
|
||||
|
||||
<para>With these two files, updates for the <literal>webapp</literal> component can be checked and
|
||||
downloaded independently of the base OS, for example with
|
||||
<command>systemd-sysupdate --component=webapp update</command>. Because
|
||||
<varname>Enabled=false</varname> was set, the administrator must first opt in with
|
||||
<command>systemd-sysupdate enable-component webapp</command>, which writes the following drop-in:</para>
|
||||
|
||||
<para><programlisting># /etc/sysupdate.webapp.component.d/50-systemd-sysupdate-enabled.conf
|
||||
# Generated via 'systemd-sysupdate enable-component'
|
||||
|
||||
[Component]
|
||||
Enabled=true
|
||||
</programlisting></para>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>A Component Suggested Only on Bare Metal</title>
|
||||
|
||||
<para>Suppose foobarOS ships a component with firmware and microcode updates that is only relevant when
|
||||
running on physical hardware, not inside a virtual machine. We can use <varname>SuggestOn…=</varname> to
|
||||
hint that the component should be enabled in that case, without enabling it automatically:</para>
|
||||
|
||||
<para><programlisting># /usr/lib/sysupdate.firmware.component
|
||||
[Component]
|
||||
Description=Firmware and Microcode Updates
|
||||
Documentation=https://docs.example.com/foobarOS/firmware
|
||||
Enabled=false
|
||||
SuggestOnVirtualization=!vm
|
||||
</programlisting></para>
|
||||
|
||||
<para>Higher-level tooling can now query the suggested components (via
|
||||
<command>systemd-sysupdate --component-suggested …</command>) and enable them as appropriate. On bare
|
||||
metal the <literal>firmware</literal> component is suggested; inside a VM it is not.</para>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.features</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -1068,6 +1068,8 @@ CurrentSymlink=myContainer</programlisting></para>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.features</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.components</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
@@ -381,6 +381,7 @@ InstancesMax=2
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sysupdate.components</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ foreach item : [
|
||||
['systemd-run', ''],
|
||||
['systemd-sysext', 'ENABLE_SYSEXT'],
|
||||
['systemd-sysinstall', 'ENABLE_SYSINSTALL'],
|
||||
['systemd-sysupdate', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-vmspawn', 'ENABLE_VMSPAWN'],
|
||||
['systemd-vpick', ''],
|
||||
['timedatectl', 'ENABLE_TIMEDATED'],
|
||||
|
||||
138
shell-completion/bash/systemd-sysupdate
Normal file
138
shell-completion/bash/systemd-sysupdate
Normal file
@@ -0,0 +1,138 @@
|
||||
# shellcheck shell=bash
|
||||
# systemd-sysupdate(8) completion -*- shell-script -*-
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# systemd is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with systemd; If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
__contains_word() {
|
||||
local w word=$1; shift
|
||||
for w in "$@"; do
|
||||
[[ $w = "$word" ]] && return
|
||||
done
|
||||
}
|
||||
|
||||
# List the components or features known to the installation. The argument is both
|
||||
# the verb to invoke ('components' or 'features') and the JSON array key to extract.
|
||||
__systemd_sysupdate_list() {
|
||||
systemd-sysupdate --no-pager --json=short "$1" 2>/dev/null |
|
||||
sed -n "s/.*\"$1\":\[\([^]]*\)\].*/\1/p" | tr ',' '\n' | tr -d '"'
|
||||
}
|
||||
|
||||
_systemd-sysupdate() {
|
||||
local i verb comps
|
||||
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
|
||||
local -A OPTS=(
|
||||
[STANDALONE]='-h --help --version
|
||||
--no-pager
|
||||
--no-legend
|
||||
-A --component-all
|
||||
-S --component-suggested
|
||||
-a --feature-all
|
||||
-s --feature-suggested
|
||||
--reboot
|
||||
--offline'
|
||||
[ARG]='-C --component
|
||||
--definitions
|
||||
--root
|
||||
--image
|
||||
--image-policy
|
||||
--transfer-source
|
||||
-m --instances-max
|
||||
--sync
|
||||
--verify
|
||||
--cleanup
|
||||
--json'
|
||||
)
|
||||
|
||||
local -A VERBS=(
|
||||
[STANDALONE]='check-new
|
||||
vacuum
|
||||
cleanup
|
||||
pending
|
||||
reboot
|
||||
components'
|
||||
[VERSION]='list
|
||||
update
|
||||
acquire'
|
||||
[FEATURE]='features
|
||||
enable-feature
|
||||
disable-feature'
|
||||
[COMPONENT]='enable-component
|
||||
disable-component'
|
||||
)
|
||||
|
||||
_init_completion || return
|
||||
|
||||
if __contains_word "$prev" ${OPTS[ARG]}; then
|
||||
case $prev in
|
||||
-C|--component)
|
||||
comps=$( __systemd_sysupdate_list components )
|
||||
;;
|
||||
--definitions|--root|--transfer-source)
|
||||
comps=$(compgen -A directory -- "$cur" )
|
||||
compopt -o dirnames
|
||||
;;
|
||||
--image)
|
||||
comps=$(compgen -A file -- "$cur" )
|
||||
compopt -o filenames
|
||||
;;
|
||||
--image-policy)
|
||||
comps=''
|
||||
;;
|
||||
--instances-max|-m)
|
||||
comps=''
|
||||
;;
|
||||
--sync|--verify|--cleanup)
|
||||
comps='no yes'
|
||||
;;
|
||||
--json)
|
||||
comps='pretty short off'
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$cur" = -* ]]; then
|
||||
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
for ((i=0; i < COMP_CWORD; i++)); do
|
||||
if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
|
||||
! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
|
||||
verb=${COMP_WORDS[i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z ${verb-} ]]; then
|
||||
comps=${VERBS[*]}
|
||||
elif __contains_word "$verb" ${VERBS[FEATURE]}; then
|
||||
comps=$( __systemd_sysupdate_list features )
|
||||
elif __contains_word "$verb" ${VERBS[COMPONENT]}; then
|
||||
comps=$( __systemd_sysupdate_list components )
|
||||
else
|
||||
# STANDALONE verbs take no argument, VERSION verbs take a remote version
|
||||
# that cannot be enumerated locally.
|
||||
comps=''
|
||||
fi
|
||||
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _systemd-sysupdate systemd-sysupdate
|
||||
92
shell-completion/zsh/_systemd-sysupdate
Normal file
92
shell-completion/zsh/_systemd-sysupdate
Normal file
@@ -0,0 +1,92 @@
|
||||
#compdef systemd-sysupdate
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
local context state state_descr line
|
||||
typeset -A opt_args
|
||||
local ret=1
|
||||
|
||||
# Pick the component/feature names out of the --json=short output of the
|
||||
# 'components'/'features' verbs. (The tabular output is unsuitable here, since
|
||||
# it prefixes each name with enabled/suggested checkmark columns.)
|
||||
_systemd-sysupdate_components() {
|
||||
local expl
|
||||
local -a components=( ${(@)${(@)${(s:,:)${${"$(_call_program -l components systemd-sysupdate --no-pager --json=short components 2>/dev/null)"#*\"components\":\[}%%\]*}}#\"}%\"} )
|
||||
_wanted components expl "component" compadd "$@" -a - components
|
||||
}
|
||||
|
||||
_systemd-sysupdate_features() {
|
||||
local expl
|
||||
local -a features=( ${(@)${(@)${(s:,:)${${"$(_call_program -l features systemd-sysupdate --no-pager --json=short features 2>/dev/null)"#*\"features\":\[}%%\]*}}#\"}%\"} )
|
||||
_wanted features expl "feature" compadd "$@" -a - features
|
||||
}
|
||||
|
||||
local -a opts=(
|
||||
{-h,--help}'[Show help message and exit]'
|
||||
'--version[Show package version and exit]'
|
||||
'--no-pager[Do not pipe output into a pager]'
|
||||
'--no-legend[Do not show the headers and footers]'
|
||||
'--json=[Show output as JSON]:mode:(pretty short off)'
|
||||
'(-A --component-all -S --component-suggested)'{-C+,--component=}'[Select component to update]:component:_systemd-sysupdate_components'
|
||||
'(-C --component -S --component-suggested)'{-A,--component-all}'[Select all components]'
|
||||
'(-C --component -A --component-all)'{-S,--component-suggested}'[Select all suggested components]'
|
||||
'(-s --feature-suggested)'{-a,--feature-all}'[Select all features]'
|
||||
'(-a --feature-all)'{-s,--feature-suggested}'[Select all suggested features]'
|
||||
'--definitions=[Find transfer definitions in specified directory]:directory:_directories'
|
||||
'--root=[Operate on an alternate filesystem root]:directory:_directories'
|
||||
'--image=[Operate on disk image as filesystem root]:image file:_files'
|
||||
'--image-policy=[Specify disk image dissection policy]:policy'
|
||||
'--transfer-source=[Specify the directory to transfer sources from]:directory:_directories'
|
||||
{-m+,--instances-max=}'[How many instances to maintain]:number'
|
||||
'--sync=[Control whether to sync data to disk]:bool:(yes no)'
|
||||
'--verify=[Force signature verification on or off]:bool:(yes no)'
|
||||
'--reboot[Reboot after updating to newer version]'
|
||||
'--offline[Do not fetch metadata from the network]'
|
||||
'--cleanup=[Clean up orphaned files after completing update]:bool:(yes no)'
|
||||
)
|
||||
|
||||
local -a commands=(
|
||||
'list:List available and installed versions'
|
||||
'features:List optional features'
|
||||
'enable-feature:Enable optional features'
|
||||
'disable-feature:Disable optional features'
|
||||
'check-new:Check if a newer version is available'
|
||||
'update:Install newest version'
|
||||
'acquire:Download newest version without installing'
|
||||
'vacuum:Make room by deleting old versions'
|
||||
'cleanup:Clean up orphaned files'
|
||||
'pending:Report whether a newer version is installed than booted'
|
||||
'reboot:Reboot if a newer version is installed than booted'
|
||||
'components:Show list of components'
|
||||
'enable-component:Enable components'
|
||||
'disable-component:Disable components'
|
||||
)
|
||||
|
||||
_arguments -s -A '-*' \
|
||||
"$opts[@]" \
|
||||
':command:->command' \
|
||||
'*:: :->argument' && ret=0
|
||||
|
||||
case $state in
|
||||
command)
|
||||
_describe -t commands 'systemd-sysupdate command' commands && ret=0
|
||||
;;
|
||||
argument)
|
||||
local curcontext=${curcontext%:*:*}:systemd-sysupdate-$words[1]:
|
||||
case $words[1] in
|
||||
features)
|
||||
_arguments -s "$opts[@]" ':feature:_systemd-sysupdate_features' && ret=0
|
||||
;;
|
||||
enable-feature|disable-feature)
|
||||
_arguments -s "$opts[@]" '*:feature:_systemd-sysupdate_features' && ret=0
|
||||
;;
|
||||
enable-component|disable-component)
|
||||
_arguments -s "$opts[@]" '*:component:_systemd-sysupdate_components' && ret=0
|
||||
;;
|
||||
*)
|
||||
_arguments -s "$opts[@]" && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
@@ -45,6 +45,7 @@ foreach item : [
|
||||
['_systemd-path', ''],
|
||||
['_systemd-run', ''],
|
||||
['_systemd-sysinstall', 'ENABLE_SYSINSTALL'],
|
||||
['_systemd-sysupdate', 'ENABLE_SYSUPDATE'],
|
||||
['_systemd-tmpfiles', 'ENABLE_TMPFILES'],
|
||||
['_timedatectl', 'ENABLE_TIMEDATED'],
|
||||
['_udevadm', ''],
|
||||
|
||||
@@ -2591,44 +2591,20 @@ typedef enum {
|
||||
UPDATE_ACTION_INSTALL = 1 << 1,
|
||||
} UpdateActionFlags;
|
||||
|
||||
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
|
||||
_cleanup_free_ char *booted_version = NULL;
|
||||
static int context_update(
|
||||
Context *c,
|
||||
const char *version,
|
||||
const char *booted_version,
|
||||
UpdateActionFlags action_flags) {
|
||||
|
||||
UpdateSet *applied = NULL;
|
||||
const char *version;
|
||||
int r;
|
||||
|
||||
assert(argc <= 2);
|
||||
version = argc >= 2 ? argv[1] : NULL;
|
||||
|
||||
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
r = context_from_cmdline(&context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (context.feature_select != SELECT_EXPLICIT)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
|
||||
if (context.component_select != SELECT_EXPLICIT)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
|
||||
|
||||
if (context.instances_max < 2)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"The --instances-max= argument must be >= 2 while updating");
|
||||
|
||||
if (context.reboot) {
|
||||
/* If automatic reboot on completion is requested, let's first determine the currently booted image */
|
||||
|
||||
r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse /etc/os-release: %m");
|
||||
if (!booted_version)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
|
||||
}
|
||||
|
||||
bool installed = false;
|
||||
int ret = 0;
|
||||
int r, ret = 0;
|
||||
|
||||
assert(c);
|
||||
|
||||
r = context_load_online(
|
||||
&context,
|
||||
c,
|
||||
/* process_image_flags= */ 0,
|
||||
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
|
||||
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
|
||||
@@ -2640,15 +2616,15 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
|
||||
/* No transfer files found. In that case, still do the installdb cleanup below */
|
||||
RET_GATHER(ret, r);
|
||||
} else {
|
||||
if (action_flags & UPDATE_ACTION_ACQUIRE)
|
||||
r = context_acquire(&context, version);
|
||||
if (FLAGS_SET(action_flags, UPDATE_ACTION_ACQUIRE))
|
||||
r = context_acquire(c, version);
|
||||
else
|
||||
r = context_process_partial_and_pending(&context, version);
|
||||
r = context_process_partial_and_pending(c, version);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */
|
||||
r = context_install(&context, version, &applied);
|
||||
r = context_install(c, version, &applied);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -2658,22 +2634,22 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
|
||||
/* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If
|
||||
* nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a
|
||||
* resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */
|
||||
if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) {
|
||||
if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && !installed) {
|
||||
int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY");
|
||||
if (f < 0 && f != -ENXIO)
|
||||
log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m");
|
||||
if (f > 0)
|
||||
(void) context_notify_subscribers(&context, /* us= */ NULL);
|
||||
(void) context_notify_subscribers(c, /* us= */ NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.cleanup > 0)
|
||||
RET_GATHER(ret, installdb_cleanup_component(&context));
|
||||
if (c->cleanup > 0)
|
||||
RET_GATHER(ret, installdb_cleanup_component(c));
|
||||
|
||||
if (installed) {
|
||||
/* We installed something, yay */
|
||||
|
||||
if (context.reboot) {
|
||||
if (c->reboot) {
|
||||
assert(applied);
|
||||
assert(booted_version);
|
||||
|
||||
@@ -2692,6 +2668,89 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
|
||||
const char *version;
|
||||
int r;
|
||||
|
||||
assert(argc <= 2);
|
||||
version = argc >= 2 ? argv[1] : NULL;
|
||||
|
||||
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
r = context_from_cmdline(&context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (context.feature_select != SELECT_EXPLICIT)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
|
||||
if (!IN_SET(context.component_select, SELECT_EXPLICIT, SELECT_ALL))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-suggested currently not supported for '%s'.", argv[0]);
|
||||
|
||||
if (context.instances_max < 2)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"The --instances-max= argument must be >= 2 while updating");
|
||||
|
||||
_cleanup_free_ char *booted_version = NULL;
|
||||
if (context.reboot) {
|
||||
/* We already checked in parse_argv() that --component=/--component-all/--component-suggested
|
||||
* are not combined with --reboot */
|
||||
assert(!context.component);
|
||||
assert(context.component_select == SELECT_EXPLICIT);
|
||||
|
||||
/* If automatic reboot on completion is requested, let's first determine the currently booted image */
|
||||
|
||||
r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse /etc/os-release: %m");
|
||||
if (!booted_version)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
|
||||
}
|
||||
|
||||
switch (context.component_select) {
|
||||
|
||||
case SELECT_EXPLICIT:
|
||||
return context_update(&context, version, booted_version, action_flags);
|
||||
|
||||
case SELECT_ALL: {
|
||||
int ret = 0;
|
||||
|
||||
/* Update the default, component-less installation first (if any). Running it before the
|
||||
* enumeration below also ensures the image (if any) is mounted and context.root is set, so
|
||||
* that we enumerate the components inside the image rather than on the host. A missing
|
||||
* default installation (ENOENT) is not an error in this mode. */
|
||||
r = context_update(&context, version, booted_version, action_flags);
|
||||
if (r != -ENOENT)
|
||||
RET_GATHER(ret, r);
|
||||
|
||||
_cleanup_strv_free_ char **component_names = NULL;
|
||||
r = context_list_components(&context, &component_names, /* ret_has_default_component= */ NULL);
|
||||
if (r < 0) {
|
||||
RET_GATHER(ret, r);
|
||||
return ret;
|
||||
}
|
||||
|
||||
STRV_FOREACH(name, component_names) {
|
||||
_cleanup_(context_done) Context cc = CONTEXT_NULL;
|
||||
|
||||
r = context_from_base_with_component(&context, *name, &cc);
|
||||
if (r < 0) {
|
||||
RET_GATHER(ret, r);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = context_update(&cc, version, booted_version, action_flags);
|
||||
if (r == -EHOSTDOWN) /* Component disabled → skip it in the "all" case. */
|
||||
continue;
|
||||
RET_GATHER(ret, r);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
|
||||
"Install new version now");
|
||||
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
|
||||
@@ -918,8 +918,9 @@ test -f "$COMPALL/target-b/comp-b-v1.bin"
|
||||
test -d /var/lib/systemd/sysupdate/installdb.comp-a
|
||||
test -d /var/lib/systemd/sysupdate/installdb.comp-b
|
||||
|
||||
# --component-all is only supported for the "cleanup" verb, refuse it elsewhere.
|
||||
(! "$SYSUPDATE" --component-all --verify=no update)
|
||||
# --component-all is not supported for every verb, so it must be refused where it isn't (e.g. "vacuum").
|
||||
# (It *is* supported for update/acquire/cleanup/enable-*/disable-*, which is exercised further down.)
|
||||
(! "$SYSUPDATE" --component-all --verify=no vacuum)
|
||||
|
||||
# With the transfer files still in place "cleanup --component-all" is a no-op:
|
||||
# nothing is orphaned.
|
||||
@@ -1449,4 +1450,375 @@ rm -rf "$CONFIGDIR/01-manifest-yes-and-retry.transfer" \
|
||||
"$SYSUPDATE" components |& grep "No components defined." >/dev/null
|
||||
[[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -r '.targets') == "[]" ]]
|
||||
|
||||
# ============================================================================
|
||||
# Components & features: enable/disable verbs, the --component-{all,suggested}
|
||||
# and --feature-{all,suggested} switches, plus the Suggest=/SuggestOn…=
|
||||
# settings. Everything below operates on freshly minted throw-away components
|
||||
# and features, and cleans up after itself.
|
||||
#
|
||||
# Layout used throughout:
|
||||
# * a component <c> is defined by a transfer directory /run/sysupdate.<c>.d/
|
||||
# (which is what makes it show up in the 'components' list) plus an optional
|
||||
# metadata file /run/sysupdate.<c>.component carrying Description=/Suggest=/…
|
||||
# * the enable-component/disable-component verbs write their Enabled= override
|
||||
# into a drop-in below /etc/sysupdate.<c>.component.d/
|
||||
# * the enable-feature/disable-feature verbs write their Enabled= override into
|
||||
# a drop-in below /etc/sysupdate.d/<f>.feature.d/ (default component) or
|
||||
# /etc/sysupdate.<c>.d/<f>.feature.d/ (named component)
|
||||
# ============================================================================
|
||||
CF="$WORKDIR/compfeat"
|
||||
|
||||
# The Suggest…MachineTag= tests below drive the machine tags via /etc/machine-info
|
||||
# (which the condition logic reads directly); back up any pre-existing file so we
|
||||
# can restore it afterwards, mirroring TEST-74-AUX-UTILS.machine-tags.sh.
|
||||
MI_BAK="$WORKDIR/machine-info.orig"
|
||||
rm -f "$MI_BAK"
|
||||
[[ -e /etc/machine-info ]] && cp -a /etc/machine-info "$MI_BAK"
|
||||
|
||||
set_machine_tags() {
|
||||
if [[ -n "${1:-}" ]]; then
|
||||
echo "TAGS=$1" >/etc/machine-info
|
||||
else
|
||||
rm -f /etc/machine-info
|
||||
fi
|
||||
}
|
||||
|
||||
restore_machine_info() {
|
||||
if [[ -e "$MI_BAK" ]]; then
|
||||
cp -a "$MI_BAK" /etc/machine-info
|
||||
else
|
||||
rm -f /etc/machine-info
|
||||
fi
|
||||
}
|
||||
|
||||
compfeat_cleanup() {
|
||||
rm -rf /run/sysupdate.d \
|
||||
/run/sysupdate.compx.d /run/sysupdate.compx.component /run/sysupdate.compx.component.d \
|
||||
/run/sysupdate.compy.d /run/sysupdate.compy.component /run/sysupdate.compy.component.d \
|
||||
/run/sysupdate.compz.d /run/sysupdate.compz.component /run/sysupdate.compz.component.d \
|
||||
/etc/sysupdate.d \
|
||||
/etc/sysupdate.compx.d /etc/sysupdate.compx.component.d \
|
||||
/etc/sysupdate.compy.d /etc/sysupdate.compy.component.d \
|
||||
/etc/sysupdate.compz.d /etc/sysupdate.compz.component.d
|
||||
rm -rf "$CF"
|
||||
}
|
||||
|
||||
compfeat_reset() {
|
||||
compfeat_cleanup
|
||||
mkdir -p "$CF/source" \
|
||||
"$CF/target-default" "$CF/target-compx" "$CF/target-compy" "$CF/target-compz"
|
||||
}
|
||||
|
||||
# (Re)generate the source payloads + SHA256SUMS for a given version. We create a
|
||||
# payload for every component/feature so a single SHA256SUMS covers them all;
|
||||
# individual transfers only ever match their own pattern.
|
||||
compfeat_source() {
|
||||
local v="${1:?}"
|
||||
local n
|
||||
for n in base compx compy compz feata featb featc; do
|
||||
echo "$n-$v-$RANDOM" >"$CF/source/$n-$v.bin"
|
||||
done
|
||||
(cd "$CF/source" && sha256sum -- *.bin >SHA256SUMS)
|
||||
}
|
||||
|
||||
# Write a regular-file transfer; optional 4th argument gates it behind a feature.
|
||||
compfeat_transfer() {
|
||||
local file="${1:?}" pat="${2:?}" tgt="${3:?}" feature="${4:-}"
|
||||
{
|
||||
if [[ -n "$feature" ]]; then
|
||||
printf '[Transfer]\nFeatures=%s\n\n' "$feature"
|
||||
fi
|
||||
printf '[Source]\nType=regular-file\nPath=%s\nMatchPattern=%s-@v.bin\n\n' "$CF/source" "$pat"
|
||||
printf '[Target]\nType=regular-file\nPath=%s\nMatchPattern=%s-@v.bin\nInstancesMax=2\n' "$tgt" "$pat"
|
||||
} >"$file"
|
||||
}
|
||||
|
||||
comp_enable_dropin() { echo "/etc/sysupdate.$1.component.d/50-systemd-sysupdate-enabled.conf"; }
|
||||
feat_enable_dropin_default() { echo "/etc/sysupdate.d/$1.feature.d/50-systemd-sysupdate-enabled.conf"; }
|
||||
feat_enable_dropin_comp() { echo "/etc/sysupdate.$1.d/$2.feature.d/50-systemd-sysupdate-enabled.conf"; }
|
||||
|
||||
# Assert a generated Enabled= drop-in exists and carries the expected value.
|
||||
assert_dropin() {
|
||||
local file="${1:?}" val="${2:?}"
|
||||
test -f "$file"
|
||||
grep "^Enabled=$val$" "$file" >/dev/null
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# enable-component / disable-component: explicit selection + observable effect
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.compx.d
|
||||
compfeat_transfer /run/sysupdate.compx.d/01-compx.transfer compx "$CF/target-compx"
|
||||
# A metadata file carrying a human-readable description (exercises loading of the
|
||||
# per-component *.component file from the search dirs).
|
||||
cat >/run/sysupdate.compx.component <<EOF
|
||||
[Component]
|
||||
Description=CompXDescription
|
||||
EOF
|
||||
|
||||
# The description from the *.component file must surface in the 'components' listing.
|
||||
"$SYSUPDATE" --no-legend components | grep -F "CompXDescription" >/dev/null
|
||||
|
||||
# A brand new component (no Enabled= override anywhere) is enabled by default and
|
||||
# can be updated.
|
||||
"$SYSUPDATE" --component=compx --verify=no update
|
||||
test -f "$CF/target-compx/compx-v1.bin"
|
||||
|
||||
# Disable it: this must drop an Enabled=no override next to the definition, and
|
||||
# subsequent updates for that component must be refused.
|
||||
"$SYSUPDATE" disable-component compx
|
||||
assert_dropin "$(comp_enable_dropin compx)" no
|
||||
(! "$SYSUPDATE" --component=compx --verify=no update) |& grep -F "Component is disabled" >/dev/null
|
||||
|
||||
# Re-enable via the "--component= + no positional argument" form and verify the
|
||||
# update works again.
|
||||
"$SYSUPDATE" --component=compx enable-component
|
||||
assert_dropin "$(comp_enable_dropin compx)" yes
|
||||
rm -f "$CF/target-compx/compx-v1.bin"
|
||||
"$SYSUPDATE" --component=compx --verify=no update
|
||||
test -f "$CF/target-compx/compx-v1.bin"
|
||||
|
||||
# Enabling a component must not conjure a bogus "<c>.component" pseudo-component
|
||||
# out of the freshly created sysupdate.compx.component.d/ drop-in directory.
|
||||
(! "$SYSUPDATE" --json=short components | grep -F '"compx.component"' >/dev/null)
|
||||
"$SYSUPDATE" --json=short components | grep -F '"compx"' >/dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# enable-component / disable-component: argument validation & error handling
|
||||
# ---------------------------------------------------------------------------
|
||||
# Positional argument and --component= are mutually exclusive.
|
||||
(! "$SYSUPDATE" --component=compx enable-component compx) |& grep -F "not both" >/dev/null
|
||||
# Syntactically invalid component name.
|
||||
(! "$SYSUPDATE" enable-component ../nope) |& grep -F "Component name invalid" >/dev/null
|
||||
# Unknown component.
|
||||
(! "$SYSUPDATE" enable-component doesnotexist) |& grep -F "Component not found" >/dev/null
|
||||
# --definitions= is incompatible with component enablement.
|
||||
(! "$SYSUPDATE" --definitions="$CF" enable-component compx) |& grep -F "may not be combined" >/dev/null
|
||||
# The feature-selection switches make no sense here.
|
||||
(! "$SYSUPDATE" --feature-all enable-component compx) |& grep -F "not supported" >/dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --component-all for enable-component / disable-component
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.compx.d /run/sysupdate.compy.d
|
||||
compfeat_transfer /run/sysupdate.compx.d/01-compx.transfer compx "$CF/target-compx"
|
||||
compfeat_transfer /run/sysupdate.compy.d/01-compy.transfer compy "$CF/target-compy"
|
||||
|
||||
# Disable *all* components in one go, then re-enable them all.
|
||||
"$SYSUPDATE" --component-all disable-component
|
||||
assert_dropin "$(comp_enable_dropin compx)" no
|
||||
assert_dropin "$(comp_enable_dropin compy)" no
|
||||
(! "$SYSUPDATE" --component=compx --verify=no update) |& grep -F "Component is disabled" >/dev/null
|
||||
(! "$SYSUPDATE" --component=compy --verify=no update) |& grep -F "Component is disabled" >/dev/null
|
||||
|
||||
"$SYSUPDATE" --component-all enable-component
|
||||
assert_dropin "$(comp_enable_dropin compx)" yes
|
||||
assert_dropin "$(comp_enable_dropin compy)" yes
|
||||
"$SYSUPDATE" --component=compx --verify=no update
|
||||
"$SYSUPDATE" --component=compy --verify=no update
|
||||
test -f "$CF/target-compx/compx-v1.bin"
|
||||
test -f "$CF/target-compy/compy-v1.bin"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --component-suggested driven by Suggest= (compx suggested, compy not)
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.compx.d /run/sysupdate.compy.d
|
||||
compfeat_transfer /run/sysupdate.compx.d/01-compx.transfer compx "$CF/target-compx"
|
||||
compfeat_transfer /run/sysupdate.compy.d/01-compy.transfer compy "$CF/target-compy"
|
||||
cat >/run/sysupdate.compx.component <<EOF
|
||||
[Component]
|
||||
Suggest=yes
|
||||
EOF
|
||||
cat >/run/sysupdate.compy.component <<EOF
|
||||
[Component]
|
||||
Suggest=no
|
||||
EOF
|
||||
|
||||
# 'enable-component --component-suggested' acts on the suggested components only.
|
||||
"$SYSUPDATE" --component-suggested enable-component
|
||||
assert_dropin "$(comp_enable_dropin compx)" yes
|
||||
test ! -e "$(comp_enable_dropin compy)"
|
||||
|
||||
# 'disable-component --component-suggested' reconciles the other way around: it
|
||||
# acts on the components that are *not* suggested (i.e. compy).
|
||||
"$SYSUPDATE" --component-suggested disable-component
|
||||
assert_dropin "$(comp_enable_dropin compy)" no
|
||||
# compx must be left as it was (still enabled from above).
|
||||
assert_dropin "$(comp_enable_dropin compx)" yes
|
||||
|
||||
# --component-suggested is not supported for the update verb.
|
||||
(! "$SYSUPDATE" --component-suggested --verify=no update) |& grep -F "not supported" >/dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SuggestOnMachineTag= for a component
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.compz.d
|
||||
compfeat_transfer /run/sysupdate.compz.d/01-compz.transfer compz "$CF/target-compz"
|
||||
cat >/run/sysupdate.compz.component <<EOF
|
||||
[Component]
|
||||
SuggestOnMachineTag=sysupdate-test-tag
|
||||
EOF
|
||||
|
||||
# Without the matching machine tag the component is not suggested, so
|
||||
# --component-suggested selects nothing.
|
||||
set_machine_tags some-other-tag
|
||||
"$SYSUPDATE" --component-suggested enable-component
|
||||
test ! -e "$(comp_enable_dropin compz)"
|
||||
|
||||
# With the matching tag present it becomes suggested and gets enabled.
|
||||
set_machine_tags sysupdate-test-tag:another
|
||||
"$SYSUPDATE" --component-suggested enable-component
|
||||
assert_dropin "$(comp_enable_dropin compz)" yes
|
||||
set_machine_tags ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# enable-feature / disable-feature on the default component + observable effect
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.d
|
||||
compfeat_transfer /run/sysupdate.d/01-base.transfer base "$CF/target-default"
|
||||
compfeat_transfer /run/sysupdate.d/50-feata.transfer feata "$CF/target-default" feata
|
||||
cat >/run/sysupdate.d/feata.feature <<EOF
|
||||
[Feature]
|
||||
Description=Feature A
|
||||
EOF
|
||||
|
||||
# The feature is listed and disabled by default, so its transfer is not installed.
|
||||
"$SYSUPDATE" features | grep -F "feata" >/dev/null
|
||||
"$SYSUPDATE" --verify=no update
|
||||
test -f "$CF/target-default/base-v1.bin"
|
||||
test ! -e "$CF/target-default/feata-v1.bin"
|
||||
|
||||
# Enabling the feature must drop an Enabled=yes override and cause the gated
|
||||
# transfer to be installed on the next update.
|
||||
"$SYSUPDATE" enable-feature feata
|
||||
assert_dropin "$(feat_enable_dropin_default feata)" yes
|
||||
"$SYSUPDATE" --verify=no update
|
||||
test -f "$CF/target-default/feata-v1.bin"
|
||||
|
||||
# Disabling it again + vacuum must remove the now-orphaned feature resource.
|
||||
"$SYSUPDATE" disable-feature feata
|
||||
assert_dropin "$(feat_enable_dropin_default feata)" no
|
||||
"$SYSUPDATE" --verify=no vacuum
|
||||
test ! -e "$CF/target-default/feata-v1.bin"
|
||||
|
||||
# Argument validation for the feature verbs.
|
||||
(! "$SYSUPDATE" enable-feature --feature-all feata) |& grep -F "not both" >/dev/null
|
||||
(! "$SYSUPDATE" enable-feature 'bad/name') |& grep -F "Feature name invalid" >/dev/null
|
||||
(! "$SYSUPDATE" --component-suggested enable-feature feata) |& grep -F "not supported" >/dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --feature-all / --feature-suggested (default component)
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.d
|
||||
compfeat_transfer /run/sysupdate.d/01-base.transfer base "$CF/target-default"
|
||||
# feata: suggested (Suggest=yes); featb: not suggested (Suggest=no);
|
||||
# featc: suggested on a machine tag we will set below.
|
||||
cat >/run/sysupdate.d/feata.feature <<EOF
|
||||
[Feature]
|
||||
Suggest=yes
|
||||
EOF
|
||||
cat >/run/sysupdate.d/featb.feature <<EOF
|
||||
[Feature]
|
||||
Suggest=no
|
||||
EOF
|
||||
cat >/run/sysupdate.d/featc.feature <<EOF
|
||||
[Feature]
|
||||
SuggestOnMachineTag=sysupdate-test-tag
|
||||
EOF
|
||||
|
||||
# --feature-all operates on every known feature.
|
||||
"$SYSUPDATE" enable-feature --feature-all
|
||||
assert_dropin "$(feat_enable_dropin_default feata)" yes
|
||||
assert_dropin "$(feat_enable_dropin_default featb)" yes
|
||||
assert_dropin "$(feat_enable_dropin_default featc)" yes
|
||||
|
||||
# --feature-suggested (no machine tag): only the Suggest=yes feature is picked.
|
||||
rm -rf /etc/sysupdate.d
|
||||
set_machine_tags unrelated
|
||||
"$SYSUPDATE" enable-feature --feature-suggested
|
||||
assert_dropin "$(feat_enable_dropin_default feata)" yes
|
||||
test ! -e "$(feat_enable_dropin_default featb)"
|
||||
test ! -e "$(feat_enable_dropin_default featc)"
|
||||
|
||||
# --feature-suggested with the machine tag set: feata + featc are picked.
|
||||
rm -rf /etc/sysupdate.d
|
||||
set_machine_tags sysupdate-test-tag
|
||||
"$SYSUPDATE" enable-feature --feature-suggested
|
||||
assert_dropin "$(feat_enable_dropin_default feata)" yes
|
||||
assert_dropin "$(feat_enable_dropin_default featc)" yes
|
||||
test ! -e "$(feat_enable_dropin_default featb)"
|
||||
set_machine_tags ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Features scoped to a named component, and across all components at once
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.d /run/sysupdate.compx.d
|
||||
compfeat_transfer /run/sysupdate.d/01-base.transfer base "$CF/target-default"
|
||||
compfeat_transfer /run/sysupdate.compx.d/01-compx.transfer compx "$CF/target-compx"
|
||||
cat >/run/sysupdate.d/feata.feature <<EOF
|
||||
[Feature]
|
||||
Description=Default feature A
|
||||
EOF
|
||||
cat >/run/sysupdate.compx.d/featx.feature <<EOF
|
||||
[Feature]
|
||||
Description=Component X feature
|
||||
EOF
|
||||
|
||||
# --component= scopes feature operations to that component: the drop-in must land
|
||||
# next to the component's definitions, and the default component is untouched.
|
||||
"$SYSUPDATE" --component=compx enable-feature --feature-all
|
||||
assert_dropin "$(feat_enable_dropin_comp compx featx)" yes
|
||||
test ! -e "$(feat_enable_dropin_default feata)"
|
||||
|
||||
# --component-all --feature-all fans out over the default component *and* every
|
||||
# named component.
|
||||
rm -rf /etc/sysupdate.d /etc/sysupdate.compx.d
|
||||
"$SYSUPDATE" --component-all enable-feature --feature-all
|
||||
assert_dropin "$(feat_enable_dropin_default feata)" yes
|
||||
assert_dropin "$(feat_enable_dropin_comp compx featx)" yes
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# update --component-all installs every component's update in one invocation
|
||||
# ---------------------------------------------------------------------------
|
||||
compfeat_reset
|
||||
compfeat_source v1
|
||||
mkdir -p /run/sysupdate.compx.d /run/sysupdate.compy.d
|
||||
compfeat_transfer /run/sysupdate.compx.d/01-compx.transfer compx "$CF/target-compx"
|
||||
compfeat_transfer /run/sysupdate.compy.d/01-compy.transfer compy "$CF/target-compy"
|
||||
|
||||
"$SYSUPDATE" --component-all --verify=no update
|
||||
test -f "$CF/target-compx/compx-v1.bin"
|
||||
test -f "$CF/target-compy/compy-v1.bin"
|
||||
|
||||
# A second version must likewise be rolled out to every component at once.
|
||||
compfeat_source v2
|
||||
"$SYSUPDATE" --component-all --verify=no update
|
||||
test -f "$CF/target-compx/compx-v2.bin"
|
||||
test -f "$CF/target-compy/compy-v2.bin"
|
||||
|
||||
# A disabled component must not turn "update --component-all" into a failure:
|
||||
# it is skipped, while the remaining enabled components are still updated.
|
||||
"$SYSUPDATE" disable-component compy
|
||||
compfeat_source v3
|
||||
"$SYSUPDATE" --component-all --verify=no update
|
||||
test -f "$CF/target-compx/compx-v3.bin"
|
||||
test ! -e "$CF/target-compy/compy-v3.bin"
|
||||
|
||||
compfeat_cleanup
|
||||
restore_machine_info
|
||||
|
||||
touch /testok
|
||||
|
||||
@@ -872,6 +872,10 @@ units = [
|
||||
'file' : 'systemd-sysinstall@.service',
|
||||
'conditions' : ['ENABLE_SYSINSTALL'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-sysupdate-auto-enable.service',
|
||||
'conditions' : ['ENABLE_SYSUPDATE'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-sysupdate-reboot.service',
|
||||
'conditions' : ['ENABLE_SYSUPDATE'],
|
||||
|
||||
21
units/systemd-sysupdate-auto-enable.service
Normal file
21
units/systemd-sysupdate-auto-enable.service
Normal file
@@ -0,0 +1,21 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Automatically Enable All Suggested Components & Features for System Updates
|
||||
Documentation=man:systemd-sysupdate-auto-enable.service(8)
|
||||
Before=systemd-sysupdate-update.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=systemd-sysupdate enable-component --component-suggested
|
||||
ExecStart=systemd-sysupdate enable-feature --component-all --feature-all
|
||||
|
||||
[Install]
|
||||
WantedBy=systemd-sysupdate-update.service
|
||||
@@ -17,7 +17,7 @@ ConditionVirtualization=!container
|
||||
[Service]
|
||||
Type=simple
|
||||
NotifyAccess=main
|
||||
ExecStart=systemd-sysupdate update --cleanup=yes
|
||||
ExecStart=systemd-sysupdate update --component-all --cleanup=yes
|
||||
|
||||
# Keep this sandboxing synchronised with systemd-sysupdate@.service
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD CAP_SETFCAP CAP_SYS_ADMIN CAP_SETPCAP CAP_DAC_OVERRIDE CAP_LINUX_IMMUTABLE
|
||||
|
||||
Reference in New Issue
Block a user