├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ ├── create-release-and-upload-assets.yml │ └── lint-and-test.yml ├── .gitignore ├── AUTHORS ├── BUILD.md ├── COPYING ├── INSTALL.md ├── MANIFEST.in ├── README.md ├── TODO ├── VERSION.txt ├── arch ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── utils.py └── views.py ├── client ├── patchman-client └── patchman-client.conf ├── debian ├── .gitignore ├── README.Debian ├── changelog ├── compat ├── control ├── copyright ├── gbp.conf ├── patchman-client.install ├── python3-patchman.cron.daily ├── python3-patchman.install ├── python3-patchman.postinst ├── python3-patchman.prerm ├── rules └── source │ └── format ├── domains ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py └── views.py ├── errata ├── __init__.py ├── admin.py ├── apps.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_erratumreference_unique_together.py │ ├── 0003_delete_erratumreference_alter_erratum_references.py │ ├── 0004_rename_packages_erratum_fixed_packages.py │ ├── 0005_erratum_affected_packages_and_more.py │ ├── 0006_alter_erratum_options.py │ ├── 0007_alter_erratum_fixed_packages.py │ └── __init__.py ├── models.py ├── serializers.py ├── sources │ ├── __init__.py │ ├── distros │ │ ├── __init__.py │ │ ├── alma.py │ │ ├── arch.py │ │ ├── centos.py │ │ ├── debian.py │ │ ├── rocky.py │ │ └── ubuntu.py │ └── repos │ │ ├── __init__.py │ │ └── yum.py ├── tasks.py ├── templates │ └── errata │ │ ├── erratum_detail.html │ │ ├── erratum_list.html │ │ └── erratum_table.html ├── urls.py ├── utils.py └── views.py ├── etc ├── patchman │ ├── apache.conf.example │ ├── celery.conf │ └── local_settings.py └── systemd │ └── system │ └── patchman-celery.service ├── hooks ├── apt │ └── 05patchman ├── dnf │ └── patchman.action ├── yum │ ├── patchman.conf │ └── patchman.py └── zypper │ └── patchman.py ├── hosts ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_host_modules.py │ ├── 0004_remove_host_tags_host_tags.py │ ├── 0005_rename_os_host_osvariant.py │ ├── 0006_migrate_to_tz_aware.py │ ├── 0007_alter_host_tags.py │ ├── 0008_alter_host_options.py │ ├── 0009_host_errata.py │ └── __init__.py ├── models.py ├── serializers.py ├── tasks.py ├── templates │ └── hosts │ │ ├── host_delete.html │ │ ├── host_detail.html │ │ ├── host_edit.html │ │ ├── host_list.html │ │ └── host_table.html ├── templatetags │ ├── __init__.py │ └── report_alert.py ├── urls.py ├── utils.py └── views.py ├── manage.py ├── modules ├── __init__.py ├── admin.py ├── apps.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20240204_2214.py │ ├── 0003_alter_module_options.py │ ├── 0004_alter_module_options.py │ └── __init__.py ├── models.py ├── serializers.py ├── templates │ └── modules │ │ ├── module_detail.html │ │ ├── module_list.html │ │ └── module_table.html ├── urls.py ├── utils.py └── views.py ├── operatingsystems ├── __init__.py ├── admin.py ├── apps.py ├── fixtures │ ├── os.json │ └── osgroup.json ├── forms.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_os_arch.py │ ├── 0003_osgroup_codename.py │ ├── 0004_alter_osgroup_unique_together.py │ ├── 0005_rename_osgroup_osrelease_rename_os_osvariant_and_more.py │ ├── 0006_osrelease_cpe_name_osvariant_codename.py │ ├── 0007_alter_osrelease_unique_together.py │ ├── 0008_alter_osrelease_options_alter_osvariant_options.py │ └── __init__.py ├── models.py ├── serializers.py ├── templates │ └── operatingsystems │ │ ├── operatingsystemrelease_table.html │ │ ├── operatingsystemvariant_table.html │ │ ├── os_landing.html │ │ ├── osrelease_delete.html │ │ ├── osrelease_detail.html │ │ ├── osrelease_list.html │ │ ├── osvariant_delete.html │ │ ├── osvariant_delete_multiple.html │ │ ├── osvariant_detail.html │ │ └── osvariant_list.html ├── urls.py ├── utils.py └── views.py ├── packages ├── __init__.py ├── admin.py ├── apps.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20250207_1319.py │ ├── 0002_delete_erratum_delete_erratumreference.py │ ├── 0003_auto_20250207_1746.py │ ├── 0004_alter_package_options_alter_packagecategory_options_and_more.py │ ├── 0005_alter_package_packagetype.py │ └── __init__.py ├── models.py ├── serializers.py ├── templates │ └── packages │ │ ├── package_detail.html │ │ ├── package_list.html │ │ ├── package_name_detail.html │ │ ├── package_name_list.html │ │ ├── package_name_table.html │ │ └── package_table.html ├── urls.py ├── utils.py └── views.py ├── patchman-client.spec ├── patchman ├── __init__.py ├── celery.py ├── receivers.py ├── settings.py ├── signals.py ├── sqlite3 │ └── base.py ├── static │ ├── css │ │ └── base.css │ ├── img │ │ ├── icon-alert.gif │ │ ├── icon-no.gif │ │ └── icon-yes.gif │ └── js │ │ ├── ajax-jquery.js │ │ ├── button-post.js │ │ └── expandable-text.js ├── urls.py └── wsgi.py ├── reports ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_report_modules.py │ ├── 0003_remove_report_accessed.py │ ├── 0004_migrate_to_tz_aware.py │ ├── 0005_alter_report_options.py │ └── __init__.py ├── models.py ├── tasks.py ├── templates │ └── reports │ │ ├── report.txt │ │ ├── report_delete.html │ │ ├── report_detail.html │ │ ├── report_list.html │ │ └── report_table.html ├── urls.py ├── utils.py └── views.py ├── repos ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_repository_repotype.py │ ├── 0003_migrate_to_tz_aware.py │ ├── 0004_rename_file_checksum_mirror_package_checksum.py │ ├── 0005_rename_package_checksum_mirror_packages_checksum.py │ ├── 0006_mirror_errata_checksum_mirror_modules_checksum.py │ └── __init__.py ├── models.py ├── repo_types │ ├── __init__.py │ ├── arch.py │ ├── deb.py │ ├── gentoo.py │ ├── rpm.py │ ├── yast.py │ └── yum.py ├── serializers.py ├── tasks.py ├── templates │ └── repos │ │ ├── mirror_delete.html │ │ ├── mirror_detail.html │ │ ├── mirror_edit.html │ │ ├── mirror_edit_repo.html │ │ ├── mirror_list.html │ │ ├── mirror_table.html │ │ ├── mirror_with_repo_list.html │ │ ├── repo_delete.html │ │ ├── repo_detail.html │ │ ├── repo_edit.html │ │ ├── repo_list.html │ │ └── repository_table.html ├── templatetags │ ├── __init__.py │ └── repo_buttons.py ├── urls.py ├── utils.py └── views.py ├── requirements.txt ├── sbin ├── patchman ├── patchman-manage └── patchman-set-secret-key ├── scripts ├── clear-django-logs.py ├── create_graph.sh ├── rpm-install.sh └── rpm-post-install.sh ├── security ├── __init__.py ├── admin.py ├── apps.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_cve_options.py │ ├── 0003_alter_cve_description_alter_cwe_description.py │ ├── 0004_alter_cve_options.py │ ├── 0005_reference_cve_references.py │ ├── 0006_alter_cve_options_alter_cvss_unique_together.py │ ├── 0007_remove_cve_title.py │ └── __init__.py ├── models.py ├── serializers.py ├── tasks.py ├── templates │ └── security │ │ ├── cve_detail.html │ │ ├── cve_list.html │ │ ├── cve_table.html │ │ ├── cwe_detail.html │ │ ├── cwe_list.html │ │ ├── cwe_table.html │ │ ├── reference_list.html │ │ ├── reference_table.html │ │ └── security_landing.html ├── urls.py ├── utils.py └── views.py ├── setup.cfg ├── setup.py ├── tox.ini └── util ├── __init__.py ├── apps.py ├── filterspecs.py ├── tasks.py ├── templates ├── 404.html ├── 500.html ├── base.html ├── dashboard.html ├── navbar.html ├── objectlist.html ├── registration │ └── login.html └── searchbar.html ├── templatetags ├── __init__.py └── common.py ├── urls.py └── views.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: furlongm 2 | patreon: furlongm 3 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code Scanning - Action" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | CodeQL-Build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | - name: Initialize CodeQL 16 | uses: github/codeql-action/init@v3 17 | - name: Autobuild 18 | uses: github/codeql-action/autobuild@v3 19 | - name: Perform CodeQL Analysis 20 | uses: github/codeql-action/analyze@v3 21 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Lint and test 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | 7 | jobs: 8 | lint-and-test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | max-parallel: 5 12 | matrix: 13 | python-version: ['3.x'] 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | sudo apt update 23 | sudo apt -y install libxslt-dev libxml2-dev 24 | python -m pip install --upgrade pip setuptools 25 | pip install -r requirements.txt 26 | - name: Lint with flake8 27 | run: | 28 | pip install flake8 29 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 30 | flake8 . --count --max-line-length=120 --show-source --statistics 31 | - name: Set secret key 32 | run: ./sbin/patchman-set-secret-key 33 | - name: Test with django 34 | run: | 35 | pip install legacy-cgi 36 | ./manage.py test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.orig 3 | *.rej 4 | tmp* 5 | *.pyc 6 | *.pyo 7 | *~ 8 | #* 9 | .svn 10 | .tox 11 | patchman.egg-info 12 | build 13 | dist 14 | run 15 | pyvenv.cfg 16 | .vscode 17 | .venv 18 | *.xml 19 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Marcus Furlong 2 | Sam Morisson 3 | Andrew Spiers 4 | Russell Sim 5 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # Bump version and tag release 2 | 3 | ```shell 4 | vim VERSION.txt # modify version 5 | git add VERSION.txt 6 | version=$( 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from arch.models import PackageArchitecture, MachineArchitecture 20 | 21 | admin.site.register(PackageArchitecture) 22 | admin.site.register(MachineArchitecture) 23 | -------------------------------------------------------------------------------- /arch/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class ArchConfig(AppConfig): 21 | name = 'arch' 22 | -------------------------------------------------------------------------------- /arch/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='MachineArchitecture', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=255, unique=True)), 19 | ], 20 | options={ 21 | 'verbose_name': 'Machine Architecture', 22 | 'abstract': False, 23 | }, 24 | ), 25 | migrations.CreateModel( 26 | name='PackageArchitecture', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('name', models.CharField(max_length=255, unique=True)), 30 | ], 31 | options={ 32 | 'verbose_name': 'Package Architecture', 33 | 'abstract': False, 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /arch/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/arch/migrations/__init__.py -------------------------------------------------------------------------------- /arch/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.db import models 19 | 20 | 21 | class Architecture(models.Model): 22 | 23 | name = models.CharField(unique=True, max_length=255) 24 | 25 | class Meta: 26 | abstract = True 27 | 28 | def __str__(self): 29 | return self.name 30 | 31 | 32 | class MachineArchitecture(Architecture): 33 | 34 | class Meta(Architecture.Meta): 35 | verbose_name = 'Machine Architecture' 36 | 37 | 38 | class PackageArchitecture(Architecture): 39 | 40 | class Meta(Architecture.Meta): 41 | verbose_name = 'Package Architecture' 42 | -------------------------------------------------------------------------------- /arch/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from arch.models import PackageArchitecture, MachineArchitecture 20 | 21 | 22 | class PackageArchitectureSerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = PackageArchitecture 25 | fields = ('id', 'name') 26 | 27 | 28 | class MachineArchitectureSerializer(serializers.HyperlinkedModelSerializer): 29 | class Meta: 30 | model = MachineArchitecture 31 | fields = ('id', 'name') 32 | -------------------------------------------------------------------------------- /arch/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from arch.models import PackageArchitecture, MachineArchitecture 18 | from patchman.signals import info_message 19 | 20 | 21 | def clean_package_architectures(): 22 | """ Remove package architectures that are no longer in use 23 | """ 24 | parches = PackageArchitecture.objects.filter(package__isnull=True) 25 | plen = parches.count() 26 | if plen == 0: 27 | info_message.send(sender=None, text='No orphaned PackageArchitectures found.') 28 | else: 29 | info_message.send(sender=None, text=f'Removing {plen} orphaned PackageArchitectures') 30 | parches.delete() 31 | 32 | 33 | def clean_machine_architectures(): 34 | """ Remove machine architectures that are no longer in use 35 | """ 36 | marches = MachineArchitecture.objects.filter( 37 | host__isnull=True, 38 | repository__isnull=True, 39 | ) 40 | mlen = marches.count() 41 | if mlen == 0: 42 | info_message.send(sender=None, text='No orphaned MachineArchitectures found.') 43 | else: 44 | info_message.send(sender=None, text=f'Removing {mlen} orphaned MachineArchitectures') 45 | marches.delete() 46 | 47 | 48 | def clean_architectures(): 49 | """ Remove architectures that are no longer in use 50 | """ 51 | clean_package_architectures() 52 | clean_machine_architectures() 53 | -------------------------------------------------------------------------------- /arch/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import viewsets 18 | 19 | from arch.models import PackageArchitecture, MachineArchitecture 20 | from arch.serializers import PackageArchitectureSerializer, \ 21 | MachineArchitectureSerializer 22 | 23 | 24 | class PackageArchitectureViewSet(viewsets.ModelViewSet): 25 | """ 26 | API endpoint that allows package architectures to be viewed or edited. 27 | """ 28 | queryset = PackageArchitecture.objects.all() 29 | serializer_class = PackageArchitectureSerializer 30 | filterset_fields = ['name'] 31 | 32 | 33 | class MachineArchitectureViewSet(viewsets.ModelViewSet): 34 | """ 35 | API endpoint that allows machine architectures to be viewed or edited. 36 | """ 37 | queryset = MachineArchitecture.objects.all() 38 | serializer_class = MachineArchitectureSerializer 39 | filterset_fields = ['name'] 40 | -------------------------------------------------------------------------------- /client/patchman-client.conf: -------------------------------------------------------------------------------- 1 | # Patchman server 2 | server=https://patchman.example.com 3 | 4 | # Options to curl 5 | curl_options="--insecure --connect-timeout 60 --max-time 300" 6 | 7 | # Space delimited tags to send 8 | tags="Server" 9 | 10 | # Does the client output a report of the upload (e.g. for cronjob output) 11 | report=false 12 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | pycompat 2 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: patchman 3 | Upstream-Contact: Marcus Furlong 4 | Source: https://github.com/furlongm/patchman 5 | 6 | Files: * 7 | Copyright: 2011-2012 VPAC http://www.vpac.org 8 | 2013-2021 Marcus Furlong 9 | License: GPL-3.0 10 | This package is free software; you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation; version 3 only. 13 | . 14 | This package is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | . 19 | You should have received a copy of the GNU General Public License 20 | along with this package; if not, see . 21 | . 22 | On Debian systems, the complete text of the GNU General 23 | Public License can be found in `/usr/share/common-licenses/GPL-3'. 24 | -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | upstream-tag = v%(version)s 3 | distribution = stable 4 | ignore-branch = True 5 | 6 | [buildpackage] 7 | ignore-new = True 8 | force-create = True 9 | 10 | [dch] 11 | git-author = True 12 | -------------------------------------------------------------------------------- /debian/patchman-client.install: -------------------------------------------------------------------------------- 1 | client/patchman-client.conf etc/patchman 2 | client/patchman-client usr/sbin 3 | hooks/apt/05patchman etc/apt/apt.conf.d 4 | -------------------------------------------------------------------------------- /debian/python3-patchman.cron.daily: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/bin/patchman -a -q 4 | -------------------------------------------------------------------------------- /debian/python3-patchman.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | etc/patchman/apache.conf.example => etc/apache2/conf-available/patchman.conf 3 | etc/patchman/local_settings.py etc/patchman 4 | etc/systemd/system/patchman-celery.service => lib/systemd/system/patchman-celery.service 5 | -------------------------------------------------------------------------------- /debian/python3-patchman.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | #DEBHELPER# 4 | 5 | if [ "$1" = "configure" ] ; then 6 | 7 | if ! grep /usr/lib/python3/dist-packages/patchman /etc/apache2/conf-available/patchman.conf >/dev/null 2>&1 ; then 8 | sed -i -e "s/^\(Define patchman_pythonpath\).*/\1 \/usr\/lib\/python3\/dist-packages/" \ 9 | /etc/apache2/conf-available/patchman.conf 10 | fi 11 | 12 | . /usr/share/apache2/apache2-maintscript-helper 13 | apache2_invoke enconf patchman.conf 14 | 15 | patchman-set-secret-key 16 | chown www-data /etc/patchman/local_settings.py 17 | 18 | mkdir -p /var/lib/patchman/db 19 | patchman-manage collectstatic --noinput 20 | 21 | patchman-manage makemigrations 22 | patchman-manage migrate --run-syncdb --fake-initial 23 | sqlite3 /var/lib/patchman/db/patchman.db 'PRAGMA journal_mode=WAL;' 24 | 25 | chown -R www-data:www-data /var/lib/patchman 26 | adduser --system --group patchman-celery 27 | usermod -a -G www-data patchman-celery 28 | chmod g+w /var/lib/patchman /var/lib/patchman/db /var/lib/patchman/db/patchman.db 29 | 30 | echo 31 | echo "Remember to run 'patchman-manage createsuperuser' to create a user." 32 | echo 33 | fi 34 | -------------------------------------------------------------------------------- /debian/python3-patchman.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [ "$1" = "remove" ] ; then 4 | . /usr/share/apache2/apache2-maintscript-helper 5 | apache2_invoke disconf patchman.conf 6 | fi 7 | 8 | if [ "$1" = "purge" ] ; then 9 | rm -f /etc/apache2/conf-available/patchman.conf 10 | rm -fr /var/lib/patchman 11 | rm -fr /etc/patchman 12 | fi 13 | 14 | #DEBHELPER# 15 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | clean:: 5 | find -name *.pyc -exec rm {} \; 6 | rm -rf debian/python3-patchman build MANIFEST dist .pybuild patchman.egg-info .tox 7 | dh_clean 8 | 9 | export PYBUILD_NAME=patchman 10 | 11 | %: 12 | dh $@ --with=python3 --buildsystem=pybuild --with=systemd 13 | 14 | override_dh_auto_test: 15 | true 16 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/domains/__init__.py -------------------------------------------------------------------------------- /domains/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from domains.models import Domain 20 | 21 | admin.site.register(Domain) 22 | -------------------------------------------------------------------------------- /domains/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class DomainsConfig(AppConfig): 21 | name = 'domains' 22 | -------------------------------------------------------------------------------- /domains/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Domain', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=255, unique=True)), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /domains/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/domains/migrations/__init__.py -------------------------------------------------------------------------------- /domains/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.db import models 19 | 20 | 21 | class Domain(models.Model): 22 | 23 | name = models.CharField(max_length=255, unique=True) 24 | 25 | def __str__(self): 26 | return self.name 27 | -------------------------------------------------------------------------------- /domains/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from domains.models import Domain 20 | 21 | 22 | class DomainSerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = Domain 25 | fields = ('id', 'name') 26 | -------------------------------------------------------------------------------- /domains/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import viewsets 18 | 19 | from domains.models import Domain 20 | from domains.serializers import DomainSerializer 21 | 22 | 23 | class DomainViewSet(viewsets.ModelViewSet): 24 | """ 25 | API endpoint that allows package architectures to be viewed or edited. 26 | """ 27 | queryset = Domain.objects.all() 28 | serializer_class = DomainSerializer 29 | filterset_fields = ['name'] 30 | -------------------------------------------------------------------------------- /errata/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/errata/__init__.py -------------------------------------------------------------------------------- /errata/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.contrib import admin 18 | from errata.models import Erratum 19 | 20 | 21 | class ErratumAdmin(admin.ModelAdmin): 22 | readonly_fields = ('affected_packages', 'fixed_packages', 'references') 23 | 24 | 25 | admin.site.register(Erratum, ErratumAdmin) 26 | -------------------------------------------------------------------------------- /errata/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class ErrataConfig(AppConfig): 21 | name = 'errata' 22 | -------------------------------------------------------------------------------- /errata/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.db import models 18 | 19 | 20 | class ErratumManager(models.Manager): 21 | def get_queryset(self): 22 | return super().get_queryset().select_related() 23 | -------------------------------------------------------------------------------- /errata/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.18 on 2025-02-08 20:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ('packages', '0002_delete_erratum_delete_erratumreference'), 12 | ('operatingsystems', '0005_rename_osgroup_osrelease_rename_os_osvariant_and_more'), 13 | ('security', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='ErratumReference', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('er_type', models.CharField(max_length=255)), 22 | ('url', models.URLField(max_length=765)), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='Erratum', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('name', models.CharField(max_length=255, unique=True)), 30 | ('e_type', models.CharField(max_length=255)), 31 | ('issue_date', models.DateTimeField()), 32 | ('synopsis', models.CharField(max_length=255)), 33 | ('cves', models.ManyToManyField(blank=True, to='security.cve')), 34 | ('osreleases', models.ManyToManyField(blank=True, to='operatingsystems.osrelease')), 35 | ('packages', models.ManyToManyField(blank=True, to='packages.package')), 36 | ('references', models.ManyToManyField(blank=True, to='errata.erratumreference')), 37 | ], 38 | options={ 39 | 'verbose_name': 'Erratum', 40 | 'verbose_name_plural': 'Errata', 41 | }, 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /errata/migrations/0002_alter_erratumreference_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-05 01:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('errata', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name='erratumreference', 15 | unique_together={('er_type', 'url')}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /errata/migrations/0003_delete_erratumreference_alter_erratum_references.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-05 19:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0005_reference_cve_references'), 10 | ('errata', '0002_alter_erratumreference_unique_together'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='erratum', 16 | name='references', 17 | ), 18 | migrations.DeleteModel( 19 | name='ErratumReference', 20 | ), 21 | migrations.AddField( 22 | model_name='erratum', 23 | name='references', 24 | field=models.ManyToManyField(blank=True, to='security.Reference'), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /errata/migrations/0004_rename_packages_erratum_fixed_packages.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-06 04:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('errata', '0003_delete_erratumreference_alter_erratum_references'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='erratum', 15 | old_name='packages', 16 | new_name='fixed_packages', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /errata/migrations/0005_erratum_affected_packages_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-06 05:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('packages', '0004_alter_package_options_alter_packagecategory_options_and_more'), 10 | ('errata', '0004_rename_packages_erratum_fixed_packages'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='erratum', 16 | name='affected_packages', 17 | field=models.ManyToManyField(blank=True, related_name='affected_by_erratum', to='packages.package'), 18 | ), 19 | migrations.AlterField( 20 | model_name='erratum', 21 | name='fixed_packages', 22 | field=models.ManyToManyField(blank=True, related_name='fixed_by_erratum', to='packages.package'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /errata/migrations/0006_alter_erratum_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-07 03:06 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('errata', '0005_erratum_affected_packages_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='erratum', 15 | options={'ordering': ['-issue_date', 'name'], 'verbose_name': 'Erratum', 'verbose_name_plural': 'Errata'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /errata/migrations/0007_alter_erratum_fixed_packages.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-10 23:49 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('packages', '0005_alter_package_packagetype'), 10 | ('errata', '0006_alter_erratum_options'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='erratum', 16 | name='fixed_packages', 17 | field=models.ManyToManyField(blank=True, related_name='provides_fix_in_erratum', to='packages.package'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /errata/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/errata/migrations/__init__.py -------------------------------------------------------------------------------- /errata/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from errata.models import Erratum 20 | 21 | 22 | class ErratumSerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = Erratum 25 | fields = ('id', 'name', 'e_type', 'issue_date', 'synopsis', 'cves', 'releases', 'references') 26 | -------------------------------------------------------------------------------- /errata/sources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/errata/sources/__init__.py -------------------------------------------------------------------------------- /errata/sources/distros/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/errata/sources/distros/__init__.py -------------------------------------------------------------------------------- /errata/sources/repos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/errata/sources/repos/__init__.py -------------------------------------------------------------------------------- /errata/templates/errata/erratum_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Errata{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Errata
  • {% endblock %} 6 | 7 | {% block content_title %} Errata {% endblock %} 8 | -------------------------------------------------------------------------------- /errata/templates/errata/erratum_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for erratum in object_list %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% endfor %} 30 | 31 |
    IDTypePublished DateSynopsisPackages AffectedPackages FixedOS Releases AffectedCVEsReferences
    {{ erratum.name }}{{ erratum.e_type }}{{ erratum.issue_date|date|default_if_none:'' }}{{ erratum.synopsis }}{% with count=erratum.affected_packages.count %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{% with count=erratum.fixed_packages.count %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{% with count=erratum.osreleases.count %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{% with count=erratum.cves.count %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{% with count=erratum.references.count %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}
    32 | -------------------------------------------------------------------------------- /errata/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.urls import path 18 | 19 | from errata import views 20 | 21 | app_name = 'errata' 22 | 23 | urlpatterns = [ 24 | path('', views.erratum_list, name='erratum_list'), 25 | path('errata//', views.erratum_detail, name='erratum_detail'), 26 | ] 27 | -------------------------------------------------------------------------------- /etc/patchman/apache.conf.example: -------------------------------------------------------------------------------- 1 | Define patchman_pythonpath /srv/patchman/ 2 | WSGIScriptAlias /patchman ${patchman_pythonpath}/patchman/wsgi.py 3 | WSGIPythonPath ${patchman_pythonpath} 4 | 5 | 6 | 7 | Require all granted 8 | 9 | AllowOverride All 10 | 11 | 12 | Alias /patchman/static "/var/lib/patchman/static" 13 | 14 | SetHandler None 15 | 16 | 17 | 18 | Require all granted 19 | 20 | 21 | 22 | # Add the IP addresses of your client networks/hosts here 23 | # to allow uploading of reports 24 | Require ip 127.0.0.0/255.0.0.0 25 | Require ip ::1/128 26 | 27 | -------------------------------------------------------------------------------- /etc/patchman/celery.conf: -------------------------------------------------------------------------------- 1 | REDIS_HOST=127.0.0.1 2 | REDIS_PORT=6379 3 | -------------------------------------------------------------------------------- /etc/systemd/system/patchman-celery.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Patchman Celery Service 3 | Requires=network-online.target 4 | After=network-onlne.target 5 | 6 | [Service] 7 | Type=simple 8 | User=patchman-celery 9 | Group=patchman-celery 10 | EnvironmentFile=/etc/patchman/celery.conf 11 | ExecStart=/usr/bin/celery --broker redis://${REDIS_HOST}:${REDIS_PORT}/0 --app patchman worker --loglevel info --beat --scheduler django_celery_beat.schedulers:DatabaseScheduler --task-events --pool threads 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /hooks/apt/05patchman: -------------------------------------------------------------------------------- 1 | DPkg::Post-Invoke { "if [ -x /usr/sbin/patchman-client ] ; then echo 'Sending report to patchman server...' ; /usr/sbin/patchman-client -n ; fi"; }; 2 | -------------------------------------------------------------------------------- /hooks/dnf/patchman.action: -------------------------------------------------------------------------------- 1 | *:any:if [ -x /usr/sbin/patchman-client ] ; then echo 'Sending report to patchman server...' ; /usr/sbin/patchman-client -n ; fi 2 | -------------------------------------------------------------------------------- /hooks/yum/patchman.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | enabled=1 3 | -------------------------------------------------------------------------------- /hooks/yum/patchman.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-2016 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | import os 18 | from yum.plugins import TYPE_CORE 19 | 20 | requires_api_version = '2.1' 21 | plugin_type = (TYPE_CORE,) 22 | 23 | 24 | def posttrans_hook(conduit): 25 | conduit.info(2, 'Sending report to patchman server...') 26 | servicecmd = conduit.confString('main', 27 | 'servicecmd', 28 | '/usr/sbin/patchman-client') 29 | args = '-n' 30 | command = f'{servicecmd} {args}> /dev/null' 31 | os.system(command) 32 | -------------------------------------------------------------------------------- /hooks/zypper/patchman.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2016 Marcus Furlong 4 | # 5 | # This file is part of Patchman. 6 | # 7 | # Patchman is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 only. 10 | # 11 | # Patchman is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Patchman. If not, see 18 | # 19 | # zypp system plugin for patchman 20 | 21 | import os 22 | import logging 23 | from zypp_plugin import Plugin 24 | 25 | 26 | class MyPlugin(Plugin): 27 | 28 | def PLUGINBEGIN(self, headers, body): # noqa 29 | logging.info('PLUGINBEGIN') 30 | logging.debug(f'headers: {headers}') 31 | self.ack() 32 | 33 | def PACKAGESETCHANGED(self, headers, body): # noqa 34 | logging.info('PACKAGESETCHANGED') 35 | logging.debug(f'headers: {headers}') 36 | print('Sending report to patchman server...') 37 | servicecmd = '/usr/sbin/patchman-client' 38 | args = '-n' 39 | command = f'{servicecmd} {args}> /dev/null' 40 | os.system(command) 41 | self.ack() 42 | 43 | def PLUGINEND(self, headers, body): # noqa 44 | logging.info('PLUGINEND') 45 | logging.debug(f'headers: {headers}') 46 | self.ack() 47 | 48 | 49 | plugin = MyPlugin() 50 | plugin.main() 51 | -------------------------------------------------------------------------------- /hosts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/hosts/__init__.py -------------------------------------------------------------------------------- /hosts/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from hosts.models import Host, HostRepo 20 | 21 | 22 | class HostAdmin(admin.ModelAdmin): 23 | readonly_fields = ('packages', 'updates') 24 | 25 | 26 | admin.site.register(Host, HostAdmin) 27 | admin.site.register(HostRepo) 28 | -------------------------------------------------------------------------------- /hosts/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class HostsConfig(AppConfig): 21 | name = 'hosts' 22 | -------------------------------------------------------------------------------- /hosts/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.forms import ModelForm, TextInput 19 | 20 | from hosts.models import Host 21 | 22 | 23 | class EditHostForm(ModelForm): 24 | 25 | def __init__(self, *args, **kwargs): 26 | super().__init__(*args, **kwargs) 27 | self.fields['hostname'].widget = TextInput(attrs={'size': 50},) 28 | self.fields['reversedns'].widget = TextInput(attrs={'size': 50},) 29 | self.fields['kernel'].widget = TextInput(attrs={'size': 50},) 30 | 31 | class Meta: 32 | model = Host 33 | fields = ('hostname', 34 | 'reversedns', 35 | 'ipaddress', 36 | 'osvariant', 37 | 'kernel', 38 | 'arch', 39 | 'reboot_required', 40 | 'host_repos_only', 41 | 'check_dns', 42 | 'tags') 43 | -------------------------------------------------------------------------------- /hosts/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.db import models 19 | 20 | 21 | class HostManager(models.Manager): 22 | def get_queryset(self): 23 | return super().get_queryset().select_related() 24 | -------------------------------------------------------------------------------- /hosts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django.utils.timezone 6 | try: 7 | import tagging.fields 8 | has_tagging = True 9 | except ImportError: 10 | has_tagging = False 11 | 12 | 13 | class Migration(migrations.Migration): 14 | 15 | initial = True 16 | 17 | dependencies = [ 18 | ] 19 | 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('hostname', models.CharField(max_length=255, unique=True)), 23 | ('ipaddress', models.GenericIPAddressField()), 24 | ('reversedns', models.CharField(blank=True, max_length=255, null=True)), 25 | ('check_dns', models.BooleanField(default=False)), 26 | ('kernel', models.CharField(max_length=255)), 27 | ('lastreport', models.DateTimeField()), 28 | ('reboot_required', models.BooleanField(default=False)), 29 | ('host_repos_only', models.BooleanField(default=True)), 30 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now)), 31 | ] 32 | if has_tagging: 33 | fields.append(('tags', tagging.fields.TagField(blank=True, max_length=255))) 34 | 35 | operations = [ 36 | migrations.CreateModel( 37 | name='Host', 38 | fields=fields, 39 | options={ 40 | 'verbose_name': 'Host', 41 | 'verbose_name_plural': 'Hosts', 42 | 'ordering': ('hostname',), 43 | }, 44 | ), 45 | migrations.CreateModel( 46 | name='HostRepo', 47 | fields=[ 48 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 49 | ('enabled', models.BooleanField(default=True)), 50 | ('priority', models.IntegerField(default=0)), 51 | ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosts.host')), 52 | ], 53 | ), 54 | ] 55 | -------------------------------------------------------------------------------- /hosts/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('domains', '0001_initial'), 13 | ('packages', '0001_initial'), 14 | ('arch', '0001_initial'), 15 | ('operatingsystems', '0002_initial'), 16 | ('hosts', '0001_initial'), 17 | ('repos', '0001_initial'), 18 | ] 19 | 20 | operations = [ 21 | migrations.AddField( 22 | model_name='hostrepo', 23 | name='repo', 24 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repos.repository'), 25 | ), 26 | migrations.AddField( 27 | model_name='host', 28 | name='arch', 29 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='arch.machinearchitecture'), 30 | ), 31 | migrations.AddField( 32 | model_name='host', 33 | name='domain', 34 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='domains.domain'), 35 | ), 36 | migrations.AddField( 37 | model_name='host', 38 | name='os', 39 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='operatingsystems.os'), 40 | ), 41 | migrations.AddField( 42 | model_name='host', 43 | name='packages', 44 | field=models.ManyToManyField(blank=True, to='packages.Package'), 45 | ), 46 | migrations.AddField( 47 | model_name='host', 48 | name='repos', 49 | field=models.ManyToManyField(blank=True, through='hosts.HostRepo', to='repos.Repository'), 50 | ), 51 | migrations.AddField( 52 | model_name='host', 53 | name='updates', 54 | field=models.ManyToManyField(blank=True, to='packages.PackageUpdate'), 55 | ), 56 | migrations.AlterUniqueTogether( 57 | name='hostrepo', 58 | unique_together={('host', 'repo')}, 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /hosts/migrations/0003_host_modules.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('modules', '0001_initial'), 10 | ('hosts', '0002_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='host', 16 | name='modules', 17 | field=models.ManyToManyField(blank=True, to='modules.Module'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /hosts/migrations/0004_remove_host_tags_host_tags.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.18 on 2025-02-04 23:37 2 | 3 | from django.apps import apps 4 | from django.db import migrations 5 | import taggit.managers 6 | try: 7 | import tagging # noqa 8 | except ImportError: 9 | pass 10 | 11 | 12 | def check_tagging_tag_field_exists(app_label, model_name, field_name): 13 | Model = apps.get_model(app_label, model_name) 14 | fields = Model._meta.get_fields() 15 | for field in fields: 16 | if field.name == field_name and 'tagging' in str(field.related_model): 17 | return True 18 | return False 19 | 20 | 21 | class Migration(migrations.Migration): 22 | 23 | dependencies = [ 24 | ('taggit', '0005_auto_20220424_2025'), 25 | ('hosts', '0003_host_modules'), 26 | ] 27 | 28 | if check_tagging_tag_field_exists('hosts', 'Host', 'tags'): 29 | operations = [ 30 | migrations.RemoveField( 31 | model_name='host', 32 | name='tags', 33 | ) 34 | ] 35 | else: 36 | operations = [] 37 | 38 | operations.append( 39 | migrations.AddField( 40 | model_name='host', 41 | name='tags', 42 | field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /hosts/migrations/0005_rename_os_host_osvariant.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.18 on 2025-02-08 20:36 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('hosts', '0004_remove_host_tags_host_tags'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='host', 15 | old_name='os', 16 | new_name='osvariant', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /hosts/migrations/0006_migrate_to_tz_aware.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.utils import timezone 3 | 4 | def make_datetimes_tz_aware(apps, schema_editor): 5 | Host = apps.get_model('hosts', 'Host') 6 | for host in Host.objects.all(): 7 | if host.lastreport and timezone.is_naive(host.lastreport): 8 | host.lastreport = timezone.make_aware(host.lastreport, timezone=timezone.get_default_timezone()) 9 | host.save() 10 | if host.updated_at and timezone.is_naive(host.updated_at): 11 | host.updated_at = timezone.make_aware(host.updated_at, timezone=timezone.get_default_timezone()) 12 | host.save() 13 | 14 | class Migration(migrations.Migration): 15 | dependencies = [ 16 | ('hosts', '0005_rename_os_host_osvariant'), 17 | ] 18 | 19 | operations = [ 20 | migrations.RunPython(make_datetimes_tz_aware, migrations.RunPython.noop), 21 | ] 22 | -------------------------------------------------------------------------------- /hosts/migrations/0007_alter_host_tags.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-02-28 19:53 2 | 3 | from django.db import migrations 4 | import taggit.managers 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('taggit', '0005_auto_20220424_2025'), 11 | ('hosts', '0006_migrate_to_tz_aware'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='host', 17 | name='tags', 18 | field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /hosts/migrations/0008_alter_host_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-04 22:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('hosts', '0007_alter_host_tags'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='host', 15 | options={'ordering': ['hostname'], 'verbose_name': 'Host', 'verbose_name_plural': 'Hosts'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /hosts/migrations/0009_host_errata.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-10 19:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('errata', '0006_alter_erratum_options'), 10 | ('hosts', '0008_alter_host_options'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='host', 16 | name='errata', 17 | field=models.ManyToManyField(blank=True, to='errata.erratum'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /hosts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/hosts/migrations/__init__.py -------------------------------------------------------------------------------- /hosts/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from hosts.models import Host, HostRepo 20 | 21 | 22 | class HostSerializer(serializers.HyperlinkedModelSerializer): 23 | bugfix_update_count = serializers.SerializerMethodField() 24 | security_update_count = serializers.SerializerMethodField() 25 | 26 | class Meta: 27 | model = Host 28 | fields = ('id', 'hostname', 'ipaddress', 'reversedns', 'check_dns', 29 | 'os', 'kernel', 'arch', 'domain', 'lastreport', 'repos', 30 | 'updates', 'reboot_required', 'host_repos_only', 'tags', 31 | 'updated_at', 'bugfix_update_count', 'security_update_count') 32 | 33 | def get_bugfix_update_count(self, obj): 34 | return obj.updates.filter(security=False).count() 35 | 36 | def get_security_update_count(self, obj): 37 | return obj.updates.filter(security=True).count() 38 | 39 | 40 | class HostRepoSerializer(serializers.HyperlinkedModelSerializer): 41 | class Meta: 42 | model = HostRepo 43 | fields = ('host', 'repo', 'enabled', 'priority') 44 | -------------------------------------------------------------------------------- /hosts/templates/hosts/host_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load common bootstrap3 %} 4 | 5 | {% block extrahead %} {{ edit_form.media }} {% endblock %} 6 | 7 | {% block page_title %}Host - {{ host }} {% endblock %} 8 | 9 | {% block breadcrumbs %} {{ block.super }}
  • Hosts
  • {{ host }}
  • {% endblock %} 10 | 11 | {% block content_title %} Host - {{ host }} {% endblock %} 12 | 13 | {% block content %} 14 | 15 |
    16 | {% if user.is_authenticated and perms.is_admin %} 17 |
    18 | {% csrf_token %} 19 | {% bootstrap_form edit_form size='small' %} 20 | 21 | 22 |
    23 | {% else %} 24 | You do not have permission to edit this Host. 25 | {% endif %} 26 |
    27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /hosts/templates/hosts/host_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Hosts{% endblock %} 4 | 5 | {% block content_title %} Hosts {% endblock %} 6 | 7 | {% block breadcrumbs %} {{ block.super }}
  • Hosts
  • {% endblock %} 8 | -------------------------------------------------------------------------------- /hosts/templates/hosts/host_table.html: -------------------------------------------------------------------------------- 1 | {% load common report_alert %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for host in object_list %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 | 28 |
    HostnameUpdatesAffected by ErrataRunning KernelOS VariantLast ReportReboot Status
    {{ host }}{% with count=host.get_num_security_updates %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{% with count=host.get_num_bugfix_updates %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{% with count=host.errata.count %}{% if count != 0 %}{{ count }}{% else %} {% endif %}{% endwith %}{{ host.kernel }}{{ host.osvariant }}{{ host.lastreport }}{% report_alert host.lastreport %}{% no_yes_img host.reboot_required %}
    29 | -------------------------------------------------------------------------------- /hosts/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/hosts/templatetags/__init__.py -------------------------------------------------------------------------------- /hosts/templatetags/report_alert.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman If not, see . 17 | 18 | from datetime import timedelta 19 | 20 | from django.template import Library 21 | from django.utils.html import format_html 22 | from django.templatetags.static import static 23 | from django.utils import timezone 24 | 25 | from util import get_setting_of_type 26 | 27 | register = Library() 28 | 29 | 30 | @register.simple_tag 31 | def report_alert(lastreport): 32 | html = '' 33 | alert_icon = static('img/icon-alert.gif') 34 | days = get_setting_of_type( 35 | setting_name='DAYS_WITHOUT_REPORT', 36 | setting_type=int, 37 | default=14, 38 | ) 39 | if lastreport < (timezone.now() - timedelta(days=days)): 40 | html = f'Outdated Report' 41 | return format_html(html) 42 | -------------------------------------------------------------------------------- /hosts/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.urls import path 19 | 20 | from hosts import views 21 | 22 | app_name = 'hosts' 23 | 24 | urlpatterns = [ 25 | path('', views.host_list, name='host_list'), 26 | path('/', views.host_detail, name='host_detail'), 27 | path('/delete/', views.host_delete, name='host_delete'), 28 | path('/edit/', views.host_edit, name='host_edit'), 29 | path('/updates/', views.host_find_updates, name='host_updates'), 30 | ] 31 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2019-2025 Marcus Furlong 4 | # 5 | # This file is part of Patchman. 6 | # 7 | # Patchman is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 only. 10 | # 11 | # Patchman is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Patchman. If not, see 18 | 19 | import os 20 | import sys 21 | 22 | 23 | def main(): 24 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'patchman.settings') 25 | try: 26 | from django.core.management import execute_from_command_line 27 | except ImportError as exc: 28 | raise ImportError( 29 | 'Could not import Django. Are you sure it is installed and ' 30 | 'available on your PYTHONPATH environment variable? Did you ' 31 | 'forget to activate a virtual environment?' 32 | ) from exc 33 | execute_from_command_line(sys.argv) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/modules/__init__.py -------------------------------------------------------------------------------- /modules/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.contrib import admin 18 | from modules.models import Module 19 | 20 | admin.site.register(Module) 21 | -------------------------------------------------------------------------------- /modules/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class ModulesConfig(AppConfig): 21 | name = 'modules' 22 | -------------------------------------------------------------------------------- /modules/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.db import models 18 | 19 | 20 | class ModuleManager(models.Manager): 21 | def get_queryset(self): 22 | return super().get_queryset().select_related() 23 | -------------------------------------------------------------------------------- /modules/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:17 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('packages', '0001_initial'), 13 | ('arch', '0001_initial'), 14 | ('repos', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Module', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('name', models.CharField(max_length=255, unique=True)), 23 | ('stream', models.CharField(max_length=255, unique=True)), 24 | ('version', models.CharField(max_length=255)), 25 | ('context', models.CharField(max_length=255, unique=True)), 26 | ('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='arch.packagearchitecture')), 27 | ('packages', models.ManyToManyField(blank=True, to='packages.Package')), 28 | ('repo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repos.repository')), 29 | ], 30 | options={ 31 | 'verbose_name': 'Module', 32 | 'verbose_name_plural': 'Modules', 33 | 'ordering': ('name',), 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /modules/migrations/0002_auto_20240204_2214.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.23 on 2024-02-04 22:14 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('arch', '0001_initial'), 10 | ('modules', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='module', 16 | name='context', 17 | field=models.CharField(max_length=255), 18 | ), 19 | migrations.AlterField( 20 | model_name='module', 21 | name='name', 22 | field=models.CharField(max_length=255), 23 | ), 24 | migrations.AlterField( 25 | model_name='module', 26 | name='stream', 27 | field=models.CharField(max_length=255), 28 | ), 29 | migrations.AlterUniqueTogether( 30 | name='module', 31 | unique_together={('name', 'stream', 'version', 'context', 'arch')}, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /modules/migrations/0003_alter_module_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.23 on 2024-02-04 22:40 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('modules', '0002_auto_20240204_2214'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='module', 15 | options={'ordering': ('name', 'stream'), 'verbose_name': 'Module', 'verbose_name_plural': 'Modules'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /modules/migrations/0004_alter_module_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-04 22:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('modules', '0003_alter_module_options'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='module', 15 | options={'ordering': ['name', 'stream'], 'verbose_name': 'Module', 'verbose_name_plural': 'Modules'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /modules/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/modules/migrations/__init__.py -------------------------------------------------------------------------------- /modules/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.db import models 18 | from django.urls import reverse 19 | 20 | from arch.models import PackageArchitecture 21 | from packages.models import Package 22 | from repos.models import Repository 23 | 24 | 25 | class Module(models.Model): 26 | 27 | name = models.CharField(max_length=255) 28 | stream = models.CharField(max_length=255) 29 | version = models.CharField(max_length=255) 30 | context = models.CharField(max_length=255) 31 | arch = models.ForeignKey(PackageArchitecture, on_delete=models.CASCADE) 32 | repo = models.ForeignKey(Repository, on_delete=models.CASCADE) 33 | packages = models.ManyToManyField(Package, blank=True) 34 | 35 | class Meta: 36 | verbose_name = 'Module' 37 | verbose_name_plural = 'Modules' 38 | unique_together = ['name', 'stream', 'version', 'context', 'arch'] 39 | ordering = ['name', 'stream'] 40 | 41 | def __str__(self): 42 | return f'{self.name}-{self.stream}-{self.version}-{self.version}-{self.context}' 43 | 44 | def get_absolute_url(self): 45 | return reverse('modules:module_detail', args=[str(self.id)]) 46 | -------------------------------------------------------------------------------- /modules/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from modules.models import Module 20 | 21 | 22 | class ModuleSerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = Module 25 | fields = ('id', 'name', 'stream', 'version', 'context', 'arch', 'repo', 'packages') 26 | -------------------------------------------------------------------------------- /modules/templates/modules/module_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Modules{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Modules
  • {% endblock %} 6 | 7 | {% block content_title %} Modules {% endblock %} 8 | -------------------------------------------------------------------------------- /modules/templates/modules/module_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for module in object_list %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
    NameStreamVersionContextRepoPackagesEnabled on Hosts
    {{ module.name }}{{ module.stream }}{{ module.version }}{{ module.context }}{{ module.repo }}{{ module.packages.count }}{{ module.host_set.count }}
    28 | -------------------------------------------------------------------------------- /modules/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.urls import path 18 | 19 | from modules import views 20 | 21 | app_name = 'modules' 22 | 23 | urlpatterns = [ 24 | path('', views.module_list, name='module_list'), 25 | path('/', views.module_detail, name='module_detail'), 26 | ] 27 | -------------------------------------------------------------------------------- /operatingsystems/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/operatingsystems/__init__.py -------------------------------------------------------------------------------- /operatingsystems/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from operatingsystems.models import OSVariant, OSRelease 20 | 21 | 22 | class OSReleaseAdmin(admin.ModelAdmin): 23 | filter_horizontal = ('repos',) 24 | 25 | 26 | admin.site.register(OSVariant) 27 | admin.site.register(OSRelease, OSReleaseAdmin) 28 | -------------------------------------------------------------------------------- /operatingsystems/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class OperatingsystemsConfig(AppConfig): 21 | name = 'operatingsystems' 22 | -------------------------------------------------------------------------------- /operatingsystems/fixtures/os.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "operatingsystems.osvariant", 4 | "fields": { 5 | "name": "Rocky Linux 9.3", 6 | "osrelease": [ 7 | "Rocky Linux 9", 8 | "Blue Onyx" 9 | ] 10 | } 11 | }, 12 | { 13 | "model": "operatingsystems.osvariant", 14 | "fields": { 15 | "name": "Rocky Linux 8.9", 16 | "osrelease": [ 17 | "Rocky Linux 8", 18 | "Green Obsidian" 19 | ] 20 | } 21 | }, 22 | { 23 | "model": "operatingsystems.osvariant", 24 | "fields": { 25 | "name": "Debian 12.5", 26 | "osrelease": [ 27 | "Debian 12", 28 | "bookworm" 29 | ] 30 | } 31 | }, 32 | { 33 | "model": "operatingsystems.osvariant", 34 | "fields": { 35 | "name": "Arch Linux", 36 | "osrelease": null 37 | } 38 | }, 39 | { 40 | "model": "operatingsystems.osvariant", 41 | "fields": { 42 | "name": "openSUSE Leap 15.6", 43 | "osrelease": null 44 | } 45 | }, 46 | { 47 | "model": "operatingsystems.osvariant", 48 | "fields": { 49 | "name": "AlmaLinux 8.10", 50 | "osrelease": [ 51 | "AlmaLinux 8", 52 | "Cerulean Leopard" 53 | ] 54 | } 55 | }, 56 | { 57 | "model": "operatingsystems.osvariant", 58 | "fields": { 59 | "name": "AlmaLinux 9.5", 60 | "osrelease": [ 61 | "AlmaLinux 9", 62 | "Teal Serval" 63 | ] 64 | } 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /operatingsystems/fixtures/osgroup.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "operatingsystems.osrelease", 4 | "fields": { 5 | "name": "CentOS 7", 6 | "codename": "", 7 | "repos": [] 8 | } 9 | }, 10 | { 11 | "model": "operatingsystems.osrelease", 12 | "fields": { 13 | "name": "CentOS 8", 14 | "codename": "", 15 | "repos": [] 16 | } 17 | }, 18 | { 19 | "model": "operatingsystems.osrelease", 20 | "fields": { 21 | "name": "Rocky Linux 8", 22 | "codename": "Green Obsidian", 23 | "repos": [] 24 | } 25 | }, 26 | { 27 | "model": "operatingsystems.osrelease", 28 | "fields": { 29 | "name": "Rocky Linux 9", 30 | "codename": "Blue Onyx", 31 | "repos": [] 32 | } 33 | }, 34 | { 35 | "model": "operatingsystems.osrelease", 36 | "fields": { 37 | "name": "AlmaLinux 8", 38 | "codename": "Cerulean Leopard", 39 | "repos": [] 40 | } 41 | }, 42 | { 43 | "model": "operatingsystems.osrelease", 44 | "fields": { 45 | "name": "AlmaLinux 9", 46 | "codename": "Teal Serval", 47 | "repos": [] 48 | } 49 | }, 50 | { 51 | "model": "operatingsystems.osrelease", 52 | "fields": { 53 | "name": "Debian 12", 54 | "codename": "bookworm", 55 | "repos": [] 56 | } 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /operatingsystems/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.forms import ModelForm, ModelMultipleChoiceField 19 | from django.contrib.admin.widgets import FilteredSelectMultiple 20 | 21 | from operatingsystems.models import OSVariant, OSRelease 22 | from repos.models import Repository 23 | 24 | 25 | class AddOSVariantToOSReleaseForm(ModelForm): 26 | 27 | def __init__(self, *args, **kwargs): 28 | super().__init__(*args, **kwargs) 29 | self.fields['osrelease'].label = 'OS Releases' 30 | 31 | class Meta: 32 | model = OSVariant 33 | fields = ('osrelease',) 34 | 35 | 36 | class CreateOSReleaseForm(ModelForm): 37 | 38 | def __init__(self, *args, **kwargs): 39 | super().__init__(*args, **kwargs) 40 | self.fields['name'].label = 'New OS Release' 41 | 42 | class Meta: 43 | model = OSRelease 44 | fields = ('name',) 45 | 46 | 47 | class AddReposToOSReleaseForm(ModelForm): 48 | 49 | repos = ModelMultipleChoiceField( 50 | queryset=Repository.objects.select_related(), 51 | required=False, 52 | label=None, 53 | widget=FilteredSelectMultiple('Repos', False, attrs={'size': '30'})) 54 | 55 | def __init__(self, *args, **kwargs): 56 | super().__init__(*args, **kwargs) 57 | self.fields['repos'].label = '' 58 | 59 | class Meta: 60 | model = OSRelease 61 | fields = ('repos',) 62 | -------------------------------------------------------------------------------- /operatingsystems/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.db import models 18 | 19 | 20 | class OSReleaseManager(models.Manager): 21 | def get_by_natural_key(self, name, codename): 22 | return self.get(name=name, codename=codename) 23 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='OS', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=255, unique=True)), 19 | ], 20 | options={ 21 | 'verbose_name': 'Operating System', 22 | 'verbose_name_plural': 'Operating Systems', 23 | 'ordering': ('name',), 24 | }, 25 | ), 26 | migrations.CreateModel( 27 | name='OSGroup', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('name', models.CharField(max_length=255, unique=True)), 31 | ], 32 | options={ 33 | 'verbose_name': 'Operating System Group', 34 | 'verbose_name_plural': 'Operating System Groups', 35 | 'ordering': ('name',), 36 | }, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('operatingsystems', '0001_initial'), 13 | ('repos', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='osgroup', 19 | name='repos', 20 | field=models.ManyToManyField(blank=True, to='repos.Repository'), 21 | ), 22 | migrations.AddField( 23 | model_name='os', 24 | name='osgroup', 25 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='operatingsystems.osgroup'), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0003_os_arch.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.25 on 2025-02-07 13:02 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('arch', '0001_initial'), 11 | ('operatingsystems', '0002_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='os', 17 | name='arch', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='arch.machinearchitecture'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0003_osgroup_codename.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.15 on 2025-01-13 18:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('operatingsystems', '0003_os_arch'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='osgroup', 15 | name='codename', 16 | field=models.CharField(blank=True, max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0004_alter_osgroup_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.15 on 2025-01-13 19:57 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('operatingsystems', '0003_osgroup_codename'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name='osgroup', 15 | unique_together={('name', 'codename')}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0005_rename_osgroup_osrelease_rename_os_osvariant_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.18 on 2025-02-08 20:36 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('repos', '0001_initial'), 10 | ('hosts', '0005_rename_os_host_osvariant'), 11 | ('operatingsystems', '0004_alter_osgroup_unique_together'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel( 16 | old_name='OSGroup', 17 | new_name='OSRelease', 18 | ), 19 | migrations.RenameModel( 20 | old_name='OS', 21 | new_name='OSVariant', 22 | ), 23 | migrations.AlterModelOptions( 24 | name='osrelease', 25 | options={'ordering': ('name',), 'verbose_name': 'Operating System Release', 'verbose_name_plural': 'Operating System Releases'}, 26 | ), 27 | migrations.AlterModelOptions( 28 | name='osvariant', 29 | options={'ordering': ('name',), 'verbose_name': 'Operating System Variant', 'verbose_name_plural': 'Operating System Variants'}, 30 | ), 31 | migrations.RenameField( 32 | model_name='osvariant', 33 | old_name='osgroup', 34 | new_name='osrelease', 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0006_osrelease_cpe_name_osvariant_codename.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-02-12 20:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('operatingsystems', '0005_rename_osgroup_osrelease_rename_os_osvariant_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='osrelease', 15 | name='cpe_name', 16 | field=models.CharField(blank=True, max_length=255, null=True, unique=True), 17 | ), 18 | migrations.AddField( 19 | model_name='osvariant', 20 | name='codename', 21 | field=models.CharField(blank=True, max_length=255), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0007_alter_osrelease_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-02-26 16:01 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('operatingsystems', '0006_osrelease_cpe_name_osvariant_codename'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name='osrelease', 15 | unique_together={('name', 'codename', 'cpe_name')}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /operatingsystems/migrations/0008_alter_osrelease_options_alter_osvariant_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-04 22:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('operatingsystems', '0007_alter_osrelease_unique_together'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='osrelease', 15 | options={'ordering': ['name'], 'verbose_name': 'Operating System Release', 'verbose_name_plural': 'Operating System Releases'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='osvariant', 19 | options={'ordering': ['name'], 'verbose_name': 'Operating System Variant', 'verbose_name_plural': 'Operating System Variants'}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /operatingsystems/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/operatingsystems/migrations/__init__.py -------------------------------------------------------------------------------- /operatingsystems/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from operatingsystems.models import OSVariant, OSRelease 20 | 21 | 22 | class OSVariantSerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = OSVariant 25 | fields = ('id', 'name', 'osrelease', 'arch') 26 | 27 | 28 | class OSReleaseSerializer(serializers.HyperlinkedModelSerializer): 29 | class Meta: 30 | model = OSRelease 31 | fields = ('id', 'name', 'codename', 'repos') 32 | -------------------------------------------------------------------------------- /operatingsystems/templates/operatingsystems/operatingsystemrelease_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for osrelease in object_list %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
    OS ReleaseCPE NameCodenameReposOS VariantsHostsErrata
    {{ osrelease.name }}{% if osrelease.cpe_name %}{{ osrelease.cpe_name }}{% endif %}{% if osrelease.codename %}{{ osrelease.codename }}{% endif %}{{ osrelease.repos.count }}{{ osrelease.osvariant_set.count }}{% host_count osrelease %}{{ osrelease.erratum_set.count }}
    28 | -------------------------------------------------------------------------------- /operatingsystems/templates/operatingsystems/operatingsystemvariant_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for osvariant in object_list %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endfor %} 24 | 25 |
    NameArchitectureCodenameHostsOS ReleaseRepos (OS Release)
    {{ osvariant }}{{ osvariant.arch }}{% if osvariant.codename %}{{ osvariant.codename }}{% else %}{% if osvariant.osrelease %}{{ osvariant.osrelease.codename }}{% endif %}{% endif %}{{ osvariant.host_set.count }}{% if osvariant.osrelease %}{{ osvariant.osrelease }}{% endif %}{% if osvariant.osrelease.repos.count != None %}{{ osvariant.osrelease.repos.count }}{% else %}0{% endif %}
    26 | -------------------------------------------------------------------------------- /operatingsystems/templates/operatingsystems/os_landing.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %} Operating Systems {% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Operating Systems
  • {% endblock %} 6 | 7 | {% block content_title %} Operating Systems {% endblock %} 8 | 9 | {% block content %} 10 | 11 |
    12 | 13 | 14 | 15 |
    OS Releases
    OS Variants
    16 |
    17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /operatingsystems/templates/operatingsystems/osrelease_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}OS Releases{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Operating Systems
  • OS Releases
  • {% endblock %} 6 | 7 | {% block content_title %} OS Releases {% endblock %} 8 | -------------------------------------------------------------------------------- /operatingsystems/templates/operatingsystems/osvariant_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% load common bootstrap3 %} 4 | 5 | {% block page_title %}OS Variants{% endblock %} 6 | 7 | {% block content_title %} OS Variants {% endblock %} 8 | 9 | {% block breadcrumbs %} {{ block.super }}
  • Operating Systems
  • OS Variants
  • {% endblock %} 10 | 11 | {% block objectlist_actions %} 12 | 13 | {% if user.is_authenticated and perms.is_admin and nohost_osvariants %} 14 | 17 | {% endif %} 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /operatingsystems/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.urls import path 19 | 20 | from operatingsystems import views 21 | 22 | app_name = 'operatingsystems' 23 | 24 | urlpatterns = [ 25 | path('', views.os_landing, name='os_landing'), 26 | path('variants/', views.osvariant_list, name='osvariant_list'), 27 | path('variants//', views.osvariant_detail, name='osvariant_detail'), 28 | path('variants//delete/', views.osvariant_delete, name='osvariant_delete'), 29 | path('variants/no_host/delete/', views.delete_nohost_osvariants, name='delete_nohost_osvariants'), 30 | path('releases/', views.osrelease_list, name='osrelease_list'), 31 | path('releases//', views.osrelease_detail, name='osrelease_detail'), 32 | path('releases//delete/', views.osrelease_delete, name='osrelease_delete'), 33 | ] 34 | -------------------------------------------------------------------------------- /packages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/packages/__init__.py -------------------------------------------------------------------------------- /packages/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from packages.models import Package, PackageName, PackageUpdate 20 | 21 | 22 | class PackageAdmin(admin.ModelAdmin): 23 | readonly_fields = ('name',) 24 | 25 | 26 | class PackageUpdateAdmin(admin.ModelAdmin): 27 | readonly_fields = ('oldpackage', 'newpackage') 28 | 29 | 30 | admin.site.register(Package, PackageAdmin) 31 | admin.site.register(PackageName) 32 | admin.site.register(PackageUpdate, PackageUpdateAdmin) 33 | -------------------------------------------------------------------------------- /packages/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class PackagesConfig(AppConfig): 21 | name = 'packages' 22 | -------------------------------------------------------------------------------- /packages/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.db import models 19 | 20 | 21 | class PackageManager(models.Manager): 22 | def get_queryset(self): 23 | return super().get_queryset().select_related() 24 | -------------------------------------------------------------------------------- /packages/migrations/0002_auto_20250207_1319.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.25 on 2025-02-07 13:19 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('packages', '0002_delete_erratum_delete_erratumreference'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='PackageCategory', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=255, unique=True)), 19 | ], 20 | options={ 21 | 'verbose_name': 'Package Category', 22 | 'verbose_name_plural': 'Package Categories', 23 | 'ordering': ('name',), 24 | }, 25 | ), 26 | migrations.AlterField( 27 | model_name='package', 28 | name='packagetype', 29 | field=models.CharField(blank=True, choices=[('R', 'rpm'), ('D', 'deb'), ('A', 'arch'), ('G', 'gentoo'), ('U', 'unknown')], max_length=1, null=True), 30 | ), 31 | migrations.AddField( 32 | model_name='package', 33 | name='category', 34 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='packages.packagecategory'), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /packages/migrations/0002_delete_erratum_delete_erratumreference.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.18 on 2025-02-04 23:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('packages', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Erratum', 15 | ), 16 | migrations.DeleteModel( 17 | name='ErratumReference', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /packages/migrations/0003_auto_20250207_1746.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.25 on 2025-02-07 17:46 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('arch', '0001_initial'), 10 | ('packages', '0002_auto_20250207_1319'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterUniqueTogether( 15 | name='package', 16 | unique_together={('name', 'epoch', 'version', 'release', 'arch', 'packagetype', 'category')}, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /packages/migrations/0004_alter_package_options_alter_packagecategory_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-04 22:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('packages', '0003_auto_20250207_1746'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='package', 15 | options={'ordering': ['name', 'epoch', 'version', 'release', 'arch']}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='packagecategory', 19 | options={'ordering': ['name'], 'verbose_name': 'Package Category', 'verbose_name_plural': 'Package Categories'}, 20 | ), 21 | migrations.AlterModelOptions( 22 | name='packagename', 23 | options={'ordering': ['name'], 'verbose_name': 'Package', 'verbose_name_plural': 'Packages'}, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /packages/migrations/0005_alter_package_packagetype.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-10 17:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('packages', '0004_alter_package_options_alter_packagecategory_options_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='package', 15 | name='packagetype', 16 | field=models.CharField(blank=True, choices=[('R', 'rpm'), ('D', 'deb'), ('A', 'pkgbuild'), ('G', 'ebuild'), ('U', 'unknown')], max_length=1, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /packages/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/packages/migrations/__init__.py -------------------------------------------------------------------------------- /packages/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from packages.models import PackageName, Package, PackageUpdate 20 | 21 | 22 | class PackageNameSerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = PackageName 25 | fields = ('id', 'name') 26 | 27 | 28 | class PackageSerializer(serializers.HyperlinkedModelSerializer): 29 | class Meta: 30 | model = Package 31 | fields = ('id', 'name', 'epoch', 'version', 'release', 'arch') 32 | 33 | 34 | class PackageUpdateSerializer(serializers.HyperlinkedModelSerializer): 35 | class Meta: 36 | model = PackageUpdate 37 | fields = ('id', 'oldpackage', 'newpackage', 'security') 38 | -------------------------------------------------------------------------------- /packages/templates/packages/package_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %}Package - {{ package }} {% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Packages
  • {{ package }}
  • {% endblock %} 6 | 7 | {% block content_title %} Package - {{ package }} {% endblock %} 8 | 9 | {% block content %} 10 | 11 |
    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    EpochVersionReleaseArchTypeRepositoriesHostsErrata
    {{ package.epoch }} {{ package.version }} {{ package.release }} {{ package.arch }} {{ package.get_packagetype_display }} Available from {{ package.repo_count }} Repositories Installed on {{ package.host_set.count }} Hosts Affected by {{ package.affected_by_erratum.count }} Errata Provides fix in {{ package.provides_fix_in_erratum.count }} Errata
    See All Versions of this Package
    34 |
    35 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /packages/templates/packages/package_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Packages{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Packages
  • {% endblock %} 6 | 7 | {% block content_title %} Packages {% endblock %} 8 | -------------------------------------------------------------------------------- /packages/templates/packages/package_name_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %}Package - {{ package }} {% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Packages
  • {{ package }}
  • {% endblock %} 6 | 7 | {% block content_title %} Package - {{ package }} {% endblock %} 8 | 9 | {% block content %} 10 | 11 |
    12 | {% if allversions %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for version in allversions %} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 |
    PackageEpochVersionReleaseArchTypeRepositoriesHostsErrata
    {{ version }} {{ version.epoch }} {{ version.version }} {{ version.release }} {{ version.arch }} {{ version.get_packagetype_display }} Available from {{ version.repo_count }} Repositories Installed on {{ version.host_set.count }} Hosts Affected by {{ version.affected_by_erratum.count }} Errata Provides fix in {{ version.provides_fix_in_erratum.count }} Errata
    38 | {% else %} 39 | No versions of this Package exist. 40 | {% endif %} 41 |
    42 | 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /packages/templates/packages/package_name_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Packages{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Packages
  • {% endblock %} 6 | 7 | {% block content_title %} Packages {% endblock %} 8 | -------------------------------------------------------------------------------- /packages/templates/packages/package_name_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% for packagename in object_list %} 11 | 12 | 13 | 14 | 15 | {% endfor %} 16 | 17 |
    PackageVersions available
    {{ packagename }}{{ packagename.package_set.count }}
    18 | -------------------------------------------------------------------------------- /packages/templates/packages/package_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for package in object_list %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | 30 |
    PackageEpochVersionReleaseArchTypeRepositoriesHostsErrata
    {{ package }} {{ package.epoch }} {{ package.version }} {{ package.release }} {{ package.arch }} {{ package.get_packagetype_display }} Available from {{ package.repo_count }} Repositories Installed on {{ package.host_set.count }} Hosts Affected by {{ package.affected_by_erratum.count }} Errata Provides fix in {{ package.provides_fix_in_erratum.count }} Errata
    31 | -------------------------------------------------------------------------------- /packages/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.urls import path 19 | 20 | from packages import views 21 | 22 | app_name = 'packages' 23 | 24 | urlpatterns = [ 25 | path('', views.package_name_list, name='package_name_list'), 26 | path('name/', views.package_name_list, name='package_name_list'), 27 | path('name//', views.package_name_detail, name='package_name_detail'), 28 | path('id/', views.package_list, name='package_list'), 29 | path('id//', views.package_detail, name='package_detail'), 30 | ] 31 | -------------------------------------------------------------------------------- /patchman-client.spec: -------------------------------------------------------------------------------- 1 | Name: patchman-client 2 | %define version %(cat VERSION.txt) 3 | %define _rpmdir %(pwd)/dist 4 | Version: %{version} 5 | Release: 1 6 | Summary: patchman-client uploads reports to the patchman server 7 | License: GPLv3 8 | URL: http://patchman.openbytes.ie 9 | Source: %{expand:%%(pwd)} 10 | BuildArch: noarch 11 | Requires: curl which coreutils util-linux gawk 12 | 13 | %define binary_payload w9.gzdio 14 | 15 | %description 16 | patchman-client provides a client that uploads reports to a patchman server 17 | 18 | %prep 19 | find . -mindepth 1 -delete 20 | cp -af %{SOURCEURL0}/. . 21 | 22 | %install 23 | mkdir -p %{buildroot}/usr/sbin 24 | mkdir -p %{buildroot}/etc/patchman 25 | cp ./client/%{name} %{buildroot}/usr/sbin 26 | cp ./client/%{name}.conf %{buildroot}/etc/patchman 27 | 28 | %files 29 | %defattr(755,root,root) 30 | /usr/sbin/patchman-client 31 | %config(noreplace) /etc/patchman/patchman-client.conf 32 | 33 | %changelog 34 | -------------------------------------------------------------------------------- /patchman/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from .receivers import * # noqa 18 | 19 | # This will make sure the app is always imported when 20 | # Django starts so that shared_task will use this app. 21 | from .celery import app as celery_app 22 | 23 | __all__ = ('celery_app',) 24 | -------------------------------------------------------------------------------- /patchman/celery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | import os 18 | from celery import Celery 19 | 20 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'patchman.settings') # noqa 21 | from django.conf import settings # noqa 22 | 23 | app = Celery('patchman') 24 | app.config_from_object('django.conf:settings', namespace='CELERY') 25 | app.autodiscover_tasks() 26 | -------------------------------------------------------------------------------- /patchman/signals.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.dispatch import Signal 19 | 20 | pbar_start = Signal() 21 | pbar_update = Signal() 22 | info_message = Signal() 23 | warning_message = Signal() 24 | error_message = Signal() 25 | debug_message = Signal() 26 | -------------------------------------------------------------------------------- /patchman/sqlite3/base.py: -------------------------------------------------------------------------------- 1 | # temporary fix for 'database is locked' error on sqlite3 2 | # can be removed when using django 5.1 and BEGIN IMMEDIATE in OPTIONS 3 | # see https://blog.pecar.me/django-sqlite-dblock for more details 4 | 5 | from django.db.backends.sqlite3 import base 6 | 7 | 8 | class DatabaseWrapper(base.DatabaseWrapper): 9 | def _start_transaction_under_autocommit(self): 10 | # Acquire a write lock immediately for transactions 11 | self.cursor().execute('BEGIN IMMEDIATE') 12 | -------------------------------------------------------------------------------- /patchman/static/css/base.css: -------------------------------------------------------------------------------- 1 | .secupdate { border-radius: 4px; display:block; background:#ff5555; } 2 | .bugupdate { border-radius: 4px; display:block; background:#ff9933; } 3 | .list-group { margin-bottom: 2px; } 4 | .list-group-item { padding: 5px; font-size: 12px; } 5 | .panel-body { padding: 5px; font-size: 12px; } 6 | .panel { margin-bottom: 5px; font-size: 12px; } 7 | .panel-heading { padding: 5px; font-size: 13px; } 8 | .brick { border-radius: 5px; margin: 3px 3px; padding: 2px 5px; height: 25px; border-style:solid; border-width:thin; } 9 | .label-brick { font-size: 12px; font-weight: normal; margin-bottom: 3px; padding-top: 5px; border-radius: 4px; height:25px; border-style:solid; border-width:thin; border-color: #222; white-space: normal; display: inline-block; } 10 | .breadcrumb { font-size: 12px; background-color: #222; border-radius: 0; margin-bottom: 3px; } 11 | .navbar { margin-bottom: 0; padding-bottom: 0; border-radius: 0; } 12 | .well-sm { margin-bottom: 0; } 13 | .centered { text-align: center; } 14 | -------------------------------------------------------------------------------- /patchman/static/img/icon-alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/patchman/static/img/icon-alert.gif -------------------------------------------------------------------------------- /patchman/static/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/patchman/static/img/icon-no.gif -------------------------------------------------------------------------------- /patchman/static/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/patchman/static/img/icon-yes.gif -------------------------------------------------------------------------------- /patchman/static/js/ajax-jquery.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | if (document.cookie && document.cookie != '') { 4 | var cookies = document.cookie.split(';'); 5 | for (var i = 0; i < cookies.length; i++) { 6 | var cookie = jQuery.trim(cookies[i]); 7 | // Does this cookie string begin with the name we want? 8 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 9 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 10 | break; 11 | } 12 | } 13 | } 14 | return cookieValue; 15 | } 16 | 17 | var csrftoken = getCookie('csrftoken'); 18 | 19 | function csrfSafeMethod(method) { 20 | // these HTTP methods do not require CSRF protection 21 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 22 | } 23 | $.ajaxSetup({ 24 | beforeSend: function(xhr, settings) { 25 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 26 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /patchman/static/js/button-post.js: -------------------------------------------------------------------------------- 1 | function repo_toggle_enabled(id, element, e) { 2 | e.preventDefault(); 3 | var url = id + "toggle_enabled/"; 4 | $.post(url); 5 | if (element.innerHTML.indexOf("icon-no.gif") > -1) { 6 | var newHTML = element.innerHTML.replace("icon-no.gif", "icon-yes.gif").replace("Disabled", "Enabled"); 7 | } else { 8 | var newHTML = element.innerHTML.replace("icon-yes.gif", "icon-no.gif").replace("Enabled", "Disabled"); 9 | } 10 | element.innerHTML = newHTML; 11 | } 12 | 13 | function repo_toggle_security(id, element, e) { 14 | e.preventDefault(); 15 | var url = id + "toggle_security/"; 16 | $.post(url); 17 | if (element.innerHTML.indexOf("icon-no.gif") > -1) { 18 | var newHTML = element.innerHTML.replace("icon-no.gif", "icon-yes.gif").replace("Non-Security", "Security"); 19 | } else { 20 | var newHTML = element.innerHTML.replace("icon-yes.gif", "icon-no.gif").replace("Security", "Non-Security"); 21 | } 22 | element.innerHTML = newHTML; 23 | } 24 | -------------------------------------------------------------------------------- /patchman/static/js/expandable-text.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | const expandableTexts = document.querySelectorAll('.expandable-text'); 3 | expandableTexts.forEach(text => { 4 | text.addEventListener('click', function() { 5 | this.textContent = this.dataset.fullText; 6 | }); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /patchman/wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | import os 18 | 19 | from django.core.wsgi import get_wsgi_application 20 | 21 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'patchman.settings') # noqa 22 | from django.conf import settings # noqa 23 | 24 | 25 | application = get_wsgi_application() 26 | -------------------------------------------------------------------------------- /reports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/reports/__init__.py -------------------------------------------------------------------------------- /reports/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from reports.models import Report 20 | 21 | 22 | class ReportAdmin(admin.ModelAdmin): 23 | readonly_fields = ('packages',) 24 | 25 | 26 | admin.site.register(Report, ReportAdmin) 27 | -------------------------------------------------------------------------------- /reports/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class ReportsConfig(AppConfig): 21 | name = 'reports' 22 | -------------------------------------------------------------------------------- /reports/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Report', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created', models.DateTimeField(auto_now_add=True)), 19 | ('accessed', models.DateTimeField(auto_now_add=True)), 20 | ('host', models.CharField(max_length=255, null=True)), 21 | ('domain', models.CharField(max_length=255, null=True)), 22 | ('tags', models.CharField(default='', max_length=255, null=True)), 23 | ('kernel', models.CharField(max_length=255, null=True)), 24 | ('arch', models.CharField(max_length=255, null=True)), 25 | ('os', models.CharField(max_length=255, null=True)), 26 | ('report_ip', models.GenericIPAddressField(blank=True, null=True)), 27 | ('protocol', models.CharField(max_length=255, null=True)), 28 | ('useragent', models.CharField(max_length=255, null=True)), 29 | ('processed', models.BooleanField(default=False)), 30 | ('packages', models.TextField(blank=True, null=True)), 31 | ('sec_updates', models.TextField(blank=True, null=True)), 32 | ('bug_updates', models.TextField(blank=True, null=True)), 33 | ('repos', models.TextField(blank=True, null=True)), 34 | ('reboot', models.TextField(blank=True, null=True)), 35 | ], 36 | options={ 37 | 'verbose_name_plural': 'Reports', 38 | 'ordering': ('-created',), 39 | }, 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /reports/migrations/0002_report_modules.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-12-11 22:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('reports', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='report', 15 | name='modules', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /reports/migrations/0003_remove_report_accessed.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-02-27 04:16 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('reports', '0002_report_modules'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='report', 15 | name='accessed', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /reports/migrations/0004_migrate_to_tz_aware.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.utils import timezone 3 | 4 | def make_datetimes_tz_aware(apps, schema_editor): 5 | Report = apps.get_model('reports', 'Report') 6 | for report in Report.objects.all(): 7 | if report.created and timezone.is_naive(report.created): 8 | report.created = timezone.make_aware(report.created, timezone=timezone.get_default_timezone()) 9 | report.save() 10 | 11 | class Migration(migrations.Migration): 12 | dependencies = [ 13 | ('reports', '0003_remove_report_accessed'), 14 | ] 15 | 16 | operations = [ 17 | migrations.RunPython(make_datetimes_tz_aware, migrations.RunPython.noop), 18 | ] 19 | -------------------------------------------------------------------------------- /reports/migrations/0005_alter_report_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-04 22:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('reports', '0004_migrate_to_tz_aware'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='report', 15 | options={'ordering': ['-created'], 'verbose_name_plural': 'Reports'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /reports/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/reports/migrations/__init__.py -------------------------------------------------------------------------------- /reports/tasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from celery import shared_task 19 | 20 | from django.db.utils import OperationalError 21 | 22 | from hosts.models import Host 23 | from reports.models import Report 24 | from util import info_message 25 | 26 | 27 | @shared_task(bind=True, autoretry_for=(OperationalError,), retry_backoff=True, retry_kwargs={'max_retries': 5}) 28 | def process_report(self, report_id): 29 | """ Task to process a single report 30 | """ 31 | report = Report.objects.get(id=report_id) 32 | report.process() 33 | 34 | 35 | @shared_task 36 | def process_reports(): 37 | """ Task to process all unprocessed reports 38 | """ 39 | reports = Report.objects.filter(processed=False) 40 | for report in reports: 41 | process_report.delay(report.id) 42 | 43 | 44 | @shared_task 45 | def clean_reports_with_no_hosts(): 46 | """ Task to clean processed reports where the host no longer exists 47 | """ 48 | for report in Report.objects.filter(processed=True): 49 | if not Host.objects.filter(hostname=report.host).exists(): 50 | text = f'Deleting report {report.id} for Host `{report.host}` as the host no longer exists' 51 | info_message.send(sender=None, text=text) 52 | report.delete() 53 | -------------------------------------------------------------------------------- /reports/templates/reports/report.txt: -------------------------------------------------------------------------------- 1 | The following data has been submitted for processing: 2 | 3 | host: {{ data.host }} 4 | os: {{ data.os }} 5 | kernel: {{ data.kernel }} 6 | report: {{ data.report }} 7 | tags: {{ data.tags }} 8 | protocol: {{ data.protocol }} 9 | reboot: {{ data.reboot }} 10 | {% if repos %} 11 | repos: 12 | {% for r in repos.splitlines %} {{ r|safe }} 13 | {% endfor %}{% endif %} 14 | {% if modules %} 15 | modules: 16 | {% for m in modules.splitlines %} {{ m|safe }} 17 | {% endfor %}{% endif %} 18 | {% if packages %} 19 | packages: 20 | {% for p in packages %}{% for info in p %} {{ info }} |{% endfor %} 21 | {% endfor %}{% endif %} 22 | {% if sec_updates %} 23 | security updates: 24 | {% for u in sec_updates.splitlines %} {{ u|safe }} 25 | {% endfor %}{% endif %} 26 | {% if bug_updates %} 27 | bugfix updates: 28 | {% for u in bug_updates.splitlines %} {{ u|safe }} 29 | {% endfor %}{% endif %} 30 | -------------------------------------------------------------------------------- /reports/templates/reports/report_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Reports{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Reports
  • {% endblock %} 6 | 7 | {% block content_title %} Reports {% endblock %} 8 | -------------------------------------------------------------------------------- /reports/templates/reports/report_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for report in object_list %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% endfor %} 22 | 23 |
    IDHostTimeIP AddressProcessed
    {{ report.id }} {{ report.host }} {{ report.created }} {{ report.report_ip }} {% yes_no_img report.processed 'Processed' 'Not Processed' %}
    24 | -------------------------------------------------------------------------------- /reports/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.urls import path 19 | 20 | from reports import views 21 | 22 | app_name = 'reports' 23 | 24 | urlpatterns = [ 25 | path('', views.report_list, name='report_list'), 26 | path('upload/', views.upload), 27 | path('/', views.report_detail, name='report_detail'), 28 | path('/delete/', views.report_delete, name='report_delete'), 29 | path('/process/', views.report_process, name='report_process'), 30 | ] 31 | -------------------------------------------------------------------------------- /repos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/repos/__init__.py -------------------------------------------------------------------------------- /repos/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.contrib import admin 19 | from repos.models import Repository, Mirror, MirrorPackage 20 | 21 | 22 | class MirrorAdmin(admin.ModelAdmin): 23 | readonly_fields = ('packages',) 24 | 25 | 26 | class MirrorPackageAdmin(admin.ModelAdmin): 27 | readonly_fields = ('package',) 28 | 29 | 30 | admin.site.register(Repository) 31 | admin.site.register(Mirror, MirrorAdmin) 32 | admin.site.register(MirrorPackage, MirrorPackageAdmin) 33 | -------------------------------------------------------------------------------- /repos/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class ReposConfig(AppConfig): 21 | name = 'repos' 22 | -------------------------------------------------------------------------------- /repos/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.db import models 19 | 20 | 21 | class RepositoryManager(models.Manager): 22 | def get_queryset(self): 23 | return super().get_queryset().select_related() 24 | -------------------------------------------------------------------------------- /repos/migrations/0002_alter_repository_repotype.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.25 on 2025-02-07 13:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('repos', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='repository', 15 | name='repotype', 16 | field=models.CharField(choices=[('R', 'rpm'), ('D', 'deb'), ('A', 'arch'), ('G', 'gentoo')], max_length=1), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /repos/migrations/0003_migrate_to_tz_aware.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.utils import timezone 3 | 4 | def make_datetimes_tz_aware(apps, schema_editor): 5 | Mirror = apps.get_model('repos', 'Mirror') 6 | for mirror in Mirror.objects.all(): 7 | if mirror.timestamp and timezone.is_naive(mirror.timestamp): 8 | mirror.timestamp = timezone.make_aware(mirror.timestamp, timezone=timezone.get_default_timezone()) 9 | mirror.save() 10 | 11 | class Migration(migrations.Migration): 12 | dependencies = [ 13 | ('repos', '0002_alter_repository_repotype'), 14 | ] 15 | 16 | operations = [ 17 | migrations.RunPython(make_datetimes_tz_aware, migrations.RunPython.noop), 18 | ] 19 | -------------------------------------------------------------------------------- /repos/migrations/0004_rename_file_checksum_mirror_package_checksum.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-01 15:50 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('repos', '0003_migrate_to_tz_aware'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='mirror', 15 | old_name='file_checksum', 16 | new_name='package_checksum', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /repos/migrations/0005_rename_package_checksum_mirror_packages_checksum.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-01 15:54 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('repos', '0004_rename_file_checksum_mirror_package_checksum'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='mirror', 15 | old_name='package_checksum', 16 | new_name='packages_checksum', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /repos/migrations/0006_mirror_errata_checksum_mirror_modules_checksum.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-01 15:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('repos', '0005_rename_package_checksum_mirror_packages_checksum'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='mirror', 15 | name='errata_checksum', 16 | field=models.CharField(blank=True, max_length=255, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='mirror', 20 | name='modules_checksum', 21 | field=models.CharField(blank=True, max_length=255, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /repos/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/repos/migrations/__init__.py -------------------------------------------------------------------------------- /repos/repo_types/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/repos/repo_types/__init__.py -------------------------------------------------------------------------------- /repos/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from repos.models import Repository, Mirror, MirrorPackage 20 | 21 | 22 | class RepositorySerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = Repository 25 | fields = ('id', 'name', 'arch', 'security', 'repotype', 'enabled', 'auth_required') 26 | 27 | 28 | class MirrorSerializer(serializers.HyperlinkedModelSerializer): 29 | class Meta: 30 | model = Mirror 31 | fields = ('id', 'repo', 'url', 'last_access_ok', 'packages_checksum', 32 | 'timestamp', 'mirrorlist', 'enabled', 'refresh', 'fail_count') 33 | 34 | 35 | class MirrorPackageSerializer(serializers.HyperlinkedModelSerializer): 36 | class Meta: 37 | model = MirrorPackage 38 | fields = ('id', 'mirror', 'package', 'enabled') 39 | -------------------------------------------------------------------------------- /repos/tasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from celery import shared_task 18 | 19 | from repos.models import Repository 20 | 21 | 22 | @shared_task 23 | def refresh_repo(repo_id, force=False): 24 | """ Refresh metadata for a single repo 25 | """ 26 | repo = Repository.objects.get(id=repo_id) 27 | repo.refresh(force) 28 | 29 | 30 | @shared_task 31 | def refresh_repos(force=False): 32 | """ Refresh metadata for all enabled repos 33 | """ 34 | repos = Repository.objects.filter(enabled=True) 35 | for repo in repos: 36 | refresh_repo.delay(repo.id, force) 37 | -------------------------------------------------------------------------------- /repos/templates/repos/mirror_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load common bootstrap3 %} 4 | 5 | {% block page_title %}Mirror - {{ mirror }}{% endblock %} 6 | 7 | {% block breadcrumbs %} {{ block.super }}
  • Repositories
  • Mirrors
  • {{ mirror }}
  • {% endblock %} 8 | 9 | {% block content_title %} Mirror - {{ mirror }} {% endblock %} 10 | 11 | {% block content %} 12 | 13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    Repo {{ mirror.repo }}
    URL {{ mirror.url }}
    Packages{{ mirror.packages.count }}
    Enabled {% yes_no_img mirror.enabled 'Enabled' 'Not Enabled' %}
    Refresh {% yes_no_img mirror.refresh 'True' 'False' %}
    Mirrorlist/Metalink {% yes_no_img mirror.mirrorlist 'True' 'False' %}
    Last Access OK {% yes_no_img mirror.last_access_ok 'True' 'False' %}
    Fail Count {{ mirror.fail_count }}
    Timestamp {{ mirror.timestamp }}
    Checksum {{ mirror.packages_checksum }}
    26 | {% if user.is_authenticated and perms.is_admin %} 27 | {% bootstrap_icon "trash" %} Delete this Mirror 28 | {% bootstrap_icon "edit" %} Edit this Mirror 29 | {% endif %} 30 |
    31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /repos/templates/repos/mirror_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load common bootstrap3 %} 4 | 5 | {% block extrahead %} {{ edit_form.media }} {% endblock %} 6 | 7 | {% block page_title %}Mirror - {{ mirror }}{% endblock %} 8 | 9 | {% block breadcrumbs %} {{ block.super }}
  • Repositories
  • Mirrors
  • {{ mirror }}
  • {% endblock %} 10 | 11 | {% block content_title %} Mirror - {{ mirror }} {% endblock %} 12 | 13 | {% block content %} 14 | 15 |
    16 | {% if user.is_authenticated and perms.is_admin %} 17 |
    18 | {% csrf_token %} 19 | {% bootstrap_form edit_form size='small' %} 20 | 21 | 22 |
    23 | {% else %} 24 | You do not have permission to edit this Mirror. 25 | {% endif %} 26 |
    27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /repos/templates/repos/mirror_edit_repo.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for mirror in object_list %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% endfor %} 32 | 33 |
    RepoIDURLPackagesEnabledRefreshMirrorlist/MetalinkLast Access OKTimestampChecksum
    {{ mirror.repo }}{{ mirror.id }}{{ mirror.url|truncatechars:25 }}{{ mirror.packages.count }}{% yes_no_img mirror.enabled 'Enabled' 'Not Enabled' %}{% yes_no_img mirror.refresh 'Yes' 'No' %}{% yes_no_img mirror.mirrorlist 'Yes' 'No' %}{% yes_no_img mirror.last_access_ok 'Yes' 'No' %}{{ mirror.timestamp }}{{ mirror.packages_checksum|truncatechars:16 }}
    34 | -------------------------------------------------------------------------------- /repos/templates/repos/mirror_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Mirrors{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Repositories
  • Mirrors
  • {% endblock %} 6 | 7 | {% block content_title %} Mirrors {% endblock %} 8 | -------------------------------------------------------------------------------- /repos/templates/repos/mirror_table.html: -------------------------------------------------------------------------------- 1 | {% load common bootstrap3 %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for mirror in object_list %} 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% endfor %} 38 | 39 |
    IDURLPackagesEnabledRefreshMirrorlist/MetalinkLast Access OKTimestampChecksumDeleteEdit
    {{ mirror.id }}{{ mirror.url|truncatechars:25 }} 24 | {% if not mirror.mirrorlist %} 25 | {{ mirror.packages.count }} 26 | {% endif %} 27 | {% yes_no_img mirror.enabled 'Enabled' 'Not Enabled' %}{% yes_no_img mirror.refresh 'Yes' 'No' %}{% yes_no_img mirror.mirrorlist 'Yes' 'No' %}{% yes_no_img mirror.last_access_ok 'Yes' 'No' %}{{ mirror.timestamp }}{% if not mirror.mirrorlist %}{{ mirror.packages_checksum|truncatechars:16 }}{% endif %}{% bootstrap_icon "trash" %} Delete this Mirror{% bootstrap_icon "edit" %} Edit this Mirror
    40 | -------------------------------------------------------------------------------- /repos/templates/repos/mirror_with_repo_list.html: -------------------------------------------------------------------------------- 1 | {% extends "repos/mirror_list.html" %} 2 | 3 | {% load common bootstrap3 %} 4 | 5 | {% block content %} 6 | 7 | {% gen_table page.object_list 'repos/mirror_edit_repo.html' %} 8 | 9 | {% if user.is_authenticated and perms.is_admin %} 10 | {% if link_form and create_form %} 11 |
    12 |
    13 | {% csrf_token %} 14 | 15 | {% bootstrap_form link_form size='small' %} 16 | 17 |
    18 |
    19 |
    20 |
    21 | {% csrf_token %} 22 | 23 | {% bootstrap_form create_form size='small' %} 24 | 25 |
    26 |
    27 | {% endif %} 28 | {% endif %} 29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /repos/templates/repos/repo_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load common bootstrap3 static %} 4 | 5 | {% block extrahead %} 6 | 7 | 8 | 9 | 10 | {{ edit_form.media }} 11 | {% endblock %} 12 | 13 | {% block page_title %}Repository - {{ repo }} {% endblock %} 14 | 15 | {% block breadcrumbs %} {{ block.super }}
  • Repositories
  • {{ repo }}
  • {% endblock %} 16 | 17 | {% block content_title %} Repository - {{ repo }} {% endblock %} 18 | 19 | {% block content %} 20 | 21 |
    22 | {% if user.is_authenticated and perms.is_admin %} 23 |
    24 | {% csrf_token %} 25 | {% bootstrap_form edit_form size='small' %} 26 | 27 | 28 |
    29 | {% else %} 30 | You do not have permission to edit this Repository. 31 | {% endif %} 32 |
    33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /repos/templates/repos/repo_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}Repositories{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Repositories
  • {% endblock %} 6 | 7 | {% block content_title %} Repositories {% endblock %} 8 | -------------------------------------------------------------------------------- /repos/templates/repos/repository_table.html: -------------------------------------------------------------------------------- 1 | {% load common repo_buttons %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for repo in object_list %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endfor %} 24 | 25 |
    Repo NameRepo IDMirrorsEnabledSecurityAuth Required
    {{ repo }}{% if repo.repo_id %} {{ repo.repo_id }} {% endif %} {{ repo.mirror_set.count }}
    {% yes_no_button_repo_en repo %}
    {% yes_no_button_repo_sec repo %}
    {% yes_no_img repo.auth_required %}
    26 | -------------------------------------------------------------------------------- /repos/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/repos/templatetags/__init__.py -------------------------------------------------------------------------------- /repos/templatetags/repo_buttons.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.template import Library 19 | from django.templatetags.static import static 20 | from django.utils.html import format_html 21 | 22 | register = Library() 23 | 24 | 25 | @register.simple_tag 26 | def yes_no_button_repo_en(repo): 27 | 28 | repo_url = repo.get_absolute_url() 29 | yes_icon = static('img/icon-yes.gif') 30 | no_icon = static('img/icon-no.gif') 31 | html = '' 38 | return format_html(html) 39 | 40 | 41 | @register.simple_tag 42 | def yes_no_button_repo_sec(repo): 43 | 44 | repo_url = repo.get_absolute_url() 45 | yes_icon = static('img/icon-yes.gif') 46 | no_icon = static('img/icon-no.gif') 47 | html = '' 54 | return format_html(html) 55 | -------------------------------------------------------------------------------- /repos/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.urls import path 19 | 20 | from repos import views 21 | 22 | app_name = 'repos' 23 | 24 | urlpatterns = [ 25 | path('', views.repo_list, name='repo_list'), 26 | path('/', views.repo_detail, name='repo_detail'), 27 | path('/toggle_enabled/', views.repo_toggle_enabled, name='repo_toggle_enabled'), 28 | path('/toggle_security/', views.repo_toggle_security, name='repo_toggle_security'), 29 | path('/edit/', views.repo_edit, name='repo_edit'), 30 | path('/delete/', views.repo_delete, name='repo_delete'), 31 | path('/refresh/', views.repo_refresh, name='repo_refresh'), 32 | path('mirrors/', views.mirror_list, name='mirror_list'), 33 | path('mirrors/mirror//', views.mirror_detail, name='mirror_detail'), 34 | path('mirrors/mirror//edit/', views.mirror_edit, name='mirror_edit'), 35 | path('mirrors/mirror//delete/', views.mirror_delete, name='mirror_delete'), 36 | ] 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==4.2.22 2 | django-taggit==4.0.0 3 | django-extensions==3.2.3 4 | django-bootstrap3==23.1 5 | python-debian==1.0.1 6 | defusedxml==0.7.1 7 | PyYAML==6.0.2 8 | chardet==5.2.0 9 | requests==2.32.4 10 | colorama==0.4.6 11 | djangorestframework==3.15.2 12 | django-filter==25.1 13 | humanize==4.12.1 14 | version-utils==0.3.2 15 | python-magic==0.4.27 16 | gitpython==3.1.44 17 | tenacity==8.2.3 18 | celery==5.4.0 19 | redis==5.2.1 20 | django-celery-beat==2.7.0 21 | tqdm==4.67.1 22 | cvss==3.4 23 | -------------------------------------------------------------------------------- /sbin/patchman-manage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2019-2025 Marcus Furlong 4 | # 5 | # This file is part of Patchman. 6 | # 7 | # Patchman is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 only. 10 | # 11 | # Patchman is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Patchman. If not, see 18 | 19 | import os 20 | import sys 21 | 22 | 23 | def main(): 24 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'patchman.settings') 25 | try: 26 | from django.core.management import execute_from_command_line 27 | except ImportError as exc: 28 | raise ImportError( 29 | 'Could not import Django. Are you sure it is installed and ' 30 | 'available on your PYTHONPATH environment variable? Did you ' 31 | 'forget to activate a virtual environment?' 32 | ) from exc 33 | execute_from_command_line(sys.argv) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /sbin/patchman-set-secret-key: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2016-2021 Marcus Furlong 4 | # 5 | # This file is part of Patchman. 6 | # 7 | # Patchman is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 only. 10 | # 11 | # Patchman is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Patchman. If not, see 18 | 19 | import fileinput 20 | import os 21 | import site 22 | import sys 23 | from random import choice 24 | from string import ascii_letters, digits 25 | 26 | if sys.prefix == '/usr': 27 | conf_path = '/etc/patchman' 28 | else: 29 | conf_path = os.path.join(sys.prefix, 'etc/patchman') 30 | # if sys.prefix + conf_path doesn't exist, try ./etc/patchman (source) 31 | if not os.path.isdir(conf_path): 32 | conf_path = './etc/patchman' 33 | # if ./etc/patchman doesn't exist, try site.getsitepackages() (pip) 34 | if not os.path.isdir(conf_path): 35 | try: 36 | sitepackages = site.getsitepackages() 37 | except AttributeError: 38 | # virtualenv, try site-packages in sys.path 39 | sp = 'site-packages' 40 | sitepackages = [s for s in sys.path if s.endswith(sp)][0] 41 | conf_path = os.path.join(sitepackages, 'etc/patchman') 42 | local_settings = os.path.join(conf_path, 'local_settings.py') 43 | 44 | chars = ascii_letters + digits + '!@#$%^&*(-_=+)' 45 | secret_key = ''.join([choice(chars) for i in range(50)]) 46 | 47 | for line in fileinput.input(local_settings, inplace=True): 48 | if line.startswith('SECRET_KEY'): 49 | print('SECRET_KEY = \'' + secret_key + '\'') 50 | else: 51 | print(line.rstrip()) 52 | -------------------------------------------------------------------------------- /scripts/clear-django-logs.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-2016 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.contrib.admin.models import LogEntry 18 | 19 | logs = LogEntry.objects.all() 20 | 21 | for log in logs: 22 | log.delete() 23 | -------------------------------------------------------------------------------- /scripts/create_graph.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2011 VPAC 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | #!/bin/bash 18 | 19 | ../patchman/manage.py graph_models -g -o patchman.png hosts operatingsystems packages arch repos domains reports 20 | -------------------------------------------------------------------------------- /scripts/rpm-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This file becomes the install section of the generated spec file. 4 | # 5 | 6 | # This is what dist.py normally does. 7 | python3 setup.py install --single-version-externally-managed --root=${RPM_BUILD_ROOT} --record="INSTALLED_FILES" 8 | 9 | # Sort the filelist so that directories appear before files. This avoids 10 | # duplicate filename problems on some systems. 11 | touch DIRS 12 | for i in `cat INSTALLED_FILES`; do 13 | if [ -f ${RPM_BUILD_ROOT}/$i ]; then 14 | echo $i >>FILES 15 | fi 16 | if [ -d ${RPM_BUILD_ROOT}/$i ]; then 17 | echo %dir $i >>DIRS 18 | fi 19 | done 20 | 21 | cat DIRS > INSTALLED_FILES 22 | sed -e '/\/etc\//s|^|%config(noreplace) |' FILES >>INSTALLED_FILES 23 | -------------------------------------------------------------------------------- /scripts/rpm-post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -e /etc/httpd/conf.d/patchman.conf ] ; then 4 | cp /etc/patchman/apache.conf.example /etc/httpd/conf.d/patchman.conf 5 | fi 6 | 7 | if ! grep /usr/lib/python3.9/site-packages /etc/httpd/conf.d/patchman.conf >/dev/null 2>&1 ; then 8 | sed -i -e "s/^\(Define patchman_pythonpath\).*/\1 \/usr\/lib\/python3.9\/site-packages/" \ 9 | /etc/httpd/conf.d/patchman.conf 10 | fi 11 | 12 | systemctl enable httpd 13 | systemctl restart httpd 14 | systemctl enable redis 15 | systemctl start redis 16 | 17 | patchman-set-secret-key 18 | chown apache /etc/patchman/local_settings.py 19 | 20 | mkdir -p /var/lib/patchman/db 21 | patchman-manage collectstatic --noinput 22 | 23 | patchman-manage makemigrations 24 | patchman-manage migrate --run-syncdb --fake-initial 25 | sqlite3 /var/lib/patchman/db/patchman.db 'PRAGMA journal_mode=WAL;' 26 | 27 | chown -R apache:apache /var/lib/patchman 28 | adduser --system --group patchman-celery 29 | usermod -a -G apache patchman-celery 30 | chmod g+w /var/lib/patchman /var/lib/patchman/db /var/lib/patchman/db/patchman.db 31 | chcon --type httpd_sys_rw_content_t /var/lib/patchman/db/patchman.db 32 | semanage port -a -t http_port_t -p tcp 5672 33 | setsebool -P httpd_can_network_memcache 1 34 | setsebool -P httpd_can_network_connect 1 35 | 36 | echo 37 | echo "Remember to run 'patchman-manage createsuperuser' to create a user." 38 | echo 39 | -------------------------------------------------------------------------------- /security/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/security/__init__.py -------------------------------------------------------------------------------- /security/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.contrib import admin 18 | from security.models import CWE, CVSS, CVE, Reference 19 | 20 | 21 | admin.site.register(CWE) 22 | admin.site.register(CVSS) 23 | admin.site.register(CVE) 24 | admin.site.register(Reference) 25 | -------------------------------------------------------------------------------- /security/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class SecurityConfig(AppConfig): 21 | name = 'security' 22 | -------------------------------------------------------------------------------- /security/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.db import models 18 | 19 | 20 | class CVEManager(models.Manager): 21 | def get_queryset(self): 22 | return super().get_queryset().select_related() 23 | -------------------------------------------------------------------------------- /security/migrations/0002_alter_cve_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-02-11 03:51 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='cve', 15 | options={'ordering': ('cve_id',)}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /security/migrations/0003_alter_cve_description_alter_cwe_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-02-26 16:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0002_alter_cve_options'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='cve', 15 | name='description', 16 | field=models.TextField(blank=True, default=''), 17 | ), 18 | migrations.AlterField( 19 | model_name='cwe', 20 | name='description', 21 | field=models.TextField(blank=True, default=''), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /security/migrations/0004_alter_cve_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-04 22:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0003_alter_cve_description_alter_cwe_description'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='cve', 15 | options={'ordering': ['cve_id']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /security/migrations/0005_reference_cve_references.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-05 19:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0004_alter_cve_options'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Reference', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('ref_type', models.CharField(max_length=255)), 18 | ('url', models.URLField(max_length=765)), 19 | ], 20 | options={ 21 | 'unique_together': {('ref_type', 'url')}, 22 | }, 23 | ), 24 | migrations.AddField( 25 | model_name='cve', 26 | name='references', 27 | field=models.ManyToManyField(blank=True, to='security.reference'), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /security/migrations/0006_alter_cve_options_alter_cvss_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.19 on 2025-03-10 17:36 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0005_reference_cve_references'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='cve', 15 | options={'ordering': ['-cve_id']}, 16 | ), 17 | migrations.AlterUniqueTogether( 18 | name='cvss', 19 | unique_together={('score', 'severity', 'version', 'vector_string')}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /security/migrations/0007_remove_cve_title.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-04-20 20:15 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('security', '0006_alter_cve_options_alter_cvss_unique_together'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='cve', 15 | name='title', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /security/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/security/migrations/__init__.py -------------------------------------------------------------------------------- /security/serializers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from rest_framework import serializers 18 | 19 | from security.models import CVE, CWE, Reference 20 | 21 | 22 | class CWESerializer(serializers.HyperlinkedModelSerializer): 23 | class Meta: 24 | model = CWE 25 | fields = ('cwe_id', 'title', 'description') 26 | 27 | 28 | class CVESerializer(serializers.HyperlinkedModelSerializer): 29 | class Meta: 30 | model = CVE 31 | fields = ('cve_id', 'description', 'cvss_score', 'cwe', 32 | 'registered_date', 'published_date', 'updated_date') 33 | 34 | 35 | class ReferenceSerializer(serializers.HyperlinkedModelSerializer): 36 | class Meta: 37 | model = Reference 38 | fields = ('id', 'ref_type', 'url') 39 | -------------------------------------------------------------------------------- /security/tasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from celery import shared_task 18 | 19 | from security.models import CVE, CWE 20 | 21 | 22 | @shared_task 23 | def update_cve(cve_id): 24 | """ Task to update a CVE 25 | """ 26 | cve = CVE.objects.get(id=cve_id) 27 | cve.fetch_cve_data() 28 | 29 | 30 | @shared_task 31 | def update_cves(): 32 | """ Task to update all CVEs 33 | """ 34 | for cve in CVE.objects.all(): 35 | update_cve.delay(cve.id) 36 | 37 | 38 | @shared_task 39 | def update_cwe(cwe_id): 40 | """ Task to update a CWE 41 | """ 42 | cwe = CWE.objects.get(id=cwe_id) 43 | cwe.fetch_cwe_data() 44 | 45 | 46 | @shared_task 47 | def update_cwes(): 48 | """ Task to update all CWEa 49 | """ 50 | for cwe in CWE.objects.all(): 51 | update_cwe.delay(cwe.id) 52 | -------------------------------------------------------------------------------- /security/templates/security/cve_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}CVEs{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Security
  • CVEs
  • {% endblock %} 6 | 7 | {% block content_title %} CVEs {% endblock %} 8 | -------------------------------------------------------------------------------- /security/templates/security/cve_table.html: -------------------------------------------------------------------------------- 1 | {% load common bootstrap3 %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for cve in object_list %} 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% endfor %} 36 | 37 |
    CVE IDLinksDescriptionCVSS ScoresCWEsReservedRejectedPublishedUpdatedErrata
    {{ cve.cve_id }} 22 | NIST {% bootstrap_icon "link" %}   23 | MITRE {% bootstrap_icon "link" %}   24 | osv.dev {% bootstrap_icon "link" %} 25 | {{ cve.description|truncatechars:60 }}{% for score in cve.cvss_scores.all %} {{ score.score }} {% endfor %}{% for cwe in cve.cwes.all %} {{ cwe.cwe_id }} {% endfor %}{{ cve.reserved_date|date|default_if_none:'' }}{{ cve.rejected_date|date|default_if_none:'' }}{{ cve.published_date|date|default_if_none:'' }}{{ cve.updated_date|date|default_if_none:'' }}{{ cve.erratum_set.count }}
    38 | -------------------------------------------------------------------------------- /security/templates/security/cwe_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load common bootstrap3 %} 4 | 5 | {% block page_title %}CWE - {{ cwe }} {% endblock %} 6 | 7 | {% block breadcrumbs %} {{ block.super }}
  • Security
  • CWEs
  • {{ cwe }}
  • {% endblock %} 8 | 9 | {% block content_title %} CWE - {{ cwe }} {% endblock %} 10 | 11 | {% block content %} 12 | 13 |
    14 | 15 | 16 | 17 | 18 | 19 |
    CWE ID{{ cwe.cwe_id }}
    Name{{ cwe.name }} {% bootstrap_icon "link" %}
    Description{{ cwe.description }}
    Affected CVEs{{ cwe.cve_set.count }}
    20 |
    21 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /security/templates/security/cwe_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}CWEs{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Security
  • CWEs
  • {% endblock %} 6 | 7 | {% block content_title %} CWEs {% endblock %} 8 | -------------------------------------------------------------------------------- /security/templates/security/cwe_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% for cwe in object_list %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% endfor %} 20 | 21 |
    CWE IDNameDescriptionCVEs
    {{ cwe.cwe_id }}{{ cwe.name }}{{ cwe.description|truncatechars:120 }}{{ cwe.cve_set.count }}
    22 | -------------------------------------------------------------------------------- /security/templates/security/reference_list.html: -------------------------------------------------------------------------------- 1 | {% extends "objectlist.html" %} 2 | 3 | {% block page_title %}References{% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Security
  • References
  • {% endblock %} 6 | 7 | {% block content_title %} References {% endblock %} 8 | -------------------------------------------------------------------------------- /security/templates/security/reference_table.html: -------------------------------------------------------------------------------- 1 | {% load common %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for eref in object_list %} 12 | 13 | 14 | 15 | 16 | 17 | {% endfor %} 18 | 19 |
    TypeURLLinked Errata
    {{ eref.ref_type }}{{ eref.url }}{{ eref.erratum_set.count }}
    20 | -------------------------------------------------------------------------------- /security/templates/security/security_landing.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %} Security {% endblock %} 4 | 5 | {% block breadcrumbs %} {{ block.super }}
  • Security
  • {% endblock %} 6 | 7 | {% block content_title %} Security {% endblock %} 8 | 9 | {% block content %} 10 | 11 |
    12 | 13 | 14 | 15 | 16 | 17 |
    CVEs
    CWEs
    Security Errata
    Security References
    18 |
    19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /security/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.urls import path 18 | 19 | from security import views 20 | 21 | app_name = 'security' 22 | 23 | urlpatterns = [ 24 | path('', views.security_landing, name='security_landing'), 25 | path('cves', views.cve_list, name='cve_list'), 26 | path('cves/', views.cve_detail, name='cve_detail'), 27 | path('cwes', views.cwe_list, name='cwe_list'), 28 | path('cwes/', views.cwe_detail, name='cwe_detail'), 29 | path('references/', views.reference_list, name='reference_list'), 30 | ] 31 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_rpm] 2 | doc_files = README.md AUTHORS COPYING INSTALL.md 3 | install-script = scripts/rpm-install.sh 4 | post-install = scripts/rpm-post-install.sh 5 | requires = /usr/bin/python3 6 | python3-django >= 4.2.20 7 | python3-django-taggit 8 | python3-django-extensions 9 | python3-django-bootstrap3 10 | python3-django-rest-framework 11 | python3-django-filter 12 | python3-debian 13 | python3-rpm 14 | python3-tqdm 15 | python3-tenacity 16 | python3-defusedxml 17 | python3-requests 18 | python3-colorama 19 | python3-magic 20 | python3-humanize 21 | memcached 22 | python3-pyyaml 23 | python3-pymemcache 24 | python3-mod_wsgi 25 | python3-importlib-metadata 26 | python3-cvss 27 | python3-redis 28 | redis 29 | celery 30 | python3-django-celery-beat 31 | python3-GitPython 32 | policycoreutils-python-utils 33 | httpd 34 | python3-dnf-plugin-post-transaction-actions 35 | 36 | [install] 37 | optimize=1 38 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = True 3 | envlist = 4 | py38 5 | py38-flake8 6 | py39 7 | py39-flake8 8 | py310 9 | py310-flake8 10 | py311 11 | py311-flake8 12 | py312 13 | py312-flake8 14 | 15 | [testenv] 16 | deps = 17 | nose 18 | -rrequirements.txt 19 | commands = nosetests 20 | 21 | [flake8] 22 | builtins = _ 23 | deps = 24 | flake8 25 | commands = flake8 26 | exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*migrations* 27 | max-line-length = 120 28 | 29 | [testenv:py38-flake8] 30 | basepython = python3.8 31 | deps = {[flake8]deps} 32 | commands = {[flake8]commands} 33 | 34 | [testenv:py39-flake8] 35 | basepython = python3.9 36 | deps = {[flake8]deps} 37 | commands = {[flake8]commands} 38 | 39 | [testenv:py310-flake8] 40 | basepython = python3.10 41 | deps = {[flake8]deps} 42 | commands = {[flake8]commands} 43 | 44 | [testenv:py311-flake8] 45 | basepython = python3.11 46 | deps = {[flake8]deps} 47 | commands = {[flake8]commands} 48 | 49 | [testenv:py312-flake8] 50 | basepython = python3.12 51 | deps = {[flake8]deps} 52 | commands = {[flake8]commands} 53 | -------------------------------------------------------------------------------- /util/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from django.apps import AppConfig 18 | 19 | 20 | class UtilConfig(AppConfig): 21 | name = 'util' 22 | -------------------------------------------------------------------------------- /util/tasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Marcus Furlong 2 | # 3 | # This file is part of Patchman. 4 | # 5 | # Patchman is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, version 3 only. 8 | # 9 | # Patchman is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with Patchman. If not, see 16 | 17 | from celery import shared_task 18 | 19 | from arch.utils import clean_architectures 20 | from modules.utils import clean_modules 21 | from packages.utils import clean_packages, clean_packageupdates, clean_packagenames 22 | from repos.utils import clean_repos, remove_mirror_trailing_slashes 23 | 24 | 25 | @shared_task 26 | def clean_database(remove_duplicate_packages=False): 27 | """ Task to check the database and remove orphaned objects 28 | Runs all clean_* functions to check database consistency 29 | """ 30 | clean_packageupdates() 31 | clean_packages(remove_duplicates=remove_duplicate_packages) 32 | clean_packagenames() 33 | clean_architectures() 34 | clean_repos() 35 | remove_mirror_trailing_slashes() 36 | clean_modules() 37 | clean_packageupdates() 38 | -------------------------------------------------------------------------------- /util/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %}404 - Page not found{% endblock %} 4 | 5 | {% block content_title %}404 - Page not found{% endblock %} 6 | 7 | {% block content %} 8 |
    9 | Sorry, but the requested page could not be found. 10 |
    11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /util/templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 - Server Error 6 | 7 | 8 |

    500 - Server Error

    9 |

    There has been an error. It has been reported to the site administrators and should be fixed shortly.

    10 | 11 | 12 | -------------------------------------------------------------------------------- /util/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% load bootstrap3 %} 6 | {% bootstrap_css %} 7 | {% bootstrap_javascript jquery=1 %} 8 | {% load static %} 9 | 10 | {% block page_title %}{% endblock %} 11 | 12 | 13 | 14 | {% block extrahead %}{% endblock %} 15 | 16 | 17 |

    18 | {% include "navbar.html" with user=user %} 19 | 24 | {% bootstrap_messages %} 25 |
    26 |
    27 |
    28 | {% block content_title %}{% endblock %} 29 |
    30 |
    31 | {% block content %}{{ content }}{% endblock %} 32 |
    33 | {% block footer %}{% endblock %} 34 |
    35 |
    36 |
    37 | 38 | 39 | -------------------------------------------------------------------------------- /util/templates/objectlist.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load common bootstrap3 static %} 4 | 5 | {% block content %} 6 | 7 |
    8 |
    9 | {% get_querydict request as querydict %} 10 | {% searchform terms querydict %} 11 | {% gen_table page.object_list table_template %} 12 |
    13 | {% object_count page %} 14 |
    15 |
    16 | {% get_querystring request as querystring %} 17 | {% bootstrap_pagination page size='small' extra=querystring %} 18 |
    19 |
    20 | Page {{ page.number }} of {{ page.paginator.num_pages }} 21 |
    22 |
    23 | 24 | {% if filter_bar %} 25 |
    26 |
    27 |
    Filter by...
    28 |
    29 | {{ filter_bar|safe }} 30 |
    31 |
    32 |
    33 | {% endif %} 34 |
    35 | 36 | {% block objectlist_actions %}{% endblock %} 37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /util/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load bootstrap3 %} 4 | 5 | {% block page_title %}Login{% endblock %} 6 | 7 | {% block breadcrumbs %}{% endblock %} 8 | 9 | {% block content %} 10 | 11 |
    12 |
    Please enter your username and password to login.
    13 |
    14 | {% if form.errors %} 15 |
    Your username and password didn't match. Please try again.
    16 | {% endif %} 17 | {% if next and user.is_authenticated %} 18 |
    Your account doesn't have access to this page. To proceed, 19 | please login with an account that has access.
    20 | {% endif %} 21 |
    22 |
    23 | {% csrf_token %} 24 |
    25 | 26 | 27 |

    28 | 29 | 30 |
    31 |
    32 |
    33 |
    34 |
    35 | 36 | 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /util/templates/searchbar.html: -------------------------------------------------------------------------------- 1 | {% load bootstrap3 %} 2 |
    3 |
    4 |
    5 | 6 | {% for key, value in querydict.items %} 7 | 8 | {% endfor %} 9 |
    10 | 11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 | -------------------------------------------------------------------------------- /util/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furlongm/patchman/334af8fbdeedc817c65a5b96eff9ffe9b1e8c938/util/templatetags/__init__.py -------------------------------------------------------------------------------- /util/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 VPAC, http://www.vpac.org 2 | # Copyright 2013-2021 Marcus Furlong 3 | # 4 | # This file is part of Patchman. 5 | # 6 | # Patchman is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 only. 9 | # 10 | # Patchman is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Patchman. If not, see 17 | 18 | from django.urls import path 19 | from django.views.generic import RedirectView 20 | 21 | from util import views 22 | 23 | app_name = 'util' 24 | 25 | urlpatterns = [ 26 | path('', RedirectView.as_view(pattern_name='util:dashboard', permanent=True)), # noqa 27 | path('dashboard/', views.dashboard, name='dashboard'), 28 | ] 29 | --------------------------------------------------------------------------------