Differ is a REST-API for retrieving package changes between images on immutable Linux distributions. It was designed for allowing the user to visualize changes in package versions in Vanilla OS, but this tool can be used by any distribution without any changes to the code.
In order to setup Differ from source, you need Go 1.22 and a Sqlite manager (in this section we use the sqlite3
command line tool).
First, you need to create a database to store all the images and releases Differ will manage, as well as auth information (see read-only subsection below).
Using the sqlite3
tool, run the following commands:
$ sqlite3 /path/to/database.db
sqlite> create table auth("ID" INTEGER, name, pass TEXT, PRIMARY KEY("ID"));
sqlite> insert into auth values(1, 'admin_user', 'admin_password'); # Replace user and password with something secure
If everything was set up correctly, the auth table should look like this:
sqlite> select * from auth;
1|admin_user|admin_password
Now, all you need to do is use the provided Makefile. Once the binary is built, run it by passing the database path as argument.
$ make
$ ./differ path/to/database.db
When setting up Differ in production, you must run export GIN_MODE=release
before running the binary.
Alternatively, you can use the provided container image. In this case, all you need to do is pull it using either Docker or Podman, create a new container and pass the database path as argument. Differ will handle the database setup by using the contents of admin_user
and admin_password
environment variables.
$ podman pull ghcr.io/vanilla-os/differ:main
$ podman run --env 'admin_user=user' --env 'admin_password=password' differ path/to/database.db
Simple check to see if the server is running correctly.
Method: GET
Endpoint: http://[base_url]/status
Parameters: None
Returns:
200 OK
on success.
{
"status": "ok"
}
Images are, as the name implies, image types shipped by the distribution. For example, a distro can ship both GNOME and a KDE versions as separate images, which would can be managed with the endpoints below:
Creates a new image in the dabatase. Every release (see subsection below) is attached to an image.
Method: POST
Endpoint: http://[base_url]/images/new
Parameters:
- Name: Image name
- URL: Where the image is hosted or its repository. For information purposes only.
{
"name": "pico",
"url": "https://github.com/Vanilla-OS/pico-image"
}
Returns:
200 OK
on success.
Retrieves information about an image given its name.
Method: GET
Endpoint: http://[base_url]/images/[name]
Parameters: None
Returns:
200 OK
on success, alongside the image information.
{
"image": {
"name": "pico",
"url": "https://github.com/Vanilla-OS/pico-image",
"releases": [
...
]
}
}
400 Bad Request
if image cannot be found.
Retrieves information about all images.
Method: GET
Endpoint: http://[base_url]/images
Parameters: None
Returns:
200 OK
on success, alongside the images information.
{
"images": [
{
"name": "pico",
"url": "https://github.com/Vanilla-OS/pico-image",
"releases": [
...
]
},
{
"name": "core",
"url": "https://github.com/Vanilla-OS/core-image",
"releases": [
...
]
}
]
}
A release is a new version of some image, where packages can be added, removed, upgraded or downgraded. A release also has a digest, which should be the image digest provided by the container manager.
Creates a new release for the given image.
Method: POST
Endpoint:http://[base_url]/images/[image]/new
Parameters:
- Digest: Image digest
- Packages: List of package names and versions in the current release.
get_all_packages.py
contains a script for extracting the list from a Debian-based distribution.
{
"digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0d",
"packages": [
{
"name": "apt",
"version": "2.7.3"
},
...
]
}
Returns:
200 OK
on success.
Retrieves the most recent release from the image.
Method: GET
Endpoint: http://[base_url]/images/[image]/latest
Parameters: None
Returns:
200 OK
on success, alongside the release information.
{
"release": {
"digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0d",
"date": "2023-11-26T12:56:53.377838864Z",
"packages": [
...
]
}
}
Searches for a specific release by its digest.
Method: GET
Endpoint: http://[base_url]/images/[image]/[digest]
Parameters: None
Returns:
200 OK
on success, alongside the release information.
{
"release": {
"digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0d",
"date": "2023-11-26T12:56:53.377838864Z",
"packages": [
...
]
}
}
400 Bad Request
if release cannot be found.
The most important endpoint in the API. Given two digests, generates a list of changed packages. This information is cached so future queries are nearly instant.
Method: GET
Endpoint: http://[base_url]/images/[image]/diff
Parameters:
- Old digest: Digest of the older image, which is usually the image the user is currently on.
- New digest: Digest of the newer image, which is usually the image the user wants to update to.
{
"old_digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0c",
"new_digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0d"
}
Returns:
200 OK
on success, alongside the modified packages.
{
"_new_digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0d",
"_old_digest": "sha256:a99e4593b23fd07e3761639e9db38c0315e198d6e39dad6070e0e0e88be3de0c",
"added": [
{
"name": "zsh-common",
"new_version": "5.9-5"
}
],
"downgraded": [
{
"name": "autoconf",
"old_version": "2.71-3"
}
],
"removed": [
{
"name": "nano",
"old_version": "6.2"
}
],
"upgraded": [
{
"name": "curl",
"old_version": "1.0.3-aplha",
"new_version": "8.2.1-2"
}
]
}
400 Bad Request
if either digest cannot be found.