- Python 88.3%
- C++ 6.1%
- Shell 4.6%
- CMake 1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
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
- Reworked external sources |
||
| .forgejo/workflows | ||
| .gear | ||
| .githooks | ||
| apt-alt-source | ||
| backend | ||
| docs | ||
| helpers | ||
| interface | ||
| scripts | ||
| src | ||
| .gitignore | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
alterator-backend-source
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 abstractioncli→ 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 composesapt_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 intoapt_source.api.Tableentries.
Repository Locations
A source descriptor can contain remote mirrors, a local repository, or both. [mirrors] describes network
locations. [local] describes a local repository:
- with
externalabsent or set tofalse,pathis a fixed local path, - with
external = true,pathis 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 becomestrueif and only if every active entry of this source insources.list.dis signed with the key specified ingpg.name. If any.listentry is unsigned or signed with a different key, this field will befalse. 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
- Initialization:
api.FrontDatabasereceives an Alterator Entry Source object. Themapperanalyzes it and creates an internal representation (apt_source.api.Table). - File Generation: When the API changes the state of sources.list records, the
[state.active]table is updated accordingly. - Invariant Maintenance: The invariant
gpg == (all entries signed by gpg.name)is maintained automatically during all operations. - Saving Changes: All changes made in
apt_source.api.Databaseusing API are saved in file system. That is,sources.listfiles are updated, but not static.sourcefiles.
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:
- Call
ListRemovableMediato enumerate present removable drives and media, and parse the TOML document. - Call
ProbeSourcefor eachmedia_idand 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. - Let the user select a medium, then call
Addwith its identifier. - Enumerate
org.altlinux.alterator.source1objects again and load theirInfoandStatusdata. - Let the user configure the registered source, preview with
CheckApply, and enable it withApply, passingalt-source://<registered-name>as the mirror. - Reload
Statusto updateavailable. Refresh indexes via the apt1 interface; a normalapt-get updaterefreshes the indexes of a connected medium. - Pass the descriptor's registered
namefield toRemove, 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"