Automatic Updates

Automatic Updates

There have been a few evolutions regarding the ways we deal with automatic updates, with many different solutions being explored in Terra. Despite how much our automatic package update system evolves, we still make our best effort to document the latest way to set up automatic updates in Terra. (Seriously!)


AndaX (Andaman Rhai scripts)

Yes, I know it sounds unusual, but .rhai scripts are used to update packages in Terra. You can check out the Rhai (opens in a new tab) documentations. Put your Rhai script in update.rhai next to the corresponding anda.hcl file. Andaman will detect it automatically.

// An example of AndaX update script.
let ver = gh("FyraLabs/subatomic"); // gets latest version from GitHub
rpm.version(ver); // updates the version in the spec file using `ver`
you need SEMICOLONS! (;)

Updates are triggered every 30 minutes using the command anda update -vv. However, nightly packages are updated every 24 hours using anda update -vv --filters nightly=1. Their anda.hcl files should look something like this:

project pkg {
	rpm {
		spec = "pkg.spec"
	labels {
		nightly = "1"

The best way to get started is to just search for other Rhai scripts in Terra. They are usually enough to do most of the tasks. If not, continue reading below.

Functions and Modules

In most cases, rpm.version(gh("...")); as a one-liner is enough to make it work™. However, you may also use the following functions:

let html_content = get("https://fyralabs.com");
let latest_ver = gh("group/repo_name");
let latest_tag = gh_tag("group/repo_name"); // for gh repos with tags but not releases
let latest_commit = gh_commit("group/repo_name");
// the above gh functions are also available for gitlab, just replace `gh` with `gitlab`, and replace group/repo_name with repoid
// you can also specify the custom domain as the first argument:
print(gitlab_commit("gitlab.gnome.org", "1551", "main")); // special: you need to specify branch for gitlab commits
let raw_readme_content = gh_rawfile("group/repo_name", "main", "README.md"); // main is the branch
let pypi_latest = pypi("example");
let max_stable_version = crates("anda");
let max_version = crates_max("anda");
let newest_version = crates_newest("anda");
let npm_latest = npm("example");
let env_val = env("KEY");
// === Requests ===
let req = new_req("https://fyralabs.com");
req.head("Content-Type", "application/json");
let html_result = req.get();
// === JSON Operations ===
let obj = json("{\"a\":1}");
print(obj.a); // 1
print(obj["a"]); // 1
let arr = json_arr("[1,2,3]");
print(arr[1]); // 2
let str = to_json(obj);
let back_to_map_obj = from_json(str);
// === Regex Operations ===
let found = find("(\\d{3})", "abc1c2345", 1); // gets regex group 1
print(found); // 234
let substituted = sub("(\\s+)", " ", "I   hate spaces      !!!");
print(substituted); // I hate spaces !!!
// === IO and Commands ===
// `sh(command, opts)` is a very complicated function:
// ## `command`
// you can supply `command` as a string or an array of strings
// if `command` is a string, `sh -c <command>` will be used
// otherwise it'll just call `command[0]` with the arguments `command[1..]`
// ## `opts`
// `opts` has to be an `rhai::Map`. inside the map, you may have the following key-value pairs: (all are optional)
// - `stdout`: possible values are "inherit" (default), "null" and "piped".
// - `stderr`: possible values are "inherit" (default), "null" and "piped".
// - `cwd`: path of current working directory as a string
// ## Errors
// If you didn't use the function correctly, you will receive the following error:
// #{ 
//   "outcome": "fatal",
//   "ctx": #{
//     "kind": "bad_param_type", // or: "empty_cmd_arr" | "bad_stdio_opt" | "bad_stdio_type"
//     "expect": "…", // expected type
//     "found": "…", // actual type received
//   },
// }
// If you did use the function correctly but there are some problems running the command:
// (Note: this doesn't include cases where the command itself returns a non-zero exit code)
// #{ 
//   "outcome": "failure",
//   "ctx": #{
//     "error": "…", // error message from the rust lib / operating system / idk
//   },
// }
// Otherwise, the function will return this:
// #{ 
//   "outcome": "success",
//   "ctx": #{
//     "stdout": "…",
//     "stderr": "…",
//     "rc": 0, // or other integers (status code)
//   },
// }
// Here are some examples:
print(sh("echo hai", #{}).ctx.rc); // most likely 0, and prints out stdout to console in real time, so you will see `hai` then `0`
print(sh(["echo", "hai"], #{ "stdout": "piped" }).ctx.stdout); // hai
print(sh("ls -alh", #{ "cwd": "anda/tools/umstellar/" })); // prints out the command output in real time, then the rhai map: `#{ "outcome": "success", "ctx": … }`
for x in ls("/") {
    if x == "bin" {
        print("I found the `/bin` folder!");
let foo = "bar";
foo.write("bar.txt"); // this might be counterintuitive but the string "bar" is written into the file "bar.txt"
obj.write("tmp.json"); // object (maps) are turned into JSON automatically
// === Templates ===
print(template(#{a: "value", b: "hai"}, "%{a} and %{b} @{random_rpm_spec_macro}"));
// value and hai %{random_rpm_spec_macro}
// Note that @{} are used to represent rpm macros (they unfortunate conflict)
// `@{` are converted to `%{` automatically.
print(template_file(#{...}, "path/to/a.hcl"));
// === RPMBuild ===
// use these functions with sh() :3
anda::rpmbuild::cmd_srpm("test.spec", "sources"); // ["mock", "--buildsrpm", "--spec", "test.spec", "--sources", "sources", ...];
anda::rpmbuild::cmd_rpm("test.spec", "sources"); // ["mock", "--rebuild", ...];
// === Anda-Config ===
let cfg_map_obj = anda::cfg::load("path/to/anda.hcl");

Special Variables

// === RPM Operations ===
// note that the `rpm` object is only available in `anda update`, but not `anda run`.
rpm.version(latest_ver); // note that version() resets the release back to 1 automatically
// Source0: https://github.com/FyraLabs/anda/archive/refs/tags/0.1.17.tar.gz
rpm.source(0, "https://github.com/FyraLabs/anda/archive/refs/tags/0.1.17.tar.gz");
// %define abc hai bai
rpm.define("abc", "hai bai");
// %global def give rabonuko a headpat!
rpm.global("def", "give rabonuko a headpat!");
rpm.release(); // resets release to 1: `Release: 1%?dist`
rpm.release(3); // Release: 3%?dist
let spec_content = rpm.f;
rpm.f = new_spec_content;
// === Labels and Constants ===
const USER_AGENT = "AndaX";
const IS_WIN32 = false; // `cfg!(windows)` in rust
const ANDAX_VER = "0.1.3"; // `env!("CARGO_PKG_VERSION")` in rust


HeadPAT (opens in a new tab) is a work-in-progress package version tracker. It is (going to be) a self-hosted microservice.

Planned usage

Just like update.rhai scripts, a .pat file will be used with the following format:

ver gh FyraLabs/anda

That's it.

However, we are exploring the posibility of adding Lua into the mix to make things more extensible:


Then HeadPAT will look for pat.lua in the same directory. However, it is uncertain if the above will be the actual format as HeadPAT is still under heavy (but slow) development.



Avoid using the GitHub API for fetching the latest versions. You may use GitLab, crates.io, Pypi, etc. instead. This is because GitHub has an API rate-limit.

Only update nightly packages when the list of filters contains nightly:

if filters.contains("nightly") {
Avoid running code unrelated to updating/bumping packages.