|
26 | 26 | from contextlib import AbstractContextManager |
27 | 27 | from pathlib import Path |
28 | 28 | from typing import Any, Optional, Union, cast |
| 29 | +from urllib.parse import urlparse |
29 | 30 |
|
30 | 31 | from mkosi.archive import can_extract_tar, extract_tar, make_cpio, make_tar |
31 | 32 | from mkosi.bootloader import ( |
@@ -343,12 +344,14 @@ def check_root_populated(context: Context) -> None: |
343 | 344 | ) |
344 | 345 |
|
345 | 346 |
|
346 | | -def configure_os_release(context: Context) -> None: |
347 | | - """Write IMAGE_ID and IMAGE_VERSION to /usr/lib/os-release in the image.""" |
| 347 | +def configure_os_release_and_appstream(context: Context) -> None: |
| 348 | + """Write os-release and appstream metainfo in the image.""" |
348 | 349 |
|
349 | 350 | if context.config.overlay or context.config.output_format.is_extension_image(): |
350 | 351 | return |
351 | 352 |
|
| 353 | + osrelease_content = {} |
| 354 | + |
352 | 355 | for candidate in ["usr/lib/os-release", "usr/lib/initrd-release", "etc/os-release"]: |
353 | 356 | osrelease = context.root / candidate |
354 | 357 |
|
@@ -385,9 +388,68 @@ def configure_os_release(context: Context) -> None: |
385 | 388 |
|
386 | 389 | newosrelease.rename(osrelease) |
387 | 390 |
|
| 391 | + osrelease_content = read_env_file(osrelease) |
| 392 | + |
388 | 393 | if ArtifactOutput.os_release in context.config.split_artifacts: |
389 | 394 | shutil.copy(osrelease, context.staging / context.config.output_split_os_release) |
390 | 395 |
|
| 396 | + # https://www.freedesktop.org/software/appstream/docs/sect-Metadata-OS.html |
| 397 | + osid = context.config.appstream_id or osrelease_content.get("ID", "linux") |
| 398 | + name = context.config.appstream_name or osrelease_content.get("NAME") or context.config.image_id or osid |
| 399 | + if context.config.appstream_description: |
| 400 | + description = context.config.appstream_description |
| 401 | + else: |
| 402 | + description = f"{osrelease_content.get('PRETTY_NAME', 'Image')} built with mkosi" |
| 403 | + icon = context.config.appstream_icon or "https://brand.systemd.io/assets/svg/systemd-logomark.svg" |
| 404 | + home_url = context.config.appstream_url or osrelease_content.get("HOME_URL") |
| 405 | + if home_url: |
| 406 | + url = urlparse(home_url) |
| 407 | + if not url.netloc: |
| 408 | + home_url = None |
| 409 | + if context.config.appstream_id: |
| 410 | + id = context.config.appstream_id |
| 411 | + elif home_url: |
| 412 | + url = urlparse(home_url) |
| 413 | + netloc = url.netloc.split(".") |
| 414 | + netloc.reverse() |
| 415 | + if "www" in netloc: |
| 416 | + netloc.remove("www") |
| 417 | + id = ".".join(netloc) |
| 418 | + id += f".{osid}" |
| 419 | + else: |
| 420 | + id = osid |
| 421 | + timestamp = ( |
| 422 | + datetime.datetime.fromtimestamp(context.config.source_date_epoch, tz=datetime.timezone.utc) |
| 423 | + if context.config.source_date_epoch is not None |
| 424 | + else datetime.datetime.now(tz=datetime.timezone.utc) |
| 425 | + ).isoformat() |
| 426 | + |
| 427 | + metainfo = '<?xml version="1.0" encoding="UTF-8"?>\n' |
| 428 | + metainfo += '<component type="operating-system">\n' |
| 429 | + metainfo += f" <id>{id}</id>\n" |
| 430 | + metainfo += f" <name>{name}</name>\n" |
| 431 | + metainfo += f" <summary>{osid} image built with mkosi</summary>\n" |
| 432 | + metainfo += f" <description><p>{description}</p></description>\n" |
| 433 | + if home_url: |
| 434 | + metainfo += f' <url type="homepage">{home_url}</url>\n' |
| 435 | + if icon: |
| 436 | + metainfo += f' <icon type="remote">{icon}</icon>\n' |
| 437 | + metainfo += " <metadata_license>FSFAP</metadata_license>\n" |
| 438 | + metainfo += " <releases>\n" |
| 439 | + metainfo += ( |
| 440 | + f' <release version="{context.config.image_version}" date="{timestamp}" type="development">\n' |
| 441 | + ) |
| 442 | + metainfo += " <description></description>\n" |
| 443 | + metainfo += " </release>\n" |
| 444 | + metainfo += " </releases>\n" |
| 445 | + metainfo += "</component>\n" |
| 446 | + |
| 447 | + metainto_out = context.root / f"usr/share/metainfo/{id}.metainfo.xml" |
| 448 | + metainto_out.parent.mkdir(parents=True, exist_ok=True) |
| 449 | + metainto_out.write_text(metainfo) |
| 450 | + if ArtifactOutput.appstream_metainfo in context.config.split_artifacts: |
| 451 | + (context.staging / context.config.output_split_appstream_metainfo).write_text(metainfo) |
| 452 | + |
391 | 453 |
|
392 | 454 | def configure_extension_release(context: Context) -> None: |
393 | 455 | if context.config.output_format not in (OutputFormat.sysext, OutputFormat.confext): |
@@ -3921,7 +3983,7 @@ def build_image(context: Context) -> None: |
3921 | 3983 | fixup_vmlinuz_location(context) |
3922 | 3984 |
|
3923 | 3985 | configure_autologin(context) |
3924 | | - configure_os_release(context) |
| 3986 | + configure_os_release_and_appstream(context) |
3925 | 3987 | configure_extension_release(context) |
3926 | 3988 | configure_initrd(context) |
3927 | 3989 | configure_ssh(context) |
|
0 commit comments