Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
987765d
Merge pull request #2849 from intelowlproject/develop
drosetti May 20, 2025
067ab3a
Add update checker management command, settings, and docs
lvb05 Dec 20, 2025
618f293
Format update checker with black and branch update
lvb05 Dec 31, 2025
eef5c15
Fix linting issues and improve update checker command output
lvb05 Jan 8, 2026
7c59a39
Fix ruff linting issues
lvb05 Jan 8, 2026
47ac868
Restore README.md (remove update checker documentation)
lvb05 Jan 10, 2026
0b12155
Fix formatting in update checker management command
lvb05 Jan 12, 2026
f07a054
Add persistent update check status model
lvb05 Jan 13, 2026
21dd0e2
Fix trailing whitespace in UpdateCheckStatus docstring
lvb05 Jan 13, 2026
14c37da
Use persistent state for update checks and add Celery task
lvb05 Jan 15, 2026
bfce6ca
Add admin GUI notification for update availability and formatting
lvb05 Jan 15, 2026
89eabf3
Add IntelOwl update check system with model, migration, scheduler and…
lvb05 Jan 25, 2026
a7f3f40
Merge branch 'develop' into feature/update-checker-2876
lvb05 Jan 25, 2026
95671b2
style: fix import ordering per ruff
lvb05 Jan 25, 2026
7b9fb40
fix: apply black formatting on tasks.py
lvb05 Jan 25, 2026
024d0a2
fix(update-checker): safe lazy imports and robust version handling
lvb05 Jan 25, 2026
ba4e056
fix(update-checker): prevent duplicate notifications and improve vers…
lvb05 Jan 25, 2026
0c9db74
Restore intel_owl/tasks.py to upstream version
lvb05 Jan 27, 2026
8839e99
Add automated update check system with celery task, model, command an…
lvb05 Jan 27, 2026
471dc63
fix: use __latest__ for django_celery_beat migration dependency
lvb05 Jan 27, 2026
c1681ba
Fix migration 0072: use literal 'days' instead of IntervalSchedule.DA…
lvb05 Jan 27, 2026
82398e2
Fix update-check notifications to work correctly with transactions an…
lvb05 Jan 28, 2026
385ef00
Fix update checker tests to use UserEventQuerySet notifications and i…
lvb05 Jan 28, 2026
7e17a87
Merge branch 'develop' into feature/update-checker-2876
lvb05 Jan 30, 2026
bff38e5
Apply Black formatting after rebase
lvb05 Jan 30, 2026
26f956b
Fix ruff formatting issue in commons settings
lvb05 Jan 30, 2026
c570770
Fix ruff formatting
lvb05 Jan 30, 2026
6f43f7e
.
lvb05 Jan 30, 2026
034ea13
.
lvb05 Jan 30, 2026
9bc6ee1
Add system update panel and API endpoint
lvb05 Feb 1, 2026
81a1e98
Fix lint issues and adjust tests
lvb05 Feb 1, 2026
09f9796
Add system update panel UI and fix lint/format issues
lvb05 Feb 2, 2026
eae37cf
Fix Prettier formatting for update checker components
lvb05 Feb 2, 2026
a554277
correct formatting and comments
lvb05 Feb 6, 2026
a8dec4f
system update notification show up on Home
lvb05 Feb 6, 2026
c1364d1
style change in notification
lvb05 Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api_app/core/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from celery import shared_task

from api_app.core.update_checker import check_for_update


@shared_task(name="intelowl.scheduled_update_check")
def scheduled_update_check():
"""
Periodic task to check for IntelOwl updates.
Intended to be triggered via celery beat.
"""
check_for_update()
129 changes: 129 additions & 0 deletions api_app/core/update_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import logging

import requests
from django.conf import settings
from django.utils.timezone import now

from api_app.models import UpdateCheckStatus
from api_app.user_events_manager.queryset import UserEventQuerySet

logger = logging.getLogger(__name__)


def normalize_version(v):
"""
Convert '1.2.3' → (1, 2, 3) so versions can be compared.
Stops at the first non-numeric part.
"""
parts = []
for x in v.split("."):
if x.isdigit():
parts.append(int(x))
else:
break
return tuple(parts)


def fetch_latest_version():
"""
Fetch the latest IntelOwl version string from the update URL.
Returns a version string without any leading 'v', or None on error.
"""
update_url = getattr(settings, "UPDATE_CHECK_URL", None)

if not update_url:
return None, "UPDATE_CHECK_URL not configured"

try:
resp = requests.get(
update_url,
headers={"User-Agent": "IntelOwl-Update-Checker"},
timeout=5,
)
resp.raise_for_status()
except requests.RequestException as exc:
logger.error(f"update check failed: {exc}")
return None, "Failed to fetch release information"

try:
data = resp.json()
except ValueError:
logger.error("invalid JSON in update response")
return None, "Invalid response from update server"

tag = data.get("tag_name")
if not tag:
return None, "Release response missing tag_name"

return tag.lstrip("v"), None


def check_for_update():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before putting this into a cron, we need to add also unittests for all the possible cases here.

"""
Compare the running IntelOwl version with the latest available one.

Returns:
(success: bool, message: str)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use type hints too please

"""
current_version_str = getattr(settings, "INTEL_OWL_VERSION", None)
if not current_version_str:
return False, "INTEL_OWL_VERSION setting missing"

latest_str, error = fetch_latest_version()
if error:
return False, error

current_version_str = str(current_version_str).lstrip("v")

current = normalize_version(current_version_str)
latest = normalize_version(latest_str)

state, _ = UpdateCheckStatus.objects.get_or_create(pk=1)
state.last_checked_at = now()

if not current or not latest:
state.save(update_fields=["last_checked_at"])
if latest_str != current_version_str:
return (
True,
f"Update available: {latest_str} (current: {current_version_str})",
)
return True, f"IntelOwl version up to date ({current_version_str})"

if latest > current:
if state.latest_version != latest_str or not state.notified:
logger.warning(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

info is better

"New IntelOwl version available: %s (current: %s)",
latest_str,
current_version_str,
)

UserEventQuerySet.notify_admins(
title="New IntelOwl version available",
message=(
f"Version {latest_str} is available "
f"(current: {current_version_str})"
),
persistent=True,
severity="warning",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

info

)
Comment on lines 73 to 78
Copy link
Contributor

@fgibertoni fgibertoni Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this create duplicate notifications if the command in ran twice ? You can check if the version has already been notified.


state.latest_version = latest_str
state.notified = True

state.save(update_fields=["latest_version", "notified", "last_checked_at"])
return (
True,
f"New IntelOwl version available: {latest_str} "
f"(current: {current_version_str})",
)

state.save(update_fields=["last_checked_at"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a duplicated save if the logic goes inside the previous if.


if latest < current:
return (
True,
f"Local version ahead of release: " f"{current_version_str} > {latest_str}",
)

return True, f"IntelOwl version up to date ({current_version_str})"
15 changes: 15 additions & 0 deletions api_app/management/commands/check_updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.core.management.base import BaseCommand

from api_app.core.update_checker import check_for_update


class Command(BaseCommand):
help = "Check for newer IntelOwl releases"

def handle(self, *args, **options):
success, message = check_for_update()

if success:
self.stdout.write(self.style.SUCCESS(message))
else:
self.stdout.write(self.style.ERROR(message))
38 changes: 38 additions & 0 deletions api_app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,44 @@
logger = logging.getLogger(__name__)


class UpdateCheckStatus(models.Model):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should also be a migration

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has not been addressed

"""
Stores global state for IntelOwl update checks.
This model is intended to be used as a singleton (accessed via get_or_create(pk=1)).
Ensures that update notifications are emitted only once per version.
"""

latest_version = models.CharField(
max_length=50,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe too much?

null=True,
blank=True,
help_text="Latest version detected during update check",
)
notified = models.BooleanField(
default=False,
help_text="Whether a notification has already been sent for this version",
)
last_checked_at = models.DateTimeField(
null=True,
blank=True,
help_text="Last time the update check was executed",
)

created_at = models.DateTimeField(default=now, editable=False)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
verbose_name = "Update check status"
verbose_name_plural = "Update check status"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are identical


def __str__(self) -> str:
return (
f"UpdateCheckStatus("
f"latest_version={self.latest_version}, "
f"notified={self.notified})"
)


class PythonModule(models.Model):
"""
Represents a Python module model used in the application.
Expand Down
7 changes: 7 additions & 0 deletions intel_owl/settings/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,10 @@
GIT_SSH_SCRIPT_PATH = (
PROJECT_LOCATION / "api_app" / "analyzers_manager" / "ssh_gitpython.sh"
)

# Update checker settings
UPDATE_CHECK_URL = get_secret(
"UPDATE_CHECK_URL",
"https://api.github.com/repos/intelowlproject/IntelOwl/releases/latest",
)
INTEL_OWL_VERSION = VERSION
Loading