Files
rollcall/lab/netbox/install.sh
T
Ilker ManapandClaude Opus 5.5 133b5f38d9
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 43s
NetBox device sync; lab NetBox container
ipam.device_sync mirrors commissioned machines as dcim devices: serial,
device type and role (created when missing), status, interfaces with MAC
objects, Rollcall's addresses assigned to them and the management address
as primary IP. Site, location and rack are matched by name from the
machine's location and never created. Ownership by tag and
rollcall_machine; a foreign device of the same name is a conflict.

lab/netbox/install.sh sets up a NetBox on the lab segment so the drivers
are tested without writing to a real NetBox.

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:00:43 +01:00

151 lines
7.4 KiB
Bash

#!/bin/bash
# Lab NetBox for Rollcall's NetBox drivers (IPAM, device sync): a Debian 13
# container on the lab segment, so tests never write to a real NetBox.
# Same install as netops (altyapi hosts/netops/kurulum.sh) without PowerDNS,
# ACME and backups; plain HTTP on :80 (the segment is isolated).
#
# Run as root inside the container; rerunnable. Secrets are generated here:
# /etc/lab-netbox/secrets.env (admin password, Rollcall's API token)
#
# On pve1 (example):
# pct create 225 local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname lab-netbox \
# --cores 2 --memory 2048 --swap 512 --rootfs zfspool:8 --unprivileged 1 \
# --net0 name=eth0,bridge=pxenet,ip=10.213.77.6/24,gw=10.213.77.1 --nameserver 10.213.77.1
# pct start 225 && pct push 225 install.sh /root/install.sh && pct exec 225 -- bash /root/install.sh
set -euo pipefail
NETBOX_VERSION=${NETBOX_VERSION:-4.7.4}
MYIP=${MYIP:-10.213.77.6}
SITE=${SITE:-lab}
PREFIXES=${PREFIXES:-"10.213.77.0/24 10.213.78.0/24 10.213.79.0/24 fd00:213:77::/64 fd00:213:78::/64"}
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
apt-get install -yq postgresql valkey-server caddy \
python3 python3-venv python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev \
libpq-dev libssl-dev zlib1g-dev git curl jq
install -d -m 700 /etc/lab-netbox
S=/etc/lab-netbox/secrets.env
if [ ! -f $S ]; then
r() { python3 -c "import secrets,sys;print(secrets.token_urlsafe(int(sys.argv[1])))" "$1"; }
cat > $S <<EOF
NETBOX_DB_PASS=$(r 24)
NETBOX_SECRET_KEY=$(r 50)
NETBOX_PEPPER=$(r 50)
NETBOX_ADMIN_PASS=$(r 18)
ROLLCALL_PASS=$(r 18)
EOF
chmod 600 $S
fi
. $S
# ------------------------------------------------------------------ PostgreSQL, Valkey
sudo() { runuser -u "$2" -- "${@:3}"; } # "sudo -u postgres ..." without sudo
if [ -z "$(sudo -u postgres psql -tAqc "SELECT 1 FROM pg_roles WHERE rolname='netbox'")" ]; then
sudo -u postgres psql -qc "CREATE ROLE netbox LOGIN PASSWORD '$NETBOX_DB_PASS'"
# UTF-8 whatever the container's locale (C in the Proxmox template)
sudo -u postgres psql -qc "CREATE DATABASE netbox OWNER netbox ENCODING 'UTF8' LC_COLLATE 'C.UTF-8' LC_CTYPE 'C.UTF-8' TEMPLATE template0"
fi
sed -i 's/^# *databases .*/databases 16/' /etc/valkey/valkey.conf || true
systemctl enable --now valkey-server
# ------------------------------------------------------------------ NetBox
id netbox >/dev/null 2>&1 || adduser --system --group --home /opt/netbox netbox
D=/opt/netbox-$NETBOX_VERSION
if [ ! -d $D ]; then
curl -fsSL https://github.com/netbox-community/netbox/archive/refs/tags/v$NETBOX_VERSION.tar.gz | tar -xz -C /opt
fi
[ -L /opt/netbox ] || { [ -d /opt/netbox ] && rmdir /opt/netbox 2>/dev/null || true; }
ln -sfn $D /opt/netbox
cat > /opt/netbox/netbox/netbox/configuration.py <<EOF
# pxestuff lab/netbox/install.sh generates this file
ALLOWED_HOSTS = ['$MYIP', 'localhost', '127.0.0.1']
DATABASES = {'default': {'NAME': 'netbox', 'USER': 'netbox', 'PASSWORD': '$NETBOX_DB_PASS',
'HOST': '127.0.0.1', 'PORT': '', 'CONN_MAX_AGE': 300}}
REDIS = {
'tasks': {'HOST': '127.0.0.1', 'PORT': 6379, 'USERNAME': '', 'PASSWORD': '', 'DATABASE': 0, 'SSL': False},
'caching': {'HOST': '127.0.0.1', 'PORT': 6379, 'USERNAME': '', 'PASSWORD': '', 'DATABASE': 1, 'SSL': False},
}
SECRET_KEY = '$NETBOX_SECRET_KEY'
API_TOKEN_PEPPERS = {1: '$NETBOX_PEPPER'}
TIME_ZONE = 'Europe/Stockholm'
CSRF_TRUSTED_ORIGINS = ['http://$MYIP']
EOF
chown -R netbox:netbox $D/netbox/media $D/netbox/reports $D/netbox/scripts 2>/dev/null || true
cd /opt/netbox && PYTHON=/usr/bin/python3 ./upgrade.sh >/var/log/netbox-upgrade.log 2>&1 || { tail -40 /var/log/netbox-upgrade.log; exit 1; }
chown -R netbox:netbox /opt/netbox/netbox/media
cp /opt/netbox/contrib/gunicorn.py /opt/netbox/gunicorn.py
sed -i "s/^bind = .*/bind = '127.0.0.1:8001'/" /opt/netbox/gunicorn.py
cp /opt/netbox/contrib/netbox.service /opt/netbox/contrib/netbox-rq.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now netbox netbox-rq
systemctl restart netbox netbox-rq
cat > /etc/caddy/Caddyfile <<EOF
# pxestuff lab/netbox/install.sh
http://$MYIP, http://localhost {
handle_path /static/* {
root * /opt/netbox/netbox/static
file_server
}
reverse_proxy 127.0.0.1:8001
}
EOF
systemctl enable caddy && systemctl restart caddy
# ------------------------------------------------------------------ what Rollcall needs
# admin and rollcall users, the ownership tag and field, Rollcall's permissions
# (as altyapi hosts/netops/seed/rollcall-izinler.py on netops), the lab site
# and its prefixes delegated to Rollcall (tagged).
NB_ADMIN_PASS=$NETBOX_ADMIN_PASS NB_ROLLCALL_PASS=$ROLLCALL_PASS NB_SITE=$SITE NB_PREFIXES=$PREFIXES \
/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py shell -c '
import os
from django.contrib.contenttypes.models import ContentType
from users.models import User, ObjectPermission
from extras.models import Tag, CustomField
from dcim.models import Site
from ipam.models import Prefix
def user(name, pw, su):
u, _ = User.objects.get_or_create(username=name)
u.set_password(pw); u.is_superuser = su; u.is_active = True; u.save()
return u
user("admin", os.environ["NB_ADMIN_PASS"], True)
rc = user("rollcall", os.environ["NB_ROLLCALL_PASS"], False)
tag, _ = Tag.objects.get_or_create(slug="rollcall", defaults={"name": "rollcall", "color": "2196f3"})
cf, _ = CustomField.objects.get_or_create(name="rollcall_machine", defaults={"type": "text", "label": "Rollcall machine"})
for m in ("ipam.ipaddress", "dcim.device"):
cf.object_types.add(ContentType.objects.get_by_natural_key(*m.split(".")))
perms = {
"rollcall-ipam-write": (["ipam.ipaddress"], ["view", "add", "change", "delete"]),
"rollcall-read": (["ipam.prefix", "ipam.vlan", "ipam.iprange", "extras.tag", "dcim.site"], ["view"]),
"rollcall-dcim-write": (["dcim.device", "dcim.interface", "dcim.macaddress"], ["view", "add", "change", "delete"]),
"rollcall-dcim-types": (["dcim.manufacturer", "dcim.devicetype", "dcim.devicerole"], ["view", "add"]),
"rollcall-dcim-read": (["dcim.location", "dcim.rack"], ["view"]),
}
for name, (types, actions) in perms.items():
p, _ = ObjectPermission.objects.get_or_create(name=name, defaults={"actions": actions})
p.actions = actions; p.enabled = True; p.save()
p.object_types.set([ContentType.objects.get_by_natural_key(*t.split(".")) for t in types])
p.users.add(rc)
site, _ = Site.objects.get_or_create(slug=os.environ["NB_SITE"], defaults={"name": os.environ["NB_SITE"].capitalize()})
for cidr in os.environ["NB_PREFIXES"].split():
p, _ = Prefix.objects.get_or_create(prefix=cidr, defaults={"description": "Rollcall lab", "scope": site})
p.tags.add(tag)
print("rollcall objects ready")
'
# Rollcall's API token (v2), made once through the API with its password
if ! grep -q '^NETBOX_TOKEN_ROLLCALL=' $S; then
curl -fsS http://127.0.0.1:8001/api/users/tokens/provision/ -H "Host: $MYIP" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d "{\"username\":\"rollcall\",\"password\":\"$ROLLCALL_PASS\",\"description\":\"rollcall region\",\"write_enabled\":true}" |
python3 -c 'import json,sys;d=json.load(sys.stdin);print("NETBOX_TOKEN_ROLLCALL=nbt_%s.%s"%(d["key"],d["token"]))' >> $S
fi
echo "--- ready"
systemctl is-active postgresql valkey-server netbox netbox-rq caddy | paste -sd' '
. $S
curl -fsS -o /dev/null -w "rollcall token: %{http_code}\n" -H "Authorization: Bearer $NETBOX_TOKEN_ROLLCALL" http://$MYIP/api/ipam/prefixes/