diff --git a/archivebox/config.py b/archivebox/config.py
index 775be8e4..e0a1ccf5 100644
--- a/archivebox/config.py
+++ b/archivebox/config.py
@@ -30,6 +30,7 @@ import inspect
import getpass
import platform
import shutil
+import requests
import django
from sqlite3 import dbapi2 as sqlite3
@@ -397,6 +398,55 @@ def get_commit_hash(config):
except Exception:
return None
+def get_version_releases(config):
+ """
+ returns a dictionary containing the GitHub release data for
+ the recommended upgrade version and the currently installed version
+ """
+ github_releases_api = "https://api.github.com/repos/pirate/archivebox/releases"
+ response = requests.get(github_releases_api)
+ if response.status_code != 200:
+ stderr('Failed to get release data from GitHub', color='lightyellow', config=config)
+ return None
+
+ releases = response.json()
+ installed_version = config['VERSION']
+ installed_version_parts = parse_tag_name(installed_version)
+
+ # find current version or nearest older version (to link to)
+ current_version = None
+ for i, release in enumerate(releases):
+ release_parts = parse_tag_name(release["tag_name"])
+ if compare_versions(release["tag_name"], installed_version) <= 0:
+ current_version = release
+ break
+
+ current_version = current_version if current_version else releases[-1]
+
+ # find upgrade version
+ upgrade_version = None
+ smallest_version_diff = parse_tag_name(releases[0]["tag_name"])[1]
+ for i, release in enumerate(releases):
+ release_parts = parse_tag_name(release["tag_name"])
+ major_version_diff = release_parts[1] - installed_version_parts[1]
+ if major_version_diff < smallest_version_diff:
+ smallest_version_diff = major_version_diff
+ if smallest_version_diff < 1:
+ break
+ upgrade_version = release
+
+ upgrade_version = upgrade_version if upgrade_version else releases[0]
+
+ return {"upgrade_version": upgrade_version, "current_version": current_version}
+
+def can_upgrade(config):
+ if config['VERSION_RELEASES']:
+ upgrade_version_tag = config['VERSION_RELEASES']['upgrade_version']['tag_name']
+ current_version_tag = config['VERSION_RELEASES']['current_version']['tag_name']
+ return compare_versions(upgrade_version_tag, current_version_tag) == 1
+ return False
+
+
############################## Derived Config ##################################
@@ -424,6 +474,8 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'ARCHIVEBOX_BINARY': {'default': lambda c: sys.argv[0] or bin_path('archivebox')},
'VERSION': {'default': lambda c: get_version(c)},
+ 'VERSION_RELEASES': {'default': lambda c: get_version_releases(c)},
+ 'CAN_UPGRADE': {'default': lambda c: can_upgrade(c)},
'COMMIT_HASH': {'default': lambda c: get_commit_hash(c)},
'PYTHON_BINARY': {'default': lambda c: sys.executable},
@@ -696,6 +748,28 @@ def load_config(defaults: ConfigDefaultDict,
# with open(os.path.join(config['OUTPUT_DIR'], CONFIG_FILENAME), 'w+') as f:
+def parse_tag_name(v):
+ """parses a version tag string formatted like 'vx.x.x'"""
+ v = re.sub(r"\+.*$", "", v) # in case version string ends with '+editable'
+ parts = re.sub(r"^v", "", v).split(".")
+ return [int(p) for p in parts]
+
+
+def compare_versions(v1, v2):
+ """
+ for two version strings v1 and v2, returns 1 if v1 is newer than v2,
+ 0 if they're equivalent and -1 if v1 is older than v2.
+ """
+ v1Parts = parse_tag_name(v1)
+ v2Parts = parse_tag_name(v2)
+ for i in range(len(v1Parts)):
+ if v1Parts[i] < v2Parts[i]:
+ return -1
+
+ if v1Parts[i] > v2Parts[i]:
+ return 1
+ return 0
+
# Logging Helpers
def stdout(*args, color: Optional[str]=None, prefix: str='', config: Optional[ConfigDict]=None) -> None:
diff --git a/archivebox/core/urls.py b/archivebox/core/urls.py
index 90544dbd..1520710c 100644
--- a/archivebox/core/urls.py
+++ b/archivebox/core/urls.py
@@ -8,7 +8,7 @@ from django.views.generic.base import RedirectView
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView, HealthCheckView
-from config import VERSION
+from config import VERSION, VERSION_RELEASES, CAN_UPGRADE
# print('DEBUG', settings.DEBUG)
@@ -31,7 +31,7 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
- path('admin/', admin.site.urls, {'extra_context': {'VERSION': VERSION}}),
+ path('admin/', admin.site.urls, {'extra_context': {'VERSION': VERSION, 'VERSION_RELEASES': VERSION_RELEASES, 'CAN_UPGRADE': CAN_UPGRADE}}),
path('health/', HealthCheckView.as_view(), name='healthcheck'),
path('error/', lambda _: 1/0),
diff --git a/archivebox/templates/admin/base.html b/archivebox/templates/admin/base.html
index b0979db5..7b00fae5 100644
--- a/archivebox/templates/admin/base.html
+++ b/archivebox/templates/admin/base.html
@@ -12,7 +12,26 @@
{% endblock %}
- {% block extrastyle %}{% endblock %}
+ {% block extrastyle %}
+
+ {% endblock %}
{% if LANGUAGE_BIDI %}
@@ -123,99 +142,39 @@