This repository shows how to use the tufup package for automated application updates.
This is done by means of a dummy Windows-application, called myapp
, that uses tufup
in combination with pyinstaller
.
NOTE: Although the example myapp
is bundled using pyinstaller
, this is not required: tufup
is completely independent of pyinstaller
, and can be used with any bundle of files.
NOTE: Although the example application is written for Windows (or macOS), this only pertains to the directories, defined in settings.py
, and the script used to run pyinstaller
.
You can simply adapt these to use the example on other operating systems.
If you have any questions, please make sure to check the existing discussions and existing issues first. (Also check tufup
discussions and tufup
issues.)
New questions can be asked in the Q&A or on stackoverflow, and bugs related to tufup-example
can be reported here.
Create a virtualenv (or equivalent) and install requirements:
pip install -r requirements.txt -r requirements-dev.txt --upgrade
For basic terminology, see documentation for TUF (The Update Framework).
We start out with a dummy application that has already integrated the tufup.client
.
See src/myapp/__init__.py
for details.
The dummy application is bundled using PyInstaller, but tufup
works with any type of "application bundle" (i.e. just a directory with content representing the application).
The example includes a basic PyInstaller .spec
file that ensures the tufup
root metadata file (root.json
) is included in the application bundle.
The dummy application specifies where all tufup
-related files will be stored.
This is illustrated in settings.py
.
The following basic steps are covered:
For quick testing, these steps have been automated in the PowerShell script
test_update_cycle.ps1
.
A detailed description of the steps, both for the repository-side and for the client-side, can be found in the following sections.
Some example scripts are provided for initializing a tufup repository and for adding new versions, see repo_*.py
.
Alternatively, tufup
offers a command line interface (CLI) for repository actions.
Type tufup -h
on the command line for more information.
Here's how to set up the example tufup repository, starting from a clean repo, i.e. no temp_my_app
dir is present in the repo root (as defined by DEV_DIR
in settings.py
):
Note: If you use the CLI, see repo_settings.py
for sensible values.
repo_init.py
(CLI: tufup init
)create_pyinstaller_bundle_win.bat
or create_pyinstaller_bundle_mac.sh
(note that our main.spec
ensures that the latest root.json
metadata file is included in the bundle)repo_add_bundle.py
(CLI: tufup targets add 1.0 temp_my_app/dist/main temp_my_app/keystore
)APP_VERSION
in myapp/settings.py
create_pyinstaller_bundle
script againrepo_add_bundle.py
again (CLI: tufup targets add 2.0 temp_my_app/dist temp_my_app/keystore
)Note: When adding a bundle, tufup
creates a patch by default, which can take quite some time.
If you want to skip patch creation, either set skip_patch=True
in the Repository.add_bundle()
call, or add the -s
option to the CLI command: tufup targets add -s 2.0 ...
.
Now we should have a temp_my_app
dir with the following structure:
temp_my_app
├ build
├ dist
├ keystore
└ repository
├ metadata
└ targets
In the targets
dir we find two app archives (1.0 and 2.0) and a corresponding patch file.
We can serve the repository on localhost as follows (relative to project root):
python -m http.server -d temp_my_app/repository
That's it for the repo-side.
On the same system (for convenience):
To simulate the initial installation on a client device, we do a manual extraction of the archive version 1.0 from the repository/targets
dir into the INSTALL_DIR
, specified in myapp/settings.py
.
In the default example the INSTALL_DIR
would be the C:\users\<username>\AppData\Local\Programs\my_app
directory.
You can use tar -xf my_app-1.0.tar.gz
in PowerShell to extract the bundle.
To install the bundle on macOS to the default location, you can use
mkdir -p ~/Applications/my_app && tar -xf temp_my_app/repository/targets/my_app-1.0.tar.gz -C ~/Applications/my_app
.
[optional] To try a patch update, copy the archive version 1.0 into the TARGET_DIR
(this would normally be done by an installer).
Assuming the repo files are being served on localhost, as described above, we can now run the newly extracted executable, main.exe
or main
, depending on platform, directly from the INSTALL_DIR
, and it will perform an update.
Metadata and targets are stored in the UPDATE_CACHE_DIR
.
BEWARE: The steps above refer to the INSTALL_DIR
for the FROZEN
state, typically C:\users\<username>\AppData\Local\Programs\my_app
on Windows.
In development, when running the myapp
example directly from source, i.e. FROZEN=False
, the INSTALL_DIR
is different from the actual install dir that would be used in production. See details in settings.py.
When playing around with this example-app, it is easy to wind up in an inconsistent state, e.g. due to stale metadata files. This may result in tuf role verification errors, for example. If this is the case, it is often easiest to start from a clean slate for both repo and client:
UPDATE_CACHE_DIR
and INSTALL_DIR
DEV_DIR
(i.e. the temp_my_app
dir described above).tufup_repo_config
Alternatively, you could run the test_update_cycle.ps1
script, which also removes stale example files from the default directories.