diff --git a/PORT_MAP.md b/PORT_MAP.md index 003339b..44d5874 100644 --- a/PORT_MAP.md +++ b/PORT_MAP.md @@ -44,6 +44,7 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports. | 22050 | radicale | radicale carddav and caldav server | | 22060 | pretalx | gunicorn | | 22070 | paperless-ng | gunicorn | +| 22080 | netbox | gunicorn | | 22999 | nginx | stub_status | ## UDP diff --git a/bundles/netbox/README.md b/bundles/netbox/README.md new file mode 100644 index 0000000..c449ce2 --- /dev/null +++ b/bundles/netbox/README.md @@ -0,0 +1,21 @@ +netbox +------ + +IP address management (IPAM) and data center infrastructure management (DCIM) tool. + +https://github.com/digitalocean/netbox + +Metadata: + + 'netbox': { + 'hostname': 'netbox.smedia.tools', + 'version': '2.0.8', + }, + 'nginx': { + 'vhosts': { + 'netbox': { + 'generics': {'netbox.conf'}, + }, + }, + } + diff --git a/bundles/netbox/files/configuration.py b/bundles/netbox/files/configuration.py new file mode 100644 index 0000000..da3b20d --- /dev/null +++ b/bundles/netbox/files/configuration.py @@ -0,0 +1,117 @@ +ALLOWED_HOSTS = [ + '${node.metadata.get('netbox/domain')}' +] + +DATABASE = { + 'NAME': 'netbox', + 'USER': 'netbox', + 'PASSWORD': '${repo.vault.password_for("netbox postgresql " + node.name)}', + 'HOST': 'localhost', + 'PORT': '', +} + +REDIS = { + 'tasks': { + 'HOST': 'localhost', + 'PORT': 6379, + 'PASSWORD': '', + 'DATABASE': 0, + 'DEFAULT_TIMEOUT': 300, + 'SSL': False, + }, + 'caching': { + 'HOST': 'localhost', + 'PORT': 6379, + 'PASSWORD': '', + 'DATABASE': 1, + 'DEFAULT_TIMEOUT': 300, + 'SSL': False, + } +} + +SECRET_KEY = '${repo.vault.password_for('django secret netbox ' + node.name, length=50)}' + +ADMINS = [ +% for name, email in sorted(node.metadata.get('netbox/admins', {})): + ['${name}', '${email}'], +% endfor +] + +BANNER_TOP = '' +BANNER_BOTTOM = '' + +BASE_PATH = '' + +CACHE_TIMEOUT = 0 + +CHANGELOG_RETENTION = ${node.metadata.get('netbox/changelog_retention_days', 90)} + +CORS_ORIGIN_ALLOW_ALL = False +CORS_ORIGIN_WHITELIST = [ +% for hostname in sorted(node.metadata.get('netbox/cors_allowlist', set())): + '${hostname}', +% endfor +] +CORS_ORIGIN_REGEX_WHITELIST = [] + +DEBUG = False + +EMAIL = { + 'SERVER': 'localhost', + 'PORT': 25, + 'USERNAME': '', + 'PASSWORD': '', + 'TIMEOUT': 10, # seconds + 'FROM_EMAIL': '', +} + +ENFORCE_GLOBAL_UNIQUE = True + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + }, + }, + 'root': { + 'handlers': ['console'], + 'level': 'WARNING', + }, +} + +LOGIN_REQUIRED = False + +MAINTENANCE_MODE = False + +MAX_PAGE_SIZE = 1000 + +MEDIA_ROOT = '/opt/netbox/media' + +NAPALM_USERNAME = '' +NAPALM_PASSWORD = '' +NAPALM_TIMEOUT = 30 +NAPALM_ARGS = {} + +PAGINATE_COUNT = 100 + +PLUGINS = {} + +PREFER_IPV4 = False + +# We use icinga for that. +RELEASE_CHECK_URL = None + +REMOTE_AUTH_BACKEND = 'netbox.authentication.RemoteUserBackend' + +SCRIPTS_ROOT = '/opt/netbox/scripts' + +TIME_ZONE = 'UTC' + +DATE_FORMAT = 'N j, Y' +SHORT_DATE_FORMAT = 'Y-m-d' +TIME_FORMAT = 'g:i a' +SHORT_TIME_FORMAT = 'H:i:s' +DATETIME_FORMAT = 'N j, Y g:i a' +SHORT_DATETIME_FORMAT = 'Y-m-d H:i' diff --git a/bundles/netbox/files/gunicorn_config.py b/bundles/netbox/files/gunicorn_config.py new file mode 100644 index 0000000..432fa07 --- /dev/null +++ b/bundles/netbox/files/gunicorn_config.py @@ -0,0 +1,7 @@ +command = '/opt/netbox/venv/bin/gunicorn' +pythonpath = '/opt/netbox/src/netbox' +bind = '127.0.0.1:22080' +workers = ${min(node.metadata.get('vm/cpu', 1), 6)*2+1} +threads = 1 +max_requests = 5000 +max_requests_jitter = 500 diff --git a/bundles/netbox/files/netbox-web.service b/bundles/netbox/files/netbox-web.service new file mode 100644 index 0000000..15c1c2c --- /dev/null +++ b/bundles/netbox/files/netbox-web.service @@ -0,0 +1,20 @@ +[Unit] +Description=NetBox WSGI Service +Documentation=https://netbox.readthedocs.io/en/stable/ +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=netbox +Group=netbox +PIDFile=/var/tmp/netbox.pid +WorkingDirectory=/opt/netbox/src +ExecStart=/opt/netbox/venv/bin/gunicorn --pid /var/tmp/netbox.pid --config /opt/netbox/gunicorn_config.py netbox.wsgi + +Restart=on-failure +RestartSec=30 +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/bundles/netbox/files/netbox-worker.service b/bundles/netbox/files/netbox-worker.service new file mode 100644 index 0000000..75767ec --- /dev/null +++ b/bundles/netbox/files/netbox-worker.service @@ -0,0 +1,20 @@ +[Unit] +Description=NetBox Request Queue Worker +Documentation=https://netbox.readthedocs.io/en/stable/ +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple + +User=netbox +Group=netbox +WorkingDirectory=/opt/netbox/src +ExecStart=/opt/netbox/venv/bin/python /opt/netbox/src/netbox/manage.py rqworker + +Restart=on-failure +RestartSec=30 +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/bundles/netbox/items.py b/bundles/netbox/items.py new file mode 100644 index 0000000..d24ca05 --- /dev/null +++ b/bundles/netbox/items.py @@ -0,0 +1,124 @@ +users = { + 'netbox': { + 'home': '/opt/netbox', + }, +} + +directories = { + '/opt/netbox/src': {}, + '/opt/netbox/media': { + 'owner': 'netbox', + }, + '/opt/netbox/scripts': { + 'owner': 'netbox', + }, +} + +git_deploy = { + '/opt/netbox/src': { + 'repo': 'https://github.com/netbox-community/netbox.git', + 'rev': node.metadata.get('netbox/version'), + 'triggers': { + 'action:netbox_install', + 'action:netbox_upgrade', + 'svc_systemd:netbox-web:restart', + 'svc_systemd:netbox-worker:restart', + }, + }, +} + +# This is a recreation of https://github.com/netbox-community/netbox/blob/develop/upgrade.sh +actions = { + 'netbox_create_virtualenv': { + 'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/netbox/venv', + 'unless': 'test -d /opt/netbox/venv/', + 'needed_by': { + 'action:netbox_install', + }, + }, + 'netbox_install': { + 'triggered': True, + 'command': ' && '.join([ + 'cd /opt/netbox/src', + '/opt/netbox/venv/bin/pip install --upgrade pip wheel setuptools django-auth-ldap gunicorn', + '/opt/netbox/venv/bin/pip install --upgrade -r requirements.txt', + ]), + 'needs': { + 'pkg_apt:build-essential', + 'pkg_apt:graphviz', + 'pkg_apt:libffi-dev', + 'pkg_apt:libldap2-dev', + 'pkg_apt:libpq-dev', + 'pkg_apt:libsasl2-dev', + 'pkg_apt:libssl-dev', + 'pkg_apt:libxml2-dev', + 'pkg_apt:libxslt1-dev', + 'pkg_apt:python3-dev', + 'pkg_apt:zlib1g-dev', + } + }, + 'netbox_upgrade': { + 'triggered': True, + 'command': ' && '.join([ + '/opt/netbox/venv/bin/python /opt/netbox/src/netbox/manage.py migrate', + '/opt/netbox/venv/bin/python /opt/netbox/src/netbox/manage.py collectstatic --no-input', + '/opt/netbox/venv/bin/python /opt/netbox/src/netbox/manage.py remove_stale_contenttypes --no-input', + '/opt/netbox/venv/bin/python /opt/netbox/src/netbox/manage.py invalidate all', # clear cached data + ]), + 'needs': { + 'action:netbox_install', + 'file:/opt/netbox/src/netbox/netbox/configuration.py', + }, + }, +} + +files = { + '/etc/systemd/system/netbox-web.service': { + 'triggers': { + 'action:systemd-reload', + 'svc_systemd:netbox-web:restart', + }, + }, + '/etc/systemd/system/netbox-worker.service': { + 'triggers': { + 'action:systemd-reload', + 'svc_systemd:netbox-worker:restart', + }, + }, + '/opt/netbox/src/netbox/netbox/configuration.py': { + 'content_type': 'mako', + 'triggers': { + 'svc_systemd:netbox-web:restart', + 'svc_systemd:netbox-worker:restart', + }, + 'needs': { + 'git_deploy:/opt/netbox/src', + }, + }, + '/opt/netbox/gunicorn_config.py': { + 'content_type': 'mako', + 'triggers': { + 'svc_systemd:netbox-web:restart', + }, + }, +} + +svc_systemd = { + 'netbox-web': { + 'needs': { + 'action:netbox_install', + 'action:netbox_upgrade', + 'file:/etc/systemd/system/netbox-web.service', + 'file:/opt/netbox/gunicorn_config.py', + 'file:/opt/netbox/src/netbox/netbox/configuration.py', + }, + }, + 'netbox-worker': { + 'needs': { + 'action:netbox_install', + 'action:netbox_upgrade', + 'file:/etc/systemd/system/netbox-worker.service', + 'file:/opt/netbox/src/netbox/netbox/configuration.py', + }, + }, +} diff --git a/bundles/netbox/metadata.py b/bundles/netbox/metadata.py new file mode 100644 index 0000000..098f8b4 --- /dev/null +++ b/bundles/netbox/metadata.py @@ -0,0 +1,106 @@ +defaults = { + 'apt': { + 'packages': { + 'build-essential': {}, + 'graphviz': {}, + 'libffi-dev': {}, + 'libldap2-dev': {}, + 'libpq-dev': {}, + 'libsasl2-dev': {}, + 'libssl-dev': {}, + 'libxml2-dev': {}, + 'libxslt1-dev': {}, + 'python3-dev': {}, + 'zlib1g-dev': {}, + }, + }, + 'backups': { + 'paths': { + '/opt/netbox/media', + '/opt/netbox/scripts', + # and database + }, + }, + 'postgresql': { + 'databases': { + 'netbox': { + 'owner': 'netbox', + }, + }, + 'roles': { + 'netbox': { + 'password': repo.vault.password_for('netbox postgresql ' + node.name), + }, + }, + }, + 'zfs': { + 'datasets': { + 'tank/netbox': {}, + 'tank/netbox/install': { + 'mountpoint': '/opt/netbox/src', + 'needed_by': { + 'directory:/opt/netbox/src', + }, + }, + 'tank/netbox/media': { + 'mountpoint': '/opt/netbox/media', + 'needed_by': { + 'directory:/opt/netbox/media', + }, + }, + 'tank/netbox/scripts': { + 'mountpoint': '/opt/netbox/scripts', + 'needed_by': { + 'directory:/opt/netbox/scripts', + }, + }, + }, + }, +} + + +@metadata_reactor.provides( + 'icinga2_api/netbox/services/NETBOX UPDATE', +) +def icinga_check_for_new_release(metadata): + return { + 'icinga2_api': { + 'netbox': { + 'services': { + 'NETBOX UPDATE': { + 'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_github_for_new_release netbox-community/netbox {}'.format(metadata.get('netbox/version')), + 'check_interval': '60m', + }, + }, + }, + }, + } + + +@metadata_reactor.provides( + 'nginx/vhosts/netbox', +) +def nginx(metadata): + if not node.has_bundle('nginx'): + raise DoNotRunAgain + + return { + 'nginx': { + 'vhosts': { + 'netbox': { + 'domain': metadata.get('netbox/domain'), + 'website_check_path': '/', + 'website_check_string': 'NetBox', + 'locations': { + '/': { + 'target': 'http://127.0.0.1:22080', + }, + '/static/': { + 'alias': '/opt/netbox/src/netbox/static/', + }, + }, + }, + }, + }, + } +