first commit
Some checks failed
ci / deploy (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled

This commit is contained in:
Vlastislav Svatek
2026-06-05 10:39:05 +02:00
commit 673e67106e
217 changed files with 76612 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
#!/bin/bash
echo "🔍 DevContainer Startup Diagnostics"
echo "=================================="
PLUGIN_WS_DIR="${PLUGIN_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
echo "📍 Current working directory: $(pwd)"
echo "👤 Current user: $(whoami)"
echo "🆔 User ID: $(id)"
echo ""
echo "🐳 Container Environment:"
echo " - NETBOX_VERSION: ${NETBOX_VERSION:-not set}"
echo " - DEBUG: ${DEBUG:-not set}"
echo " - SECRET_KEY: ${SECRET_KEY:0:20}... (truncated)"
echo " - DB_HOST: ${DB_HOST:-not set}"
echo " - DB_NAME: ${DB_NAME:-not set}"
echo " - DB_USER: ${DB_USER:-not set}"
echo " - REDIS_HOST: ${REDIS_HOST:-not set}"
echo " - SUPERUSER_NAME: ${SUPERUSER_NAME:-not set}"
echo ""
echo "🔗 Service Connectivity:"
echo " - PostgreSQL: $(timeout 3 bash -c 'cat < /dev/null > /dev/tcp/postgres/5432' 2>/dev/null && echo 'Connected' || echo 'Not reachable')"
echo " - Redis: $(timeout 3 bash -c 'cat < /dev/null > /dev/tcp/redis/6379' 2>/dev/null && echo 'Connected' || echo 'Not reachable')"
echo ""
echo "🗂️ File System:"
echo " - NetBox venv: $(test -f /opt/netbox/venv/bin/activate && echo 'Exists' || echo 'Missing')"
echo " - Plugin directory: $(test -d "$PLUGIN_WS_DIR" && echo 'Exists' || echo 'Missing')"
echo " - Setup script: $(test -f "$PLUGIN_WS_DIR/.devcontainer/scripts/setup.sh" && echo 'Exists' || echo 'Missing')"
echo " - Start script: $(test -f "$PLUGIN_WS_DIR/.devcontainer/scripts/start-netbox.sh" && echo 'Exists' || echo 'Missing')"
echo " - Start script executable: $(test -x "$PLUGIN_WS_DIR/.devcontainer/scripts/start-netbox.sh" && echo 'Yes' || echo 'No')"
echo " - Plugin config: $(test -f "$PLUGIN_WS_DIR/.devcontainer/config/plugin-config.py" && echo 'Found' || echo 'Missing (using defaults)')"
echo " - NetBox config path: /opt/netbox/netbox/netbox/configuration.py"
echo ""
echo "🚀 Process Status:"
if [ -f /tmp/netbox.pid ]; then
PID=$(cat /tmp/netbox.pid)
if [ -z "$PID" ]; then
echo " - NetBox server: PID file exists but is empty"
elif kill -0 "$PID" 2>/dev/null; then
echo " - NetBox server: Running (PID: $PID)"
else
echo " - NetBox server: PID file exists but process not running"
echo " (PID $PID is dead - NetBox may have crashed)"
fi
else
echo " - NetBox server: Not started"
fi
# Check port listening
echo ""
echo "🌍 Port Check:"
if command -v netstat >/dev/null 2>&1; then
echo " - Port 8000: $(netstat -tuln 2>/dev/null | grep :8000 >/dev/null && echo 'Listening' || echo 'Not listening')"
elif command -v ss >/dev/null 2>&1; then
echo " - Port 8000: $(ss -tuln 2>/dev/null | grep :8000 >/dev/null && echo 'Listening' || echo 'Not listening')"
else
echo " - Port 8000: $(cat /proc/net/tcp 2>/dev/null | awk '$2 ~ /:1F40$/ {print "Listening"; exit}' | grep -q "Listening" && echo 'Listening' || echo 'Not listening')"
fi
echo ""
echo "✅ Diagnostic complete!"

View File

@@ -0,0 +1,224 @@
#!/bin/bash
# Quick alias loader for current session
# Usage: source .devcontainer/scripts/load-aliases.sh
export PATH="/opt/netbox/venv/bin:$PATH"
export DEBUG="${DEBUG:-True}"
PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Clean up empty CA bundle vars (Compose/devcontainer inject "" when host var is
# unset, which breaks requests/curl). When setup.sh has installed custom CAs
# into the system trust store, point to it instead.
for _ca_var in REQUESTS_CA_BUNDLE SSL_CERT_FILE CURL_CA_BUNDLE; do
_val="${!_ca_var}"
if [ -z "$_val" ]; then
if [ -f /etc/ssl/certs/ca-certificates.crt ]; then
declare -x "$_ca_var=/etc/ssl/certs/ca-certificates.crt"
else
unset "$_ca_var"
fi
fi
done
unset _ca_var _val
# Load shared process management helpers
if ! source "$PLUGIN_DIR/.devcontainer/scripts/process-helpers.sh"; then
printf '%s\n' "Failed to load process-helpers.sh" >&2
return 1
fi
netbox-run-bg() { "$PLUGIN_DIR/.devcontainer/scripts/start-netbox.sh" --background; }
netbox-run() { "$PLUGIN_DIR/.devcontainer/scripts/start-netbox.sh"; }
# Robust stop command that kills both tracked and orphaned processes
netbox-stop() {
echo "🛑 Stopping NetBox and RQ workers..."
if [ -f /tmp/netbox.pid ]; then
local PID
PID=$(cat /tmp/netbox.pid 2>/dev/null)
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
if is_expected_pid "$PID" "python.*runserver.*8000"; then
graceful_kill_pid "$PID"
echo " Stopped NetBox (PID: $PID)"
else
echo " Skipping stale /tmp/netbox.pid (PID $PID is not NetBox runserver)"
fi
fi
rm -f /tmp/netbox.pid
fi
if [ -f /tmp/rqworker.pid ]; then
local PID
PID=$(cat /tmp/rqworker.pid 2>/dev/null)
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
if is_expected_pid "$PID" "python.*rqworker"; then
graceful_kill_pid "$PID"
echo " Stopped RQ worker (PID: $PID)"
else
echo " Skipping stale /tmp/rqworker.pid (PID $PID is not rqworker)"
fi
fi
rm -f /tmp/rqworker.pid
fi
if pgrep -f "python.*rqworker" >/dev/null 2>&1; then
local ORPHAN_COUNT
ORPHAN_COUNT=$(pgrep -cf "python.*rqworker" 2>/dev/null || echo 0)
graceful_kill_pattern "python.*rqworker"
echo " Killed $ORPHAN_COUNT orphaned RQ worker(s)"
fi
if pgrep -f "python.*runserver.*8000" >/dev/null 2>&1; then
graceful_kill_pattern "python.*runserver.*8000"
echo " Killed orphaned NetBox server(s)"
fi
echo "✅ All processes stopped"
}
netbox-restart() {
netbox-stop && sleep 1 && netbox-run-bg
}
netbox-reload() {
cd "$PLUGIN_DIR" || return 1
if command -v uv >/dev/null 2>&1; then
uv pip install -e . || return 1
else
pip install -e . || return 1
fi
netbox-restart
}
alias netbox-logs="tail -f /tmp/netbox.log"
alias rq-logs="tail -f /tmp/rqworker.log"
netbox-status() {
local PID
if [ -f /tmp/netbox.pid ]; then
PID=$(cat /tmp/netbox.pid 2>/dev/null)
if [ -n "$PID" ] && is_expected_pid "$PID" "python.*runserver.*8000"; then
echo "NetBox is running (PID: $PID)"
else
echo "NetBox is not running"
fi
else
echo "NetBox is not running"
fi
if [ -f /tmp/rqworker.pid ]; then
PID=$(cat /tmp/rqworker.pid 2>/dev/null)
if [ -n "$PID" ] && is_expected_pid "$PID" "python.*rqworker"; then
echo "RQ worker is running (PID: $PID)"
else
echo "RQ worker is not running"
fi
else
echo "RQ worker is not running"
fi
}
rq-status() {
local PID
if [ -f /tmp/rqworker.pid ]; then
PID=$(cat /tmp/rqworker.pid 2>/dev/null)
if [ -n "$PID" ] && is_expected_pid "$PID" "python.*rqworker"; then
echo "RQ worker is running (PID: $PID)"
else
echo "RQ worker is not running"
fi
else
echo "RQ worker is not running"
fi
}
netbox-shell() {
cd /opt/netbox/netbox && source /opt/netbox/venv/bin/activate && python manage.py shell
}
netbox-test() {
cd "$PLUGIN_DIR" && source /opt/netbox/venv/bin/activate && python -m pytest "$@"
}
netbox-manage() {
cd /opt/netbox/netbox && source /opt/netbox/venv/bin/activate && python manage.py "$@"
}
plugin-install() {
cd "$PLUGIN_DIR" || return 1
if command -v uv >/dev/null 2>&1; then
uv pip install -e .
else
pip install -e .
fi
}
plugins-install() {
if [ -f "$PLUGIN_DIR/.devcontainer/extra-requirements.txt" ]; then
source /opt/netbox/venv/bin/activate && pip install -r "$PLUGIN_DIR/.devcontainer/extra-requirements.txt"
else
echo "No .devcontainer/extra-requirements.txt found"
fi
}
ruff-check() { cd "$PLUGIN_DIR" && command ruff check .; }
ruff-format() { cd "$PLUGIN_DIR" && command ruff format .; }
ruff-fix() { cd "$PLUGIN_DIR" && command ruff check --fix .; }
diagnose() { "$PLUGIN_DIR/.devcontainer/scripts/diagnose.sh"; }
# RQ job inspection commands
rq-stats() {
cd /opt/netbox/netbox && source /opt/netbox/venv/bin/activate && python manage.py rqstats
}
rq-jobs() {
cd /opt/netbox/netbox && source /opt/netbox/venv/bin/activate && python manage.py shell -c \
"from django_rq import get_queue; q = get_queue('default'); print(f'Jobs in queue: {len(q)}'); [print(f' {job.id[:8]}: {job.func_name} - {job.get_status()}') for job in q.jobs[:10]]"
}
rq-failed() {
cd /opt/netbox/netbox && source /opt/netbox/venv/bin/activate && python manage.py shell -c \
"from django_rq import get_failed_queue; q = get_failed_queue(); print(f'Failed jobs: {len(q)}'); [print(f' {job.id[:8]}: {job.func_name}') for job in q.jobs[:10]]"
}
rq-recent() {
cd /opt/netbox/netbox && source /opt/netbox/venv/bin/activate && python manage.py shell -c \
"from core.models import Job; jobs = Job.objects.all().order_by('-created')[:10]; [print(f'{j.id}: {j.name[:50]} - {getattr(j.status, \"value\", j.status)} ({j.user})') for j in jobs]"
}
# Help
dev-help() {
echo "🎯 NetBox LibreNMS Plugin Development Commands:"
echo ""
echo "📊 NetBox Server Management:"
echo " netbox-run-bg : Start NetBox in background"
echo " netbox-run : Start NetBox in foreground (for debugging)"
echo " netbox-stop : Stop NetBox and RQ worker"
echo " netbox-restart : Restart NetBox and RQ worker"
echo " netbox-reload : Reinstall plugin and restart NetBox"
echo " netbox-status : Check if NetBox and RQ worker are running"
echo " netbox-logs : View NetBox server logs"
echo ""
echo "⚙️ Background Jobs (RQ Worker):"
echo " rq-status : Check if RQ worker is running"
echo " rq-logs : View RQ worker logs"
echo " rq-stats : Show RQ queue statistics"
echo " rq-jobs : List jobs in default queue"
echo " rq-failed : List failed jobs"
echo " rq-recent : Show recent NetBox jobs"
echo ""
echo "🛠️ Development Tools:"
echo " netbox-shell : Open NetBox Django shell"
echo " netbox-test : Run plugin tests"
echo " netbox-manage : Run Django management commands"
echo " plugin-install : Reinstall plugin in development mode"
echo ""
echo "🧹 Code Quality:"
echo " ruff-check : Check code with Ruff"
echo " ruff-format : Format code with Ruff"
echo " ruff-fix : Auto-fix code issues with Ruff"
echo ""
echo "🔎 Diagnostics:"
echo " diagnose : Run startup diagnostics"
echo " dev-help : Show this help message"
echo ""
echo "📖 NetBox available at: http://localhost:8000 (admin/admin)"
}
echo "✅ Dev helpers loaded! Try: rq-status, rq-stats, rq-recent, dev-help"

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Shared process management helpers.
# Sourced by load-aliases.sh and start-netbox.sh.
# Graceful termination: SIGTERM, wait, then SIGKILL if still alive.
graceful_kill_pid() {
local pid="$1"
kill -15 "$pid" 2>/dev/null || true
sleep 2
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
}
graceful_kill_pattern() {
local pattern="$1"
pkill -15 -f "$pattern" 2>/dev/null || true
sleep 2
pgrep -f "$pattern" >/dev/null 2>&1 && pkill -9 -f "$pattern" 2>/dev/null || true
}
# Verify a PID matches the expected process before killing it
is_expected_pid() {
local pid="$1" pattern="$2"
ps -p "$pid" -o args= 2>/dev/null | grep -Eq "$pattern"
}

329
.devcontainer/scripts/setup.sh Executable file
View File

@@ -0,0 +1,329 @@
#!/bin/bash
set -e
echo "🚀 Setting up NetBox LibreNMS Plugin development environment..."
echo "📍 Current working directory: $(pwd)"
echo "👤 Current user: $(whoami)"
NETBOX_VERSION=${NETBOX_VERSION:-"latest"}
echo "📦 Using NetBox Docker image: netboxcommunity/netbox:${NETBOX_VERSION}"
# ---------------------------------------------------------------------------
# Detect plugin workspace directory (must contain pyproject.toml).
# Prints the resolved path to stdout on success, or an empty string on
# failure. Always exits 0 — callers must check for an empty result.
# ---------------------------------------------------------------------------
detect_plugin_workspace() {
if [ -f "$PWD/pyproject.toml" ]; then
echo "$PWD"
elif [ -d "/workspaces/netbox-librenms-plugin" ] && [ -f "/workspaces/netbox-librenms-plugin/pyproject.toml" ]; then
echo "/workspaces/netbox-librenms-plugin"
else
local candidate
candidate=$(find /workspaces -maxdepth 2 -type f -name pyproject.toml 2>/dev/null | head -n1 | xargs -r dirname || true)
if [ -n "$candidate" ] && [ -f "$candidate/pyproject.toml" ]; then
echo "$candidate"
else
echo ""
fi
fi
}
# Clean up empty CA bundle vars (Compose injects "" when host var is unset)
for _ca_var in REQUESTS_CA_BUNDLE SSL_CERT_FILE CURL_CA_BUNDLE; do
_val="${!_ca_var}"
[ -z "$_val" ] && unset "$_ca_var"
done
unset _ca_var _val
# Configure proxy for apt and pip if proxy environment variables are set
if [ -n "$HTTP_PROXY" ] || [ -n "$HTTPS_PROXY" ]; then
echo "🌐 Configuring proxy settings..."
# Configure apt proxy
if [ -n "$HTTP_PROXY" ]; then
echo "Acquire::http::Proxy \"$HTTP_PROXY\";" > /etc/apt/apt.conf.d/80proxy
SAFE_HTTP_PROXY=$(echo "$HTTP_PROXY" | sed 's|://[^@]*@|://***:***@|')
echo " ✓ apt HTTP proxy: $SAFE_HTTP_PROXY"
fi
if [ -n "$HTTPS_PROXY" ]; then
echo "Acquire::https::Proxy \"$HTTPS_PROXY\";" >> /etc/apt/apt.conf.d/80proxy
SAFE_HTTPS_PROXY=$(echo "$HTTPS_PROXY" | sed 's|://[^@]*@|://***:***@|')
echo " ✓ apt HTTPS proxy: $SAFE_HTTPS_PROXY"
fi
# Configure pip proxy via environment (already set, but ensure it's exported)
export HTTP_PROXY HTTPS_PROXY http_proxy https_proxy NO_PROXY no_proxy
# Install custom CA certificate into the system trust store (for MITM proxies)
PLUGIN_WS_DIR_EARLY="$(detect_plugin_workspace)"
[ -z "$PLUGIN_WS_DIR_EARLY" ] && PLUGIN_WS_DIR_EARLY="/workspaces/netbox-librenms-plugin"
CA_BUNDLE_SRC="$PLUGIN_WS_DIR_EARLY/ca-bundle.crt"
if [ -f "$CA_BUNDLE_SRC" ]; then
echo "🔐 Installing custom CA certificate into system trust store..."
cert_count=$(grep -c '-----BEGIN CERTIFICATE-----' "$CA_BUNDLE_SRC" 2>/dev/null || true)
if [ "${cert_count:-0}" -eq 0 ]; then
echo " ⚠️ ca-bundle.crt does not contain any PEM certificate blocks; skipping CA install."
else
mkdir -p /usr/local/share/ca-certificates/proxy
# Remove stale split fragments so they don't accumulate across rebuilds
find /usr/local/share/ca-certificates/proxy -maxdepth 1 -name 'cert-*' -delete 2>/dev/null || true
# Split the bundle into individual certs — update-ca-certificates needs one
# cert per file and skips non-CA leaf certs, so extract each PEM block as
# a separate .crt file.
csplit -z -f /usr/local/share/ca-certificates/proxy/cert- \
"$CA_BUNDLE_SRC" '/-----BEGIN CERTIFICATE-----/' '{*}' \
>/dev/null 2>&1
CSPLIT_STATUS=$?
if [ "$CSPLIT_STATUS" -ne 0 ]; then
echo " ⚠️ Failed to split ca-bundle.crt (csplit exit code: $CSPLIT_STATUS). Skipping CA install."
elif compgen -G "/usr/local/share/ca-certificates/proxy/cert-*" > /dev/null; then
# Rename split fragments to .crt
for f in /usr/local/share/ca-certificates/proxy/cert-*; do
mv "$f" "${f}.crt" 2>/dev/null || true
done
update-ca-certificates 2>/dev/null
echo " ✓ CA certificate installed into system trust store ($cert_count cert(s))"
else
echo " ⚠️ No certificate fragments were generated from ca-bundle.crt; skipping CA install."
fi
fi
# Point environment variables to the system bundle
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
export GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt
# Configure pip globally so isolated virtualenvs (e.g. pre-commit) also
# use the system CA bundle instead of their bundled certifi.
pip config set global.cert /etc/ssl/certs/ca-certificates.crt 2>/dev/null || true
else
echo " No ca-bundle.crt found at $CA_BUNDLE_SRC, skipping CA install"
# Only disable git SSL verification if explicitly opted-in via ALLOW_GIT_SSL_DISABLE.
# Silently disabling SSL is a security risk; prefer providing a CA bundle instead.
if [ "${ALLOW_GIT_SSL_DISABLE:-false}" = "true" ]; then
git config --global http.sslVerify false
echo " ⚠️ git SSL verification disabled globally (ALLOW_GIT_SSL_DISABLE=true)"
else
echo " ⚠️ No CA bundle found and git SSL verification was NOT disabled."
echo " If you need to disable it, set ALLOW_GIT_SSL_DISABLE=true in .devcontainer/.env"
echo " Preferred: provide a ca-bundle.crt in the workspace root instead."
fi
fi
fi
# Verify NetBox virtual environment exists
if [ ! -f "/opt/netbox/venv/bin/activate" ]; then
echo "❌ NetBox virtual environment not found at /opt/netbox/venv/"
echo "This might indicate an issue with the NetBox Docker image."
exit 1
fi
echo "🐍 Activating NetBox virtual environment..."
source /opt/netbox/venv/bin/activate
# Choose installer (uv if available, else pip)
if command -v uv >/dev/null 2>&1; then
PIP_CMD="uv pip"
else
PIP_CMD="pip"
fi
# Install dev tools
echo "🔧 Installing development dependencies..."
apt-get update -qq
apt-get install -y -qq net-tools git
$PIP_CMD install pytest pytest-django ruff pre-commit
# Install GitHub CLI (gh)
# NOTE: The chained && commands below mean a partial failure (e.g. wget succeeds
# but apt-get install gh fails) may leave artifacts (keyring, sources list, temp
# file). This is acceptable here because it only runs during container build —
# a rebuild will retry from scratch. If this block is ever moved to a runtime
# script, consider adding a trap or explicit cleanup on error.
if ! command -v gh >/dev/null 2>&1; then
echo "🔧 Installing GitHub CLI..."
(type -p wget >/dev/null || apt-get install -y -qq wget) \
&& install -d -m 755 /etc/apt/keyrings \
&& out=$(mktemp) \
&& wget -qO "$out" https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& cat "$out" | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& apt-get update -qq \
&& apt-get install -y -qq gh \
&& rm -f "$out" \
&& echo " ✓ GitHub CLI installed: $(gh --version | head -1)" \
|| echo "⚠️ GitHub CLI installation failed (non-fatal)"
fi
# Detect plugin workspace directory using the shared helper
PLUGIN_WS_DIR="$(detect_plugin_workspace)"
if [ -z "$PLUGIN_WS_DIR" ]; then
echo "❌ Could not locate plugin workspace directory (pyproject.toml not found)."
echo " Checked: $PWD and /workspaces/*"
exit 1
fi
echo "📂 Plugin workspace: $PLUGIN_WS_DIR"
# Install this plugin in development mode
echo "📦 Installing plugin in development mode from: $PLUGIN_WS_DIR"
if [ ! -f "$PLUGIN_WS_DIR/pyproject.toml" ] && [ ! -f "$PLUGIN_WS_DIR/setup.py" ]; then
echo "❌ Neither pyproject.toml nor setup.py found in $PLUGIN_WS_DIR"
ls -la "$PLUGIN_WS_DIR" || true
exit 2
fi
cd "$PLUGIN_WS_DIR"
$PIP_CMD install -e .
CONF_FILE="/opt/netbox/netbox/netbox/configuration.py"
# Optional extras
if [ -f "$PLUGIN_WS_DIR/.devcontainer/extra-requirements.txt" ]; then
echo "📦 Installing extra packages from extra-requirements.txt..."
$PIP_CMD install -r "$PLUGIN_WS_DIR/.devcontainer/extra-requirements.txt"
fi
# Inject plugin loader into standard NetBox configuration if present
if [ -f "$CONF_FILE" ]; then
if ! grep -q "# Devcontainer Plugins Loader" "$CONF_FILE" 2>/dev/null; then
{
echo "";
echo "# Devcontainer Plugins Loader";
echo "# Import PLUGINS/PLUGINS_CONFIG and optional extras dynamically from the workspace";
echo "import importlib.util, os";
echo "PLUGINS = ['netbox_librenms_plugin']";
echo "PLUGINS_CONFIG = {'netbox_librenms_plugin': {}}";
echo "_pc_path = '$PLUGIN_WS_DIR/.devcontainer/config/plugin-config.py'";
echo "if os.path.isfile(_pc_path):";
echo " _spec = importlib.util.spec_from_file_location('workspace_plugin_config', _pc_path)";
echo " _mod = importlib.util.module_from_spec(_spec)";
echo " try:";
echo " _spec.loader.exec_module(_mod) # type: ignore[attr-defined]";
echo " PLUGINS = getattr(_mod, 'PLUGINS', PLUGINS)";
echo " PLUGINS_CONFIG = getattr(_mod, 'PLUGINS_CONFIG', PLUGINS_CONFIG)";
echo " except Exception as e:";
echo " print(f'⚠️ Failed to load plugin-config.py: {e}')";
echo "else:";
echo " print(' plugin-config.py not found; using defaults')";
echo "# Import optional extra NetBox configuration (uppercase settings)";
echo "_xc_path = '$PLUGIN_WS_DIR/.devcontainer/config/extra-configuration.py'";
echo "if os.path.isfile(_xc_path):";
echo " _xc_spec = importlib.util.spec_from_file_location('workspace_extra_configuration', _xc_path)";
echo " _xc_mod = importlib.util.module_from_spec(_xc_spec)";
echo " try:";
echo " _xc_spec.loader.exec_module(_xc_mod) # type: ignore[attr-defined]";
echo " for _name in dir(_xc_mod):";
echo " if _name.isupper():";
echo " globals()[_name] = getattr(_xc_mod, _name)";
echo " except Exception as e:";
echo " print(f'⚠️ Failed to apply extra-configuration.py: {e}')";
echo "# Import Codespaces configuration when applicable (uppercase settings)";
echo "_cs_path = '$PLUGIN_WS_DIR/.devcontainer/config/codespaces-configuration.py'";
echo "if os.environ.get('CODESPACES') == 'true' and os.path.isfile(_cs_path):";
echo " _cs_spec = importlib.util.spec_from_file_location('workspace_codespaces_configuration', _cs_path)";
echo " _cs_mod = importlib.util.module_from_spec(_cs_spec)";
echo " try:";
echo " _cs_spec.loader.exec_module(_cs_mod) # type: ignore[attr-defined]";
echo " for _name in dir(_cs_mod):";
echo " if _name.isupper():";
echo " globals()[_name] = getattr(_cs_mod, _name)";
echo " except Exception as e:";
echo " print(f'⚠️ Failed to apply codespaces-configuration.py: {e}')";
echo "# Ensure SECRET_KEY exists: prefer environment, fallback to a dev placeholder";
echo "if 'SECRET_KEY' not in globals() or not SECRET_KEY:";
echo " SECRET_KEY = os.environ.get('SECRET_KEY', 'dummydummydummydummydummydummydummydummydummydummydummydummy')";
} >> "$CONF_FILE"
fi
if grep -q "netbox_librenms_plugin" "$CONF_FILE" 2>/dev/null; then
echo "✅ Plugin configuration exists in NetBox settings"
fi
else
echo "⚠️ Warning: $CONF_FILE not found"
echo "Plugin configuration may need to be added manually"
fi
# Run migrations and collectstatic
cd /opt/netbox/netbox
# Wait briefly for DB (compose healthchecks should ensure availability)
export DEBUG="${DEBUG:-True}"
echo "🗃️ Applying database migrations..."
python manage.py migrate 2>&1 | grep -E "(Operations to perform|Running migrations|Apply all migrations|No migrations to apply|\s+Applying|\s+OK)" || true
echo "🔐 Creating superuser (if not exists)..."
echo " Credentials are read from environment variables (see .devcontainer/.env)"
python manage.py shell -c "
import os
from django.contrib.auth import get_user_model
User = get_user_model()
username = (os.environ.get('SUPERUSER_NAME') or '').strip() or 'admin'
email = (os.environ.get('SUPERUSER_EMAIL') or '').strip() or 'admin@example.com'
password = (os.environ.get('SUPERUSER_PASSWORD') or '').strip() or 'admin'
if not User.objects.filter(username=username).exists():
User.objects.create_superuser(username, email, password)
print(f'Created superuser: {username}')
else:
print(f'Superuser {username} already exists')
" 2>/dev/null || true
echo "📊 Collecting static files..."
python manage.py collectstatic --noinput >/dev/null 2>&1 || true
# Set up pre-commit hooks
echo "🪝 Installing pre-commit hooks..."
cd "$PLUGIN_WS_DIR"
git config --global --add safe.directory "$PLUGIN_WS_DIR"
pre-commit install --install-hooks 2>/dev/null || echo "⚠️ Pre-commit hook installation failed (may already be installed)"
# Ensure scripts are executable
chmod +x "$PLUGIN_WS_DIR/.devcontainer/scripts/start-netbox.sh" || true
chmod +x "$PLUGIN_WS_DIR/.devcontainer/scripts/diagnose.sh" || true
chmod +x "$PLUGIN_WS_DIR/.devcontainer/scripts/load-aliases.sh" || true
# Load aliases and welcome message from the canonical source (load-aliases.sh).
# Appended to .bashrc so every interactive shell gets them automatically.
# Guard with a sentinel so rerunning setup.sh doesn't create duplicate entries.
BASHRC_SENTINEL="# NetBox LibreNMS Plugin — source aliases from the single canonical file"
if ! grep -qF "$BASHRC_SENTINEL" ~/.bashrc 2>/dev/null; then
cat >> ~/.bashrc << EOF
$BASHRC_SENTINEL
source "$PLUGIN_WS_DIR/.devcontainer/scripts/load-aliases.sh"
# Show welcome message for new terminals
bash "$PLUGIN_WS_DIR/.devcontainer/scripts/welcome.sh"
EOF
fi
# Fix Git remote URLs for dev container compatibility
echo "🔧 Checking Git remote configuration..."
cd "$PLUGIN_WS_DIR"
CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
if [[ "$CURRENT_REMOTE" == git@github.com:* ]]; then
# Convert SSH URL to HTTPS for dev container compatibility
HTTPS_URL=$(echo "$CURRENT_REMOTE" | sed 's|git@github.com:|https://github.com/|')
git remote set-url origin "$HTTPS_URL"
echo "✅ Converted Git remote from SSH to HTTPS: $HTTPS_URL"
echo " This ensures compatibility with GitHub CLI authentication in dev containers"
elif [[ "$CURRENT_REMOTE" == https://github.com/* ]]; then
echo "✅ Git remote already uses HTTPS: $CURRENT_REMOTE"
else
echo " Git remote URL: $CURRENT_REMOTE (no changes needed)"
fi
# Final validation
cd /opt/netbox/netbox
if python -c "import netbox_librenms_plugin; print('✅ Plugin import successful')" 2>/dev/null | grep -q "✅ Plugin import successful"; then
echo "✅ Plugin is properly installed and importable"
else
echo "⚠️ Warning: Plugin may not be properly installed"
fi
echo ""
echo "🚀 NetBox LibreNMS Plugin Dev Environment Ready!"

View File

@@ -0,0 +1,107 @@
#!/bin/bash
# Check if we should run in background or foreground
BACKGROUND=false
if [ "$1" = "--background" ] || [ "$1" = "-b" ]; then
BACKGROUND=true
fi
echo "🌐 Starting NetBox development server..."
# Set required environment variables
export DEBUG="${DEBUG:-True}"
# Detect Codespaces and set access URL
if [ "$CODESPACES" = "true" ] && [ -n "$CODESPACE_NAME" ]; then
GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN="${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:-app.github.dev}"
ACCESS_URL="https://${CODESPACE_NAME}-8000.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
echo "🔗 GitHub Codespaces detected"
else
ACCESS_URL="http://localhost:8000"
echo "🐛 Debug: ACCESS_URL is set to: $ACCESS_URL"
fi
# Load shared process management helpers
if ! source "$(dirname "$0")/process-helpers.sh"; then
echo "ERROR: Failed to load process-helpers.sh" >&2
exit 1
fi
# Kill any orphaned processes (not tracked by PID file)
echo "🧹 Cleaning up orphaned processes..."
if pgrep -f "python.*rqworker" >/dev/null 2>&1; then
echo " Found orphaned RQ workers, killing..."
graceful_kill_pattern "python.*rqworker"
fi
if pgrep -f "python.*runserver.*8000" >/dev/null 2>&1; then
echo " Found orphaned NetBox servers, killing..."
graceful_kill_pattern "python.*runserver.*8000"
fi
# Stop any tracked processes from PID files
if [ -f /tmp/netbox.pid ]; then
OLD_PID=$(cat /tmp/netbox.pid 2>/dev/null)
if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
if is_expected_pid "$OLD_PID" "python.*runserver.*8000"; then
graceful_kill_pid "$OLD_PID"
else
echo "⚠️ Skipping stale /tmp/netbox.pid (PID $OLD_PID is not NetBox runserver)"
fi
fi
rm -f /tmp/netbox.pid
fi
if [ -f /tmp/rqworker.pid ]; then
OLD_PID=$(cat /tmp/rqworker.pid 2>/dev/null)
if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
if is_expected_pid "$OLD_PID" "python.*rqworker"; then
graceful_kill_pid "$OLD_PID"
else
echo "⚠️ Skipping stale /tmp/rqworker.pid (PID $OLD_PID is not rqworker)"
fi
fi
rm -f /tmp/rqworker.pid
fi
# Activate NetBox virtual environment
source /opt/netbox/venv/bin/activate
# Navigate to NetBox directory
cd /opt/netbox/netbox
# Start RQ worker in background
echo "⚙️ Starting RQ worker..."
(
source /opt/netbox/venv/bin/activate
cd /opt/netbox/netbox
python manage.py rqworker --verbosity=1
) > /tmp/rqworker.log 2>&1 &
RQ_PID=$!
echo $RQ_PID > /tmp/rqworker.pid
echo "✅ RQ worker started (PID: $RQ_PID)"
if [ "$BACKGROUND" = true ]; then
echo "🚀 Starting NetBox in background"
(
export DEBUG="${DEBUG:-True}"
source /opt/netbox/venv/bin/activate
cd /opt/netbox/netbox
python manage.py runserver 0.0.0.0:8000 --verbosity=0
) > /tmp/netbox.log 2>&1 &
NETBOX_PID=$!
echo $NETBOX_PID > /tmp/netbox.pid
echo "✅ NetBox started in background (PID: $NETBOX_PID)"
echo "📍 Access NetBox at: $ACCESS_URL"
echo "💡 If clicking the URL opens 0.0.0.0:8000, manually type: localhost:8000"
echo "📄 View logs with: netbox-logs"
echo "🛑 Stop NetBox with: netbox-stop"
else
echo "🌍 Starting NetBox in foreground"
echo "📍 Access NetBox at: $ACCESS_URL"
echo "💡 If clicking the URL opens 0.0.0.0:8000, manually type: localhost:8000"
echo ""
python manage.py runserver 0.0.0.0:8000
fi

View File

@@ -0,0 +1,56 @@
#!/bin/bash
# Ensure aliases are available in the postAttach terminal session
source "$(dirname "$0")/load-aliases.sh" 2>/dev/null
echo ""
echo "🎯 NetBox LibreNMS Plugin Development Environment"
PLUGIN_WS_DIR="${PLUGIN_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
if [ ! -f "$PLUGIN_WS_DIR/.devcontainer/config/plugin-config.py" ]; then
echo ""
echo "⚠️ Plugin configuration not found: .devcontainer/config/plugin-config.py"
echo " Create it first: cp .devcontainer/config/plugin-config.py.example .devcontainer/config/plugin-config.py"
echo " Then edit it and set your plugin values (e.g. LibreNMS server URL/token)"
fi
# Check GitHub CLI authentication status
echo ""
if command -v gh >/dev/null 2>&1; then
if gh auth status >/dev/null 2>&1; then
# Get the authenticated user info
GH_USER=$(gh api user --jq '.login' 2>/dev/null || echo "unknown")
echo "✅ GitHub authenticated as: $GH_USER"
echo " Git is configured for GitHub operations"
else
echo "🔑 GitHub CLI available but not authenticated"
echo " Run 'gh auth login' to authenticate with GitHub"
echo " This will automatically configure Git for pushing/pulling"
fi
else
echo "⚠️ GitHub CLI not available"
fi
echo ""
if [ -n "$CODESPACES" ]; then
echo "🌐 GitHub Codespaces Environment:"
echo " NetBox will be available via automatic port forwarding"
echo " Check the 'Ports' panel for the forwarded port labeled 'NetBox Web Interface'"
if [ -n "$CODESPACE_NAME" ]; then
# Try to construct the likely URL (GitHub Codespaces pattern)
CODESPACE_URL="https://${CODESPACE_NAME}-8000.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:-preview.app.github.dev}"
echo " Expected URL: $CODESPACE_URL"
fi
echo " 💡 Click the link in the Ports panel or look for the 'Open in Browser' button"
else
echo "🖥️ Local Development Environment:"
echo " NetBox will be available at: http://localhost:8000 (paste into you browser)"
fi
echo ""
echo "🚀 Quick start:"
echo " • Type 'netbox-run' to start the development server"
echo " • Type 'netbox-restart' to restart NetBox (after config changes)"
echo " • Type 'dev-help' to see all available commands"
echo " • Edit code in the workspace - auto-reload is enabled"
echo ""