diff --git a/data/tuf_root.json b/data/tuf_root.json deleted file mode 100644 index a1c5c50..0000000 --- a/data/tuf_root.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "signatures": [ - { - "bundle": { - "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", - "messageSignature": { - "messageDigest": { - "algorithm": "SHA2_256", - "digest": "POgT0U1+hNzb6a9TwHV6hGE7GWyhutD1wS/e/Rd20EU=" - }, - "signature": "MEQCID6QUeEd1eeT2Pko1L1BXBrvnS0ob53PLT0M4n2gh3NJAiBhxzy4aBEHbbGLDSVOmgAjl5uf60/blhqH4OFvjyDkVA==" - }, - "verificationMaterial": { - "certificate": { - "rawBytes": "MIIC1jCCAlugAwIBAgIUfjl4ydqqY/H/0o4icgVYEWTP/v0wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwMTIwMjAwNjEyWhcNMjYwMTIwMjAxNjEyWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrt+rE9hkME7UPyQ9TMCmXjcmAoOROZM3nKmWb+NErRwfw0Bys6QsSeOHpJaluqdNVnk7l+BjsNubHq7VgVyi6OCAXowggF2MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUyCz0GeR7j+lfxQkmxu/JEaApvMcwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wJQYDVR0RAQH/BBswGYEXYXNoZXIubm9ybGFuZEBnbWFpbC5jb20wLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMC4GCisGAQQBg78wAQgEIAweaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGb3QPOQQAABAMARjBEAiBb8fZbmthA8mv4WOOAoADe0WC1zElNtWCT7McORTCIFgIgM8RYib8mxbCBBEgOR4YyyGmpoWm1ozktIKezTIdt1UYwCgYIKoZIzj0EAwMDaQAwZgIxAMjWqlEKFB/AO5+CaHQTi7SaKx7TGvsSDBG0LNkgWWCMGecyrfQzWH21oNJ+6hXwDwIxAJk5p5snr3JRvE8YuqiOu7OIeTJjamlD4/AmN703tY8opvEIxi/eirPTBcGZnKBCEQ==" - }, - "timestampVerificationData": {}, - "tlogEntries": [ - { - "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzY2U4MTNkMTRkN2U4NGRjZGJlOWFmNTNjMDc1N2E4NDYxM2IxOTZjYTFiYWQwZjVjMTJmZGVmZDE3NzZkMDQ1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRDZRVWVFZDFlZVQyUGtvMUwxQlhCcnZuUzBvYjUzUExUME00bjJnaDNOSkFpQmh4enk0YUJFSGJiR0xEU1ZPbWdBamw1dWY2MC9ibGhxSDRPRnZqeURrVkE9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhha05EUVd4MVowRjNTVUpCWjBsVlptcHNOSGxrY1hGWkwwZ3ZNRzgwYVdOblZsbEZWMVJRTDNZd2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDFVU1hkTmFrRjNUbXBGZVZkb1kwNU5hbGwzVFZSSmQwMXFRWGhPYWtWNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZNY25RcmNrVTVhR3ROUlRkVlVIbFJPVlJOUTIxWWFtTnRRVzlQVWs5YVRUTnVTMjBLVjJJclRrVnlVbmRtZHpCQ2VYTTJVWE5UWlU5SWNFcGhiSFZ4WkU1V2JtczNiQ3RDYW5OT2RXSkljVGRXWjFaNWFUWlBRMEZZYjNkblowWXlUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlY1UTNvd0NrZGxVamRxSzJ4bWVGRnJiWGgxTDBwRllVRndkazFqZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVdWaE9iMXBZU1hWaWJUbDVZa2RHZFZwRlFtNWlWMFp3WWtNMWFtSXlNSGRNUVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdWaFNGSXdZMGhOTmt4NU9XNWhXRkp2WkZkSmRWa3lPWFJNTW5oMldqSnNkVXd5T1doa1dGSnZUVU0wUjBOcGMwZEJVVkZDQ21jM09IZEJVV2RGU1VGM1pXRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3eWVIWmFNbXgxVERJNWFHUllVbTlOU1VkS1FtZHZja0puUlVVS1FXUmFOVUZuVVVOQ1NITkZaVkZDTTBGSVZVRXpWREIzWVhOaVNFVlVTbXBIVWpSamJWZGpNMEZ4U2t0WWNtcGxVRXN6TDJnMGNIbG5Remh3TjI4MFFRcEJRVWRpTTFGUVQxRlJRVUZDUVUxQlVtcENSVUZwUW1JNFpscGliWFJvUVRodGRqUlhUMDlCYjBGRVpUQlhRekY2Uld4T2RGZERWRGROWTA5U1ZFTkpDa1puU1dkTk9GSlphV0k0YlhoaVEwSkNSV2RQVWpSWmVYbEhiWEJ2VjIweGIzcHJkRWxMWlhwVVNXUjBNVlZaZDBObldVbExiMXBKZW1vd1JVRjNUVVFLWVZGQmQxcG5TWGhCVFdwWGNXeEZTMFpDTDBGUE5TdERZVWhSVkdrM1UyRkxlRGRVUjNaelUwUkNSekJNVG10blYxZERUVWRsWTNseVpsRjZWMGd5TVFwdlRrb3JObWhZZDBSM1NYaEJTbXMxY0RWemJuSXpTbEoyUlRoWmRYRnBUM1UzVDBsbFZFcHFZVzFzUkRRdlFXMU9OekF6ZEZrNGIzQjJSVWw0YVM5bENtbHlVRlJDWTBkYWJrdENRMFZSUFQwS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX19", - "inclusionPromise": { - "signedEntryTimestamp": "MEUCIQD1+/YCK53+es5KAv3jrVoxern5l4e0F8OTzKby24/jFQIgZHfbGd2+kqWz5t1RLayuRLlkHyrbMtYtOR7XVv46jxc=" - }, - "inclusionProof": { - "checkpoint": { - "envelope": "rekor.sigstore.dev - 1193050959916656506\n716889433\nb0OnBiQa80jv6Icj5AmdMghXuKkDDIYSKgNI3xOhhSc=\n\n\u2014 rekor.sigstore.dev wNI9ajBEAiAx0qC1SiHUxe0sMC9XI1omTl6kirXv36Vh617AsLlmjwIgdlwKCxNnClCASgAPle1yfKoJ8bgkjdbUO5AaDfzvCB4=\n" - }, - "hashes": [ - "XpzAosRqxJnvhT7wcb7hHAGKq/r9GmOk6fSUxPHsRh8=", - "8oOSwBWL2tkyqBFEEVIXXUlTJzVNXCeYjQZF+3r8k5E=", - "HJp6FOCqSkgCDFKe4327oWTCVocIpthKCip5QWhAnaQ=", - "F2NprceH75+FS/RA8c7WW/oP9ZgBeEFy97fQbQv0BV8=", - "mFzajEdBByK/sfQoms9H1M5cGjA4yOzeWHUi3geKSQs=", - "33HWD2+L5XvDEP0HywDQu3/p4c4PFUtMq5HWC2LcZas=", - "RPgucTidcZ1NKPE5C7f//Mq/BDP8FQ8eKWRYfFlAxvU=", - "Tq3aqCW5jZmOK3/zgMVJDERZU7xddmp7jiAlnCwOiHY=", - "MMgKSENHKqdc6mSMivIKkx9R+su5l5/lCYM5Utr/s14=", - "O9XvmIfOnHkcGvWDY6S6ITWYSPOOtG5t6v/3YhrW4kk=", - "lc4ahx1LKbZvEQtjgSQ1SWjhZC4XQLXn1ZZnH2B6x6g=", - "xRdqHoaHeCEbz8GHV1LtSOnN+V5ZeRZTCfFAUdPiKYc=", - "aIChwQPNz8w9PcBXWExpconzbrRfAux4kC5V6whEI7k=", - "n9EEOUEfsUsQQBmuOuIrKwiYk8j22mF5bs3rLsac5uY=", - "mrq/uGLKxgJ0AtYkDXnUKEX4B23PtjqkTaToDSECXy0=", - "kkTEow+M+pTGx96grjhnTg4TKTNBRoGGfsSXIn5oXnQ=", - "vk+Sc7c1laTnH9uCSqZ0Un3rutG4UGrLDkm3cECOZUU=", - "WFmkMhmL2tOzDi6lp4zGgaCzwux2vGOM44v1vr1wuDs=", - "F9MSQ5SmoFr+hoADclpdFY52/TLfHDnNPYb9ZNYO5gI=", - "T4DqWD42hAtN+vX8jKCWqoC4meE4JekI9LxYGCcPy1M=" - ], - "logIndex": "716889431", - "rootHash": "b0OnBiQa80jv6Icj5AmdMghXuKkDDIYSKgNI3xOhhSc=", - "treeSize": "716889433" - }, - "integratedTime": "1768939573", - "kindVersion": { - "kind": "hashedrekord", - "version": "0.0.1" - }, - "logId": { - "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" - }, - "logIndex": "838793693" - } - ] - } - }, - "keyid": "cc082adb1eb06b2f2441e5d5ee182e8d4934ccc6ba3aa1ac116afc58d69daaec", - "sig": "MEQCID6QUeEd1eeT2Pko1L1BXBrvnS0ob53PLT0M4n2gh3NJAiBhxzy4aBEHbbGLDSVOmgAjl5uf60/blhqH4OFvjyDkVA==" - } - ], - "signed": { - "_type": "root", - "consistent_snapshot": true, - "expires": "2027-01-20T20:05:02Z", - "keys": { - "16baae2daac43d4214b0cfcaf6fec20d52345d09f239d352ab176825e95b4695": { - "keytype": "sigstore-oidc", - "keyval": { - "identity": "https://github.com/synodic/synodic-updates/.github/workflows/online-sign.yml@refs/heads/main", - "issuer": "https://token.actions.githubusercontent.com" - }, - "scheme": "Fulcio", - "x-tuf-on-ci-online-uri": "sigstore:" - }, - "cc082adb1eb06b2f2441e5d5ee182e8d4934ccc6ba3aa1ac116afc58d69daaec": { - "keytype": "sigstore-oidc", - "keyval": { - "identity": "asher.norland@gmail.com", - "issuer": "https://github.com/login/oauth" - }, - "scheme": "Fulcio", - "x-tuf-on-ci-keyowner": "@behemyth" - } - }, - "roles": { - "root": { - "keyids": [ - "cc082adb1eb06b2f2441e5d5ee182e8d4934ccc6ba3aa1ac116afc58d69daaec" - ], - "threshold": 1 - }, - "snapshot": { - "keyids": [ - "16baae2daac43d4214b0cfcaf6fec20d52345d09f239d352ab176825e95b4695" - ], - "threshold": 1, - "x-tuf-on-ci-expiry-period": 365, - "x-tuf-on-ci-signing-period": 60 - }, - "targets": { - "keyids": [ - "cc082adb1eb06b2f2441e5d5ee182e8d4934ccc6ba3aa1ac116afc58d69daaec" - ], - "threshold": 1 - }, - "timestamp": { - "keyids": [ - "16baae2daac43d4214b0cfcaf6fec20d52345d09f239d352ab176825e95b4695" - ], - "threshold": 1, - "x-tuf-on-ci-expiry-period": 2, - "x-tuf-on-ci-signing-period": 1 - } - }, - "spec_version": "1.0.31", - "version": 1, - "x-tuf-on-ci-expiry-period": 365, - "x-tuf-on-ci-signing-period": 60 - } -} \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 06bc5c2..be92fbe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,9 +5,9 @@ An application frontend for [porringer](https://www.github.com/synodic/porringer ## Features - **System Tray Application**: Runs unobtrusively in the system tray -- **Secure Self-Updates**: Automatic updates using [TUF](https://theupdateframework.io/) for cryptographic verification +- **Secure Self-Updates**: Automatic updates using [Velopack](https://velopack.io/) for seamless installation and delta updates - **Multiple Update Channels**: Support for stable releases and development prereleases -- **Rollback Support**: Automatic backup and rollback on update failure +- **Cross-Platform**: Windows, macOS, and Linux support ## Installation diff --git a/docs/updates.md b/docs/updates.md index 5ed6b5d..d26b66d 100644 --- a/docs/updates.md +++ b/docs/updates.md @@ -1,73 +1,58 @@ # Self-Update System -Synodic Client includes a secure self-update mechanism built on: +Synodic Client includes a self-update mechanism built on: -- **[TUF (The Update Framework)](https://theupdateframework.io/)** - Cryptographic verification of update artifacts -- **[porringer](https://www.github.com/synodic/porringer)** - Version checking via PyPI and download management +- **[Velopack](https://velopack.io/)** - Cross-platform installer and auto-update framework +- **GitHub Releases** - Distribution of update packages ## Update Channels -| Channel | Description | PyPI Versions | +| Channel | Description | Release Type | |---------|-------------|---------------| -| `STABLE` | Production releases only | Final releases (e.g., `1.0.0`) | -| `DEVELOPMENT` | Includes prereleases | All versions (e.g., `1.0.0.dev1`) | - -The channel is automatically selected based on how the application is running: - -- **Frozen executable** (PyInstaller): Uses `STABLE` channel -- **Running from source**: Uses `DEVELOPMENT` channel +| `stable` | Production releases only | Final releases (e.g., `1.0.0`) | +| `dev` | Development builds | Prereleases (e.g., `1.0.0.dev123`) | ## Update Workflow ```mermaid flowchart TD - A[Check PyPI] --> B{Update Available?} + A[Check GitHub Releases] --> B{Update Available?} B -->|No| C[Done] - B -->|Yes| D[Download via TUF] - D --> E[Verify Signature] - E --> F[Backup Current] - F --> G[Apply Update] - G --> H{Success?} - H -->|Yes| I[Restart] - H -->|No| J[Rollback] - I --> K[Cleanup Backup] + B -->|Yes| D[Download Update] + D --> E[Apply Delta/Full Package] + E --> F[Restart Application] ``` -1. **Check** - Query PyPI for newer versions -2. **Download** - Fetch artifact with TUF verification -3. **Backup** - Create backup of current executable -4. **Apply** - Replace executable with new version -5. **Restart** - Spawn new process and exit -6. **Cleanup** - Remove backup after successful verification - -If an update fails to apply, the system automatically offers rollback to the previous version. +1. **Check** - Query GitHub releases for newer versions via Velopack +2. **Download** - Fetch full or delta package +3. **Apply** - Velopack handles installation +4. **Restart** - Launch updated version ## Programmatic Usage ```python -from porringer.api import API, APIParameters -from porringer.schema import LocalConfiguration - from synodic_client.client import Client from synodic_client.updater import UpdateChannel, UpdateConfig # Initialize client = Client() -porringer = API(LocalConfiguration(), APIParameters(logger)) # Configure for development channel config = UpdateConfig(channel=UpdateChannel.DEVELOPMENT) -client.initialize_updater(porringer, config) +client.initialize_updater(config) # Check for updates info = client.check_for_update() if info and info.available: print(f"Update available: {info.current_version} -> {info.latest_version}") - # Download and apply - if client.download_update(): - if client.apply_update(): - client.restart_for_update() + # Download with progress + def on_progress(percent: int) -> None: + print(f"Downloading: {percent}%") + + if client.download_update(on_progress): + # Apply and restart + client.apply_update_and_restart() ``` ## Configuration @@ -77,33 +62,26 @@ The `UpdateConfig` dataclass controls update behavior: ```python @dataclass class UpdateConfig: - # PyPI package name for version checks - package_name: str = 'synodic_client' - - # TUF repository URL for secure artifact download - tuf_repository_url: str = 'https://synodic.github.io/synodic-updates' + # GitHub repository URL for Velopack to discover releases + repo_url: str = 'https://github.com/synodic/synodic-client' - # Channel determines whether to include prereleases + # Channel determines whether to use dev or stable releases channel: UpdateChannel = UpdateChannel.STABLE - # Local paths for metadata, downloads, and backups - metadata_dir: Path = Path.home() / '.synodic' / 'tuf_metadata' - download_dir: Path = Path.home() / '.synodic' / 'downloads' - backup_dir: Path = Path.home() / '.synodic' / 'backup' + @property + def channel_name(self) -> str: + """Get the channel name for Velopack.""" + return 'dev' if self.channel == UpdateChannel.DEVELOPMENT else 'stable' ``` -## TUF Repository - -The TUF repository is managed separately at [synodic/synodic-updates](https://github.com/synodic/synodic-updates) using [tuf-on-ci](https://github.com/theupdateframework/tuf-on-ci). - -After initialization, copy the `root.json` from the published repository to `data/tuf_root.json` in this project for bundling with the executable. +## GitHub Releases Structure -### Target Naming Convention +Velopack packages are published to GitHub Releases with the following structure: -Artifacts are named by platform: +| Platform | Files | +|----------|-------| +| Windows | `synodic-Setup.exe`, `synodic-{version}-full.nupkg` | +| Linux | `synodic.AppImage` | +| macOS | `synodic.app` (packaged) | -| Platform | Target Name Pattern | -|----------|---------------------| -| Windows | `synodic-{version}-windows-x64.exe` | -| macOS | `synodic-{version}-macos-x64` | -| Linux | `synodic-{version}-linux-x64` | +Velopack automatically manages `releases.{channel}.json` files for update discovery. diff --git a/tool/pyinstaller/synodic.spec b/tool/pyinstaller/synodic.spec index afc9243..5d2fe72 100644 --- a/tool/pyinstaller/synodic.spec +++ b/tool/pyinstaller/synodic.spec @@ -9,9 +9,6 @@ hiddenimports = [] # Add porringer metadata so entry points work datas += copy_metadata('porringer') -# Add TUF metadata for secure updates -datas += copy_metadata('tuf') - # Add your plugin packages here as you add them to dependencies # Example: datas += copy_metadata('porringer-plugin-name') # Example: hiddenimports += ['porringer_plugin_name']