f"--release={config.release}",
f"--architecture={config.architecture}",
*([f"--mirror={config.mirror}"] if config.mirror else []),
+ *([f"--snapshot={config.snapshot}"] if config.snapshot else []),
f"--repository-key-check={config.repository_key_check}",
f"--repository-key-fetch={config.repository_key_fetch}",
*([f"--repositories={repository}" for repository in config.repositories]),
)
+def run_latest_snapshot(args: Args, config: Config) -> None:
+ print(config.distribution.latest_snapshot(config))
+
+
def run_shell(args: Args, config: Config) -> None:
opname = "acquire shell in" if args.verb == Verb.shell else "boot"
if config.output_format not in (OutputFormat.directory, OutputFormat.disk):
finalize_image_version(args, last)
return
+ if args.verb == Verb.latest_snapshot:
+ run_latest_snapshot(args, last)
+ return
+
if args.verb == Verb.clean:
if tools and args.force > 0:
run_clean(args, tools)
box = enum.auto()
sandbox = enum.auto()
init = enum.auto()
+ latest_snapshot = enum.auto()
def supports_cmdline(self) -> bool:
return self in (
)
def needs_tools(self) -> bool:
- return self in (Verb.box, Verb.sandbox, Verb.journalctl, Verb.coredumpctl, Verb.ssh)
+ return self in (
+ Verb.box,
+ Verb.sandbox,
+ Verb.journalctl,
+ Verb.coredumpctl,
+ Verb.ssh,
+ Verb.latest_snapshot,
+ )
def needs_build(self) -> bool:
return self in (
release: str
architecture: Architecture
mirror: Optional[str]
+ snapshot: Optional[str]
local_mirror: Optional[str]
repository_key_check: bool
repository_key_fetch: bool
"distribution": self.distribution,
"release": self.release,
"mirror": self.mirror,
+ "snapshot": self.snapshot,
"architecture": self.architecture,
# Caching the package manager used does not matter for the default tools tree because we don't
# cache the package manager metadata for the tools tree either. In fact, it can cause issues as
help="Distribution mirror to use",
scope=SettingScope.universal,
),
+ ConfigSetting(
+ dest="snapshot",
+ section="Distribution",
+ help="Distribution snapshot to use",
+ path_suffixes=("snapshot",),
+ path_read_text=True,
+ scope=SettingScope.universal,
+ tools=True,
+ ),
ConfigSetting(
dest="local_mirror",
section="Distribution",
if args.directory is None:
return False
- if args.verb in (Verb.clean, Verb.sandbox):
+ if args.verb in (Verb.clean, Verb.sandbox, Verb.latest_snapshot):
return False
if args.verb == Verb.summary and args.force > 0:
Release: {bold(none_to_na(config.release))}
Architecture: {config.architecture}
Mirror: {none_to_default(config.mirror)}
+ Snapshot: {none_to_none(config.snapshot)}
Local Mirror (build): {none_to_none(config.local_mirror)}
Repo Signature/Key check: {yes_no(config.repository_key_check)}
Fetch Repository Keys: {yes_no(config.repository_key_fetch)}
# SPDX-License-Identifier: LGPL-2.1-or-later
+import os
+import subprocess
from pathlib import Path
+from typing import Optional, overload
from mkosi.config import Config
from mkosi.mounts import finalize_certificate_mounts
from mkosi.run import run, workdir
-def curl(config: Config, url: str, output_dir: Path, log: bool = True) -> None:
- run(
+@overload
+def curl(
+ config: Config,
+ url: str,
+ *,
+ output_dir: Optional[Path],
+ log: bool = True,
+) -> None: ...
+
+
+@overload
+def curl(
+ config: Config,
+ url: str,
+ *,
+ output_dir: None = None,
+ log: bool = True,
+) -> str: ...
+
+
+def curl(config: Config, url: str, *, output_dir: Optional[Path] = None, log: bool = True) -> Optional[str]:
+ result = run(
[
"curl",
"--location",
- "--output-dir", workdir(output_dir),
- "--remote-name",
+ *(["--output-dir", workdir(output_dir)] if output_dir else []),
+ *(["--remote-name"] if output_dir else []),
"--no-progress-meter",
"--fail",
*(["--silent"] if not log else []),
*(["--proxy-key", "/proxy.clientkey"] if config.proxy_client_key else []),
url,
],
+ stdout=None if output_dir else subprocess.PIPE,
sandbox=config.sandbox(
network=True,
- options=["--bind", output_dir, workdir(output_dir), *finalize_certificate_mounts(config)],
+ options=[
+ *(["--bind", os.fspath(output_dir), workdir(output_dir)] if output_dir else []),
+ *finalize_certificate_mounts(config)
+ ],
),
log=log,
) # fmt: skip
+
+ return None if output_dir else result.stdout
from pathlib import Path
from typing import TYPE_CHECKING, Optional, cast
+from mkosi.log import die
from mkosi.util import StrEnum, read_env_file
if TYPE_CHECKING:
def grub_prefix(cls) -> str:
return "grub"
+ @classmethod
+ def latest_snapshot(cls, config: "Config") -> str:
+ die(f"{cls.pretty_name()} does not support snapshots")
+
class Distribution(StrEnum):
# Please consult docs/distribution-policy.md and contact one
def createrepo(self, context: "Context") -> None:
return self.installer().package_manager(context.config).createrepo(context)
+ def latest_snapshot(self, config: "Config") -> str:
+ return self.installer().latest_snapshot(config)
+
def installer(self) -> type[DistributionInstaller]:
modname = str(self).replace("-", "_")
mod = importlib.import_module(f"mkosi.distributions.{modname}")
from mkosi.context import Context
from mkosi.distributions import centos, join_mirror
from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey
+from mkosi.log import die
class Installer(centos.Installer):
gpgurls: tuple[str, ...],
repo: str,
) -> list[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
if context.config.mirror:
url = f"baseurl={join_mirror(context.config.mirror, f'$releasever/{repo}/$basearch/os')}"
else:
# SPDX-License-Identifier: LGPL-2.1-or-later
+import datetime
import tempfile
from collections.abc import Iterable
from pathlib import Path
from mkosi.config import Architecture, Config
from mkosi.context import Context
from mkosi.curl import curl
-from mkosi.distributions import DistributionInstaller, PackageType
+from mkosi.distributions import DistributionInstaller, PackageType, join_mirror
from mkosi.installer.pacman import Pacman, PacmanRepository
from mkosi.log import complete_step, die
curl(
context.config,
"https://archlinux.org/packages/core/any/archlinux-keyring/download",
- Path(d),
+ output_dir=Path(d),
)
extract_tar(
next(Path(d).iterdir()),
yield PacmanRepository("core", context.config.local_mirror)
else:
if context.config.architecture.is_arm_variant():
- url = f"{context.config.mirror or 'http://mirror.archlinuxarm.org'}/$arch/$repo"
+ if context.config.snapshot and not context.config.mirror:
+ die("There is no known public mirror for snapshots of Arch Linux ARM")
+
+ mirror = context.config.mirror or "http://mirror.archlinuxarm.org"
+ else:
+ if context.config.mirror:
+ mirror = context.config.mirror
+ elif context.config.snapshot:
+ mirror = "https://archive.archlinux.org"
+ else:
+ mirror = "https://geo.mirror.pkgbuild.com"
+
+ if context.config.snapshot:
+ url = join_mirror(mirror, f"repos/{context.config.snapshot}/$repo/os/$arch")
+ elif context.config.architecture.is_arm_variant():
+ url = join_mirror(mirror, "$arch/$repo")
else:
- url = f"{context.config.mirror or 'https://geo.mirror.pkgbuild.com'}/$repo/os/$arch"
+ url = join_mirror(mirror, "$repo/os/$arch")
# Testing repositories have to go before regular ones to to take precedence.
repos = [
die(f"Architecture {a} is not supported by Arch Linux")
return a
+
+ @classmethod
+ def latest_snapshot(cls, config: Config) -> str:
+ url = join_mirror(config.mirror or "https://archive.archlinux.org", "repos/last/lastsync")
+ return datetime.datetime.fromtimestamp(int(curl(config, url)), datetime.timezone.utc).strftime(
+ "%Y/%m/%d"
+ )
@classmethod
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
gpgurls = (
find_rpm_gpgkey(
context,
from mkosi.config import Architecture, Config
from mkosi.context import Context
+from mkosi.curl import curl
from mkosi.distributions import (
Distribution,
DistributionInstaller,
from mkosi.installer.dnf import Dnf
from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey, setup_rpm
from mkosi.log import die
+from mkosi.util import startswith
from mkosi.versioncomp import GenericVersion
CENTOS_SIG_REPO_PRIORITY = 50
gpgurls: tuple[str, ...],
repo: str,
) -> Iterable[RpmRepository]:
+ mirror = context.config.mirror
+ if not mirror and context.config.snapshot:
+ mirror = "https://composes.stream.centos.org"
+
+ if context.config.snapshot and mirror not in (
+ "https://composes.stream.centos.org",
+ "https://mirror.facebook.net/centos-composes",
+ ):
+ die(
+ f"Snapshot= is only supported for {cls.pretty_name()} if Mirror=https://composes.stream.centos.org"
+ )
+
+ if (
+ mirror in ("https://composes.stream.centos.org", "https://mirror.facebook.net/centos-composes")
+ and not context.config.snapshot
+ ):
+ die(f"Snapshot= must be used on {cls.pretty_name()} if Mirror={mirror}")
+
if context.config.local_mirror:
yield RpmRepository(repo, f"baseurl={context.config.local_mirror}", gpgurls)
- elif mirror := context.config.mirror:
+ elif mirror:
+ if mirror == "https://composes.stream.centos.org":
+ subdir = f"stream-{context.config.release}/production"
+ elif mirror == "https://mirror.facebook.net/centos-composes":
+ subdir = context.config.release
+ elif repo == "extras":
+ subdir = "SIGs/$stream"
+ else:
+ subdir = "$stream"
+
+ if context.config.snapshot:
+ subdir += f"/CentOS-Stream-{context.config.release}-{context.config.snapshot}/compose"
+
if repo == "extras":
yield RpmRepository(
repo.lower(),
- f"baseurl={join_mirror(mirror, f'SIGs/$stream/{repo}/$basearch/extras-common')}",
+ f"baseurl={join_mirror(mirror, f'{subdir}/{repo}/$basearch/extras-common')}",
gpgurls,
)
yield RpmRepository(
f"{repo.lower()}-source",
- f"baseurl={join_mirror(mirror, f'SIGs/$stream/{repo}/source/extras-common')}",
+ f"baseurl={join_mirror(mirror, f'{subdir}/{repo}/source/extras-common')}",
gpgurls,
enabled=False,
)
-
else:
yield RpmRepository(
repo.lower(),
- f"baseurl={join_mirror(mirror, f'$stream/{repo}/$basearch/os')}",
+ f"baseurl={join_mirror(mirror, f'{subdir}/{repo}/$basearch/os')}",
gpgurls,
)
yield RpmRepository(
f"{repo.lower()}-debuginfo",
- f"baseurl={join_mirror(mirror, f'$stream/{repo}/$basearch/debug/tree')}",
+ f"baseurl={join_mirror(mirror, f'{subdir}/{repo}/$basearch/debug/tree')}",
gpgurls,
enabled=False,
)
yield RpmRepository(
f"{repo.lower()}-source",
- f"baseurl={join_mirror(mirror, f'$stream/{repo}/source/tree')}",
+ f"baseurl={join_mirror(mirror, f'{subdir}/{repo}/source/tree')}",
gpgurls,
enabled=False,
)
yield from cls.repository_variants(context, gpgurls, "BaseOS")
yield from cls.repository_variants(context, gpgurls, "AppStream")
- yield from cls.repository_variants(context, gpgurls, "extras")
yield from cls.repository_variants(context, gpgurls, "CRB")
+ if not context.config.snapshot:
+ yield from cls.repository_variants(context, gpgurls, "extras")
yield from cls.epel_repositories(context)
yield from cls.sig_repositories(context)
enabled=False,
priority=CENTOS_SIG_REPO_PRIORITY,
)
+
+ @classmethod
+ def latest_snapshot(cls, config: Config) -> str:
+ mirror = config.mirror or "https://composes.stream.centos.org"
+
+ if mirror == "https://mirror.facebook.net/centos-composes":
+ subdir = config.release
+ else:
+ subdir = f"stream-{config.release}/production"
+
+ url = join_mirror(mirror, f"{subdir}/latest-CentOS-Stream/compose/.composeinfo")
+
+ for line in curl(config, url).splitlines():
+ if snapshot := startswith(line, f"id = CentOS-Stream-{config.release}-"):
+ return snapshot
+
+ die("composeinfo is missing compose ID field")
# SPDX-License-Identifier: LGPL-2.1-or-later
import itertools
+import json
import tempfile
from collections.abc import Iterable
from pathlib import Path
+from typing import cast
from mkosi.archive import extract_tar
from mkosi.config import Architecture, Config
from mkosi.context import Context
-from mkosi.distributions import DistributionInstaller, PackageType
+from mkosi.curl import curl
+from mkosi.distributions import DistributionInstaller, PackageType, join_mirror
from mkosi.installer.apt import Apt, AptRepository
from mkosi.log import die
from mkosi.run import run, workdir
)
return
- mirror = context.config.mirror or "http://deb.debian.org/debian"
+ if context.config.mirror:
+ mirror = context.config.mirror
+ elif context.config.snapshot:
+ mirror = "https://snapshot.debian.org"
+ else:
+ mirror = "http://deb.debian.org"
+
+ if context.config.snapshot:
+ url = join_mirror(mirror, f"archive/debian/{context.config.snapshot}")
+ else:
+ url = join_mirror(mirror, "debian")
+
signedby = Path("/usr/share/keyrings/debian-archive-keyring.gpg")
yield AptRepository(
types=types,
- url=mirror,
+ url=url,
suite=context.config.release,
components=components,
signedby=signedby,
)
# Debug repos are typically not mirrored.
- url = "http://deb.debian.org/debian-debug"
+ if context.config.snapshot:
+ url = join_mirror(mirror, f"archive/debian-debug/{context.config.snapshot}")
+ else:
+ url = join_mirror(mirror, "debian-debug")
yield AptRepository(
types=types,
if context.config.release in ("unstable", "sid"):
return
- yield AptRepository(
- types=types,
- url=mirror,
- suite=f"{context.config.release}-updates",
- components=components,
- signedby=signedby,
- )
+ if not context.config.snapshot:
+ yield AptRepository(
+ types=types,
+ url=join_mirror(mirror, "debian"),
+ suite=f"{context.config.release}-updates",
+ components=components,
+ signedby=signedby,
+ )
+
+ # Security updates repos are never mirrored.
+ if context.config.snapshot:
+ url = join_mirror(mirror, f"archive/debian-security/{context.config.snapshot}")
+ else:
+ url = join_mirror(mirror, "debian-security")
yield AptRepository(
types=types,
- # Security updates repos are never mirrored.
- url="http://security.debian.org/debian-security",
+ url=url,
suite=f"{context.config.release}-security",
components=components,
signedby=signedby,
return a
+ @classmethod
+ def latest_snapshot(cls, config: Config) -> str:
+ url = join_mirror(config.mirror or "https://snapshot.debian.org", "mr/timestamp")
+ return cast(str, json.loads(curl(config, url))["result"]["debian"][-1])
+
def install_apt_sources(context: Context, repos: Iterable[AptRepository]) -> None:
sources = context.root / f"etc/apt/sources.list.d/{context.config.release}.sources"
# let's fetch it from distribution-gpg-keys on github if necessary, which is generally up-to-date.
with tempfile.TemporaryDirectory() as d:
# The rawhide key is a symlink and github doesn't redirect those to the actual file for some reason
- curl(context.config, f"{DISTRIBUTION_GPG_KEYS_UPSTREAM}/RPM-GPG-KEY-fedora-rawhide-primary", Path(d))
+ curl(
+ context.config,
+ f"{DISTRIBUTION_GPG_KEYS_UPSTREAM}/RPM-GPG-KEY-fedora-rawhide-primary",
+ output_dir=Path(d),
+ )
return (Path(d) / "RPM-GPG-KEY-fedora-rawhide-primary").read_text()
curl(
context.config,
f"{DISTRIBUTION_GPG_KEYS_UPSTREAM}/RPM-GPG-KEY-fedora-{version + 1}-primary",
- Path(d),
+ output_dir=Path(d),
log=False,
)
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
gpgurls = find_fedora_rpm_gpgkeys(context)
+ if context.config.snapshot and context.config.release != "rawhide":
+ die(f"Snapshot= is only supported for rawhide on {cls.pretty_name()}")
+
+ mirror = context.config.mirror
+ if not mirror and context.config.snapshot:
+ mirror = "https://kojipkgs.fedoraproject.org"
+
+ if context.config.snapshot and mirror != "https://kojipkgs.fedoraproject.org":
+ die(
+ f"Snapshot= is only supported for {cls.pretty_name()} if Mirror=https://kojipkgs.fedoraproject.org"
+ )
+
+ if mirror == "https://kojipkgs.fedoraproject.org" and not context.config.snapshot:
+ die(
+ f"Snapshot= must be used on {cls.pretty_name()} if Mirror=https://kojipkgs.fedoraproject.org"
+ )
+
if context.config.local_mirror:
yield RpmRepository("fedora", f"baseurl={context.config.local_mirror}", gpgurls)
return
f"{repo.lower()}-debuginfo", f"{url}/$basearch/debug/tree", gpgurls, enabled=False
)
yield RpmRepository(f"{repo.lower()}-source", f"{url}/source/tree", gpgurls, enabled=False)
- elif m := context.config.mirror:
- directory = "development" if context.config.release == "rawhide" else "releases"
- url = f"baseurl={join_mirror(m, f'linux/{directory}/$releasever/Everything')}"
+ elif mirror:
+ if mirror == "https://kojipkgs.fedoraproject.org":
+ subdir = f"compose/{context.config.release}"
+ else:
+ subdir = "linux/"
+ subdir += "development" if context.config.release == "rawhide" else "releases"
+ subdir += "/$releasever"
+
+ if context.config.snapshot:
+ subdir += f"/Fedora-{context.config.release.capitalize()}-{context.config.snapshot}/compose"
+
+ url = f"baseurl={join_mirror(mirror, f'{subdir}/Everything')}"
yield RpmRepository("fedora", f"{url}/$basearch/os", gpgurls)
yield RpmRepository("fedora-debuginfo", f"{url}/$basearch/debug/tree", gpgurls, enabled=False)
yield RpmRepository("fedora-source", f"{url}/source/tree", gpgurls, enabled=False)
if context.config.release != "rawhide":
- url = f"baseurl={join_mirror(m, 'linux/updates/$releasever/Everything')}"
+ url = f"baseurl={join_mirror(mirror, 'linux/updates/$releasever/Everything')}"
yield RpmRepository("updates", f"{url}/$basearch", gpgurls)
yield RpmRepository("updates-debuginfo", f"{url}/$basearch/debug", gpgurls, enabled=False)
yield RpmRepository("updates-source", f"{url}/source/tree", gpgurls, enabled=False)
- url = f"baseurl={join_mirror(m, 'linux/updates/testing/$releasever/Everything')}"
+ url = f"baseurl={join_mirror(mirror, 'linux/updates/testing/$releasever/Everything')}"
yield RpmRepository("updates-testing", f"{url}/$basearch", gpgurls, enabled=False)
yield RpmRepository(
"updates-testing-debuginfo", f"{url}/$basearch/debug", gpgurls, enabled=False
die(f"Architecture {a} is not supported by Fedora")
return a
+
+ @classmethod
+ def latest_snapshot(cls, config: Config) -> str:
+ mirror = config.mirror or "https://kojipkgs.fedoraproject.org"
+
+ url = join_mirror(
+ mirror, f"compose/{config.release}/latest-Fedora-{config.release.capitalize()}/COMPOSE_ID"
+ )
+
+ return curl(config, url).removeprefix(f"Fedora-{config.release.capitalize()}-").strip()
@classmethod
def repositories(cls, context: Context, local: bool = True) -> Iterable[AptRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
if context.config.local_mirror and local:
yield AptRepository(
types=("deb",),
@classmethod
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
gpgurls = (
find_rpm_gpgkey(
context,
@classmethod
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
mirror = context.config.mirror or "http://mirror.openmandriva.org"
gpgurls = (
zypper = cls.package_manager(context.config) is Zypper
mirror = context.config.mirror or "https://download.opensuse.org"
- if context.config.release == "tumbleweed" or context.config.release.isdigit():
+ if context.config.release == "tumbleweed":
gpgkeys = tuple(
p
for key in ("RPM-GPG-KEY-openSUSE-Tumbleweed", "RPM-GPG-KEY-openSUSE")
),
) # fmt: skip
- if context.config.release == "tumbleweed":
+ if context.config.snapshot:
+ if context.config.architecture != Architecture.x86_64:
+ die(f"Snapshot= is only supported for x86-64 on {cls.pretty_name()}")
+
+ subdir = f"history/{context.config.snapshot}"
+ else:
if context.config.architecture == Architecture.x86_64:
subdir = ""
elif context.config.architecture == Architecture.arm64:
subdir = "ports/riscv"
else:
die(f"{context.config.architecture} not supported by openSUSE Tumbleweed")
- else:
- if context.config.architecture != Architecture.x86_64:
- die(f"Old snapshots are only supported for x86-64 on {cls.pretty_name()}")
-
- subdir = f"history/{context.config.release}"
for repo in ("oss", "non-oss"):
url = join_mirror(mirror, f"{subdir}/tumbleweed/repo/{repo}")
enabled=repo == "oss",
)
- if context.config.release == "tumbleweed":
+ if not context.config.snapshot:
for d in ("debug", "source"):
url = join_mirror(mirror, f"{subdir}/{d}/tumbleweed/repo/{repo}")
yield RpmRepository(
enabled=False,
)
- if context.config.release == "tumbleweed":
+ if not context.config.snapshot:
url = join_mirror(mirror, f"{subdir}/update/tumbleweed")
yield RpmRepository(
id="oss-update",
enabled=False,
)
else:
+ if context.config.snapshot:
+ die(f"Snapshot= is only supported for Tumbleweed on {cls.pretty_name()}")
+
if (
context.config.release in ("current", "stable", "leap")
and context.config.architecture != Architecture.x86_64
return a
+ @classmethod
+ def latest_snapshot(cls, config: Config) -> str:
+ url = join_mirror(config.mirror or "https://download.opensuse.org", "history/latest")
+ return curl(config, url).strip()
+
def fetch_gpgurls(context: Context, repourl: str) -> tuple[str, ...]:
gpgurls = [f"{repourl}/repodata/repomd.xml.key"]
with tempfile.TemporaryDirectory() as d:
- curl(context.config, f"{repourl}/repodata/repomd.xml", Path(d))
+ curl(context.config, f"{repourl}/repodata/repomd.xml", output_dir=Path(d))
xml = (Path(d) / "repomd.xml").read_text()
root = ElementTree.fromstring(xml)
@classmethod
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
gpgurls = cls.gpgurls(context)
yield from cls.repository_variants(context, gpgurls, "baseos")
yield from cls.repository_variants(context, gpgurls, "appstream")
from mkosi.context import Context
from mkosi.distributions import centos, join_mirror
from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey
+from mkosi.log import die
class Installer(centos.Installer):
@classmethod
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
gpgurls = cls.gpgurls(context)
yield from cls.repository_variants(context, gpgurls, "baseos")
yield from cls.repository_variants(context, gpgurls, "appstream")
from mkosi.context import Context
from mkosi.distributions import centos, join_mirror
from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey
+from mkosi.log import die
class Installer(centos.Installer):
gpgurls: tuple[str, ...],
repo: str,
) -> list[RpmRepository]:
+ if context.config.snapshot:
+ die(f"Snapshot= is not supported for {cls.pretty_name()}")
+
if context.config.mirror:
url = f"baseurl={join_mirror(context.config.mirror, f'$releasever/{repo}/$basearch/os')}"
else:
# SPDX-License-Identifier: LGPL-2.1-or-later
+import datetime
+import locale
from collections.abc import Iterable
from pathlib import Path
+from mkosi.config import Config
from mkosi.context import Context
-from mkosi.distributions import Distribution, debian
+from mkosi.curl import curl
+from mkosi.distributions import Distribution, debian, join_mirror
from mkosi.installer.apt import AptRepository
+from mkosi.log import die
+from mkosi.util import startswith
class Installer(debian.Installer):
suite=context.config.release,
components=components,
signedby=signedby,
+ snapshot=context.config.snapshot,
)
yield AptRepository(
suite=f"{context.config.release}-updates",
components=components,
signedby=signedby,
+ snapshot=context.config.snapshot,
)
# Security updates repos are never mirrored. But !x86 are on the ports server.
suite=f"{context.config.release}-security",
components=components,
signedby=signedby,
+ snapshot=context.config.snapshot,
)
+
+ @classmethod
+ def latest_snapshot(cls, config: Config) -> str:
+ mirror = config.mirror or "http://snapshot.ubuntu.com"
+ release = curl(config, join_mirror(mirror, f"ubuntu/dists/{config.release}-updates/Release"))
+
+ for line in release.splitlines():
+ if date := startswith(line, "Date: "):
+ # %a and %b parse the abbreviated day of the week and the abbreviated month which are both
+ # locale-specific so set the locale to C explicitly to make sure we try to parse the english
+ # abbreviations used in the Release file.
+ lc = locale.setlocale(locale.LC_TIME)
+ try:
+ locale.setlocale(locale.LC_TIME, "C")
+ return datetime.datetime.strptime(date, "%a, %d %b %Y %H:%M:%S %Z").strftime(
+ "%Y%m%dT%H%M%SZ"
+ )
+ finally:
+ locale.setlocale(locale.LC_TIME, lc)
+
+ die("Release file is missing Date field")
suite: str
components: tuple[str, ...]
signedby: Optional[Path]
+ snapshot: Optional[str] = None
def __str__(self) -> str:
return textwrap.dedent(
Suites: {self.suite}
Components: {" ".join(self.components)}
{"Signed-By" if self.signedby else "Trusted"}: {self.signedby or "yes"}
+ {f"Snapshot: {self.snapshot}" if self.snapshot else ""}
"""
)
`mkosi [options…] completion [shell]`
+`mkosi [options…] latest-snapshot`
+
`mkosi [options…] help`
# DESCRIPTION
See the documentation for `ToolsTreeProfiles=` for a list of
available profiles.
+`latest-snapshot`
+: Output the latest available snapshot in the configured mirror.
+
+ This verb is useful to automatically bump snapshots every so often.
+ Note that this verb only outputs the latest snapshot. It's up to the
+ caller to ensure that the snapshot is written to the intended configuration
+ file.
+
`help`
: This verb is equivalent to the `--help` switch documented below: it
shows a brief usage explanation.
| | x86-64 | aarch64 |
|----------------|-----------------------------------|--------------------------------|
- | `debian` | http://deb.debian.org/debian | |
+ | `debian` | http://deb.debian.org | |
| `arch` | https://geo.mirror.pkgbuild.com | http://mirror.archlinuxarm.org |
| `opensuse` | http://download.opensuse.org | |
| `kali` | http://http.kali.org/kali | |
| `openmandriva` | http://mirrors.openmandriva.org | |
| `azure` | https://packages.microsoft.com/ | |
+`Snapshot=`
+: Download packages from the given snapshot instead of downloading the latest
+ distribution packages from the given mirror. Takes a snapshot ID (the format
+ of the snapshot ID differs per distribution), use the `latest-snapshot` verb
+ to figure out the latest available snapshot.
+
+ If this setting is configured and `Mirror=` is not explicitly configured, different
+ default mirrors are used:
+
+ | | x86-64 | aarch64 |
+ |----------------|------------------------------------|--------------------------------|
+ | `debian` | https://snapshot.debian.org | |
+ | `arch` | https://archive.archlinux.org | http://mirror.archlinuxarm.org |
+ | `opensuse` | http://download.opensuse.org | |
+ | `ubuntu` | http://archive.ubuntu.com | http://ports.ubuntu.com |
+ | `centos` | https://composes.stream.centos.org | |
+ | `fedora` | https://kojipkgs.fedoraproject.org | |
+
+ For any distribution not listed above, snapshots are not supported.
+
`LocalMirror=`, `--local-mirror=`
: The mirror will be used as a local, plain and direct mirror instead
of using it as a prefix for the full set of repositories normally supported
"Target": "/qux"
}
],
+ "Snapshot": "snapshot",
"SourceDateEpoch": 12345,
"Splash": "/splash",
"SplitArtifacts": [
sign_expected_pcr=ConfigFeature.disabled,
sign=False,
skeleton_trees=[ConfigTree(Path("/foo/bar"), Path("/")), ConfigTree(Path("/bar/baz"), Path("/qux"))],
+ snapshot="snapshot",
source_date_epoch=12345,
splash=Path("/splash"),
split_artifacts=[ArtifactOutput.uki, ArtifactOutput.kernel],