├── netbox_config_backup ├── tasks.py ├── tests │ ├── __init__.py │ ├── test_forms.py │ ├── test_api.py │ ├── test_models.py │ ├── test_views.py │ └── test_filtersets.py ├── backup │ ├── __init__.py │ └── processing.py ├── graphql │ ├── __init__.py │ ├── schema.py │ ├── types.py │ └── filters.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── listbackups.py │ │ ├── runbackup.py │ │ ├── fix_missing.py │ │ └── rebuild.py ├── migrations │ ├── __init__.py │ ├── 0011_alter_backup_name.py │ ├── 0005_commit_add_time_field.py │ ├── 0012_backup_status.py │ ├── 0021_backupfile_last_change.py │ ├── 0014_backup_config_status.py │ ├── 0008_backupjob_scheduled_nullable.py │ ├── 0016_add_pid_to_backup_job.py │ ├── 0004_custom_constraints.py │ ├── 0007_backup_job_add_scheduled.py │ ├── 0015_backup_comments_backup_description.py │ ├── 0006_backup_add_commit_last_time.py │ ├── 0010_backup_ip.py │ ├── 0019_alter_backup_device.py │ ├── 0017_add_job_to_backupjob.py │ ├── 0020_clean_rq_queue.py │ ├── 0022_model_ordering.py │ ├── 0003_primary_model_to_bigid.py │ ├── 0018_move_to_nbmodel.py │ ├── 0013_backup__to_netboxmodel.py │ ├── 0001_initial.py │ ├── 0002_git_models.py │ └── 0009_bctc_file_model_backup_fk_restructure.py ├── template_content.py ├── templatetags │ ├── __init__.py │ └── ncb_split.py ├── api │ ├── __init__.py │ ├── urls.py │ ├── views.py │ └── serializers.py ├── exceptions │ └── __init__.py ├── utils │ ├── db.py │ ├── __init__.py │ ├── logger.py │ ├── git.py │ ├── backups.py │ ├── rq.py │ ├── napalm.py │ └── configs.py ├── templates │ └── netbox_config_backup │ │ ├── repository.html │ │ ├── buttons │ │ ├── config.html │ │ ├── diff.html │ │ └── runbackups.html │ │ ├── config.html │ │ ├── inc │ │ └── backup_tables.html │ │ ├── backupjob.html │ │ ├── compliance.html │ │ ├── diff.html │ │ ├── backups.html │ │ └── backup.html ├── jobs │ ├── __init__.py │ ├── housekeeping.py │ └── backup.py ├── models │ ├── abstract.py │ ├── __init__.py │ ├── jobs.py │ ├── repository.py │ └── backups.py ├── helpers.py ├── choices.py ├── urls.py ├── __init__.py ├── navigation.py ├── querysets │ └── __init__.py ├── object_actions │ └── __init__.py ├── filtersets.py ├── tables.py ├── forms.py ├── git.py └── views.py ├── .black ├── MANIFEST.in ├── ruff.toml ├── .github ├── workflows │ ├── release.yml │ ├── pr_approval.yml │ ├── build-test.yml │ ├── ci.yml │ └── pypi.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yaml │ └── bug_report.yaml ├── FUNDING.yml ├── release-drafter.yml └── configuration.testing.py ├── .pre-commit-config.yaml ├── pyproject.toml ├── README.md ├── .gitignore └── LICENSE /netbox_config_backup/tasks.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/backup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/graphql/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/template_content.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/templatetags/ncb_split.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netbox_config_backup/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .serializers import * 2 | -------------------------------------------------------------------------------- /.black: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | skip-string-normalization = 1 3 | line-length = 120 -------------------------------------------------------------------------------- /netbox_config_backup/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | class JobExit(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /netbox_config_backup/utils/db.py: -------------------------------------------------------------------------------- 1 | from django import db 2 | 3 | 4 | def close_db(): 5 | db.connections.close_all() 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | recursive-include netbox_config_backup/templates * 4 | recursive-include netbox_config_backup/static * -------------------------------------------------------------------------------- /netbox_config_backup/templates/netbox_config_backup/repository.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/layout.html' %} 2 | 3 | {% block content %} 4 | Repository 5 | {% endblock %} -------------------------------------------------------------------------------- /netbox_config_backup/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | from .backup import BackupRunner 2 | from .housekeeping import BackupHousekeeping 3 | 4 | __all__ = ('BackupRunner', 'BackupHousekeeping') 5 | -------------------------------------------------------------------------------- /netbox_config_backup/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .backups import get_backup_tables 2 | from .git import Differ 3 | 4 | __all__ = ( 5 | 'get_backup_tables', 6 | 'Differ', 7 | ) 8 | -------------------------------------------------------------------------------- /netbox_config_backup/api/urls.py: -------------------------------------------------------------------------------- 1 | from netbox.api.routers import NetBoxRouter 2 | from .views import * 3 | 4 | 5 | router = NetBoxRouter() 6 | router.register('backup', BackupViewSet) 7 | urlpatterns = router.urls 8 | -------------------------------------------------------------------------------- /netbox_config_backup/models/abstract.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class BigIDModel(models.Model): 5 | id = models.BigAutoField(primary_key=True) 6 | 7 | class Meta: 8 | abstract = True 9 | -------------------------------------------------------------------------------- /netbox_config_backup/templates/netbox_config_backup/buttons/config.html: -------------------------------------------------------------------------------- 1 | 2 | {{ label }} 3 | 4 | -------------------------------------------------------------------------------- /netbox_config_backup/templates/netbox_config_backup/buttons/diff.html: -------------------------------------------------------------------------------- 1 | 2 | {{ label }} 3 | 4 | -------------------------------------------------------------------------------- /netbox_config_backup/templates/netbox_config_backup/buttons/runbackups.html: -------------------------------------------------------------------------------- 1 | 2 | {{ label }} 3 | 4 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | exclude = [] 2 | line-length = 120 3 | target-version = "py310" 4 | 5 | [lint] 6 | extend-select = ["E1", "E2", "E3", "E501", "W"] 7 | ignore = ["F403", "F405"] 8 | preview = true 9 | 10 | [lint.per-file-ignores] 11 | "template_code.py" = ["E501"] 12 | 13 | [format] 14 | quote-style = "single" 15 | -------------------------------------------------------------------------------- /netbox_config_backup/api/views.py: -------------------------------------------------------------------------------- 1 | from netbox.api.viewsets import NetBoxModelViewSet 2 | from netbox_config_backup.api import BackupSerializer 3 | from netbox_config_backup.models import Backup 4 | 5 | 6 | class BackupViewSet(NetBoxModelViewSet): 7 | queryset = Backup.objects.all() 8 | serializer_class = BackupSerializer 9 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Drafter 3 | 4 | on: 5 | push: 6 | branches: 7 | - "main" 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: release-drafter/release-drafter@v5 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/pr_approval.yml: -------------------------------------------------------------------------------- 1 | name: Auto approve 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | branches: 7 | - "main" 8 | 9 | jobs: 10 | auto-approve: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | pull-requests: write 14 | if: github.actor == 'dansheps' 15 | steps: 16 | - uses: hmarr/auto-approve-action@v4 -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.6.9 4 | hooks: 5 | - id: ruff 6 | name: "Ruff linter" 7 | args: [ netbox_config_backup/ ] 8 | - repo: https://github.com/psf/black-pre-commit-mirror 9 | rev: 25.1.0 10 | hooks: 11 | - id: black 12 | name: "Black" 13 | args: [--check] 14 | 15 | -------------------------------------------------------------------------------- /netbox_config_backup/graphql/schema.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import strawberry 4 | import strawberry_django 5 | 6 | from .types import * 7 | 8 | 9 | @strawberry.type(name="Query") 10 | class BackupQuery: 11 | backup: BackupType = strawberry_django.field() 12 | backup_list: List[BackupType] = strawberry_django.field() 13 | 14 | 15 | schema = [ 16 | BackupQuery, 17 | ] 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: 💬 Community Slack 5 | url: https://netdev.chat/ 6 | about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems" -------------------------------------------------------------------------------- /netbox_config_backup/models/__init__.py: -------------------------------------------------------------------------------- 1 | from netbox_config_backup.models.backups import Backup 2 | from netbox_config_backup.models.repository import ( 3 | BackupCommit, 4 | BackupFile, 5 | BackupObject, 6 | BackupCommitTreeChange, 7 | ) 8 | from netbox_config_backup.models.jobs import BackupJob 9 | 10 | 11 | __all__ = ( 12 | 'Backup', 13 | 'BackupCommit', 14 | 'BackupFile', 15 | 'BackupObject', 16 | 'BackupCommitTreeChange', 17 | 'BackupJob', 18 | ) 19 | -------------------------------------------------------------------------------- /netbox_config_backup/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | def get_logger(): 6 | # Setup logging to Stdout 7 | formatter = logging.Formatter('[%(asctime)s][%(levelname)s] - %(message)s') 8 | stdouthandler = logging.StreamHandler(sys.stdout) 9 | stdouthandler.setLevel(logging.DEBUG) 10 | stdouthandler.setFormatter(formatter) 11 | logger = logging.getLogger("netbox_config_backup") 12 | logger.addHandler(stdouthandler) 13 | 14 | return logger 15 | -------------------------------------------------------------------------------- /netbox_config_backup/templates/netbox_config_backup/config.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %} 2 | 3 | {% block content %} 4 |
{{backup_config}}| Backup | 19 |{{ object.backup|linkify }} | 20 |
|---|---|
| Status | 23 |{{ object.status }} | 24 |
{% for line in diff %}{% spaceless %}
19 | {% if line == '+++' or line == '---' %}
20 | {% elif line|make_list|first == '@' %}
21 | {{line}}
22 | {% elif line|make_list|first == '+' %}
23 | {{line|make_list|slice:'1:'|join:''}}
24 | {% elif line|make_list|first == '-' %}
25 | {{line|make_list|slice:'1:'|join:''}}
26 | {% else %}
27 | {{line}}
28 | {% endif %}
29 | {% endspaceless %}{% endfor %}
30 | {% for line in diff %}{% spaceless %}
21 | {% if line == '+++' or line == '---' %}
22 | {% elif line|make_list|first == '@' %}
23 | {{line}}
24 | {% elif line|make_list|first == '+' %}
25 | {{line|make_list|slice:'1:'|join:''}}
26 | {% elif line|make_list|first == '-' %}
27 | {{line|make_list|slice:'1:'|join:''}}
28 | {% else %}
29 | {{line}}
30 | {% endif %}
31 | {% endspaceless %}{% endfor %}
32 | | Name | 19 |{{ object.name|placeholder }} | 20 ||
|---|---|---|
| UUID | 23 |{{ object.uuid }} | 24 ||
| Device | 27 | {% if object.device %} 28 |{{ object.device|placeholder }} | 29 | {% else %} 30 |{{ object.device|placeholder }} | 31 | {% endif %} 32 |
| IP Address | 35 | {% if object.ip %} 36 |{{ object.ip|placeholder }} | 37 | {% else %} 38 |{{ object.ip|placeholder }} | 39 | {% endif %} 40 |
| Description | 43 |{{ object.description|placeholder }} | 44 |
| Config Saved | 60 |{{ object.config_status | placeholder }} | 61 |
|---|---|
| Scheduled | 64 |{{ status.scheduled | placeholder }}{% if status.scheduled %} ({{status.next_attempt}}){% endif %} | 65 |
| Last Job | 68 |{{ status.last_job.completed | placeholder }} | 69 |
| Last Success | 72 |{{ status.last_success | placeholder }} | 73 |
| Last Change | 76 |{{ status.last_change | placeholder }} | 77 |