← All Modules

assay.pkg

Package manager framework — catalog loading, target abstractions, plan generation, and version comparison. Introduced in v0.15.5.

local pkg = require("assay.pkg")

Layering model

catalog.load and templates.load both accept an ordered array of directory paths. Entries from later layers overwrite entries with the same id (full-entry override, no field-merge):

Layer index_origin value
1"built-in"
2"plugin:<dirname>"
3+"operator:<filename>"

Strict-override: if a later-layer entry fails validation, any earlier valid entry with the same id is cleared (the caller sees a missing entry + a validation error, never a silent fallback).

_origin is a synthetic field — not part of the on-disk TOML schema. Downstream serializers (API responses, plan writers) must strip it before output.


pkg.catalog

Catalog TOML shape:

[package]
id = "curl"
display_name = "cURL"
methods = ["apt"] # ordered; first method is preferred at plan time

[package.apt]
source_list = "deb https://pkgs.example.com/debian stable main"
package_name = "curl"

[package.binary] # optional; enables binary install fallback
release_api = "https://api.github.com/repos/example/curl/releases/latest"
asset_pattern = "curl-{ver}-linux-{arch}.tar.gz"
sha256_source = "checksums" # one of: "asset" (sibling .sha256), "checksums" (sha256sums.txt)
install_path = "/usr/local/bin/curl"
mode = "0755"

pkg.templates

Templates group catalog ids into named sets.

Template TOML shape:

[template]
id = "base"
display_name = "Base tooling"
packages = ["curl", "jq", "git"]

pkg.target

Target:exec(cmd, opts?)

Run a command on the target. Returns {status, stdout, stderr, timed_out} (same shape as shell.exec).

The safe cross-target opts subset is:

KeyTypeDescription
timeoutnumberSeconds; 0 means no timeout
envtable{[name] = value} extra environment variables
stdinstring | bytesBytes piped to the inner process via systemd-run --pipe / shell.exec stdin

shell.exec-only opts (cwd) are silently dropped on machine targets — there's no working-directory equivalent for a transient nspawn unit. Passing them is out-of-contract.

local t = pkg.target.host()
local r = t:exec("whoami", { timeout = 5 })
print(r.stdout)   -- "root\n"

local m = pkg.target.machine("mycontainer")
local r2 = m:exec("dpkg-query -W curl", { timeout = 10 })
if r2.timed_out then error("exec timed out") end

pkg.version

Simple SemVer-style comparator (integers only; non-numeric trailing components silently dropped). Strips a leading "v" before parsing.

pkg.version.cmp("1.2.3", "1.2.4")   -- -1
pkg.version.cmp("v2.0", "2.0.0")    -- 0  (leading "v" stripped; zero-padded)

pkg.plan

local ops = pkg.plan(target_id, desired_set, actual, catalog_entries)

Pure function — no I/O, no side effects. Builds a deterministic operation array to converge actual toward desired_set.

Returns an array of operation tables. Each operation has at minimum {op, id, method}:

opAdditional fieldsMeaning
"install"target_versionPackage not installed
"upgrade"from, toInstalled but version < available
"skip"reasonNo catalog entry found for id

pkg.plan never removes — packages in actual but not in desired_set are ignored.

local catalog = pkg.catalog.load({ "/opt/myapp/catalog", "/etc/myapp/packages.d" })
local ops = pkg.plan("host", { "curl", "jq" }, {
  curl = { installed = true,  version = "7.88.0", available = "8.0.0" },
  jq   = { installed = false },
}, catalog.entries)
-- ops = [
--   { op="upgrade", id="curl", method="apt", from="7.88.0", to="8.0.0" },
--   { op="install", id="jq",   method="apt", target_version=nil },
-- ]

Caller responsibilities

These pieces stay outside the framework because they're product-specific: audit-event emission, distributed locking, per-run log rotation, and the desired-state file. Callers compose the building blocks (pkg.catalog, pkg.templates, pkg.target, pkg.version, pkg.method.*, pkg.release, pkg.plan, pkg.query_all, pkg.apply) into their own reconcile loop.