1147Moving Custom Domains to iCloud Mail

Web Hosting Services like pair.com used to provide free email hosting since 1997 - along with their other services - but in 2025 they moved to their new Pair Platinum Mailbox, which means mailboxes are no longer free at pair.com

Prices are $2.99/month for 1 mailbox, discounts apply when you use more mailboxes. For example, have 10 mailboxes would cost $25.00/month.

I have one high-frequency mailbox and several low-frequency ones - like info@ or hello@. One solution could be to create aliases and routh all the mails to one mailbox.

Another option is to use Custom Domains and iCloud+. iCloud+ allow up to 5 custom domains, with up to 3 email addresses per domain.

Instructions: iCloud

  1. Go to your iCloud+ Features page.
  2. Click Custom Email Domain → Add a domain you own.
  3. iCloud will show instructions and private values to update the custom DNS record of your domain name registrar.

Instructions: pair.com

  1. In the pair.com ACC, navigate to domainsyour domainCustom DNS Records.
  2. Add the new DNS Entries:

MX

Type: MX
Host: @
Value: mx01.mail.icloud.com.
Priority: 10

What "A" Record is this MX for? → Host
What preference should this MX have? → Priority
What is the full host name of the mailserver? → Value

TXT

Type: TXT
Host: @
Value: apple-domain=12345

Hostname: → Host
Text Record: → Value

SPF

SPF is set as type TXT

Type: TXT
Host: @
Value: "v=spf1 include:icloud.com ~all"

Add the values string as is creates an error on the pair.com side. Remove the quotation marks.

DKIM

SPF is set as type CNAME

Type: CNAME
Host: sig1._domainkey
Value: sig1.dkim.your.domain.at.icloudmailadmin.com.

Hostname: → Host
Address: → Value

Finish

It can take up to 72 hours to propage the DNS settings. Keep checking the iCloud settings, your domain will show up there once it's done.

pair.com's SPF and DKIM can remain inactive.

1140Merging Data and Automated Workflows in a Affinity Studio

Tested with Affinity Studio April '26 (4425) on MacOS 14.8.3

Problem: How to automate Catalogue Production in Affinity

Creating a catalogue involves copying and pasting assests, including texts, captions, names and images from data sources into Affinity. If the data source change, that process had to be repeated manually.

Solution: Data Merge!

Affinity offers the Data Merge feature which allows for the importing of structured data and the automatic generation of pages.

Overview of the Data Merge Process

  1. Create a source data file, in our example case we use Google Sheets to make a spreadsheet, which can be exported as CSV. Other source data types: Plain Text, CSV, TSV, JSON, XLSX

  2. Create a Affinity Layout document, design the fields you want to be populated with data from the CSV file. Images can be imported from URLs or from folder relative to the main Affinity document.

  3. In Affinity, use the from Window → Layout → Data Merge panel to import the CSV file and bind the data to the visual elements.

  4. Generate a new Affinity document based to the design & data source.

1. Creating the Data Source

Any structured data source can be used, we will be using Google Sheets, as it allows for collaborative editing. Data from Google Sheets can be exported as CSV by doing File → Download → Comma-separated values (.csv).

2. Importing the CSV in Affintiy

2.1. Opening the Data Merge Panel

The Data Merge Panel can be found in Window → Layout → Data Merge.

2.2. Adding the Data File

Clicking the Add Data File... Button opens up a dialogue, which allows you to upload your CSV file

2.3. Successful Import

Once imported, you can step through - and if necessary post-process - the data.

2.4. Binding Data Fields to Design Elements

This is where the magic happends:

  • Select the place where you want the data from the data field inserted. This can be within a textbox or in case of images, in the picture frame.

  • Select which field you want to bind. In our example CSV file, we only have Name, Image and Text.

2.5. Stepping through and Preview

Use the left/right arrows (◀︎ ▶︎) to step trough the data and see how it fits your design.

2.6. Generate

Once you are happy, press the Generate Button. This will take your design as a template and merge it with the data file, creating a new Affinity document. That means, that when the data changes, and data merge is run again, another new document will be created.

Files

Challenges

Can a generated document be updated when the data source changes?

No. Each time the Generate Button in the Data Merge panel is clicked a new document is generated.

Can I import text styles from the CSV file?

No, that's not possible. However, each field can be given the desired styles. In the case of longer text with embedded styles, a workaround it to use Markdown syntax to indicate **bold** and _italic_ in the source file and then do Find & Replace in the Generated Document.

1134Folder Actions in macOS

Folder Actions are the hidden superpowert in macOS. They are triggered when files are added to a folder, they then can execute an action.

The Action can be written in Automator, AppleScript or Javascript.

They are great for repetitive actions.

Problem: I have a Google Sheet that I am exporting as CSV, which in turn is used to contruct a table within Hugo. Downloading the CSV from Sheets puts it on the Desktop.

This action checks the Desktop, if it finds the file, it moves it.

Writing the Action in Automator

Filter Finder Items makes sure only the desired files are affected.

Setting up Folder Action

  • Select your Folder
  • Right-click and say Services -> Folder Actions Setup...

Choose a Script to attach.

Now whenever a file matching the criteria is added to the folder, the Folder Action is executed.

1132Hugo – Minimal Breadcrumbs

A minimal version of breadcrumbs in Hugo, using linkTitle:

{{- range .Ancestors.Reverse }}
  <a href="{{ .RelPermalink }}">{{ if .IsHome }}{{ "Home" }}{{ else }}{{ .LinkTitle }}{{ end }}</a> →
{{- end }}
  <a class="current" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>

Even more minimal:

{{- range .Ancestors.Reverse }}
  <a href="{{ .RelPermalink }}">{{ .LinkTitle }}{{ end }}</a> →
  <a class="current" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>

For this to work, make sure to add linkTitle to the FrontMatter:

+++
title     = 'Really long and winding title'
linkTitle = 'short'
draft     = false
+++

...

If linkTitle is not present, title is used instead.

1128is_int & is_numeric and in PHP

is_int returns true only if the variable is an integer.

If you want to check is a variable has a numeric value, use is_numeric.


$a = 123;
$b = "123;

is_int($a); // true
is_int($b); // false

is_numeric($a); // true
is_numeric($b); // false

1126Downloading a Website from archive.org

Sometimes it is necessary to download an archival copy of a complete website, luckily https://web.archive.org crawls the interwebs regularly. Unluckily there is no direct way to download a whole website directly.

One tool that facilitates this is the aptley named Wayback Machine Downloader:

Usage

wayback_machine_downloader https://example.com

I have been running into some issues with the original version:

Downloading https://www.example.com to websites/www.example.com/ 
from Wayback Machine archives.

Getting snapshot pages../Users/abc/.rbenv/versions/3.1.1/lib/
ruby/3.1.0/open-uri.rb:364: in `open_http': 400 BAD REQUEST
(OpenURI::HTTPError)
...

This fork is working:

wayback_machine_downloader https://example.com

Downloading https://example.com to websites/example.com/
from Wayback Machine archives.
Getting snapshot pages from Wayback Machine API...
.. found 6350 snapshots.
Saved snapshot list to websites/example.com/.cdx.json
...

If you want to download only to a certain timestamp, use the --to switch:

wayback_machine_downloader --to 20241231235959 https://example.com

1120Setting Memory/Buffer Bool for MariaDB

  • OS: Ubuntu
  • DB: MariaDB

innodb_buffer_pool_size defaults to 128M which might not be enought if you have a big database.

MariaDB Documentation: Configure the InnoDB Buffer Pool Size

0. Log in with root via SSH

1. Check Buffer Size

mysql
mysql>
MariaDB>SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.001 sec)

That's 128M default. 128 * 1024 * 1024

2. Add a MariaDB configuration file

cd /etc/mysql/mariadb.conf.d
ls -l

-rw-r--r-- 1 root root  575 Feb 19 00:56 50-client.cnf
-rw-r--r-- 1 root root  231 Feb 19 00:56 50-mysql-clients.cnf
-rw-r--r-- 1 root root  927 Feb 19 00:56 50-mysqld_safe.cnf
-rw-r--r-- 1 root root 3769 Feb 19 00:56 50-server.cnf
-rw-r--r-- 1 root root  570 Feb 19 00:56 60-galera.cnf

nano z-custom-mariadb.cnf

[mysqld]
innodb_buffer_pool_size=2G

3. Restart DB Server

sudo systemctl restart mariadb

Troubleshooting

Q. I updated the value, but the value is not changed on the server.
A. Check that your configuration file ends in .cnf and not in .conf - otherwise it won't be read.

1118Minimal Multilang

Language Switcher

EN JA AT

Language Divs

This is content is English.
このコンテンツは日本語です。

State saved in Cookie

Try to change the language and reload the page.
<style>
a        {color: #00f; background: #fff; text-decoration: none; }
a:hover  { color: #fff; background: #00f; }
a:active { color: #00f; background: #00f;}
.active { color: #000; background: #ff0;}
.hidden { display: none; }
</style>

<script>
// Supported languages, first is default
const languages = ['en', 'ja', 'at']

// Read activeLang from Cookie OR init with 'en'
var activeLang = document.cookie.split("; ").find((row) => row.startsWith("lang="))?.split("=")[1] || languages[0];

// Set activeLang & Cookie
function setLang(l) {
  document.cookie = `lang=${l}`
  activeLang = l
  showLang(l)
}

// Show/hide all elements with activeLang class
const languageClasses = languages.map(e=>'.'+e)
const languageIDs = languages.map(e=>'#'+e)
function showLang(l) {
  document.querySelectorAll(languageIDs).forEach(e => e.classList.remove('active'))
  document.querySelectorAll('#'+l).forEach(e => e.classList.add('active'))
  document.querySelectorAll(languageClasses).forEach(e => e.classList.add('hidden'))
  document.querySelectorAll('.'+l).forEach(e => e.classList.remove('hidden'))
}

// Document Ready
document.addEventListener("DOMContentLoaded", (e) => {
  showLang(activeLang)
})
</script>

<div id="lang">
 <a href="" onClick="setLang('ja');return false" id="ja">JA</a>
 <a href="" onClick="setLang('en');return false" id="en">EN</a>
 <a href="" onClick="setLang('at');return false" id="at">AT</a>
</div>

11177z as a Compression/Decompression Utility

7z is an alternative to zip

Create an archive a montior compression progress:

7z a archive.zip my-folder/

a stands for add

Snapshot of the compression progress:

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=C,Utf16=off,HugeFiles=on,64 bits,1 CPU QEMU Virtual CPU version 1.0 (623),ASM)

Scanning the drive:
1004 folders, 136557 files, 6612288441 bytes (6306 MiB)                        

Creating archive: archive.zip

Items to compress: 137561

 98% 133273 + file/ . /01/12345.jpg   

7z also give you a nice ensurance, that everything is OK, when it's done.

Files read from disk: 136557
Archive size: 6118548821 bytes (5836 MiB)
Everything is Ok

1116Enables SSH/SFTP Access via Plesk

When setting a up a new server via Plesk on a Debian System, one of the first tasks is to enable SSH/SFTP users.

By default & security SSH permissions are off.

ssh user@host.name
Permission denied (publickey).

To change that, log into the server as su - via the Plesk SSH Interface - and update the settings in /etc/ssh/sshd_config

Also a good idea to make a backup, before changing anyting.

cd /etc/ssh/
cp sshd_config sshd_config.back

Change PasswordAuthentication and KbdInteractiveAuthentication to yes

nano sshd_config
...
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication yes
#PermitEmptyPasswords no

# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
KbdInteractiveAuthentication yes
...

The SSH daemon/service needs to be restarted:

sudo systemctl restart ssh

SSH/SFTP log-in should now work!