Web development projects often require printable reports or invoices, but relying on the browser's built-in print function is a frustrating experience.
Results vary with operating systems and render engines, leading to frequent bugs and broken layouts.
Worse, fixing them risks degrading other platforms.
Puppetteer, the successor to PhantomJS, solves these issues by bundling its own Chromium browser.
It supports pixel-perfect PDF generation and respects CSS page margins, allowing full control over the PDF page layout.
My htmlpdf.js command-line tool automates HTML to PDF conversion:
Bluetooth devices have become ubiquitous, whether it's headphones, heart rate monitors, smart watches, or light bulbs. While many of them come with mobile apps for control and configuration, sometimes it's convenient to access them from a computer instead. If this is not supported out of the box, recording the Bluetooth traffic is useful in order to create a custom tool.
Helpfully, newer versions of Wireshark include an androiddump utility to capture Bluetooth traffic directly from Android phones. The setup couldn't be simpler: connect the phone to the computer via USB, enable USB debugging, select the "Android Bluetooth" capture source in Wireshark, and start recording!
Android also has a function to log all traffic into a file, which can later be copied to a computer for analysis in Wireshark. This is similar to creating a tcpdump of TCP/IP traffic, just for Bluetooth. Unfortunately, the location of this log file changes between Android versions, and access seems to require root in recent versions. One reliable method I found was:
Run adb root to gain root-level access to the phone
Run adb bugreport bugreport.out to create a bug report ZIP file. Either this contains the Bluetooth log file directly (search for a btsnoop_hci.log), or the full path of the log file (grep the bug report for btsnoop_hci.log)
If the log was not contained in the bug report, get it via adb pull <full_path_to_logfile>
The last years have seen a rapid rise in the number of web sites that support a
"dark mode". Some pages offer an explicit light/dark switch. But typically the
selection is based on the browsers "prefers-color-scheme" CSS selector. It is
surprisingly difficult to change this browser default without switching the whole
Follow the instructions below to switch to dark mode.
The color scheme preference of your browser is not setlightdark.
For Chrome, the instructions depend on the system it is running on.
In Chrome, open the top-right "..." menu and go to "Settings"
There is no direct way to enable dark mode only for Chrome on iOS. You have
to change the whole device to iOS via "Settings" → "Display &
Chrome switches into dark mode when it is started with the --force-dark-mode command line flag.
Close all Chrome instances
Shift-Right click the Chrome shortcut in the taskbar or on the desktop
In the "Shortcut" tab, append --force-dark-mode to the "Target" field
Close the dialog with "OK"
Restart Chrome with that shortcut
Safari doesn't have a separate setting for dark mode. It always follows the operating system setting.
Changing the system setting on iOS
Open "Display & Brightness"
Changing the system setting on MacOS
Open the system settings in the Apple menu
Open the "General" dialog
Select the "Dark" appearance
Firefox has hidden configuration option that enables dark mode:
Type about:config into the address bar and press Enter
Type ui.systemUsesDarkTheme into the search bar
The search will not find anything, but allow you to add a new preference with that name
Set the property type to "Number" and click the "+" button to create:
Enter the value "1" to enable dark mode and click the check mark to save:
Administrating an LDAP server may be hard — using it doesn't have to be.
LDAP servers commonly provide auth services to web servers, mail servers, web apps, and so on.
To do this, the LDAP database stores user and group membership information. The combination of these two datasets
allows both authentication (is the user who they claim to be?) and
authorization (is the user in a group that has permission to perform a specific action?).
Thus, LDAP enables central management of user, group, and permission information for any number of services.
So what does an LDAP database consist of?
An LDAP database contains a hierarchical data structure, similar to a directory tree.
Each tree node is called an entity.
LDAP doesn't distinguish between files and directories.
Entities often contain both child entities — like a directory — as well as attributes, which are similar to a file's content.
Each entity has a distinguished name (DN), which is the entity's absolute
pathname in LDAP tree. The elements of the pathname are called
relative distinguished names (RDNs).
These concepts are pretty similar to filesystem directory trees. The key differences are:
Directory separators: LDAP uses , instead of /
RDN format: RDNs are typically key-value-pairs, instead of simple
strings: uid=ca instead of Desktop. Commonly used keys
are dc, o, ou, uid.
Parent nodes are on the right: so it's dc=child,dc=parent instead of
Consequently, DNs usually look like this: uid=ca,ou=people,dc=caichinger,dc=com
Entities have attributes, which store the entity's data, similar to a file's contents. Each attribute has a type that
describes the attribute's data structure, as well as one or more values
containing the attribute's information. Additionally, each attribute can have
options — a rarely used feature for distinguishing different versions (e.g. English, German) of the same attribute.
Entities also have associated object classes, which are conceptually
similar to attribute types. But whereas types describe attributes, object
classes describe which attributes must be found within the entity.
Both attribute types and entity object classes are metadata: they describe the database's schema. Each of these metadata objects has an OID.
Aside from the schema definition, OIDs are also used for other
database-specific metadata, such as identifying extended requests and responses.
OIDs are denoted as dot-separated numbers, e.g. 1.2.840.1234567890, but often
have human-readable names assigned as well.
What actions can be performed over the LDAP protocol?
Binding: authenticating to the LDAP server — essentially "logging in". Since most servers don't allow un-authenticated querying, this is required before performing any other actions.
Many servers also support re-authentication as a different user over existing
connections: this is known as re-binding.
Searching: querying the existing LDAP directory tree, and listing its information.
Add, modify, and delete: altering the LDAP directory tree.
Many others, often including custom commands.
Querying an LDAP server is straight-forward with the ldapsearch tool:
-h ldap.caichinger.com # LDAP server name
-D "uid=ca,ou=people,dc=caichinger,dc=com" # Authenticate as uid=caichinger
-W # Ask for password for uid=caichinger
-b "ou=people,dc=caichinger,dc=com" # Base search path
(uid=caichinger) # Search expression
The -D and -W switches tell ldapsearch as which user to authenticate as.
The -b switch defines the "base directory" where the search should start. The
search expression is then applied to all entities under this directory tree.
The server response will then contain all matching users, as well as their
If you have any questions after this whirlwind-tour of LDAP, please leave a comment!
Every now and then we need to batch-convert timestamps. The date command
shipped on Linux distributions does this nicely:
date "+%c" --date=@1539561600
I recently ran into a similar problem when logfiles contained Unix
timestamps instead of human-readable dates. Using date seemed a bit clumsy
here. Fortunately, Superuser.com had a nice solution involving
Vim. The following sequence converts the timestamp under the cursor and
records a macro q to facilitate future conversions:
qq " start recording
"mciw " put time in register m and replace it…
<C-r>=strftime("%c", @m)<CR> " …with localized datetime
<Esc> " exit insert mode
q " stop recording
Quick and convenient — and easily incorporated into a macro to convert timestamps across the entire file.