feat: update version to 2026.9.7 in pyproject.toml

Signed-off-by: d9pouces <github@19pouces.net>
This commit is contained in:
d9pouces
2026-09-09 22:20:16 +02:00
parent 380e976f76
commit 4409c38579
21 changed files with 252 additions and 72 deletions
+51 -11
View File
@@ -5,13 +5,29 @@ one-line `.list` files or DEB822 `.sources` files, whether at the top level
or under `sources.list.d/`) is copied into `<dst>/etc/apt`, disabled so it no
longer takes effect, and the sources declared in the configuration are added
as new, enabled `.sources` files.
Each source can be trusted two ways: `signed_by` holds the ASCII-armored
content of a GPG public key, which is converted with `gpg --dearmor` into a
binary keyring dedicated to this one source, written to
`/etc/apt/keyrings/<name>.gpg` - it is that path, not the key's own content,
that ends up in the rendered `.sources` file's `Signed-By` field.
`signed_by_file` instead names a keyring already expected to exist on the
target system, used verbatim as `Signed-By` (no file is written for it); if
both are given, `signed_by` takes precedence. `gpg` is only used here for
the armored-to-binary format conversion; the actual signature verification
of the repository itself is left to APT when it fetches the repository's
Release/InRelease file.
"""
from __future__ import annotations
import logging
import shutil
import subprocess
import tempfile
from pathlib import Path
from id_configurator.config import AptSource
from id_configurator.context import Context
from id_configurator.modules.base import Module, register
@@ -43,6 +59,28 @@ def _disable_deb822_content(content: str) -> str:
return "\n\n".join(disabled_stanzas) + "\n"
def _dearmor_gpg_key(name: str, content: bytes) -> bytes:
"""Convert an ASCII-armored GPG public key into apt's binary keyring format."""
with tempfile.TemporaryDirectory() as gnupg_home:
path = shutil.which("gpg")
if path is not None:
result = subprocess.run(
[path, "--homedir", gnupg_home, "--batch", "--dearmor"],
input=content,
capture_output=True,
check=False,
)
else:
raise RuntimeError(
"gpg is not available; the GNUPG package must be installed in the environment running id-configurator"
)
if result.returncode != 0:
raise RuntimeError(
f"invalid GPG key for apt source {name}: {result.stderr.decode('utf-8', 'replace')}"
)
return result.stdout
@register
class AptModule(Module):
name = "apt"
@@ -59,7 +97,6 @@ class AptModule(Module):
def apply(self, context: Context) -> None:
self._copy_existing_disabled(context)
self._write_configured_keys(context)
self._write_configured_sources(context)
@staticmethod
@@ -82,19 +119,22 @@ class AptModule(Module):
)
logger.info("disabled existing apt source %s", entry.name)
@staticmethod
def _write_configured_keys(context: Context) -> None:
for key in context.config.configurations.apt.keys:
context.write_file(
f"{_KEYRINGS_DIR}/{key.name}.asc", key.content.encode("utf-8")
)
logger.info("installed apt keyring %s", key.name)
def _resolve_signed_by(self, context: Context, source: AptSource) -> str | None:
if source.signed_by:
keyring = _dearmor_gpg_key(source.name, source.signed_by.encode("utf-8"))
relative = f"{_KEYRINGS_DIR}/{source.name}.gpg"
context.write_file(relative, keyring)
logger.info("installed apt keyring for source %s", source.name)
return f"/{relative}"
if source.signed_by_file:
return source.signed_by_file
return None
@staticmethod
def _write_configured_sources(context: Context) -> None:
def _write_configured_sources(self, context: Context) -> None:
for source in context.config.configurations.apt.sources:
signed_by_path = self._resolve_signed_by(context, source)
context.write_file(
f"{_SOURCES_LIST_D}/{source.name}.sources",
source.to_deb822().encode("utf-8"),
source.to_deb822(signed_by_path).encode("utf-8"),
)
logger.info("added apt source %s", source.name)