Compare commits

..

No commits in common. "release" and "2.0.0" have entirely different histories.

138 changed files with 4045 additions and 1464 deletions

View file

@ -1,13 +1,10 @@
.git* .git
.github
.travis.yml
*.md *.md
build*
docker-compose*
env env
test-configuration build*
docker-compose.override.yml
.netbox/.git* .netbox/.git*
.netbox/.pre-commit-config.yaml .netbox/.travis.yml
.netbox/.readthedocs.yaml
.netbox/.tx
.netbox/contrib
.netbox/scripts .netbox/scripts
.netbox/upgrade.sh

View file

@ -2,12 +2,17 @@
"Verbose": false, "Verbose": false,
"Debug": false, "Debug": false,
"IgnoreDefaults": false, "IgnoreDefaults": false,
"SpacesAfterTabs": false, "SpacesAftertabs": false,
"NoColor": false, "NoColor": false,
"Exclude": ["LICENSE", "\\.initializers", "\\.vscode"], "Exclude": [
"LICENSE",
"\\.initializers",
"\\.vscode"
],
"AllowedContentTypes": [], "AllowedContentTypes": [],
"PassedFiles": [], "PassedFiles": [],
"Disable": { "Disable": {
// set these options to true to disable specific checks
"EndOfLine": false, "EndOfLine": false,
"Indentation": false, "Indentation": false,
"InsertFinalNewline": false, "InsertFinalNewline": false,

View file

@ -9,6 +9,3 @@ indent_size = 2
[*.py] [*.py]
indent_size = 4 indent_size = 4
[VERSION]
insert_final_newline = false

4
.github/FUNDING.yml vendored
View file

@ -1,8 +1,8 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
- cimnine - cimnine
- tobiasge - tobiasge
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username

View file

@ -1,147 +1,148 @@
name: Bug report name: Bug report
description: Create a report about a malfunction of the Docker setup description: Create a report about a malfunction of the Docker setup
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Please only raise an issue if you're certain that you've found a bug. Please only raise an issue if you're certain that you've found a bug.
Else, see these other means to get help: Else, see these other means to get help:
- See our troubleshooting section: - See our troubleshooting section:
https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting
- Have a look at the rest of the wiki: - Have a look at the rest of the wiki:
https://github.com/netbox-community/netbox-docker/wiki https://github.com/netbox-community/netbox-docker/wiki
- Check the release notes: - Check the release notes:
https://github.com/netbox-community/netbox-docker/releases https://github.com/netbox-community/netbox-docker/releases
- Look through the issues already resolved: - Look through the issues already resolved:
https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed
If you did not find what you're looking for, If you did not find what you're looking for,
try the help of our community: try the help of our community:
- Post to Github Discussions: - Post to Github Discussions:
https://github.com/netbox-community/netbox-docker/discussions https://github.com/netbox-community/netbox-docker/discussions
- Join the `#netbox-docker` channel on our Slack: - Join the `#netbox-docker` channel on our Slack:
https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ
- Ask on the NetBox mailing list: - Ask on the NetBox mailing list:
https://groups.google.com/d/forum/netbox-discuss https://groups.google.com/d/forum/netbox-discuss
Please don't open an issue to open a PR. Please don't open an issue to open a PR.
Just submit the PR, that's good enough. Just submit the PR, that's good enough.
- type: textarea - type: textarea
id: current-behavior id: current-behavior
attributes: attributes:
label: Current Behavior label: Current Behavior
description: Please describe what you did and how you think it misbehaved description: Please describe what you did and how you think it misbehaved
placeholder: I tried to … by doing …, but it … placeholder: I tried to … by doing …, but it …
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: expected-behavior id: expected-behavior
attributes: attributes:
label: Expected Behavior label: Expected Behavior
description: Please describe what you expected instead description: Please describe what you expected instead
placeholder: I expected that … when I do … placeholder: I expected that … when I do …
validations: validations:
required: true required: true
- type: input - type: input
id: docker-compose-version id: docker-compose-version
attributes: attributes:
label: Docker Compose Version label: Docker Compose Version
description: Please paste the output of `docker-compose version` (or `docker compose version`) description: Please paste the output of `docker-compose version`
placeholder: Docker Compose version vX.Y.Z placeholder: Docker Compose version vX.Y.Z
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: docker-version id: docker-version
attributes: attributes:
label: Docker Version label: Docker Version
description: Please paste the output of `docker version` description: Please paste the output of `docker version`
render: text render: text
placeholder: | placeholder: |
Client: Client:
Cloud integration: 1.0.17 Cloud integration: 1.0.17
Version: 20.10.8 Version: 20.10.8
API version: 1.41 API version: 1.41
Go version: go1.16.6 Go version: go1.16.6
Git commit: 3967b7d Git commit: 3967b7d
Built: Fri Jul 30 19:55:20 2021 Built: Fri Jul 30 19:55:20 2021
OS/Arch: darwin/amd64 OS/Arch: darwin/amd64
Context: default Context: default
Experimental: true Experimental: true
Server: Docker Engine - Community Server: Docker Engine - Community
Engine: Engine:
Version: 20.10.8 Version: 20.10.8
API version: 1.41 (minimum version 1.12) API version: 1.41 (minimum version 1.12)
Go version: go1.16.6 Go version: go1.16.6
Git commit: 75249d8 Git commit: 75249d8
Built: Fri Jul 30 19:52:10 2021 Built: Fri Jul 30 19:52:10 2021
OS/Arch: linux/amd64 OS/Arch: linux/amd64
Experimental: false Experimental: false
containerd: containerd:
Version: 1.4.9 Version: 1.4.9
GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 GitCommit: e25210fe30a0a703442421b0f60afac609f950a3
runc: runc:
Version: 1.0.1 Version: 1.0.1
GitCommit: v1.0.1-0-g4144b63 GitCommit: v1.0.1-0-g4144b63
docker-init: docker-init:
Version: 0.19.0 Version: 0.19.0
GitCommit: de40ad0 GitCommit: de40ad0
validations: validations:
required: true required: true
- type: input - type: input
id: git-rev id: git-rev
attributes: attributes:
label: The git Revision label: The git Revision
description: Please paste the output of `git rev-parse HEAD` description: Please paste the output of `git rev-parse HEAD`
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: git-status id: git-status
attributes: attributes:
label: The git Status label: The git Status
description: Please paste the output of `git status` description: Please paste the output of `git status`
render: text render: text
placeholder: | placeholder: |
On branch main On branch main
nothing to commit, working tree clean nothing to commit, working tree clean
validations: validations:
required: true required: true
- type: input - type: input
id: run-command id: run-command
attributes: attributes:
label: Startup Command label: Startup Command
description: Please specify the command you used to start the project description: Please specify the command you used to start the project
placeholder: docker compose up placeholder: docker compose up
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: netbox-logs id: netbox-logs
attributes: attributes:
label: NetBox Logs label: NetBox Logs
description: Please paste the output of `docker-compose logs netbox` (or `docker compose logs netbox`) description: Please paste the output of `docker-compose logs netbox` (or `docker compose logs netbox`)
render: text render: text
placeholder: | placeholder: |
netbox_1 | ⚙️ Applying database migrations netbox_1 | ⚙️ Applying database migrations
netbox_1 | 🧬 loaded config '/etc/netbox/config/configuration.py' netbox_1 | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/a.py' netbox_1 | 🧬 loaded config '/etc/netbox/config/a.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/extra.py' netbox_1 | 🧬 loaded config '/etc/netbox/config/extra.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/logging.py' netbox_1 | 🧬 loaded config '/etc/netbox/config/logging.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/plugins.py' netbox_1 | 🧬 loaded config '/etc/netbox/config/plugins.py'
... ...
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: docker-compose-override-yml id: docker-compose-override-yml
attributes: attributes:
label: Content of docker-compose.override.yml label: Content of docker-compose.override.yml
description: Please paste the output of `cat docker-compose.override.yml` description: Please paste the output of `cat docker-compose.override.yml`
render: yaml render: yaml
placeholder: | placeholder: |
services: version: '3.4'
netbox: services:
ports: netbox:
- '8080:8080' ports:
validations: - '8080:8080'
required: true validations:
required: true

View file

@ -6,7 +6,7 @@ contact_links:
- name: Chat - name: Chat
url: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ url: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ
about: "Usually the quickest way to seek help with small issues is to join our #netbox-docker Slack channel." about: 'Usually the quickest way to seek help with small issues is to join our #netbox-docker Slack channel.'
- name: Community Wiki - name: Community Wiki
url: https://github.com/netbox-community/netbox-docker/wiki url: https://github.com/netbox-community/netbox-docker/wiki

View file

@ -1,68 +1,68 @@
name: Feature or Change Request name: Feature or Change Request
description: Request a new feature or a change of the current behavior description: Request a new feature or a change of the current behavior
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
This issue type is to propose new features for the Docker setup. This issue type is to propose new features for the Docker setup.
To just spin an idea, see the Github Discussions section, please. To just spin an idea, see the Github Discussions section, please.
Before asking for help, see these links first: Before asking for help, see these links first:
- See our troubleshooting section: - See our troubleshooting section:
https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting
- Have a look at the rest of the wiki: - Have a look at the rest of the wiki:
https://github.com/netbox-community/netbox-docker/wiki https://github.com/netbox-community/netbox-docker/wiki
- Check the release notes: - Check the release notes:
https://github.com/netbox-community/netbox-docker/releases https://github.com/netbox-community/netbox-docker/releases
- Look through the issues already resolved: - Look through the issues already resolved:
https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed
If you did not find what you're looking for, If you did not find what you're looking for,
try the help of our community: try the help of our community:
- Post to Github Discussions: - Post to Github Discussions:
https://github.com/netbox-community/netbox-docker/discussions https://github.com/netbox-community/netbox-docker/discussions
- Join the `#netbox-docker` channel on our Slack: - Join the `#netbox-docker` channel on our Slack:
https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ
- Ask on the NetBox mailing list: - Ask on the NetBox mailing list:
https://groups.google.com/d/forum/netbox-discuss https://groups.google.com/d/forum/netbox-discuss
Please don't open an issue to open a PR. Please don't open an issue to open a PR.
Just submit the PR, that's good enough. Just submit the PR, that's good enough.
- type: textarea - type: textarea
id: desired-behavior id: desired-behavior
attributes: attributes:
label: Desired Behavior label: Desired Behavior
description: Please describe the desired behavior description: Please describe the desired behavior
placeholder: To me, it would be useful, if … because … placeholder: To me, it would be useful, if … because …
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: contrast-to-current id: contrast-to-current
attributes: attributes:
label: Contrast to Current Behavior label: Contrast to Current Behavior
description: Please describe how the desired behavior is different from the current behavior description: Please describe how the desired behavior is different from the current behavior
placeholder: The current behavior is …, but this lacks … placeholder: The current behavior is …, but this lacks …
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: required-changes id: required-changes
attributes: attributes:
label: Required Changes label: Required Changes
description: If you can, please elaborate what changes will be required to implement the desired behavior description: If you can, please elaborate what changes will be required to implement the desired behavior
placeholder: I suggest to change the file … placeholder: I suggest to change the file …
validations: validations:
required: false required: false
- type: textarea - type: textarea
id: discussion id: discussion
attributes: attributes:
label: "Discussion: Benefits and Drawbacks" label: 'Discussion: Benefits and Drawbacks'
description: | description: |
Please make your case here: Please make your case here:
- Why do you think this project and the community will benefit from your suggestion? - Why do you think this project and the community will benefit from your suggestion?
- What are the drawbacks of this change? Is it backwards-compatible? - What are the drawbacks of this change? Is it backwards-compatible?
- Anything else that you think is relevant to the discussion of this feature/change request. - Anything else that you think is relevant to the discussion of this feature/change request.
placeholder: I suggest to change the file … placeholder: I suggest to change the file …
validations: validations:
required: false required: false

View file

@ -80,6 +80,6 @@ into the release notes.
Please put an x into the brackets (like `[x]`) if you've completed that task. Please put an x into the brackets (like `[x]`) if you've completed that task.
--> -->
- [ ] I have read the comments and followed the PR template. * [ ] I have read the comments and followed the PR template.
- [ ] I have explained my PR according to the information in the comments. * [ ] I have explained my PR according to the information in the comments.
- [ ] My PR targets the `develop` branch. * [ ] My PR targets the `develop` branch.

View file

@ -1,103 +1,67 @@
---
name: push name: push
on: on:
push: push:
branches-ignore: branches-ignore:
- release - release
- renovate/**
pull_request: pull_request:
branches-ignore: branches-ignore:
- release - release
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
lint: lint:
runs-on: ubuntu-24.04 runs-on: ubuntu-latest
name: Checks syntax of our code name: Checks syntax of our code
permissions:
contents: read
packages: read
statuses: write
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
with: with:
# Full git history is needed to get a proper # Full git history is needed to get a proper list of changed files within `super-linter`
# list of changed files within `super-linter` fetch-depth: 0
fetch-depth: 0 - uses: actions/setup-python@v4
- name: Lint Code Base with:
uses: super-linter/super-linter@v8 python-version: '3.9'
env: - name: Lint Code Base
DEFAULT_BRANCH: develop uses: github/super-linter@v4
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} env:
SUPPRESS_POSSUM: true DEFAULT_BRANCH: develop
LINTER_RULES_PATH: / GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_ALL_CODEBASE: false SUPPRESS_POSSUM: true
VALIDATE_BIOME_FORMAT: false LINTER_RULES_PATH: /
VALIDATE_CHECKOV: false VALIDATE_ALL_CODEBASE: false
VALIDATE_DOCKERFILE: false VALIDATE_DOCKERFILE: false
VALIDATE_GITHUB_ACTIONS_ZIZMOR: false FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*)
VALIDATE_GITLEAKS: false
VALIDATE_JSCPD: false EDITORCONFIG_FILE_NAME: .ecrc
VALIDATE_TRIVY: false DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml
FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*) MARKDOWN_CONFIG_FILE: .markdown-lint.yml
EDITORCONFIG_FILE_NAME: .editorconfig-checker.json PYTHON_BLACK_CONFIG_FILE: pyproject.toml
DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml PYTHON_FLAKE8_CONFIG_FILE: .flake8
MARKDOWN_CONFIG_FILE: .markdown-lint.yml PYTHON_ISORT_CONFIG_FILE: pyproject.toml
PYTHON_BLACK_CONFIG_FILE: pyproject.toml
PYTHON_FLAKE8_CONFIG_FILE: .flake8
PYTHON_ISORT_CONFIG_FILE: pyproject.toml
YAML_CONFIG_FILE: .yamllint.yaml
build: build:
continue-on-error: ${{ matrix.build_cmd != './build-latest.sh' }} continue-on-error: ${{ matrix.docker_from == 'alpine:edge' }}
strategy: strategy:
matrix: matrix:
build_cmd: build_cmd:
- ./build-latest.sh - ./build-latest.sh
- PRERELEASE=true ./build-latest.sh - PRERELEASE=true ./build-latest.sh
- ./build.sh feature - ./build.sh feature
- ./build.sh main - ./build.sh develop
os: docker_from:
- ubuntu-24.04 - '' # use the default of the build script
- ubuntu-24.04-arm
fail-fast: false fail-fast: false
env: runs-on: ubuntu-latest
GH_ACTION: enable
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IMAGE_NAMES: docker.io/netboxcommunity/netbox
runs-on: ${{ matrix.os }}
name: Builds new NetBox Docker Images name: Builds new NetBox Docker Images
steps: steps:
- id: git-checkout - id: git-checkout
name: Checkout name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v3
- id: buildx-setup - id: docker-build
name: Set up Docker Buildx name: Build the image from '${{ matrix.docker_from }}' with '${{ matrix.build_cmd }}'
uses: docker/setup-buildx-action@v3 run: ${{ matrix.build_cmd }}
- id: arm-install-skopeo env:
name: Install 'skopeo' on ARM64 DOCKER_FROM: ${{ matrix.docker_from }}
if: matrix.os == 'ubuntu-24.04-arm' GH_ACTION: enable
run: | - id: docker-test
sudo apt-get install -y skopeo name: Test the image
- id: arm-buildx-platform run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
name: Set BUILDX_PLATFORM to ARM64 if: steps.docker-build.outputs.skipped != 'true'
if: matrix.os == 'ubuntu-24.04-arm'
run: |
echo "BUILDX_PLATFORM=linux/arm64" >>"${GITHUB_ENV}"
- id: docker-build
name: Build the image for '${{ matrix.os }}' with '${{ matrix.build_cmd }}'
run: ${{ matrix.build_cmd }}
env:
BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }}
- id: arm-time-limit
name: Set Netbox container start_period higher on ARM64
if: matrix.os == 'ubuntu-24.04-arm'
run: |
echo "NETBOX_START_PERIOD=240s" >>"${GITHUB_ENV}"
- id: docker-test
name: Test the image
run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
if: steps.docker-build.outputs.skipped != 'true'

View file

@ -1,4 +1,3 @@
---
name: release name: release
on: on:
@ -6,83 +5,83 @@ on:
types: types:
- published - published
schedule: schedule:
- cron: "45 5 * * *" - cron: '45 5 * * *'
workflow_dispatch:
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
build: build_cmd:
- { "cmd": "./build-latest.sh", "branch": "release" } - ./build-latest.sh
- { "cmd": "./build.sh main", "branch": "release" } - PRERELEASE=true ./build-latest.sh
# Build pre release images from our develop branch - ./build.sh feature
# This is used to test the latest changes before they are merged into the main branch - ./build.sh develop
- { "cmd": "PRERELEASE=true ./build-latest.sh", "branch": "develop" }
- { "cmd": "./build.sh feature", "branch": "develop" }
platform:
- linux/amd64,linux/arm64
fail-fast: false fail-fast: false
runs-on: ubuntu-24.04 runs-on: ubuntu-latest
name: Builds new NetBox Docker Images name: Builds new NetBox Docker Images
env: env:
GH_ACTION: enable GH_ACTION: enable
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IMAGE_NAMES: docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox ghcr.io/netbox-community/netbox
steps: steps:
- id: source-checkout -
name: Checkout name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v3
with: -
ref: ${{ matrix.build.branch }} name: Get Version of NetBox Docker
- id: set-netbox-docker-version run: |
name: Get Version of NetBox Docker echo "::set-output name=version::$(cat VERSION)"
run: echo "version=$(cat VERSION)" >>"$GITHUB_OUTPUT" shell: bash
shell: bash -
- id: check-build-needed id: docker-build
name: Check if the build is needed for '${{ matrix.build.cmd }}' name: Build the image with '${{ matrix.build_cmd }}'
env: run: ${{ matrix.build_cmd }}
CHECK_ONLY: "true" -
run: ${{ matrix.build.cmd }} name: Test the image
# docker.io run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
- id: docker-io-login if: steps.docker-build.outputs.skipped != 'true'
name: Login to docker.io
uses: docker/login-action@v3 # docker.io
with: -
registry: docker.io name: Login to docker.io
username: ${{ secrets.dockerhub_username }} uses: docker/login-action@v2
password: ${{ secrets.dockerhub_password }} with:
if: steps.check-build-needed.outputs.skipped != 'true' registry: docker.io
- id: buildx-setup username: ${{ secrets.dockerhub_username }}
name: Set up Docker Buildx password: ${{ secrets.dockerhub_password }}
uses: docker/setup-buildx-action@v3 if: steps.docker-build.outputs.skipped != 'true'
with: -
version: "lab:latest" name: Push the image to docker.io
driver: cloud run: ${{ matrix.build_cmd }} --push-only
endpoint: "netboxcommunity/netbox-default" if: steps.docker-build.outputs.skipped != 'true'
if: steps.check-build-needed.outputs.skipped != 'true'
# quay.io # quay.io
- id: quay-io-login -
name: Login to Quay.io name: Login to Quay.io
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: quay.io registry: quay.io
username: ${{ secrets.quayio_username }} username: ${{ secrets.quayio_username }}
password: ${{ secrets.quayio_password }} password: ${{ secrets.quayio_password }}
if: steps.check-build-needed.outputs.skipped != 'true' if: steps.docker-build.outputs.skipped != 'true'
# ghcr.io -
- id: ghcr-io-login name: Build and push the image with '${{ matrix.build_cmd }}'
name: Login to GitHub Container Registry run: ${{ matrix.build_cmd }} --push
uses: docker/login-action@v3 env:
with: DOCKER_REGISTRY: quay.io
registry: ghcr.io if: steps.docker-build.outputs.skipped != 'true'
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} # ghcr.io
if: steps.check-build-needed.outputs.skipped != 'true' -
- id: build-and-push name: Login to GitHub Container Registry
name: Push the image uses: docker/login-action@v2
run: ${{ matrix.build.cmd }} --push with:
if: steps.check-build-needed.outputs.skipped != 'true' registry: ghcr.io
env: username: ${{ github.repository_owner }}
BUILDX_PLATFORM: ${{ matrix.platform }} password: ${{ secrets.GITHUB_TOKEN }}
BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }} if: steps.docker-build.outputs.skipped != 'true'
-
name: Build and push the image with '${{ matrix.build_cmd }}'
run: ${{ matrix.build_cmd }} --push
env:
DOCKER_REGISTRY: ghcr.io
DOCKER_ORG: netbox-community
if: steps.docker-build.outputs.skipped != 'true'

3
.gitignore vendored
View file

@ -1,6 +1,6 @@
*.sql.gz *.sql.gz
.netbox .netbox
.python-version .initializers
docker-compose.override.yml docker-compose.override.yml
*.pem *.pem
configuration/* configuration/*
@ -11,4 +11,5 @@ configuration/ldap/*
!configuration/ldap/ldap_config.py !configuration/ldap/ldap_config.py
!configuration/logging.py !configuration/logging.py
!configuration/plugins.py !configuration/plugins.py
prometheus.yml
super-linter.log super-linter.log

View file

@ -1,4 +0,0 @@
---
rules:
line-length:
max: 160

View file

@ -1,7 +1,6 @@
ARG FROM ARG FROM
FROM ${FROM} AS builder FROM ${FROM} as builder
COPY --from=ghcr.io/astral-sh/uv:0.9 /uv /usr/local/bin/
RUN export DEBIAN_FRONTEND=noninteractive \ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update -qq \ && apt-get update -qq \
&& apt-get upgrade \ && apt-get upgrade \
@ -14,28 +13,18 @@ RUN export DEBIAN_FRONTEND=noninteractive \
libpq-dev \ libpq-dev \
libsasl2-dev \ libsasl2-dev \
libssl-dev \ libssl-dev \
libxml2-dev \
libxmlsec1 \
libxmlsec1-dev \
libxmlsec1-openssl \
libxslt-dev \
pkg-config \
python3-dev \ python3-dev \
&& /usr/local/bin/uv venv /opt/netbox/venv python3-pip \
python3-venv \
&& python3 -m venv /opt/netbox/venv \
&& /opt/netbox/venv/bin/python3 -m pip install --upgrade \
pip \
setuptools \
wheel
ARG NETBOX_PATH ARG NETBOX_PATH
COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt / COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt /
ENV VIRTUAL_ENV=/opt/netbox/venv RUN /opt/netbox/venv/bin/pip install \
RUN \
# Gunicorn is not needed because we use Nginx Unit
sed -i -e '/gunicorn/d' /requirements.txt && \
# We need 'social-auth-core[all]' in the Docker image. But if we put it in our own requirements-container.txt
# we have potential version conflicts and the build will fail.
# That's why we just replace it in the original requirements.txt.
sed -i -e 's/social-auth-core/social-auth-core\[all\]/g' /requirements.txt && \
# The same is true for 'django-storages'
sed -i -e 's/django-storages/django-storages\[azure,boto3,dropbox,google,libcloud,sftp\]/g' /requirements.txt && \
/usr/local/bin/uv pip install \
-r /requirements.txt \ -r /requirements.txt \
-r /requirements-container.txt -r /requirements-container.txt
@ -44,33 +33,32 @@ RUN \
### ###
ARG FROM ARG FROM
FROM ${FROM} AS main FROM ${FROM} as main
COPY docker/unit.list /etc/apt/sources.list.d/unit.list
ADD --chmod=444 --chown=0:0 https://unit.nginx.org/keys/nginx-keyring.gpg /usr/share/keyrings/nginx-keyring.gpg
RUN export DEBIAN_FRONTEND=noninteractive \ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update -qq \ && apt-get update -qq \
&& apt-get upgrade \ && apt-get upgrade \
--yes -qq --no-install-recommends \ --yes -qq --no-install-recommends \
&& apt-get install \ && apt-get install \
--yes -qq --no-install-recommends \ --yes -qq --no-install-recommends \
bzip2 \
ca-certificates \ ca-certificates \
curl \ curl \
libldap-common \
libpq5 \ libpq5 \
libxmlsec1-openssl \
openssh-client \
openssl \ openssl \
python3 \ python3 \
python3-distutils \
tini \ tini \
unit-python3.12=1.34.2-1~noble \ && curl -sL https://nginx.org/keys/nginx_signing.key \
unit=1.34.2-1~noble \ > /etc/apt/trusted.gpg.d/nginx.asc && \
echo "deb https://packages.nginx.org/unit/debian/ bullseye unit" \
> /etc/apt/sources.list.d/unit.list \
&& apt-get update -qq \
&& apt-get install \
--yes -qq --no-install-recommends \
unit=1.27.0-1~bullseye \
unit-python3.9=1.27.0-1~bullseye \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Copy the modified 'requirements*.txt' files, to have the files actually used during installation
COPY --from=builder /requirements.txt /requirements-container.txt /opt/netbox/
COPY --from=builder /usr/local/bin/uv /usr/local/bin/
COPY --from=builder /opt/netbox/venv /opt/netbox/venv COPY --from=builder /opt/netbox/venv /opt/netbox/venv
ARG NETBOX_PATH ARG NETBOX_PATH
@ -79,25 +67,25 @@ COPY ${NETBOX_PATH} /opt/netbox
COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py
COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py
COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh
COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh
COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh
COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY initializers/ /opt/netbox/initializers/
COPY configuration/ /etc/netbox/config/ COPY configuration/ /etc/netbox/config/
COPY docker/nginx-unit.json /etc/unit/ COPY docker/nginx-unit.json /etc/unit/
COPY VERSION /opt/netbox/VERSION
WORKDIR /opt/netbox/netbox WORKDIR /opt/netbox/netbox
# Must set permissions for '/opt/netbox/netbox/media' directory # Must set permissions for '/opt/netbox/netbox/media' directory
# to g+w so that pictures can be uploaded to netbox. # to g+w so that pictures can be uploaded to netbox.
RUN mkdir -p static media /opt/unit/state/ /opt/unit/tmp/ \ RUN mkdir -p static /opt/unit/state/ /opt/unit/tmp/ \
&& chown -R unit:root /opt/unit/ media reports scripts \ && chown -R unit:root media /opt/unit/ \
&& chmod -R g+w /opt/unit/ media reports scripts \ && chmod -R g+w media /opt/unit/ \
&& cd /opt/netbox/ && SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python -m mkdocs build \ && cd /opt/netbox/ && SECRET_KEY="dummy" /opt/netbox/venv/bin/python -m mkdocs build \
--config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \ --config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \
&& DEBUG="true" SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input \ && SECRET_KEY="dummy" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input
&& mkdir /opt/netbox/netbox/local \
&& echo "build: Docker-$(cat /opt/netbox/VERSION)" > /opt/netbox/netbox/local/release.yaml
ENV LANG=C.utf8 PATH=/opt/netbox/venv/bin:$PATH VIRTUAL_ENV=/opt/netbox/venv UV_NO_CACHE=1 ENV LANG=C.UTF-8
ENTRYPOINT [ "/usr/bin/tini", "--" ] ENTRYPOINT [ "/usr/bin/tini", "--" ]
CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ] CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ]

View file

@ -1,19 +0,0 @@
# Maintainers of _NetBox Docker_
This file lists all currently recognized maintainers of the _NetBox Docker_ project in alphabetical order:
- @cimnine
- @tobiasge
## Stepping Down
Every maintainer is a volunteer and may step down as maintainer at any time without providing any reason.
To make this explicit, the maintainer is asked to update this file.
The last maintainer stepping down is asked to archive the project on GitHub to indicate that the project is no longer maintained.
## Signing up
Everyone is welcome to sign up as maintainer by creating a PR and add their own username to the list.
The current maintainers shall discuss the application.
They may turn down an application if they don't feel confident that the new maintainer is a positive addition.

View file

@ -1,71 +0,0 @@
# Development, Maintenance and Community Principals for _NetBox Docker_
These principals shall guide the development and the maintenance of _NetBox Docker_.
## Basic principals
This project is maintained on voluntary basis.
Everyone is asked to respect that.
This means, that …
- … sometimes features are not implemented as fast as one might like -- or not at all.
- … sometimes nobody is looking at bugs, or they are not fixed as fast as one might like -- or not at all.
- … sometimes PRs are not reviewed for an extended period.
Everyone is welcome to provide improvements and bugfixes to the benefit of everyone else.
## Development Principals
The goal of the _NetBox Docker_ project is to provide a container to run the basic NetBox project.
The container should feel like a native container -- as if it were provided by NetBox itself:
- Configuration via environment variables where feasible.
- Except: Whenever a complex type such as a `dict` is required as value of a configuration setting,
then it shall not be provided through an environment variable.
- Configuration of secrets via secret files.
- Log output to standard out (STDOUT/`&1`) / standard error (STDERR/`&2`).
- Volumes for data and cache directories.
- Otherwise, no mounts shall be necessary.
- Runs a non-root user by default.
- One process / role for each instance.
The container generally does not provide more features than the basic NetBox project itself provides.
It may provide additional Python dependencies than the upstream project,
so that all configurable features of NetBox can be used in the container without further modification.
The container may provide helpers, so that it feels and behaves like a native container.
The container does not bundle any community plugins.
## Maintenance Principals
The main goals of maintaining _NetBox Docker_ are:
- Keeping the project at a high quality level.
- Keeping the maintenance effort minimal.
- Coordinating development efforts.
The following guidelines help us to achieve these goals:
- As many maintenance tasks as possible shall be automated or scripted.
- All manual tasks must be documented.
- All changes are reviewed by at least one maintainer.
- Changes of maintainers are reviewed by at least one other maintainer.
(Except if there's only one maintainer left.)
- The infrastructure beyond what GitHub provides shall be kept to a minimum.
- On request, every maintainer shall get access to infrastructure that is beyond GitHub
(at the time of writing that's _Docker Hub_ and _Quay_ in particular).
## Community Principals
This project is developed by the NetBox community for the NetBox community.
We welcome contributions, as long as they are in line with the principals above.
The maintainers of NetBox Docker are not the support team.
The community is expected to help each other out.
Always remember:
Behind every screen (or screen-reader) on the other end is a fellow human.
Be nice and respectful, thankful for help,
and value ideas and contributions,
even when they don't fit the goals.

View file

@ -3,24 +3,20 @@
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/netbox-community/netbox-docker)][github-release] [![GitHub release (latest by date)](https://img.shields.io/github/v/release/netbox-community/netbox-docker)][github-release]
[![GitHub stars](https://img.shields.io/github/stars/netbox-community/netbox-docker)][github-stargazers] [![GitHub stars](https://img.shields.io/github/stars/netbox-community/netbox-docker)][github-stargazers]
![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/netbox-community/netbox-docker) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/netbox-community/netbox-docker)
![Github release workflow](https://img.shields.io/github/actions/workflow/status/netbox-community/netbox-docker/release.yml?branch=release) ![Github release workflow](https://img.shields.io/github/workflow/status/netbox-community/netbox-docker/release)
![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox) ![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox)
[![GitHub license](https://img.shields.io/github/license/netbox-community/netbox-docker)][netbox-docker-license] [![GitHub license](https://img.shields.io/github/license/netbox-community/netbox-docker)][netbox-docker-license]
[The GitHub repository][netbox-docker-github] houses the components needed to build NetBox as a container. [The GitHub repository](netbox-docker-github) houses the components needed to build NetBox as a container.
Images are built regularly using the code in that repository Images are built regularly using the code in that repository and are pushed to [Docker Hub][netbox-dockerhub], [Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr].
and are pushed to [Docker Hub][netbox-dockerhub],
[Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr].
_NetBox Docker_ is a project developed and maintained by the _NetBox_ community.
Do you have any questions? Do you have any questions?
Before opening an issue on GitHub, Before opening an issue on Github,
please join [our Slack][netbox-docker-slack] please join [our Slack][netbox-docker-slack] and ask for help in the [`#netbox-docker`][netbox-docker-slack-channel] channel.
and ask for help in the [`#netbox-docker`][netbox-docker-slack-channel] channel,
or start a new [GitHub Discussion][github-discussions].
[github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers [github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers
[github-release]: https://github.com/netbox-community/netbox-docker/releases [github-release]: https://github.com/netbox-community/netbox-docker/releases
[netbox-docker-microbadger]: https://microbadger.com/images/netboxcommunity/netbox
[netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/ [netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/
[netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox [netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox
[netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox [netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox
@ -29,7 +25,6 @@ or start a new [GitHub Discussion][github-discussions].
[netbox-docker-slack-channel]: https://netdev-community.slack.com/archives/C01P0GEVBU7 [netbox-docker-slack-channel]: https://netdev-community.slack.com/archives/C01P0GEVBU7
[netbox-slack-channel]: https://netdev-community.slack.com/archives/C01P0FRSXRV [netbox-slack-channel]: https://netdev-community.slack.com/archives/C01P0FRSXRV
[netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE [netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE
[github-discussions]: https://github.com/netbox-community/netbox-docker/discussions
## Quickstart ## Quickstart
@ -39,27 +34,29 @@ There is a more complete [_Getting Started_ guide on our wiki][wiki-getting-star
```bash ```bash
git clone -b release https://github.com/netbox-community/netbox-docker.git git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker cd netbox-docker
# Copy the example override file tee docker-compose.override.yml <<EOF
cp docker-compose.override.yml.example docker-compose.override.yml version: '3.4'
# Read and edit the file to your liking services:
docker compose pull netbox:
docker compose up ports:
- 8000:8080
EOF
docker-compose pull
docker-compose up
``` ```
The whole application will be available after a few minutes. The whole application will be available after a few minutes.
Open the URL `http://0.0.0.0:8000/` in a web-browser. Open the URL `http://0.0.0.0:8000/` in a web-browser.
You should see the NetBox homepage. You should see the NetBox homepage.
In the top-right corner you can login.
The default credentials are:
To create the first admin user run this command: * Username: **admin**
* Password: **admin**
```bash * API Token: **0123456789abcdef0123456789abcdef01234567**
docker compose exec netbox /opt/netbox/netbox/manage.py createsuperuser
```
If you need to restart Netbox from an empty database often,
you can also set the `SUPERUSER_*` variables in your `docker-compose.override.yml`.
[wiki-getting-started]: https://github.com/netbox-community/netbox-docker/wiki/Getting-Started [wiki-getting-started]: https://github.com/netbox-community/netbox-docker/wiki/Getting-Started
[docker-reception]: https://github.com/nxt-engineering/reception
## Container Image Tags ## Container Image Tags
@ -67,39 +64,50 @@ New container images are built and published automatically every ~24h.
> We recommend to use either the `vX.Y.Z-a.b.c` tags or the `vX.Y-a.b.c` tags in production! > We recommend to use either the `vX.Y.Z-a.b.c` tags or the `vX.Y-a.b.c` tags in production!
- `vX.Y.Z-a.b.c`, `vX.Y-a.b.c`: * `vX.Y.Z-a.b.c`, `vX.Y-a.b.c`:
These are release builds containing _NetBox version_ `vX.Y.Z`. These are release builds containing _NetBox version_ `vX.Y.Z`.
They contain the support files of _NetBox Docker version_ `a.b.c`. They contain the support files of _NetBox Docker version_ `a.b.c`.
You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility.
These images are automatically built from [the corresponding releases of NetBox][netbox-releases]. These images are automatically built from [the corresponding releases of NetBox][netbox-releases].
- `latest-a.b.c`: * `latest-a.b.c`:
These are release builds, containing the latest stable version of NetBox. These are release builds, containing the latest stable version of NetBox.
They contain the support files of _NetBox Docker version_ `a.b.c`. They contain the support files of _NetBox Docker version_ `a.b.c`.
You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility.
- `snapshot-a.b.c`: These images are automatically built from [the `master` branch of NetBox][netbox-master].
* `snapshot-a.b.c`:
These are prerelease builds. These are prerelease builds.
They contain the support files of _NetBox Docker version_ `a.b.c`. They contain the support files of _NetBox Docker version_ `a.b.c`.
You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility.
These images are automatically built from the [`main` branch of NetBox][netbox-main]. These images are automatically built from the [`develop` branch of NetBox][netbox-develop].
For each of the above tag, there is an extra tag: For each of the above tag, there is an extra tag:
- `vX.Y.Z`, `vX.Y`: * `vX.Y.Z`, `vX.Y`:
This is the same version as `vX.Y.Z-a.b.c` (or `vX.Y-a.b.c`, respectively). This is the same version as `vX.Y.Z-a.b.c` (or `vX.Y-a.b.c`, respectively).
- `latest` It always points to the latest version of _NetBox Docker_.
* `latest`
This is the same version as `latest-a.b.c`. This is the same version as `latest-a.b.c`.
It always points to the latest version of _NetBox Docker_. It always points to the latest version of _NetBox Docker_.
- `snapshot` * `snapshot`
This is the same version as `snapshot-a.b.c`. This is the same version as `snapshot-a.b.c`.
It always points to the latest version of _NetBox Docker_. It always points to the latest version of _NetBox Docker_.
Then there is currently one extra tags for each of the above tags:
* `-ldap`:
These container images contain additional dependencies and configuration files for connecting NetBox to an LDAP directory.
[Learn more about that in our wiki][netbox-docker-ldap].
[netbox-releases]: https://github.com/netbox-community/netbox/releases [netbox-releases]: https://github.com/netbox-community/netbox/releases
[netbox-main]: https://github.com/netbox-community/netbox/tree/main [netbox-master]: https://github.com/netbox-community/netbox/tree/master
[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop
[netbox-branches]: https://github.com/netbox-community/netbox/branches
[netbox-docker-ldap]: https://github.com/netbox-community/netbox-docker/wiki/LDAP
## Documentation ## Documentation
Please refer [to our wiki on GitHub][netbox-docker-wiki] for further information on how to use the NetBox Docker image properly. Please refer [to our wiki on GitHub][netbox-docker-wiki] for further information on how to use the NetBox Docker image properly.
The wiki covers advanced topics such as using files for secrets, configuring TLS, deployment to Kubernetes, monitoring and configuring LDAP. The wiki covers advanced topics such as using files for secrets, configuring TLS, deployment to Kubernetes, monitoring and configuring NAPALM and LDAP.
Our wiki is a community effort. Our wiki is a community effort.
Feel free to correct errors, update outdated information or provide additional guides and insights. Feel free to correct errors, update outdated information or provide additional guides and insights.
@ -110,7 +118,7 @@ Feel free to correct errors, update outdated information or provide additional g
Feel free to ask questions in our [GitHub Community][netbox-community] Feel free to ask questions in our [GitHub Community][netbox-community]
or [join our Slack][netbox-docker-slack] and ask [in our channel `#netbox-docker`][netbox-docker-slack-channel], or [join our Slack][netbox-docker-slack] and ask [in our channel `#netbox-docker`][netbox-docker-slack-channel],
which is free to use and where there are almost always people online that can help you. which is free to use and where there are almost always people online that can help you in the Slack channel.
If you need help with using NetBox or developing for it or against it's API If you need help with using NetBox or developing for it or against it's API
you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack instance very helpful. you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack instance very helpful.
@ -119,18 +127,17 @@ you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack ins
## Dependencies ## Dependencies
This project relies only on _Docker_ and _docker-compose_ meeting these requirements: This project relies only on *Docker* and *docker-compose* meeting these requirements:
- The _Docker version_ must be at least `20.10.10`. * The *Docker version* must be at least `19.03`.
- The _containerd version_ must be at least `1.5.6`. * The *docker-compose version* must be at least `1.28.0`.
- The _docker-compose version_ must be at least `1.28.0`.
To check the version installed on your system run `docker --version` and `docker compose version`. To check the version installed on your system run `docker --version` and `docker-compose --version`.
## Updating ## Updating
Please read [the release notes][releases] carefully when updating to a new image version. Please read [the release notes][releases] carefully when updating to a new image version.
Note that the version of the NetBox Docker container image must stay in sync with the version of the Git repository. Note that the version of the NetBox Docker container image must stay in sync with the code.
If you update for the first time, be sure [to follow our _How To Update NetBox Docker_ guide in the wiki][netbox-docker-wiki-updating]. If you update for the first time, be sure [to follow our _How To Update NetBox Docker_ guide in the wiki][netbox-docker-wiki-updating].
@ -139,8 +146,7 @@ If you update for the first time, be sure [to follow our _How To Update NetBox D
## Rebuilding the Image ## Rebuilding the Image
`./build.sh` can be used to rebuild the container image. `./build.sh` can be used to rebuild the container image. See `./build.sh --help` for more information.
See `./build.sh --help` for more information or `./build-latest.sh` for an example.
For more details on custom builds [consult our wiki][netbox-docker-wiki-build]. For more details on custom builds [consult our wiki][netbox-docker-wiki-build].
@ -149,15 +155,13 @@ For more details on custom builds [consult our wiki][netbox-docker-wiki-build].
## Tests ## Tests
We have a test script. We have a test script.
It runs NetBox's own unit tests and ensures that NetBox starts: It runs NetBox's own unit tests and ensures that all initializers work:
```bash ```bash
IMAGE=docker.io/netboxcommunity/netbox:latest ./test.sh IMAGE=netboxcommunity/netbox:latest ./test.sh
``` ```
## Support ## Support
This repository is currently maintained by the community. This repository is currently maintained by the community.
The community is expected to help each other.
Please consider sponsoring the maintainers of this project. Please consider sponsoring the maintainers of this project.

View file

@ -1 +1 @@
3.4.2 2.0.0

View file

@ -1,5 +0,0 @@
---
paths:
.github/workflows/**/*.{yml,yaml}:
ignore:
- ".*ubuntu-24.04-arm.*"

View file

@ -1,9 +0,0 @@
#!/bin/bash
NEEDED_COMMANDS="curl jq docker skopeo"
for c in $NEEDED_COMMANDS; do
if ! command -v "$c" &>/dev/null; then
echo "⚠️ '$c' is not installed. Can't proceed with build."
exit 1
fi
done

View file

@ -0,0 +1,8 @@
#!/bin/bash
push_image_to_registry() {
local target_tag=$1
echo "⏫ Pushing '${target_tag}'"
$DRY docker push "${target_tag}"
echo "✅ Finished pushing the Docker image '${target_tag}'."
}

View file

@ -1,18 +1,82 @@
#!/bin/bash #!/bin/bash
# Retrieves image configuration from public images in DockerHub
check_if_tags_exists() { # Functions from https://gist.github.com/cirocosta/17ea17be7ac11594cb0f290b0a3ac0d1
local image=$1 # Optimised for our use case
local tag=$2
skopeo list-tags "docker://$image" | jq -r ".Tags | contains([\"$tag\"])"
}
get_image_label() { get_image_label() {
local label=$1 local label=$1
local image=$2 local image=$2
skopeo inspect "docker://$image" | jq -r ".Labels[\"$label\"]" local tag=$3
local token
token=$(_get_token "$image")
local digest
digest=$(_get_digest "$image" "$tag" "$token")
local retval="null"
if [ "$digest" != "null" ]; then
retval=$(_get_image_configuration "$image" "$token" "$digest" "$label")
fi
echo "$retval"
}
get_image_layers() {
local image=$1
local tag=$2
local token
token=$(_get_token "$image")
_get_layers "$image" "$tag" "$token"
} }
get_image_last_layer() { get_image_last_layer() {
local image=$1 local image=$1
skopeo inspect "docker://$image" | jq -r ".Layers | last" local tag=$2
local token
token=$(_get_token "$image")
local layers
mapfile -t layers < <(_get_layers "$image" "$tag" "$token")
echo "${layers[-1]}"
}
_get_image_configuration() {
local image=$1
local token=$2
local digest=$3
local label=$4
curl \
--silent \
--location \
--header "Authorization: Bearer $token" \
"https://registry-1.docker.io/v2/$image/blobs/$digest" |
jq -r ".config.Labels.\"$label\""
}
_get_token() {
local image=$1
curl \
--silent \
"https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io" |
jq -r '.token'
}
_get_digest() {
local image=$1
local tag=$2
local token=$3
curl \
--silent \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
--header "Authorization: Bearer $token" \
"https://registry-1.docker.io/v2/$image/manifests/$tag" |
jq -r '.config.digest'
}
_get_layers() {
local image=$1
local tag=$2
local token=$3
curl \
--silent \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
--header "Authorization: Bearer $token" \
"https://registry-1.docker.io/v2/$image/manifests/$tag" |
jq -r '.layers[].digest'
} }

View file

@ -19,14 +19,3 @@ gh_env() {
echo "${@}" >>"${GITHUB_ENV}" echo "${@}" >>"${GITHUB_ENV}"
fi fi
} }
###
# Prints the output to the file defined in ${GITHUB_OUTPUT}.
# Only executes if ${GH_ACTION} is defined.
# Example Usage: gh_env "FOO_VAR=bar_value"
###
gh_out() {
if [ -n "${GH_ACTION}" ]; then
echo "${@}" >>"$GITHUB_OUTPUT"
fi
}

View file

@ -1,27 +1,26 @@
#!/bin/bash #!/bin/bash
# Builds the latest released version # Builds the latest released version
# Check if we have everything needed for the build
source ./build-functions/check-commands.sh
source ./build-functions/gh-functions.sh
echo "▶️ $0 $*" echo "▶️ $0 $*"
CURL_ARGS=( ###
--silent # Check for the jq library needed for parsing JSON
) ###
if ! command -v jq; then
echo "⚠️ jq command missing from \$PATH!"
exit 1
fi
### ###
# Checking for the presence of GITHUB_TOKEN # Checking for the presence of GITHUB_OAUTH_CLIENT_ID
# and GITHUB_OAUTH_CLIENT_SECRET
### ###
if [ -n "${GITHUB_TOKEN}" ]; then if [ -n "${GITHUB_OAUTH_CLIENT_ID}" ] && [ -n "${GITHUB_OAUTH_CLIENT_SECRET}" ]; then
echo "🗝 Performing authenticated Github API calls." echo "🗝 Performing authenticated Github API calls."
CURL_ARGS+=( GITHUB_OAUTH_PARAMS="client_id=${GITHUB_OAUTH_CLIENT_ID}&client_secret=${GITHUB_OAUTH_CLIENT_SECRET}"
--header "Authorization: Bearer ${GITHUB_TOKEN}"
)
else else
echo "🕶 Performing unauthenticated Github API calls. This might result in lower Github rate limits!" echo "🕶 Performing unauthenticated Github API calls. This might result in lower Github rate limits!"
GITHUB_OAUTH_PARAMS=""
fi fi
### ###
@ -43,27 +42,31 @@ fi
### ###
ORIGINAL_GITHUB_REPO="netbox-community/netbox" ORIGINAL_GITHUB_REPO="netbox-community/netbox"
GITHUB_REPO="${GITHUB_REPO-$ORIGINAL_GITHUB_REPO}" GITHUB_REPO="${GITHUB_REPO-$ORIGINAL_GITHUB_REPO}"
URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/releases" URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/releases?${GITHUB_OAUTH_PARAMS}"
# Composing the JQ commans to extract the most recent version number # Composing the JQ commans to extract the most recent version number
JQ_LATEST="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==${PRERELEASE-false}) | .tag_name" JQ_LATEST="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==${PRERELEASE-false}) | .tag_name"
CURL="curl" CURL="curl -sS"
# Querying the Github API to fetch the most recent version number # Querying the Github API to fetch the most recent version number
VERSION=$($CURL "${CURL_ARGS[@]}" "${URL_RELEASES}" | jq -r "${JQ_LATEST}" 2>/dev/null) VERSION=$($CURL "${URL_RELEASES}" | jq -r "${JQ_LATEST}")
### ###
# Check if the prerelease version is actually higher than stable version # Check if the prerelease version is actually higher than stable version
### ###
if [ "${PRERELEASE}" == "true" ]; then if [ "${PRERELEASE}" == "true" ]; then
JQ_STABLE="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==false) | .tag_name" JQ_STABLE="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==false) | .tag_name"
STABLE_VERSION=$($CURL "${CURL_ARGS[@]}" "${URL_RELEASES}" | jq -r "${JQ_STABLE}" 2>/dev/null) STABLE_VERSION=$($CURL "${URL_RELEASES}" | jq -r "${JQ_STABLE}")
MAJOR_STABLE=$(expr "${STABLE_VERSION}" : 'v\([0-9]\+\)') # shellcheck disable=SC2003
MINOR_STABLE=$(expr "${STABLE_VERSION}" : 'v[0-9]\+\.\([0-9]\+\)') MAJOR_STABLE=$(expr match "${STABLE_VERSION}" 'v\([0-9]\+\)')
MAJOR_UNSTABLE=$(expr "${VERSION}" : 'v\([0-9]\+\)') # shellcheck disable=SC2003
MINOR_UNSTABLE=$(expr "${VERSION}" : 'v[0-9]\+\.\([0-9]\+\)') MINOR_STABLE=$(expr match "${STABLE_VERSION}" 'v[0-9]\+\.\([0-9]\+\)')
# shellcheck disable=SC2003
MAJOR_UNSTABLE=$(expr match "${VERSION}" 'v\([0-9]\+\)')
# shellcheck disable=SC2003
MINOR_UNSTABLE=$(expr match "${VERSION}" 'v[0-9]\+\.\([0-9]\+\)')
if { if {
[ "${MAJOR_STABLE}" -eq "${MAJOR_UNSTABLE}" ] && [ "${MAJOR_STABLE}" -eq "${MAJOR_UNSTABLE}" ] &&
@ -72,7 +75,10 @@ if [ "${PRERELEASE}" == "true" ]; then
echo "❎ Latest unstable version '${VERSION}' is not higher than the latest stable version '$STABLE_VERSION'." echo "❎ Latest unstable version '${VERSION}' is not higher than the latest stable version '$STABLE_VERSION'."
if [ -z "$DEBUG" ]; then if [ -z "$DEBUG" ]; then
gh_out "skipped=true" if [ -n "${GH_ACTION}" ]; then
echo "::set-output name=skipped::true"
fi
exit 0 exit 0
else else
echo "⚠️ Would exit here with code '0', but DEBUG is enabled." echo "⚠️ Would exit here with code '0', but DEBUG is enabled."

583
build.sh
View file

@ -6,127 +6,87 @@ echo "▶️ $0 $*"
set -e set -e
if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
_BOLD=$(tput bold) echo "Usage: ${0} <branch> [--push|--push-only]"
_GREEN=$(tput setaf 2) echo " branch The branch or tag to build. Required."
_CYAN=$(tput setaf 6) echo " --push Pushes the built Docker image to the registry."
_CLEAR=$(tput sgr0) echo " --push-only Only pushes the Docker image to the registry, but does not build it."
echo ""
cat <<END_OF_HELP echo "You can use the following ENV variables to customize the build:"
${_BOLD}Usage:${_CLEAR} ${0} <branch> [--push] echo " SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO})."
echo " Default: netbox-community"
branch The branch or tag to build. Required. echo " SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO})."
--push Pushes the built container image to the registry. echo " Default: netbox"
echo " URL Where to fetch the code from."
${_BOLD}You can use the following ENV variables to customize the build:${_CLEAR} echo " Must be a git repository. Can be private."
echo " Default: https://github.com/\${SRC_ORG}/\${SRC_REPO}.git"
SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). echo " NETBOX_PATH The path where netbox will be checkout out."
${_GREEN}Default:${_CLEAR} netbox-community echo " Must not be outside of the netbox-docker repository (because of Docker)!"
echo " Default: .netbox"
SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). echo " SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered."
${_GREEN}Default:${_CLEAR} netbox echo " This may be useful, if you are manually managing the NETBOX_PATH."
echo " Default: undefined"
URL Where to fetch the code from. echo " TAG The version part of the docker tag."
Must be a git repository. Can be private. echo " Default:"
${_GREEN}Default:${_CLEAR} https://github.com/\${SRC_ORG}/\${SRC_REPO}.git echo " When <branch>=master: latest"
echo " When <branch>=develop: snapshot"
NETBOX_PATH The path where netbox will be checkout out. echo " Else: same as <branch>"
Must not be outside of the netbox-docker repository (because of Docker)! echo " DOCKER_REGISTRY The Docker repository's registry (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
${_GREEN}Default:${_CLEAR} .netbox echo " Used for tagging the image."
echo " Default: docker.io"
SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered. echo " DOCKER_ORG The Docker repository's organisation (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
This may be useful, if you are manually managing the NETBOX_PATH. echo " Used for tagging the image."
${_GREEN}Default:${_CLEAR} undefined echo " Default: netboxcommunity"
echo " DOCKER_REPO The Docker repository's name (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
TAG The version part of the image tag. echo " Used for tagging the image."
${_GREEN}Default:${_CLEAR} echo " Default: netbox"
When <branch>=main: snapshot echo " DOCKER_TAG The name of the tag which is applied to the image."
Else: same as <branch> echo " Useful for pushing into another registry than hub.docker.com."
echo " Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG}"
IMAGE_NAMES The names used for the image including the registry echo " DOCKER_SHORT_TAG The name of the short tag which is applied to the"
Used for tagging the image. echo " image. This is used to tag all patch releases to their"
${_GREEN}Default:${_CLEAR} docker.io/netboxcommunity/netbox echo " containing version e.g. v2.5.1 -> v2.5"
${_CYAN}Example:${_CLEAR} 'docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox' echo " Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:<MAJOR>.<MINOR>"
echo " DOCKERFILE The name of Dockerfile to use."
DOCKER_TAG The name of the tag which is applied to the image. echo " Default: Dockerfile"
Useful for pushing into another registry than hub.docker.com. echo " DOCKER_FROM The base image to use."
${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG} echo " Default: 'debian:11-slim'"
echo " DOCKER_TARGET A specific target to build."
DOCKER_SHORT_TAG The name of the short tag which is applied to the echo " It's currently not possible to pass multiple targets."
image. This is used to tag all patch releases to their echo " Default: main"
containing version e.g. v2.5.1 -> v2.5 echo " HTTP_PROXY The proxy to use for http requests."
${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:<MAJOR>.<MINOR> echo " Example: http://proxy.domain.tld:3128"
echo " Default: undefined"
DOCKERFILE The name of Dockerfile to use. echo " NO_PROXY Comma-separated list of domain extensions proxy should not be used for."
${_GREEN}Default:${_CLEAR} Dockerfile echo " Example: .domain1.tld,.domain2.tld"
echo " Default: undefined"
DOCKER_FROM The base image to use. echo " DEBUG If defined, the script does not stop when certain checks are unsatisfied."
${_GREEN}Default:${_CLEAR} 'ubuntu:24.04' echo " Default: undefined"
echo " DRY_RUN Prints all build statements instead of running them."
BUILDX_PLATFORM echo " Default: undefined"
Specifies the platform(s) to build the image for. echo " GH_ACTION If defined, special 'echo' statements are enabled that set the"
${_CYAN}Example:${_CLEAR} 'linux/amd64,linux/arm64' echo " following environment variables in Github Actions:"
${_GREEN}Default:${_CLEAR} 'linux/amd64' echo " - FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable"
echo " Default: undefined"
BUILDX_BUILDER_NAME echo ""
If defined, the image build will be assigned to the given builder. echo "Examples:"
If you specify this variable, make sure that the builder exists. echo " ${0} master"
If this value is not defined, a new builx builder with the directory name of the echo " This will fetch the latest 'master' branch, build a Docker Image and tag it"
current directory (i.e. '$(basename "${PWD}")') is created." echo " 'netboxcommunity/netbox:latest'."
${_CYAN}Example:${_CLEAR} 'clever_lovelace' echo " ${0} develop"
${_GREEN}Default:${_CLEAR} undefined echo " This will fetch the latest 'develop' branch, build a Docker Image and tag it"
echo " 'netboxcommunity/netbox:snapshot'."
BUILDX_REMOVE_BUILDER echo " ${0} v2.6.6"
If defined (and only if BUILDX_BUILDER_NAME is undefined), echo " This will fetch the 'v2.6.6' tag, build a Docker Image and tag it"
then the buildx builder created by this script will be removed after use. echo " 'netboxcommunity/netbox:v2.6.6' and 'netboxcommunity/netbox:v2.6'."
This is useful if you build NetBox Docker on an automated system that does echo " ${0} develop-2.7"
not manage the builders for you. echo " This will fetch the 'develop-2.7' branch, build a Docker Image and tag it"
${_CYAN}Example:${_CLEAR} 'on' echo " 'netboxcommunity/netbox:develop-2.7'."
${_GREEN}Default:${_CLEAR} undefined echo " SRC_ORG=cimnine ${0} feature-x"
echo " This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,"
HTTP_PROXY The proxy to use for http requests. echo " build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'."
${_CYAN}Example:${_CLEAR} http://proxy.domain.tld:3128 echo " SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x"
${_GREEN}Default:${_CLEAR} undefined echo " This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,"
echo " build a Docker Image and tag it 'cimnine/netbox:feature-x'."
NO_PROXY Comma-separated list of domain extensions proxy should not be used for.
${_CYAN}Example:${_CLEAR} .domain1.tld,.domain2.tld
${_GREEN}Default:${_CLEAR} undefined
DEBUG If defined, the script does not stop when certain checks are unsatisfied.
${_GREEN}Default:${_CLEAR} undefined
DRY_RUN Prints all build statements instead of running them.
${_GREEN}Default:${_CLEAR} undefined
GH_ACTION If defined, special 'echo' statements are enabled that set the
following environment variables in Github Actions:
- FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable
${_GREEN}Default:${_CLEAR} undefined
CHECK_ONLY Only checks if the build is needed and sets the GH Action output.
${_GREEN}Default:${_CLEAR} undefined
${_BOLD}Examples:${_CLEAR}
${0} main
This will fetch the latest 'main' branch, build a Docker Image and tag it
'netboxcommunity/netbox:snapshot'.
${0} v4.2.0
This will fetch the 'v4.2.0' tag, build a Docker Image and tag it
'netboxcommunity/netbox:v4.2.0' and 'netboxcommunity/netbox:v4.2'.
${0} feature
This will fetch the 'feature' branch, build a Docker Image and tag it
'netboxcommunity/netbox:feature'.
SRC_ORG=cimnine ${0} feature-x
This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,
build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'.
SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x
This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,
build a Docker Image and tag it 'cimnine/netbox:feature-x'.
END_OF_HELP
if [ "${1}x" == "x" ]; then if [ "${1}x" == "x" ]; then
exit 1 exit 1
@ -135,15 +95,8 @@ END_OF_HELP
fi fi
fi fi
# Check if we have everything needed for the build
source ./build-functions/check-commands.sh
# Load all build functions
source ./build-functions/get-public-image-config.sh
source ./build-functions/gh-functions.sh source ./build-functions/gh-functions.sh
IMAGE_NAMES="${IMAGE_NAMES-docker.io/netboxcommunity/netbox}"
IFS=' ' read -ra IMAGE_NAMES <<<"${IMAGE_NAMES}"
### ###
# Enabling dry-run mode # Enabling dry-run mode
### ###
@ -172,7 +125,7 @@ if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ]; then
REMOTE_EXISTS=$(git ls-remote --heads --tags "${URL}" "${NETBOX_BRANCH}" | wc -l) REMOTE_EXISTS=$(git ls-remote --heads --tags "${URL}" "${NETBOX_BRANCH}" | wc -l)
if [ "${REMOTE_EXISTS}" == "0" ]; then if [ "${REMOTE_EXISTS}" == "0" ]; then
echo "❌ Remote branch '${NETBOX_BRANCH}' not found in '${URL}'; Nothing to do" echo "❌ Remote branch '${NETBOX_BRANCH}' not found in '${URL}'; Nothing to do"
gh_out "skipped=true" gh_echo "::set-output name=skipped::true"
exit 0 exit 0
fi fi
echo "🌐 Checking out '${NETBOX_BRANCH}' of NetBox from the url '${URL}' into '${NETBOX_PATH}'" echo "🌐 Checking out '${NETBOX_BRANCH}' of NetBox from the url '${URL}' into '${NETBOX_PATH}'"
@ -217,13 +170,13 @@ fi
# Determining the value for DOCKER_FROM # Determining the value for DOCKER_FROM
### ###
if [ -z "$DOCKER_FROM" ]; then if [ -z "$DOCKER_FROM" ]; then
DOCKER_FROM="docker.io/ubuntu:24.04" DOCKER_FROM="debian:11-slim"
fi fi
### ###
# Variables for labelling the docker image # Variables for labelling the docker image
### ###
BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%S+00:00')" BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')"
if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then
GIT_REF="$(git rev-parse HEAD)" GIT_REF="$(git rev-parse HEAD)"
@ -255,7 +208,10 @@ DOCKER_REGISTRY="${DOCKER_REGISTRY-docker.io}"
DOCKER_ORG="${DOCKER_ORG-netboxcommunity}" DOCKER_ORG="${DOCKER_ORG-netboxcommunity}"
DOCKER_REPO="${DOCKER_REPO-netbox}" DOCKER_REPO="${DOCKER_REPO-netbox}"
case "${NETBOX_BRANCH}" in case "${NETBOX_BRANCH}" in
main) master)
TAG="${TAG-latest}"
;;
develop)
TAG="${TAG-snapshot}" TAG="${TAG-snapshot}"
;; ;;
*) *)
@ -264,195 +220,188 @@ main)
esac esac
### ###
# composing the final TARGET_DOCKER_TAG # Determine targets to build
### ###
TARGET_DOCKER_TAG="${DOCKER_TAG-${TAG}}" DEFAULT_DOCKER_TARGETS=("main")
TARGET_DOCKER_TAG_PROJECT="${TARGET_DOCKER_TAG}-${PROJECT_VERSION}" DOCKER_TARGETS=("${DOCKER_TARGET:-"${DEFAULT_DOCKER_TARGETS[@]}"}")
echo "🏭 Building the following targets:" "${DOCKER_TARGETS[@]}"
gh_echo "::endgroup::"
### ###
# composing the additional DOCKER_SHORT_TAG, # Build each target
# i.e. "v4.2.0" becomes "v4.2",
# which is only relevant for version tags
# Also let "latest" follow the highest version
### ###
if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then export DOCKER_BUILDKIT=${DOCKER_BUILDKIT-1}
MAJOR=${BASH_REMATCH[1]} for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
MINOR=${BASH_REMATCH[2]} gh_echo "::group::🏗 Building the target '${DOCKER_TARGET}'"
echo "🏗 Building the target '${DOCKER_TARGET}'"
TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-v${MAJOR}.${MINOR}}" ###
TARGET_DOCKER_LATEST_TAG="latest" # composing the final TARGET_DOCKER_TAG
TARGET_DOCKER_SHORT_TAG_PROJECT="${TARGET_DOCKER_SHORT_TAG}-${PROJECT_VERSION}" ###
TARGET_DOCKER_LATEST_TAG_PROJECT="${TARGET_DOCKER_LATEST_TAG}-${PROJECT_VERSION}" TARGET_DOCKER_TAG="${DOCKER_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:${TAG}}"
fi if [ "${DOCKER_TARGET}" != "main" ]; then
TARGET_DOCKER_TAG="${TARGET_DOCKER_TAG}-${DOCKER_TARGET}"
fi
TARGET_DOCKER_TAG_PROJECT="${TARGET_DOCKER_TAG}-${PROJECT_VERSION}"
IMAGE_NAME_TAGS=() gh_env "FINAL_DOCKER_TAG=${TARGET_DOCKER_TAG_PROJECT}"
for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do gh_echo "::set-output name=skipped::false"
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG_PROJECT}") ###
# composing the additional DOCKER_SHORT_TAG,
# i.e. "v2.6.1" becomes "v2.6",
# which is only relevant for version tags
# Also let "latest" follow the highest version
###
if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then
MAJOR=${BASH_REMATCH[1]}
MINOR=${BASH_REMATCH[2]}
TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:v${MAJOR}.${MINOR}}"
TARGET_DOCKER_LATEST_TAG="${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:latest"
if [ "${DOCKER_TARGET}" != "main" ]; then
TARGET_DOCKER_SHORT_TAG="${TARGET_DOCKER_SHORT_TAG}-${DOCKER_TARGET}"
TARGET_DOCKER_LATEST_TAG="${TARGET_DOCKER_LATEST_TAG}-${DOCKER_TARGET}"
fi
TARGET_DOCKER_SHORT_TAG_PROJECT="${TARGET_DOCKER_SHORT_TAG}-${PROJECT_VERSION}"
TARGET_DOCKER_LATEST_TAG_PROJECT="${TARGET_DOCKER_LATEST_TAG}-${PROJECT_VERSION}"
fi
###
# Proceeding to buils stage, except if `--push-only` is passed
###
if [ "${2}" != "--push-only" ]; then
###
# Checking if the build is necessary,
# meaning build only if one of those values changed:
# - base image digest
# - netbox git ref (Label: netbox.git-ref)
# - netbox-docker git ref (Label: org.opencontainers.image.revision)
###
# Load information from registry (only for docker.io)
SHOULD_BUILD="false"
BUILD_REASON=""
if [ -z "${GH_ACTION}" ]; then
# Asuming non Github builds should always proceed
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} interactive"
elif [ "$DOCKER_REGISTRY" = "docker.io" ]; then
source ./build-functions/get-public-image-config.sh
IFS=':' read -ra DOCKER_FROM_SPLIT <<<"${DOCKER_FROM}"
if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ .*/.* ]]; then
# Need to use "library/..." for images the have no two part name
DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}"
fi
BASE_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM_SPLIT[0]}" "${DOCKER_FROM_SPLIT[1]}")
mapfile -t IMAGES_LAYERS_OLD < <(get_image_layers "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
NETBOX_GIT_REF_OLD=$(get_image_label netbox.git-ref "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
GIT_REF_OLD=$(get_image_label org.opencontainers.image.revision "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${BASE_LAST_LAYER}\$"; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} debian"
fi
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox"
fi
if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox-docker"
fi
else
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} no-check"
fi
###
# Composing all arguments for `docker build`
###
DOCKER_BUILD_ARGS=(
--pull
--target "${DOCKER_TARGET}"
-f "${DOCKERFILE}"
-t "${TARGET_DOCKER_TAG}"
-t "${TARGET_DOCKER_TAG_PROJECT}"
)
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_SHORT_TAG}")
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_SHORT_TAG_PROJECT}")
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_LATEST_TAG}")
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_LATEST_TAG_PROJECT}")
fi
# --label
DOCKER_BUILD_ARGS+=(
--label "netbox.original-tag=${TARGET_DOCKER_TAG_PROJECT}"
--label "org.opencontainers.image.created=${BUILD_DATE}"
--label "org.opencontainers.image.version=${PROJECT_VERSION}"
)
if [ -d ".git" ]; then
DOCKER_BUILD_ARGS+=(
--label "org.opencontainers.image.revision=${GIT_REF}"
)
fi
if [ -d "${NETBOX_PATH}/.git" ]; then
DOCKER_BUILD_ARGS+=(
--label "netbox.git-branch=${NETBOX_GIT_BRANCH}"
--label "netbox.git-ref=${NETBOX_GIT_REF}"
--label "netbox.git-url=${NETBOX_GIT_URL}"
)
fi
if [ -n "${BUILD_REASON}" ]; then
BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON")
DOCKER_BUILD_ARGS+=(--label "netbox.build-reason=${BUILD_REASON}")
fi
# --build-arg
DOCKER_BUILD_ARGS+=(--build-arg "NETBOX_PATH=${NETBOX_PATH}")
if [ -n "${DOCKER_FROM}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "FROM=${DOCKER_FROM}")
fi
# shellcheck disable=SC2031
if [ -n "${HTTP_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "http_proxy=${HTTP_PROXY}")
DOCKER_BUILD_ARGS+=(--build-arg "https_proxy=${HTTPS_PROXY}")
fi
if [ -n "${NO_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "no_proxy=${NO_PROXY}")
fi
###
# Building the docker image
###
if [ "${SHOULD_BUILD}" == "true" ]; then
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG_PROJECT}'."
echo " Build reason set to: ${BUILD_REASON}"
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG_PROJECT}'"
echo "🔎 Inspecting labels on '${TARGET_DOCKER_TAG_PROJECT}'"
$DRY docker inspect "${TARGET_DOCKER_TAG_PROJECT}" --format "{{json .Config.Labels}}" | jq
else
echo "Build skipped because sources didn't change"
echo "::set-output name=skipped::true"
fi
fi
###
# Pushing the docker images if either `--push` or `--push-only` are passed
###
if [ "${2}" == "--push" ] || [ "${2}" == "--push-only" ]; then
source ./build-functions/docker-functions.sh
push_image_to_registry "${TARGET_DOCKER_TAG}"
push_image_to_registry "${TARGET_DOCKER_TAG_PROJECT}"
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
push_image_to_registry "${TARGET_DOCKER_SHORT_TAG}"
push_image_to_registry "${TARGET_DOCKER_SHORT_TAG_PROJECT}"
push_image_to_registry "${TARGET_DOCKER_LATEST_TAG}"
push_image_to_registry "${TARGET_DOCKER_LATEST_TAG_PROJECT}"
fi
fi
gh_echo "::endgroup::"
done done
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_SHORT_TAG}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_SHORT_TAG_PROJECT}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_LATEST_TAG}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_LATEST_TAG_PROJECT}")
done
fi
FINAL_DOCKER_TAG="${IMAGE_NAME_TAGS[0]}"
gh_env "FINAL_DOCKER_TAG=${IMAGE_NAME_TAGS[0]}"
###
# Checking if the build is necessary,
# meaning build only if one of those values changed:
# - a new tag is beeing created
# - base image digest
# - netbox git ref (Label: netbox.git-ref)
# - netbox-docker git ref (Label: org.opencontainers.image.revision)
###
# Load information from registry (only for first registry in "IMAGE_NAMES")
SHOULD_BUILD="false"
BUILD_REASON=""
if [ -z "${GH_ACTION}" ]; then
# Asuming non Github builds should always proceed
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} interactive"
elif [ "false" == "$(check_if_tags_exists "${IMAGE_NAMES[0]}" "$TARGET_DOCKER_TAG")" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} newtag"
else
echo "Checking labels for '${FINAL_DOCKER_TAG}'"
BASE_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM}")
OLD_BASE_LAST_LAYER=$(get_image_label netbox.last-base-image-layer "${FINAL_DOCKER_TAG}")
NETBOX_GIT_REF_OLD=$(get_image_label netbox.git-ref "${FINAL_DOCKER_TAG}")
GIT_REF_OLD=$(get_image_label org.opencontainers.image.revision "${FINAL_DOCKER_TAG}")
if [ "${BASE_LAST_LAYER}" != "${OLD_BASE_LAST_LAYER}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} ubuntu"
fi
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox"
fi
if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox-docker"
fi
fi
if [ "${SHOULD_BUILD}" != "true" ]; then
echo "Build skipped because sources didn't change"
gh_out "skipped=true"
exit 0 # Nothing to do -> exit
else
gh_out "skipped=false"
fi
gh_echo "::endgroup::"
if [ "${CHECK_ONLY}" = "true" ]; then
echo "Only check if build needed was requested. Exiting"
exit 0
fi
###
# Build the image
###
gh_echo "::group::🏗 Building the image"
###
# Composing all arguments for `docker build`
###
DOCKER_BUILD_ARGS=(
--pull
--target main
-f "${DOCKERFILE}"
)
for IMAGE_NAME in "${IMAGE_NAME_TAGS[@]}"; do
DOCKER_BUILD_ARGS+=(-t "${IMAGE_NAME}")
done
# --label
DOCKER_BUILD_ARGS+=(
--label "netbox.original-tag=${TARGET_DOCKER_TAG_PROJECT}"
--label "org.opencontainers.image.created=${BUILD_DATE}"
--label "org.opencontainers.image.version=${PROJECT_VERSION}"
)
if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then
DOCKER_BUILD_ARGS+=(
--label "org.opencontainers.image.revision=${GIT_REF}"
)
fi
if [ -d "${NETBOX_PATH}/.git" ] && [ -z "${SKIP_GIT}" ]; then
DOCKER_BUILD_ARGS+=(
--label "netbox.git-branch=${NETBOX_GIT_BRANCH}"
--label "netbox.git-ref=${NETBOX_GIT_REF}"
--label "netbox.git-url=${NETBOX_GIT_URL}"
)
fi
if [ -n "${BUILD_REASON}" ]; then
BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON")
DOCKER_BUILD_ARGS+=(--label "netbox.build-reason=${BUILD_REASON}")
DOCKER_BUILD_ARGS+=(--label "netbox.last-base-image-layer=${BASE_LAST_LAYER}")
fi
# --build-arg
DOCKER_BUILD_ARGS+=(--build-arg "NETBOX_PATH=${NETBOX_PATH}")
if [ -n "${DOCKER_FROM}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "FROM=${DOCKER_FROM}")
fi
# shellcheck disable=SC2031
if [ -n "${HTTP_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "http_proxy=${HTTP_PROXY}")
DOCKER_BUILD_ARGS+=(--build-arg "https_proxy=${HTTPS_PROXY}")
fi
if [ -n "${NO_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "no_proxy=${NO_PROXY}")
fi
DOCKER_BUILD_ARGS+=(--platform "${BUILDX_PLATFORM-linux/amd64}")
if [ "${2}" == "--push" ]; then
# output type=docker does not work with pushing
DOCKER_BUILD_ARGS+=(
--output=type=image
--push
)
else
DOCKER_BUILD_ARGS+=(
--output=type=docker
)
fi
###
# Building the docker image
###
if [ -z "${BUILDX_BUILDER_NAME}" ]; then
BUILDX_BUILDER_NAME="$(basename "${PWD}")"
fi
if ! docker buildx ls | grep --quiet --word-regexp "${BUILDX_BUILDER_NAME}"; then
echo "👷 Creating new Buildx Builder '${BUILDX_BUILDER_NAME}'"
$DRY docker buildx create --name "${BUILDX_BUILDER_NAME}"
BUILDX_BUILDER_CREATED="yes"
fi
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG_PROJECT}'."
echo " Build reason set to: ${BUILD_REASON}"
$DRY docker buildx \
--builder "${BUILDX_BUILDER_NAME}" \
build \
"${DOCKER_BUILD_ARGS[@]}" \
.
echo "✅ Finished building the Docker images"
gh_echo "::endgroup::" # End group for Build
gh_echo "::group::🏗 Image Labels"
echo "🔎 Inspecting labels on '${IMAGE_NAME_TAGS[0]}'"
$DRY docker inspect "${IMAGE_NAME_TAGS[0]}" --format "{{json .Config.Labels}}" | jq
gh_echo "::endgroup::"
gh_echo "::group::🏗 Clean up"
if [ -n "${BUILDX_REMOVE_BUILDER}" ] && [ "${BUILDX_BUILDER_CREATED}" == "yes" ]; then
echo "👷 Removing Buildx Builder '${BUILDX_BUILDER_NAME}'"
$DRY docker buildx rm "${BUILDX_BUILDER_NAME}"
fi
gh_echo "::endgroup::"

View file

@ -7,17 +7,12 @@
import re import re
from os import environ from os import environ
from os.path import abspath, dirname, join from os.path import abspath, dirname, join
from typing import Any, Callable, Tuple
# For reference see https://docs.netbox.dev/en/stable/configuration/ # For reference see https://netbox.readthedocs.io/en/stable/configuration/
# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py # Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py
###
# NetBox-Docker Helper functions
###
# Read secret from file # Read secret from file
def _read_secret(secret_name: str, default: str | None = None) -> str | None: def _read_secret(secret_name, default = None):
try: try:
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8') f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
except EnvironmentError: except EnvironmentError:
@ -26,25 +21,6 @@ def _read_secret(secret_name: str, default: str | None = None) -> str | None:
with f: with f:
return f.readline().strip() return f.readline().strip()
# If the `map_fn` isn't defined, then the value that is read from the environment (or the default value if not found) is returned.
# If the `map_fn` is defined, then `map_fn` is invoked and the value (that was read from the environment or the default value if not found)
# is passed to it as a parameter. The value returned from `map_fn` is then the return value of this function.
# The `map_fn` is not invoked, if the value (that was read from the environment or the default value if not found) is None.
def _environ_get_and_map(variable_name: str, default: str | None = None, map_fn: Callable[[str], Any | None] = None) -> Any | None:
env_value = environ.get(variable_name, default)
if env_value == None:
return env_value
if not map_fn:
return env_value
return map_fn(env_value)
_AS_BOOL = lambda value : value.lower() == 'true'
_AS_INT = lambda value : int(value)
_AS_LIST = lambda value : list(filter(None, value.split(' ')))
_BASE_DIR = dirname(dirname(abspath(__file__))) _BASE_DIR = dirname(dirname(abspath(__file__)))
######################### #########################
@ -58,27 +34,22 @@ _BASE_DIR = dirname(dirname(abspath(__file__)))
# #
# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local'] # Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ') ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ')
# ensure that '*' or 'localhost' is always in ALLOWED_HOSTS (needed for health checks)
if '*' not in ALLOWED_HOSTS and 'localhost' not in ALLOWED_HOSTS:
ALLOWED_HOSTS.append('localhost')
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: # PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
# https://docs.djangoproject.com/en/stable/ref/settings/#databases # https://docs.djangoproject.com/en/stable/ref/settings/#databases
DATABASES = { DATABASE = {
'default': { 'NAME': environ.get('DB_NAME', 'netbox'), # Database name
'NAME': environ.get('DB_NAME', 'netbox'), # Database name 'USER': environ.get('DB_USER', ''), # PostgreSQL username
'USER': environ.get('DB_USER', ''), # PostgreSQL username 'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')),
'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')), # PostgreSQL password
# PostgreSQL password 'HOST': environ.get('DB_HOST', 'localhost'), # Database server
'HOST': environ.get('DB_HOST', 'localhost'), # Database server 'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default)
'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default) 'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')},
'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')}, # Database connection SSLMODE
# Database connection SSLMODE 'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
'CONN_MAX_AGE': _environ_get_and_map('DB_CONN_MAX_AGE', '300', _AS_INT), # Max database connection age
# Max database connection age 'DISABLE_SERVER_SIDE_CURSORS': environ.get('DB_DISABLE_SERVER_SIDE_CURSORS', 'False').lower() == 'true',
'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL), # Disable the use of server-side cursors transaction pooling
# Disable the use of server-side cursors transaction pooling
}
} }
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
@ -87,26 +58,19 @@ DATABASES = {
REDIS = { REDIS = {
'tasks': { 'tasks': {
'HOST': environ.get('REDIS_HOST', 'localhost'), 'HOST': environ.get('REDIS_HOST', 'localhost'),
'PORT': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT), 'PORT': int(environ.get('REDIS_PORT', 6379)),
'SENTINELS': [tuple(uri.split(':')) for uri in _environ_get_and_map('REDIS_SENTINELS', '', _AS_LIST) if uri != ''],
'SENTINEL_SERVICE': environ.get('REDIS_SENTINEL_SERVICE', 'default'),
'SENTINEL_TIMEOUT': _environ_get_and_map('REDIS_SENTINEL_TIMEOUT', 10, _AS_INT),
'USERNAME': environ.get('REDIS_USERNAME', ''),
'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')), 'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT), 'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL), 'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL), 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False').lower() == 'true',
}, },
'caching': { 'caching': {
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')), 'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
'PORT': _environ_get_and_map('REDIS_CACHE_PORT', environ.get('REDIS_PORT', '6379'), _AS_INT), 'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))),
'SENTINELS': [tuple(uri.split(':')) for uri in _environ_get_and_map('REDIS_CACHE_SENTINELS', '', _AS_LIST) if uri != ''],
'SENTINEL_SERVICE': environ.get('REDIS_CACHE_SENTINEL_SERVICE', environ.get('REDIS_SENTINEL_SERVICE', 'default')),
'USERNAME': environ.get('REDIS_CACHE_USERNAME', environ.get('REDIS_USERNAME', '')),
'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))), 'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))),
'DATABASE': _environ_get_and_map('REDIS_CACHE_DATABASE', '1', _AS_INT), 'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL), 'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL), 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False')).lower() == 'true',
}, },
} }
@ -116,11 +80,6 @@ REDIS = {
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', '')) SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', ''))
API_TOKEN_PEPPERS = {}
if api_token_pepper := _read_secret('api_token_pepper_1', environ.get('API_TOKEN_PEPPER_1', '')):
API_TOKEN_PEPPERS.update({1: api_token_pepper})
######################### #########################
# # # #
@ -128,232 +87,170 @@ if api_token_pepper := _read_secret('api_token_pepper_1', environ.get('API_TOKEN
# # # #
######################### #########################
# # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of
# # application errors (assuming correct email settings are provided). # application errors (assuming correct email settings are provided).
# ADMINS = [ ADMINS = [
# # ['John Doe', 'jdoe@example.com'], # ['John Doe', 'jdoe@example.com'],
# ] ]
if 'ALLOWED_URL_SCHEMES' in environ: # URL schemes that are allowed within links in NetBox
ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST) ALLOWED_URL_SCHEMES = (
'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
)
# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same # Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. # content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
if 'BANNER_TOP' in environ: BANNER_TOP = environ.get('BANNER_TOP', '')
BANNER_TOP = environ.get('BANNER_TOP', None) BANNER_BOTTOM = environ.get('BANNER_BOTTOM', '')
if 'BANNER_BOTTOM' in environ:
BANNER_BOTTOM = environ.get('BANNER_BOTTOM', None)
# Text to include on the login page above the login form. HTML is allowed. # Text to include on the login page above the login form. HTML is allowed.
if 'BANNER_LOGIN' in environ: BANNER_LOGIN = environ.get('BANNER_LOGIN', '')
BANNER_LOGIN = environ.get('BANNER_LOGIN', None)
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
# BASE_PATH = 'netbox/'
BASE_PATH = environ.get('BASE_PATH', '')
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
if 'CHANGELOG_RETENTION' in environ: CHANGELOG_RETENTION = int(environ.get('CHANGELOG_RETENTION', 90))
CHANGELOG_RETENTION = _environ_get_and_map('CHANGELOG_RETENTION', None, _AS_INT)
# Maximum number of days to retain job results (scripts and reports). Set to 0 to retain job results in the database indefinitely. (Default: 90)
if 'JOB_RETENTION' in environ:
JOB_RETENTION = _environ_get_and_map('JOB_RETENTION', None, _AS_INT)
# JOBRESULT_RETENTION was renamed to JOB_RETENTION in the v3.5.0 release of NetBox. For backwards compatibility, map JOBRESULT_RETENTION to JOB_RETENTION
elif 'JOBRESULT_RETENTION' in environ:
JOB_RETENTION = _environ_get_and_map('JOBRESULT_RETENTION', None, _AS_INT)
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
CORS_ORIGIN_ALLOW_ALL = _environ_get_and_map('CORS_ORIGIN_ALLOW_ALL', 'False', _AS_BOOL) CORS_ORIGIN_ALLOW_ALL = environ.get('CORS_ORIGIN_ALLOW_ALL', 'False').lower() == 'true'
CORS_ORIGIN_WHITELIST = _environ_get_and_map('CORS_ORIGIN_WHITELIST', 'https://localhost', _AS_LIST) CORS_ORIGIN_WHITELIST = list(filter(None, environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' ')))
CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in _environ_get_and_map('CORS_ORIGIN_REGEX_WHITELIST', '', _AS_LIST)] CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in list(filter(None, environ.get('CORS_ORIGIN_REGEX_WHITELIST', '').split(' ')))]
# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag.
# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like:
# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev
CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', '').split(' ')))
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
# sensitive information about your installation. Only enable debugging while performing testing. # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
# Never enable debugging on a production system. # on a production system.
DEBUG = _environ_get_and_map('DEBUG', 'False', _AS_BOOL) DEBUG = environ.get('DEBUG', 'False').lower() == 'true'
# This parameter serves as a safeguard to prevent some potentially dangerous behavior,
# such as generating new database schema migrations.
# Set this to True only if you are actively developing the NetBox code base.
DEVELOPER = _environ_get_and_map('DEVELOPER', 'False', _AS_BOOL)
# Email settings # Email settings
EMAIL = { EMAIL = {
'SERVER': environ.get('EMAIL_SERVER', 'localhost'), 'SERVER': environ.get('EMAIL_SERVER', 'localhost'),
'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT), 'PORT': int(environ.get('EMAIL_PORT', 25)),
'USERNAME': environ.get('EMAIL_USERNAME', ''), 'USERNAME': environ.get('EMAIL_USERNAME', ''),
'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')), 'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')),
'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL), 'USE_SSL': environ.get('EMAIL_USE_SSL', 'False').lower() == 'true',
'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL), 'USE_TLS': environ.get('EMAIL_USE_TLS', 'False').lower() == 'true',
'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''), 'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''),
'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''), 'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''),
'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # seconds 'TIMEOUT': int(environ.get('EMAIL_TIMEOUT', 10)), # seconds
'FROM_EMAIL': environ.get('EMAIL_FROM', ''), 'FROM_EMAIL': environ.get('EMAIL_FROM', ''),
} }
# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table # Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. # (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
if 'ENFORCE_GLOBAL_UNIQUE' in environ: ENFORCE_GLOBAL_UNIQUE = environ.get('ENFORCE_GLOBAL_UNIQUE', 'False').lower() == 'true'
ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL)
# By default, netbox sends census reporting data using a single HTTP request each time a worker starts.
# This data enables the project maintainers to estimate how many NetBox deployments exist and track the adoption of new versions over time.
# The only data reported by this function are the NetBox version, Python version, and a pseudorandom unique identifier.
# To opt out of census reporting, set CENSUS_REPORTING_ENABLED to False.
if 'CENSUS_REPORTING_ENABLED' in environ:
CENSUS_REPORTING_ENABLED = _environ_get_and_map('CENSUS_REPORTING_ENABLED', None, _AS_BOOL)
# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models. # by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
EXEMPT_VIEW_PERMISSIONS = _environ_get_and_map('EXEMPT_VIEW_PERMISSIONS', '', _AS_LIST) EXEMPT_VIEW_PERMISSIONS = list(filter(None, environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' ')))
# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks).
HTTP_PROXIES = {
'http': environ.get('HTTP_PROXY', None),
'https': environ.get('HTTPS_PROXY', None),
}
# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing
# NetBox from an internal IP.
INTERNAL_IPS = _environ_get_and_map('INTERNAL_IPS', '127.0.0.1 ::1', _AS_LIST)
# Enable GraphQL API. # Enable GraphQL API.
if 'GRAPHQL_ENABLED' in environ: GRAPHQL_ENABLED = environ.get('GRAPHQL_ENABLED', 'True').lower() == 'true'
GRAPHQL_ENABLED = _environ_get_and_map('GRAPHQL_ENABLED', None, _AS_BOOL)
# # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
# # https://docs.djangoproject.com/en/stable/topics/logging/ # https://docs.djangoproject.com/en/stable/topics/logging/
# LOGGING = {} LOGGING = {}
# Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain # Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
# authenticated to NetBox indefinitely. # are permitted to access most data in NetBox (excluding secrets) but not make any changes.
LOGIN_PERSISTENCE = _environ_get_and_map('LOGIN_PERSISTENCE', 'False', _AS_BOOL) LOGIN_REQUIRED = environ.get('LOGIN_REQUIRED', 'False').lower() == 'true'
# When enabled, only authenticated users are permitted to access any part of NetBox.
# Disabling this will allow unauthenticated users to access most areas of NetBox (but not make any changes).
LOGIN_REQUIRED = _environ_get_and_map('LOGIN_REQUIRED', 'True', _AS_BOOL)
# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to # The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
# re-authenticate. (Default: 1209600 [14 days]) # re-authenticate. (Default: 1209600 [14 days])
LOGIN_TIMEOUT = _environ_get_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT) LOGIN_TIMEOUT = int(environ.get('LOGIN_TIMEOUT', 1209600))
# Setting this to True will display a "maintenance mode" banner at the top of every page. # Setting this to True will display a "maintenance mode" banner at the top of every page.
if 'MAINTENANCE_MODE' in environ: MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
MAINTENANCE_MODE = _environ_get_and_map('MAINTENANCE_MODE', None, _AS_BOOL)
# Maps provider # Maps provider
if 'MAPS_URL' in environ: MAPS_URL = environ.get('MAPS_URL', None)
MAPS_URL = environ.get('MAPS_URL', None)
# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. # An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request # "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
# all objects by specifying "?limit=0". # all objects by specifying "?limit=0".
if 'MAX_PAGE_SIZE' in environ: MAX_PAGE_SIZE = int(environ.get('MAX_PAGE_SIZE', 1000))
MAX_PAGE_SIZE = _environ_get_and_map('MAX_PAGE_SIZE', None, _AS_INT)
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
# the default value of this setting is derived from the installed location. # the default value of this setting is derived from the installed location.
MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media')) MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media'))
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
METRICS_ENABLED = _environ_get_and_map('METRICS_ENABLED', 'False', _AS_BOOL) METRICS_ENABLED = environ.get('METRICS_ENABLED', 'False').lower() == 'true'
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
NAPALM_USERNAME = environ.get('NAPALM_USERNAME', '')
NAPALM_PASSWORD = _read_secret('napalm_password', environ.get('NAPALM_PASSWORD', ''))
# NAPALM timeout (in seconds). (Default: 30)
NAPALM_TIMEOUT = int(environ.get('NAPALM_TIMEOUT', 30))
# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
# be provided as a dictionary.
NAPALM_ARGS = {}
# Determine how many objects to display per page within a list. (Default: 50) # Determine how many objects to display per page within a list. (Default: 50)
if 'PAGINATE_COUNT' in environ: PAGINATE_COUNT = int(environ.get('PAGINATE_COUNT', 50))
PAGINATE_COUNT = _environ_get_and_map('PAGINATE_COUNT', None, _AS_INT)
# # Enable installed plugins. Add the name of each plugin to the list. # Enable installed plugins. Add the name of each plugin to the list.
# PLUGINS = [] PLUGINS = []
# # Plugins configuration settings. These settings are used by various plugins that the user may have installed. # Plugins configuration settings. These settings are used by various plugins that the user may have installed.
# # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
# PLUGINS_CONFIG = { PLUGINS_CONFIG = {
# } }
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
# prefer IPv4 instead. # prefer IPv4 instead.
if 'PREFER_IPV4' in environ: PREFER_IPV4 = environ.get('PREFER_IPV4', 'False').lower() == 'true'
PREFER_IPV4 = _environ_get_and_map('PREFER_IPV4', None, _AS_BOOL)
# The default value for the amperage field when creating new power feeds.
if 'POWERFEED_DEFAULT_AMPERAGE' in environ:
POWERFEED_DEFAULT_AMPERAGE = _environ_get_and_map('POWERFEED_DEFAULT_AMPERAGE', None, _AS_INT)
# The default value (percentage) for the max_utilization field when creating new power feeds.
if 'POWERFEED_DEFAULT_MAX_UTILIZATION' in environ:
POWERFEED_DEFAULT_MAX_UTILIZATION = _environ_get_and_map('POWERFEED_DEFAULT_MAX_UTILIZATION', None, _AS_INT)
# The default value for the voltage field when creating new power feeds.
if 'POWERFEED_DEFAULT_VOLTAGE' in environ:
POWERFEED_DEFAULT_VOLTAGE = _environ_get_and_map('POWERFEED_DEFAULT_VOLTAGE', None, _AS_INT)
# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. # Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ: RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22))
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT) RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220))
if 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH' in environ:
RACK_ELEVATION_DEFAULT_UNIT_WIDTH = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', None, _AS_INT)
# Remote authentication support # Remote authentication support
REMOTE_AUTH_AUTO_CREATE_GROUPS = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_GROUPS', 'False', _AS_BOOL) REMOTE_AUTH_ENABLED = environ.get('REMOTE_AUTH_ENABLED', 'False').lower() == 'true'
REMOTE_AUTH_AUTO_CREATE_USER = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'False', _AS_BOOL) REMOTE_AUTH_BACKEND = environ.get('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
REMOTE_AUTH_BACKEND = _environ_get_and_map('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend', _AS_LIST)
REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST)
# REMOTE_AUTH_DEFAULT_PERMISSIONS = {} # dicts can't be configured via environment variables. See extra.py instead.
REMOTE_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL)
REMOTE_AUTH_GROUP_HEADER = _environ_get_and_map('REMOTE_AUTH_GROUP_HEADER', 'HTTP_REMOTE_USER_GROUP')
REMOTE_AUTH_GROUP_SEPARATOR = _environ_get_and_map('REMOTE_AUTH_GROUP_SEPARATOR', '|')
REMOTE_AUTH_GROUP_SYNC_ENABLED = _environ_get_and_map('REMOTE_AUTH_GROUP_SYNC_ENABLED', 'False', _AS_BOOL)
REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER') REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
REMOTE_AUTH_USER_EMAIL = environ.get('REMOTE_AUTH_USER_EMAIL', 'HTTP_REMOTE_USER_EMAIL') REMOTE_AUTH_AUTO_CREATE_USER = environ.get('REMOTE_AUTH_AUTO_CREATE_USER', 'True').lower() == 'true'
REMOTE_AUTH_USER_FIRST_NAME = environ.get('REMOTE_AUTH_USER_FIRST_NAME', 'HTTP_REMOTE_USER_FIRST_NAME') REMOTE_AUTH_DEFAULT_GROUPS = list(filter(None, environ.get('REMOTE_AUTH_DEFAULT_GROUPS', '').split(' ')))
REMOTE_AUTH_USER_LAST_NAME = environ.get('REMOTE_AUTH_USER_LAST_NAME', 'HTTP_REMOTE_USER_LAST_NAME')
REMOTE_AUTH_SUPERUSER_GROUPS = _environ_get_and_map('REMOTE_AUTH_SUPERUSER_GROUPS', '', _AS_LIST)
REMOTE_AUTH_SUPERUSERS = _environ_get_and_map('REMOTE_AUTH_SUPERUSERS', '', _AS_LIST)
REMOTE_AUTH_STAFF_GROUPS = _environ_get_and_map('REMOTE_AUTH_STAFF_GROUPS', '', _AS_LIST)
REMOTE_AUTH_STAFF_USERS = _environ_get_and_map('REMOTE_AUTH_STAFF_USERS', '', _AS_LIST)
# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the # This repository is used to check whether there is a new release of NetBox available. Set to None to disable the
# version check or use the URL below to check for release in the official NetBox repository. # version check or use the URL below to check for release in the official NetBox repository.
# https://api.github.com/repos/netbox-community/netbox/releases
RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None) RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None)
# RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases'
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
# this setting is derived from the installed location.
REPORTS_ROOT = environ.get('REPORTS_ROOT', '/etc/netbox/reports')
# Maximum execution time for background tasks, in seconds. # Maximum execution time for background tasks, in seconds.
RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT) RQ_DEFAULT_TIMEOUT = int(environ.get('RQ_DEFAULT_TIMEOUT', 300))
# The name to use for the csrf token cookie. # The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of
CSRF_COOKIE_NAME = environ.get('CSRF_COOKIE_NAME', 'csrftoken') # this setting is derived from the installed location.
SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag.
# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like:
# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev
CSRF_TRUSTED_ORIGINS = _environ_get_and_map('CSRF_TRUSTED_ORIGINS', '', _AS_LIST)
# The name to use for the session cookie.
SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME', 'sessionid')
# If true, the `includeSubDomains` directive will be included in the HTTP Strict Transport Security (HSTS) header.
# This directive instructs the browser to apply the HSTS policy to all subdomains of the current domain.
SECURE_HSTS_INCLUDE_SUBDOMAINS = _environ_get_and_map('SECURE_HSTS_INCLUDE_SUBDOMAINS', 'False', _AS_BOOL)
# If true, the `preload` directive will be included in the HTTP Strict Transport Security (HSTS) header.
# This directive instructs the browser to preload the site in HTTPS. Browsers that use the HSTS preload list will force the
# site to be accessed via HTTPS even if the user types HTTP in the address bar.
SECURE_HSTS_PRELOAD = _environ_get_and_map('SECURE_HSTS_PRELOAD', 'False', _AS_BOOL)
# If set to a non-zero integer value, the SecurityMiddleware sets the HTTP Strict Transport Security (HSTS) header on all
# responses that do not already have it. This will instruct the browser that the website must be accessed via HTTPS,
# blocking any HTTP request.
SECURE_HSTS_SECONDS = _environ_get_and_map('SECURE_HSTS_SECONDS', 0, _AS_INT)
# If true, all non-HTTPS requests will be automatically redirected to use HTTPS.
SECURE_SSL_REDIRECT = _environ_get_and_map('SECURE_SSL_REDIRECT', 'False', _AS_BOOL)
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path. # database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
SESSION_FILE_PATH = environ.get('SESSION_FILE_PATH', environ.get('SESSIONS_ROOT', None)) SESSION_FILE_PATH = environ.get('SESSIONS_ROOT', None)
# Time zone (default: UTC) # Time zone (default: UTC)
TIME_ZONE = environ.get('TIME_ZONE', 'UTC') TIME_ZONE = environ.get('TIME_ZONE', 'UTC')
# If true disables miscellaneous functionality which depends on access to the Internet. # Date/time formatting. See the following link for supported formats:
ISOLATED_DEPLOYMENT = _environ_get_and_map('ISOLATED_DEPLOYMENT', 'False', _AS_BOOL) # https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y')
SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d')
TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a')
SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s')
DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a')
SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i')

View file

@ -15,6 +15,12 @@
# 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp', # 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
# ) # )
## NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
## be provided as a dictionary.
# NAPALM_ARGS = {}
## Enable installed plugins. Add the name of each plugin to the list. ## Enable installed plugins. Add the name of each plugin to the list.
# from netbox.configuration.configuration import PLUGINS # from netbox.configuration.configuration import PLUGINS
# PLUGINS.append('my_plugin') # PLUGINS.append('my_plugin')

View file

@ -31,12 +31,9 @@ AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_REFERRALS: 0 ldap.OPT_REFERRALS: 0
} }
AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = environ.get('AUTH_LDAP_BIND_AS_AUTHENTICATING_USER', 'False').lower() == 'true' # Set the DN and password for the NetBox service account.
AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '')
# Set the DN and password for the NetBox service account if needed. AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', ''))
if not AUTH_LDAP_BIND_AS_AUTHENTICATING_USER:
AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '')
AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', ''))
# Set a string template that describes any users distinguished name based on the username. # Set a string template that describes any users distinguished name based on the username.
AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None) AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None)
@ -49,38 +46,20 @@ AUTH_LDAP_START_TLS = environ.get('AUTH_LDAP_START_TLS', 'False').lower() == 'tr
# ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true' LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true'
# Include this setting if you want to validate the LDAP server certificates against a CA certificate directory on your server
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, LDAP_CA_CERT_DIR)
LDAP_CA_CERT_DIR = environ.get('LDAP_CA_CERT_DIR', None)
# Include this setting if you want to validate the LDAP server certificates against your own CA.
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, LDAP_CA_CERT_FILE)
LDAP_CA_CERT_FILE = environ.get('LDAP_CA_CERT_FILE', None)
AUTH_LDAP_USER_SEARCH_BASEDN = environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '') AUTH_LDAP_USER_SEARCH_BASEDN = environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '')
AUTH_LDAP_USER_SEARCH_ATTR = environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName') AUTH_LDAP_USER_SEARCH_ATTR = environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName')
AUTH_LDAP_USER_SEARCH_FILTER: str = environ.get(
'AUTH_LDAP_USER_SEARCH_FILTER', f'({AUTH_LDAP_USER_SEARCH_ATTR}=%(user)s)'
)
AUTH_LDAP_USER_SEARCH = LDAPSearch( AUTH_LDAP_USER_SEARCH = LDAPSearch(
AUTH_LDAP_USER_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, AUTH_LDAP_USER_SEARCH_FILTER AUTH_LDAP_USER_SEARCH_BASEDN,
ldap.SCOPE_SUBTREE,
"(" + AUTH_LDAP_USER_SEARCH_ATTR + "=%(user)s)"
) )
# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group # This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
# heirarchy. # heirarchy.
AUTH_LDAP_GROUP_SEARCH_BASEDN = environ.get('AUTH_LDAP_GROUP_SEARCH_BASEDN', '') AUTH_LDAP_GROUP_SEARCH_BASEDN = environ.get('AUTH_LDAP_GROUP_SEARCH_BASEDN', '')
AUTH_LDAP_GROUP_SEARCH_CLASS = environ.get('AUTH_LDAP_GROUP_SEARCH_CLASS', 'group') AUTH_LDAP_GROUP_SEARCH_CLASS = environ.get('AUTH_LDAP_GROUP_SEARCH_CLASS', 'group')
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SUBTREE,
AUTH_LDAP_GROUP_SEARCH_FILTER: str = environ.get( "(objectClass=" + AUTH_LDAP_GROUP_SEARCH_CLASS + ")")
'AUTH_LDAP_GROUP_SEARCH_FILTER', f'(objectclass={AUTH_LDAP_GROUP_SEARCH_CLASS})'
)
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
)
AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType')) AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType'))
# Define a group required to login. # Define a group required to login.
@ -109,6 +88,3 @@ AUTH_LDAP_USER_ATTR_MAP = {
"last_name": environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'), "last_name": environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'),
"email": environ.get('AUTH_LDAP_ATTR_MAIL', 'mail') "email": environ.get('AUTH_LDAP_ATTR_MAIL', 'mail')
} }
# Update user object with the latest values from the LDAP directory every time the user logs in.
AUTH_LDAP_ALWAYS_UPDATE_USER = environ.get('AUTH_LDAP_ALWAYS_UPDATE_USER', 'True').lower() == 'true'

View file

@ -1,22 +1,5 @@
version: '3.4'
services: services:
netbox: netbox:
ports: ports:
- "8000:8080" - 8000:8080
# If you want the Nginx unit status page visible from the
# outside of the container add the following port mapping:
# - "8001:8081"
# healthcheck:
# Time for which the health check can fail after the container is started.
# This depends mostly on the performance of your database. On the first start,
# when all tables need to be created the start_period should be higher than on
# subsequent starts. For the first start after major version upgrades of NetBox
# the start_period might also need to be set higher.
# Default value in our docker-compose.yml is 60s
# start_period: 90s
# environment:
# SKIP_SUPERUSER: "false"
# SUPERUSER_API_TOKEN: ""
# SUPERUSER_EMAIL: ""
# SUPERUSER_NAME: ""
# SUPERUSER_PASSWORD: ""

View file

@ -1,5 +0,0 @@
services:
netbox:
ports:
- "127.0.0.1:8000:8080"

View file

@ -1,61 +1,39 @@
version: '3.4'
services: services:
netbox: &netbox netbox:
image: ${IMAGE-docker.io/netboxcommunity/netbox:latest} image: ${IMAGE-netboxcommunity/netbox:latest}
depends_on: depends_on:
postgres: - postgres
condition: service_healthy - redis
redis: - redis-cache
condition: service_healthy
redis-cache:
condition: service_healthy
env_file: env/netbox.env env_file: env/netbox.env
user: "unit:root" environment:
SKIP_STARTUP_SCRIPTS: ${SKIP_STARTUP_SCRIPTS-false}
user: 'unit:root'
volumes: volumes:
- ./test-configuration/test_config.py:/etc/netbox/config/test_config.py:z,ro - ./startup_scripts:/opt/netbox/startup_scripts:z,ro
healthcheck: - ./${INITIALIZERS_DIR-initializers}:/opt/netbox/initializers:z,ro
test: curl -f http://localhost:8080/login/ || exit 1 - ./configuration:/etc/netbox/config:z,ro
start_period: ${NETBOX_START_PERIOD-120s} - ./reports:/etc/netbox/reports:z,ro
timeout: 3s - ./scripts:/etc/netbox/scripts:z,ro
interval: 15s - netbox-media-files:/opt/netbox/netbox/media:z
netbox-worker:
<<: *netbox
command:
- /opt/netbox/venv/bin/python
- /opt/netbox/netbox/manage.py
- rqworker
healthcheck:
test: ps -aux | grep -v grep | grep -q rqworker || exit 1
start_period: 40s
timeout: 3s
interval: 15s
postgres: postgres:
image: docker.io/postgres:17-alpine image: postgres:14-alpine
env_file: env/postgres.env env_file: env/postgres.env
healthcheck: redis:
test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER ## $$ because of docker-compose image: redis:7-alpine
start_period: 20s
interval: 1s
timeout: 5s
retries: 5
redis: &redis
image: docker.io/valkey/valkey:8.1-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env
- valkey-server --save "" --appendonly no --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
env_file: env/redis.env env_file: env/redis.env
healthcheck:
test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]'
start_period: 5s
timeout: 3s
interval: 1s
retries: 5
redis-cache: redis-cache:
<<: *redis image: redis:7-alpine
command:
- sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env
- redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
env_file: env/redis-cache.env env_file: env/redis-cache.env
volumes: volumes:
netbox-media-files: netbox-media-files:
driver: local driver: local

View file

@ -1,87 +1,67 @@
version: '3.4'
services: services:
netbox: &netbox netbox: &netbox
image: docker.io/netboxcommunity/netbox:${VERSION-v4.4-3.4.1} image: netboxcommunity/netbox:${VERSION-v3.2-2.0.0}
depends_on: depends_on:
- postgres - postgres
- redis - redis
- redis-cache - redis-cache
- netbox-worker
env_file: env/netbox.env env_file: env/netbox.env
user: "unit:root" user: 'unit:root'
healthcheck:
test: curl -f http://localhost:8080/login/ || exit 1
start_period: 90s
timeout: 3s
interval: 15s
volumes: volumes:
- ./configuration:/etc/netbox/config:z,ro - ./startup_scripts:/opt/netbox/startup_scripts:z,ro
- netbox-media-files:/opt/netbox/netbox/media:rw - ./initializers:/opt/netbox/initializers:z,ro
- netbox-reports-files:/opt/netbox/netbox/reports:rw - ./configuration:/etc/netbox/config:z,ro
- netbox-scripts-files:/opt/netbox/netbox/scripts:rw - ./reports:/etc/netbox/reports:z,ro
- ./scripts:/etc/netbox/scripts:z,ro
- netbox-media-files:/opt/netbox/netbox/media:z
netbox-worker: netbox-worker:
<<: *netbox <<: *netbox
depends_on: depends_on:
netbox: - redis
condition: service_healthy - postgres
command: command:
- /opt/netbox/venv/bin/python - /opt/netbox/venv/bin/python
- /opt/netbox/netbox/manage.py - /opt/netbox/netbox/manage.py
- rqworker - rqworker
healthcheck: netbox-housekeeping:
test: ps -aux | grep -v grep | grep -q rqworker || exit 1 <<: *netbox
start_period: 20s depends_on:
timeout: 3s - redis
interval: 15s - postgres
command:
- /opt/netbox/housekeeping.sh
# postgres # postgres
postgres: postgres:
image: docker.io/postgres:17-alpine image: postgres:14-alpine
healthcheck:
test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER
start_period: 20s
timeout: 30s
interval: 10s
retries: 5
env_file: env/postgres.env env_file: env/postgres.env
volumes: volumes:
- netbox-postgres-data:/var/lib/postgresql/data - netbox-postgres-data:/var/lib/postgresql/data
# redis # redis
redis: redis:
image: docker.io/valkey/valkey:8.1-alpine image: redis:7-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env
- valkey-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
healthcheck: &redis-healthcheck
test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]'
start_period: 5s
timeout: 3s
interval: 1s
retries: 5
env_file: env/redis.env env_file: env/redis.env
volumes: volumes:
- netbox-redis-data:/data - netbox-redis-data:/data
redis-cache: redis-cache:
image: docker.io/valkey/valkey:8.1-alpine image: redis:7-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env
- valkey-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose - redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
healthcheck: *redis-healthcheck
env_file: env/redis-cache.env env_file: env/redis-cache.env
volumes:
- netbox-redis-cache-data:/data
volumes: volumes:
netbox-media-files: netbox-media-files:
driver: local driver: local
netbox-postgres-data: netbox-postgres-data:
driver: local driver: local
netbox-redis-cache-data:
driver: local
netbox-redis-data: netbox-redis-data:
driver: local driver: local
netbox-reports-files:
driver: local
netbox-scripts-files:
driver: local

View file

@ -46,8 +46,6 @@ if ! ./manage.py migrate --check >/dev/null 2>&1; then
./manage.py remove_stale_contenttypes --no-input ./manage.py remove_stale_contenttypes --no-input
echo "⚙️ Removing expired user sessions" echo "⚙️ Removing expired user sessions"
./manage.py clearsessions ./manage.py clearsessions
echo "⚙️ Building search index (lazy)"
./manage.py reindex --lazy
fi fi
# Create Superuser if required # Create Superuser if required
@ -72,24 +70,22 @@ else
fi fi
./manage.py shell --interface python <<END ./manage.py shell --interface python <<END
from users.models import Token, User from django.contrib.auth.models import User
from users.models import Token
if not User.objects.filter(username='${SUPERUSER_NAME}'): if not User.objects.filter(username='${SUPERUSER_NAME}'):
u = User.objects.create_superuser('${SUPERUSER_NAME}', '${SUPERUSER_EMAIL}', '${SUPERUSER_PASSWORD}') u=User.objects.create_superuser('${SUPERUSER_NAME}', '${SUPERUSER_EMAIL}', '${SUPERUSER_PASSWORD}')
Token.objects.create(user=u, key='${SUPERUSER_API_TOKEN}') Token.objects.create(user=u, key='${SUPERUSER_API_TOKEN}')
END END
echo "💡 Superuser Username: ${SUPERUSER_NAME}, E-Mail: ${SUPERUSER_EMAIL}" echo "💡 Superuser Username: ${SUPERUSER_NAME}, E-Mail: ${SUPERUSER_EMAIL}"
fi fi
./manage.py shell --interface python <<END # Run the startup scripts (and initializers)
from users.models import Token if [ "$SKIP_STARTUP_SCRIPTS" == "true" ]; then
try: echo "↩️ Skipping startup scripts"
old_default_token = Token.objects.get(key="0123456789abcdef0123456789abcdef01234567") else
if old_default_token: echo "import runpy; runpy.run_path('../startup_scripts')" | ./manage.py shell --interface python
print("⚠️ Warning: You have the old default admin API token in your database. This token is widely known; please remove it. Log in as your superuser and check API Tokens in your user menu.") fi
except Token.DoesNotExist:
pass
END
echo "✅ Initialisation is done." echo "✅ Initialisation is done."

8
docker/housekeeping.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash
SECONDS=${HOUSEKEEPING_INTERVAL:=86400}
echo "Interval set to ${SECONDS} seconds"
while true; do
date
/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py housekeeping
sleep "${SECONDS}s"
done

View file

@ -1,7 +1,6 @@
#!/bin/bash #!/bin/bash
UNIT_CONFIG="${UNIT_CONFIG-/etc/unit/nginx-unit.json}" UNIT_CONFIG="${UNIT_CONFIG-/etc/unit/nginx-unit.json}"
# Also used in "nginx-unit.json"
UNIT_SOCKET="/opt/unit/unit.sock" UNIT_SOCKET="/opt/unit/unit.sock"
load_configuration() { load_configuration() {
@ -36,7 +35,7 @@ load_configuration() {
http://localhost/config http://localhost/config
) )
if [ "$RESP_CODE" != "200" ]; then if [ "$RESP_CODE" != "200" ]; then
echo "⚠️ Could not load Unit configuration" echo "⚠️ Could no load Unit configuration"
kill "$(cat /opt/unit/unit.pid)" kill "$(cat /opt/unit/unit.pid)"
return 1 return 1
fi fi
@ -51,7 +50,7 @@ exec unitd \
--control unix:$UNIT_SOCKET \ --control unix:$UNIT_SOCKET \
--pid /opt/unit/unit.pid \ --pid /opt/unit/unit.pid \
--log /dev/stdout \ --log /dev/stdout \
--statedir /opt/unit/state/ \ --state /opt/unit/state/ \
--tmpdir /opt/unit/tmp/ \ --tmp /opt/unit/tmp/ \
--user unit \ --user unit \
--group root --group root

View file

@ -1,65 +1,27 @@
{ {
"listeners": { "listeners": {
"0.0.0.0:8080": { "*:8080": {
"pass": "routes/main", "pass": "routes"
"forwarded": {
"client_ip": "X-Forwarded-For",
"protocol": "X-Forwarded-Proto",
"source": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}
},
"0.0.0.0:8081": {
"pass": "routes/status",
"forwarded": {
"client_ip": "X-Forwarded-For",
"protocol": "X-Forwarded-Proto",
"source": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}
},
"[::]:8080": {
"pass": "routes/main",
"forwarded": {
"client_ip": "X-Forwarded-For",
"protocol": "X-Forwarded-Proto",
"source": ["fc00::/7", "fe80::/10"]
}
},
"[::]:8081": {
"pass": "routes/status",
"forwarded": {
"client_ip": "X-Forwarded-For",
"protocol": "X-Forwarded-Proto",
"source": ["fc00::/7", "fe80::/10"]
}
} }
}, },
"routes": {
"main": [ "routes": [
{ {
"match": { "match": {
"uri": "/static/*" "uri": "/static/*"
},
"action": {
"share": "/opt/netbox/netbox${uri}"
}
}, },
{ "action": {
"action": { "share": "/opt/netbox/netbox${uri}"
"pass": "applications/netbox"
}
} }
], },
"status": [
{ {
"match": { "action": {
"uri": "/status/*" "pass": "applications/netbox"
},
"action": {
"proxy": "http://unix:/opt/unit/unit.sock"
}
} }
] }
}, ],
"applications": { "applications": {
"netbox": { "netbox": {
"type": "python 3", "type": "python 3",
@ -73,10 +35,6 @@
} }
} }
}, },
"access_log": "/dev/stdout",
"settings": { "access_log": "/dev/stdout"
"http": {
"max_body_size": 104857600
}
}
} }

View file

@ -1 +0,0 @@
deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] http://packages.nginx.org/unit/ubuntu/ noble unit

15
env/netbox.env vendored
View file

@ -1,4 +1,3 @@
API_TOKEN_PEPPER_1=Qy+F=OTeGskWQ(wTMgjc+NPPlz6YwFXY=KHIIg=wpYXT&e(6u8
CORS_ORIGIN_ALLOW_ALL=True CORS_ORIGIN_ALLOW_ALL=True
DB_HOST=postgres DB_HOST=postgres
DB_NAME=netbox DB_NAME=netbox
@ -16,8 +15,13 @@ EMAIL_USERNAME=netbox
EMAIL_USE_SSL=false EMAIL_USE_SSL=false
EMAIL_USE_TLS=false EMAIL_USE_TLS=false
GRAPHQL_ENABLED=true GRAPHQL_ENABLED=true
HOUSEKEEPING_INTERVAL=86400
MAX_PAGE_SIZE=1000
MEDIA_ROOT=/opt/netbox/netbox/media MEDIA_ROOT=/opt/netbox/netbox/media
METRICS_ENABLED=false METRICS_ENABLED=false
NAPALM_PASSWORD=
NAPALM_TIMEOUT=10
NAPALM_USERNAME=
REDIS_CACHE_DATABASE=1 REDIS_CACHE_DATABASE=1
REDIS_CACHE_HOST=redis-cache REDIS_CACHE_HOST=redis-cache
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false
@ -29,6 +33,11 @@ REDIS_INSECURE_SKIP_TLS_VERIFY=false
REDIS_PASSWORD=H733Kdjndks81 REDIS_PASSWORD=H733Kdjndks81
REDIS_SSL=false REDIS_SSL=false
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X' SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
SKIP_SUPERUSER=true SKIP_STARTUP_SCRIPTS=false
SKIP_SUPERUSER=false
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
SUPERUSER_EMAIL=admin@example.com
SUPERUSER_NAME=admin
SUPERUSER_PASSWORD=admin
WEBHOOKS_ENABLED=true WEBHOOKS_ENABLED=true

View file

@ -0,0 +1,7 @@
# - prefix: 10.0.0.0/16
# rir: RFC1918
# tenant: tenant1
# - prefix: fd00:ccdd::/32
# rir: RFC4193 ULA
# - prefix: 2001:db8::/32
# rir: RFC3849

7
initializers/asns.yml Normal file
View file

@ -0,0 +1,7 @@
# - asn: 1
# rir: RFC1918
# tenant: tenant1
# - asn: 2
# rir: RFC4193 ULA
# - asn: 3
# rir: RFC3849

71
initializers/cables.yml Normal file
View file

@ -0,0 +1,71 @@
# # Required parameters for termination X ('a' or 'b'):
# #
# # ```
# # termination_x_name -> name of interface
# # termination_x_device -> name of the device interface belongs to
# # termination_x_class -> required if different than 'Interface' which is the default
# # ```
# #
# # Supported termination classes: Interface, ConsolePort, ConsoleServerPort, FrontPort, RearPort, PowerPort, PowerOutlet
# #
# #
# # If a termination is a circuit then the required parameter is termination_x_circuit.
# # Required parameters for a circuit termination:
# #
# # ```
# # termination_x_circuit:
# # term_side -> termination side of a circuit. Must be A or B
# # cid -> circuit ID value
# # site OR provider_network -> name of Site or ProviderNetwork respectively. If both provided, Site takes precedence
# # ```
# #
# # If a termination is a power feed then the required parameter is termination_x_feed.
# #
# # ```
# # termination_x_feed:
# # name -> name of the PowerFeed object
# # power_panel:
# # name -> name of the PowerPanel the PowerFeed is attached to
# # site -> name of the Site in which the PowerPanel is present
# # ```
# #
# # Any other Cable parameters supported by Netbox are supported as the top level keys, e.g. 'type', 'status', etc.
# #
# # - termination_a_name: console
# # termination_a_device: spine
# # termination_a_class: ConsolePort
# # termination_b_name: tty9
# # termination_b_device: console-server
# # termination_b_class: ConsoleServerPort
# # type: cat6
# #
# - termination_a_name: to-server02
# termination_a_device: server01
# termination_b_name: to-server01
# termination_b_device: server02
# status: planned
# type: mmf
# - termination_a_name: eth0
# termination_a_device: server02
# termination_b_circuit:
# term_side: A
# cid: Circuit_ID-1
# site: AMS 1
# type: cat6
# - termination_a_name: psu0
# termination_a_device: server04
# termination_a_class: PowerPort
# termination_b_feed:
# name: power feed 1
# power_panel:
# name: power panel AMS 1
# site: AMS 1
# - termination_a_name: outlet1
# termination_a_device: server04
# termination_a_class: PowerOutlet
# termination_b_name: psu1
# termination_b_device: server04
# termination_b_class: PowerPort

View file

@ -0,0 +1,6 @@
# - name: VPLS
# slug: vpls
# - name: MPLS
# slug: mpls
# - name: Internet
# slug: internet

View file

@ -0,0 +1,7 @@
# - cid: Circuit_ID-1
# provider: Provider1
# type: Internet
# tenant: tenant1
# - cid: Circuit_ID-2
# provider: Provider2
# type: MPLS

View file

@ -0,0 +1,4 @@
# - name: Group 1
# slug: group-1
# - name: Group 2
# slug: group-2

View file

@ -0,0 +1,2 @@
# - name: Hyper-V
# slug: hyper-v

View file

@ -0,0 +1,7 @@
# - name: cluster1
# type: Hyper-V
# group: Group 1
# tenant: tenant1
# - name: cluster2
# type: Hyper-V
# site: SING 1

View file

@ -0,0 +1,7 @@
# - name: Network-Team
# slug: network-team
# description: This is a new contact group for the Network-Team
# - name: New Contact Group
# slug: new-contact-group
# description: This is a new contact group sub under of Network-Team
# parent: Network-Team

View file

@ -0,0 +1,3 @@
# - name: New Contact Role
# slug: new-contact-role
# description: This is a new contact role description

20
initializers/contacts.yml Normal file
View file

@ -0,0 +1,20 @@
# - name: Lee Widget
# title: CEO of Widget Corp
# phone: 221-555-1212
# email: widgetCEO@widgetcorp.com
# address: 1200 Nowhere Blvd, Scranton NJ, 555111
# comments: This is a very important contact
# - name: Ali Gator
# group: Network-Team
# title: Consultant for Widget Corp
# phone: 221-555-1213
# email: Consultant@widgetcorp.com
# address: 1200 Nowhere Blvd, Scranton NJ, 555111
# comments: This is a very important contact
# - name: Karlchen Maier
# group: New Contact Group
# title: COO of Widget Corp
# phone: 221-555-1214
# email: Karlchen@widgetcorp.com
# address: 1200 Nowhere Blvd, Scranton NJ, 555111
# comments: This is a very important contact

View file

@ -0,0 +1,93 @@
## Possible Choices:
## type:
## - text
## - integer
## - boolean
## - date
## - url
## - select
## filter_logic:
## - disabled
## - loose
## - exact
##
## Examples:
# text_field:
# type: text
# label: Custom Text
# description: Enter text in a text field.
# required: false
# weight: 0
# on_objects:
# - dcim.models.Device
# - dcim.models.Rack
# - dcim.models.Site
# - dcim.models.DeviceType
# - ipam.models.IPAddress
# - ipam.models.Prefix
# - tenancy.models.Tenant
# - virtualization.models.VirtualMachine
# integer_field:
# type: integer
# label: Custom Number
# description: Enter numbers into an integer field.
# required: true
# filter_logic: loose
# weight: 10
# on_objects:
# - tenancy.models.Tenant
# select_field:
# type: select
# label: Choose between items
# required: false
# filter_logic: exact
# weight: 30
# default: First Item
# on_objects:
# - dcim.models.Device
# choices:
# - First Item
# - Second Item
# - Third Item
# - Fifth Item
# - Fourth Item
# select_field_legacy_format:
# type: select
# label: Choose between items
# required: false
# filter_logic: loose
# weight: 30
# on_objects:
# - dcim.models.Device
# choices:
# - value: A # this is the deprecated format.
# - value: B # we only use it for the tests.
# - value: C # please see above for the new format.
# - value: "D like deprecated"
# weight: 999
# - value: E
# boolean_field:
# type: boolean
# label: Yes Or No?
# required: true
# filter_logic: loose
# default: "false" # important: put "false" in quotes!
# weight: 90
# on_objects:
# - dcim.models.Device
# url_field:
# type: url
# label: Hyperlink
# description: Link to something nice.
# required: true
# filter_logic: disabled
# on_objects:
# - tenancy.models.Tenant
# date_field:
# type: date
# label: Important Date
# required: false
# filter_logic: disabled
# on_objects:
# - dcim.models.Device

View file

@ -0,0 +1,21 @@
## Possible Choices:
## new_window:
## - True
## - False
## content_type:
## - device
## - site
## - any-other-content-type
##
## Examples:
# - name: link_to_repo
# link_text: 'Link to Netbox Docker'
# link_url: 'https://github.com/netbox-community/netbox-docker'
# new_window: False
# content_type: device
# - name: link_to_localhost
# link_text: 'Link to localhost'
# link_url: 'http://localhost'
# new_window: True
# content_type: device

View file

@ -0,0 +1,35 @@
## Possible Choices:
## type:
## - virtual
## - lag
## - 1000base-t
## - ... and many more. See for yourself:
## https://github.com/netbox-community/netbox/blob/295d4f0394b431351c0cb2c3ecc791df68c6c2fb/netbox/dcim/choices.py#L510
##
## Examples:
# - device: server01
# name: ath0
# type: 1000base-t
# lag: ae0
# bridge: br0
# - device: server01
# name: ath1
# type: 1000base-t
# parent: ath0
# - device: server01
# enabled: true
# type: 1000base-x-sfp
# name: to-server02
# - device: server02
# enabled: true
# type: 1000base-x-sfp
# name: to-server01
# - device: server02
# enabled: true
# type: 1000base-t
# name: eth0
# - device: server02
# enabled: true
# type: virtual
# name: loopback

View file

@ -0,0 +1,15 @@
# - name: switch
# slug: switch
# color: Grey
# - name: router
# slug: router
# color: Cyan
# - name: load-balancer
# slug: load-balancer
# color: Red
# - name: server
# slug: server
# color: Blue
# - name: patchpanel
# slug: patchpanel
# color: Black

View file

@ -0,0 +1,57 @@
# - model: Model 1
# manufacturer: Manufacturer 1
# slug: model-1
# u_height: 2
# custom_field_data:
# text_field: Description
# - model: Model 2
# manufacturer: Manufacturer 1
# slug: model-2
# custom_field_data:
# text_field: Description
# - model: Model 3
# manufacturer: Manufacturer 1
# slug: model-3
# is_full_depth: false
# u_height: 0
# custom_field_data:
# text_field: Description
# - model: Other
# manufacturer: No Name
# slug: other
# custom_field_data:
# text_field: Description
# interfaces:
# - name: eth0
# type: 1000base-t
# mgmt_only: True
# - name: eth1
# type: 1000base-t
# console_server_ports:
# - name_template: ttyS[1-48]
# type: rj-45
# power_ports:
# - name_template: psu[0,1]
# type: iec-60320-c14
# maximum_draw: 35
# allocated_draw: 35
# front_ports:
# - name_template: front[1,2]
# type: 8p8c
# rear_port_template: rear[0,1]
# rear_port_position_template: "[1,2]"
# rear_ports:
# - name_template: rear[0,1]
# type: 8p8c
# positions_template: "[3,2]"
# device_bays:
# - name: bay0 # both non-template and template field specified; non-template field takes precedence
# name_template: bay[0-9]
# label: test0
# label_template: test[0-5,9,6-8]
# description: Test description
# power_outlets:
# - name_template: outlet[0,1]
# type: iec-60320-c5
# power_port: psu0
# feed_leg: B

53
initializers/devices.yml Normal file
View file

@ -0,0 +1,53 @@
## Possible Choices:
## face:
## - front
## - rear
## status:
## - offline
## - active
## - planned
## - staged
## - failed
## - inventory
## - decommissioning
##
## Examples:
# - name: server01
# device_role: server
# device_type: Other
# site: AMS 1
# rack: rack-01
# face: front
# position: 1
# custom_field_data:
# text_field: Description
# - name: server02
# device_role: server
# device_type: Other
# site: AMS 2
# rack: rack-02
# face: front
# position: 2
# primary_ip4: 10.1.1.2/24
# primary_ip6: 2001:db8:a000:1::2/64
# custom_field_data:
# text_field: Description
# - name: server03
# device_role: server
# device_type: Other
# site: SING 1
# rack: rack-03
# face: front
# position: 3
# custom_field_data:
# text_field: Description
# - name: server04
# device_role: server
# device_type: Other
# site: SING 1
# location: cage 101
# face: front
# position: 3
# custom_field_data:
# text_field: Description

9
initializers/groups.yml Normal file
View file

@ -0,0 +1,9 @@
# applications:
# users:
# - technical_user
# readers:
# users:
# - reader
# writers:
# users:
# - writer

View file

@ -0,0 +1,44 @@
## Possible Choices:
## status:
## - active
## - reserved
## - deprecated
## - dhcp
## role:
## - loopback
## - secondary
## - anycast
## - vip
## - vrrp
## - hsrp
## - glbp
## - carp
##
## Examples:
# - address: 10.1.1.1/24
# device: server01
# interface: to-server02
# status: active
# vrf: vrf1
# - address: 2001:db8:a000:1::1/64
# device: server01
# interface: to-server02
# status: active
# vrf: vrf1
# - address: 10.1.1.2/24
# device: server02
# interface: to-server01
# status: active
# - address: 2001:db8:a000:1::2/64
# device: server02
# interface: to-server01
# status: active
# - address: 10.1.1.10/24
# description: reserved IP
# status: reserved
# tenant: tenant1
# - address: 2001:db8:a000:1::10/64
# description: reserved IP
# status: reserved
# tenant: tenant1

View file

@ -0,0 +1,3 @@
# - name: cage 101
# slug: cage-101
# site: SING 1

View file

@ -0,0 +1,6 @@
# - name: Manufacturer 1
# slug: manufacturer-1
# - name: Manufacturer 2
# slug: manufacturer-2
# - name: No Name
# slug: no-name

View file

@ -0,0 +1,48 @@
# all.ro:
# actions:
# - view
# description: 'Read Only for All Objects'
# enabled: true
# groups:
# - applications
# - readers
# object_types: all
# users:
# - jdoe
# all.rw:
# actions:
# - add
# - change
# - delete
# - view
# description: 'Read/Write for All Objects'
# enabled: true
# groups:
# - writers
# object_types: all
# network_team.rw:
# actions:
# - add
# - change
# - delete
# - view
# description: "Network Team Permissions"
# enabled: true
# object_types:
# circuits:
# - circuit
# - circuittermination
# - circuittype
# - provider
# dcim: all
# ipam:
# - aggregate
# - ipaddress
# - prefix
# - rir
# - role
# - routetarget
# - service
# - vlan
# - vlangroup
# - vrf

View file

@ -0,0 +1,15 @@
# - name: Platform 1
# slug: platform-1
# manufacturer: Manufacturer 1
# napalm_driver: driver1
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# - name: Platform 2
# slug: platform-2
# manufacturer: Manufacturer 2
# napalm_driver: driver2
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# - name: Platform 3
# slug: platform-3
# manufacturer: No Name
# napalm_driver: driver3
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"

View file

@ -0,0 +1,14 @@
# - name: power feed 1
# power_panel: power panel AMS 1
# voltage: 208
# amperage: 50
# max_utilization: 80
# phase: Single phase
# rack: rack-01
# - name: power feed 2
# power_panel: power panel SING 1
# voltage: 208
# amperage: 50
# max_utilization: 80
# phase: Three-phase
# rack: rack-03

View file

@ -0,0 +1,5 @@
# - name: power panel AMS 1
# site: AMS 1
# - name: power panel SING 1
# site: SING 1
# location: cage 101

View file

@ -0,0 +1,2 @@
# - name: Main Management
# slug: main-management

29
initializers/prefixes.yml Normal file
View file

@ -0,0 +1,29 @@
## Possible Choices:
## status:
## - container
## - active
## - reserved
## - deprecated
##
## Examples:
# - description: prefix1
# prefix: 10.1.1.0/24
# site: AMS 1
# status: active
# tenant: tenant1
# vlan: vlan1
# - description: prefix2
# prefix: 10.1.2.0/24
# site: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2
# is_pool: true
# vrf: vrf2
# - description: ipv6 prefix1
# prefix: 2001:db8:a000:1::/64
# site: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2

View file

@ -0,0 +1,6 @@
# - name: Provider1
# slug: provider1
# asn: 121
# - name: Provider2
# slug: provider2
# asn: 122

View file

@ -0,0 +1,12 @@
# - name: Role 1
# slug: role-1
# color: Pink
# - name: Role 2
# slug: role-2
# color: Cyan
# - name: Role 3
# slug: role-3
# color: Grey
# - name: Role 4
# slug: role-4
# color: Teal

41
initializers/racks.yml Normal file
View file

@ -0,0 +1,41 @@
## Possible Choices:
## width:
## - 19
## - 23
## types:
## - 2-post-frame
## - 4-post-frame
## - 4-post-cabinet
## - wall-frame
## - wall-cabinet
## outer_unit:
## - mm
## - in
##
## Examples:
# - site: AMS 1
# name: rack-01
# role: Role 1
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description
# - site: AMS 2
# name: rack-02
# role: Role 2
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description
# - site: SING 1
# name: rack-03
# location: cage 101
# role: Role 3
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description

10
initializers/regions.yml Normal file
View file

@ -0,0 +1,10 @@
# - name: Singapore
# slug: singapore
# - name: Amsterdam
# slug: amsterdam
# - name: Downtown
# slug: downtown
# parent: Amsterdam
# - name: Suburbs
# slug: suburbs
# parent: Amsterdam

9
initializers/rirs.yml Normal file
View file

@ -0,0 +1,9 @@
# - is_private: true
# name: RFC1918
# slug: rfc1918
# - is_private: true
# name: RFC4193 ULA
# slug: rfc4193-ula
# - is_private: true
# name: RFC3849
# slug: rfc3849

View file

@ -0,0 +1,3 @@
# - name: 65000:1001
# tenant: tenant1
# - name: 65000:1002

15
initializers/services.yml Normal file
View file

@ -0,0 +1,15 @@
# - name: DNS
# protocol: TCP
# ports:
# - 53
# virtual_machine: virtual machine 1
# - name: DNS
# protocol: UDP
# ports:
# - 53
# virtual_machine: virtual machine 1
# - name: MISC
# protocol: UDP
# ports:
# - 4000
# device: server01

30
initializers/sites.yml Normal file
View file

@ -0,0 +1,30 @@
# - name: AMS 1
# slug: ams1
# region: Downtown
# status: active
# facility: Amsterdam 1
# custom_field_data:
# text_field: Description for AMS1
# - name: AMS 2
# slug: ams2
# region: Downtown
# status: active
# facility: Amsterdam 2
# custom_field_data:
# text_field: Description for AMS2
# - name: AMS 3
# slug: ams3
# region: Suburbs
# status: active
# facility: Amsterdam 3
# tenant: tenant1
# custom_field_data:
# text_field: Description for AMS3
# - name: SING 1
# slug: sing1
# region: Singapore
# status: active
# facility: Singapore 1
# tenant: tenant2
# custom_field_data:
# text_field: Description for SING1

12
initializers/tags.yml Normal file
View file

@ -0,0 +1,12 @@
# - name: Tag 1
# slug: tag-1
# color: Pink
# - name: Tag 2
# slug: tag-2
# color: Cyan
# - name: Tag 3
# slug: tag-3
# color: Grey
# - name: Tag 4
# slug: tag-4
# color: Teal

View file

@ -0,0 +1,4 @@
# - name: Tenant Group 1
# slug: tenant-group-1
# - name: Tenant Group 2
# slug: tenant-group-2

5
initializers/tenants.yml Normal file
View file

@ -0,0 +1,5 @@
# - name: tenant1
# slug: tenant1
# - name: tenant2
# slug: tenant2
# group: Tenant Group 2

15
initializers/users.yml Normal file
View file

@ -0,0 +1,15 @@
# technical_user:
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
# reader:
# password: reader
# writer:
# password: writer
# api_token: "" # a token is generated automatically unless the value is explicity set to empty
# jdoe:
# first_name: John
# last_name: Doe
# api_token: 0123456789jdoe789abcdef01234567jdoe
# is_active: True
# is_superuser: False
# is_staff: False
# email: john.doe@example.com

View file

@ -0,0 +1,28 @@
## Possible Choices:
## status:
## - active
## - offline
## - staged
##
## Examples:
# - cluster: cluster1
# comments: VM1
# disk: 200
# memory: 4096
# name: virtual machine 1
# platform: Platform 2
# status: active
# tenant: tenant1
# vcpus: 8
# - cluster: cluster1
# comments: VM2
# disk: 100
# memory: 2048
# name: virtual machine 2
# platform: Platform 2
# primary_ip4: 10.1.1.10/24
# primary_ip6: 2001:db8:a000:1::10/64
# status: active
# tenant: tenant1
# vcpus: 8

View file

@ -0,0 +1,12 @@
# - description: Network Interface 1
# enabled: true
# mac_address: 00:77:77:77:77:77
# mtu: 1500
# name: Network Interface 1
# virtual_machine: virtual machine 1
# - description: Network Interface 2
# enabled: true
# mac_address: 00:55:55:55:55:55
# mtu: 1500
# name: Network Interface 2
# virtual_machine: virtual machine 1

View file

@ -0,0 +1,24 @@
# - name: VLAN group 1
# scope_type: dcim.region
# scope: Amsterdam
# slug: vlan-group-1
# - name: VLAN group 2
# scope_type: dcim.site
# scope: AMS 1
# slug: vlan-group-2
# - name: VLAN group 3
# scope_type: dcim.location
# scope: cage 101
# slug: vlan-group-3
# - name: VLAN group 4
# scope_type: dcim.rack
# scope: rack-01
# slug: vlan-group-4
# - name: VLAN group 5
# scope_type: virtualization.cluster
# scope: cluster1
# slug: vlan-group-5
# - name: VLAN group 6
# scope_type: virtualization.clustergroup
# scope: Group 1
# slug: vlan-group-6

19
initializers/vlans.yml Normal file
View file

@ -0,0 +1,19 @@
## Possible Choices:
## status:
## - active
## - reserved
## - deprecated
##
## Examples:
# - name: vlan1
# site: AMS 1
# status: active
# vid: 5
# role: Main Management
# description: VLAN 5 for MGMT
# - group: VLAN group 2
# name: vlan2
# site: AMS 1
# status: active
# vid: 1300

8
initializers/vrfs.yml Normal file
View file

@ -0,0 +1,8 @@
# - enforce_unique: true
# name: vrf1
# tenant: tenant1
# description: main VRF
# - enforce_unique: true
# name: vrf2
# rd: "6500:6500"
# tenant: tenant2

27
initializers/webhooks.yml Normal file
View file

@ -0,0 +1,27 @@
## Possible Choices:
## object_types:
## - device
## - site
## - any-other-content-type
## types:
## - type_create
## - type_update
## - type_delete
## Examples:
# - name: device_creation
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# - cable
# type_create: True
# - name: device_update
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# type_update: True
# - name: device_delete
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# type_delete: True

View file

@ -0,0 +1,46 @@
from dcim.choices import DeviceStatusChoices
from dcim.models import ConsolePort, Device, PowerPort
from extras.reports import Report
class DeviceConnectionsReport(Report):
description = "Validate the minimum physical connections for each device"
def test_console_connection(self):
# Check that every console port for every active device has a connection defined.
active = DeviceStatusChoices.STATUS_ACTIVE
for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active):
if console_port.connected_endpoint is None:
self.log_failure(
console_port.device,
"No console connection defined for {}".format(console_port.name)
)
elif not console_port.connection_status:
self.log_warning(
console_port.device,
"Console connection for {} marked as planned".format(console_port.name)
)
else:
self.log_success(console_port.device)
def test_power_connections(self):
# Check that every active device has at least two connected power supplies.
for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE):
connected_ports = 0
for power_port in PowerPort.objects.filter(device=device):
if power_port.connected_endpoint is not None:
connected_ports += 1
if not power_port.connection_status:
self.log_warning(
device,
"Power connection for {} marked as planned".format(power_port.name)
)
if connected_ports < 2:
self.log_failure(
device,
"{} connected power supplies found (2 needed)".format(connected_ports)
)
else:
self.log_success(device)

View file

@ -1,6 +1,5 @@
django-auth-ldap==5.2.0 django-auth-ldap==4.1.0
dulwich==0.24.8 django-storages[azure,boto3,dropbox,google,libcloud,sftp]==1.12.3
python3-saml==1.16.0 napalm==4.0.0
--no-binary lxml psycopg2==2.9.3
--no-binary xmlsec ruamel.yaml==0.17.21
sentry-sdk[django]==2.43.0

0
scripts/__init__.py Normal file
View file

View file

@ -0,0 +1,25 @@
import sys
from django.contrib.auth.models import User
from startup_script_utils import load_yaml
from users.models import Token
users = load_yaml("/opt/netbox/initializers/users.yml")
if users is None:
sys.exit()
for username, user_details in users.items():
api_token = user_details.pop("api_token", Token.generate_key())
password = user_details.pop("password", User.objects.make_random_password())
user, created = User.objects.get_or_create(username=username, defaults=user_details)
if created:
user.set_password(password)
user.save()
if api_token:
Token.objects.get_or_create(user=user, key=api_token)
print("👤 Created user", username)

View file

@ -0,0 +1,23 @@
import sys
from startup_script_utils import load_yaml
from users.models import AdminGroup, AdminUser
groups = load_yaml("/opt/netbox/initializers/groups.yml")
if groups is None:
sys.exit()
for groupname, group_details in groups.items():
group, created = AdminGroup.objects.get_or_create(name=groupname)
if created:
print("👥 Created group", groupname)
for username in group_details.get("users", []):
user = AdminUser.objects.get(username=username)
if user:
group.user_set.add(user)
print(" 👤 Assigned user %s to group %s" % (username, group.name))
group.save()

View file

@ -0,0 +1,68 @@
import sys
from django.contrib.contenttypes.models import ContentType
from startup_script_utils import load_yaml
from users.models import AdminGroup, AdminUser, ObjectPermission
object_permissions = load_yaml("/opt/netbox/initializers/object_permissions.yml")
if object_permissions is None:
sys.exit()
for permission_name, permission_details in object_permissions.items():
object_permission, created = ObjectPermission.objects.get_or_create(
name=permission_name,
defaults={
"description": permission_details["description"],
"enabled": permission_details["enabled"],
"actions": permission_details["actions"],
},
)
if permission_details.get("object_types", 0):
object_types = permission_details["object_types"]
if object_types == "all":
object_permission.object_types.set(ContentType.objects.all())
else:
for app_label, models in object_types.items():
if models == "all":
app_models = ContentType.objects.filter(app_label=app_label)
for app_model in app_models:
object_permission.object_types.add(app_model.id)
else:
# There is
for model in models:
object_permission.object_types.add(
ContentType.objects.get(app_label=app_label, model=model)
)
print("🔓 Created object permission", object_permission.name)
if permission_details.get("groups", 0):
for groupname in permission_details["groups"]:
group = AdminGroup.objects.filter(name=groupname).first()
if group:
object_permission.groups.add(group)
print(
" 👥 Assigned group %s object permission of %s"
% (groupname, object_permission.name)
)
if permission_details.get("users", 0):
for username in permission_details["users"]:
user = AdminUser.objects.filter(username=username).first()
if user:
object_permission.users.add(user)
print(
" 👤 Assigned user %s object permission of %s"
% (username, object_permission.name)
)
object_permission.save()

View file

@ -0,0 +1,67 @@
import sys
from extras.models import CustomField
from startup_script_utils import load_yaml
def get_class_for_class_path(class_path):
import importlib
from django.contrib.contenttypes.models import ContentType
module_name, class_name = class_path.rsplit(".", 1)
module = importlib.import_module(module_name)
clazz = getattr(module, class_name)
return ContentType.objects.get_for_model(clazz)
customfields = load_yaml("/opt/netbox/initializers/custom_fields.yml")
if customfields is None:
sys.exit()
for cf_name, cf_details in customfields.items():
custom_field, created = CustomField.objects.get_or_create(name=cf_name)
if created:
if cf_details.get("default", False):
custom_field.default = cf_details["default"]
if cf_details.get("description", False):
custom_field.description = cf_details["description"]
if cf_details.get("label", False):
custom_field.label = cf_details["label"]
for object_type in cf_details.get("on_objects", []):
custom_field.content_types.add(get_class_for_class_path(object_type))
if cf_details.get("required", False):
custom_field.required = cf_details["required"]
if cf_details.get("type", False):
custom_field.type = cf_details["type"]
if cf_details.get("filter_logic", False):
custom_field.filter_logic = cf_details["filter_logic"]
if cf_details.get("weight", -1) >= 0:
custom_field.weight = cf_details["weight"]
if cf_details.get("choices", False):
custom_field.choices = []
for choice_detail in cf_details.get("choices", []):
if isinstance(choice_detail, dict) and "value" in choice_detail:
# legacy mode
print(
f"⚠️ Please migrate the choice '{choice_detail['value']}' of '{cf_name}'"
+ " to the new format, as 'weight' is no longer supported!"
)
custom_field.choices.append(choice_detail["value"])
else:
custom_field.choices.append(choice_detail)
custom_field.save()
print("🔧 Created custom field", cf_name)

View file

@ -0,0 +1,35 @@
import sys
from django.contrib.contenttypes.models import ContentType
from extras.models import CustomLink
from startup_script_utils import load_yaml, split_params
custom_links = load_yaml("/opt/netbox/initializers/custom_links.yml")
if custom_links is None:
sys.exit()
def get_content_type_id(content_type):
try:
return ContentType.objects.get(model=content_type).id
except ContentType.DoesNotExist:
pass
for link in custom_links:
content_type = link.pop("content_type")
link["content_type_id"] = get_content_type_id(content_type)
if link["content_type_id"] is None:
print(
"⚠️ Unable to create Custom Link '{0}': The content_type '{1}' is unknown".format(
link.get("name"), content_type
)
)
continue
matching_params, defaults = split_params(link)
custom_link, created = CustomLink.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔗 Created Custom Link '{0}'".format(custom_link.name))

View file

@ -0,0 +1,24 @@
import sys
from extras.models import Tag
from startup_script_utils import load_yaml, split_params
from utilities.choices import ColorChoices
tags = load_yaml("/opt/netbox/initializers/tags.yml")
if tags is None:
sys.exit()
for params in tags:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
matching_params, defaults = split_params(params)
tag, created = Tag.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created Tag", tag.name)

View file

@ -0,0 +1,36 @@
import sys
from django.contrib.contenttypes.models import ContentType
from extras.models import Webhook
from startup_script_utils import load_yaml, split_params
webhooks = load_yaml("/opt/netbox/initializers/webhooks.yml")
if webhooks is None:
sys.exit()
def get_content_type_id(hook_name, content_type):
try:
return ContentType.objects.get(model=content_type).id
except ContentType.DoesNotExist as ex:
print("⚠️ Webhook '{0}': The object_type '{1}' is unknown.".format(hook_name, content_type))
raise ex
for hook in webhooks:
obj_types = hook.pop("object_types")
try:
obj_type_ids = [get_content_type_id(hook["name"], obj) for obj in obj_types]
except ContentType.DoesNotExist:
continue
matching_params, defaults = split_params(hook)
webhook, created = Webhook.objects.get_or_create(**matching_params, defaults=defaults)
if created:
webhook.content_types.set(obj_type_ids)
webhook.save()
print("🪝 Created Webhook {0}".format(webhook.name))

View file

@ -0,0 +1,16 @@
import sys
from startup_script_utils import load_yaml, split_params
from tenancy.models import TenantGroup
tenant_groups = load_yaml("/opt/netbox/initializers/tenant_groups.yml")
if tenant_groups is None:
sys.exit()
for params in tenant_groups:
matching_params, defaults = split_params(params)
tenant_group, created = TenantGroup.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔳 Created Tenant Group", tenant_group.name)

View file

@ -0,0 +1,34 @@
import sys
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant, TenantGroup
tenants = load_yaml("/opt/netbox/initializers/tenants.yml")
if tenants is None:
sys.exit()
optional_assocs = {"group": (TenantGroup, "name")}
for params in tenants:
custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params)
tenant, created = Tenant.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("👩‍💻 Created Tenant", tenant.name)
set_custom_fields_values(tenant, custom_field_data)

View file

@ -0,0 +1,26 @@
import sys
from dcim.models import Region
from startup_script_utils import load_yaml, split_params
regions = load_yaml("/opt/netbox/initializers/regions.yml")
if regions is None:
sys.exit()
optional_assocs = {"parent": (Region, "name")}
for params in regions:
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params)
region, created = Region.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🌐 Created region", region.name)

View file

@ -0,0 +1,35 @@
import sys
from dcim.models import Region, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
sites = load_yaml("/opt/netbox/initializers/sites.yml")
if sites is None:
sys.exit()
optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")}
for params in sites:
custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params)
site, created = Site.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("📍 Created site", site.name)
set_custom_fields_values(site, custom_field_data)

View file

@ -0,0 +1,25 @@
import sys
from dcim.models import Location, Site
from startup_script_utils import load_yaml, split_params
rack_groups = load_yaml("/opt/netbox/initializers/locations.yml")
if rack_groups is None:
sys.exit()
match_params = ["name", "slug", "site"]
required_assocs = {"site": (Site, "name")}
for params in rack_groups:
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
location, created = Location.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created location", location.name)

View file

@ -0,0 +1,24 @@
import sys
from dcim.models import RackRole
from startup_script_utils import load_yaml, split_params
from utilities.choices import ColorChoices
rack_roles = load_yaml("/opt/netbox/initializers/rack_roles.yml")
if rack_roles is None:
sys.exit()
for params in rack_roles:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
matching_params, defaults = split_params(params)
rack_role, created = RackRole.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created rack role", rack_role.name)

View file

@ -0,0 +1,47 @@
import sys
from dcim.models import Location, Rack, RackRole, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
racks = load_yaml("/opt/netbox/initializers/racks.yml")
if racks is None:
sys.exit()
match_params = ["name", "site"]
required_assocs = {"site": (Site, "name")}
optional_assocs = {
"role": (RackRole, "name"),
"tenant": (Tenant, "name"),
"location": (Location, "name"),
}
for params in racks:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
rack, created = Rack.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔳 Created rack", rack.site, rack.name)
set_custom_fields_values(rack, custom_field_data)

View file

@ -0,0 +1,42 @@
import sys
from dcim.models import Location, PowerPanel, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
power_panels = load_yaml("/opt/netbox/initializers/power_panels.yml")
if power_panels is None:
sys.exit()
match_params = ["name", "site"]
required_assocs = {"site": (Site, "name")}
optional_assocs = {"location": (Location, "name")}
for params in power_panels:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
power_panel, created = PowerPanel.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("⚡ Created Power Panel", power_panel.site, power_panel.name)
set_custom_fields_values(power_panel, custom_field_data)

Some files were not shown because too many files have changed in this diff Show more