ansible: rewrite netbox role to match live netbox.vntx.net (bare-metal install)

The previous role was netbox-community/netbox-docker (compose-based) but
the live host runs the upstream tarball install directly: system Postgres
17, system Redis 8, gunicorn under systemd, and Caddy reverse-proxy.
Re-running the old role would have torn the working install down.

This rewrite mirrors what's actually deployed:

- defaults: NetBox 4.6.0, /opt/netbox layout, gunicorn 5w/3t/120s, all
  configuration.py knobs exposed as ansible vars.
- tasks: install OS deps, ensure netbox user, create Postgres role + db,
  download + extract release tarball, run upgrade.sh, render
  configuration.py / gunicorn.py / systemd units, enable services.
- templates: configuration.py.j2 covers the same keys the live file sets;
  netbox.service / netbox-rq.service are byte-equivalent to the live
  units (modulo paths driven by the install-dir var).
- caddy role gets a per-host Caddyfile-netbox.vntx.net.j2 mirroring the
  live vhost (25MB body limit, /static/* file_server, reverse_proxy to
  127.0.0.1:8001).
- inventory: new netbox_servers group; netbox.vntx.net added to
  caddy_servers so the per-host Caddyfile is wired up.
- host_vars/netbox.vntx.net.yml: secrets reference vault_* vars; role
  refuses to render config.py until they're set.

Operator action: create host_vars/netbox.vntx.net/vault.yml with
netbox_secret_key, netbox_db_password, and netbox_api_token_peppers
(value visible via `sudo cat /opt/netbox/netbox/netbox/configuration.py`
on the live host). See roles/netbox/README.md.
This commit is contained in:
Graham McIntire 2026-05-08 12:17:30 -05:00
parent 2f85d55edd
commit f3f10c3e36
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
19 changed files with 577 additions and 562 deletions

View file

@ -1,13 +1,18 @@
---
# Host-specific configuration for netbox.vntx.net
# Ansible connection settings
# Host-specific configuration for netbox.vntx.net.
# Connection
ansible_user: graham
ansible_host: 204.110.191.243
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_private_key_file: /Users/graham/.ssh/id_ed25519
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
# This host's network is configured out of band; don't let the general role
# clobber it.
network:
skip: true
# Public-facing reverse proxy + SSH-from-LAN — mirrors the live ufw rules.
firewall_allow_rules:
- port: 80
proto: tcp
@ -16,25 +21,19 @@ firewall_allow_rules:
proto: tcp
comment: HTTPS
# Override defaults for NetBox installation
netbox_port: 8001
# ---- netbox role overrides ----
netbox_admins:
- name: "Graham McIntire"
email: "graham@vntx.net"
netbox_allowed_hosts:
- netbox.vntx.net
netbox_caddy_domain: netbox.vntx.net
netbox_login_required: true
# Secrets — pull from a vault file alongside this one (host_vars/netbox.vntx.net/vault.yml,
# encrypted with ansible-vault). The role asserts these are set before rendering config.
netbox_secret_key: "{{ vault_netbox_secret_key }}"
netbox_db_password: "{{ vault_netbox_db_password }}"
netbox_admin_password: "{{ vault_netbox_admin_password }}"
netbox_admin_email: "admin@vntx.net"
# Set allowed hosts for this installation (space-separated string)
netbox_allowed_hosts: "netbox.vntx.net localhost 127.0.0.1"
# Email configuration for VNTX environment
netbox_email_from: "netbox@vntx.net"
# Time zone
netbox_timezone: "America/Chicago"
# Enable metrics for monitoring
netbox_metrics_enabled: true
# Custom branding if desired
# netbox_branding_title: "VNTX NetBox"
# netbox_branding_url: "https://vntx.net"
netbox_api_token_peppers: "{{ vault_netbox_api_token_peppers | default({}) }}"

View file

@ -41,6 +41,10 @@ aprs.w5isp.com
[caddy_servers]
skippy.w5isp.com
netbox.vntx.net
[netbox_servers]
netbox.vntx.net
[bind9_servers]
ns1.as393837.net ansible_host=204.110.191.222

View file

@ -33,6 +33,15 @@
roles:
- resolvers
- name: Install and configure NetBox
hosts: netbox_servers
become: true
gather_facts: true
tags:
- netbox
roles:
- netbox
- name: Install and configure Caddy
hosts: caddy_servers
become: true

View file

@ -0,0 +1,21 @@
{{ netbox_caddy_domain | default(inventory_hostname) }} {
# Set max request body size
request_body {
max_size {{ netbox_caddy_max_body_size | default('25MB') }}
}
# Serve static files
handle /static/* {
root * {{ netbox_install_dir | default('/opt/netbox') }}/netbox
file_server
}
# Proxy to Gunicorn backend
handle {
reverse_proxy {{ netbox_listen_addr | default('127.0.0.1:8001') }} {
header_up X-Forwarded-Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
}

View file

@ -0,0 +1,42 @@
# netbox role
Bare-metal NetBox install matching the live netbox.vntx.net layout:
- Debian 13 (trixie) host
- System PostgreSQL 17 + Redis 8
- NetBox release tarball extracted under `/opt/netbox-<version>`, symlinked to `/opt/netbox`
- Python venv managed by NetBox's bundled `upgrade.sh`
- gunicorn bound to `127.0.0.1:8001` under systemd (`netbox.service`)
- Background worker under `netbox-rq.service`
- Reverse proxy via the repo's existing `caddy` role using the per-host
template `roles/caddy/templates/Caddyfile-netbox.vntx.net.j2`
## Required vault variables
The role refuses to render config without these. Put them in
`host_vars/netbox.vntx.net/vault.yml` (encrypted with ansible-vault) or any
equivalent secrets store:
```yaml
netbox_secret_key: "<>= 50 random chars>" # generate with /opt/netbox/netbox/generate_secret_key.py
netbox_db_password: "<postgres password>"
netbox_api_token_peppers: # optional, only if rotating peppers
1: "<32+ random hex chars>"
```
## Idempotency
Re-running the playbook against a host already at the target version is a
no-op except for config rendering. To upgrade:
1. Bump `netbox_version` in defaults or host_vars.
2. Run the play. The role downloads the new tarball, re-points the
`/opt/netbox` symlink, runs `upgrade.sh` (which handles venv + migrations
+ collectstatic), and bounces the systemd units.
## What the role does NOT manage
- The data inside NetBox (sites, devices, IPs, etc.). Use the
`netbox.netbox` Ansible collection or NetBox's import APIs for that.
- Postgres performance tuning beyond defaults (shared_buffers, etc.).
- Off-host backups of the Postgres DB. Add a separate cron / restic job.

View file

@ -1,106 +1,134 @@
---
# Directory configuration
netbox_home: /opt/netbox
# Bare-metal NetBox install (Debian 13 + tarball + system Postgres/Redis +
# system Caddy). Mirrors the live install on netbox.vntx.net.
# Override per-host as needed; never commit secrets — keep them in
# ansible-vault or host_vars marked vault-only.
# Version configuration
netbox_version: release
netbox_docker_tag: latest
netbox_postgres_version: 15-alpine
netbox_redis_version: 7-alpine
netbox_version: "4.6.0"
netbox_release_url: "https://github.com/netbox-community/netbox/archive/v{{ netbox_version }}.tar.gz"
# Network configuration
netbox_port: 8080
netbox_listen_address: 0.0.0.0
netbox_domain: netbox.vntx.net
# Install layout — matches NetBox upstream docs. /opt/netbox is the extracted
# release root; /opt/netbox/netbox is the Django project; /opt/netbox/venv is
# the Python virtualenv that upgrade.sh manages.
netbox_install_dir: /opt/netbox
netbox_user: netbox
netbox_group: netbox
# Database configuration
# Listen address for gunicorn (matches the live gunicorn.py). Caddy proxies here.
netbox_listen_addr: "127.0.0.1:8001"
netbox_gunicorn_workers: 5
netbox_gunicorn_threads: 3
netbox_gunicorn_timeout: 120
netbox_gunicorn_max_requests: 5000
netbox_gunicorn_max_requests_jitter: 500
# PostgreSQL
netbox_db_name: netbox
netbox_db_user: netbox
netbox_db_password: "{{ vault_netbox_db_password | default('changeme') }}"
netbox_db_host: postgres
netbox_db_port: 5432
netbox_db_host: localhost
netbox_db_port: ""
netbox_db_conn_max_age: 300
# netbox_db_password: provided via host_vars/vault — no default
# Redis configuration
netbox_redis_host: redis
# Redis (matches the live install — both queues on the same local instance)
netbox_redis_host: localhost
netbox_redis_port: 6379
netbox_redis_password: "{{ vault_netbox_redis_password | default('') }}"
netbox_redis_cache_host: redis-cache
netbox_redis_cache_port: 6379
netbox_redis_cache_password: "{{ vault_netbox_redis_cache_password | default('') }}"
netbox_redis_username: ""
netbox_redis_password: ""
netbox_redis_tasks_db: 0
netbox_redis_caching_db: 1
netbox_redis_ssl: false
# NetBox configuration
netbox_secret_key: "{{ vault_netbox_secret_key | default('changeme_to_a_very_long_random_string') }}"
netbox_superuser_api_token: "{{ vault_netbox_superuser_api_token | default('') }}"
netbox_allowed_hosts: "*"
netbox_cors_origin_allow_all: true
# Application config
netbox_allowed_hosts:
- "{{ inventory_hostname }}"
netbox_admins: [] # e.g. [{name: "Graham McIntire", email: "graham@vntx.net"}]
netbox_debug: false
netbox_email_server: localhost
netbox_email_port: 25
netbox_email_username: ""
netbox_email_password: ""
netbox_email_use_ssl: false
netbox_email_use_tls: false
netbox_email_from: "netbox@{{ ansible_fqdn }}"
netbox_email_timeout: 10
# Login configuration
netbox_login_required: false
netbox_login_timeout: 1209600 # 14 days in seconds
# Media and upload configuration
netbox_media_root: /opt/netbox/netbox/media
netbox_reports_root: /opt/netbox/netbox/reports
netbox_scripts_root: /opt/netbox/netbox/scripts
netbox_max_upload_size: 26214400 # 25MB
# Housekeeping
netbox_housekeeping_interval: 86400 # 24 hours
# Metrics
netbox_login_required: true
netbox_login_persistence: false
netbox_login_form_hidden: false
netbox_logout_redirect_url: home
netbox_metrics_enabled: false
netbox_time_zone: UTC
netbox_default_language: en-us
netbox_base_path: ""
netbox_session_cookie_name: sessionid
netbox_csrf_cookie_name: csrftoken
netbox_session_file_path: "" # empty -> falls back to db
netbox_release_check_url: "" # empty -> disabled
netbox_rq_default_timeout: 300
netbox_login_timeout: "" # empty -> Django default
netbox_internal_ips:
- "127.0.0.1"
- "::1"
# Plugins
# Auth / API
netbox_allow_token_retrieval: false
netbox_remote_auth_enabled: false
netbox_remote_auth_backend: "netbox.authentication.RemoteUserBackend"
netbox_remote_auth_header: "HTTP_REMOTE_USER"
netbox_remote_auth_user_first_name: "HTTP_REMOTE_USER_FIRST_NAME"
netbox_remote_auth_user_last_name: "HTTP_REMOTE_USER_LAST_NAME"
netbox_remote_auth_user_email: "HTTP_REMOTE_USER_EMAIL"
netbox_remote_auth_auto_create_user: true
netbox_remote_auth_default_groups: []
netbox_remote_auth_default_permissions: {}
# CORS
netbox_cors_origin_allow_all: false
netbox_cors_origin_whitelist: []
netbox_cors_origin_regex_whitelist: []
netbox_exempt_view_permissions: []
netbox_auth_password_validators: []
netbox_logging: {}
# Plugins (must match what's in venv via local_requirements.txt)
netbox_plugins: []
netbox_plugins_config: {}
netbox_local_requirements: []
# Admin user configuration
netbox_create_superuser: true
netbox_admin_user: admin
netbox_admin_email: "admin@{{ ansible_fqdn }}"
netbox_admin_password: "{{ vault_netbox_admin_password | default('changeme') }}"
# Email
netbox_email:
SERVER: localhost
PORT: 25
USERNAME: ""
PASSWORD: ""
USE_SSL: false
USE_TLS: false
TIMEOUT: 10
FROM_EMAIL: ""
# NAPALM configuration (for network device integration)
netbox_napalm_username: ""
netbox_napalm_password: ""
netbox_napalm_timeout: 30
# Secrets — MUST come from host_vars / ansible-vault. Listed here so the
# role's contract is self-documenting; the role refuses to render config
# until they're provided.
# netbox_secret_key: <from vault>
# netbox_db_password: <from vault>
# netbox_api_token_peppers: { 1: "<from vault>" }
# Time zone
netbox_timezone: UTC
# Caddy reverse-proxy hostname (drives the per-host Caddyfile rendered by
# the existing `caddy` role; this role just sets up the backend).
netbox_caddy_domain: "{{ inventory_hostname }}"
netbox_caddy_max_body_size: "25MB"
# Date and time formatting
netbox_date_format: "N j, Y"
netbox_short_date_format: "Y-m-d"
netbox_time_format: "g:i a"
netbox_short_time_format: "H:i:s"
netbox_datetime_format: "N j, Y g:i a"
netbox_short_datetime_format: "Y-m-d H:i"
# Pagination
netbox_paginate_count: 50
netbox_max_page_size: 1000
# GraphQL
netbox_graphql_enabled: true
# Webhook configuration
netbox_webhooks_enabled: true
netbox_webhook_redis_queue: default
# Cache timeout
netbox_cache_timeout: 900 # 15 minutes
# Changelog retention
netbox_changelog_retention: 90 # days
# Job retention
netbox_jobresult_retention: 90 # days
# OS deps required to build/run NetBox on Debian/Ubuntu
netbox_system_packages:
- postgresql
- postgresql-client
- redis-server
- python3
- python3-dev
- python3-venv
- python3-pip
- build-essential
- libxml2-dev
- libxslt1-dev
- libffi-dev
- libpq-dev
- libssl-dev
- libsasl2-dev
- libldap2-dev
- zlib1g-dev
- graphviz
- git
- acl # required for community.postgresql to become postgres user
- python3-psycopg2 # for ansible postgres modules

View file

@ -1,20 +1,17 @@
---
- name: restart netbox
systemd:
name: netbox-docker
- name: Restart netbox
ansible.builtin.systemd:
name: netbox
state: restarted
daemon_reload: true
- name: stop netbox
systemd:
name: netbox-docker
state: stopped
- name: Restart netbox-rq
ansible.builtin.systemd:
name: netbox-rq
state: restarted
daemon_reload: true
- name: start netbox
systemd:
name: netbox-docker
state: started
- name: restart caddy
systemd:
- name: Restart caddy
ansible.builtin.systemd:
name: caddy
state: restarted
state: restarted

View file

@ -1,258 +1,200 @@
---
- name: Install prerequisites for Docker
apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
- git
- python3-full
state: present
update_cache: true
when: ansible_os_family == "Debian"
# Bare-metal NetBox install (mirrors live netbox.vntx.net layout). Runs:
# 1. system packages
# 2. netbox user/group
# 3. PostgreSQL role + database
# 4. Redis (default config; just ensure running)
# 5. Fetch + extract release tarball into {{ netbox_install_dir }}
# 6. Run upgrade.sh (creates venv, installs deps, runs migrate + collectstatic)
# 7. Render configuration.py + gunicorn.py
# 8. Render systemd units (netbox + netbox-rq)
# 9. Render per-host Caddyfile (the caddy role's host-specific template hook)
- name: Add Docker's official GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/debian/gpg
dest: /usr/share/keyrings/docker-archive-keyring.asc
mode: '0644'
- name: Assert required secrets are present
ansible.builtin.assert:
that:
- netbox_secret_key is defined and netbox_secret_key | length >= 50
- netbox_db_password is defined and netbox_db_password | length > 0
fail_msg: >-
netbox_secret_key (>= 50 chars) and netbox_db_password must be set in
host_vars / ansible-vault before this role can run. See
ansible/roles/netbox/README.md.
- name: Add Docker repository
ansible.builtin.deb822_repository:
name: docker
types: deb
uris: https://download.docker.com/linux/debian
suites: "{{ ansible_distribution_release }}"
components: stable
architectures: "{{ ansible_architecture | replace('x86_64', 'amd64') }}"
signed_by: /usr/share/keyrings/docker-archive-keyring.asc
state: present
- name: Install Docker packages
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
- name: Install system packages
ansible.builtin.apt:
name: "{{ netbox_system_packages }}"
state: present
update_cache: true
- name: Ensure Docker service is started and enabled
systemd:
name: docker
- name: Ensure PostgreSQL is started and enabled
ansible.builtin.systemd:
name: postgresql
state: started
enabled: true
- name: Create netbox directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ netbox_home }}/netbox-docker"
- "{{ netbox_home }}/netbox-docker/env"
- "{{ netbox_home }}/data"
- "{{ netbox_home }}/data/redis"
- "{{ netbox_home }}/data/redis-cache"
- "{{ netbox_home }}/data/media"
- "{{ netbox_home }}/data/reports"
- "{{ netbox_home }}/data/scripts"
- name: Create postgres data directory with specific permissions
file:
path: "{{ netbox_home }}/data/postgres"
state: directory
mode: '0700'
- name: Clone netbox-docker repository
git:
repo: https://github.com/netbox-community/netbox-docker.git
dest: "{{ netbox_home }}/netbox-docker"
version: "{{ netbox_version }}"
force: true
notify: restart netbox
# Configuration directory creation removed - using default configuration with env vars
- name: Deploy docker-compose override file
template:
src: docker-compose.override.yml.j2
dest: "{{ netbox_home }}/netbox-docker/docker-compose.override.yml"
mode: '0644'
notify: restart netbox
- name: Deploy netbox.env file
template:
src: env.j2
dest: "{{ netbox_home }}/netbox-docker/env/netbox.env"
mode: '0600'
notify: restart netbox
- name: Deploy postgres.env file
template:
src: postgres.env.j2
dest: "{{ netbox_home }}/netbox-docker/env/postgres.env"
mode: '0600'
notify: restart netbox
- name: Deploy redis.env file
template:
src: redis.env.j2
dest: "{{ netbox_home }}/netbox-docker/env/redis.env"
mode: '0600'
notify: restart netbox
- name: Deploy redis-cache.env file
template:
src: redis-cache.env.j2
dest: "{{ netbox_home }}/netbox-docker/env/redis-cache.env"
mode: '0600'
notify: restart netbox
- name: Remove custom configuration.py if exists
file:
path: "{{ netbox_home }}/netbox-docker/configuration/configuration.py"
state: absent
- name: Pull container images
command: docker compose -f docker-compose.yml -f docker-compose.override.yml pull
args:
chdir: "{{ netbox_home }}/netbox-docker"
register: pull_result
changed_when: "'Pulling' in pull_result.stdout"
- name: Deploy systemd service for NetBox
template:
src: netbox-docker.service.j2
dest: /etc/systemd/system/netbox-docker.service
owner: root
group: root
mode: '0644'
notify: restart netbox
- name: Reload systemd daemon
systemd:
daemon_reload: true
- name: Start and enable NetBox service
systemd:
name: netbox-docker
- name: Ensure Redis is started and enabled
ansible.builtin.systemd:
name: redis-server
state: started
enabled: true
- name: Wait for initial container startup
pause:
seconds: 15
- name: Create netbox group
ansible.builtin.group:
name: "{{ netbox_group }}"
system: true
state: present
- name: Check container status
command: docker ps -a
register: container_status
changed_when: false
- name: Create netbox user
ansible.builtin.user:
name: "{{ netbox_user }}"
group: "{{ netbox_group }}"
system: true
shell: /usr/sbin/nologin
home: "{{ netbox_install_dir }}"
create_home: false
state: present
- name: Debug container status
debug:
var: container_status.stdout_lines
when: "'ANSIBLE_DEBUG' in ansible_env or ansible_verbosity > 0"
- name: Create PostgreSQL role
community.postgresql.postgresql_user:
name: "{{ netbox_db_user }}"
password: "{{ netbox_db_password }}"
state: present
become: true
become_user: postgres
- name: Wait for NetBox to be ready
uri:
url: "http://localhost:{{ netbox_port }}/api/"
method: GET
status_code:
- 200
- 403 # API might return 403 if auth is required
validate_certs: false
timeout: 30
register: result
until: result.status in [200, 403]
retries: 30
delay: 10
- name: Create NetBox database
community.postgresql.postgresql_db:
name: "{{ netbox_db_name }}"
owner: "{{ netbox_db_user }}"
encoding: UTF-8
state: present
become: true
become_user: postgres
- name: Check if superuser exists
command: >
docker exec netbox-docker-netbox-1
python /opt/netbox/netbox/manage.py shell -c
"from django.contrib.auth import get_user_model; User = get_user_model(); exit(0 if User.objects.filter(username='{{ netbox_admin_user }}').exists() else 1)"
register: superuser_check
- name: Check installed NetBox version
ansible.builtin.slurp:
src: "{{ netbox_install_dir }}/netbox/release.yaml"
register: netbox_release_file
failed_when: false
changed_when: false
- name: Create superuser
command: >
docker exec netbox-docker-netbox-1
python /opt/netbox/netbox/manage.py createsuperuser
--username {{ netbox_admin_user }}
--email {{ netbox_admin_email }}
--noinput
environment:
DJANGO_SUPERUSER_PASSWORD: "{{ netbox_admin_password }}"
when:
- netbox_create_superuser | bool
- superuser_check.rc != 0
- name: Set installed-version fact
ansible.builtin.set_fact:
netbox_installed_version: >-
{{
(netbox_release_file.content | b64decode
| regex_search('version:\s*"?([0-9.]+)"?', '\1')
| first)
if netbox_release_file is defined and netbox_release_file.content is defined
else ''
}}
# Caddy reverse proxy setup
- name: Install Caddy prerequisites
apt:
name:
- debian-keyring
- debian-archive-keyring
- apt-transport-https
- curl
state: present
update_cache: true
- name: Decide whether NetBox needs (re)install
ansible.builtin.set_fact:
netbox_needs_install: "{{ netbox_installed_version != netbox_version }}"
- name: Add Caddy GPG key
shell: |
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
args:
creates: /usr/share/keyrings/caddy-stable-archive-keyring.gpg
- name: Download NetBox release tarball
ansible.builtin.get_url:
url: "{{ netbox_release_url }}"
dest: "/tmp/netbox-{{ netbox_version }}.tar.gz"
mode: "0644"
when: netbox_needs_install
- name: Add Caddy repository
shell: |
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
args:
creates: /etc/apt/sources.list.d/caddy-stable.list
- name: Extract NetBox release into a versioned directory
ansible.builtin.unarchive:
src: "/tmp/netbox-{{ netbox_version }}.tar.gz"
dest: /opt
remote_src: true
creates: "/opt/netbox-{{ netbox_version }}"
when: netbox_needs_install
- name: Fix permissions on Caddy repository files
file:
path: "{{ item }}"
mode: '0644'
loop:
- /usr/share/keyrings/caddy-stable-archive-keyring.gpg
- /etc/apt/sources.list.d/caddy-stable.list
- name: Symlink {{ netbox_install_dir }} -> /opt/netbox-{{ netbox_version }}
ansible.builtin.file:
src: "/opt/netbox-{{ netbox_version }}"
dest: "{{ netbox_install_dir }}"
state: link
force: true
when: netbox_needs_install
notify:
- Restart netbox
- Restart netbox-rq
- name: Update apt cache after adding Caddy repo
apt:
update_cache: true
- name: Install Caddy
apt:
name: caddy
state: present
- name: Create Caddy log directory
file:
path: /var/log/caddy
- name: Ensure netbox owns mutable subdirs
ansible.builtin.file:
path: "{{ netbox_install_dir }}/netbox/{{ item }}"
state: directory
owner: caddy
group: caddy
mode: '0755'
owner: "{{ netbox_user }}"
group: "{{ netbox_group }}"
mode: "0755"
loop:
- media
- reports
- scripts
- static
- name: Configure Caddy for NetBox
template:
src: Caddyfile.j2
dest: /etc/caddy/Caddyfile
- name: Render local_requirements.txt (plugin Python deps)
ansible.builtin.copy:
dest: "{{ netbox_install_dir }}/local_requirements.txt"
content: "{{ netbox_local_requirements | join('\n') }}\n"
owner: root
group: root
mode: '0644'
notify: restart caddy
mode: "0644"
notify:
- Restart netbox
- Restart netbox-rq
- name: Ensure Caddy is started and enabled
systemd:
name: caddy
- name: Render configuration.py
ansible.builtin.template:
src: configuration.py.j2
dest: "{{ netbox_install_dir }}/netbox/netbox/configuration.py"
owner: root
group: "{{ netbox_group }}"
mode: "0640"
notify:
- Restart netbox
- Restart netbox-rq
- name: Render gunicorn.py
ansible.builtin.template:
src: gunicorn.py.j2
dest: "{{ netbox_install_dir }}/gunicorn.py"
owner: "{{ netbox_user }}"
group: "{{ netbox_group }}"
mode: "0644"
notify: Restart netbox
- name: Run NetBox upgrade.sh (creates venv, installs deps, migrates DB, collectstatic)
ansible.builtin.command: "{{ netbox_install_dir }}/upgrade.sh"
args:
chdir: "{{ netbox_install_dir }}"
register: netbox_upgrade
when: netbox_needs_install
changed_when: netbox_upgrade.rc == 0
- name: Render netbox systemd unit
ansible.builtin.template:
src: netbox.service.j2
dest: /etc/systemd/system/netbox.service
owner: root
group: root
mode: "0644"
notify: Restart netbox
- name: Render netbox-rq systemd unit
ansible.builtin.template:
src: netbox-rq.service.j2
dest: /etc/systemd/system/netbox-rq.service
owner: root
group: root
mode: "0644"
notify: Restart netbox-rq
- name: Enable and start NetBox services
ansible.builtin.systemd:
name: "{{ item }}"
enabled: true
state: started
enabled: true
daemon_reload: true
loop:
- netbox
- netbox-rq

View file

@ -1,54 +0,0 @@
# {{ ansible_managed }}
# Caddy configuration for NetBox reverse proxy
{{ netbox_domain | default('netbox.vntx.net') }} {
# Enable compression
encode gzip
# Reverse proxy to NetBox
reverse_proxy localhost:{{ netbox_port }} {
# Set headers for proper operation
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
# WebSocket support
header_up Connection {>Connection}
header_up Upgrade {>Upgrade}
}
# Logging
log {
output file /var/log/caddy/netbox-access.log
format json
}
# Security headers
header {
# Enable HSTS
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Disable content type sniffing
X-Content-Type-Options "nosniff"
# Enable XSS protection
X-XSS-Protection "1; mode=block"
# Clickjacking protection
X-Frame-Options "SAMEORIGIN"
# Referrer policy
Referrer-Policy "strict-origin-when-cross-origin"
}
# Handle large uploads (for NetBox file uploads)
request_body {
max_size 100MB
}
}
# Redirect HTTP to HTTPS
http://{{ netbox_domain | default('netbox.vntx.net') }} {
redir https://{{ netbox_domain | default('netbox.vntx.net') }}{uri} permanent
}

View file

@ -0,0 +1,126 @@
{{ ansible_managed | comment(decoration='# ') }}
#
# NetBox configuration. All knobs come from ansible vars; see
# roles/netbox/defaults/main.yml for defaults and host_vars / vault for
# per-host overrides + secrets.
ALLOWED_HOSTS = {{ netbox_allowed_hosts | to_json }}
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': {{ netbox_db_name | to_json }},
'USER': {{ netbox_db_user | to_json }},
'PASSWORD': {{ netbox_db_password | to_json }},
'HOST': {{ netbox_db_host | to_json }},
'PORT': {{ netbox_db_port | to_json }},
'CONN_MAX_AGE': {{ netbox_db_conn_max_age }},
}
}
REDIS = {
'tasks': {
'HOST': {{ netbox_redis_host | to_json }},
'PORT': {{ netbox_redis_port }},
'USERNAME': {{ netbox_redis_username | to_json }},
'PASSWORD': {{ netbox_redis_password | to_json }},
'DATABASE': {{ netbox_redis_tasks_db }},
'SSL': {{ netbox_redis_ssl | bool }},
},
'caching': {
'HOST': {{ netbox_redis_host | to_json }},
'PORT': {{ netbox_redis_port }},
'USERNAME': {{ netbox_redis_username | to_json }},
'PASSWORD': {{ netbox_redis_password | to_json }},
'DATABASE': {{ netbox_redis_caching_db }},
'SSL': {{ netbox_redis_ssl | bool }},
},
}
SECRET_KEY = {{ netbox_secret_key | to_json }}
{% if netbox_api_token_peppers is defined %}
API_TOKEN_PEPPERS = {{ netbox_api_token_peppers | to_json }}
{% endif %}
ADMINS = [
{% for a in netbox_admins %}
({{ a.name | to_json }}, {{ a.email | to_json }}),
{% endfor %}
]
ALLOW_TOKEN_RETRIEVAL = {{ netbox_allow_token_retrieval | bool }}
AUTH_PASSWORD_VALIDATORS = {{ netbox_auth_password_validators | to_json }}
BASE_PATH = {{ netbox_base_path | to_json }}
CORS_ORIGIN_ALLOW_ALL = {{ netbox_cors_origin_allow_all | bool }}
CORS_ORIGIN_WHITELIST = {{ netbox_cors_origin_whitelist | to_json }}
CORS_ORIGIN_REGEX_WHITELIST = {{ netbox_cors_origin_regex_whitelist | to_json }}
CSRF_COOKIE_NAME = {{ netbox_csrf_cookie_name | to_json }}
DEBUG = {{ netbox_debug | bool }}
DEFAULT_LANGUAGE = {{ netbox_default_language | to_json }}
EMAIL = {
'SERVER': {{ netbox_email.SERVER | to_json }},
'PORT': {{ netbox_email.PORT }},
'USERNAME': {{ netbox_email.USERNAME | to_json }},
'PASSWORD': {{ netbox_email.PASSWORD | to_json }},
'USE_SSL': {{ netbox_email.USE_SSL | bool }},
'USE_TLS': {{ netbox_email.USE_TLS | bool }},
'TIMEOUT': {{ netbox_email.TIMEOUT }},
'FROM_EMAIL': {{ netbox_email.FROM_EMAIL | to_json }},
}
EXEMPT_VIEW_PERMISSIONS = {{ netbox_exempt_view_permissions | to_json }}
INTERNAL_IPS = {{ netbox_internal_ips | to_json }}
LOGGING = {{ netbox_logging | to_json }}
LOGIN_PERSISTENCE = {{ netbox_login_persistence | bool }}
LOGIN_REQUIRED = {{ netbox_login_required | bool }}
{% if netbox_login_timeout %}
LOGIN_TIMEOUT = {{ netbox_login_timeout }}
{% else %}
LOGIN_TIMEOUT = None
{% endif %}
LOGIN_FORM_HIDDEN = {{ netbox_login_form_hidden | bool }}
LOGOUT_REDIRECT_URL = {{ netbox_logout_redirect_url | to_json }}
METRICS_ENABLED = {{ netbox_metrics_enabled | bool }}
PLUGINS = {{ netbox_plugins | to_json }}
{% if netbox_plugins_config %}
PLUGINS_CONFIG = {{ netbox_plugins_config | to_json }}
{% endif %}
REMOTE_AUTH_ENABLED = {{ netbox_remote_auth_enabled | bool }}
REMOTE_AUTH_BACKEND = {{ netbox_remote_auth_backend | to_json }}
REMOTE_AUTH_HEADER = {{ netbox_remote_auth_header | to_json }}
REMOTE_AUTH_USER_FIRST_NAME = {{ netbox_remote_auth_user_first_name | to_json }}
REMOTE_AUTH_USER_LAST_NAME = {{ netbox_remote_auth_user_last_name | to_json }}
REMOTE_AUTH_USER_EMAIL = {{ netbox_remote_auth_user_email | to_json }}
REMOTE_AUTH_AUTO_CREATE_USER = {{ netbox_remote_auth_auto_create_user | bool }}
REMOTE_AUTH_DEFAULT_GROUPS = {{ netbox_remote_auth_default_groups | to_json }}
REMOTE_AUTH_DEFAULT_PERMISSIONS = {{ netbox_remote_auth_default_permissions | to_json }}
{% if netbox_release_check_url %}
RELEASE_CHECK_URL = {{ netbox_release_check_url | to_json }}
{% else %}
RELEASE_CHECK_URL = None
{% endif %}
RQ_DEFAULT_TIMEOUT = {{ netbox_rq_default_timeout }}
SESSION_COOKIE_NAME = {{ netbox_session_cookie_name | to_json }}
{% if netbox_session_file_path %}
SESSION_FILE_PATH = {{ netbox_session_file_path | to_json }}
{% else %}
SESSION_FILE_PATH = None
{% endif %}
TIME_ZONE = {{ netbox_time_zone | to_json }}

View file

@ -1,37 +0,0 @@
services:
netbox:
ports:
- "{{ netbox_listen_address }}:{{ netbox_port }}:8080"
volumes:
- {{ netbox_home }}/data/media:/opt/netbox/netbox/media
- {{ netbox_home }}/data/reports:/opt/netbox/netbox/reports
- {{ netbox_home }}/data/scripts:/opt/netbox/netbox/scripts
environment:
SKIP_SUPERUSER: "false"
SUPERUSER_API_TOKEN: "{{ netbox_superuser_api_token }}"
SUPERUSER_EMAIL: "{{ netbox_admin_email }}"
SUPERUSER_NAME: "{{ netbox_admin_user }}"
SUPERUSER_PASSWORD: "{{ netbox_admin_password }}"
restart: unless-stopped
netbox-worker:
volumes:
- {{ netbox_home }}/data/media:/opt/netbox/netbox/media
- {{ netbox_home }}/data/reports:/opt/netbox/netbox/reports
- {{ netbox_home }}/data/scripts:/opt/netbox/netbox/scripts
restart: unless-stopped
postgres:
volumes:
- {{ netbox_home }}/data/postgres:/var/lib/postgresql/data
restart: unless-stopped
redis:
volumes:
- {{ netbox_home }}/data/redis:/data
restart: unless-stopped
redis-cache:
volumes:
- {{ netbox_home }}/data/redis-cache:/data
restart: unless-stopped

View file

@ -1,82 +0,0 @@
# NetBox environment configuration
# {{ ansible_managed }}
# PostgreSQL database configuration
DB_NAME={{ netbox_db_name }}
DB_USER={{ netbox_db_user }}
DB_PASSWORD={{ netbox_db_password }}
DB_HOST={{ netbox_db_host }}
DB_PORT={{ netbox_db_port }}
# Redis configuration
REDIS_HOST={{ netbox_redis_host }}
REDIS_PORT={{ netbox_redis_port }}
REDIS_PASSWORD={{ netbox_redis_password }}
REDIS_DATABASE=0
REDIS_SSL=false
# Redis cache configuration
REDIS_CACHE_HOST={{ netbox_redis_cache_host }}
REDIS_CACHE_PORT={{ netbox_redis_cache_port }}
REDIS_CACHE_PASSWORD={{ netbox_redis_cache_password }}
REDIS_CACHE_DATABASE=1
REDIS_CACHE_SSL=false
# NetBox configuration
SECRET_KEY={{ netbox_secret_key }}
SUPERUSER_API_TOKEN={{ netbox_superuser_api_token }}
ALLOWED_HOSTS={{ netbox_allowed_hosts }}
DEBUG={{ netbox_debug | lower }}
METRICS_ENABLED={{ netbox_metrics_enabled | lower }}
# Email configuration
EMAIL_SERVER={{ netbox_email_server }}
EMAIL_PORT={{ netbox_email_port }}
EMAIL_USERNAME={{ netbox_email_username }}
EMAIL_PASSWORD={{ netbox_email_password }}
EMAIL_USE_SSL={{ netbox_email_use_ssl | lower }}
EMAIL_USE_TLS={{ netbox_email_use_tls | lower }}
EMAIL_SSL_CERTFILE=
EMAIL_SSL_KEYFILE=
EMAIL_FROM={{ netbox_email_from }}
EMAIL_TIMEOUT={{ netbox_email_timeout }}
# NAPALM configuration
NAPALM_USERNAME={{ netbox_napalm_username }}
NAPALM_PASSWORD={{ netbox_napalm_password }}
NAPALM_TIMEOUT={{ netbox_napalm_timeout }}
# Housekeeping
HOUSEKEEPING_INTERVAL={{ netbox_housekeeping_interval }}
# Webhooks
WEBHOOKS_ENABLED={{ netbox_webhooks_enabled | lower }}
# GraphQL API
GRAPHQL_ENABLED={{ netbox_graphql_enabled | lower }}
# Time zone
TZ={{ netbox_timezone }}
# Change logging
CHANGELOG_RETENTION={{ netbox_changelog_retention }}
# Job results
JOBRESULT_RETENTION={{ netbox_jobresult_retention }}
# Maximum upload size in bytes
MAX_UPLOAD_SIZE={{ netbox_max_upload_size }}
# Pagination
PAGINATE_COUNT={{ netbox_paginate_count }}
MAX_PAGE_SIZE={{ netbox_max_page_size }}
# Cache timeout
CACHE_TIMEOUT={{ netbox_cache_timeout }}
# CORS
CORS_ORIGIN_ALLOW_ALL={{ netbox_cors_origin_allow_all | lower }}
# Login persistence
LOGIN_PERSISTENCE={{ netbox_login_timeout }}
LOGIN_REQUIRED={{ netbox_login_required | lower }}

View file

@ -0,0 +1,10 @@
{{ ansible_managed | comment(decoration='# ') }}
bind = '{{ netbox_listen_addr }}'
workers = {{ netbox_gunicorn_workers }}
threads = {{ netbox_gunicorn_threads }}
timeout = {{ netbox_gunicorn_timeout }}
max_requests = {{ netbox_gunicorn_max_requests }}
max_requests_jitter = {{ netbox_gunicorn_max_requests_jitter }}

View file

@ -1,17 +0,0 @@
[Unit]
Description=NetBox Docker Compose Application
Requires=docker.service
After=docker.service network-online.target
RequiresMountsFor={{ netbox_home }}
[Service]
Type=exec
WorkingDirectory={{ netbox_home }}/netbox-docker
ExecStart=/usr/bin/docker compose -f docker-compose.yml -f docker-compose.override.yml up
ExecStop=/usr/bin/docker compose -f docker-compose.yml -f docker-compose.override.yml down
TimeoutStopSec=70
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,22 @@
{{ ansible_managed | comment(decoration='# ') }}
[Unit]
Description=NetBox Request Queue Worker
Documentation=https://docs.netbox.dev/
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User={{ netbox_user }}
Group={{ netbox_group }}
WorkingDirectory={{ netbox_install_dir }}
ExecStart={{ netbox_install_dir }}/venv/bin/python3 {{ netbox_install_dir }}/netbox/manage.py rqworker high default low
Restart=on-failure
RestartSec=30
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,23 @@
{{ ansible_managed | comment(decoration='# ') }}
[Unit]
Description=NetBox WSGI Service
Documentation=https://docs.netbox.dev/
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User={{ netbox_user }}
Group={{ netbox_group }}
PIDFile=/var/tmp/netbox.pid
WorkingDirectory={{ netbox_install_dir }}
ExecStart={{ netbox_install_dir }}/venv/bin/gunicorn --pid /var/tmp/netbox.pid --pythonpath {{ netbox_install_dir }}/netbox --config {{ netbox_install_dir }}/gunicorn.py netbox.wsgi
Restart=on-failure
RestartSec=30
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View file

@ -1,6 +0,0 @@
# PostgreSQL environment configuration
# {{ ansible_managed }}
POSTGRES_USER={{ netbox_db_user }}
POSTGRES_PASSWORD={{ netbox_db_password }}
POSTGRES_DB={{ netbox_db_name }}

View file

@ -1,6 +0,0 @@
# Redis cache environment configuration
# {{ ansible_managed }}
{% if netbox_redis_cache_password %}
REDIS_PASSWORD={{ netbox_redis_cache_password }}
{% endif %}

View file

@ -1,6 +0,0 @@
# Redis environment configuration
# {{ ansible_managed }}
{% if netbox_redis_password %}
REDIS_PASSWORD={{ netbox_redis_password }}
{% endif %}