APT Source Management Interface
  • Python 88.3%
  • C++ 6.1%
  • Shell 4.6%
  • CMake 1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Andrey Alekseev 7c501748b4
Some checks failed
SAST for shell scripts (ShellCheck) / shellcheck (push) Successful in 26s
RPM build / secrets-check (push) Successful in 1m6s
RPM build / build-rpm (p11) (push) Failing after 55s
RPM build / build-rpm (sisyphus) (push) Failing after 36s
Formatting check / check_formatting (push) Successful in 34s
0.1.6-alt1
- Reworked external sources
2026-09-25 17:09:34 +04:00
.forgejo/workflows build: package apt-alt-source with backend 2026-09-24 18:39:08 +04:00
.gear 0.1.6-alt1 2026-09-25 17:09:34 +04:00
.githooks chore: only format and stage Python files that are actually staged 2026-05-13 22:31:14 +03:00
apt-alt-source test(apt-alt-source): use input FIFOs for interactive method tests 2026-09-24 18:43:05 +04:00
backend feat: implement batch method 2026-09-16 19:37:37 +04:00
docs docs: document CheckSign and CheckProvides D-Bus methods 2026-09-25 17:09:33 +04:00
helpers refactor: install media helper under libexec 2026-09-16 16:15:32 +04:00
interface feat: implement batch method 2026-09-16 19:37:37 +04:00
scripts fix: rework backend generator 2026-09-18 19:20:46 +04:00
src fix: skip CheckProvides when no enabled system sources exist 2026-09-25 17:09:34 +04:00
.gitignore chore: remove .* pattern from .gitignore 2026-03-30 14:39:41 +04:00
LICENSE init: initial commit 2026-02-11 21:15:38 +04:00
pyproject.toml 0.1.6-alt1 2026-09-25 17:09:34 +04:00
README.md build: package apt-alt-source with backend 2026-09-24 18:39:08 +04:00

alterator-backend-source

License Language Platform

alterator-backend-source is Python module (CLI and API) for work with software sources based on Alterator Entry Source. The solution based on [apt-source], alterator-module-executor and alterator-backend-systeminfo.

Project Architecture

The project is strictly divided into two modules:

  • api → domain logic and filesystem abstraction
  • cli → user-facing command-line interface

High-level Dependency Diagram

          ┌────────────┐
          │    cli     │
          │ (commands) │
          └─────┬──────┘
                │
                ▼
        ┌───────────────────┐
        │ api.FrontDatabase │ ← central API object
        └─────┬─────┬───────┘
              │     │
              │     └──────────┐
              │                ▼
              │       ┌───────────────────┐
              │       │  mapper.to_list   │
              │       └──────┬────────────┘
              │              │
              ▼              ▼
    ┌────────────────────────────┐
    │ apt_source.api.Database    │
    └────────────────────────────┘

How It Works (Data Flow)

The module's operation centers on translating data from the Alterator Entry Source format into files that apt can understand.

Core Components

  • api.FrontDatabase: facade class that provides high-level methods for working with sources. It composes apt_source.api.Database.
  • api.media.MediaManager: lists, probes, registers, mounts, and verifies repositories on removable media.
  • api.mount.MountController: creates read-only mounts and keeps them active while an operation holds a lease.
  • api.mapper.to_list: inner translation module responsible for converting Alterator Entry Source structures into apt_source.api.Table entries.

Repository Locations

A source descriptor can contain remote mirrors, a local repository, or both. [mirrors] describes network locations. [local] describes a local repository:

  • with external absent or set to false, path is a fixed local path,
  • with external = true, path is relative to the media root.

External media is therefore a local repository location whose availability can change, not a third source type. Status reports whether the registered medium is currently available.

External media can provide an Alterator Entry Source descriptor under .disk/alterator/*.source. A regular indexed APT-RPM repository can also be used without a descriptor. In both cases ProbeSource returns a normalized Alterator Entry Source descriptor, so frontends can handle the source like any other source descriptor.

If a .source descriptor is present on the external media, it must resolve to exact indexed repository entries. If the descriptor is absent, the backend derives one from base/pkglist.<component> and RPMS.<component>:

[local]
external = true
path = "repo"

[[local.entries]]
suite = "ALTLinux"
component = "main"
arch = ["x86_64", "noarch"]

Registered external descriptors contain non-empty local.entries. External sources never fall back to rpm-dir and never store a desktop or runtime mount path. Frontends do not pass local.entries to Apply.

Removable-media lifecycle

Registration records enough information to locate the device again and to verify that it contains the same repository. Persistent records and descriptors are stored in /var/lib/alterator-backend-source/media. Temporary mounts are created below /run/alterator/source-media with ro,nodev,nosuid,noexec. The backend-owned registry format is documented for maintainers in External-media registry.

APT addresses packages through alt-source://<registered-name>/<repository-relative-path>. The separately packaged The apt-alt-source APT method is built from apt-alt-source in this repository and remains a separate RPM produced by the same SRPM as the backend. It runs /usr/libexec/alterator-backend-source/alterator-source-media-helper when it needs a file. The helper asks the backend to mount and verify the registered media, returns the local mount path to APT, and records which APT process is using it. The mount is removed when no APT process is using it. After reboot, the next APT request mounts the media again.

Add and Apply never fetch APT indexes: they only modify sources lists and local state, so slow mirrors or apt errors can never make sources-list modification fail. Index updates are a separate operation owned by the frontend through the apt1 interface. A normal apt-get update refreshes a connected medium. If it is absent, APT keeps the existing indexes and prints a warning.

sources.list File Generation

Based on the generated table, a file is created in APT's effective sources.list.d directory as resolved from the runtime APT configuration:

  • For each Alterator source: {name}_autogenerated.list

The file contains all remote mirrors and the optional local repository. Active entries are uncommented; inactive entries remain present as commented lines.

Entry generation logic:

Component Remote Mirrors Fixed Local Repository Fixed Local with local.entries External Media
Type rpm rpm-dir with file:// URI exact rpm entries with file:// URI exact rpm entries with alt-source:// URI

Initial State: Disabled, unsigned. Architectures: Filtered based on current system architecture (via alterator-backend-systeminfo)

State Management

Information about the repository's active state is stored in the [state.active] section of the dynamical Alterator Entry Source itself. This should ensure synchronization runtime object's internal state according to sources.list files (runtime completeness).

Example of the dynamically generated section:

[state.active]
gpg = true
mirrors = ["ftp.altlinux.org"]
protocols = ["https"]
components = { classic = [ "x86_64", "noarch", "x86_64-i586" ], debuginfo = [ "x86_64" ] }

Field Semantics:

  • gpg: This field is descriptive, not imperative. It automatically becomes true if and only if every active entry of this source in sources.list.d is signed with the key specified in gpg.name. If any .list entry is unsigned or signed with a different key, this field will be false. This provides a quick way to verify source integrity and validity.
  • mirrors, protocols, components: Reflect the currently active set of mirrors, protocols, and components that are actually present in the sources.list files.

Synchronization Workflow

  1. Initialization: api.FrontDatabase receives an Alterator Entry Source object. The mapper analyzes it and creates an internal representation (apt_source.api.Table).
  2. File Generation: When the API changes the state of sources.list records, the [state.active] table is updated accordingly.
  3. Invariant Maintenance: The invariant gpg == (all entries signed by gpg.name) is maintained automatically during all operations.
  4. Saving Changes: All changes made in apt_source.api.Database using API are saved in file system. That is, sources.list files are updated, but not static .source files.

D-Bus Integration

The scripts/ folder contains scripts for generating .backend configuration files for alterator-module-executor from /usr/share/alterator/sources/ files. This is necessary for registering D-Bus objects that implement the interface described here. Each D-Bus object is a single registered source.

External media management is exposed by org.altlinux.alterator.sources_lists1 on /org/altlinux/alterator/global. Its full interface is described here.

Method D-Bus signature Description
Get → as Return active raw APT source entries.
ListRemovableMedia → s Return a TOML document describing present removable drives and media without mounting them.
SourceInfos → as Return every source descriptor as TOML, each with an embedded runtime.status subtable.
ProbeSource s → s, as, i Check one medium or disk image and return its descriptor as TOML, or an empty string.
Add s → as, i Register the source on the given medium or disk image. The new source remains disabled.
Remove s → i Disable and unregister an external source by its registered source name.

A zero response means success. ProbeSource and Add return their diagnostics in stderr_strings: a non-zero response means failure and stderr_strings carries the error message. Get, ListRemovableMedia, and SourceInfos have no response code, so their failures are reported as D-Bus errors. Remove accepts either the registered source name or its 64-character source ID. When the registry record is already gone but leftover runtime state remains (descriptor, generated APT table), Remove cleans it up and succeeds; it fails only when the name references nothing at all.

Removable media are addressed by stable identifiers, never by device paths: partuuid:<PARTUUID> for a partition, uuid:<FS-UUID> for a filesystem, or the absolute path of a disk image file (for example .iso or .img). ListRemovableMedia returns a TOML document with one [[devices]] table per drive and one [[media]] table per registrable filesystem:

[[devices]]
device = "id:usb-VendorCo_ProductCode_8236421115004725668-0:0" # udev by-id identity; "path:/dev/sr0" as fallback
path = "/dev/sda"
model = "VendorCo ProductCode"
drive = "usb"                        # lsblk TRAN name: usb, sata, nvme, mmc, ... ; "optical" for optical drives, "unknown" when undetected
size = 31457280000                   # whole-device size in bytes

[[media]]
media_id = "partuuid:195ce96c-..."   # the stable identifier for ProbeSource and Add
device = "id:usb-VendorCo_ProductCode_8236421115004725668-0:0"
path = "/dev/sda1"
fs_type = "btrfs"
label = ""
size = 1755316224                    # partition/medium size in bytes
mountpoint = "/run/media/user/USB"   # present only while the medium is mounted
fs_size = 31455280128                # present only when lsblk reports filesystem usage
fs_used = 2100112332

[media.registration]                 # present only when the medium is already registered
added = true
sources = ["usb-demo"]               # registered source names, matching the descriptors' name field

The mountpoint, fs_size, and fs_used fields are omitted when unknown, so an unmounted medium shows its filesystem type, label, and total size, but not usage. The drive field carries the lsblk TRAN transport name, except that optical drives report optical and undetected transports report unknown. Media already carrying registered source(s) include a [media.registration] subtable with added = true and their source_ids in sources, so frontends can mark them as already in use. The sources values are the registered source names — the name field of the source descriptors — so frontends can map them to the actual source objects. Internal fixed partitions, swap devices, and drives without any registrable filesystem are not listed. The listing never mounts anything. ProbeSource mounts the selected medium read-only when it is not already mounted, verifies that it carries a supported repository, and unmounts it again. An empty string means the medium carries no valid repository. Invalid media — for example a filesystem with more than one source descriptor — fail with a non-zero response and a user-readable message in stderr_strings; an absent medium or missing image file is a D-Bus error.

Add takes the same media identifier, re-inspects the medium at that moment, and registers the source found on it. Adding already-registered media fails with an error in stderr_strings; the existing registration is kept. It creates the org.altlinux.alterator.source1 object and rolls the registration back if object generation fails. It does not enable the source or return the new object path. Re-enumerate the source objects after Add or Remove with:

busctl call \
    org.altlinux.alterator \
    /org/altlinux/alterator \
    org.altlinux.alterator.manager \
    GetObjects s \
    org.altlinux.alterator.source1

Use the object paths returned by GetObjects, do not build one from the descriptor. Registration can add a numeric suffix to the source name when that name is already used. If a successful Add is not yet reflected by GetObjects, retry while alterator-module-executor reloads its objects.

For every source object, Info returns its Alterator Entry Source descriptor and Status returns runtime state as TOML. External sources include the following additional status:

[media]
available = true

available reports whether the registered device can be found. APT mounts available media on demand. The source remains registered and its APT entries are not disabled when media is unavailable. The media table is absent for non-external sources.

SourceInfos on sources_lists1 returns all of this in one round-trip: an array with one TOML document per source — the same descriptor Info returns, prefixed with the source's dbus_path (the source1 object path) and extended with a runtime.status subtable holding the same report as Status (including media for external sources). Elements are ordered by source name; an empty array means no sources are installed. The injections live only in SourceInfos output: Info itself returns the clean stored descriptor.

Object node names derive from the descriptor's name field: spaces and dashes become underscores; a name carrying any other character that cannot appear in a D-Bus path gets no object at all (the backend generator skips it with a warning, and SourceInfos reports it as absent). Name collisions are disambiguated by appending underscores until unique. The rules live in one place (alterator_backend_source.api.source_objects) shared by the backend generator and SourceInfos, so reported paths always match the generated objects.

To activate an external source, call Apply with alt-source://<registered-name>, selected components, and the signature key name. The medium must have been registered with Add beforehand and be present; otherwise Apply fails. The backend combines that registration with the descriptor's relative path and exact entries. Apply has the following signature:

Apply(s mirror, as components, as multiarch_additions, s sign_key_name)
    → as stdout_strings, as stderr_strings, i response

multiarch_additions is ignored for external sources generated from local.entries, because their entries are enabled as-is instead of being reconstructed.

An empty mirror or empty component list disables the source. A frontend should use CheckApply to preview changes and may use CheckProvides before Apply to detect package conflicts. A zero response means success, on failure, show stderr_strings, or stdout_strings when stderr is empty. ListRemovableMedia never mounts anything. ProbeSource may temporarily mount removable media read-only, but neither operation registers a source or changes APT configuration and neither requires PolicyKit authentication. Add, Remove, and Apply are authenticated write operations.

Recommended frontend flow:

  1. Call ListRemovableMedia to enumerate present removable drives and media, and parse the TOML document.
  2. Call ProbeSource for each media_id and parse the TOML descriptor. An empty string means the medium carries no repository; a D-Bus error means the medium was removed — list the media again.
  3. Let the user select a medium, then call Add with its identifier.
  4. Enumerate org.altlinux.alterator.source1 objects again and load their Info and Status data.
  5. Let the user configure the registered source, preview with CheckApply, and enable it with Apply, passing alt-source://<registered-name> as the mirror.
  6. Reload Status to update available. Refresh indexes via the apt1 interface; a normal apt-get update refreshes the indexes of a connected medium.
  7. Pass the descriptor's registered name field to Remove, then enumerate source objects again.

Installation

sudo apt-get install alterator-backend-source

Usage

Get inactive object description

$ busctl call \
          org.altlinux.alterator \
          /org/altlinux/alterator/source_alt_sisyphus \
          org.altlinux.alterator.source1 \
          Info
s "type = \"Source\"\nname = \"alt-sisyphus\"\n\ncategory = \"Alt\"\n\ndisplay_name.ru = \"\320\220\320\273\321\214\321\202 \320\233\320\270\320\275\321\203\320\272\321\201: \320\241\320\270\320\267\320\270\321\204\"\ndisplay_name.en = \"ALT Linux: Sisyphus\"\ncomment.ru = \"\320\240\320\265\320\263\321\203\320\273\321\217\321\200\320\275\320\260\321\217 \320\262\320\265\321\202\320\272\320\260\"\ncomment.en = \"Regular branch\"\n\nbranch.provides = \"sisyphus\"\n\ngpg.name = \"alt\"\ngpg.vendor = \"ALT Linux incominger <incominger@altlinux.org>\"\ngpg.fingerprint = \"49B01E70C64124E6742EAE7195C584D5AE4AE412\"\n\n\n[local]\npath = \"/run/media/sisyphus\"\n\n[mirrors.\"ftp.altlinux.org\"]\n\"/pub/distributions/ALTLinux/Sisyphus/\" = [\"http\", \"https\", \"ftp\"]\n\"/ALTLinux/Sisyphus/\" = [\"rsync\"]\n\n[mirrors.\"mirror.yandex.ru\"]\n\"/altlinux/Sisyphus\" = [ \"https\" ]\n\n[multiarch.\"x86_64-i586\"]\nbase_arch = \"x86_64\"\ndisplay_name.en = \"32-bit support\"\ncomment.en = \"...\"\n\n\n[components.classic]\ndisplay_name.en = \"classic\"\ncomment.en = \"classic\"\nhas_noarch = true\narch = [\n    \"x86_64\",\n    \"i586\",\n    \"aarch64\"\n]\nmultiarch = [ \"x86_64-i586\" ]\n\n[components.debuginfo]\ndisplay_name.en = \"debuginfo\"\ncomment.en = \"debuginfo\"\nrequires = \"classic\"\narch = [\"x86_64\"]\n\n[components.gostcrypto]\ndisplay_name.en = \"gostcrypto\"\ncomment.en = \"gostcrypto\"\nrequires = \"classic\"\narch = [\n    \"x86_64\",\n    \"i586\",\n    \"aarch64\"\n]\n\n[state.active]\nmirrors = []\nprotocols = []\n\n[state.active.components]\n"

Checking a mirror activation result (test running)

$ busctl call \
          org.altlinux.alterator \
          /org/altlinux/alterator/source_alt_sisyphus \
          org.altlinux.alterator.source1 \
          CheckApply "sasass" \
          "https://ftp.altlinux.org" \
          2 "classic" "debuginfo" \
          1 "x86_64-i586" \
          "alt"
asas 4 "rpm [alt] https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ x86_64 classic" "rpm [alt] https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ noarch classic" "rpm [alt] https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ x86_64-i586 classic" "rpm [alt] https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ x86_64 debuginfo" 0

Activate mirror with given components

$ busctl call \
          org.altlinux.alterator \
          /org/altlinux/alterator/source_alt_sisyphus \
          org.altlinux.alterator.source1 \
          Apply "sasass" \
          "https://ftp.altlinux.org" \
          2 "classic" "debuginfo" \
          1 "x86_64-i586" \
          "alt"
asasi 0 0 0

Get object description after activation

$ busctl call \
          org.altlinux.alterator \
          /org/altlinux/alterator/source_alt_sisyphus \
          org.altlinux.alterator.source1 \
          Info
s "type = \"Source\"\nname = \"alt-sisyphus\"\n\ncategory = \"Alt\"\n\ndisplay_name.ru = \"\320\220\320\273\321\214\321\202 \320\233\320\270\320\275\321\203\320\272\321\201: \320\241\320\270\320\267\320\270\321\204\"\ndisplay_name.en = \"ALT Linux: Sisyphus\"\ncomment.ru = \"\320\240\320\265\320\263\321\203\320\273\321\217\321\200\320\275\320\260\321\217 \320\262\320\265\321\202\320\272\320\260\"\ncomment.en = \"Regular branch\"\n\nbranch.provides = \"sisyphus\"\n\ngpg.name = \"alt\"\ngpg.vendor = \"ALT Linux incominger <incominger@altlinux.org>\"\ngpg.fingerprint = \"49B01E70C64124E6742EAE7195C584D5AE4AE412\"\n\n\n[local]\npath = \"/run/media/sisyphus\"\n\n[mirrors.\"ftp.altlinux.org\"]\n\"/pub/distributions/ALTLinux/Sisyphus/\" = [\"http\", \"https\", \"ftp\"]\n\"/ALTLinux/Sisyphus/\" = [\"rsync\"]\n\n[mirrors.\"mirror.yandex.ru\"]\n\"/altlinux/Sisyphus\" = [ \"https\" ]\n\n[multiarch.\"x86_64-i586\"]\nbase_arch = \"x86_64\"\ndisplay_name.en = \"32-bit support\"\ncomment.en = \"...\"\n\n\n[components.classic]\ndisplay_name.en = \"classic\"\ncomment.en = \"classic\"\nhas_noarch = true\narch = [\n    \"x86_64\",\n    \"i586\",\n    \"aarch64\"\n]\nmultiarch = [ \"x86_64-i586\" ]\n\n[components.debuginfo]\ndisplay_name.en = \"debuginfo\"\ncomment.en = \"debuginfo\"\nrequires = \"classic\"\narch = [\"x86_64\"]\n\n[components.gostcrypto]\ndisplay_name.en = \"gostcrypto\"\ncomment.en = \"gostcrypto\"\nrequires = \"classic\"\narch = [\n    \"x86_64\",\n    \"i586\",\n    \"aarch64\"\n]\n\n[state.active]\nmirrors = [\"ftp.altlinux.org\"]\nprotocols = [\"https\"]\ngpg = true\n\n[state.active.components]\nclassic = [\"x86_64\", \"noarch\", \"x86_64-i586\"]\ndebuginfo = [\"x86_64\"]\n"

Checking a source disabling

$ busctl call \
          org.altlinux.alterator \
          /org/altlinux/alterator/source_alt_sisyphus \
          org.altlinux.alterator.source1 \
          CheckApply "sasass" \
          "" \
          0 \
          0 \
          ""
asas 0 4 "rpm https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ x86_64 classic" "rpm https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ noarch classic" "rpm https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ x86_64-i586 classic" "rpm https://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus/ x86_64 debuginfo"