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.bat
orcreate_pyinstaller_bundle_mac.sh
(note that ourmain.spec
ensures that the latestroot.json
metadata 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_VERSION
inmyapp/settings.py
- run the
create_pyinstaller_bundle
script again - run
repo_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 theINSTALL_DIR
, specified inmyapp/settings.py
.In the default example the
INSTALL_DIR
would be theC:\users\<username>\AppData\Local\Programs\my_app
directory. You can usetar -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
ormain
, 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_DIR
andINSTALL_DIR
- for the repo-side, remove
DEV_DIR
(i.e. thetemp_my_app
dir 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.