From 89af77d6c399e6ed8a24a6c538ad8edecf9117f9 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Fri, 17 May 2024 13:00:31 -0700 Subject: [PATCH 01/12] Add data to update responses and fix branch bug (#18) # Description Include initramfs metadata in update check responses Adds debug logging to diagnose update issues Fixes branch reference in `check_update_initramfs` # Issues Contributes to https://github.com/NeonGeckoCom/skill-update/issues/89 # Other Notes Add unit test coverage before merge --------- Co-authored-by: Daniel McKnight --- neon_phal_plugin_device_updater/__init__.py | 23 ++++---- tests/unit_tests.py | 60 ++++++++++++++++++++- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/neon_phal_plugin_device_updater/__init__.py b/neon_phal_plugin_device_updater/__init__.py index 5072303..b519298 100644 --- a/neon_phal_plugin_device_updater/__init__.py +++ b/neon_phal_plugin_device_updater/__init__.py @@ -92,13 +92,11 @@ def initramfs_hash(self) -> Optional[str]: if not self._initramfs_hash: try: Popen("mount_firmware", shell=True).wait(5) - with open(self.initramfs_real_path, "rb") as f: - self._initramfs_hash = hashlib.md5(f.read()).hexdigest() except Exception as e: LOG.error(e) - if isfile(self.initramfs_real_path): - with open(self.initramfs_real_path, "rb") as f: - self._initramfs_hash = hashlib.md5(f.read()).hexdigest() + if isfile(self.initramfs_real_path): + with open(self.initramfs_real_path, "rb") as f: + self._initramfs_hash = hashlib.md5(f.read()).hexdigest() LOG.debug(f"hash={self._initramfs_hash}") return self._initramfs_hash @@ -283,6 +281,8 @@ def _get_gh_latest_release_tag(self, track: str = None) -> str: default_time = "2000-01-01T00:00:00Z" url = f'https://api.github.com/repos/{self.release_repo}/releases' + LOG.debug(f"Getting releases from {self.release_repo}. " + f"prerelease={include_prerelease}") releases: list = requests.get(url).json() if not include_prerelease: releases = [r for r in releases if not r.get('prerelease', True)] @@ -309,6 +309,7 @@ def _get_gh_release_meta_from_tag(self, tag: str) -> dict: f"{self.build_info}") meta_url = (f"https://raw.githubusercontent.com/{self.release_repo}/" f"{tag}/{installed_os}.yaml") + LOG.debug(f"Getting metadata from {meta_url}") resp = requests.get(meta_url) if not resp.ok: raise ValueError(f"Unable to get metadata for tag={tag}") @@ -355,16 +356,20 @@ def check_update_initramfs(self, message: Message): Handle a request to check for initramfs updates @param message: `neon.check_update_initramfs` Message """ - branch = message.data.get("track") or self._default_branch + track = message.data.get("track") or self._default_branch + track = "beta" if track in ("dev", "beta") else "stable" try: meta = self._get_gh_release_meta_from_tag( - self._get_gh_latest_release_tag(self._default_branch)) + self._get_gh_latest_release_tag(track)) update_available = meta['initramfs']['md5'] != self.initramfs_hash except Exception as e: LOG.exception(e) - update_available = self._legacy_check_initramfs_update_available(branch) + meta = dict() + update_available = self._legacy_check_initramfs_update_available(track) self.bus.emit(message.response({"update_available": update_available, - "track": branch})) + "new_meta": meta.get('initramfs'), + "current_hash": self.initramfs_hash, + "track": track})) def check_update_squashfs(self, message: Message): """ diff --git a/tests/unit_tests.py b/tests/unit_tests.py index 70296c3..df54461 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -34,6 +34,9 @@ from os import remove from os.path import isfile, basename, join, dirname + +from ovos_bus_client import Message + from neon_phal_plugin_device_updater import DeviceUpdater from ovos_utils.messagebus import FakeBus from ovos_utils.log import LOG @@ -205,8 +208,61 @@ def test_get_gh_release_meta_from_tag(self): self.assertTrue('/opi5/' in opi_meta['download_url']) def test_check_update_initramfs(self): - # TODO - pass + self.plugin.initramfs_real_path = join(dirname(__file__), "initramfs") + with open(self.plugin.initramfs_real_path, 'w+') as f: + f.write("test") + self.plugin._initramfs_hash = None + self.plugin._build_info = {"base_os": {"name": "debian-neon-image-rpi4"}} + + self.assertEqual(self.plugin.release_repo, "NeonGeckoCom/neon-os") + + update_default = Message("neon.check_update_initramfs", {}, {}) + update_stable = Message("neon.check_update_initramfs", + {"track": "stable"}, {}) + update_beta = Message("neon.check_update_initramfs", + {"track": "beta"}, {}) + + # Test update available + + # Test stable + stable_resp = self.bus.wait_for_response(update_stable).data + self.assertTrue(stable_resp['update_available']) + self.assertEqual(stable_resp['track'], "stable") + stable_meta = stable_resp['new_meta'] + self.assertIsInstance(stable_meta['md5'], str) + self.assertIsInstance(stable_meta['path'], str) + self.assertEqual(stable_resp['current_hash'], self.plugin.initramfs_hash) + + # Test beta + beta_resp = self.bus.wait_for_response(update_beta).data + self.assertTrue(beta_resp['update_available']) + self.assertEqual(beta_resp['track'], "beta") + beta_meta = beta_resp['new_meta'] + self.assertIsInstance(beta_meta['md5'], str) + self.assertIsInstance(beta_meta['path'], str) + self.assertEqual(beta_resp['current_hash'], self.plugin.initramfs_hash) + self.assertNotEqual(stable_meta, beta_meta) + + # Test default stable + self.plugin._default_branch = "master" + default_stable = self.bus.wait_for_response(update_default).data + self.assertEqual(default_stable, stable_resp) + + # Test default beta + self.plugin._default_branch = "dev" + default_beta = self.bus.wait_for_response(update_default).data + self.assertEqual(default_beta, beta_resp) + + # Test already updated + self.plugin._initramfs_hash = stable_meta['md5'] + stable_resp = self.bus.wait_for_response(update_stable).data + self.assertFalse(stable_resp['update_available']) + + self.plugin._initramfs_hash = beta_meta['md5'] + beta_resp = self.bus.wait_for_response(update_beta).data + self.assertFalse(beta_resp['update_available']) + + remove(self.plugin.initramfs_real_path) def test_check_update_squashfs(self): # TODO From 66b3192c8aebbf0b96c69efd1e87ca23ccd41b1a Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Fri, 17 May 2024 20:00:48 +0000 Subject: [PATCH 02/12] Increment Version to 0.2.1a1 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 96062fc..22fa655 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.2.0" +__version__ = "0.2.1a1" From bb4becd45463fbe67001d0c126ee976724748062 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Fri, 17 May 2024 20:01:10 +0000 Subject: [PATCH 03/12] Update Changelog --- CHANGELOG.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68ede8e..59156c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,12 @@ # Changelog -## [0.1.1a2](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.1.1a2) (2024-04-05) +## [0.2.1a1](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.2.1a1) (2024-05-17) -[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.1.1a1...0.1.1a2) +[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.2.0...0.2.1a1) **Merged pull requests:** -- Update ovos-utils dependency spec [\#16](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/16) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [0.1.1a1](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.1.1a1) (2024-03-12) - -[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.1.0...0.1.1a1) - -**Merged pull requests:** - -- Refactor to use neon-os Releases [\#15](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/15) ([NeonDaniel](https://github.com/NeonDaniel)) +- Add data to update responses and fix branch bug [\#18](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/18) ([NeonDaniel](https://github.com/NeonDaniel)) From 6f3a29d3c8955bbb641841bbcdca07923240bbe4 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:10:48 -0700 Subject: [PATCH 04/12] Add endpoint to check download status (#19) # Description Add `neon.device_updater.get_download_status` endpoint Update tests and documentation Updates `2222.us` references to `download.neonaiservices.com` # Issues Contributes to https://github.com/NeonGeckoCom/skill-update/issues/91 # Other Notes --------- Co-authored-by: Daniel McKnight --- README.md | 13 +++++- neon_phal_plugin_device_updater/__init__.py | 34 ++++++++++---- tests/unit_tests.py | 51 +++++++++++++++++---- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 09a92c5..d75a6a0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ PHAL: initramfs_url: "https://github.com/NeonGeckoCom/neon_debos/raw/{}/overlays/02-rpi4/boot/firmware/initramfs" initramfs_path: /opt/neon/firmware/initramfs initramfs_update_path: /opt/neon/initramfs - squashfs_url: "https://2222.us/app/files/neon_images/pi/mycroft_mark_2/updates/{}/" squashfs_path: /opt/neon/update.squashfs default_track: dev ``` @@ -54,3 +53,15 @@ Check for an available InitramFS update and emit a response with data: ```python Message("neon.update_squashfs", {'track': 'dev'}) ``` + +### Get Build Info +Get metadata for currently installed build: +```python +Message("neon.device_updater.get_build_info") +``` + +### Get Download Status +Query the plugin if an update is currently downloading: +```python +Message("neon.device_updater.get_download_status") +``` diff --git a/neon_phal_plugin_device_updater/__init__.py b/neon_phal_plugin_device_updater/__init__.py index b519298..ab73662 100644 --- a/neon_phal_plugin_device_updater/__init__.py +++ b/neon_phal_plugin_device_updater/__init__.py @@ -60,6 +60,7 @@ def __init__(self, bus=None, name="neon-phal-plugin-device-updater", self._default_branch = self.config.get("default_track") or "master" self._build_info = None self._initramfs_hash = None + self._downloading = False # Register messagebus listeners self.bus.on("neon.check_update_initramfs", self.check_update_initramfs) @@ -70,13 +71,15 @@ def __init__(self, bus=None, name="neon-phal-plugin-device-updater", self.check_update_available) self.bus.on("neon.device_updater.get_build_info", self.get_build_info) + self.bus.on("neon.device_updater.get_download_status", + self.get_download_status) @property def squashfs_url(self): log_deprecation("FTP update references are deprecated.", "1.0.0") - return self.config.get("squashfs_url", "https://2222.us/app/files/" - "neon_images/pi/mycroft_mark_2/" - "updates/{}/") + return self.config.get("squashfs_url", + "https://download.neonaiservices.com/neon_os/" + "core/rpi4/updates/{}/") @property def initramfs_url(self): @@ -233,8 +236,7 @@ def _legacy_get_squashfs_latest(self, track: str = None) -> Optional[str]: return self._stream_download_file(download_url, download_path) - @staticmethod - def _stream_download_file(download_url: str, + def _stream_download_file(self, download_url: str, download_path: str) -> Optional[str]: """ Download a remote resource to a local path and return the path to the @@ -247,6 +249,7 @@ def _stream_download_file(download_url: str, # Download the update LOG.info(f"Downloading update from {download_url}") temp_dl_path = f"{download_path}.download" + self._downloading = True try: with requests.get(download_url, stream=True) as stream: with open(temp_dl_path, 'wb') as f: @@ -258,14 +261,17 @@ def _stream_download_file(download_url: str, if file_mib < 100: LOG.error(f"Downloaded file is too small ({file_mib}MiB)") remove(temp_dl_path) + self._downloading = False return shutil.move(temp_dl_path, download_path) LOG.info(f"Saved download to {download_path}") + self._downloading = False return download_path except Exception as e: LOG.exception(e) if isfile(temp_dl_path): remove(temp_dl_path) + self._downloading = False def _get_gh_latest_release_tag(self, track: str = None) -> str: """ @@ -433,13 +439,14 @@ def update_squashfs(self, message: Message): download_url = update_metadata['download_url'].replace( f"/{platform}/", f"/{platform}/updates/").replace(".img.xz", ".squashfs") - download_path = join(dirname(self.initramfs_update_path), - update_metadata['build_version']) + download_path = str(join(dirname(self.initramfs_update_path), + update_metadata['build_version'])) if isfile(download_path): LOG.info("Update already downloaded") - return download_path - update_file = self._stream_download_file(download_url, - download_path) + update_file = download_path + else: + update_file = self._stream_download_file(download_url, + download_path) except Exception as e: LOG.exception(f"Failed to get download_url: {e}") update_file = self._legacy_get_squashfs_latest(track) @@ -516,3 +523,10 @@ def get_build_info(self, message: Message): @param message: `neon.device_updater.get_build_info` Message """ self.bus.emit(message.response(self.build_info)) + + def get_download_status(self, message: Message): + """ + Handle a request to check if a download is in-progress + @param message: `neon.device_updater.get_download_status` Message + """ + self.bus.emit(message.response(data={"downloading": self._downloading})) diff --git a/tests/unit_tests.py b/tests/unit_tests.py index df54461..c3d9ecb 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -28,7 +28,8 @@ import logging import unittest from tempfile import mkstemp -from time import time +from threading import Thread +from time import time, sleep import requests @@ -277,11 +278,11 @@ def test_update_initramfs(self): pass def test_stream_download_file(self): - valid_os_url = "https://2222.us/app/files/neon_images/test_images/test_os.img.xz" - valid_update_file = "https://2222.us/app/files/neon_images/test_images/update_file.squashfs" - invalid_update_file = "https://2222.us/app/files/neon_images/test_images/metadata.json" - valid_path = "https://2222.us/app/files/neon_images/test_images/" - invalid_path = "https://2222.us/app/files/neon_images/invalid_directory/" + valid_os_url = "https://download.neonaiservices.com/test_images/test_os.img.xz" + valid_update_file = "https://download.neonaiservices.com/test_images/update_file.squashfs" + invalid_update_file = "https://download.neonaiservices.com/test_images/metadata.json" + valid_path = "https://download.neonaiservices.com/test_images/" + invalid_path = "https://download.neonaiservices.com/invalid_directory/" _, output_path = mkstemp() remove(output_path) @@ -292,14 +293,46 @@ def test_stream_download_file(self): self.assertFalse(isfile(output_path)) self.plugin._stream_download_file(invalid_path, output_path) self.assertFalse(isfile(output_path)) - - self.plugin._stream_download_file(valid_os_url, output_path) + self.assertFalse(self.plugin._downloading) + + thread = Thread(target=self.plugin._stream_download_file, + args=(valid_os_url, output_path)) + thread.start() + sleep(0.5) + self.assertTrue(self.plugin._downloading) + thread.join() self.assertTrue(isfile(output_path)) + self.assertFalse(self.plugin._downloading) remove(output_path) - self.plugin._stream_download_file(valid_update_file, output_path) + + thread = Thread(target=self.plugin._stream_download_file, + args=(valid_update_file, output_path)) + thread.start() + sleep(0.5) + self.assertTrue(self.plugin._downloading) + thread.join() self.assertTrue(isfile(output_path)) + self.assertFalse(self.plugin._downloading) remove(output_path) + def test_get_build_info(self): + resp = self.plugin.bus.wait_for_response( + Message("neon.device_updater.get_build_info")) + self.assertEqual(resp.data, self.plugin.build_info) + + def test_get_download_status(self): + self.assertFalse(self.plugin._downloading) + resp = self.plugin.bus.wait_for_response(Message( + "neon.device_updater.get_download_status")) + self.assertFalse(resp.data['downloading']) + + self.plugin._downloading = True + resp = self.plugin.bus.wait_for_response(Message( + "neon.device_updater.get_download_status")) + self.assertTrue(resp.data['downloading']) + + self.plugin._downloading = False + if __name__ == '__main__': unittest.main() From 2af3332efd6ddc94b0716d15ac94098c446f6553 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 10 Jun 2024 20:11:02 +0000 Subject: [PATCH 05/12] Increment Version to 0.2.1a2 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 22fa655..fbfc628 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.2.1a1" +__version__ = "0.2.1a2" From 83ce5f636b95831031862bed877680ff894eb67b Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 10 Jun 2024 20:11:23 +0000 Subject: [PATCH 06/12] Update Changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59156c8..5bac1a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.2.1a2](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.2.1a2) (2024-06-10) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.2.1a1...0.2.1a2) + +**Merged pull requests:** + +- Add endpoint to check download status [\#19](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/19) ([NeonDaniel](https://github.com/NeonDaniel)) + ## [0.2.1a1](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.2.1a1) (2024-05-17) [Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.2.0...0.2.1a1) From 7e728afd070a0856fdbe1fe128bbdb2ad2be160d Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Mon, 15 Jul 2024 09:51:59 -0700 Subject: [PATCH 07/12] Update to handle latest release when there are more pre-releases than the API returns --- neon_phal_plugin_device_updater/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/neon_phal_plugin_device_updater/__init__.py b/neon_phal_plugin_device_updater/__init__.py index ab73662..33ae8b9 100644 --- a/neon_phal_plugin_device_updater/__init__.py +++ b/neon_phal_plugin_device_updater/__init__.py @@ -284,14 +284,16 @@ def _get_gh_latest_release_tag(self, track: str = None) -> str: valid release """ include_prerelease = (track or self._default_branch) in ("dev", "beta") - default_time = "2000-01-01T00:00:00Z" url = f'https://api.github.com/repos/{self.release_repo}/releases' LOG.debug(f"Getting releases from {self.release_repo}. " f"prerelease={include_prerelease}") - releases: list = requests.get(url).json() if not include_prerelease: - releases = [r for r in releases if not r.get('prerelease', True)] + url = f"{url}/latest" + release = requests.get(url).json() + return release.get("tag_name") + + releases: list = requests.get(url).json() installed_os = self.build_info.get("base_os", {}).get("name") if not installed_os: raise RuntimeError(f"Unable to determine installed OS from: " From 1e7cc74877fac2cda0fde01a2b96480dff975088 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 15 Jul 2024 16:52:19 +0000 Subject: [PATCH 08/12] Increment Version to 0.2.1a3 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index fbfc628..a5f90eb 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.2.1a2" +__version__ = "0.2.1a3" From 842f2d1aa76de071ee80006580658a920f6a01e8 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:54:10 -0700 Subject: [PATCH 09/12] Fix backwards-compat branch ref in legacy update method (#21) # Description Fix branch ref used in legacy update method # Issues Addresses test failure https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/20 # Other Notes --------- Co-authored-by: Daniel McKnight --- neon_phal_plugin_device_updater/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neon_phal_plugin_device_updater/__init__.py b/neon_phal_plugin_device_updater/__init__.py index 33ae8b9..1c82e3c 100644 --- a/neon_phal_plugin_device_updater/__init__.py +++ b/neon_phal_plugin_device_updater/__init__.py @@ -125,6 +125,7 @@ def _legacy_check_initramfs_update_available(self, @return: True if a newer initramfs is available to download """ branch = branch or self._default_branch + branch = "master" if branch == "stable" else branch if not self.initramfs_url: raise RuntimeError("No initramfs_url configured") initramfs_url = self.initramfs_url.format(branch) From 7f2c6b487ba3a08962188d254bdd3aac3215862d Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 15 Jul 2024 21:54:26 +0000 Subject: [PATCH 10/12] Increment Version to 0.2.1a4 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index a5f90eb..faa4481 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.2.1a3" +__version__ = "0.2.1a4" From 9f6bb26d585da1b6608dcd92f609755ebc6f3476 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 15 Jul 2024 21:54:45 +0000 Subject: [PATCH 11/12] Update Changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bac1a7..68539e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.2.1a4](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.2.1a4) (2024-07-15) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.2.1a3...0.2.1a4) + +**Merged pull requests:** + +- Fix backwards-compat branch ref in legacy update method [\#21](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/21) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [0.2.1a3](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.2.1a3) (2024-07-15) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.2.1a2...0.2.1a3) + ## [0.2.1a2](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.2.1a2) (2024-06-10) [Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.2.1a1...0.2.1a2) From 9de6c412a354f83b1b4430ecb101019c838c6a6a Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Tue, 16 Jul 2024 20:29:22 +0000 Subject: [PATCH 12/12] Increment Version to 0.3.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index faa4481..bb6d588 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.2.1a4" +__version__ = "0.3.0"