mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 10:13:34 +08:00
sysupdate: Support changelogs & appstream metadata
Makes it possible to specify URLs to a changelog and an appstream catalog XML in the sysupdate.d/*.conf files. This will be passed along to the clients of systemd-sysupdated, which can then present this data.
This commit is contained in:
parent
42c0b689a8
commit
db8849f2d4
118
docs/APPSTREAM_BUNDLE.md
Normal file
118
docs/APPSTREAM_BUNDLE.md
Normal file
@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Appstream Bundle
|
||||
category: Interfaces
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# Appstream Bundle
|
||||
|
||||
NOTE: This document is a work-in-progress.
|
||||
|
||||
NOTE: This isn't yet implemented in libappstream and the software centers.
|
||||
|
||||
[Appstream catalogs](https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html)
|
||||
are a standardized way to expose metadata about system components, apps, and updates to software
|
||||
centers (i.e. GNOME Software and KDE Discover). The `<bundle/>` tag links an appstream component
|
||||
to a packaging format. This is used by the software centers to decide which code path (or plugin)
|
||||
should handle the component. For instance: components with a `<bundle type="package">...</bundle>`
|
||||
will be handled by [PackageKit](https://www.freedesktop.org/software/PackageKit/), and components
|
||||
with a `<bundle type="flatpak">...</bundle>` will be handled by [libflatpak](https://docs.flatpak.org/).
|
||||
This document will define how to format an appstream component's `<bundle>` tag such that software
|
||||
centers will know to manage it using systemd. The following syntax will be supported:
|
||||
|
||||
A `type="systemd"` attribute. This tells the software center that it should treat the bundle tag
|
||||
as described in this document.
|
||||
|
||||
A `class=""` attribute, with the following possible values: `sysupdate`, `extension`, `confext`,
|
||||
or `portable`. These correspond to sysupdate components, sysexts, confexts, and portable services
|
||||
respectively.
|
||||
|
||||
The value of the tag will be used as the name of the image (corresponding to the `class=` attribute).
|
||||
So for instance, `<bundle type="systemd" class="extension">foobar</bundle>` corresponds to a sysext
|
||||
named "foobar". For `class="sysupdate"`, there is a special case: if the value is empty, then the
|
||||
bundle actually refers to the host system.
|
||||
|
||||
## Examples
|
||||
|
||||
```xml
|
||||
<component type="addon">
|
||||
<id>com.example.Devel</id>
|
||||
<extends>com.example.OS</extends>
|
||||
<name>Development Tools</name>
|
||||
<summary>Tools essential to develop Example OS</summary>
|
||||
<provides>
|
||||
<binary>gcc</binary>
|
||||
<binary>g++</binary>
|
||||
<binary>make</binary>
|
||||
<binary>autoconf</binary>
|
||||
<binary>cmake</binary>
|
||||
<binary>meson</binary>
|
||||
<binary>ninja</binary>
|
||||
</provides>
|
||||
<developer_name>Example, inc.</developer_name>
|
||||
<releases>
|
||||
<release version="45" date="2024-01-15" />
|
||||
<release version="44" date="2023-12-08" />
|
||||
<release version="43" date="2023-11-10" />
|
||||
</releases>
|
||||
<bundle type="systemd" class="extension">devel</bundle>
|
||||
</component>
|
||||
```
|
||||
|
||||
defines a sysext named `devel` to be presented by the software center. It will be
|
||||
updated via `systemd-sysupdated`'s `extension:devel` target. It will be treated
|
||||
as a plugin for the operating system itself.
|
||||
|
||||
```xml
|
||||
<component merge="append">
|
||||
<id>com.example.OS</id>
|
||||
<releases>
|
||||
<release version="45" date="2024-01-15" urgency="high">
|
||||
<description>
|
||||
<p>This release includes various bug fixes and performance improvements</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
<bundle type="systemd" class="sysupdate" />
|
||||
</component>
|
||||
```
|
||||
|
||||
extends existing appstream metadata for the host OS with a changelog. It also tells the software
|
||||
center that the host OS should be updated using the `host` target for `systemd-sysupdated`.
|
||||
|
||||
```xml
|
||||
<component type="service">
|
||||
<id>com.example.Foobar</id>
|
||||
<name>Foobar Service</name>
|
||||
<summary>Service that does foo to bar</summary>
|
||||
<icon type="remote">https://example.com/products/foobar/logo.svg</icon>
|
||||
<url type="homepage">https://example.com/products/foobar</url>
|
||||
<provides>
|
||||
<dbus type="system">com.example.Foobar</dbus>
|
||||
</provides>
|
||||
<developer_name>Example, inc.</developer_name>
|
||||
<releases>
|
||||
<release version="1.0.1" date="2024-02-16" urgency="critical">
|
||||
<description>
|
||||
<p>This release fixes a major security vulnerability. Please update ASAP.</p>
|
||||
</description>
|
||||
<issues>
|
||||
<issue type="cve">CVE-2024-28153</issue>
|
||||
</issues>
|
||||
</release>
|
||||
<release version="1.1-beta" date="2024-01-08" type="development" />
|
||||
<release version="1.0" date="2023-11-23">
|
||||
<description>
|
||||
<p>Initial release!</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
<bundle type="systemd" class="portable">foobar</bundle>
|
||||
</component>
|
||||
```
|
||||
|
||||
defines a portable service named `foobar` to be presented by the software center. It will be
|
||||
updated via `systemd-sysupdated`'s `portable:foobar` target. It will be marked as an
|
||||
urgent update. It will be presented to the user with a display name, a description, and
|
||||
a custom icon.
|
@ -487,6 +487,38 @@
|
||||
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ChangeLog=</varname></term>
|
||||
|
||||
<listitem><para>Optionally takes a human-presentable URL to a website containing a change-log of
|
||||
the resource being updated.</para>
|
||||
|
||||
<para>This may be set multiple times in a single transfer definition. If set multiple times, the
|
||||
values are gathered into a list of URLs. Adding a value of the empty string will clear the existing
|
||||
list of all values.</para>
|
||||
|
||||
<para>This setting supports specifier expansion. See below for details on supported
|
||||
specifiers. This setting will also expand the <literal>@v</literal> wildcard pattern. See above
|
||||
for details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>AppStream=</varname></term>
|
||||
|
||||
<listitem><para>Optionally takes a URL to an
|
||||
<ulink url="https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html">AppStream catalog</ulink>
|
||||
XML file. This may be used by software centers (such as GNOME Software or KDE Discover) to present
|
||||
rich metadata about the resources being updated. This includes display names, changelogs, icons,
|
||||
and more. The specified catalog must include <ulink url="https://systemd.io/APPSTREAM_BUNDLE">special metadata</ulink>
|
||||
to be correctly associated with <command>systemd-sysupdate</command> by the software centers.</para>
|
||||
|
||||
<para>This setting supports specifier expansion. See below for details on supported
|
||||
specifiers.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
@ -50,6 +50,9 @@ Transfer *transfer_free(Transfer *t) {
|
||||
free(t->current_symlink);
|
||||
free(t->final_path);
|
||||
|
||||
strv_free(t->changelog);
|
||||
strv_free(t->appstream);
|
||||
|
||||
partition_info_destroy(&t->partition_info);
|
||||
|
||||
resource_destroy(&t->source);
|
||||
@ -168,6 +171,48 @@ static int config_parse_min_version(
|
||||
return free_and_replace(*version, resolved);
|
||||
}
|
||||
|
||||
static int config_parse_url_specifiers(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
char ***s = ASSERT_PTR(data);
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
int r;
|
||||
|
||||
assert(rvalue);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
*s = strv_free(*s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!http_url_is_valid(resolved)) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0,
|
||||
"%s= URL is not valid, ignoring: %s", lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = strv_push(s, TAKE_PTR(resolved));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_current_symlink(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
@ -431,6 +476,8 @@ int transfer_read_definition(Transfer *t, const char *path) {
|
||||
{ "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version },
|
||||
{ "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions },
|
||||
{ "Transfer", "Verify", config_parse_bool, 0, &t->verify },
|
||||
{ "Transfer", "ChangeLog", config_parse_url_specifiers, 0, &t->changelog },
|
||||
{ "Transfer", "AppStream", config_parse_url_specifiers, 0, &t->appstream },
|
||||
{ "Source", "Type", config_parse_resource_type, 0, &t->source.type },
|
||||
{ "Source", "Path", config_parse_resource_path, 0, &t->source },
|
||||
{ "Source", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->source.path_relative_to },
|
||||
|
@ -26,6 +26,9 @@ struct Transfer {
|
||||
uint64_t instances_max;
|
||||
bool remove_temporary;
|
||||
|
||||
char **changelog;
|
||||
char **appstream;
|
||||
|
||||
/* When creating a new partition/file, optionally override these attributes explicitly */
|
||||
sd_id128_t partition_uuid;
|
||||
bool partition_uuid_set;
|
||||
|
@ -162,8 +162,10 @@ static int context_read_definitions(
|
||||
"No transfer definitions found.");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
r = transfer_resolve_paths(c->transfers[i], root, node);
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
|
||||
r = transfer_resolve_paths(t, root, node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -480,6 +482,7 @@ static int context_show_version(Context *c, const char *version) {
|
||||
have_read_only = false, have_growfs = false, have_sha256 = false;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
_cleanup_strv_free_ char **changelog_urls = NULL;
|
||||
UpdateSet *us;
|
||||
int r;
|
||||
|
||||
@ -521,13 +524,30 @@ static int context_show_version(Context *c, const char *version) {
|
||||
table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
|
||||
|
||||
/* Determine if the target will make use of partition/fs attributes for any of the transfers */
|
||||
for (size_t n = 0; n < c->n_transfers; n++) {
|
||||
Transfer *tr = c->transfers[n];
|
||||
FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
|
||||
Transfer *tr = *transfer;
|
||||
|
||||
if (tr->target.type == RESOURCE_PARTITION)
|
||||
show_partition_columns = true;
|
||||
if (RESOURCE_IS_FILESYSTEM(tr->target.type))
|
||||
show_fs_columns = true;
|
||||
|
||||
STRV_FOREACH(changelog, tr->changelog) {
|
||||
assert(*changelog);
|
||||
|
||||
_cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
|
||||
if (!changelog_url)
|
||||
return log_oom();
|
||||
|
||||
/* Avoid duplicates */
|
||||
if (strv_contains(changelog_urls, changelog_url))
|
||||
continue;
|
||||
|
||||
/* changelog_urls takes ownership of expanded changelog_url */
|
||||
r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t n = 0; n < us->n_instances; n++) {
|
||||
@ -666,13 +686,14 @@ static int context_show_version(Context *c, const char *version) {
|
||||
if (!have_sha256)
|
||||
(void) table_hide_column_from_display(t, 12);
|
||||
|
||||
|
||||
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
|
||||
printf("%s%s%s Version: %s\n"
|
||||
" State: %s%s%s\n"
|
||||
"Installed: %s%s\n"
|
||||
"Available: %s%s\n"
|
||||
"Protected: %s%s%s\n"
|
||||
" Obsolete: %s%s%s\n\n",
|
||||
" Obsolete: %s%s%s\n",
|
||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
|
||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
||||
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
||||
@ -680,6 +701,15 @@ static int context_show_version(Context *c, const char *version) {
|
||||
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
||||
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
||||
|
||||
STRV_FOREACH(url, changelog_urls) {
|
||||
_cleanup_free_ char *changelog_link = NULL;
|
||||
r = terminal_urlify(*url, NULL, &changelog_link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
printf("ChangeLog: %s\n", changelog_link);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
|
||||
@ -694,6 +724,7 @@ static int context_show_version(Context *c, const char *version) {
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
|
||||
SD_JSON_BUILD_PAIR_STRV("changelog_urls", changelog_urls),
|
||||
SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
@ -990,6 +1021,7 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
||||
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
||||
_cleanup_(context_freep) Context* context = NULL;
|
||||
_cleanup_strv_free_ char **appstream_urls = NULL;
|
||||
const char *version;
|
||||
int r;
|
||||
|
||||
@ -1013,8 +1045,8 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
_cleanup_strv_free_ char **versions = NULL;
|
||||
const char *current = NULL;
|
||||
|
||||
for (size_t i = 0; i < context->n_update_sets; i++) {
|
||||
UpdateSet *us = context->update_sets[i];
|
||||
FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
|
||||
UpdateSet *us = *update_set;
|
||||
|
||||
if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
|
||||
FLAGS_SET(us->flags, UPDATE_NEWEST))
|
||||
@ -1025,8 +1057,20 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
|
||||
STRV_FOREACH(appstream_url, (*tr)->appstream) {
|
||||
/* Avoid duplicates */
|
||||
if (strv_contains(appstream_urls, *appstream_url))
|
||||
continue;
|
||||
|
||||
r = strv_extend(&appstream_urls, *appstream_url);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
|
||||
SD_JSON_BUILD_PAIR_STRV("all", versions));
|
||||
SD_JSON_BUILD_PAIR_STRV("all", versions),
|
||||
SD_JSON_BUILD_PAIR_STRV("appstream_urls", appstream_urls));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user