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:
- initialize a repository
- initial release
- build the application, including trusted root metadata from the repository
- create an archive for the application and register it in the repo
- second release
- build the new release
- create an archive for the new release, create a patch, and register both in the repo
- serve the repository on a local test server
- run the "installed" application, so it can perform an automatic update
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.
- run
repo_init.py(CLI:tufup init) - run
create_pyinstaller_bundle_win.batorcreate_pyinstaller_bundle_mac.sh(note that ourmain.specensures that the latestroot.jsonmetadata file is included in the bundle) - run
repo_add_bundle.py(CLI:tufup targets add 1.0 temp_my_app/dist/main temp_my_app/keystore) - modify the app, and/or increment
APP_VERSIONinmyapp/settings.py - run the
create_pyinstaller_bundlescript again - run
repo_add_bundle.pyagain (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/targetsdir into theINSTALL_DIR, specified inmyapp/settings.py.In the default example the
INSTALL_DIRwould be theC:\users\<username>\AppData\Local\Programs\my_appdirectory. You can usetar -xf my_app-1.0.tar.gzin 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.exeormain, depending on platform, directly from theINSTALL_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:
- for the client-side, remove
UPDATE_CACHE_DIRandINSTALL_DIR - for the repo-side, remove
DEV_DIR(i.e. thetemp_my_appdir described above) - remove
.tufup_repo_config - follow the steps above to set up the repo-side and client-side
Alternatively, you could run the test_update_cycle.ps1 script, which also removes stale example files from the default directories.