Skip to content

Conversation

@kdmukai
Copy link
Contributor

@kdmukai kdmukai commented Dec 25, 2025

Description

NOTES:


Brief summary

Eliminates the hard-coded Controller.VERSION string in favor of using the current git status (either written to disk when a SeedSigner OS image is built or pulled live at run time in local dev).

Dynamic version detection

Version info is collected from multiple possible sources, depending on the context:

  • version.json file written into a SeedSigner OS image.
  • Current git state retrieved by git shell calls (typical local dev).
  • Current git state parsed out of .git/ files
    • e.g. my dev device will rsync just the minimal required .git/ files to retrieve current git state for Version.
  • Github Actions CI env vars.

New "Version" screen in the Settings menu

Output is dynamic to support a clean, simple display for release images as well as more detailed info for local dev or test images.

OpeningSplashView_current_git_state SettingsMenuView_2
VersionView VersionView_current_git_state


Motivation

Nice to have for official release images:

The timestamp at the bottom of the new version screen is the date of the last commit included in the release. May provide a nudge for users to update if they see that a lot of time has passed. The timestamp is also one more thing that a paranoid user could verify for themselves (though an evil image can always just lie about its timestamp).

Win for test images / release candidates:

As the project keeps growing we'll need more "black box testers" (hands-on testing w/out knowing any code details). Ideally we'd have test images automatically built for PRs, when new translations are submitted, etc.

We currently have to manually modify the Controller.VERSION string to indicate a special build version on the opening splash screen.

This PR instead automatically displays the current git branch / tag / commit hash as the version string. And as a test branch goes through further iterations, the commit hash and timestamp will help us keep track of who's feedback is still relevant ("Wait, can you confirm you saw this on abcd123? That should have been fixed.").

And of course it'll always be a bit of a security risk to have people use test images, but the extra bit of transparency provided by this info (which exact commit from what exact timestamp) helps a little. It's still no guarantee, though.

Extra info and debugging in local dev:

  • Sanity check to know that your updated code is indeed onboard the dev device. In local dev the version screen's timestamp is the most recent last modified time of the python source files.
  • Quick way to remind yourself which branch / commit is onboard.
  • The screenshot generator is updated to render version screens that reflect the most recent official release AND another screenshot for the current git state (e.g. "v0.8.6" and "new_feature_branch_foo").

How it works

Copied from the new Version class' docstring:

SeedSigner OS lifecycle:
    * The SeedSigner OS build process runs the tools/write_versionfile.py script to
      generate version.json.
    * The version.json file is included in the SeedSigner OS image.

The version data is fetched differently depending on the environment:

In SeedSigner OS:
    * Version data is read exclusively from version.json at runtime.

In the SeedSigner OS builder:
- Note: the build process relies on `git` being installed so we leverage it here.
    * version_name:
        * read dynamically from `git` shell calls to retrieve, in order:
            * Current git branch name
            * Current git tag name
            * Current git short commit hash
    * version_fork:
        * the git repo owner parsed out of the remote url.
        * https://github.com/SeedSigner/seedsigner.git -> "SeedSigner"
        * Read dynamically from shell `git` call to check the remote "origin" URL.
    * short_commit_hash:
        * the commit hash for the current branch / tag / detached HEAD.
        * Read dynamically from shell `git` call.
    * version_timestamp:
        * the last git commit time for the current branch / tag / detached HEAD.
        * Read dynamically from shell `git` call.

In local dev:
- Similar process as for the SeedSigner OS builder, but with additional fallbacks and
  a different method for determining version_timestamp.
- If a local version.json is present, it will be ignored; it may be out of date and
  could lead to confusion.
    * version_name:
        * `git` shell calls + directly parsing the .git/HEAD file and .git/refs/tags
          when necessary.
    * version_fork:
        * `git` shell call + parse the .git/config for the remote "origin" URL.
    * commit_hash:
        * Shell `git` call + parse the .git/HEAD file and
          .git/refs/heads/<branch_name> when necessary.
    * version_timestamp: determined by scanning the src/ directory for the most
      recently modified python file.

In Github Actions CI:
    * version_name: read from GITHUB_REF_NAME env var.
    * version_fork: read from PR_AUTHOR custom CI env var or GITHUB_REPOSITORY_OWNER.
    * version_timestamp: Shell `git` call to get the last commit time for the current
      branch/tag/commit.
    * commit_hash: read from SOURCE_SHA custom CI env var or GITHUB_SHA.

Release images

Release images are built in SeedSigner OS with --app-branch pointing to a git tag and --app-repo pointing to our main repo (i.e. not someone's fork). If the specified git tag is a "clean" semantic version (e.g. "0.8.6" vs "0.8.7-rc1") and we're pulling from the main repo, we deduce that this is a release image and so the version info is displayed in its simplified form.

OpeningSplashView VersionView

Current branch name

In this local dev / test image scenario we get a more detailed dev/debugging version of the screen.

  • fork: PR author (when run in github CI) or owner of the current repo (local dev).
  • commit: short commit hash for the current git state.

Makes a minimal effort to add line breaks for long branch names. Identifying the current branch name is really only for local dev or possibly something like a test image. So we don't need to aim for perfect presentation here.

OpeningSplashView copy VersionView_current_git_state


Tag name

If the current git state is pointing to a tag but we're running on a fork (repo owner is not "SeedSigner") OR the tag name is not a clean semantic version, then the version info stays in its dev output format:
OpeningSplashView_current_git_state VersionView_current_git_state


Commit hash

Falls back to the short commit hash when git is in a detached HEAD state (and not on a tag). Also not meant for normal users to ever see.

OpeningSplashView_current_git_state VersionView_current_git_state

Coding considerations

Coverage goal: 100%

This all ended up being much bigger than I originally anticipated due to the number of different ways to retrieve the version information. So to facilitate testing, you'll see that basically every tiny unit of functionality is broken out into its own VersionUtils method, with some meta-methods invoking various smaller ones.

But so many little pieces, it felt crucial to reach 100% test coverage. So the accompanying tests are unavoidably numerous. And there are some trivial tests noted that only exist so we can get to that 100% coverage. It's both a worthy goal and a little ridiculous at the same time.

Note that our pyproject.toml settings have skip_covered = true so when version.py is at 100% you won't see it listed in the coverage report at all.

Simplicity elsewhere

The Version class is the only place where any other part of the main code should interact with the version data. It is just a simple placeholder that determines the version data when the Version singleton is first instantiated, and then provides basic get_* methods.

Sandboxing riskier calls

I was initially very uncomfortable adding the git shell calls; any shell command introduces some new risks. But those calls are only made when running in local dev. So I added a new not_allowed_in_seedsigner_os decorator and its associated NotAllowedInSeedSignerOS.

This same restriction was applied to all VersionUtils calls that try to read directly from the .git/ subdir.


Testing tips

The VersionUtils tests are grouped into specific sub-collections that reflect the different ways it retrieves version info.

e.g. TestVersionUtils_VersionFile vs TestVersionUtils_GitShell etc.

One useful way to review the test suite and its coverage is to run just one of these test classes at a time and then inspect the html coverage report:

coverage run -m pytest ./tests/test_version.py::TestVersionUtils_VersionFile && coverage html && coverage report | grep version.py

coverage run -m pytest ./tests/test_version.py::TestVersionUtils_GitShell && coverage html && coverage report | grep version.py

You'll see hit or miss coverage in various places, but the specific area being tested (e.g. the *_from_git_shell() calls) will show full coverage.

Test various local git states

For quick testing against different local git states, you can run the write_versionfile.py script and see the version info based on your current git state:

# writes to src/seedsigner/version.json
python tools/write_versionfile.py

The screenshot generator also creates screenshot variants for both:

  • The most recent official release tag (e.g. 0.8.6).
  • Your current git state.

And of course you can run this branch on your local dev device. However, how this code retrieves version info will depend on if your dev device has the git shell command available and if you transfer the .git/ dir. I have set up rsync in my local dev to sync:

  • .git/HEAD
  • .git/config
  • .git/refs/heads/*
  • .git/refs/tags/*

Try from:

  • a checked out branch
  • create a tag for this branch, then git checkout <tag_name>
  • grab the latest commit hash and git checkout <commit_hash>.

You can run all of this as a clone of my repo on this version_screen_with_git_info branch.

Or you can run it as a PR branch in your fork via:

git fetch upstream pull/858/head:pr_858
git checkout pr_858

Integration with SeedSigner OS

See the accompanying SeedSigner/seedsigner-os#102

A simple new script (tools/write_versionfile.py) instantiates the Version which, in this context, will retrieve the current git information via git shell commands. It then writes the version info to src/seedsigner/version.json which is included in the final image.

The write_versionfile.py script doesn't do any new work on its own; all of the functionality was implemented in VersionUtils so that the script would only invoke commands that are already covered by the test suite.

It really only exists as a separate script because we have previously preferred to keep any if __name__ == "__main__": command line executable bits out of the main source code.


Misc Notes

Screenshot generator

The screnshot generator's run_before and run_after optional params have always been pretty primitive. The new Version-related screenshots required mocking/patching that is more like individual test cases (e.g. mocking us into SeedSigner OS for one screenshot).

So this PR has already merged in #860 as a dependency.


This pull request is categorized as a:

  • New feature

Checklist

  • I’ve run pytest and made sure all unit tests pass before submitting the PR

If you modified or added functionality/workflow, did you add new unit tests?

  • Yes: 100% coverage!

I have tested this PR on the following platforms/os:

@kdmukai kdmukai moved this to 0.9.0 In Progress in @SeedSigner Development Board Dec 26, 2025
@kdmukai kdmukai changed the title [Feature] Version screen; includes live git info in local dev [Feature] Dynamic version detection; Version display screen Dec 27, 2025
@kdmukai kdmukai marked this pull request as ready for review December 27, 2025 22:15
@kdmukai kdmukai moved this from 0.9.0 In Progress to 0.9.0 Needs Code Review in @SeedSigner Development Board Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 0.9.0 Needs Code Review

Development

Successfully merging this pull request may close these issues.

1 participant