diff --git a/TODO.md b/TODO.md
index b216feb8218..257fd32d127 100644
--- a/TODO.md
+++ b/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
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 486f565815f..eac4465769b 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -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'],
diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml
index 2855c650c88..7386a399e7c 100644
--- a/man/systemd-sysupdate.xml
+++ b/man/systemd-sysupdate.xml
@@ -22,6 +22,7 @@
systemd-sysupdate-update.timersystemd-sysupdate-reboot.servicesystemd-sysupdate-reboot.timer
+ systemd-sysupdate-auto-enable.servicesystemd-sysupdate.service
@@ -87,6 +88,12 @@
appropriate time (i.e. typically at night). The two sets of service/timer units may be enabled
separately.
+ The systemd-sysupdate-auto-enable.service 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 systemd-sysupdate-update.service, 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 enable-component/enable-feature invocations.
+
For details about transfer files and examples see
sysupdate.d5.
@@ -121,6 +128,21 @@
+
+ FEATURE…
+ FEATURE…
+
+ Enables or disables the specified optional features. This writes an
+ Enabled= setting into a drop-in next to the feature definition, see
+ sysupdate.features5
+ for details. The features may be specified either as arguments or selected via the
+ or switches. Note that this only
+ changes the configuration: to actually download the newly enabled transfers, or remove the disabled
+ ones, invoke or afterwards.
+
+
+
+
@@ -209,6 +231,18 @@
+
+ COMPONENT…
+ COMPONENT…
+
+ Enables or disables the specified components. This writes an
+ Enabled= setting into a drop-in next to the component definition. The components
+ may be specified either as arguments or selected via the ,
+ or switches.
+
+
+
+
@@ -280,15 +314,57 @@
- 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
- cleanup command; all other commands will fail if this switch is used.
+ 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 update,
+ acquire, cleanup, enable-feature,
+ disable-feature, enable-component and
+ disable-component commands; all other commands will fail if this switch is
+ used.This option may not be combined with .
+
+
+
+
+ Similar to , but operates only on the components that
+ are currently suggested for this system, as determined by the
+ Suggest= and SuggestOn…= settings in the component definition
+ files. This is supported for the enable-component and
+ disable-component commands.
+
+ This option may not be combined with .
+
+
+
+
+
+
+
+
+ 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
+ enable-feature and disable-feature commands.
+
+
+
+
+
+
+
+
+ Similar to , but operates only on the optional features
+ that are currently suggested for this system, as determined by the
+ Suggest= and SuggestOn…= settings in the feature definition
+ files. This is supported for the enable-feature and
+ disable-feature commands.
+
+
+
+
@@ -416,6 +492,8 @@
systemd1sysupdate.d5
+ sysupdate.features5
+ sysupdate.components5systemd-sysupdated.service8systemd-repart8
diff --git a/man/sysupdate.components.xml b/man/sysupdate.components.xml
new file mode 100644
index 00000000000..54cec98c7c8
--- /dev/null
+++ b/man/sysupdate.components.xml
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
+ sysupdate.components
+ systemd
+
+
+
+ sysupdate.components
+ 5
+
+
+
+ sysupdate.components
+ Definition Files for Update Components
+
+
+
+
+ /etc/sysupdate.component.component
+ /run/sysupdate.component.component
+ /usr/local/lib/sysupdate.component.component
+ /usr/lib/sysupdate.component.component
+
+
+
+
+ Description
+
+ A "Component" is a set of
+ sysupdate.d5
+ transfer definitions that
+ systemd-sysupdate8
+ 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:
+ sysupdate.component.d/*.transfer (as opposed to the
+ default, component-less sysupdate.d/*.transfer).
+ Components are selected via the , or
+ switches of
+ systemd-sysupdate8, and
+ enumerated by its components command.
+
+ 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
+ sysupdate.features5
+ 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.
+
+ A component does not require a *.component file: as soon as a matching
+ sysupdate.component.d/ directory with transfer definitions
+ exists, the component is available for updating. The *.component 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.
+
+ When a component is disabled (via Enabled=false, see below), operations that
+ would download or install new versions — most importantly update — refuse to act on
+ it. Other operations, such as vacuum and cleanup, 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 enable-component and
+ disable-component commands of
+ systemd-sysupdate8,
+ which write an Enabled= setting into a drop-in next to the component definition.
+
+ Component definitions support drop-in files, which are most commonly used to override the
+ Enabled= setting. Drop-ins are read from
+ sysupdate.component.component.d/*.conf in the same set of
+ directories as the main file. They can also be masked to hide the component definition entirely.
+
+ Each *.component file contains one section: [Component].
+
+
+
+ [Component] Section Options
+
+ This section defines general properties of this component.
+
+
+
+ Description=
+
+ 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.
+
+
+
+
+
+ Documentation=
+
+ A user-presentable URL to documentation about this component.
+ This setting supports specifier expansion; see below for details on supported specifiers.
+
+
+
+
+
+ Enabled=
+
+ Whether or not this component is enabled. If unspecified, the component is enabled
+ by default. When disabled, systemd-sysupdate 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.
+
+
+
+
+
+ Suggest=
+
+ 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 switch of
+ systemd-sysupdate8)
+ so that the system administrator or higher-level tooling may act on it.
+
+ If this setting is not specified, the SuggestOn…= conditions described below
+ are evaluated instead to determine whether the component is suggested. If neither this setting nor any
+ SuggestOn…= condition is specified, the component is not suggested.
+
+
+
+
+
+ SuggestOnArchitecture=
+ SuggestOnFirmware=
+ SuggestOnVirtualization=
+ SuggestOnHost=
+ SuggestOnFraction=
+ SuggestOnKernelCommandLine=
+ SuggestOnVersion=
+ SuggestOnCredential=
+ SuggestOnSecurity=
+ SuggestOnOSRelease=
+ SuggestOnMachineTag=
+
+ Suggest this component for enablement depending on system properties. These settings
+ take the same arguments and implement the same semantics — including the leading !
+ for negation — as the identically-named
+ ConditionArchitecture=, ConditionFirmware=,
+ ConditionVirtualization=, ConditionHost=,
+ ConditionFraction=, ConditionKernelCommandLine=,
+ ConditionVersion=, ConditionCredential=,
+ ConditionSecurity=, ConditionOSRelease= and
+ ConditionMachineTag= settings for unit files, which are documented in
+ systemd.unit5.
+ The component is suggested if all specified conditions apply. As with the Condition…=
+ settings, assigning an empty string to one of these resets the list.
+
+ These conditions are only evaluated if Suggest= is not specified.
+
+
+
+
+
+
+
+ Specifiers
+
+ Specifiers may be used in the Documentation= setting. The following expansions
+ are understood:
+
+
+
+
+
+ Examples
+
+
+ A Separately Updated Container Image
+
+ We'll use the hypothetical "foobarOS" described in
+ sysupdate.d5 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
+ webapp.
+
+ First, the component definition, carrying its metadata and marking it as disabled by default:
+
+ # /usr/lib/sysupdate.webapp.component
+[Component]
+Description=WebApp Container Image
+Documentation=https://docs.example.com/foobarOS/webapp
+Enabled=false
+
+
+ Next, the transfer definition for the component, placed in the component's own directory
+ sysupdate.webapp.d/:
+
+ # /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
+
+
+ With these two files, updates for the webapp component can be checked and
+ downloaded independently of the base OS, for example with
+ systemd-sysupdate --component=webapp update. Because
+ Enabled=false was set, the administrator must first opt in with
+ systemd-sysupdate enable-component webapp, which writes the following drop-in:
+
+ # /etc/sysupdate.webapp.component.d/50-systemd-sysupdate-enabled.conf
+# Generated via 'systemd-sysupdate enable-component'
+
+[Component]
+Enabled=true
+
+
+
+
+ A Component Suggested Only on Bare Metal
+
+ 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 SuggestOn…= to
+ hint that the component should be enabled in that case, without enabling it automatically:
+
+ # /usr/lib/sysupdate.firmware.component
+[Component]
+Description=Firmware and Microcode Updates
+Documentation=https://docs.example.com/foobarOS/firmware
+Enabled=false
+SuggestOnVirtualization=!vm
+
+
+ Higher-level tooling can now query the suggested components (via
+ systemd-sysupdate --component-suggested …) and enable them as appropriate. On bare
+ metal the firmware component is suggested; inside a VM it is not.
+
+
+
+
+ See Also
+
+ systemd1
+ systemd-sysupdate8
+ sysupdate.d5
+ sysupdate.features5
+
+
+
+
diff --git a/man/sysupdate.d.xml b/man/sysupdate.d.xml
index 29daeda81a0..9c27bf444a0 100644
--- a/man/sysupdate.d.xml
+++ b/man/sysupdate.d.xml
@@ -1068,6 +1068,8 @@ CurrentSymlink=myContainer
systemd1systemd-sysupdate8
+ sysupdate.features5
+ sysupdate.components5systemd-repart8
diff --git a/man/sysupdate.features.xml b/man/sysupdate.features.xml
index d3a7a638101..29f067f61ba 100644
--- a/man/sysupdate.features.xml
+++ b/man/sysupdate.features.xml
@@ -381,6 +381,7 @@ InstancesMax=2
systemd1systemd-sysupdate8sysupdate.d5
+ sysupdate.components5
diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build
index cddf742059d..863ceb693bb 100644
--- a/shell-completion/bash/meson.build
+++ b/shell-completion/bash/meson.build
@@ -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'],
diff --git a/shell-completion/bash/systemd-sysupdate b/shell-completion/bash/systemd-sysupdate
new file mode 100644
index 00000000000..fb5f5344251
--- /dev/null
+++ b/shell-completion/bash/systemd-sysupdate
@@ -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 .
+
+__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
diff --git a/shell-completion/zsh/_systemd-sysupdate b/shell-completion/zsh/_systemd-sysupdate
new file mode 100644
index 00000000000..e7930159e5a
--- /dev/null
+++ b/shell-completion/zsh/_systemd-sysupdate
@@ -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
diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build
index edad8bcb325..7ffd4250acf 100644
--- a/shell-completion/zsh/meson.build
+++ b/shell-completion/zsh/meson.build
@@ -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', ''],
diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c
index dadf307ff64..28ea65c0338 100644
--- a/src/sysupdate/sysupdate.c
+++ b/src/sysupdate/sysupdate.c
@@ -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) {
diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh
index 74af9296cc7..84c057c3723 100755
--- a/test/units/TEST-72-SYSUPDATE.sh
+++ b/test/units/TEST-72-SYSUPDATE.sh
@@ -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 is defined by a transfer directory /run/sysupdate..d/
+# (which is what makes it show up in the 'components' list) plus an optional
+# metadata file /run/sysupdate..component carrying Description=/Suggest=/…
+# * the enable-component/disable-component verbs write their Enabled= override
+# into a drop-in below /etc/sysupdate..component.d/
+# * the enable-feature/disable-feature verbs write their Enabled= override into
+# a drop-in below /etc/sysupdate.d/.feature.d/ (default component) or
+# /etc/sysupdate..d/.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 </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 ".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 </run/sysupdate.compy.component </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 </run/sysupdate.d/feata.feature </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 </run/sysupdate.d/featb.feature </run/sysupdate.d/featc.feature </run/sysupdate.d/feata.feature </run/sysupdate.compx.d/featx.feature <