I'm managing a netbox version 3.6.9. The version is quite old, however, we're running it privately with only a few humans having access and without the need for any new features. Recently, the netbox branching plugin was announced and now we're in the process of upgrading.
We use reports (this links to the docs of version 3.7 because I could not find 3.6 docs) for formulating a few invariants which we want to abide to. E.g. naming conventions, duplicate address and prefix detection, etc.
We know that reports are being deprecated by netbox version 4 and users are advised to convert them to scripts. This is why we set up a staging version running 4.4.10 and developed a way to run our reports with both
The result is a sort of compatibility layer which you can use in your reports file:
from extras.scripts import Script # TODO Report may become unavailable at some point because it is deprecated in netbox 4 from extras.reports import Report from netbox.settings import VERSION import inspect # most of the strange code in here is for implementing compatibility with netbox Reports and # Script through a wide range of netbox versions from 3.6.9 to 4.4.10. We initially # implemented both Reports and Scripts to be used with netbox. We started using reports as # they produce more structured output. Netbox 4 deprecated the Report but retained backwards # compatibility, however even the Script class logging methods have changed between Netbox 3 # and 4: log(message) -> log(msg=message, obj=o). With the current netbox, the scripts remain # as the only possible method and dramatically improved readability in the mean time. # Everything is good :) Except for this abstraction. # figure out if we're supposed to provide Script or Report BaseModel = None for s in inspect.stack(): if s.filename.endswith('extras/models/scripts.py'): # we're called through netbox scripts BaseModel = Script break elif s.filename.endswith('extras/models/reports.py'): # we're called through netbox reports BaseModel = Report break if not BaseModel: # we're called through migrations if int(VERSION.split('.')[0]) <= 3: BaseModel = Report else: BaseModel = Script class CustomBase(BaseModel): """ provide our abstraction for implementation of the log_* methods for the three different types. Counting all combinations of Report and Script during their lifetimes, log_* methods may have been called with several argument options (log* means log_info, log_warning, etc.): log*(msg) used by Scripts with netbox <= 3 log*(obj) used by Reports log*(obj, msg) used by Reports log*(msg=msg, obj=obj) Scripts with netbox >= 4 log*(obj=obj) Scripts with netbox >= 4 log*(msg, obj) Scripts with netbox >= 4 use this signature: log*(msg=msg, obj=obj) reading the documentation for Scripts, msg may be None for log_success. """ # _log is already defined from Scripts and Reports (made that mistake). def __log(self, typ, *args, **kwargs): method = getattr(super(), f'log_{typ}') if isinstance(self, Report): # for Report, log requires obj, message obj = kwargs.get('obj', args[1] if len(args) == 2 else None) msg = kwargs.get('msg', args[0] if len(args) >= 1 else None) method(obj, msg) elif isinstance(self, Script): if int(VERSION.split('.')[0]) <= 3: # pre 4 versions used only a single string method(' '.join([ str(x) for x in list(args) + list(kwargs.values()) ])) else: # post 4 use msg=msg, obj=obj method(*args, **kwargs) def log_debug(self, *args, **kwargs): self.__log('debug', *args, **kwargs) def log_success(self, *args, **kwargs): self.__log('success', *args, **kwargs) def log_info(self, *args, **kwargs): self.__log('info', *args, **kwargs) def log_warning(self, *args, **kwargs): self.__log('warning', *args, **kwargs) def log_failure(self, *args, **kwargs): self.__log('failure', *args, **kwargs) def run(self, *args, **kwargs): """ only netbox 3.x scripts require the run method but since we define it here, we need to cover the two other cases: reports and scripts with netbox >= 4 """ if isinstance(self, Report): super().run(*args, **kwargs) return elif VERSION.startswith('3.'): # Netbox3 Script really needs the run() method and raises an exception otherwise if (method := getattr(self, 'pre_run', None)): # only run if pre_run exists method() for name, method in inspect.getmembers(self, predicate=inspect.ismethod): if not name.startswith('test_'): continue method() if (method := getattr(self, 'post_run', None)): # only run if post_run exists method() else: super().run(*args, **kwargs) # start your reporting classes here
It's not pretty but it works.
Howto use it:
Either place it within your file or create a own file e.g. custom_base.py and create it
alongside your main file(s) as either report or script.
Use the logging signature as
log*(msg=msg, obj=obj)
Where log* is one of the logging functions.
This very short post describes how to unlock Debian Bullseye with natively encrypted ZFS-on-Root. I'm assuming dropbear is installed and configured. No special configuration option is required.
Connect to the dropbear server and issue
zfsunlock
TIL the hard way how to configure grub to automatically install bootcode on all drives. Bootcode needs to be present on at least one drive. During package and operating system upgrades, the process of installing/updating bootcode is crucial if you expect the next system boot to be successful.
For software RAID configurations, such as mdadm or zfs, the bootcode must be installed on
all 'boot' drives (i.e. drives which contain your / or /boot file systems) to avoid boot
failures because
Grub provides a way to install bootcode manually. This method is well known and used in most tutorials as well as during Debian system installation:
grub-install /dev/sda grub-install /dev/sdb
but there is also a way of performing the task automatically (at least on Debian):
dpkg-reconfigure grub-pc
By interactively selecting all boot disks, you lower your chances of boot failure, however, we need to make sure to run the either one of the commands after replacing a disk.
In 2017, I had discovered Warren Zevon and am listening to his music ever since. In fact, I had Frank and Jesse James transcribed as piano sheet music by mysheetmusictranscriptions.com. MySheetMusicTranscriptions granted me permission to share it within a small community for private (non-commercial) use only.
Have you ever used ps aux | grep ... on GNU/Linux to grep for a running process? Have you
also used ps aux to view the whole process list on your terminal? At first glance, the two
commands behave exactly the same, however ps will truncate every line to fit within your
terminal's witdh of echo $COLUMNS (see the man page ps(1)). If you want wide output
(132 columns), specify another w e.g. ps auxw. If you want unlimited output, specify yet
another w e.g. ps auxww.
This post will explain how to set up IPv6 connectivity via a initramfs script to remotely unlock your root partition on a server which uses a technology stack of dropbear (to be included in the intramfs) and cryptsetup via IPv6 and IPv6 only. Unlocking your root partition with this workflow is less secure than using the out-of-band management if you consider unattended hardware access of an attacker to your device as probable.
/etc/dropbear-initramfs to /etc/dropbear/initramfs and Thorsten Glaser inspired me to
use his scripts for creating a repo
initramfs-ipv6. I'm currently working on
using a lacp bond for unlocking, stay tuned.I am assuming you are successfully using dropbear to remotely unlock your root partition using cryptsetup via legacy-ip (IPv4).
Accessing the internet through an always-on VPN full tunnel could be considered standard user behaviour these days. Things start to get annoying when the network (provider) limits VPN usage. For me, the most prominent case was an Eduroam wifi at a german university which grants only the absolute minimum of network access to its users, as required by the Eduroam specification. Here's the full list of mandatory (as in RFC MUST) unblocked ports (see page 32):
You are using linux with an IPv6 unique local address (ULA) but no IPv6 unicast address
assigned to the interface but use them e.g. in a VPN context to provide IPv6 unicast
connectivity through NAT. The same interface is also set up as the default route. This causes
the default configuration of getaddrinfo(3) with ai_flags set to include AI_ADDRCONFIG
to prefer IPv4 because missing IPv6 connectivity is assumed.