Request for Comments: Transitioning to APK

RFC — Turris OS APK

This document proposes options in transition of Turris OS package management Updater infrastructure to Alpine Package Keeper (hereinafter APK).

Rationale

As OpenWrt transitioned to APK from opkg, we should move to it as well. We currently have a very extensive Updater project, but as APK is more advanced (and reliable) package manager than opkg is, many of its features can be left to from big parts or fully for APK.

Document Notes

This document uses examples of packages. It is written as a Python script for awesome Chimera Linux's cbuild. Most of the code will be left out for brevity.

I also include example package definitions for cports and also a pre-built repository.

Specific APK features

APK does not have some features common for other package managers, like weak dependencies or groups. But it still does include some intriguing features, like:

Virtual Packages

APK can create virtual packages on the fly, allowing specifying their dependencies1.

We can create one just like this:

> apk add --virtual my-devtools rust clang mold

Uninstalling package my-devtools will, unless rust, clang and mold are requested by something else, trigger their removal.

These can be used, for example, to ad-hoc group packages together or as, as you can see later, as nodes for world solver. So packages can have e.g. install_if constraints on virtual packages or even their negations.

Install If

Although APK does not support weak dependencies or recommendations, it does support something like it in reverse. A package can specify install_if, which, in case of fulfilling these constraints will install the package.

pkgname = "sway"
# … removed for brevity …

@subpackage("sway-backgrounds")
def _(self):
	self.subdesc = "backgrounds"
	self.install_if = [self.parent]
	return ["usr/share/backgrounds"]

Example from https://github.com/chimera-linux/cports/blob/5fe0b75dfe9ca7741204c8fc4995f43b26f10f2f/main/sway/template.py

Negations

We can also specify a negative constraint. APK's solver is pretty simple, with no support for priorities, so negation of a dependency will create a conflict.

If we take a look on the install_if example for sway-backgrounds, it's condition is, that it gets installed when sway is installed, but it is not (in our example, at least) a dependency of anything, therefore we can specify a negative dependency !sway-backgrounds which will prevent it from being installed.

Package Lists

One of Updater's features is package lists. Currently these are configured using UCI configuration document.

These can be (likely) implemented using just packages which provide nothing, just specify dependencies for the APK solver.

pkgname     = "turris-pkglist-tor"
pkgver      = "2026.03.08"
pkgrel      = 0
build_style = "meta"
depends     = ["tor", "tor-hs"]
pkgdesc     = "The Onion Router"
license     = "custom:none"
url         = "https://turris.cz"

Updater then can just add turris-pkglist-tor to the world file.

Device-dependent packages

This is a job for Turris OS updater, reacting on available devices and managing world file accordingly.

if Devices.is_class_available DVB then
	APK.add "turris-device-dvb"
else
	APK.del "turris-device-dvb"
end

Executing of the package turris-device-dvb can vary. One option would be creating such package with dependency on TVH.

pkgname = "turris-device-dvb"
depends = ["tvheadend", "turris-webapps-tvheadend"]

This introduces an issue, when user does not want to have tvheadend installed despite having DVB tuner, for example, if they wish to use different software. For such cases alternative solution could be:

pkgname    = "tvheadend"
install_if = ["turris-device-dvb=0.1.0-r0"]
# …
pkgname    = "turris-webapps-tvheadend"
install_if = ["turris-device-dvb=0.1.0-r0", "tvheadend"]
# …

In this case, user could just specifcy !tvheadend constraint in the world file. As we build packages ourselves anyways, adding install_if to packages is a non-issue.

Priorities

APK does not have priority on constraints in the world file. The only priority available is for providers, serving as tie-breaker when two packages provide the same virtual package and the constraints can be fulfilled by both.

So our current solution of having basically our own implementation of opkg with our own solver would require keeping doing exactly this but for APK. As APK is more capable than opkg, this, in my opinion, seems to be a case of doing 80% of the work for solving 5% of edge cases.

Providers

Another example of updaters feature which can be done only using APK is current (as of 2026-03-08) case of Knot Resolver.

The default Knot Resolver is v5 but v6 is available at user's request.

If we include Knot Resolver v5 as, let's say part of a base system metapackage, installing v6 would be basically impossible.

Adding Knot Resolver v6 to the world file would then require the updater to have higher reasoning over the world file, which introduces additional complexity to the updater itself and the rest of the system.

This is a good use case for provider priorities, as we could define packages in this way:

pkgname           = "knot-resolver-v5"
provides          = ["turris-meta-dns-resolver"]
provider_priority = 20
# …
pkgname           = "knot-resolver-v6"
provides          = ["turris-meta-dns-resolver"]
provider_priority = 10
# …
pkgname = "turris-meta-base"
depends = ["virtual:turris-meta-dns-resolver!knot-resolver-v5", ]
# …

Drawbacks

  • Transitioning from updater scripts to APK may require additional work to reinterpret them in APK context
  • Unless we reimplement basically the package manager (or at least the solver part) ourselves, priorities are not much a thing to do
  • Would require extensive work on updater and probably package building infrastructure, but as opkg is getting officially dead, it is a thing that would have to be done anyways (unless we want to continue supporting opkg completely on our own which may get more difficult and complex as time goes and opkg is kinda crappy…)

Further reads

Attachments

  1. Not completely sure about how it works internally, maybe programmatic API allows more?